Working JSON metadata along side Sony metadata

This commit is contained in:
Charles Haley 2010-05-13 21:57:54 +01:00
parent cd6c46dba5
commit d1c040d546
9 changed files with 142 additions and 319 deletions

View File

@ -387,6 +387,9 @@ class BookList(list):
__getslice__ = None __getslice__ = None
__setslice__ = None __setslice__ = None
def __init__(self, oncard, prefix):
pass
def supports_tags(self): def supports_tags(self):
''' Return True if the the device supports tags (collections) for this book list. ''' ''' Return True if the the device supports tags (collections) for this book list. '''
raise NotImplementedError() raise NotImplementedError()
@ -399,3 +402,17 @@ class BookList(list):
''' '''
raise NotImplementedError() 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

@ -1,2 +1,6 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
MEDIA_XML = 'database/cache/media.xml'
CACHE_XML = 'Sony Reader/database/cache.xml'

View File

@ -5,13 +5,14 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import re, time, functools import re, time, functools
from uuid import uuid4 as _uuid from uuid import uuid4 as _uuid
import xml.dom.minidom as dom import xml.dom.minidom as dom
from base64 import b64decode as decode
from base64 import b64encode as encode from base64 import b64encode as encode
from calibre.devices.interface import BookList as _BookList from calibre.devices.interface import BookList as _BookList
from calibre.devices import strftime as _strftime from calibre.devices import strftime as _strftime
from calibre.devices import strptime from calibre.devices.usbms.books import Book as _Book
from calibre.devices.prs505 import MEDIA_XML
from calibre.devices.prs505 import CACHE_XML
strftime = functools.partial(_strftime, zone=time.gmtime) strftime = functools.partial(_strftime, zone=time.gmtime)
@ -50,62 +51,7 @@ class book_metadata_field(object):
obj.elem.setAttribute(self.attr, val) obj.elem.setAttribute(self.attr, val)
class Book(object): class Book(_Book):
""" Provides a view onto the XML element that represents a book """
title = book_metadata_field("title")
authors = book_metadata_field("author", \
formatter=lambda x: [x if x and x.strip() else _('Unknown')])
mime = book_metadata_field("mime")
rpath = book_metadata_field("path")
id = book_metadata_field("id", formatter=int)
sourceid = book_metadata_field("sourceid", formatter=int)
size = book_metadata_field("size", formatter=lambda x : int(float(x)))
# When setting this attribute you must use an epoch
datetime = book_metadata_field("date", formatter=strptime, setter=strftime)
@dynamic_property
def title_sorter(self):
doc = '''String to sort the title. If absent, title is returned'''
def fget(self):
src = self.elem.getAttribute('titleSorter').strip()
if not src:
src = self.title
return src
def fset(self, val):
self.elem.setAttribute('titleSorter', sortable_title(unicode(val)))
return property(doc=doc, fget=fget, fset=fset)
@dynamic_property
def thumbnail(self):
doc = \
"""
The thumbnail. Should be a height 68 image.
Setting is not supported.
"""
def fget(self):
th = self.elem.getElementsByTagName(self.prefix + "thumbnail")
if not len(th):
th = self.elem.getElementsByTagName("cache:thumbnail")
if len(th):
for n in th[0].childNodes:
if n.nodeType == n.ELEMENT_NODE:
th = n
break
rc = ""
for node in th.childNodes:
if node.nodeType == node.TEXT_NODE:
rc += node.data
return decode(rc)
return property(fget=fget, doc=doc)
@dynamic_property
def path(self):
doc = """ Absolute path to book on device. Setting not supported. """
def fget(self):
return self.mountpath + self.rpath
return property(fget=fget, doc=doc)
@dynamic_property @dynamic_property
def db_id(self): def db_id(self):
doc = '''The database id in the application database that this file corresponds to''' doc = '''The database id in the application database that this file corresponds to'''
@ -115,42 +61,26 @@ class Book(object):
return int(match.group(1)) return int(match.group(1))
return property(fget=fget, doc=doc) return property(fget=fget, doc=doc)
def __init__(self, node, mountpath, tags, prefix=""):
self.elem = node
self.prefix = prefix
self.tags = tags
self.mountpath = mountpath
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')
class BookList(_BookList): class BookList(_BookList):
def __init__(self, xml_file, mountpath, report_progress=None): def __init__(self, oncard, prefix):
_BookList.__init__(self) _BookList.__init__(self, oncard, prefix)
if prefix is None:
return
db = CACHE_XML if oncard else MEDIA_XML
xml_file = open(prefix + db, 'rb')
xml_file.seek(0) xml_file.seek(0)
self.document = dom.parse(xml_file) self.document = dom.parse(xml_file)
self.root_element = self.document.documentElement self.root_element = self.document.documentElement
self.mountpath = mountpath self.mountpath = prefix
records = self.root_element.getElementsByTagName('records') records = self.root_element.getElementsByTagName('records')
self.tag_order = {}
if records: if records:
self.prefix = 'xs1:' self.prefix = 'xs1:'
self.root_element = records[0] self.root_element = records[0]
else: else:
self.prefix = '' self.prefix = ''
self.tag_order = {}
nodes = self.root_element.childNodes
for i, book in enumerate(nodes):
if report_progress:
report_progress((i+1) / float(len(nodes)), _('Getting list of books on device...'))
if hasattr(book, 'tagName') and book.tagName.endswith('text'):
tags = [i.getAttribute('title') for i in self.get_playlists(book.getAttribute('id'))]
self.append(Book(book, mountpath, tags, prefix=self.prefix))
def max_id(self): def max_id(self):
max = 0 max = 0
@ -180,32 +110,32 @@ class BookList(_BookList):
return child return child
return None return None
def add_book(self, mi, name, collections, size, ctime): def add_book(self, book, collections):
if book in self:
return
""" Add a node into the DOM tree, representing a book """ """ Add a node into the DOM tree, representing a book """
book = self.book_by_path(name)
if book is not None:
self.remove_book(name)
node = self.document.createElement(self.prefix + "text") node = self.document.createElement(self.prefix + "text")
mime = MIME_MAP.get(name.rpartition('.')[-1].lower(), MIME_MAP['epub']) mime = MIME_MAP.get(book.lpath.rpartition('.')[-1].lower(), MIME_MAP['epub'])
cid = self.max_id()+1 cid = self.max_id()+1
book.sony_id = cid
self.append(book)
try: try:
sourceid = str(self[0].sourceid) if len(self) else '1' sourceid = str(self[0].sourceid) if len(self) else '1'
except: except:
sourceid = '1' sourceid = '1'
attrs = { attrs = {
"title" : mi.title, "title" : book.title,
'titleSorter' : sortable_title(mi.title), 'titleSorter' : sortable_title(book.title),
"author" : mi.format_authors() if mi.format_authors() else _('Unknown'), "author" : book.format_authors() if book.format_authors() else _('Unknown'),
"page":"0", "part":"0", "scale":"0", \ "page":"0", "part":"0", "scale":"0", \
"sourceid":sourceid, "id":str(cid), "date":"", \ "sourceid":sourceid, "id":str(cid), "date":"", \
"mime":mime, "path":name, "size":str(size) "mime":mime, "path":book.lpath, "size":str(book.size)
} }
for attr in attrs.keys(): for attr in attrs.keys():
node.setAttributeNode(self.document.createAttribute(attr)) node.setAttributeNode(self.document.createAttribute(attr))
node.setAttribute(attr, attrs[attr]) node.setAttribute(attr, attrs[attr])
try: try:
w, h, data = mi.thumbnail w, h, data = book.thumbnail
except: except:
w, h, data = None, None, None w, h, data = None, None, None
@ -218,14 +148,11 @@ class BookList(_BookList):
th.appendChild(jpeg) th.appendChild(jpeg)
node.appendChild(th) node.appendChild(th)
self.root_element.appendChild(node) self.root_element.appendChild(node)
book = Book(node, self.mountpath, [], prefix=self.prefix)
book.datetime = ctime
self.append(book)
tags = [] tags = []
for item in collections: for item in collections:
item = item.strip() item = item.strip()
mitem = getattr(mi, item, None) mitem = getattr(book, item, None)
titems = [] titems = []
if mitem: if mitem:
if isinstance(mitem, list): if isinstance(mitem, list):
@ -241,37 +168,34 @@ class BookList(_BookList):
tags.extend(titems) tags.extend(titems)
if tags: if tags:
tags = list(set(tags)) tags = list(set(tags))
if hasattr(mi, 'tag_order'): if hasattr(book, 'tag_order'):
self.tag_order.update(mi.tag_order) self.tag_order.update(book.tag_order)
self.set_tags(book, tags) self.set_playlists(cid, tags)
def _delete_book(self, node): def _delete_node(self, node):
nid = node.getAttribute('id') nid = node.getAttribute('id')
self.remove_from_playlists(nid) self.remove_from_playlists(nid)
node.parentNode.removeChild(node) node.parentNode.removeChild(node)
node.unlink() node.unlink()
def delete_book(self, cid): def delete_node(self, lpath):
''' '''
Remove DOM node corresponding to book with C{id == cid}. Remove DOM node corresponding to book with lpath.
Also remove book from any collections it is part of. Also remove book from any collections it is part of.
''' '''
for book in self: for child in self.root_element.childNodes:
if str(book.id) == str(cid): if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("id"):
self.remove(book) if child.getAttribute('path') == lpath:
self._delete_book(book.elem) self._delete_node(child)
break break
def remove_book(self, path): 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.
''' '''
for book in self: self.remove(book)
if path.endswith(book.rpath): self.delete_node(book.lpath)
self.remove(book)
self._delete_book(book.elem)
break
def playlists(self): def playlists(self):
ans = [] ans = []
@ -343,11 +267,6 @@ class BookList(_BookList):
pli.parentNode.removeChild(pli) pli.parentNode.removeChild(pli)
pli.unlink() pli.unlink()
def set_tags(self, book, tags):
tags = [t for t in tags if t]
book.tags = tags
self.set_playlists(book.id, tags)
def set_playlists(self, id, collections): def set_playlists(self, id, collections):
self.remove_from_playlists(id) self.remove_from_playlists(id)
for collection in set(collections): for collection in set(collections):
@ -358,15 +277,6 @@ class BookList(_BookList):
item.setAttribute('id', str(id)) item.setAttribute('id', str(id))
coll.appendChild(item) coll.appendChild(item)
def get_playlists(self, bookid):
ans = []
for pl in self.playlists():
for item in pl.childNodes:
if hasattr(item, 'tagName') and item.tagName.endswith('item'):
if item.getAttribute('id') == str(bookid):
ans.append(pl)
return ans
def next_id(self): def next_id(self):
return self.document.documentElement.getAttribute('nextID') return self.document.documentElement.getAttribute('nextID')
@ -378,27 +288,41 @@ class BookList(_BookList):
src = self.document.toxml('utf-8') + '\n' src = self.document.toxml('utf-8') + '\n'
stream.write(src.replace("'", '&apos;')) stream.write(src.replace("'", '&apos;'))
def book_by_id(self, id):
for book in self:
if str(book.id) == str(id):
return book
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:
continue continue
db_ids = [i.getAttribute('id') for i in pl.childNodes if hasattr(i, 'getAttribute')] # make a list of the ids
pl_book_ids = [getattr(self.book_by_id(i), 'db_id', None) for i in db_ids] sony_ids = [id.getAttribute('id') \
for id in pl.childNodes if hasattr(id, 'getAttribute')]
# convert IDs in playlist to a list of lpaths
sony_paths = [sony_id_cache[id] for id in sony_ids]
# create list of books containing lpaths
books = [books_lpath_cache.get(p, None) for p in sony_paths]
# create dict of db_id -> sony_id
imap = {} imap = {}
for i, j in zip(pl_book_ids, db_ids): for book, sony_id in zip(books, sony_ids):
imap[i] = j if book is not None:
pl_book_ids = [i for i in pl_book_ids if i is not None] imap[book.application_id] = sony_id
ordered_ids = [i for i in self.tag_order[title] if i in pl_book_ids] # 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
ordered_ids = [db_id for db_id in self.tag_order[title] if db_id in imap]
# rewrite the playlist in the correct order
if len(ordered_ids) < len(pl.childNodes): if len(ordered_ids) < len(pl.childNodes):
continue continue
children = [i for i in pl.childNodes if hasattr(i, 'getAttribute')] children = [i for i in pl.childNodes if hasattr(i, 'getAttribute')]
for child in children: for child in children:
pl.removeChild(child) pl.removeChild(child)
child.unlink() child.unlink()
@ -439,7 +363,6 @@ def fix_ids(main, carda, cardb):
except KeyError: except KeyError:
item.parentNode.removeChild(item) item.parentNode.removeChild(item)
item.unlink() item.unlink()
db.reorder_playlists() db.reorder_playlists()
regen_ids(main) regen_ids(main)

View File

@ -11,15 +11,14 @@ Device driver for the SONY PRS-505
import os import os
import re import re
import time
from itertools import cycle
from calibre.devices.usbms.cli import CLI from calibre.devices.usbms.driver import USBMS
from calibre.devices.usbms.device import Device
from calibre.devices.prs505.books import BookList, fix_ids from calibre.devices.prs505.books import BookList, fix_ids
from calibre.devices.prs505 import MEDIA_XML
from calibre.devices.prs505 import CACHE_XML
from calibre import __appname__ from calibre import __appname__
class PRS505(CLI, Device): class PRS505(USBMS):
name = 'PRS-300/505 Device Interface' name = 'PRS-300/505 Device Interface'
gui_name = 'SONY Reader' gui_name = 'SONY Reader'
@ -46,9 +45,6 @@ class PRS505(CLI, Device):
MAIN_MEMORY_VOLUME_LABEL = 'Sony Reader Main Memory' MAIN_MEMORY_VOLUME_LABEL = 'Sony Reader Main Memory'
STORAGE_CARD_VOLUME_LABEL = 'Sony Reader Storage Card' STORAGE_CARD_VOLUME_LABEL = 'Sony Reader Storage Card'
MEDIA_XML = 'database/cache/media.xml'
CACHE_XML = 'Sony Reader/database/cache.xml'
CARD_PATH_PREFIX = __appname__ CARD_PATH_PREFIX = __appname__
SUPPORTS_SUB_DIRS = True SUPPORTS_SUB_DIRS = True
@ -60,67 +56,18 @@ class PRS505(CLI, Device):
'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 initialize(self):
USBMS.initialize(self)
self.booklist_class = BookList
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
def open(self):
self.report_progress = lambda x, y: x
Device.open(self)
def write_cache(prefix):
try:
cachep = os.path.join(prefix, *(self.CACHE_XML.split('/')))
if not os.path.exists(cachep):
dname = os.path.dirname(cachep)
if not os.path.exists(dname):
try:
os.makedirs(dname, mode=0777)
except:
time.sleep(5)
os.makedirs(dname, mode=0777)
with open(cachep, 'wb') as f:
f.write(u'''<?xml version="1.0" encoding="UTF-8"?>
<cache xmlns="http://www.kinoma.com/FskCache/1">
</cache>
'''.encode('utf8'))
return True
except:
import traceback
traceback.print_exc()
return False
if self._card_a_prefix is not None:
if not write_cache(self._card_a_prefix):
self._card_a_prefix = None
if self._card_b_prefix is not None:
if not write_cache(self._card_b_prefix):
self._card_b_prefix = None
def get_device_information(self, end_session=True): def get_device_information(self, end_session=True):
return (self.gui_name, '', '', '') return (self.gui_name, '', '', '')
def books(self, oncard=None, end_session=True):
if oncard == 'carda' and not self._card_a_prefix:
self.report_progress(1.0, _('Getting list of books on device...'))
return []
elif oncard == 'cardb' and not self._card_b_prefix:
self.report_progress(1.0, _('Getting list of books on device...'))
return []
elif oncard and oncard != 'carda' and oncard != 'cardb':
self.report_progress(1.0, _('Getting list of books on device...'))
return []
db = self.__class__.CACHE_XML if oncard else self.__class__.MEDIA_XML
prefix = self._card_a_prefix if oncard == 'carda' else self._card_b_prefix if oncard == 'cardb' else self._main_prefix
bl = BookList(open(prefix + db, 'rb'), prefix, self.report_progress)
paths = bl.purge_corrupted_files()
for path in paths:
path = os.path.join(prefix, path)
if os.path.exists(path):
os.unlink(path)
self.report_progress(1.0, _('Getting list of books on device...'))
return bl
def filename_callback(self, fname, mi): def filename_callback(self, fname, mi):
if getattr(mi, 'application_id', None) is not None: if getattr(mi, 'application_id', None) is not None:
base = fname.rpartition('.')[0] base = fname.rpartition('.')[0]
@ -129,90 +76,17 @@ class PRS505(CLI, Device):
fname = base + suffix + '.' + fname.rpartition('.')[-1] fname = base + suffix + '.' + fname.rpartition('.')[-1]
return fname return fname
def upload_books(self, files, names, on_card=None, end_session=True,
metadata=None):
path = self._sanity_check(on_card, files)
paths, ctimes, sizes = [], [], []
names = iter(names)
metadata = iter(metadata)
for i, infile in enumerate(files):
mdata, fname = metadata.next(), names.next()
filepath = self.create_upload_path(path, mdata, fname)
paths.append(filepath)
self.put_file(infile, paths[-1], replace_file=True)
ctimes.append(os.path.getctime(paths[-1]))
sizes.append(os.stat(paths[-1]).st_size)
self.report_progress((i+1) / float(len(files)), _('Transferring books to device...'))
self.report_progress(1.0, _('Transferring books to device...'))
return zip(paths, sizes, ctimes, cycle([on_card]))
def add_books_to_metadata(self, locations, metadata, booklists):
if not locations or not metadata:
return
metadata = iter(metadata)
for location in locations:
info = metadata.next()
path = location[0]
oncard = location[3]
blist = 2 if oncard == 'cardb' else 1 if oncard == 'carda' else 0
if self._main_prefix and path.startswith(self._main_prefix):
name = path.replace(self._main_prefix, '')
elif self._card_a_prefix and path.startswith(self._card_a_prefix):
name = path.replace(self._card_a_prefix, '')
elif self._card_b_prefix and path.startswith(self._card_b_prefix):
name = path.replace(self._card_b_prefix, '')
name = name.replace('\\', '/')
name = name.replace('//', '/')
if name.startswith('/'):
name = name[1:]
opts = self.settings()
collections = opts.extra_customization.split(',') if opts.extra_customization else []
booklist = booklists[blist]
if not hasattr(booklist, 'add_book'):
raise ValueError(('Incorrect upload location %s. Did you choose the'
' correct card A or B, to send books to?')%oncard)
booklist.add_book(info, name, collections, *location[1:-1])
fix_ids(*booklists)
def delete_books(self, paths, end_session=True):
for i, path in enumerate(paths):
self.report_progress((i+1) / float(len(paths)), _('Removing books from device...'))
if os.path.exists(path):
os.unlink(path)
try:
os.removedirs(os.path.dirname(path))
except:
pass
self.report_progress(1.0, _('Removing books from device...'))
@classmethod
def remove_books_from_metadata(cls, paths, booklists):
for path in paths:
for bl in booklists:
if hasattr(bl, 'remove_book'):
bl.remove_book(path)
fix_ids(*booklists)
def sync_booklists(self, booklists, end_session=True): def sync_booklists(self, booklists, end_session=True):
print 'in sync_booklists'
fix_ids(*booklists) fix_ids(*booklists)
if not os.path.exists(self._main_prefix): if not os.path.exists(self._main_prefix):
os.makedirs(self._main_prefix) os.makedirs(self._main_prefix)
with open(self._main_prefix + self.__class__.MEDIA_XML, 'wb') as f: with open(self._main_prefix + MEDIA_XML, 'wb') as f:
booklists[0].write(f) booklists[0].write(f)
def write_card_prefix(prefix, listid): def write_card_prefix(prefix, listid):
if prefix is not None and hasattr(booklists[listid], 'write'): if prefix is not None and hasattr(booklists[listid], 'write'):
tgt = os.path.join(prefix, *(self.CACHE_XML.split('/'))) tgt = os.path.join(prefix, *(CACHE_XML.split('/')))
base = os.path.dirname(tgt) base = os.path.dirname(tgt)
if not os.path.exists(base): if not os.path.exists(base):
os.makedirs(base) os.makedirs(base)
@ -221,8 +95,7 @@ class PRS505(CLI, Device):
write_card_prefix(self._card_a_prefix, 1) write_card_prefix(self._card_a_prefix, 1)
write_card_prefix(self._card_b_prefix, 2) write_card_prefix(self._card_b_prefix, 2)
self.report_progress(1.0, _('Sending metadata to device...')) USBMS.sync_booklists(self, booklists, end_session)
class PRS700(PRS505): class PRS700(PRS505):
@ -241,5 +114,3 @@ class PRS700(PRS505):
OSX_MAIN_MEM = re.compile(r'Sony PRS-((700/[^:]+)|((6|9)00)) Media') OSX_MAIN_MEM = re.compile(r'Sony PRS-((700/[^:]+)|((6|9)00)) Media')
OSX_CARD_A_MEM = re.compile(r'Sony PRS-((700/[^:]+:)|((6|9)00 ))MS Media') OSX_CARD_A_MEM = re.compile(r'Sony PRS-((700/[^:]+:)|((6|9)00 ))MS Media')
OSX_CARD_B_MEM = re.compile(r'Sony PRS-((700/[^:]+:)|((6|9)00 ))SD Media') OSX_CARD_B_MEM = re.compile(r'Sony PRS-((700/[^:]+:)|((6|9)00 ))SD Media')

View File

@ -33,7 +33,9 @@ class Book(MetaInformation):
self.path = os.path.join(prefix, lpath) self.path = os.path.join(prefix, lpath)
if os.sep == '\\': if os.sep == '\\':
self.path = self.path.replace('/', '\\') self.path = self.path.replace('/', '\\')
self.lpath = lpath self.lpath = lpath.replace('\\', '/')
else:
self.lpath = lpath
self.mime = mime_type_ext(path_to_ext(lpath)) self.mime = mime_type_ext(path_to_ext(lpath))
self.size = os.stat(self.path).st_size if size == None else size self.size = os.stat(self.path).st_size if size == None else size
self.db_id = None self.db_id = None

View File

@ -31,32 +31,36 @@ class USBMS(CLI, Device):
CAN_SET_METADATA = True CAN_SET_METADATA = True
METADATA_CACHE = 'metadata.calibre' METADATA_CACHE = 'metadata.calibre'
def initialize(self):
Device.initialize(self)
self.booklist_class = BookList
def get_device_information(self, end_session=True): def get_device_information(self, end_session=True):
self.report_progress(1.0, _('Get device information...')) self.report_progress(1.0, _('Get device information...'))
return (self.get_gui_name(), '', '', '') return (self.get_gui_name(), '', '', '')
def books(self, oncard=None, end_session=True): def books(self, oncard=None, end_session=True):
from calibre.ebooks.metadata.meta import path_to_ext from calibre.ebooks.metadata.meta import path_to_ext
bl = BookList()
metadata = BookList()
need_sync = False
if oncard == 'carda' and not self._card_a_prefix: if oncard == 'carda' and not self._card_a_prefix:
self.report_progress(1.0, _('Getting list of books on device...')) self.report_progress(1.0, _('Getting list of books on device...'))
return bl return []
elif oncard == 'cardb' and not self._card_b_prefix: elif oncard == 'cardb' and not self._card_b_prefix:
self.report_progress(1.0, _('Getting list of books on device...')) self.report_progress(1.0, _('Getting list of books on device...'))
return bl return []
elif oncard and oncard != 'carda' and oncard != 'cardb': elif oncard and oncard != 'carda' and oncard != 'cardb':
self.report_progress(1.0, _('Getting list of books on device...')) self.report_progress(1.0, _('Getting list of books on device...'))
return bl 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 \ 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) 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. # make a dict cache of paths so the lookup in the loop below is faster.
bl_cache = {} bl_cache = {}
@ -109,7 +113,6 @@ class USBMS(CLI, Device):
# find on the device. If need_sync is True then there were either items # find on the device. If need_sync is True then there were either items
# on the device that were not in bl or some of the items were changed. # on the device that were not in bl or some of the items were changed.
if self.count_found_in_bl != len(bl) or need_sync: if self.count_found_in_bl != len(bl) or need_sync:
print 'resync'
if oncard == 'cardb': if oncard == 'cardb':
self.sync_booklists((None, None, metadata)) self.sync_booklists((None, None, metadata))
elif oncard == 'carda': elif oncard == 'carda':
@ -122,7 +125,6 @@ class USBMS(CLI, Device):
def upload_books(self, files, names, on_card=None, end_session=True, def upload_books(self, files, names, on_card=None, end_session=True,
metadata=None): metadata=None):
path = self._sanity_check(on_card, files) path = self._sanity_check(on_card, files)
paths = [] paths = []
@ -145,7 +147,6 @@ class USBMS(CLI, Device):
self.report_progress((i+1) / float(len(files)), _('Transferring books to device...')) self.report_progress((i+1) / float(len(files)), _('Transferring books to device...'))
self.report_progress(1.0, _('Transferring books to device...')) self.report_progress(1.0, _('Transferring books to device...'))
return zip(paths, cycle([on_card])) return zip(paths, cycle([on_card]))
def upload_cover(self, path, filename, metadata): def upload_cover(self, path, filename, metadata):
@ -174,11 +175,10 @@ class USBMS(CLI, Device):
lpath = path.partition(prefix)[2] lpath = path.partition(prefix)[2]
if lpath.startswith(os.sep): if lpath.startswith(os.sep):
lpath = lpath[len(os.sep):] lpath = lpath[len(os.sep):]
lpath = lpath.replace('\\', '/')
book = Book(prefix, lpath, other=info) book = Book(prefix, lpath, other=info)
opts = self.settings()
if book not in booklists[blist]: collections = opts.extra_customization.split(',') if opts.extra_customization else []
booklists[blist].append(book) 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...'))
@ -209,7 +209,7 @@ class USBMS(CLI, Device):
for bl in booklists: for bl in booklists:
for book in bl: for book in bl:
if path.endswith(book.path): if path.endswith(book.path):
bl.remove(book) bl.remove_book(book)
self.report_progress(1.0, _('Removing books from device metadata listing...')) self.report_progress(1.0, _('Removing books from device metadata listing...'))
def sync_booklists(self, booklists, end_session=True): def sync_booklists(self, booklists, end_session=True):
@ -217,7 +217,7 @@ class USBMS(CLI, Device):
os.makedirs(self._main_prefix) os.makedirs(self._main_prefix)
def write_prefix(prefix, listid): def write_prefix(prefix, listid):
if prefix is not None and isinstance(booklists[listid], BookList): if prefix is not None and isinstance(booklists[listid], self.booklist_class):
if not os.path.exists(prefix): if not os.path.exists(prefix):
os.makedirs(prefix) os.makedirs(prefix)
js = [item.to_json() for item in booklists[listid]] js = [item.to_json() for item in booklists[listid]]
@ -230,9 +230,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): def parse_metadata_cache(cls, prefix, name, bl):
js = [] js = []
bl = BookList()
need_sync = False need_sync = False
try: try:
with open(os.path.join(prefix, name), 'rb') as f: with open(os.path.join(prefix, name), 'rb') as f:
@ -249,7 +248,7 @@ class USBMS(CLI, Device):
except: except:
import traceback import traceback
traceback.print_exc() traceback.print_exc()
bl = BookList() bl = []
return bl, need_sync return bl, need_sync
@classmethod @classmethod

View File

@ -254,11 +254,11 @@ class MetaInformation(object):
setattr(self, x, getattr(mi, x, None)) setattr(self, x, getattr(mi, x, None))
def print_all_attributes(self): def print_all_attributes(self):
for x in ('author_sort', 'title_sort', 'comments', 'category', 'publisher', for x in ('author', 'author_sort', 'title_sort', 'comments', 'category', 'publisher',
'series', 'series_index', 'rating', 'isbn', 'language', 'series', 'series_index', 'tags', 'rating', 'isbn', 'language',
'application_id', 'manifest', 'toc', 'spine', 'guide', 'cover', 'application_id', 'manifest', 'toc', 'spine', 'guide', 'cover',
'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc', 'pubdate', 'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc', 'pubdate',
'rights', 'publication_type', 'uuid', 'rights', 'publication_type', 'uuid', 'tag_order',
): ):
prints(x, getattr(self, x, 'None')) prints(x, getattr(self, x, 'None'))
@ -278,7 +278,7 @@ class MetaInformation(object):
'isbn', 'application_id', 'manifest', 'spine', 'toc', 'isbn', 'application_id', 'manifest', 'spine', 'toc',
'cover', 'language', 'guide', 'book_producer', 'cover', 'language', 'guide', 'book_producer',
'timestamp', 'lccn', 'lcc', 'ddc', 'pubdate', 'rights', 'timestamp', 'lccn', 'lcc', 'ddc', 'pubdate', 'rights',
'publication_type', 'uuid',): 'publication_type', 'uuid', 'tag_order'):
if hasattr(mi, attr): if hasattr(mi, attr):
val = getattr(mi, attr) val = getattr(mi, attr)
if val is not None: if val is not None:

View File

@ -821,7 +821,9 @@ class DeviceGUI(object):
def sync_to_device(self, on_card, delete_from_library, def sync_to_device(self, on_card, delete_from_library,
specific_format=None, send_ids=None, do_auto_convert=True): specific_format=None, send_ids=None, do_auto_convert=True):
ids = [self.library_view.model().id(r) for r in self.library_view.selectionModel().selectedRows()] if send_ids is None else send_ids ids = [self.library_view.model().id(r) \
for r in self.library_view.selectionModel().selectedRows()] \
if send_ids is None else send_ids
if not self.device_manager or not ids or len(ids) == 0: if not self.device_manager or not ids or len(ids) == 0:
return return
@ -842,8 +844,7 @@ class DeviceGUI(object):
ids = iter(ids) ids = iter(ids)
for mi in metadata: for mi in metadata:
if mi.cover and os.access(mi.cover, os.R_OK): if mi.cover and os.access(mi.cover, os.R_OK):
mi.thumbnail = self.cover_to_thumbnail(open(mi.cover, mi.thumbnail = self.cover_to_thumbnail(open(mi.cover, 'rb').read())
'rb').read())
imetadata = iter(metadata) imetadata = iter(metadata)
files = [getattr(f, 'name', None) for f in _files] files = [getattr(f, 'name', None) for f in _files]
@ -890,7 +891,9 @@ class DeviceGUI(object):
bad.append(self.library_view.model().db.title(id, index_is_id=True)) bad.append(self.library_view.model().db.title(id, index_is_id=True))
if auto != []: if auto != []:
format = specific_format if specific_format in list(set(settings.format_map).intersection(set(available_output_formats()))) else None format = specific_format if specific_format in \
list(set(settings.format_map).intersection(set(available_output_formats()))) \
else None
if not format: if not format:
for fmt in settings.format_map: for fmt in settings.format_map:
if fmt in list(set(settings.format_map).intersection(set(available_output_formats()))): if fmt in list(set(settings.format_map).intersection(set(available_output_formats()))):
@ -1039,7 +1042,8 @@ class DeviceGUI(object):
loc[i] = True loc[i] = True
break break
if mi.authors and \ if mi.authors and \
re.sub('(?u)\W|[_]', '', mi.authors.lower()) in cache['authors']: re.sub('(?u)\W|[_]', '', authors_to_string(mi.authors).lower()) \
in cache['authors']:
loc[i] = True loc[i] = True
break break
return loc return loc

View File

@ -17,7 +17,7 @@ from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, pyqtSignal, \
SIGNAL, QObject, QSize, QModelIndex, QDate SIGNAL, QObject, QSize, QModelIndex, QDate
from calibre import strftime from calibre import strftime
from calibre.ebooks.metadata import fmt_sidx, authors_to_string from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
from calibre.gui2 import NONE, TableView, config, error_dialog, UNDEFINED_QDATE from calibre.gui2 import NONE, TableView, config, error_dialog, UNDEFINED_QDATE
from calibre.gui2.dialogs.comments_dialog import CommentsDialog from calibre.gui2.dialogs.comments_dialog import CommentsDialog
@ -1378,7 +1378,10 @@ class DeviceBooksModel(BooksModel):
def libcmp(x, y): def libcmp(x, y):
x, y = self.db[x].in_library, self.db[y].in_library x, y = self.db[x].in_library, self.db[y].in_library
return cmp(x, y) return cmp(x, y)
fcmp = strcmp('title_sorter') if col == 0 else strcmp('authors') if col == 1 else \ def authorcmp(x, y):
x, y = authors_to_string(self.db[x].authors), authors_to_string(self.db[y].authors)
return cmp(x, y)
fcmp = strcmp('title_sorter') if col == 0 else authorcmp if col == 1 else \
sizecmp if col == 2 else datecmp if col == 3 else tagscmp if col == 4 else libcmp sizecmp if col == 2 else datecmp if col == 3 else tagscmp if col == 4 else libcmp
self.map.sort(cmp=fcmp, reverse=descending) self.map.sort(cmp=fcmp, reverse=descending)
if len(self.map) == len(self.db): if len(self.map) == len(self.db):
@ -1446,9 +1449,9 @@ class DeviceBooksModel(BooksModel):
au = self.db[self.map[row]].authors au = self.db[self.map[row]].authors
if not au: if not au:
au = self.unknown au = self.unknown
if role == Qt.EditRole: # if role == Qt.EditRole:
return QVariant(authors_to_string(au)) # return QVariant(au)
return QVariant(" & ".join(au)) return QVariant(authors_to_string(au))
elif col == 2: elif col == 2:
size = self.db[self.map[row]].size size = self.db[self.map[row]].size
return QVariant(BooksView.human_readable(size)) return QVariant(BooksView.human_readable(size))
@ -1501,7 +1504,7 @@ class DeviceBooksModel(BooksModel):
self.db[idx].title = val self.db[idx].title = val
self.db[idx].title_sorter = val self.db[idx].title_sorter = val
elif col == 1: elif col == 1:
self.db[idx].authors = val self.db[idx].authors = string_to_authors(val)
elif col == 4: elif col == 4:
tags = [i.strip() for i in val.split(',')] tags = [i.strip() for i in val.split(',')]
tags = [t for t in tags if t] tags = [t for t in tags if t]