mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Working JSON metadata along side Sony metadata
This commit is contained in:
parent
cd6c46dba5
commit
d1c040d546
@ -387,6 +387,9 @@ class BookList(list):
|
||||
__getslice__ = None
|
||||
__setslice__ = None
|
||||
|
||||
def __init__(self, oncard, prefix):
|
||||
pass
|
||||
|
||||
def supports_tags(self):
|
||||
''' Return True if the the device supports tags (collections) for this book list. '''
|
||||
raise NotImplementedError()
|
||||
@ -399,3 +402,17 @@ 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)
|
||||
|
@ -1,2 +1,6 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
MEDIA_XML = 'database/cache/media.xml'
|
||||
|
||||
CACHE_XML = 'Sony Reader/database/cache.xml'
|
||||
|
@ -5,13 +5,14 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
import re, time, functools
|
||||
from uuid import uuid4 as _uuid
|
||||
import xml.dom.minidom as dom
|
||||
from base64 import b64decode as decode
|
||||
from base64 import b64encode as encode
|
||||
|
||||
|
||||
from calibre.devices.interface import BookList as _BookList
|
||||
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)
|
||||
|
||||
@ -50,62 +51,7 @@ class book_metadata_field(object):
|
||||
obj.elem.setAttribute(self.attr, val)
|
||||
|
||||
|
||||
class Book(object):
|
||||
""" 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)
|
||||
|
||||
class Book(_Book):
|
||||
@dynamic_property
|
||||
def db_id(self):
|
||||
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 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):
|
||||
|
||||
def __init__(self, xml_file, mountpath, report_progress=None):
|
||||
_BookList.__init__(self)
|
||||
def __init__(self, oncard, prefix):
|
||||
_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)
|
||||
self.document = dom.parse(xml_file)
|
||||
self.root_element = self.document.documentElement
|
||||
self.mountpath = mountpath
|
||||
self.mountpath = prefix
|
||||
records = self.root_element.getElementsByTagName('records')
|
||||
self.tag_order = {}
|
||||
|
||||
if records:
|
||||
self.prefix = 'xs1:'
|
||||
self.root_element = records[0]
|
||||
else:
|
||||
self.prefix = ''
|
||||
|
||||
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))
|
||||
self.tag_order = {}
|
||||
|
||||
def max_id(self):
|
||||
max = 0
|
||||
@ -180,32 +110,32 @@ class BookList(_BookList):
|
||||
return child
|
||||
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 """
|
||||
book = self.book_by_path(name)
|
||||
if book is not None:
|
||||
self.remove_book(name)
|
||||
|
||||
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
|
||||
book.sony_id = cid
|
||||
self.append(book)
|
||||
try:
|
||||
sourceid = str(self[0].sourceid) if len(self) else '1'
|
||||
except:
|
||||
sourceid = '1'
|
||||
attrs = {
|
||||
"title" : mi.title,
|
||||
'titleSorter' : sortable_title(mi.title),
|
||||
"author" : mi.format_authors() if mi.format_authors() else _('Unknown'),
|
||||
"title" : book.title,
|
||||
'titleSorter' : sortable_title(book.title),
|
||||
"author" : book.format_authors() if book.format_authors() else _('Unknown'),
|
||||
"page":"0", "part":"0", "scale":"0", \
|
||||
"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():
|
||||
node.setAttributeNode(self.document.createAttribute(attr))
|
||||
node.setAttribute(attr, attrs[attr])
|
||||
try:
|
||||
w, h, data = mi.thumbnail
|
||||
w, h, data = book.thumbnail
|
||||
except:
|
||||
w, h, data = None, None, None
|
||||
|
||||
@ -218,14 +148,11 @@ class BookList(_BookList):
|
||||
th.appendChild(jpeg)
|
||||
node.appendChild(th)
|
||||
self.root_element.appendChild(node)
|
||||
book = Book(node, self.mountpath, [], prefix=self.prefix)
|
||||
book.datetime = ctime
|
||||
self.append(book)
|
||||
|
||||
tags = []
|
||||
for item in collections:
|
||||
item = item.strip()
|
||||
mitem = getattr(mi, item, None)
|
||||
mitem = getattr(book, item, None)
|
||||
titems = []
|
||||
if mitem:
|
||||
if isinstance(mitem, list):
|
||||
@ -241,37 +168,34 @@ class BookList(_BookList):
|
||||
tags.extend(titems)
|
||||
if tags:
|
||||
tags = list(set(tags))
|
||||
if hasattr(mi, 'tag_order'):
|
||||
self.tag_order.update(mi.tag_order)
|
||||
self.set_tags(book, tags)
|
||||
if hasattr(book, 'tag_order'):
|
||||
self.tag_order.update(book.tag_order)
|
||||
self.set_playlists(cid, tags)
|
||||
|
||||
def _delete_book(self, node):
|
||||
def _delete_node(self, node):
|
||||
nid = node.getAttribute('id')
|
||||
self.remove_from_playlists(nid)
|
||||
node.parentNode.removeChild(node)
|
||||
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.
|
||||
'''
|
||||
for book in self:
|
||||
if str(book.id) == str(cid):
|
||||
self.remove(book)
|
||||
self._delete_book(book.elem)
|
||||
for child in self.root_element.childNodes:
|
||||
if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("id"):
|
||||
if child.getAttribute('path') == lpath:
|
||||
self._delete_node(child)
|
||||
break
|
||||
|
||||
def remove_book(self, path):
|
||||
def remove_book(self, book):
|
||||
'''
|
||||
Remove DOM node corresponding to book with C{path == path}.
|
||||
Also remove book from any collections it is part of.
|
||||
'''
|
||||
for book in self:
|
||||
if path.endswith(book.rpath):
|
||||
self.remove(book)
|
||||
self._delete_book(book.elem)
|
||||
break
|
||||
self.delete_node(book.lpath)
|
||||
|
||||
def playlists(self):
|
||||
ans = []
|
||||
@ -343,11 +267,6 @@ class BookList(_BookList):
|
||||
pli.parentNode.removeChild(pli)
|
||||
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):
|
||||
self.remove_from_playlists(id)
|
||||
for collection in set(collections):
|
||||
@ -358,15 +277,6 @@ class BookList(_BookList):
|
||||
item.setAttribute('id', str(id))
|
||||
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):
|
||||
return self.document.documentElement.getAttribute('nextID')
|
||||
|
||||
@ -378,24 +288,38 @@ class BookList(_BookList):
|
||||
src = self.document.toxml('utf-8') + '\n'
|
||||
stream.write(src.replace("'", '''))
|
||||
|
||||
def book_by_id(self, id):
|
||||
for book in self:
|
||||
if str(book.id) == str(id):
|
||||
return book
|
||||
|
||||
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():
|
||||
pl = self.playlist_by_title(title)
|
||||
if not pl:
|
||||
continue
|
||||
db_ids = [i.getAttribute('id') for i in pl.childNodes if hasattr(i, 'getAttribute')]
|
||||
pl_book_ids = [getattr(self.book_by_id(i), 'db_id', None) for i in db_ids]
|
||||
# make a list of the 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 = {}
|
||||
for i, j in zip(pl_book_ids, db_ids):
|
||||
imap[i] = j
|
||||
pl_book_ids = [i for i in pl_book_ids if i is not None]
|
||||
ordered_ids = [i for i in self.tag_order[title] if i in pl_book_ids]
|
||||
for book, sony_id in zip(books, sony_ids):
|
||||
if book 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
|
||||
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):
|
||||
continue
|
||||
children = [i for i in pl.childNodes if hasattr(i, 'getAttribute')]
|
||||
@ -439,7 +363,6 @@ def fix_ids(main, carda, cardb):
|
||||
except KeyError:
|
||||
item.parentNode.removeChild(item)
|
||||
item.unlink()
|
||||
|
||||
db.reorder_playlists()
|
||||
|
||||
regen_ids(main)
|
||||
|
@ -11,15 +11,14 @@ Device driver for the SONY PRS-505
|
||||
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from itertools import cycle
|
||||
|
||||
from calibre.devices.usbms.cli import CLI
|
||||
from calibre.devices.usbms.device import Device
|
||||
from calibre.devices.usbms.driver import USBMS
|
||||
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__
|
||||
|
||||
class PRS505(CLI, Device):
|
||||
class PRS505(USBMS):
|
||||
|
||||
name = 'PRS-300/505 Device Interface'
|
||||
gui_name = 'SONY Reader'
|
||||
@ -46,9 +45,6 @@ class PRS505(CLI, Device):
|
||||
MAIN_MEMORY_VOLUME_LABEL = 'Sony Reader Main Memory'
|
||||
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__
|
||||
|
||||
SUPPORTS_SUB_DIRS = True
|
||||
@ -60,67 +56,18 @@ class PRS505(CLI, Device):
|
||||
'series, tags, authors'
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
if getattr(mi, 'application_id', None) is not None:
|
||||
base = fname.rpartition('.')[0]
|
||||
@ -129,90 +76,17 @@ class PRS505(CLI, Device):
|
||||
fname = base + suffix + '.' + fname.rpartition('.')[-1]
|
||||
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):
|
||||
print 'in sync_booklists'
|
||||
fix_ids(*booklists)
|
||||
if not os.path.exists(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)
|
||||
|
||||
def write_card_prefix(prefix, listid):
|
||||
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)
|
||||
if not os.path.exists(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_b_prefix, 2)
|
||||
|
||||
self.report_progress(1.0, _('Sending metadata to device...'))
|
||||
|
||||
USBMS.sync_booklists(self, booklists, end_session)
|
||||
|
||||
class PRS700(PRS505):
|
||||
|
||||
@ -241,5 +114,3 @@ class PRS700(PRS505):
|
||||
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_B_MEM = re.compile(r'Sony PRS-((700/[^:]+:)|((6|9)00 ))SD Media')
|
||||
|
||||
|
||||
|
@ -33,6 +33,8 @@ class Book(MetaInformation):
|
||||
self.path = os.path.join(prefix, lpath)
|
||||
if os.sep == '\\':
|
||||
self.path = self.path.replace('/', '\\')
|
||||
self.lpath = lpath.replace('\\', '/')
|
||||
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
|
||||
|
@ -31,32 +31,36 @@ class USBMS(CLI, Device):
|
||||
CAN_SET_METADATA = True
|
||||
METADATA_CACHE = 'metadata.calibre'
|
||||
|
||||
def initialize(self):
|
||||
Device.initialize(self)
|
||||
self.booklist_class = BookList
|
||||
|
||||
def get_device_information(self, end_session=True):
|
||||
self.report_progress(1.0, _('Get device information...'))
|
||||
return (self.get_gui_name(), '', '', '')
|
||||
|
||||
def books(self, oncard=None, end_session=True):
|
||||
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:
|
||||
self.report_progress(1.0, _('Getting list of books on device...'))
|
||||
return bl
|
||||
return []
|
||||
elif oncard == 'cardb' and not self._card_b_prefix:
|
||||
self.report_progress(1.0, _('Getting list of books on device...'))
|
||||
return bl
|
||||
return []
|
||||
elif oncard and oncard != 'carda' and oncard != 'cardb':
|
||||
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
|
||||
metadata = self.booklist_class(oncard, prefix)
|
||||
|
||||
ebook_dirs = self.EBOOK_DIR_CARD_A if oncard == 'carda' else \
|
||||
self.EBOOK_DIR_CARD_B if oncard == 'cardb' else \
|
||||
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.
|
||||
bl_cache = {}
|
||||
@ -109,7 +113,6 @@ class USBMS(CLI, Device):
|
||||
# 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.
|
||||
if self.count_found_in_bl != len(bl) or need_sync:
|
||||
print 'resync'
|
||||
if oncard == 'cardb':
|
||||
self.sync_booklists((None, None, metadata))
|
||||
elif oncard == 'carda':
|
||||
@ -122,7 +125,6 @@ class USBMS(CLI, Device):
|
||||
|
||||
def upload_books(self, files, names, on_card=None, end_session=True,
|
||||
metadata=None):
|
||||
|
||||
path = self._sanity_check(on_card, files)
|
||||
|
||||
paths = []
|
||||
@ -145,7 +147,6 @@ class USBMS(CLI, Device):
|
||||
self.report_progress((i+1) / float(len(files)), _('Transferring books to device...'))
|
||||
|
||||
self.report_progress(1.0, _('Transferring books to device...'))
|
||||
|
||||
return zip(paths, cycle([on_card]))
|
||||
|
||||
def upload_cover(self, path, filename, metadata):
|
||||
@ -174,11 +175,10 @@ class USBMS(CLI, Device):
|
||||
lpath = path.partition(prefix)[2]
|
||||
if lpath.startswith(os.sep):
|
||||
lpath = lpath[len(os.sep):]
|
||||
lpath = lpath.replace('\\', '/')
|
||||
book = Book(prefix, lpath, other=info)
|
||||
|
||||
if book not in booklists[blist]:
|
||||
booklists[blist].append(book)
|
||||
opts = self.settings()
|
||||
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...'))
|
||||
|
||||
@ -209,7 +209,7 @@ class USBMS(CLI, Device):
|
||||
for bl in booklists:
|
||||
for book in bl:
|
||||
if path.endswith(book.path):
|
||||
bl.remove(book)
|
||||
bl.remove_book(book)
|
||||
self.report_progress(1.0, _('Removing books from device metadata listing...'))
|
||||
|
||||
def sync_booklists(self, booklists, end_session=True):
|
||||
@ -217,7 +217,7 @@ class USBMS(CLI, Device):
|
||||
os.makedirs(self._main_prefix)
|
||||
|
||||
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):
|
||||
os.makedirs(prefix)
|
||||
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...'))
|
||||
|
||||
@classmethod
|
||||
def parse_metadata_cache(cls, prefix, name):
|
||||
def parse_metadata_cache(cls, prefix, name, bl):
|
||||
js = []
|
||||
bl = BookList()
|
||||
need_sync = False
|
||||
try:
|
||||
with open(os.path.join(prefix, name), 'rb') as f:
|
||||
@ -249,7 +248,7 @@ class USBMS(CLI, Device):
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
bl = BookList()
|
||||
bl = []
|
||||
return bl, need_sync
|
||||
|
||||
@classmethod
|
||||
|
@ -254,11 +254,11 @@ class MetaInformation(object):
|
||||
setattr(self, x, getattr(mi, x, None))
|
||||
|
||||
def print_all_attributes(self):
|
||||
for x in ('author_sort', 'title_sort', 'comments', 'category', 'publisher',
|
||||
'series', 'series_index', 'rating', 'isbn', 'language',
|
||||
for x in ('author', 'author_sort', 'title_sort', 'comments', 'category', 'publisher',
|
||||
'series', 'series_index', 'tags', 'rating', 'isbn', 'language',
|
||||
'application_id', 'manifest', 'toc', 'spine', 'guide', 'cover',
|
||||
'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc', 'pubdate',
|
||||
'rights', 'publication_type', 'uuid',
|
||||
'rights', 'publication_type', 'uuid', 'tag_order',
|
||||
):
|
||||
prints(x, getattr(self, x, 'None'))
|
||||
|
||||
@ -278,7 +278,7 @@ class MetaInformation(object):
|
||||
'isbn', 'application_id', 'manifest', 'spine', 'toc',
|
||||
'cover', 'language', 'guide', 'book_producer',
|
||||
'timestamp', 'lccn', 'lcc', 'ddc', 'pubdate', 'rights',
|
||||
'publication_type', 'uuid',):
|
||||
'publication_type', 'uuid', 'tag_order'):
|
||||
if hasattr(mi, attr):
|
||||
val = getattr(mi, attr)
|
||||
if val is not None:
|
||||
|
@ -821,7 +821,9 @@ class DeviceGUI(object):
|
||||
|
||||
def sync_to_device(self, on_card, delete_from_library,
|
||||
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:
|
||||
return
|
||||
|
||||
@ -842,8 +844,7 @@ class DeviceGUI(object):
|
||||
ids = iter(ids)
|
||||
for mi in metadata:
|
||||
if mi.cover and os.access(mi.cover, os.R_OK):
|
||||
mi.thumbnail = self.cover_to_thumbnail(open(mi.cover,
|
||||
'rb').read())
|
||||
mi.thumbnail = self.cover_to_thumbnail(open(mi.cover, 'rb').read())
|
||||
imetadata = iter(metadata)
|
||||
|
||||
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))
|
||||
|
||||
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:
|
||||
for fmt in settings.format_map:
|
||||
if fmt in list(set(settings.format_map).intersection(set(available_output_formats()))):
|
||||
@ -1039,7 +1042,8 @@ class DeviceGUI(object):
|
||||
loc[i] = True
|
||||
break
|
||||
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
|
||||
break
|
||||
return loc
|
||||
|
@ -17,7 +17,7 @@ from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, pyqtSignal, \
|
||||
SIGNAL, QObject, QSize, QModelIndex, QDate
|
||||
|
||||
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.gui2 import NONE, TableView, config, error_dialog, UNDEFINED_QDATE
|
||||
from calibre.gui2.dialogs.comments_dialog import CommentsDialog
|
||||
@ -1378,7 +1378,10 @@ class DeviceBooksModel(BooksModel):
|
||||
def libcmp(x, y):
|
||||
x, y = self.db[x].in_library, self.db[y].in_library
|
||||
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
|
||||
self.map.sort(cmp=fcmp, reverse=descending)
|
||||
if len(self.map) == len(self.db):
|
||||
@ -1446,9 +1449,9 @@ class DeviceBooksModel(BooksModel):
|
||||
au = self.db[self.map[row]].authors
|
||||
if not au:
|
||||
au = self.unknown
|
||||
if role == Qt.EditRole:
|
||||
# if role == Qt.EditRole:
|
||||
# return QVariant(au)
|
||||
return QVariant(authors_to_string(au))
|
||||
return QVariant(" & ".join(au))
|
||||
elif col == 2:
|
||||
size = self.db[self.map[row]].size
|
||||
return QVariant(BooksView.human_readable(size))
|
||||
@ -1501,7 +1504,7 @@ class DeviceBooksModel(BooksModel):
|
||||
self.db[idx].title = val
|
||||
self.db[idx].title_sorter = val
|
||||
elif col == 1:
|
||||
self.db[idx].authors = val
|
||||
self.db[idx].authors = string_to_authors(val)
|
||||
elif col == 4:
|
||||
tags = [i.strip() for i in val.split(',')]
|
||||
tags = [t for t in tags if t]
|
||||
|
Loading…
x
Reference in New Issue
Block a user