mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
1) improve performance of OnDevice refresh. Instead of rebuilding the complete book list, iterate through the existing one and set the value of OnDevice correctly.
2) Fix problems with Sony readers and metadata caching. Needed to ensure that when a book is added to the booklist from the JSON cache, it is added to the sony cache if it isn't already there. 3) Build the sony metadata maps (caches) on the fly instead of in reorder_playlists. 4) Refactor method declarations. 5) Move the JSON cache to the root of the card for Sony devices.
This commit is contained in:
parent
dc130d56a9
commit
9a0dfff78e
@ -10,9 +10,8 @@ from base64 import b64encode as encode
|
|||||||
|
|
||||||
from calibre.devices.usbms.books import BookList as _BookList
|
from calibre.devices.usbms.books import BookList as _BookList
|
||||||
from calibre.devices import strftime as _strftime
|
from calibre.devices import strftime as _strftime
|
||||||
from calibre.devices.usbms.books import Book as _Book
|
from calibre.devices.prs505 import MEDIA_XML, CACHE_XML
|
||||||
from calibre.devices.prs505 import MEDIA_XML
|
from calibre.devices.errors import PathError
|
||||||
from calibre.devices.prs505 import CACHE_XML
|
|
||||||
|
|
||||||
strftime = functools.partial(_strftime, zone=time.gmtime)
|
strftime = functools.partial(_strftime, zone=time.gmtime)
|
||||||
|
|
||||||
@ -33,10 +32,14 @@ def sortable_title(title):
|
|||||||
|
|
||||||
class BookList(_BookList):
|
class BookList(_BookList):
|
||||||
|
|
||||||
def __init__(self, oncard, prefix):
|
def __init__(self, oncard, prefix, settings):
|
||||||
_BookList.__init__(self, oncard, prefix)
|
_BookList.__init__(self, oncard, prefix, settings)
|
||||||
if prefix is None:
|
if prefix is None:
|
||||||
return
|
return
|
||||||
|
self.sony_id_cache = {}
|
||||||
|
self.books_lpath_cache = {}
|
||||||
|
opts = settings()
|
||||||
|
self.collections = opts.extra_customization.split(',') if opts.extra_customization else []
|
||||||
db = CACHE_XML if oncard else MEDIA_XML
|
db = CACHE_XML if oncard else MEDIA_XML
|
||||||
xml_file = open(prefix + db, 'rb')
|
xml_file = open(prefix + db, 'rb')
|
||||||
xml_file.seek(0)
|
xml_file.seek(0)
|
||||||
@ -50,8 +53,21 @@ class BookList(_BookList):
|
|||||||
self.root_element = records[0]
|
self.root_element = records[0]
|
||||||
else:
|
else:
|
||||||
self.prefix = ''
|
self.prefix = ''
|
||||||
|
for child in self.root_element.childNodes:
|
||||||
|
if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("id"):
|
||||||
|
self.sony_id_cache[child.getAttribute('id')] = child.getAttribute('path')
|
||||||
|
# set the key to none. Will be filled in later when booklist is built
|
||||||
|
self.books_lpath_cache[child.getAttribute('path')] = None
|
||||||
self.tag_order = {}
|
self.tag_order = {}
|
||||||
|
|
||||||
|
paths = self.purge_corrupted_files()
|
||||||
|
for path in paths:
|
||||||
|
try:
|
||||||
|
self.del_file(path, end_session=False)
|
||||||
|
except PathError: # Incase this is a refetch without a sync in between
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
def max_id(self):
|
def max_id(self):
|
||||||
max = 0
|
max = 0
|
||||||
for child in self.root_element.childNodes:
|
for child in self.root_element.childNodes:
|
||||||
@ -73,22 +89,27 @@ class BookList(_BookList):
|
|||||||
def supports_tags(self):
|
def supports_tags(self):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def book_by_path(self, path):
|
def add_book(self, book, replace_metadata):
|
||||||
for child in self.root_element.childNodes:
|
# Add a node into the DOM tree, representing a book. Also add to booklist
|
||||||
if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("path"):
|
|
||||||
if path == child.getAttribute('path'):
|
|
||||||
return child
|
|
||||||
return None
|
|
||||||
|
|
||||||
def add_book(self, book, collections=None):
|
|
||||||
if book in self:
|
if book in self:
|
||||||
return
|
# replacing metadata for book
|
||||||
""" Add a node into the DOM tree, representing a book """
|
self.delete_node(book.lpath)
|
||||||
node = self.document.createElement(self.prefix + "text")
|
else:
|
||||||
mime = MIME_MAP.get(book.lpath.rpartition('.')[-1].lower(), MIME_MAP['epub'])
|
self.append(book)
|
||||||
|
if not replace_metadata:
|
||||||
|
if self.books_lpath_cache.has_key(book.lpath):
|
||||||
|
self.books_lpath_cache[book.lpath] = book
|
||||||
|
return
|
||||||
|
# Book not in metadata. Add it. Note that we don't need to worry about
|
||||||
|
# extra books in the Sony metadata. The reader deletes them for us when
|
||||||
|
# we disconnect. That said, if it becomes important one day, we can do
|
||||||
|
# it by scanning the books_lpath_cache for None entries and removing the
|
||||||
|
# corresponding nodes.
|
||||||
|
self.books_lpath_cache[book.lpath] = book
|
||||||
cid = self.max_id()+1
|
cid = self.max_id()+1
|
||||||
book.sony_id = cid
|
node = self.document.createElement(self.prefix + "text")
|
||||||
self.append(book)
|
self.sony_id_cache[cid] = book.lpath
|
||||||
|
mime = MIME_MAP.get(book.lpath.rpartition('.')[-1].lower(), MIME_MAP['epub'])
|
||||||
try:
|
try:
|
||||||
sourceid = str(self[0].sourceid) if len(self) else '1'
|
sourceid = str(self[0].sourceid) if len(self) else '1'
|
||||||
except:
|
except:
|
||||||
@ -120,7 +141,7 @@ class BookList(_BookList):
|
|||||||
self.root_element.appendChild(node)
|
self.root_element.appendChild(node)
|
||||||
|
|
||||||
tags = []
|
tags = []
|
||||||
for item in collections:
|
for item in self.collections:
|
||||||
item = item.strip()
|
item = item.strip()
|
||||||
mitem = getattr(book, item, None)
|
mitem = getattr(book, item, None)
|
||||||
titems = []
|
titems = []
|
||||||
@ -141,6 +162,7 @@ class BookList(_BookList):
|
|||||||
if hasattr(book, 'tag_order'):
|
if hasattr(book, 'tag_order'):
|
||||||
self.tag_order.update(book.tag_order)
|
self.tag_order.update(book.tag_order)
|
||||||
self.set_playlists(cid, tags)
|
self.set_playlists(cid, tags)
|
||||||
|
return True # metadata cache has changed. Must sync at end
|
||||||
|
|
||||||
def _delete_node(self, node):
|
def _delete_node(self, node):
|
||||||
nid = node.getAttribute('id')
|
nid = node.getAttribute('id')
|
||||||
@ -162,7 +184,8 @@ class BookList(_BookList):
|
|||||||
def remove_book(self, book):
|
def remove_book(self, book):
|
||||||
'''
|
'''
|
||||||
Remove DOM node corresponding to book with C{path == path}.
|
Remove DOM node corresponding to book with C{path == path}.
|
||||||
Also remove book from any collections it is part of.
|
Also remove book from any collections it is part of, and remove
|
||||||
|
from the booklist
|
||||||
'''
|
'''
|
||||||
self.remove(book)
|
self.remove(book)
|
||||||
self.delete_node(book.lpath)
|
self.delete_node(book.lpath)
|
||||||
@ -264,15 +287,6 @@ class BookList(_BookList):
|
|||||||
stream.write(src.replace("'", '''))
|
stream.write(src.replace("'", '''))
|
||||||
|
|
||||||
def reorder_playlists(self):
|
def reorder_playlists(self):
|
||||||
sony_id_cache = {}
|
|
||||||
for child in self.root_element.childNodes:
|
|
||||||
if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("id"):
|
|
||||||
sony_id_cache[child.getAttribute('id')] = child.getAttribute('path')
|
|
||||||
|
|
||||||
books_lpath_cache = {}
|
|
||||||
for book in self:
|
|
||||||
books_lpath_cache[book.lpath] = book
|
|
||||||
|
|
||||||
for title in self.tag_order.keys():
|
for title in self.tag_order.keys():
|
||||||
pl = self.playlist_by_title(title)
|
pl = self.playlist_by_title(title)
|
||||||
if not pl:
|
if not pl:
|
||||||
@ -281,9 +295,9 @@ class BookList(_BookList):
|
|||||||
sony_ids = [id.getAttribute('id') \
|
sony_ids = [id.getAttribute('id') \
|
||||||
for id in pl.childNodes if hasattr(id, 'getAttribute')]
|
for id in pl.childNodes if hasattr(id, 'getAttribute')]
|
||||||
# convert IDs in playlist to a list of lpaths
|
# convert IDs in playlist to a list of lpaths
|
||||||
sony_paths = [sony_id_cache[id] for id in sony_ids]
|
sony_paths = [self.sony_id_cache[id] for id in sony_ids]
|
||||||
# create list of books containing lpaths
|
# create list of books containing lpaths
|
||||||
books = [books_lpath_cache.get(p, None) for p in sony_paths]
|
books = [self.books_lpath_cache.get(p, None) for p in sony_paths]
|
||||||
# create dict of db_id -> sony_id
|
# create dict of db_id -> sony_id
|
||||||
imap = {}
|
imap = {}
|
||||||
for book, sony_id in zip(books, sony_ids):
|
for book, sony_id in zip(books, sony_ids):
|
||||||
|
@ -58,8 +58,6 @@ class PRS505(USBMS):
|
|||||||
'series, tags, authors'
|
'series, tags, authors'
|
||||||
EXTRA_CUSTOMIZATION_DEFAULT = ', '.join(['series', 'tags'])
|
EXTRA_CUSTOMIZATION_DEFAULT = ', '.join(['series', 'tags'])
|
||||||
|
|
||||||
METADATA_CACHE = "database/cache/metadata.calibre"
|
|
||||||
|
|
||||||
def windows_filter_pnp_id(self, pnp_id):
|
def windows_filter_pnp_id(self, pnp_id):
|
||||||
return '_LAUNCHER' in pnp_id
|
return '_LAUNCHER' in pnp_id
|
||||||
|
|
||||||
|
@ -108,16 +108,19 @@ class Book(MetaInformation):
|
|||||||
|
|
||||||
class BookList(_BookList):
|
class BookList(_BookList):
|
||||||
|
|
||||||
|
def __init__(self, oncard, prefix, settings):
|
||||||
|
pass
|
||||||
|
|
||||||
def supports_tags(self):
|
def supports_tags(self):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def set_tags(self, book, tags):
|
def set_tags(self, book, tags):
|
||||||
book.tags = tags
|
book.tags = tags
|
||||||
|
|
||||||
def add_book(self, book, collections=None):
|
def add_book(self, book, replace_metadata):
|
||||||
'''
|
'''
|
||||||
Add the book to the booklist. Intent is to maintain any device-internal
|
Add the book to the booklist. Intent is to maintain any device-internal
|
||||||
metadata
|
metadata. Return True if booklists must be sync'ed
|
||||||
'''
|
'''
|
||||||
if book not in self:
|
if book not in self:
|
||||||
self.append(book)
|
self.append(book)
|
||||||
|
@ -58,20 +58,22 @@ class USBMS(CLI, Device):
|
|||||||
prefix = self._card_a_prefix if oncard == 'carda' else \
|
prefix = self._card_a_prefix if oncard == 'carda' else \
|
||||||
self._card_b_prefix if oncard == 'cardb' \
|
self._card_b_prefix if oncard == 'cardb' \
|
||||||
else self._main_prefix
|
else self._main_prefix
|
||||||
metadata = self.booklist_class(oncard, prefix)
|
|
||||||
|
|
||||||
ebook_dirs = self.EBOOK_DIR_CARD_A if oncard == 'carda' else \
|
ebook_dirs = self.EBOOK_DIR_CARD_A if oncard == 'carda' else \
|
||||||
self.EBOOK_DIR_CARD_B if oncard == 'cardb' else \
|
self.EBOOK_DIR_CARD_B if oncard == 'cardb' else \
|
||||||
self.get_main_ebook_dir()
|
self.get_main_ebook_dir()
|
||||||
|
|
||||||
bl, need_sync = self.parse_metadata_cache(prefix, self.METADATA_CACHE,
|
# build a temporary list of books from the metadata cache
|
||||||
self.booklist_class(oncard, prefix))
|
bl, need_sync = self.parse_metadata_cache(prefix, self.METADATA_CACHE)
|
||||||
# make a dict cache of paths so the lookup in the loop below is faster.
|
# make a dict cache of paths so the lookup in the loop below is faster.
|
||||||
bl_cache = {}
|
bl_cache = {}
|
||||||
for idx,b in enumerate(bl):
|
for idx,b in enumerate(bl):
|
||||||
bl_cache[b.lpath] = idx
|
bl_cache[b.lpath] = idx
|
||||||
self.count_found_in_bl = 0
|
self.count_found_in_bl = 0
|
||||||
|
|
||||||
|
# Make the real booklist that will be filled in below
|
||||||
|
metadata = self.booklist_class(oncard, prefix, self.settings)
|
||||||
|
|
||||||
def update_booklist(filename, path, prefix):
|
def update_booklist(filename, path, prefix):
|
||||||
changed = False
|
changed = False
|
||||||
if path_to_ext(filename) in self.FORMATS:
|
if path_to_ext(filename) in self.FORMATS:
|
||||||
@ -86,7 +88,8 @@ class USBMS(CLI, Device):
|
|||||||
else:
|
else:
|
||||||
item = self.book_from_path(prefix, lpath)
|
item = self.book_from_path(prefix, lpath)
|
||||||
changed = True
|
changed = True
|
||||||
metadata.append(item)
|
if metadata.add_book(item, replace_metadata=False):
|
||||||
|
changed = True
|
||||||
except: # Probably a filename encoding error
|
except: # Probably a filename encoding error
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@ -183,10 +186,7 @@ class USBMS(CLI, Device):
|
|||||||
if book.size is None:
|
if book.size is None:
|
||||||
book.size = os.stat(path).st_size
|
book.size = os.stat(path).st_size
|
||||||
|
|
||||||
opts = self.settings()
|
booklists[blist].add_book(book, replace_metadata=True)
|
||||||
collections = opts.extra_customization.split(',') if opts.extra_customization else []
|
|
||||||
booklists[blist].add_book(book, collections, *location[1:-1])
|
|
||||||
|
|
||||||
self.report_progress(1.0, _('Adding books to device metadata listing...'))
|
self.report_progress(1.0, _('Adding books to device metadata listing...'))
|
||||||
|
|
||||||
def delete_books(self, paths, end_session=True):
|
def delete_books(self, paths, end_session=True):
|
||||||
@ -237,7 +237,8 @@ class USBMS(CLI, Device):
|
|||||||
self.report_progress(1.0, _('Sending metadata to device...'))
|
self.report_progress(1.0, _('Sending metadata to device...'))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_metadata_cache(cls, prefix, name, bl):
|
def parse_metadata_cache(cls, prefix, name):
|
||||||
|
bl = []
|
||||||
js = []
|
js = []
|
||||||
need_sync = False
|
need_sync = False
|
||||||
try:
|
try:
|
||||||
|
@ -371,7 +371,7 @@ class BooksModel(QAbstractTableModel):
|
|||||||
def set_device_connected(self, is_connected):
|
def set_device_connected(self, is_connected):
|
||||||
self.device_connected = is_connected
|
self.device_connected = is_connected
|
||||||
self.read_config()
|
self.read_config()
|
||||||
self.refresh(reset=True)
|
self.db.refresh_ondevice()
|
||||||
self.database_changed.emit(self.db)
|
self.database_changed.emit(self.db)
|
||||||
|
|
||||||
def set_book_on_device_func(self, func):
|
def set_book_on_device_func(self, func):
|
||||||
|
@ -947,7 +947,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
self.device_manager.device)
|
self.device_manager.device)
|
||||||
self.location_view.model().device_connected(self.device_manager.device)
|
self.location_view.model().device_connected(self.device_manager.device)
|
||||||
self.eject_action.setEnabled(True)
|
self.eject_action.setEnabled(True)
|
||||||
self.refresh_ondevice_info (device_connected = True)
|
# don't refresh_ondevice here. It will happen in metadata_downloaded
|
||||||
else:
|
else:
|
||||||
self.save_device_view_settings()
|
self.save_device_view_settings()
|
||||||
self.device_connected = False
|
self.device_connected = False
|
||||||
|
@ -547,6 +547,12 @@ class ResultCache(SearchQueryParser):
|
|||||||
def count(self):
|
def count(self):
|
||||||
return len(self._map)
|
return len(self._map)
|
||||||
|
|
||||||
|
def refresh_ondevice(self, db):
|
||||||
|
ondevice_col = self.FIELD_MAP['ondevice']
|
||||||
|
for item in self._data:
|
||||||
|
if item is not None:
|
||||||
|
item[ondevice_col] = db.book_on_device_string(item[0])
|
||||||
|
|
||||||
def refresh(self, db, field=None, ascending=True):
|
def refresh(self, db, field=None, ascending=True):
|
||||||
temp = db.conn.get('SELECT * FROM meta2')
|
temp = db.conn.get('SELECT * FROM meta2')
|
||||||
self._data = list(itertools.repeat(None, temp[-1][0]+2)) if temp else []
|
self._data = list(itertools.repeat(None, temp[-1][0]+2)) if temp else []
|
||||||
|
@ -245,6 +245,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
self.has_id = self.data.has_id
|
self.has_id = self.data.has_id
|
||||||
self.count = self.data.count
|
self.count = self.data.count
|
||||||
|
|
||||||
|
self.refresh_ondevice = functools.partial(self.data.refresh_ondevice, self)
|
||||||
|
|
||||||
self.refresh()
|
self.refresh()
|
||||||
self.last_update_check = self.last_modified()
|
self.last_update_check = self.last_modified()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user