mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Wireless device driver: detect future-dated books in the calibre library and refuse to sync them.
This commit is contained in:
parent
295441d9b2
commit
77c4c45612
@ -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):
|
||||||
'''
|
'''
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user