mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
Add an option to not preserve user collections, but instead have them controlled by calibre metadata.
Make the tags_list_editor show the old name when renaming
This commit is contained in:
parent
a7e20ef517
commit
ee97153ce4
@ -6,7 +6,6 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
|||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import os, time
|
import os, time
|
||||||
from pprint import pprint
|
|
||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
@ -179,8 +178,8 @@ class XMLCache(object):
|
|||||||
path = record.get('path', None)
|
path = record.get('path', None)
|
||||||
if path:
|
if path:
|
||||||
if path not in playlist_map:
|
if path not in playlist_map:
|
||||||
playlist_map[path] = set()
|
playlist_map[path] = []
|
||||||
playlist_map[path].add(title)
|
playlist_map[path].append(title)
|
||||||
debug_print('Finish build_id_playlist_map. Found', len(playlist_map))
|
debug_print('Finish build_id_playlist_map. Found', len(playlist_map))
|
||||||
return playlist_map
|
return playlist_map
|
||||||
|
|
||||||
@ -309,14 +308,14 @@ class XMLCache(object):
|
|||||||
book.thumbnail = raw
|
book.thumbnail = raw
|
||||||
break
|
break
|
||||||
break
|
break
|
||||||
book.device_collections = list(playlist_map.get(book.lpath, set()))
|
book.device_collections = playlist_map.get(book.lpath, [])
|
||||||
debug_print('Finished updating JSON cache:', bl_index)
|
debug_print('Finished updating JSON cache:', bl_index)
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
# Update XML from JSON {{{
|
# Update XML from JSON {{{
|
||||||
def update(self, booklists, collections_attributes):
|
def update(self, booklists, collections_attributes):
|
||||||
debug_print('In update. Starting update XML from JSON')
|
debug_print('Starting update', collections_attributes)
|
||||||
for i, booklist in booklists.items():
|
for i, booklist in booklists.items():
|
||||||
playlist_map = self.build_id_playlist_map(i)
|
playlist_map = self.build_id_playlist_map(i)
|
||||||
debug_print('Updating XML Cache:', i)
|
debug_print('Updating XML Cache:', i)
|
||||||
@ -332,8 +331,7 @@ class XMLCache(object):
|
|||||||
# this book
|
# this book
|
||||||
if book.device_collections is None:
|
if book.device_collections is None:
|
||||||
book.device_collections = []
|
book.device_collections = []
|
||||||
book.device_collections = list(set(book.device_collections) |
|
book.device_collections = playlist_map.get(book.lpath, [])
|
||||||
playlist_map.get(book.lpath, set()))
|
|
||||||
self.update_playlists(i, root, booklist, collections_attributes)
|
self.update_playlists(i, root, booklist, collections_attributes)
|
||||||
# Update the device collections because update playlist could have added
|
# Update the device collections because update playlist could have added
|
||||||
# some new ones.
|
# some new ones.
|
||||||
@ -341,10 +339,9 @@ class XMLCache(object):
|
|||||||
for i, booklist in booklists.items():
|
for i, booklist in booklists.items():
|
||||||
playlist_map = self.build_id_playlist_map(i)
|
playlist_map = self.build_id_playlist_map(i)
|
||||||
for book in booklist:
|
for book in booklist:
|
||||||
book.device_collections = list(set(book.device_collections) |
|
book.device_collections = playlist_map.get(book.lpath, [])
|
||||||
playlist_map.get(book.lpath, set()))
|
|
||||||
self.fix_ids()
|
self.fix_ids()
|
||||||
debug_print('Finished update XML from JSON')
|
debug_print('Finished update')
|
||||||
|
|
||||||
def rebuild_collections(self, booklist, bl_index):
|
def rebuild_collections(self, booklist, bl_index):
|
||||||
if bl_index not in self.record_roots:
|
if bl_index not in self.record_roots:
|
||||||
|
@ -11,6 +11,7 @@ from calibre.devices.mime import mime_type_ext
|
|||||||
from calibre.devices.interface import BookList as _BookList
|
from calibre.devices.interface import BookList as _BookList
|
||||||
from calibre.constants import filesystem_encoding, preferred_encoding
|
from calibre.constants import filesystem_encoding, preferred_encoding
|
||||||
from calibre import isbytestring
|
from calibre import isbytestring
|
||||||
|
from calibre.utils.config import prefs
|
||||||
|
|
||||||
class Book(MetaInformation):
|
class Book(MetaInformation):
|
||||||
|
|
||||||
@ -76,7 +77,7 @@ class Book(MetaInformation):
|
|||||||
in C{other} takes precedence, unless the information in C{other} is NULL.
|
in C{other} takes precedence, unless the information in C{other} is NULL.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
MetaInformation.smart_update(self, other)
|
MetaInformation.smart_update(self, other, replace_tags=True)
|
||||||
|
|
||||||
for attr in self.BOOK_ATTRS:
|
for attr in self.BOOK_ATTRS:
|
||||||
if hasattr(other, attr):
|
if hasattr(other, attr):
|
||||||
@ -132,7 +133,9 @@ class CollectionsBookList(BookList):
|
|||||||
def get_collections(self, collection_attributes):
|
def get_collections(self, collection_attributes):
|
||||||
collections = {}
|
collections = {}
|
||||||
series_categories = set([])
|
series_categories = set([])
|
||||||
collection_attributes = list(collection_attributes)+['device_collections']
|
collection_attributes = list(collection_attributes)
|
||||||
|
if prefs['preserve_user_collections']:
|
||||||
|
collection_attributes += ['device_collections']
|
||||||
for attr in collection_attributes:
|
for attr in collection_attributes:
|
||||||
attr = attr.strip()
|
attr = attr.strip()
|
||||||
for book in self:
|
for book in self:
|
||||||
|
@ -268,7 +268,7 @@ class MetaInformation(object):
|
|||||||
):
|
):
|
||||||
prints(x, getattr(self, x, 'None'))
|
prints(x, getattr(self, x, 'None'))
|
||||||
|
|
||||||
def smart_update(self, mi):
|
def smart_update(self, mi, replace_tags=False):
|
||||||
'''
|
'''
|
||||||
Merge the information in C{mi} into self. In case of conflicts, the information
|
Merge the information in C{mi} into self. In case of conflicts, the information
|
||||||
in C{mi} takes precedence, unless the information in mi is NULL.
|
in C{mi} takes precedence, unless the information in mi is NULL.
|
||||||
@ -291,7 +291,10 @@ class MetaInformation(object):
|
|||||||
setattr(self, attr, val)
|
setattr(self, attr, val)
|
||||||
|
|
||||||
if mi.tags:
|
if mi.tags:
|
||||||
self.tags += mi.tags
|
if replace_tags:
|
||||||
|
self.tags = mi.tags
|
||||||
|
else:
|
||||||
|
self.tags += mi.tags
|
||||||
self.tags = list(set(self.tags))
|
self.tags = list(set(self.tags))
|
||||||
|
|
||||||
if mi.author_sort_map:
|
if mi.author_sort_map:
|
||||||
|
@ -45,6 +45,7 @@ class AddSave(QTabWidget, Ui_TabWidget):
|
|||||||
self.metadata_box.layout().insertWidget(0, self.filename_pattern)
|
self.metadata_box.layout().insertWidget(0, self.filename_pattern)
|
||||||
self.opt_swap_author_names.setChecked(prefs['swap_author_names'])
|
self.opt_swap_author_names.setChecked(prefs['swap_author_names'])
|
||||||
self.opt_add_formats_to_existing.setChecked(prefs['add_formats_to_existing'])
|
self.opt_add_formats_to_existing.setChecked(prefs['add_formats_to_existing'])
|
||||||
|
self.preserve_user_collections.setChecked(prefs['preserve_user_collections'])
|
||||||
help = '\n'.join(textwrap.wrap(c.get_option('template').help, 75))
|
help = '\n'.join(textwrap.wrap(c.get_option('template').help, 75))
|
||||||
self.save_template.initialize('save_to_disk', opts.template, help)
|
self.save_template.initialize('save_to_disk', opts.template, help)
|
||||||
self.send_template.initialize('send_to_device', opts.send_template, help)
|
self.send_template.initialize('send_to_device', opts.send_template, help)
|
||||||
@ -71,6 +72,7 @@ class AddSave(QTabWidget, Ui_TabWidget):
|
|||||||
prefs['filename_pattern'] = pattern
|
prefs['filename_pattern'] = pattern
|
||||||
prefs['swap_author_names'] = bool(self.opt_swap_author_names.isChecked())
|
prefs['swap_author_names'] = bool(self.opt_swap_author_names.isChecked())
|
||||||
prefs['add_formats_to_existing'] = bool(self.opt_add_formats_to_existing.isChecked())
|
prefs['add_formats_to_existing'] = bool(self.opt_add_formats_to_existing.isChecked())
|
||||||
|
prefs['preserve_user_collections'] = bool(self.preserve_user_collections.isChecked())
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
<item row="2" column="0" colspan="2">
|
<item row="2" column="0" colspan="2">
|
||||||
<widget class="QCheckBox" name="opt_add_formats_to_existing">
|
<widget class="QCheckBox" name="opt_add_formats_to_existing">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>If an existing book with a similar title and author is found that does not have the format being added, the format is added
|
<string>If an existing book with a similar title and author is found that does not have the format being added, the format is added
|
||||||
to the existing book, instead of creating a new entry. If the existing book already has the format, then it is silently ignored.
|
to the existing book, instead of creating a new entry. If the existing book already has the format, then it is silently ignored.
|
||||||
|
|
||||||
Title match ignores leading indefinite articles ("the", "a", "an"), punctuation, case, etc. Author match is exact.</string>
|
Title match ignores leading indefinite articles ("the", "a", "an"), punctuation, case, etc. Author match is exact.</string>
|
||||||
@ -179,7 +179,31 @@ Title match ignores leading indefinite articles ("the", "a",
|
|||||||
</attribute>
|
</attribute>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label_4">
|
<widget class="QCheckBox" name="preserve_user_collections">
|
||||||
|
<property name="text">
|
||||||
|
<string>Preserve user collections.</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_41">
|
||||||
|
<property name="text">
|
||||||
|
<string>If checked, collections will not be deleted even if a book with changed metadata is resent and the collection is not in the book's metadata. In addition, editing collections on the device view will be enabled.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_42">
|
||||||
|
<property name="text">
|
||||||
|
<string> </string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_43">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Here you can control how calibre will save your books when you click the Send to Device button. This setting can be overriden for individual devices by customizing the device interface plugins in Preferences->Plugins</string>
|
<string>Here you can control how calibre will save your books when you click the Send to Device button. This setting can be overriden for individual devices by customizing the device interface plugins in Preferences->Plugins</string>
|
||||||
</property>
|
</property>
|
||||||
|
@ -7,6 +7,36 @@ from PyQt4.QtGui import QDialog, QListWidgetItem
|
|||||||
from calibre.gui2.dialogs.tag_list_editor_ui import Ui_TagListEditor
|
from calibre.gui2.dialogs.tag_list_editor_ui import Ui_TagListEditor
|
||||||
from calibre.gui2 import question_dialog, error_dialog
|
from calibre.gui2 import question_dialog, error_dialog
|
||||||
|
|
||||||
|
class ListWidgetItem(QListWidgetItem):
|
||||||
|
|
||||||
|
def __init__(self, txt):
|
||||||
|
QListWidgetItem.__init__(self, txt)
|
||||||
|
self.old_value = txt
|
||||||
|
self.cur_value = txt
|
||||||
|
|
||||||
|
def data(self, role):
|
||||||
|
if role == Qt.DisplayRole:
|
||||||
|
if self.old_value != self.cur_value:
|
||||||
|
return _('%s (was %s)'%(self.cur_value, self.old_value))
|
||||||
|
else:
|
||||||
|
return self.cur_value
|
||||||
|
elif role == Qt.EditRole:
|
||||||
|
return self.cur_value
|
||||||
|
else:
|
||||||
|
return QListWidgetItem.data(self, role)
|
||||||
|
|
||||||
|
def setData(self, role, data):
|
||||||
|
if role == Qt.EditRole:
|
||||||
|
self.cur_value = data.toString()
|
||||||
|
QListWidgetItem.setData(self, role, data)
|
||||||
|
|
||||||
|
def text(self):
|
||||||
|
return self.cur_value
|
||||||
|
|
||||||
|
def setText(self, txt):
|
||||||
|
self.cur_value = txt
|
||||||
|
QListWidgetItem.setText(txt)
|
||||||
|
|
||||||
class TagListEditor(QDialog, Ui_TagListEditor):
|
class TagListEditor(QDialog, Ui_TagListEditor):
|
||||||
|
|
||||||
def __init__(self, window, tag_to_match, data, compare):
|
def __init__(self, window, tag_to_match, data, compare):
|
||||||
@ -21,7 +51,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
|
|||||||
for k,v in data:
|
for k,v in data:
|
||||||
self.all_tags[v] = k
|
self.all_tags[v] = k
|
||||||
for tag in sorted(self.all_tags.keys(), cmp=compare):
|
for tag in sorted(self.all_tags.keys(), cmp=compare):
|
||||||
item = QListWidgetItem(tag)
|
item = ListWidgetItem(tag)
|
||||||
item.setData(Qt.UserRole, self.all_tags[tag])
|
item.setData(Qt.UserRole, self.all_tags[tag])
|
||||||
self.available_tags.addItem(item)
|
self.available_tags.addItem(item)
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ from calibre.gui2 import NONE, config, UNDEFINED_QDATE
|
|||||||
from calibre.utils.pyparsing import ParseException
|
from calibre.utils.pyparsing import ParseException
|
||||||
from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors
|
from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.utils.config import tweaks
|
from calibre.utils.config import tweaks, prefs
|
||||||
from calibre.utils.date import dt_factory, qt_to_dt, isoformat
|
from calibre.utils.date import dt_factory, qt_to_dt, isoformat
|
||||||
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
|
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
|
||||||
from calibre.utils.search_query_parser import SearchQueryParser
|
from calibre.utils.search_query_parser import SearchQueryParser
|
||||||
@ -928,11 +928,12 @@ class DeviceBooksModel(BooksModel): # {{{
|
|||||||
if index.isValid() and self.editable:
|
if index.isValid() and self.editable:
|
||||||
cname = self.column_map[index.column()]
|
cname = self.column_map[index.column()]
|
||||||
if cname in ('title', 'authors') or \
|
if cname in ('title', 'authors') or \
|
||||||
(cname == 'collections' and self.db.supports_collections()):
|
(cname == 'collections' and \
|
||||||
|
self.db.supports_collections() and \
|
||||||
|
prefs['preserve_user_collections']):
|
||||||
flags |= Qt.ItemIsEditable
|
flags |= Qt.ItemIsEditable
|
||||||
return flags
|
return flags
|
||||||
|
|
||||||
|
|
||||||
def search(self, text, reset=True):
|
def search(self, text, reset=True):
|
||||||
if not text or not text.strip():
|
if not text or not text.strip():
|
||||||
self.map = list(range(len(self.db)))
|
self.map = list(range(len(self.db)))
|
||||||
|
@ -15,7 +15,7 @@ from calibre.gui2.library.delegates import RatingDelegate, PubDateDelegate, \
|
|||||||
TextDelegate, DateDelegate, TagsDelegate, CcTextDelegate, \
|
TextDelegate, DateDelegate, TagsDelegate, CcTextDelegate, \
|
||||||
CcBoolDelegate, CcCommentsDelegate, CcDateDelegate
|
CcBoolDelegate, CcCommentsDelegate, CcDateDelegate
|
||||||
from calibre.gui2.library.models import BooksModel, DeviceBooksModel
|
from calibre.gui2.library.models import BooksModel, DeviceBooksModel
|
||||||
from calibre.utils.config import tweaks
|
from calibre.utils.config import tweaks, prefs
|
||||||
from calibre.gui2 import error_dialog, gprefs
|
from calibre.gui2 import error_dialog, gprefs
|
||||||
from calibre.gui2.library import DEFAULT_SORT
|
from calibre.gui2.library import DEFAULT_SORT
|
||||||
|
|
||||||
@ -500,7 +500,9 @@ class DeviceBooksView(BooksView): # {{{
|
|||||||
self.setAcceptDrops(False)
|
self.setAcceptDrops(False)
|
||||||
|
|
||||||
def contextMenuEvent(self, event):
|
def contextMenuEvent(self, event):
|
||||||
self.edit_collections_menu.setVisible(self._model.db.supports_collections())
|
self.edit_collections_menu.setVisible(
|
||||||
|
self._model.db.supports_collections() and \
|
||||||
|
prefs['preserve_user_collections'])
|
||||||
self.context_menu.popup(event.globalPos())
|
self.context_menu.popup(event.globalPos())
|
||||||
event.accept()
|
event.accept()
|
||||||
|
|
||||||
|
@ -473,6 +473,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{
|
|||||||
self.search_restriction.setEnabled(False)
|
self.search_restriction.setEnabled(False)
|
||||||
for action in list(self.delete_menu.actions())[1:]:
|
for action in list(self.delete_menu.actions())[1:]:
|
||||||
action.setEnabled(False)
|
action.setEnabled(False)
|
||||||
|
# Reset the view in case something changed while it was invisible
|
||||||
|
self.current_view().reset()
|
||||||
self.set_number_of_books_shown()
|
self.set_number_of_books_shown()
|
||||||
|
|
||||||
|
|
||||||
|
@ -698,6 +698,8 @@ def _prefs():
|
|||||||
# calibre server can execute searches
|
# calibre server can execute searches
|
||||||
c.add_opt('saved_searches', default={}, help=_('List of named saved searches'))
|
c.add_opt('saved_searches', default={}, help=_('List of named saved searches'))
|
||||||
c.add_opt('user_categories', default={}, help=_('User-created tag browser categories'))
|
c.add_opt('user_categories', default={}, help=_('User-created tag browser categories'))
|
||||||
|
c.add_opt('preserve_user_collections', default=True,
|
||||||
|
help=_('Preserve all collections even if not in library metadata.'))
|
||||||
|
|
||||||
c.add_opt('migrated', default=False, help='For Internal use. Don\'t modify.')
|
c.add_opt('migrated', default=False, help='For Internal use. Don\'t modify.')
|
||||||
return c
|
return c
|
||||||
|
Loading…
x
Reference in New Issue
Block a user