Merge from custcol trunk

This commit is contained in:
Charles Haley 2010-05-20 08:40:00 +01:00
commit 07246d842a
9 changed files with 187 additions and 153 deletions

View File

@ -442,7 +442,7 @@ from calibre.devices.irexdr.driver import IREXDR1000, IREXDR800
from calibre.devices.jetbook.driver import JETBOOK from calibre.devices.jetbook.driver import JETBOOK
from calibre.devices.kindle.driver import KINDLE, KINDLE2, KINDLE_DX from calibre.devices.kindle.driver import KINDLE, KINDLE2, KINDLE_DX
from calibre.devices.nook.driver import NOOK from calibre.devices.nook.driver import NOOK
from calibre.devices.prs505.driver import PRS505, PRS700 from calibre.devices.prs505.driver import PRS505
from calibre.devices.android.driver import ANDROID, S60 from calibre.devices.android.driver import ANDROID, S60
from calibre.devices.nokia.driver import N770, N810 from calibre.devices.nokia.driver import N770, N810
from calibre.devices.eslick.driver import ESLICK from calibre.devices.eslick.driver import ESLICK
@ -510,7 +510,6 @@ plugins += [
KINDLE_DX, KINDLE_DX,
NOOK, NOOK,
PRS505, PRS505,
PRS700,
ANDROID, ANDROID,
S60, S60,
N770, N770,

View File

@ -293,8 +293,7 @@ class DevicePlugin(Plugin):
put the book. len(metadata) == len(files). Apart from the regular put the book. len(metadata) == len(files). Apart from the regular
cover (path to cover), there may also be a thumbnail attribute, which should cover (path to cover), there may also be a thumbnail attribute, which should
be used in preference. The thumbnail attribute is of the form be used in preference. The thumbnail attribute is of the form
(width, height, cover_data as jpeg). In addition the MetaInformation (width, height, cover_data as jpeg).
objects can have a tag_order attribute.
''' '''
raise NotImplementedError() raise NotImplementedError()
@ -396,14 +395,6 @@ class BookList(list):
''' 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()
def set_tags(self, book, tags):
'''
Set the tags for C{book} to C{tags}.
@param tags: A list of strings. Can be empty.
@param book: A book object that is in this BookList.
'''
raise NotImplementedError()
def add_book(self, book, replace_metadata): 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

View File

@ -1,12 +1,9 @@
# -*- coding: utf-8 -*-
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net> ' \ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
'2009, John Schember <john at nachtimwald.com>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
''' '''
Device driver for the SONY PRS-505 Device driver for the SONY devices
''' '''
import os import os
@ -20,27 +17,33 @@ from calibre import __appname__
class PRS505(USBMS): class PRS505(USBMS):
name = 'PRS-300/505 Device Interface' name = 'SONY Device Interface'
gui_name = 'SONY Reader' gui_name = 'SONY Reader'
description = _('Communicate with the Sony PRS-300/505/500 eBook reader.') description = _('Communicate with all the Sony eBook readers.')
author = 'Kovid Goyal and John Schember' author = 'Kovid Goyal'
supported_platforms = ['windows', 'osx', 'linux'] supported_platforms = ['windows', 'osx', 'linux']
path_sep = '/' path_sep = '/'
FORMATS = ['epub', 'lrf', 'lrx', 'rtf', 'pdf', 'txt'] FORMATS = ['epub', 'lrf', 'lrx', 'rtf', 'pdf', 'txt']
VENDOR_ID = [0x054c] #: SONY Vendor Id VENDOR_ID = [0x054c] #: SONY Vendor Id
PRODUCT_ID = [0x031e] #: Product Id for the PRS 300/505/new 500 PRODUCT_ID = [0x031e]
BCD = [0x229, 0x1000, 0x22a] BCD = [0x229, 0x1000, 0x22a, 0x31a]
VENDOR_NAME = 'SONY' VENDOR_NAME = 'SONY'
WINDOWS_MAIN_MEM = re.compile('PRS-(505|300|500)') WINDOWS_MAIN_MEM = re.compile(
WINDOWS_CARD_A_MEM = re.compile(r'PRS-(505|500)[#/]\S+:MS') r'(PRS-(505|300|500))|'
WINDOWS_CARD_B_MEM = re.compile(r'PRS-(505|500)[#/]\S+:SD') r'(PRS-((700[#/])|((6|9)00&)))'
)
WINDOWS_CARD_A_MEM = re.compile(
r'(PRS-(505|500)[#/]\S+:MS)|'
r'(PRS-((700[/#]\S+:)|((6|9)00[#_]))MS)'
)
WINDOWS_CARD_B_MEM = re.compile(
r'(PRS-(505|500)[#/]\S+:SD)|'
r'(PRS-((700[/#]\S+:)|((6|9)00[#_]))SD)'
)
OSX_MAIN_MEM = re.compile(r'Sony PRS-(((505|300|500)/[^:]+)|(300)) Media')
OSX_CARD_A_MEM = re.compile(r'Sony PRS-(505|500)/[^:]+:MS Media')
OSX_CARD_B_MEM = re.compile(r'Sony PRS-(505|500)/[^:]+:SD Media')
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'
@ -101,27 +104,12 @@ class PRS505(USBMS):
opts = self.settings() opts = self.settings()
collections = ['series', 'tags'] collections = ['series', 'tags']
if opts.extra_customization: if opts.extra_customization:
collections = opts.extra_customization.split(',') collections = [x.strip() for x in
opts.extra_customization.split(',')]
c.update(blists, collections) c.update(blists, collections)
c.write() c.write()
USBMS.sync_booklists(self, booklists, end_session=end_session) USBMS.sync_booklists(self, booklists, end_session=end_session)
class PRS700(PRS505):
name = 'PRS-600/700/900 Device Interface'
description = _('Communicate with the Sony PRS-600/700/900 eBook reader.')
author = 'Kovid Goyal and John Schember'
gui_name = 'SONY Reader'
supported_platforms = ['windows', 'osx', 'linux']
BCD = [0x31a]
WINDOWS_MAIN_MEM = re.compile('PRS-((700[#/])|((6|9)00&))')
WINDOWS_CARD_A_MEM = re.compile(r'PRS-((700[/#]\S+:)|((6|9)00[#_]))MS')
WINDOWS_CARD_B_MEM = re.compile(r'PRS-((700[/#]\S+:)|((6|9)00[#_]))SD')
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')

View File

@ -101,16 +101,25 @@ class XMLCache(object):
# Playlist management {{{ # Playlist management {{{
def purge_broken_playlist_items(self, root): def purge_broken_playlist_items(self, root):
for item in root.xpath( for pl in root.xpath('//*[local-name()="playlist"]'):
'//*[local-name()="playlist"]/*[local-name()="item"]'): seen = set([])
id_ = item.get('id', None) for item in list(pl):
if id_ is None or not root.xpath( id_ = item.get('id', None)
'//*[local-name()!="item" and @id="%s"]'%id_): if id_ is None or id_ in seen or not root.xpath(
if DEBUG: '//*[local-name()!="item" and @id="%s"]'%id_):
prints('Purging broken playlist item:', if DEBUG:
etree.tostring(item, with_tail=False)) if id_ is None:
item.getparent().remove(item) cause = 'invalid id'
elif id_ in seen:
cause = 'duplicate item'
else:
cause = 'id not found'
prints('Purging broken playlist item:',
id_, 'from playlist:', pl.get('title', None),
'because:', cause)
item.getparent().remove(item)
continue
seen.add(id_)
def prune_empty_playlists(self): def prune_empty_playlists(self):
for i, root in self.record_roots.items(): for i, root in self.record_roots.items():
@ -144,7 +153,7 @@ class XMLCache(object):
self.ensure_unique_playlist_titles() self.ensure_unique_playlist_titles()
self.prune_empty_playlists() self.prune_empty_playlists()
for i, root in self.record_roots.items(): for i, root in self.record_roots.items():
ans[i] = {} ans[i] = []
for playlist in root.xpath('//*[local-name()="playlist"]'): for playlist in root.xpath('//*[local-name()="playlist"]'):
items = [] items = []
for item in playlist: for item in playlist:
@ -153,7 +162,7 @@ class XMLCache(object):
'//*[local-name()="text" and @id="%s"]'%id_) '//*[local-name()="text" and @id="%s"]'%id_)
if records: if records:
items.append(records[0]) items.append(records[0])
ans[i] = {playlist.get('title'):items} ans[i].append((playlist.get('title'), items))
return ans return ans
def get_or_create_playlist(self, bl_idx, title): def get_or_create_playlist(self, bl_idx, title):
@ -175,6 +184,8 @@ class XMLCache(object):
# }}} # }}}
def fix_ids(self): # {{{ def fix_ids(self): # {{{
if DEBUG:
prints('Running fix_ids()')
def ensure_numeric_ids(root): def ensure_numeric_ids(root):
idmap = {} idmap = {}
@ -255,7 +266,19 @@ class XMLCache(object):
def update_booklist(self, bl, bl_index): def update_booklist(self, bl, bl_index):
if bl_index not in self.record_roots: if bl_index not in self.record_roots:
return return
if DEBUG:
prints('Updating JSON cache:', bl_index)
root = self.record_roots[bl_index] root = self.record_roots[bl_index]
pmap = self.get_playlist_map()[bl_index]
playlist_map = {}
for title, records in pmap:
for record in records:
path = record.get('path', None)
if path:
if path not in playlist_map:
playlist_map[path] = []
playlist_map[path].append(title)
for book in bl: for book in bl:
record = self.book_by_lpath(book.lpath, root) record = self.book_by_lpath(book.lpath, root)
if record is not None: if record is not None:
@ -282,6 +305,10 @@ class XMLCache(object):
book.thumbnail = raw book.thumbnail = raw
break break
break break
if book.lpath in playlist_map:
tags = playlist_map[book.lpath]
book.device_collections = tags
# }}} # }}}
# Update XML from JSON {{{ # Update XML from JSON {{{
@ -290,7 +317,7 @@ class XMLCache(object):
for i, booklist in booklists.items(): for i, booklist in booklists.items():
if DEBUG: if DEBUG:
prints('Updating booklist:', i) prints('Updating XML Cache:', i)
root = self.record_roots[i] root = self.record_roots[i]
for book in booklist: for book in booklist:
path = os.path.join(self.prefixes[i], *(book.lpath.split('/'))) path = os.path.join(self.prefixes[i], *(book.lpath.split('/')))
@ -304,6 +331,10 @@ class XMLCache(object):
self.fix_ids() self.fix_ids()
# This is needed to update device_collections
for i, booklist in booklists.items():
self.update_booklist(booklist, i)
def update_playlists(self, bl_index, root, booklist, playlist_map, def update_playlists(self, bl_index, root, booklist, playlist_map,
collections_attributes): collections_attributes):
collections = booklist.get_collections(collections_attributes) collections = booklist.get_collections(collections_attributes)
@ -340,7 +371,25 @@ class XMLCache(object):
nsmap=playlist.nsmap, attrib={'id':id_}) nsmap=playlist.nsmap, attrib={'id':id_})
playlist.append(item) playlist.append(item)
# Delete playlist entries not in collections
for playlist in root.xpath('//*[local-name()="playlist"]'):
title = playlist.get('title', None)
if title not in collections:
if DEBUG:
prints('Deleting playlist:', playlist.get('title', ''))
playlist.getparent().remove(playlist)
continue
books = collections[title]
records = [self.book_by_lpath(b.lpath, root) for b in books]
records = [x for x in records if x is not None]
ids = [x.get('id', None) for x in records]
ids = [x for x in ids if x is not None]
for item in list(playlist):
if item.get('id', None) not in ids:
if DEBUG:
prints('Deleting item:', item.get('id', ''),
'from playlist:', playlist.get('title', ''))
playlist.remove(item)
def create_text_record(self, root, bl_id, lpath): def create_text_record(self, root, bl_id, lpath):
namespace = self.namespaces[bl_id] namespace = self.namespaces[bl_id]
@ -395,8 +444,19 @@ class XMLCache(object):
child.iterchildren(reversed=True).next().tail = '\n'+'\t'*level child.iterchildren(reversed=True).next().tail = '\n'+'\t'*level
root.iterchildren(reversed=True).next().tail = '\n'+'\t'*(level-1) root.iterchildren(reversed=True).next().tail = '\n'+'\t'*(level-1)
def move_playlists_to_bottom(self):
for root in self.record_roots.values():
seen = []
for pl in root.xpath('//*[local-name()="playlist"]'):
pl.getparent().remove(pl)
seen.append(pl)
for pl in seen:
root.append(pl)
def write(self): def write(self):
for i, path in self.paths.items(): for i, path in self.paths.items():
self.move_playlists_to_bottom()
self.cleanup_whitespace(i) self.cleanup_whitespace(i)
raw = etree.tostring(self.roots[i], encoding='UTF-8', raw = etree.tostring(self.roots[i], encoding='UTF-8',
xml_declaration=True) xml_declaration=True)

View File

@ -14,14 +14,14 @@ from calibre import isbytestring
class Book(MetaInformation): class Book(MetaInformation):
BOOK_ATTRS = ['lpath', 'size', 'mime'] BOOK_ATTRS = ['lpath', 'size', 'mime', 'device_collections']
JSON_ATTRS = [ JSON_ATTRS = [
'lpath', 'title', 'authors', 'mime', 'size', 'tags', 'author_sort', 'lpath', 'title', 'authors', 'mime', 'size', 'tags', 'author_sort',
'title_sort', 'comments', 'category', 'publisher', 'series', 'title_sort', 'comments', 'category', 'publisher', 'series',
'series_index', 'rating', 'isbn', 'language', 'application_id', 'series_index', 'rating', 'isbn', 'language', 'application_id',
'book_producer', 'lccn', 'lcc', 'ddc', 'rights', 'publication_type', 'book_producer', 'lccn', 'lcc', 'ddc', 'rights', 'publication_type',
'uuid' 'uuid',
] ]
def __init__(self, prefix, lpath, size=None, other=None): def __init__(self, prefix, lpath, size=None, other=None):
@ -29,6 +29,7 @@ class Book(MetaInformation):
MetaInformation.__init__(self, '') MetaInformation.__init__(self, '')
self.device_collections = []
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('/', '\\')
@ -45,27 +46,7 @@ class Book(MetaInformation):
self.smart_update(other) self.smart_update(other)
def __eq__(self, other): def __eq__(self, other):
spath = self.path return self.path == getattr(other, 'path', None)
opath = other.path
if not isinstance(self.path, unicode):
try:
spath = unicode(self.path)
except:
try:
spath = self.path.decode(filesystem_encoding)
except:
spath = self.path
if not isinstance(other.path, unicode):
try:
opath = unicode(other.path)
except:
try:
opath = other.path.decode(filesystem_encoding)
except:
opath = other.path
return spath == opath
@dynamic_property @dynamic_property
def db_id(self): def db_id(self):
@ -119,9 +100,6 @@ class BookList(_BookList):
def supports_tags(self): def supports_tags(self):
return True return True
def set_tags(self, book, tags):
book.tags = tags
def add_book(self, book, replace_metadata): def add_book(self, book, replace_metadata):
if book not in self: if book not in self:
self.append(book) self.append(book)
@ -134,7 +112,9 @@ class BookList(_BookList):
def get_collections(self, collection_attributes): def get_collections(self, collection_attributes):
collections = {} collections = {}
series_categories = set([]) series_categories = set([])
collection_attributes = list(collection_attributes)+['device_collections']
for attr in collection_attributes: for attr in collection_attributes:
attr = attr.strip()
for book in self: for book in self:
val = getattr(book, attr, None) val = getattr(book, attr, None)
if not val: continue if not val: continue
@ -145,11 +125,17 @@ class BookList(_BookList):
elif isinstance(val, unicode): elif isinstance(val, unicode):
val = [val] val = [val]
for category in val: for category in val:
if attr == 'tags' and len(category) > 1 and \
category[0] == '[' and category[-1] == ']':
continue
if category not in collections: if category not in collections:
collections[category] = [] collections[category] = []
collections[category].append(book) if book not in collections[category]:
if attr == 'series': collections[category].append(book)
series_categories.add(category) if attr == 'series':
series_categories.add(category)
# Sort collections
for category, books in collections.items(): for category, books in collections.items():
def tgetter(x): def tgetter(x):
return getattr(x, 'title_sort', 'zzzz') return getattr(x, 'title_sort', 'zzzz')

View File

@ -258,7 +258,7 @@ class MetaInformation(object):
'series', 'series_index', 'tags', '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', 'tag_order', 'rights', 'publication_type', 'uuid'
): ):
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', 'tag_order'): 'publication_type', 'uuid'):
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

@ -102,6 +102,9 @@ 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.db.refresh_ondevice() self.db.refresh_ondevice()
if is_connected and self.sorted_on[0] == 'ondevice':
self.resort()
def set_book_on_device_func(self, func): def set_book_on_device_func(self, func):
self.book_on_device = func self.book_on_device = func
@ -340,9 +343,6 @@ class BooksModel(QAbstractTableModel): # {{{
ans = [] ans = []
for id in ids: for id in ids:
mi = self.db.get_metadata(id, index_is_id=True, get_cover=True) mi = self.db.get_metadata(id, index_is_id=True, get_cover=True)
if mi.series is not None:
mi.tag_order = { mi.series: self.db.books_in_series_of(id,
index_is_id=True)}
ans.append(mi) ans.append(mi)
return ans return ans
@ -807,7 +807,7 @@ class DeviceBooksModel(BooksModel): # {{{
'authors' : _('Author(s)'), 'authors' : _('Author(s)'),
'timestamp' : _('Date'), 'timestamp' : _('Date'),
'size' : _('Size'), 'size' : _('Size'),
'tags' : _('Tags') 'tags' : _('Collections')
} }
self.marked_for_deletion = {} self.marked_for_deletion = {}
self.search_engine = OnDeviceSearch(self) self.search_engine = OnDeviceSearch(self)
@ -896,7 +896,8 @@ class DeviceBooksModel(BooksModel): # {{{
x, y = int(self.db[x].size), int(self.db[y].size) x, y = int(self.db[x].size), int(self.db[y].size)
return cmp(x, y) return cmp(x, y)
def tagscmp(x, y): def tagscmp(x, y):
x, y = ','.join(self.db[x].tags), ','.join(self.db[y].tags) x = ','.join(self.db[x].device_collections)
y = ','.join(self.db[y].device_collections)
return cmp(x, y) return cmp(x, y)
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
@ -966,7 +967,7 @@ class DeviceBooksModel(BooksModel): # {{{
data[_('Path')] = item.path data[_('Path')] = item.path
dt = dt_factory(item.datetime, assume_utc=True) dt = dt_factory(item.datetime, assume_utc=True)
data[_('Timestamp')] = isoformat(dt, sep=' ', as_utc=False) data[_('Timestamp')] = isoformat(dt, sep=' ', as_utc=False)
data[_('Tags')] = ', '.join(item.tags) data[_('Collections')] = ', '.join(item.device_collections)
self.new_bookdisplay_data.emit(data) self.new_bookdisplay_data.emit(data)
def paths(self, rows): def paths(self, rows):
@ -1000,7 +1001,7 @@ class DeviceBooksModel(BooksModel): # {{{
dt = dt_factory(dt, assume_utc=True, as_utc=False) dt = dt_factory(dt, assume_utc=True, as_utc=False)
return QVariant(strftime(TIME_FMT, dt.timetuple())) return QVariant(strftime(TIME_FMT, dt.timetuple()))
elif cname == 'tags': elif cname == 'tags':
tags = self.db[self.map[row]].tags tags = self.db[self.map[row]].device_collections
if tags: if tags:
return QVariant(', '.join(tags)) return QVariant(', '.join(tags))
elif role == Qt.ToolTipRole and index.isValid(): elif role == Qt.ToolTipRole and index.isValid():
@ -1047,7 +1048,7 @@ class DeviceBooksModel(BooksModel): # {{{
elif cname == 'tags': elif cname == 'tags':
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]
self.db.set_tags(self.db[idx], tags) self.db[idx].device_collections = tags
self.dataChanged.emit(index, index) self.dataChanged.emit(index, index)
self.booklist_dirtied.emit() self.booklist_dirtied.emit()
done = True done = True

View File

@ -578,12 +578,14 @@ class ResultCache(SearchQueryParser):
self._map_filtered = list(self._map) self._map_filtered = list(self._map)
def seriescmp(self, x, y): def seriescmp(self, x, y):
sidx = self.FIELD_MAP['series']
try: try:
ans = cmp(self._data[x][9].lower(), self._data[y][9].lower()) ans = cmp(self._data[x][sidx].lower(), self._data[y][sidx].lower())
except AttributeError: # Some entries may be None except AttributeError: # Some entries may be None
ans = cmp(self._data[x][9], self._data[y][9]) ans = cmp(self._data[x][sidx], self._data[y][sidx])
if ans != 0: return ans if ans != 0: return ans
return cmp(self._data[x][10], self._data[y][10]) sidx = self.FIELD_MAP['series_index']
return cmp(self._data[x][sidx], self._data[y][sidx])
def cmp(self, loc, x, y, asstr=True, subsort=False): def cmp(self, loc, x, y, asstr=True, subsort=False):
try: try:

View File

@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
HTTP server for remote access to the calibre database. HTTP server for remote access to the calibre database.
''' '''
import sys, textwrap, operator, os, re, logging, cStringIO import sys, textwrap, operator, os, re, logging, cStringIO, copy
import __builtin__ import __builtin__
from itertools import repeat from itertools import repeat
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
@ -63,21 +63,21 @@ class LibraryServer(object):
BOOK = textwrap.dedent('''\ BOOK = textwrap.dedent('''\
<book xmlns:py="http://genshi.edgewall.org/" <book xmlns:py="http://genshi.edgewall.org/"
id="${r[0]}" id="${r[FM['id']]}"
title="${r[1]}" title="${r[FM['title']]}"
sort="${r[11]}" sort="${r[FM['sort']]}"
author_sort="${r[12]}" author_sort="${r[FM['author_sort']]}"
authors="${authors}" authors="${authors}"
rating="${r[4]}" rating="${r[FM['rating']]}"
timestamp="${timestamp}" timestamp="${timestamp}"
pubdate="${pubdate}" pubdate="${pubdate}"
size="${r[6]}" size="${r[FM['size']]}"
isbn="${r[14] if r[14] else ''}" isbn="${r[FM['isbn']] if r[FM['isbn']] else ''}"
formats="${r[13] if r[13] else ''}" formats="${r[FM['formats']] if r[FM['formats']] else ''}"
series = "${r[9] if r[9] else ''}" series = "${r[FM['series']] if r[FM['series']] else ''}"
series_index="${r[10]}" series_index="${r[FM['series_index']]}"
tags="${r[7] if r[7] else ''}" tags="${r[FM['tags']] if r[FM['tags']] else ''}"
publisher="${r[3] if r[3] else ''}">${r[8] if r[8] else ''} publisher="${r[FM['publisher']] if r[FM['publisher']] else ''}">${r[FM['comments']] if r[FM['comments']] else ''}
</book> </book>
''') ''')
@ -86,13 +86,13 @@ class LibraryServer(object):
MOBILE_BOOK = textwrap.dedent('''\ MOBILE_BOOK = textwrap.dedent('''\
<tr xmlns:py="http://genshi.edgewall.org/"> <tr xmlns:py="http://genshi.edgewall.org/">
<td class="thumbnail"> <td class="thumbnail">
<img type="image/jpeg" src="/get/thumb/${r[0]}" border="0"/> <img type="image/jpeg" src="/get/thumb/${r[FM['id']]}" border="0"/>
</td> </td>
<td> <td>
<py:for each="format in r[13].split(',')"> <py:for each="format in r[FM['formats']].split(',')">
<span class="button"><a href="/get/${format}/${authors}-${r[1]}_${r[0]}.${format}">${format.lower()}</a></span>&nbsp; <span class="button"><a href="/get/${format}/${authors}-${r[FM['title']]}_${r[FM['id']]}.${format}">${format.lower()}</a></span>&nbsp;
</py:for> </py:for>
${r[1]}${(' ['+r[9]+'-'+r[10]+']') if r[9] else ''} by ${authors} - ${r[6]/1024}k - ${r[3] if r[3] else ''} ${pubdate} ${'['+r[7]+']' if r[7] else ''} ${r[FM['title']]}${(' ['+r[FM['series']]+'-'+r[FM['series_index']]+']') if r[FM['series']] else ''} by ${authors} - ${r[FM['size']]/1024}k - ${r[FM['publisher']] if r[FM['publisher']] else ''} ${pubdate} ${'['+r[FM['tags']]+']' if r[FM['tags']] else ''}
</td> </td>
</tr> </tr>
''') ''')
@ -628,22 +628,23 @@ class LibraryServer(object):
ids = self.db.data.parse(search) if search and search.strip() else self.db.data.universal_set() ids = self.db.data.parse(search) if search and search.strip() else self.db.data.universal_set()
record_list = list(iter(self.db)) record_list = list(iter(self.db))
FM = self.db.FIELD_MAP
# Sort the record list # Sort the record list
if sortby == "bytitle" or authorid or tagid: if sortby == "bytitle" or authorid or tagid:
record_list.sort(lambda x, y: record_list.sort(lambda x, y:
cmp(title_sort(x[self.db.FIELD_MAP['title']]), cmp(title_sort(x[FM['title']]),
title_sort(y[self.db.FIELD_MAP['title']]))) title_sort(y[FM['title']])))
elif seriesid: elif seriesid:
record_list.sort(lambda x, y: record_list.sort(lambda x, y:
cmp(x[self.db.FIELD_MAP['series_index']], cmp(x[FM['series_index']],
y[self.db.FIELD_MAP['series_index']])) y[FM['series_index']]))
else: # Sort by date else: # Sort by date
record_list = reversed(record_list) record_list = reversed(record_list)
fmts = self.db.FIELD_MAP['formats'] fmts = FM['formats']
pat = re.compile(r'EPUB|PDB', re.IGNORECASE) pat = re.compile(r'EPUB|PDB', re.IGNORECASE)
record_list = [x for x in record_list if x[0] in ids and record_list = [x for x in record_list if x[FM['id']] in ids and
pat.search(x[fmts] if x[fmts] else '') is not None] pat.search(x[fmts] if x[fmts] else '') is not None]
next_offset = offset + self.max_stanza_items next_offset = offset + self.max_stanza_items
nrecord_list = record_list[offset:next_offset] nrecord_list = record_list[offset:next_offset]
@ -663,10 +664,10 @@ class LibraryServer(object):
) % '&amp;'.join(q) ) % '&amp;'.join(q)
for record in nrecord_list: for record in nrecord_list:
r = record[self.db.FIELD_MAP['formats']] r = record[FM['formats']]
r = r.upper() if r else '' r = r.upper() if r else ''
z = record[self.db.FIELD_MAP['authors']] z = record[FM['authors']]
if not z: if not z:
z = _('Unknown') z = _('Unknown')
authors = ' & '.join([i.replace('|', ',') for i in authors = ' & '.join([i.replace('|', ',') for i in
@ -674,19 +675,19 @@ class LibraryServer(object):
# Setup extra description # Setup extra description
extra = [] extra = []
rating = record[self.db.FIELD_MAP['rating']] rating = record[FM['rating']]
if rating > 0: if rating > 0:
rating = ''.join(repeat('&#9733;', rating)) rating = ''.join(repeat('&#9733;', rating))
extra.append('RATING: %s<br />'%rating) extra.append('RATING: %s<br />'%rating)
tags = record[self.db.FIELD_MAP['tags']] tags = record[FM['tags']]
if tags: if tags:
extra.append('TAGS: %s<br />'%\ extra.append('TAGS: %s<br />'%\
prepare_string_for_xml(', '.join(tags.split(',')))) prepare_string_for_xml(', '.join(tags.split(','))))
series = record[self.db.FIELD_MAP['series']] series = record[FM['series']]
if series: if series:
extra.append('SERIES: %s [%s]<br />'%\ extra.append('SERIES: %s [%s]<br />'%\
(prepare_string_for_xml(series), (prepare_string_for_xml(series),
fmt_sidx(float(record[self.db.FIELD_MAP['series_index']])))) fmt_sidx(float(record[FM['series_index']]))))
fmt = 'epub' if 'EPUB' in r else 'pdb' fmt = 'epub' if 'EPUB' in r else 'pdb'
mimetype = guess_type('dummy.'+fmt)[0] mimetype = guess_type('dummy.'+fmt)[0]
@ -699,17 +700,18 @@ class LibraryServer(object):
authors=authors, authors=authors,
tags=tags, tags=tags,
series=series, series=series,
FM=self.db.FIELD_MAP, FM=FM,
extra='\n'.join(extra), extra='\n'.join(extra),
mimetype=mimetype, mimetype=mimetype,
fmt=fmt, fmt=fmt,
urn=record[self.db.FIELD_MAP['uuid']], urn=record[FM['uuid']],
timestamp=strftime('%Y-%m-%dT%H:%M:%S+00:00', record[5]) timestamp=strftime('%Y-%m-%dT%H:%M:%S+00:00',
record[FM['timestamp']])
) )
books.append(self.STANZA_ENTRY.generate(**data)\ books.append(self.STANZA_ENTRY.generate(**data)\
.render('xml').decode('utf8')) .render('xml').decode('utf8'))
return self.STANZA.generate(subtitle='', data=books, FM=self.db.FIELD_MAP, return self.STANZA.generate(subtitle='', data=books, FM=FM,
next_link=next_link, updated=updated, id='urn:calibre:main').render('xml') next_link=next_link, updated=updated, id='urn:calibre:main').render('xml')
@ -734,23 +736,25 @@ class LibraryServer(object):
raise cherrypy.HTTPError(400, 'num: %s is not an integer'%num) raise cherrypy.HTTPError(400, 'num: %s is not an integer'%num)
ids = self.db.data.parse(search) if search and search.strip() else self.db.data.universal_set() ids = self.db.data.parse(search) if search and search.strip() else self.db.data.universal_set()
ids = sorted(ids) ids = sorted(ids)
items = [r for r in iter(self.db) if r[0] in ids] FM = self.db.FIELD_MAP
items = copy.deepcopy([r for r in iter(self.db) if r[FM['id']] in ids])
if sort is not None: if sort is not None:
self.sort(items, sort, (order.lower().strip() == 'ascending')) self.sort(items, sort, (order.lower().strip() == 'ascending'))
book, books = MarkupTemplate(self.MOBILE_BOOK), [] book, books = MarkupTemplate(self.MOBILE_BOOK), []
for record in items[(start-1):(start-1)+num]: for record in items[(start-1):(start-1)+num]:
if record[13] is None: if record[FM['formats']] is None:
record[13] = '' record[FM['formats']] = ''
if record[6] is None: if record[FM['size']] is None:
record[6] = 0 record[FM['size']] = 0
aus = record[2] if record[2] else __builtin__._('Unknown') aus = record[FM['authors']] if record[FM['authors']] else __builtin__._('Unknown')
authors = '|'.join([i.replace('|', ',') for i in aus.split(',')]) authors = '|'.join([i.replace('|', ',') for i in aus.split(',')])
record[10] = fmt_sidx(float(record[10])) record[FM['series_index']] = \
ts, pd = strftime('%Y/%m/%d %H:%M:%S', record[5]), \ fmt_sidx(float(record[FM['series_index']]))
strftime('%Y/%m/%d %H:%M:%S', record[self.db.FIELD_MAP['pubdate']]) ts, pd = strftime('%Y/%m/%d %H:%M:%S', record[FM['timestamp']]), \
strftime('%Y/%m/%d %H:%M:%S', record[FM['pubdate']])
books.append(book.generate(r=record, authors=authors, timestamp=ts, books.append(book.generate(r=record, authors=authors, timestamp=ts,
pubdate=pd).render('xml').decode('utf-8')) pubdate=pd, FM=FM).render('xml').decode('utf-8'))
updated = self.db.last_modified() updated = self.db.last_modified()
cherrypy.response.headers['Content-Type'] = 'text/html; charset=utf-8' cherrypy.response.headers['Content-Type'] = 'text/html; charset=utf-8'
@ -759,8 +763,9 @@ class LibraryServer(object):
url_base = "/mobile?search=" + search+";order="+order+";sort="+sort+";num="+str(num) url_base = "/mobile?search=" + search+";order="+order+";sort="+sort+";num="+str(num)
return self.MOBILE.generate(books=books, start=start, updated=updated, search=search, sort=sort, order=order, num=num, return self.MOBILE.generate(books=books, start=start, updated=updated,
total=len(ids), url_base=url_base).render('html') search=search, sort=sort, order=order, num=num, FM=FM,
total=len(ids), url_base=url_base).render('html')
@expose @expose
@ -785,25 +790,27 @@ class LibraryServer(object):
order = order.lower().strip() == 'ascending' order = order.lower().strip() == 'ascending'
ids = self.db.data.parse(search) if search and search.strip() else self.db.data.universal_set() ids = self.db.data.parse(search) if search and search.strip() else self.db.data.universal_set()
ids = sorted(ids) ids = sorted(ids)
items = [r for r in iter(self.db) if r[0] in ids] FM = self.db.FIELD_MAP
items = copy.deepcopy([r for r in iter(self.db) if r[FM['id']] in ids])
if sort is not None: if sort is not None:
self.sort(items, sort, order) self.sort(items, sort, order)
book, books = MarkupTemplate(self.BOOK), [] book, books = MarkupTemplate(self.BOOK), []
for record in items[start:start+num]: for record in items[start:start+num]:
aus = record[2] if record[2] else __builtin__._('Unknown') aus = record[FM['authors']] if record[FM['authors']] else __builtin__._('Unknown')
authors = '|'.join([i.replace('|', ',') for i in aus.split(',')]) authors = '|'.join([i.replace('|', ',') for i in aus.split(',')])
record[10] = fmt_sidx(float(record[10])) record[FM['series_index']] = \
ts, pd = strftime('%Y/%m/%d %H:%M:%S', record[5]), \ fmt_sidx(float(record[FM['series_index']]))
strftime('%Y/%m/%d %H:%M:%S', record[self.db.FIELD_MAP['pubdate']]) ts, pd = strftime('%Y/%m/%d %H:%M:%S', record[FM['timestamp']]), \
strftime('%Y/%m/%d %H:%M:%S', record[FM['pubdate']])
books.append(book.generate(r=record, authors=authors, timestamp=ts, books.append(book.generate(r=record, authors=authors, timestamp=ts,
pubdate=pd).render('xml').decode('utf-8')) pubdate=pd, FM=FM).render('xml').decode('utf-8'))
updated = self.db.last_modified() updated = self.db.last_modified()
cherrypy.response.headers['Content-Type'] = 'text/xml' cherrypy.response.headers['Content-Type'] = 'text/xml'
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated) cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
return self.LIBRARY.generate(books=books, start=start, updated=updated, return self.LIBRARY.generate(books=books, start=start, updated=updated,
total=len(ids)).render('xml') total=len(ids), FM=FM).render('xml')
@expose @expose
def index(self, **kwargs): def index(self, **kwargs):