Wireless device driver: detect future-dated books in the calibre library and refuse to sync them.

This commit is contained in:
Charles Haley 2014-09-16 08:06:27 +02:00
parent 295441d9b2
commit 77c4c45612
3 changed files with 68 additions and 29 deletions

View File

@ -726,7 +726,7 @@ class DevicePlugin(Plugin):
''' '''
return False return False
def synchronize_with_db(self, db, book_id, book_metadata): def synchronize_with_db(self, db, book_id, book_metadata, first_call):
''' '''
Called during book matching when a book on the device is matched with Called during book matching when a book on the device is matched with
a book in calibre's db. The method is responsible for syncronizing a book in calibre's db. The method is responsible for syncronizing
@ -740,23 +740,28 @@ class DevicePlugin(Plugin):
This is useful when the calire data is correct but must be sent to the This is useful when the calire data is correct but must be sent to the
device. device.
The second value in the tuple specifies whether a book format should be The second value is itself a 2-value tuple. The first value in the tuple
sent to the device. The intent is to permit verifying that the book on specifies whether a book format should be sent to the device. The intent
the device is the same as the book in calibre. Return None if no book is is to permit verifying that the book on the device is the same as the
to be sent, otherwise return the base file name on the device (a string book in calibre. This value must be None if no book is to be sent,
like foobar.epub). Be sure to include the extension in the name. The otherwise return the base file name on the device (a string like
device subsystem will construct a send_books job for all books with not- foobar.epub). Be sure to include the extension in the name. The device
None returned values. Note: other than to later retrieve the extension, subsystem will construct a send_books job for all books with not- None
the name is ignored in cases where the device uses a template to returned values. Note: other than to later retrieve the extension, the
generate the file name, which most do. name is ignored in cases where the device uses a template to generate
the file name, which most do. The second value in the returned tuple
indicated whether the format is future-dated. Return True if it is,
otherwise return False. Calibre will display a dialog to the user
listing all future dated books.
Extremely important: this method is called on the GUI thread. It must Extremely important: this method is called on the GUI thread. It must
be threadsafe with respect to the device manager's thread. be threadsafe with respect to the device manager's thread.
book_id: the calibre id for the book in the database. book_id: the calibre id for the book in the database.
book_metadata: the Metadata object for the book coming from the device. book_metadata: the Metadata object for the book coming from the device.
first_call: True if this is the first call during a sync, False otherwise
''' '''
return None, None return (None, (None, False))
class BookList(list): class BookList(list):
''' '''

View File

@ -1546,36 +1546,49 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
def specialize_global_preferences(self, device_prefs): def specialize_global_preferences(self, device_prefs):
device_prefs.set_overrides(manage_device_metadata='on_connect') device_prefs.set_overrides(manage_device_metadata='on_connect')
def _show_message(self, message):
self._call_client("DISPLAY_MESSAGE",
{'messageKind': self.MESSAGE_SHOW_TOAST,
'message': message})
def _check_if_format_send_needed(self, db, id_, book): def _check_if_format_send_needed(self, db, id_, book):
if not self.will_ask_for_update_books: if not self.will_ask_for_update_books:
return None return None
from calibre.utils.date import parse_date, now, isoformat from calibre.utils.date import parse_date, isoformat
try: try:
if not hasattr(book, '_format_mtime_'): if not hasattr(book, '_format_mtime_'):
return None return None
cc_mtime = parse_date(book.get('_format_mtime_'), as_utc=False)
ext = posixpath.splitext(book.lpath)[1][1:] ext = posixpath.splitext(book.lpath)[1][1:]
fmt_metadata = db.new_api.format_metadata(id_, ext) fmt_metadata = db.new_api.format_metadata(id_, ext)
if fmt_metadata: if fmt_metadata:
calibre_mtime = fmt_metadata['mtime'] calibre_mtime = fmt_metadata['mtime']
if calibre_mtime > self.now:
if not self.have_sent_future_dated_book_message:
self.have_sent_future_dated_book_message = True
self._show_message(_('You have book formats in your library '
'with dates in the future. See calibre '
'for details'))
return (None, True)
cc_mtime = parse_date(book.get('_format_mtime_'), as_utc=False)
self._debug(book.title, 'cal_mtime', calibre_mtime, 'cc_mtime', cc_mtime) self._debug(book.title, 'cal_mtime', calibre_mtime, 'cc_mtime', cc_mtime)
if cc_mtime < calibre_mtime: if cc_mtime < calibre_mtime:
book.set('_format_mtime_', isoformat(now())) book.set('_format_mtime_', isoformat(self.now))
return posixpath.basename(book.lpath) return (posixpath.basename(book.lpath), False)
except: except:
self._debug('exception checking if must send format', book.title) self._debug('exception checking if must send format', book.title)
traceback.print_exc() traceback.print_exc()
return None return (None, False)
@synchronous('sync_lock') @synchronous('sync_lock')
def synchronize_with_db(self, db, id_, book): def synchronize_with_db(self, db, id_, book, first_call):
from calibre.utils.date import parse_date, is_date_undefined from calibre.utils.date import parse_date, is_date_undefined, now
def show_message(message):
self._call_client("DISPLAY_MESSAGE", if first_call:
{'messageKind': self.MESSAGE_SHOW_TOAST, self.have_sent_future_dated_book_message = False
'message': message}) self.now = now()
if self.have_bad_sync_columns or not (self.is_read_sync_col or if self.have_bad_sync_columns or not (self.is_read_sync_col or
self.is_read_date_sync_col): self.is_read_date_sync_col):
@ -1589,24 +1602,24 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
if self.is_read_sync_col: if self.is_read_sync_col:
if self.is_read_sync_col not in fm: if self.is_read_sync_col not in fm:
self._debug('is_read_sync_col not in field_metadata') self._debug('is_read_sync_col not in field_metadata')
show_message(_("The read sync column %s is " self._show_message(_("The read sync column %s is "
"not in calibre's library")%self.is_read_sync_col) "not in calibre's library")%self.is_read_sync_col)
self.have_bad_sync_columns = True self.have_bad_sync_columns = True
elif fm[self.is_read_sync_col]['datatype'] != 'bool': elif fm[self.is_read_sync_col]['datatype'] != 'bool':
self._debug('is_read_sync_col not bool type') self._debug('is_read_sync_col not bool type')
show_message(_("The read sync column %s is " self._show_message(_("The read sync column %s is "
"not a Yes/No column")%self.is_read_sync_col) "not a Yes/No column")%self.is_read_sync_col)
self.have_bad_sync_columns = True self.have_bad_sync_columns = True
if self.is_read_date_sync_col: if self.is_read_date_sync_col:
if self.is_read_date_sync_col not in fm: if self.is_read_date_sync_col not in fm:
self._debug('is_read_date_sync_col not in field_metadata') self._debug('is_read_date_sync_col not in field_metadata')
show_message(_("The read date sync column %s is " self._show_message(_("The read date sync column %s is "
"not in calibre's library")%self.is_read_date_sync_col) "not in calibre's library")%self.is_read_date_sync_col)
self.have_bad_sync_columns = True self.have_bad_sync_columns = True
elif fm[self.is_read_date_sync_col]['datatype'] != 'datetime': elif fm[self.is_read_date_sync_col]['datatype'] != 'datetime':
self._debug('is_read_date_sync_col not date type') self._debug('is_read_date_sync_col not date type')
show_message(_("The read date sync column %s is " self._show_message(_("The read date sync column %s is "
"not a Date column")%self.is_read_date_sync_col) "not a Date column")%self.is_read_date_sync_col)
self.have_bad_sync_columns = True self.have_bad_sync_columns = True
@ -1786,6 +1799,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self.is_read_date_sync_col = None self.is_read_date_sync_col = None
self.have_checked_sync_columns = False self.have_checked_sync_columns = False
self.have_bad_sync_columns = False self.have_bad_sync_columns = False
self.have_sent_future_dated_book_message = False
self.now = None
message = None message = None
compression_quality_ok = True compression_quality_ok = True

View File

@ -1776,6 +1776,8 @@ class DeviceMixin(object): # {{{
book_ids_to_refresh = set() book_ids_to_refresh = set()
book_formats_to_send = [] book_formats_to_send = []
books_with_future_dates = []
self._first_call_to_synchronize_with_db = True
def update_book(id_, book) : def update_book(id_, book) :
if not update_metadata: if not update_metadata:
@ -1791,9 +1793,13 @@ class DeviceMixin(object): # {{{
return False return False
if self.device_manager.device is not None: if self.device_manager.device is not None:
set_of_ids, fmt_name = \ set_of_ids, (fmt_name, date_bad) = \
self.device_manager.device.synchronize_with_db(db, id_, book) self.device_manager.device.synchronize_with_db(db, id_, book,
if fmt_name is not None: self._first_call_to_synchronize_with_db)
self._first_call_to_synchronize_with_db = False
if date_bad:
books_with_future_dates.append(book.title)
elif fmt_name is not None:
book_formats_to_send.append((id_, fmt_name)) book_formats_to_send.append((id_, fmt_name))
if set_of_ids is not None: if set_of_ids is not None:
book_ids_to_refresh.update(set_of_ids) book_ids_to_refresh.update(set_of_ids)
@ -1946,6 +1952,19 @@ class DeviceMixin(object): # {{{
# Shouldn't ever happen, but just in case # Shouldn't ever happen, but just in case
traceback.print_exc() traceback.print_exc()
# Inform user about future-dated books
try:
if books_with_future_dates:
d = error_dialog(self, _('Book format sync problem'),
_('Some book formats in your library cannot be '
'synced because they have dates in the future'),
det_msg='\n'.join(books_with_future_dates),
show=False,
show_copy_button=True)
d.show()
except:
traceback.print_exc()
if DEBUG: if DEBUG:
prints('DeviceJob: set_books_in_library finished: time=', prints('DeviceJob: set_books_in_library finished: time=',
time.time() - start_time) time.time() - start_time)