mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 18:24:30 -04:00
Merge branch 'master' of https://github.com/cbhaley/calibre
This commit is contained in:
commit
1baae866cc
@ -732,12 +732,23 @@ class DevicePlugin(Plugin):
|
|||||||
a book in calibre's db. The method is responsible for syncronizing
|
a book in calibre's db. The method is responsible for syncronizing
|
||||||
data from the device to calibre's db (if needed).
|
data from the device to calibre's db (if needed).
|
||||||
|
|
||||||
The method must return a set of calibre book ids changed if calibre's
|
The method must return a two-value tuple. The first value is a set of
|
||||||
database was changed, None if the database was not changed. If the
|
calibre book ids changed if calibre's database was changed or None if the
|
||||||
method returns an empty set then the metadata for the book on the
|
database was not changed. If the first value is an empty set then the
|
||||||
device is updated with calibre's metadata and given back to the device,
|
metadata for the book on the device is updated with calibre's metadata
|
||||||
but no GUI refresh of that book is done. This is useful when the calire
|
and given back to the device, but no GUI refresh of that book is done.
|
||||||
data is correct but must be sent to the device.
|
This is useful when the calire data is correct but must be sent to the
|
||||||
|
device.
|
||||||
|
|
||||||
|
The second value in the tuple specifies whether a book format should be
|
||||||
|
sent to the device. The intent is to permit verifying that the book on
|
||||||
|
the device is the same as the book in calibre. Return None if no book is
|
||||||
|
to be sent, otherwise return the base file name on the device (a string
|
||||||
|
like foobar.epub). Be sure to include the extension in the name. The
|
||||||
|
device subsystem will construct a send_books job for all books with not-
|
||||||
|
None returned values. Note: other than to later retrieve the extension,
|
||||||
|
the name is ignored in cases where the device uses a template to
|
||||||
|
generate the file name, which most do.
|
||||||
|
|
||||||
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.
|
||||||
@ -745,7 +756,7 @@ class DevicePlugin(Plugin):
|
|||||||
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.
|
||||||
'''
|
'''
|
||||||
return None
|
return None, None
|
||||||
|
|
||||||
class BookList(list):
|
class BookList(list):
|
||||||
'''
|
'''
|
||||||
|
@ -999,7 +999,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
'pubdateFormat': tweaks['gui_pubdate_display_format'],
|
'pubdateFormat': tweaks['gui_pubdate_display_format'],
|
||||||
'timestampFormat': tweaks['gui_timestamp_display_format'],
|
'timestampFormat': tweaks['gui_timestamp_display_format'],
|
||||||
'lastModifiedFormat': tweaks['gui_last_modified_display_format'],
|
'lastModifiedFormat': tweaks['gui_last_modified_display_format'],
|
||||||
'calibre_version': numeric_version})
|
'calibre_version': numeric_version,
|
||||||
|
'canSupportUpdateBooks': True})
|
||||||
if opcode != 'OK':
|
if opcode != 'OK':
|
||||||
# Something wrong with the return. Close the socket
|
# Something wrong with the return. Close the socket
|
||||||
# and continue.
|
# and continue.
|
||||||
@ -1017,6 +1018,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
self._close_device_socket()
|
self._close_device_socket()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Set up to recheck the sync columns
|
||||||
|
self.have_checked_sync_columns = False
|
||||||
client_can_stream_books = result.get('canStreamBooks', False)
|
client_can_stream_books = result.get('canStreamBooks', False)
|
||||||
self._debug('Device can stream books', client_can_stream_books)
|
self._debug('Device can stream books', client_can_stream_books)
|
||||||
client_can_stream_metadata = result.get('canStreamMetadata', False)
|
client_can_stream_metadata = result.get('canStreamMetadata', False)
|
||||||
@ -1043,6 +1046,12 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
self._debug('Can send OK to sendbook', self.can_send_ok_to_sendbook)
|
self._debug('Can send OK to sendbook', self.can_send_ok_to_sendbook)
|
||||||
self.can_accept_library_info = result.get('canAcceptLibraryInfo', False)
|
self.can_accept_library_info = result.get('canAcceptLibraryInfo', False)
|
||||||
self._debug('Can accept library info', self.can_accept_library_info)
|
self._debug('Can accept library info', self.can_accept_library_info)
|
||||||
|
self.will_ask_for_update_books = result.get('willAskForUpdateBooks', False)
|
||||||
|
self._debug('Will ask for update books', self.will_ask_for_update_books)
|
||||||
|
self.set_temp_mark_when_syncing_read = \
|
||||||
|
result.get('setTempMarkWhenReadInfoSynced', False)
|
||||||
|
self._debug('Will set temp mark when syncing read',
|
||||||
|
self.set_temp_mark_when_syncing_read)
|
||||||
|
|
||||||
if not self.settings().extra_customization[self.OPT_USE_METADATA_CACHE]:
|
if not self.settings().extra_customization[self.OPT_USE_METADATA_CACHE]:
|
||||||
self.client_can_use_metadata_cache = False
|
self.client_can_use_metadata_cache = False
|
||||||
@ -1223,7 +1232,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
'canScan':True,
|
'canScan':True,
|
||||||
'willUseCachedMetadata': self.client_can_use_metadata_cache,
|
'willUseCachedMetadata': self.client_can_use_metadata_cache,
|
||||||
'supportsSync': (bool(self.is_read_sync_col) or
|
'supportsSync': (bool(self.is_read_sync_col) or
|
||||||
bool(self.is_read_date_sync_col))})
|
bool(self.is_read_date_sync_col)),
|
||||||
|
'canSupportBookFormatSync': True})
|
||||||
bl = CollectionsBookList(None, self.PREFIX, self.settings)
|
bl = CollectionsBookList(None, self.PREFIX, self.settings)
|
||||||
if opcode == 'OK':
|
if opcode == 'OK':
|
||||||
count = result['count']
|
count = result['count']
|
||||||
@ -1252,6 +1262,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
book.set('_is_read_', r.get('_is_read_', None))
|
book.set('_is_read_', r.get('_is_read_', None))
|
||||||
book.set('_sync_type_', r.get('_sync_type_', None))
|
book.set('_sync_type_', r.get('_sync_type_', None))
|
||||||
book.set('_last_read_date_', r.get('_last_read_date_', None))
|
book.set('_last_read_date_', r.get('_last_read_date_', None))
|
||||||
|
book.set('_format_mtime_', r.get('_format_mtime_', None))
|
||||||
else:
|
else:
|
||||||
books_to_send.append(r['priKey'])
|
books_to_send.append(r['priKey'])
|
||||||
|
|
||||||
@ -1529,6 +1540,30 @@ 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 _check_if_format_send_needed(self, db, id_, book):
|
||||||
|
if not self.will_ask_for_update_books:
|
||||||
|
return None
|
||||||
|
|
||||||
|
from calibre.utils.date import parse_date, now, isoformat
|
||||||
|
try:
|
||||||
|
if not hasattr(book, '_format_mtime_'):
|
||||||
|
return None
|
||||||
|
|
||||||
|
cc_mtime = parse_date(book.get('_format_mtime_'), as_utc=False)
|
||||||
|
ext = posixpath.splitext(book.lpath)[1][1:]
|
||||||
|
fmt_metadata = db.new_api.format_metadata(id_, ext)
|
||||||
|
if fmt_metadata:
|
||||||
|
calibre_mtime = fmt_metadata['mtime']
|
||||||
|
self._debug(book.title, 'cal_mtime', calibre_mtime, 'cc_mtime', cc_mtime)
|
||||||
|
if cc_mtime < calibre_mtime:
|
||||||
|
book.set('_format_mtime_', isoformat(now()))
|
||||||
|
return posixpath.basename(book.lpath)
|
||||||
|
except:
|
||||||
|
self._debug('exception checking if must send format', book.title)
|
||||||
|
traceback.print_exc()
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@synchronous('sync_lock')
|
@synchronous('sync_lock')
|
||||||
def synchronize_with_db(self, db, id_, book):
|
def synchronize_with_db(self, db, id_, book):
|
||||||
from calibre.utils.date import parse_date, is_date_undefined
|
from calibre.utils.date import parse_date, is_date_undefined
|
||||||
@ -1540,7 +1575,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
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):
|
||||||
# Not syncing or sync columns are invalid
|
# Not syncing or sync columns are invalid
|
||||||
return None
|
return (None, self._check_if_format_send_needed(db, id_, book))
|
||||||
|
|
||||||
# Check the validity of the columns once per connection. We do it
|
# Check the validity of the columns once per connection. We do it
|
||||||
# here because we have access to the db to get field_metadata
|
# here because we have access to the db to get field_metadata
|
||||||
@ -1572,7 +1607,12 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
|
|
||||||
self.have_checked_sync_columns = True
|
self.have_checked_sync_columns = True
|
||||||
if self.have_bad_sync_columns:
|
if self.have_bad_sync_columns:
|
||||||
return None
|
return (None, self._check_if_format_send_needed(db, id_, book))
|
||||||
|
|
||||||
|
# if we are marking synced books, clear all the current marks
|
||||||
|
if self.set_temp_mark_when_syncing_read:
|
||||||
|
self._debug('clearing temp marks')
|
||||||
|
db.set_marked_ids(())
|
||||||
|
|
||||||
sync_type = book.get('_sync_type_', None)
|
sync_type = book.get('_sync_type_', None)
|
||||||
# We need to check if our attributes are in the book. If they are not
|
# We need to check if our attributes are in the book. If they are not
|
||||||
@ -1624,6 +1664,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
book.get('title', 'huh?'), 'to', is_read, calibre_val)
|
book.get('title', 'huh?'), 'to', is_read, calibre_val)
|
||||||
changed_books = db.new_api.set_field(self.is_read_sync_col,
|
changed_books = db.new_api.set_field(self.is_read_sync_col,
|
||||||
{id_: is_read})
|
{id_: is_read})
|
||||||
|
if self.set_temp_mark_when_syncing_read:
|
||||||
|
db.data.toggle_marked_ids({id_})
|
||||||
elif calibre_val is not None:
|
elif calibre_val is not None:
|
||||||
# Calibre value wins. Force the metadata for the
|
# Calibre value wins. Force the metadata for the
|
||||||
# book to be sent to the device even if the mod
|
# book to be sent to the device even if the mod
|
||||||
@ -1648,6 +1690,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
book.get('title', 'huh?'), 'to', is_read_date, calibre_val)
|
book.get('title', 'huh?'), 'to', is_read_date, calibre_val)
|
||||||
changed_books |= db.new_api.set_field(self.is_read_date_sync_col,
|
changed_books |= db.new_api.set_field(self.is_read_date_sync_col,
|
||||||
{id_: is_read_date})
|
{id_: is_read_date})
|
||||||
|
if self.set_temp_mark_when_syncing_read:
|
||||||
|
db.data.toggle_marked_ids({id_})
|
||||||
elif calibre_val is not None:
|
elif calibre_val is not None:
|
||||||
self._debug('special update is_read_date to calibre value',
|
self._debug('special update is_read_date to calibre value',
|
||||||
book.get('title', 'huh?'), 'to', calibre_val)
|
book.get('title', 'huh?'), 'to', calibre_val)
|
||||||
@ -1674,6 +1718,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
'to', is_read, 'was', orig_is_read)
|
'to', is_read, 'was', orig_is_read)
|
||||||
changed_books = db.new_api.set_field(self.is_read_sync_col,
|
changed_books = db.new_api.set_field(self.is_read_sync_col,
|
||||||
{id_: is_read})
|
{id_: is_read})
|
||||||
|
if self.set_temp_mark_when_syncing_read:
|
||||||
|
db.data.toggle_marked_ids({id_})
|
||||||
except:
|
except:
|
||||||
self._debug('exception standard syncing is_read', self.is_read_sync_col)
|
self._debug('exception standard syncing is_read', self.is_read_sync_col)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@ -1689,6 +1735,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
'to', is_read_date, 'was', orig_is_read_date)
|
'to', is_read_date, 'was', orig_is_read_date)
|
||||||
changed_books |= db.new_api.set_field(self.is_read_date_sync_col,
|
changed_books |= db.new_api.set_field(self.is_read_date_sync_col,
|
||||||
{id_: is_read_date})
|
{id_: is_read_date})
|
||||||
|
if self.set_temp_mark_when_syncing_read:
|
||||||
|
db.data.toggle_marked_ids({id_})
|
||||||
except:
|
except:
|
||||||
self._debug('Exception standard syncing is_read_date',
|
self._debug('Exception standard syncing is_read_date',
|
||||||
self.is_read_date_sync_col)
|
self.is_read_date_sync_col)
|
||||||
@ -1697,14 +1745,14 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
if changed_books or force_return_changed_books:
|
if changed_books or force_return_changed_books:
|
||||||
# One of the two values was synced, giving a (perhaps empty) list of
|
# One of the two values was synced, giving a (perhaps empty) list of
|
||||||
# changed books. Return that.
|
# changed books. Return that.
|
||||||
return changed_books
|
return (changed_books, self._check_if_format_send_needed(db, id_, book))
|
||||||
|
|
||||||
# Nothing was synced. The user might have changed the value in calibre.
|
# Nothing was synced. The user might have changed the value in calibre.
|
||||||
# If so, that value will be sent to the device in the normal way. Note
|
# If so, that value will be sent to the device in the normal way. Note
|
||||||
# that because any updated value has already been synced and so will
|
# that because any updated value has already been synced and so will
|
||||||
# also be sent, the device should put the calibre value into its
|
# also be sent, the device should put the calibre value into its
|
||||||
# checkbox (or whatever it uses)
|
# checkbox (or whatever it uses)
|
||||||
return None
|
return (None, self._check_if_format_send_needed(db, id_, book))
|
||||||
|
|
||||||
@synchronous('sync_lock')
|
@synchronous('sync_lock')
|
||||||
def startup(self):
|
def startup(self):
|
||||||
|
@ -34,6 +34,7 @@ from calibre.constants import DEBUG
|
|||||||
from calibre.utils.config import tweaks, device_prefs
|
from calibre.utils.config import tweaks, device_prefs
|
||||||
from calibre.utils.magick.draw import thumbnail
|
from calibre.utils.magick.draw import thumbnail
|
||||||
from calibre.library.save_to_disk import find_plugboard
|
from calibre.library.save_to_disk import find_plugboard
|
||||||
|
from calibre.ptempfile import PersistentTemporaryFile, force_unicode as filename_to_unicode
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class DeviceJob(BaseJob): # {{{
|
class DeviceJob(BaseJob): # {{{
|
||||||
@ -1774,6 +1775,7 @@ class DeviceMixin(object): # {{{
|
|||||||
self.db_book_uuid_cache = db_book_uuid_cache
|
self.db_book_uuid_cache = db_book_uuid_cache
|
||||||
|
|
||||||
book_ids_to_refresh = set()
|
book_ids_to_refresh = set()
|
||||||
|
book_formats_to_send = []
|
||||||
|
|
||||||
def update_book(id_, book) :
|
def update_book(id_, book) :
|
||||||
if not update_metadata:
|
if not update_metadata:
|
||||||
@ -1789,7 +1791,10 @@ 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 = self.device_manager.device.synchronize_with_db(db, id_, book)
|
set_of_ids, fmt_name = \
|
||||||
|
self.device_manager.device.synchronize_with_db(db, id_, book)
|
||||||
|
if fmt_name is not None:
|
||||||
|
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)
|
||||||
return True
|
return True
|
||||||
@ -1913,6 +1918,34 @@ class DeviceMixin(object): # {{{
|
|||||||
# This shouldn't ever happen, but just in case ...
|
# This shouldn't ever happen, but just in case ...
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
|
# Sync books if necessary
|
||||||
|
try:
|
||||||
|
files, names, metadata = [], [], []
|
||||||
|
for id_, fmt_name in book_formats_to_send:
|
||||||
|
if DEBUG:
|
||||||
|
prints('DeviceJob: Syncing book. id:', id_, 'name from device', fmt_name)
|
||||||
|
ext = os.path.splitext(fmt_name)[1][1:]
|
||||||
|
fmt_info = db.new_api.format_metadata(id_, ext)
|
||||||
|
if fmt_info:
|
||||||
|
try:
|
||||||
|
pt = PersistentTemporaryFile(suffix='caltmpfmt.'+ext)
|
||||||
|
db.new_api.copy_format_to(id_, ext, pt)
|
||||||
|
pt.close()
|
||||||
|
files.append(filename_to_unicode(os.path.abspath(pt.name)))
|
||||||
|
names.append(fmt_name)
|
||||||
|
metadata.append(db.new_api.get_metadata(id_, get_cover=True))
|
||||||
|
except:
|
||||||
|
prints('Problem creating temporary file for', fmt_name)
|
||||||
|
traceback.print_exc()
|
||||||
|
else:
|
||||||
|
if DEBUG:
|
||||||
|
prints("DeviceJob: book doesn't have that format")
|
||||||
|
if files:
|
||||||
|
self.upload_books(files, names, metadata)
|
||||||
|
except:
|
||||||
|
# Shouldn't ever happen, but just in case
|
||||||
|
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