More testing of:

1) initial condition: cache does not exist on book
2) adding and removing books
3) subsequent conditions: cache exists

In addition:
1) added metadata correction for books matched with something other than UUID.
2) Refactored changes to BookList, to move the additional methods into USMBS from Interface.
3) Made classmethods in USBMS into normal methods.
This commit is contained in:
Charles Haley 2010-05-14 12:17:14 +01:00
parent 5c9e2ae267
commit 72fbd67c17
7 changed files with 82 additions and 97 deletions

View File

@ -402,17 +402,3 @@ class BookList(list):
'''
raise NotImplementedError()
def add_book(self, book, collections=None):
'''
Add the book to the booklist. Intent is to maintain any device-internal
metadata
'''
if book not in self:
self.append(book)
def remove_book(self, book):
'''
Remove a book from the booklist. Correct any device metadata at the
same time
'''
self.remove(book)

View File

@ -8,7 +8,7 @@ import xml.dom.minidom as dom
from base64 import b64encode as encode
from calibre.devices.interface import BookList as _BookList
from calibre.devices.usbms.books import BookList as _BookList
from calibre.devices import strftime as _strftime
from calibre.devices.usbms.books import Book as _Book
from calibre.devices.prs505 import MEDIA_XML
@ -31,36 +31,6 @@ def uuid():
def sortable_title(title):
return re.sub('^\s*A\s+|^\s*The\s+|^\s*An\s+', '', title).rstrip()
class book_metadata_field(object):
""" Represents metadata stored as an attribute """
def __init__(self, attr, formatter=None, setter=None):
self.attr = attr
self.formatter = formatter
self.setter = setter
def __get__(self, obj, typ=None):
""" Return a string. String may be empty if self.attr is absent """
return self.formatter(obj.elem.getAttribute(self.attr)) if \
self.formatter else obj.elem.getAttribute(self.attr).strip()
def __set__(self, obj, val):
""" Set the attribute """
val = self.setter(val) if self.setter else val
if not isinstance(val, unicode):
val = unicode(val, 'utf8', 'replace')
obj.elem.setAttribute(self.attr, val)
class Book(_Book):
@dynamic_property
def db_id(self):
doc = '''The database id in the application database that this file corresponds to'''
def fget(self):
match = re.search(r'_(\d+)$', self.rpath.rpartition('.')[0])
if match:
return int(match.group(1))
return property(fget=fget, doc=doc)
class BookList(_BookList):
def __init__(self, oncard, prefix):
@ -318,7 +288,12 @@ class BookList(_BookList):
imap = {}
for book, sony_id in zip(books, sony_ids):
if book is not None:
imap[book.application_id] = sony_id
db_id = book.application_id
if db_id is None:
db_id = book.db_id
print 'here', db_id
if db_id is not None:
imap[book.application_id] = sony_id
# filter the list, removing books not on device but on playlist
books = [i for i in books if i is not None]
# filter the order specification to the books we have

View File

@ -13,6 +13,7 @@ import os
import re
from calibre.devices.usbms.driver import USBMS
from calibre.devices.usbms.books import Book
from calibre.devices.prs505.books import BookList, fix_ids
from calibre.devices.prs505 import MEDIA_XML
from calibre.devices.prs505 import CACHE_XML
@ -59,8 +60,9 @@ class PRS505(USBMS):
METADATA_CACHE = "database/cache/metadata.calibre"
def initialize(self):
USBMS.initialize(self)
USBMS.initialize(self) # Must be first, so _class vars are set right
self.booklist_class = BookList
self.book_class = Book
def windows_filter_pnp_id(self, pnp_id):
return '_LAUNCHER' in pnp_id

View File

@ -37,12 +37,8 @@ class Book(MetaInformation):
else:
self.lpath = lpath
self.mime = mime_type_ext(path_to_ext(lpath))
self.size = os.stat(self.path).st_size if size == None else size
self.db_id = None
try:
self.datetime = time.gmtime(os.path.getctime(self.path))
except ValueError:
self.datetime = time.gmtime()
self.size = None # will be set later
self.datetime = time.gmtime()
if other:
self.smart_update(other)
@ -70,6 +66,16 @@ class Book(MetaInformation):
return spath == opath
@dynamic_property
def db_id(self):
doc = '''The database id in the application database that this file corresponds to'''
def fget(self):
match = re.search(r'_(\d+)$', self.lpath.rpartition('.')[0])
if match:
return int(match.group(1))
return None
return property(fget=fget, doc=doc)
@dynamic_property
def title_sorter(self):
doc = '''String to sort the title. If absent, title is returned'''
@ -81,13 +87,6 @@ class Book(MetaInformation):
def thumbnail(self):
return None
# def __str__(self):
# '''
# Return a utf-8 encoded string with title author and path information
# '''
# return self.title.encode('utf-8') + " by " + \
# self.authors.encode('utf-8') + " at " + self.path.encode('utf-8')
def smart_update(self, other):
'''
Merge the information in C{other} into self. In case of conflicts, the information
@ -115,3 +114,17 @@ class BookList(_BookList):
def set_tags(self, book, tags):
book.tags = tags
def add_book(self, book, collections=None):
'''
Add the book to the booklist. Intent is to maintain any device-internal
metadata
'''
if book not in self:
self.append(book)
def remove_book(self, book):
'''
Remove a book from the booklist. Correct any device metadata at the
same time
'''
self.remove(book)

View File

@ -34,6 +34,7 @@ class USBMS(CLI, Device):
def initialize(self):
Device.initialize(self)
self.booklist_class = BookList
self.book_class = Book
def get_device_information(self, end_session=True):
self.report_progress(1.0, _('Get device information...'))
@ -52,7 +53,9 @@ class USBMS(CLI, Device):
self.report_progress(1.0, _('Getting list of books on device...'))
return []
prefix = self._card_a_prefix if oncard == 'carda' else self._card_b_prefix if oncard == 'cardb' else self._main_prefix
prefix = self._card_a_prefix if oncard == 'carda' else \
self._card_b_prefix if oncard == 'cardb' \
else self._main_prefix
metadata = self.booklist_class(oncard, prefix)
ebook_dirs = self.EBOOK_DIR_CARD_A if oncard == 'carda' else \
@ -61,7 +64,6 @@ class USBMS(CLI, Device):
bl, need_sync = self.parse_metadata_cache(prefix, self.METADATA_CACHE,
self.booklist_class(oncard, prefix))
# make a dict cache of paths so the lookup in the loop below is faster.
bl_cache = {}
for idx,b in enumerate(bl):
@ -77,10 +79,10 @@ class USBMS(CLI, Device):
lpath = lpath[len(os.sep):]
idx = bl_cache.get(lpath.replace('\\', '/'), None)
if idx is not None:
item, changed = self.__class__.update_metadata_item(bl[idx])
item, changed = self.update_metadata_item(bl[idx])
self.count_found_in_bl += 1
else:
item = self.__class__.book_from_path(prefix, lpath)
item = self.book_from_path(prefix, lpath)
changed = True
metadata.append(item)
except: # Probably a filename encoding error
@ -175,7 +177,10 @@ class USBMS(CLI, Device):
lpath = path.partition(prefix)[2]
if lpath.startswith(os.sep):
lpath = lpath[len(os.sep):]
book = Book(prefix, lpath, other=info)
book = self.book_class(prefix, lpath, other=info)
if book.size is None:
book.size = os.stat(path).st_size
opts = self.settings()
collections = opts.extra_customization.split(',') if opts.extra_customization else []
booklists[blist].add_book(book, collections, *location[1:-1])
@ -229,19 +234,14 @@ class USBMS(CLI, Device):
self.report_progress(1.0, _('Sending metadata to device...'))
@classmethod
def parse_metadata_cache(cls, prefix, name, bl):
def parse_metadata_cache(self, prefix, name, bl):
js = []
need_sync = False
try:
with open(os.path.join(prefix, name), 'rb') as f:
js = json.load(f, encoding='utf-8')
for item in js:
lpath = item.get('lpath', None)
if not lpath or not os.path.exists(os.path.join(prefix, lpath)):
need_sync = True
continue
book = Book(prefix, lpath)
book = self.book_class(prefix, item.get('lpath', None))
for key in item.keys():
setattr(book, key, item[key])
bl.append(book)
@ -249,35 +249,33 @@ class USBMS(CLI, Device):
import traceback
traceback.print_exc()
bl = []
need_sync = True
return bl, need_sync
@classmethod
def update_metadata_item(cls, item):
def update_metadata_item(self, item):
changed = False
size = os.stat(item.path).st_size
if size != item.size:
changed = True
mi = cls.metadata_from_path(item.path)
mi = self.metadata_from_path(item.path)
item.smart_update(mi)
item.size = size
return item, changed
@classmethod
def metadata_from_path(cls, path):
return cls.metadata_from_formats([path])
def metadata_from_path(self, path):
return self.metadata_from_formats([path])
@classmethod
def metadata_from_formats(cls, fmts):
def metadata_from_formats(self, fmts):
from calibre.ebooks.metadata.meta import metadata_from_formats
from calibre.customize.ui import quick_metadata
with quick_metadata:
return metadata_from_formats(fmts)
@classmethod
def book_from_path(cls, prefix, path):
def book_from_path(self, prefix, path):
from calibre.ebooks.metadata import MetaInformation
if cls.settings().read_metadata or cls.MUST_READ_METADATA:
mi = cls.metadata_from_path(os.path.join(prefix, path))
if self.settings().read_metadata or self.MUST_READ_METADATA:
mi = self.metadata_from_path(os.path.join(prefix, path))
else:
from calibre.ebooks.metadata.meta import metadata_from_filename
mi = metadata_from_filename(os.path.basename(path),
@ -286,6 +284,6 @@ class USBMS(CLI, Device):
if mi is None:
mi = MetaInformation(os.path.splitext(os.path.basename(path))[0],
[_('Unknown')])
book = Book(prefix, path, other=mi)
mi.size = os.stat(os.path.join(prefix, path)).st_size
book = self.book_class(prefix, path, other=mi)
return book

View File

@ -523,7 +523,8 @@ class DeviceGUI(object):
d = ChooseFormatDialog(self, _('Choose format to send to device'),
self.device_manager.device.settings().format_map)
d.exec_()
fmt = d.format().lower()
if d.format():
fmt = d.format().lower()
dest, sub_dest = dest.split(':')
if dest in ('main', 'carda', 'cardb'):
if not self.device_connected or not self.device_manager:
@ -998,7 +999,7 @@ class DeviceGUI(object):
if changed:
self.library_view.model().refresh_ids(list(changed))
def book_on_device(self, index, format=None, reset=False):
def book_on_device(self, id, format=None, reset=False):
loc = [None, None, None]
if reset:
@ -1030,7 +1031,7 @@ class DeviceGUI(object):
if uuid is not None:
self.book_db_uuid_cache[i].add(uuid)
mi = self.library_view.model().db.get_metadata(index, index_is_id=True)
mi = self.library_view.model().db.get_metadata(id, index_is_id=True)
for i, l in enumerate(self.booklists()):
if mi.uuid in self.book_db_uuid_cache[i]:
loc[i] = True
@ -1038,7 +1039,7 @@ class DeviceGUI(object):
db_title = re.sub('(?u)\W|[_]', '', mi.title.lower())
cache = self.book_db_title_cache[i].get(db_title, None)
if cache:
if index in cache['db_ids']:
if id in cache['db_ids']:
loc[i] = True
break
if mi.authors and \
@ -1057,11 +1058,11 @@ class DeviceGUI(object):
mi = self.library_view.model().db.get_metadata(idx, index_is_id=False)
title = re.sub('(?u)\W|[_]', '', mi.title.lower())
if title not in self.db_book_title_cache:
self.db_book_title_cache[title] = {'authors':set(), 'db_ids':set()}
self.db_book_title_cache[title] = {'authors':{}, 'db_ids':{}}
authors = authors_to_string(mi.authors).lower() if mi.authors else ''
authors = re.sub('(?u)\W|[_]', '', authors)
self.db_book_title_cache[title]['authors'].add(authors)
self.db_book_title_cache[title]['db_ids'].add(mi.application_id)
self.db_book_title_cache[title]['authors'][authors] = mi
self.db_book_title_cache[title]['db_ids'][mi.application_id] = mi
self.db_book_uuid_cache.add(mi.uuid)
# Now iterate through all the books on the device, setting the in_library field
@ -1069,6 +1070,7 @@ class DeviceGUI(object):
# 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
resend_metadata = False
for booklist in booklists:
for book in booklist:
if getattr(book, 'uuid', None) in self.db_book_uuid_cache:
@ -1082,11 +1084,20 @@ class DeviceGUI(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])
resend_metadata = True
continue
if book.db_id in d['db_ids']:
book.in_library = True
book.smart_update(d['db_ids'][book.db_id])
resend_metadata = True
continue
book_authors = authors_to_string(book.authors).lower() if book.authors else ''
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])
resend_metadata = True
if resend_metadata:
# Correcting metadata cache on device.
self.device_manager.sync_booklists(None, booklists)

View File

@ -470,14 +470,14 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
im = PILImage.open(f)
im.convert('RGB').save(path, 'JPEG')
def book_on_device(self, index):
def book_on_device(self, id):
if callable(self.book_on_device_func):
return self.book_on_device_func(index)
return self.book_on_device_func(id)
return None
def book_on_device_string(self, index):
def book_on_device_string(self, id):
loc = []
on = self.book_on_device(index)
on = self.book_on_device(id)
if on is not None:
m, a, b = on
if m is not None: