diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py
index 42d0f3c863..6394626a9f 100644
--- a/src/calibre/devices/usbms/books.py
+++ b/src/calibre/devices/usbms/books.py
@@ -132,6 +132,8 @@ class CollectionsBookList(BookList):
return True
def get_collections(self, collection_attributes):
+ from calibre.devices.usbms.driver import debug_print
+ debug_print('Starting get_collections:', prefs['manage_device_metadata'])
collections = {}
series_categories = set([])
# This map of sets is used to avoid linear searches when testing for
@@ -146,14 +148,19 @@ class CollectionsBookList(BookList):
# book in all existing collections. Do not add any new ones.
attrs = ['device_collections']
if getattr(book, '_new_book', False):
- if prefs['preserve_user_collections']:
+ if prefs['manage_device_metadata'] == 'manual':
# Ensure that the book is in all the book's existing
# collections plus all metadata collections
attrs += collection_attributes
else:
- # The book's existing collections are ignored. Put the book
- # in collections defined by its metadata.
+ # For new books, both 'on_send' and 'on_connect' do the same
+ # thing. The book's existing collections are ignored. Put
+ # the book in collections defined by its metadata.
attrs = collection_attributes
+ elif prefs['manage_device_metadata'] == 'on_connect':
+ # For existing books, modify the collections only if the user
+ # specified 'on_connect'
+ attrs = collection_attributes
for attr in attrs:
attr = attr.strip()
val = getattr(book, attr, None)
diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py
index 377ec36c16..73a329be58 100644
--- a/src/calibre/devices/usbms/driver.py
+++ b/src/calibre/devices/usbms/driver.py
@@ -58,7 +58,7 @@ class USBMS(CLI, Device):
debug_print ('USBMS: Fetching list of books from device. oncard=', oncard)
- dummy_bl = BookList(None, None, None)
+ dummy_bl = self.booklist_class(None, None, None)
if oncard == 'carda' and not self._card_a_prefix:
self.report_progress(1.0, _('Getting list of books on device...'))
@@ -78,6 +78,8 @@ class USBMS(CLI, Device):
self.EBOOK_DIR_CARD_B if oncard == 'cardb' else \
self.get_main_ebook_dir()
+ debug_print ('USBMS: dirs are:', prefix, ebook_dirs)
+
# get the metadata cache
bl = self.booklist_class(oncard, prefix, self.settings)
need_sync = self.parse_metadata_cache(bl, prefix, self.METADATA_CACHE)
diff --git a/src/calibre/ebooks/epub/fix/epubcheck.py b/src/calibre/ebooks/epub/fix/epubcheck.py
index f5c8086e7c..fd913a654b 100644
--- a/src/calibre/ebooks/epub/fix/epubcheck.py
+++ b/src/calibre/ebooks/epub/fix/epubcheck.py
@@ -21,7 +21,7 @@ class Epubcheck(ePubFixer):
def long_description(self):
return _('Workarounds for bugs in the latest release of epubcheck. '
'epubcheck reports many things as errors that are not '
- 'actually errors. %prog will try to detect these and replace '
+ 'actually errors. epub-fix will try to detect these and replace '
'them with constructs that epubcheck likes. This may cause '
'significant changes to your epub, complain to the epubcheck '
'project.')
diff --git a/src/calibre/ebooks/epub/fix/unmanifested.py b/src/calibre/ebooks/epub/fix/unmanifested.py
index 71913e9d50..da7a9a9d0e 100644
--- a/src/calibre/ebooks/epub/fix/unmanifested.py
+++ b/src/calibre/ebooks/epub/fix/unmanifested.py
@@ -18,7 +18,7 @@ class Unmanifested(ePubFixer):
@property
def long_description(self):
- return _('Fix unmanifested files. %prog can either add them to '
+ return _('Fix unmanifested files. epub-fix can either add them to '
'the manifest or delete them as specified by the '
'delete unmanifested option.')
diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py
index e2a99864ec..91afac8aa2 100644
--- a/src/calibre/gui2/device.py
+++ b/src/calibre/gui2/device.py
@@ -33,6 +33,7 @@ from calibre.devices.apple.driver import ITUNES_ASYNC
from calibre.devices.folder_device.driver import FOLDER_DEVICE
from calibre.ebooks.metadata.meta import set_metadata
from calibre.constants import DEBUG
+from calibre.utils.config import prefs
# }}}
@@ -1424,19 +1425,24 @@ class DeviceMixin(object): # {{{
aus = re.sub('(?u)\W|[_]', '', aus)
self.db_book_title_cache[title]['author_sort'][aus] = mi
self.db_book_title_cache[title]['db_ids'][mi.application_id] = mi
- self.db_book_uuid_cache[mi.uuid] = mi.application_id
+ self.db_book_uuid_cache[mi.uuid] = mi
# Now iterate through all the books on the device, setting the
# in_library field Fastest and most accurate key is the uuid. Second is
# the application_id, which is really the db key, but as this can
# accidentally match across libraries we also verify the title. The
# db_id exists on Sony devices. Fallback is title and author match
+
+ update_metadata = prefs['manage_device_metadata'] == 'on_connect'
for booklist in booklists:
for book in booklist:
if getattr(book, 'uuid', None) in self.db_book_uuid_cache:
+ if update_metadata:
+ book.smart_update(self.db_book_uuid_cache[book.uuid])
book.in_library = True
# ensure that the correct application_id is set
- book.application_id = self.db_book_uuid_cache[book.uuid]
+ book.application_id = \
+ self.db_book_uuid_cache[book.uuid].application_id
continue
book_title = book.title.lower() if book.title else ''
@@ -1446,11 +1452,13 @@ class DeviceMixin(object): # {{{
if d is not None:
if getattr(book, 'application_id', None) in d['db_ids']:
book.in_library = True
- book.smart_update(d['db_ids'][book.application_id])
+ if update_metadata:
+ book.smart_update(d['db_ids'][book.application_id])
continue
if book.db_id in d['db_ids']:
book.in_library = True
- book.smart_update(d['db_ids'][book.db_id])
+ if update_metadata:
+ book.smart_update(d['db_ids'][book.db_id])
continue
if book.authors:
# Compare against both author and author sort, because
@@ -1459,14 +1467,19 @@ class DeviceMixin(object): # {{{
book_authors = re.sub('(?u)\W|[_]', '', book_authors)
if book_authors in d['authors']:
book.in_library = True
- book.smart_update(d['authors'][book_authors])
+ if update_metadata:
+ book.smart_update(d['authors'][book_authors])
elif book_authors in d['author_sort']:
book.in_library = True
- book.smart_update(d['author_sort'][book_authors])
+ if update_metadata:
+ book.smart_update(d['author_sort'][book_authors])
# Set author_sort if it isn't already
asort = getattr(book, 'author_sort', None)
if not asort and book.authors:
book.author_sort = self.library_view.model().db.author_sort_from_authors(book.authors)
+ if update_metadata:
+ if self.device_manager.is_device_connected:
+ self.device_manager.sync_booklists(None, booklists)
# }}}
diff --git a/src/calibre/gui2/dialogs/config/add_save.py b/src/calibre/gui2/dialogs/config/add_save.py
index b1f5621f44..8eb6cf7bd0 100644
--- a/src/calibre/gui2/dialogs/config/add_save.py
+++ b/src/calibre/gui2/dialogs/config/add_save.py
@@ -45,7 +45,12 @@ class AddSave(QTabWidget, Ui_TabWidget):
self.metadata_box.layout().insertWidget(0, self.filename_pattern)
self.opt_swap_author_names.setChecked(prefs['swap_author_names'])
self.opt_add_formats_to_existing.setChecked(prefs['add_formats_to_existing'])
- self.preserve_user_collections.setChecked(prefs['preserve_user_collections'])
+ if prefs['manage_device_metadata'] == 'manual':
+ self.manage_device_metadata.setCurrentIndex(0)
+ elif prefs['manage_device_metadata'] == 'on_send':
+ self.manage_device_metadata.setCurrentIndex(1)
+ else:
+ self.manage_device_metadata.setCurrentIndex(2)
help = '\n'.join(textwrap.wrap(c.get_option('template').help, 75))
self.save_template.initialize('save_to_disk', opts.template, help)
self.send_template.initialize('send_to_device', opts.send_template, help)
@@ -72,12 +77,14 @@ class AddSave(QTabWidget, Ui_TabWidget):
prefs['filename_pattern'] = pattern
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['preserve_user_collections'] = bool(self.preserve_user_collections.isChecked())
-
+ if self.manage_device_metadata.currentIndex() == 0:
+ prefs['manage_device_metadata'] = 'manual'
+ elif self.manage_device_metadata.currentIndex() == 1:
+ prefs['manage_device_metadata'] = 'on_send'
+ else:
+ prefs['manage_device_metadata'] = 'on_connect'
return True
-
-
if __name__ == '__main__':
from PyQt4.Qt import QApplication
app=QApplication([])
diff --git a/src/calibre/gui2/dialogs/config/add_save.ui b/src/calibre/gui2/dialogs/config/add_save.ui
index 64a8137aa1..80df2d80ca 100644
--- a/src/calibre/gui2/dialogs/config/add_save.ui
+++ b/src/calibre/gui2/dialogs/config/add_save.ui
@@ -6,7 +6,7 @@
0
0
- 588
+ 953
516
@@ -177,32 +177,37 @@ Title match ignores leading indefinite articles ("the", "a",
Sending to &device
-
- -
-
+
+
-
+
- Preserve device collections.
+ Metadata &management:
+
+
+ manage_device_metadata
- -
+
-
- 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 in the device view will be enabled. If unchecked, collections will be always reflect only the metadata in the calibre library.
+ <li><b>Manual Management</b>: Calibre updates the metadata and adds collections only when a book is sent. With this option, calibre will never remove a collection.</li>
+<li><b>Only on send</b>: Calibre updates metadata and adds/removes collections for a book only when it is sent to the device. </li>
+<li><b>Automatic management</b>: Calibre automatically keeps metadata on the device in sync with the calibre library, on every connect</li></ul>
true
- -
+
-
-
+
- -
+
-
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
@@ -212,9 +217,28 @@ Title match ignores leading indefinite articles ("the", "a",
- -
+
-
+ -
+
+
-
+
+ Manual management
+
+
+ -
+
+ Only on send
+
+
+ -
+
+ Automatic management
+
+
+
+
diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py
index 9a4bb22f82..2d038d9ddc 100644
--- a/src/calibre/gui2/init.py
+++ b/src/calibre/gui2/init.py
@@ -409,7 +409,8 @@ class StatusBar(QStatusBar): # {{{
self.clearMessage()
def message_changed(self, msg):
- if not msg or msg.isEmpty() or msg.isNull():
+ if not msg or msg.isEmpty() or msg.isNull() or \
+ not unicode(msg).strip():
extra = ''
if self.device_string:
extra = ' ..::.. ' + self.device_string
diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py
index f3b650f531..c44efa2354 100644
--- a/src/calibre/gui2/layout.py
+++ b/src/calibre/gui2/layout.py
@@ -8,12 +8,13 @@ __docformat__ = 'restructuredtext en'
from PyQt4.Qt import QIcon, Qt, QWidget, QAction, QToolBar, QSize, QVariant, \
QAbstractListModel, QFont, QApplication, QPalette, pyqtSignal, QToolButton, \
QModelIndex, QListView, QAbstractButton, QPainter, QPixmap, QColor, \
- QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QComboBox
+ QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout
from calibre.constants import __appname__, filesystem_encoding
from calibre.gui2.search_box import SearchBox2, SavedSearchBox
from calibre.gui2.throbber import ThrobbingButton
from calibre.gui2 import NONE
+from calibre.gui2.widgets import ComboBoxWithHelp
from calibre import human_readable
class ToolBar(QToolBar): # {{{
@@ -280,12 +281,7 @@ class SearchBar(QWidget): # {{{
self._layout = l = QHBoxLayout()
self.setLayout(self._layout)
- self.restriction_label = QLabel(_("&Restrict to:"))
- l.addWidget(self.restriction_label)
- self.restriction_label.setSizePolicy(QSizePolicy.Minimum,
- QSizePolicy.Minimum)
-
- x = QComboBox(self)
+ x = ComboBoxWithHelp(self)
x.setMaximumSize(QSize(150, 16777215))
x.setObjectName("search_restriction")
x.setToolTip(_("Books display will be restricted to those matching the selected saved search"))
@@ -344,7 +340,6 @@ class SearchBar(QWidget): # {{{
x.setToolTip(_("Delete current saved search"))
self.label.setBuddy(parent.search)
- self.restriction_label.setBuddy(parent.search_restriction)
# }}}
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index 3bbab52b33..9f1a72b021 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -944,7 +944,7 @@ class DeviceBooksModel(BooksModel): # {{{
(cname == 'collections' and \
callable(getattr(self.db, 'supports_collections', None)) and \
self.db.supports_collections() and \
- prefs['preserve_user_collections']):
+ prefs['manage_device_metadata']=='manual'):
flags |= Qt.ItemIsEditable
return flags
diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py
index 9d85dce075..c6c32f86f7 100644
--- a/src/calibre/gui2/library/views.py
+++ b/src/calibre/gui2/library/views.py
@@ -503,7 +503,7 @@ class DeviceBooksView(BooksView): # {{{
self.edit_collections_menu.setVisible(
callable(getattr(self._model.db, 'supports_collections', None)) and \
self._model.db.supports_collections() and \
- prefs['preserve_user_collections'])
+ prefs['manage_device_metadata'] == 'manual')
self.context_menu.popup(event.globalPos())
event.accept()
diff --git a/src/calibre/gui2/search_restriction_mixin.py b/src/calibre/gui2/search_restriction_mixin.py
index 3a71fa3de0..f677c839d8 100644
--- a/src/calibre/gui2/search_restriction_mixin.py
+++ b/src/calibre/gui2/search_restriction_mixin.py
@@ -7,7 +7,8 @@ Created on 10 Jun 2010
class SearchRestrictionMixin(object):
def __init__(self):
- self.search_restriction.activated[str].connect(self.apply_search_restriction)
+ self.search_restriction.initialize(help_text=_('Restrict to'))
+ self.search_restriction.activated[int].connect(self.apply_search_restriction)
self.library_view.model().count_changed_signal.connect(self.restriction_count_changed)
self.search_restriction.setSizeAdjustPolicy(self.search_restriction.AdjustToMinimumContentsLengthWithIcon)
self.search_restriction.setMinimumContentsLength(10)
@@ -27,8 +28,8 @@ class SearchRestrictionMixin(object):
if self.restriction_in_effect:
self.set_number_of_books_shown()
- def apply_search_restriction(self, r):
- r = unicode(r)
+ def apply_search_restriction(self, i):
+ r = unicode(self.search_restriction.currentText())
if r is not None and r != '':
self.restriction_in_effect = True
restriction = 'search:"%s"'%(r)
diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py
index e3fd503872..97758482fc 100644
--- a/src/calibre/gui2/widgets.py
+++ b/src/calibre/gui2/widgets.py
@@ -538,6 +538,53 @@ class HistoryLineEdit(QComboBox):
def text(self):
return self.currentText()
+class ComboBoxWithHelp(QComboBox):
+ '''
+ A combobox where item 0 is help text. CurrentText will return '' for item 0.
+ Be sure to always fetch the text with currentText. Don't use the signals
+ that pass a string, because they will not correct the text.
+ '''
+ def __init__(self, parent=None):
+ QComboBox.__init__(self, parent)
+ self.currentIndexChanged[int].connect(self.index_changed)
+ self.help_text = ''
+ self.state_set = False
+
+ def initialize(self, help_text=_('Search')):
+ self.help_text = help_text
+ self.set_state()
+
+ def set_state(self):
+ if not self.state_set:
+ if self.currentIndex() == 0:
+ self.setItemText(0, self.help_text)
+ self.setStyleSheet('QComboBox { color: gray }')
+ else:
+ self.setItemText(0, '')
+ self.setStyleSheet('QComboBox { color: black }')
+
+ def index_changed(self, index):
+ self.state_set = False
+ self.set_state()
+
+ def currentText(self):
+ if self.currentIndex() == 0:
+ return ''
+ return QComboBox.currentText(self)
+
+ def itemText(self, idx):
+ if idx == 0:
+ return ''
+ return QComboBox.itemText(self, idx)
+
+ def showPopup(self):
+ self.setItemText(0, '')
+ QComboBox.showPopup(self)
+
+ def hidePopup(self):
+ QComboBox.hidePopup(self)
+ self.set_state()
+
class PythonHighlighter(QSyntaxHighlighter):
Rules = []
diff --git a/src/calibre/utils/config.py b/src/calibre/utils/config.py
index f24a6d2e30..5c4bd55644 100644
--- a/src/calibre/utils/config.py
+++ b/src/calibre/utils/config.py
@@ -698,8 +698,8 @@ def _prefs():
# calibre server can execute 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('preserve_user_collections', default=True,
- help=_('Preserve all collections even if not in library metadata.'))
+ c.add_opt('manage_device_metadata', default='manual',
+ help=_('How and when calibre updates metadata on the device.'))
c.add_opt('migrated', default=False, help='For Internal use. Don\'t modify.')
return c