mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge from custcol trunk
This commit is contained in:
commit
07246d842a
@ -442,7 +442,7 @@ from calibre.devices.irexdr.driver import IREXDR1000, IREXDR800
|
||||
from calibre.devices.jetbook.driver import JETBOOK
|
||||
from calibre.devices.kindle.driver import KINDLE, KINDLE2, KINDLE_DX
|
||||
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.nokia.driver import N770, N810
|
||||
from calibre.devices.eslick.driver import ESLICK
|
||||
@ -510,7 +510,6 @@ plugins += [
|
||||
KINDLE_DX,
|
||||
NOOK,
|
||||
PRS505,
|
||||
PRS700,
|
||||
ANDROID,
|
||||
S60,
|
||||
N770,
|
||||
|
@ -293,8 +293,7 @@ class DevicePlugin(Plugin):
|
||||
put the book. len(metadata) == len(files). Apart from the regular
|
||||
cover (path to cover), there may also be a thumbnail attribute, which should
|
||||
be used in preference. The thumbnail attribute is of the form
|
||||
(width, height, cover_data as jpeg). In addition the MetaInformation
|
||||
objects can have a tag_order attribute.
|
||||
(width, height, cover_data as jpeg).
|
||||
'''
|
||||
raise NotImplementedError()
|
||||
|
||||
@ -396,14 +395,6 @@ class BookList(list):
|
||||
''' Return True if the the device supports tags (collections) for this book list. '''
|
||||
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):
|
||||
'''
|
||||
Add the book to the booklist. Intent is to maintain any device-internal
|
||||
|
@ -1,12 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net> ' \
|
||||
'2009, John Schember <john at nachtimwald.com>'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
'''
|
||||
Device driver for the SONY PRS-505
|
||||
Device driver for the SONY devices
|
||||
'''
|
||||
|
||||
import os
|
||||
@ -20,27 +17,33 @@ from calibre import __appname__
|
||||
|
||||
class PRS505(USBMS):
|
||||
|
||||
name = 'PRS-300/505 Device Interface'
|
||||
name = 'SONY Device Interface'
|
||||
gui_name = 'SONY Reader'
|
||||
description = _('Communicate with the Sony PRS-300/505/500 eBook reader.')
|
||||
author = 'Kovid Goyal and John Schember'
|
||||
description = _('Communicate with all the Sony eBook readers.')
|
||||
author = 'Kovid Goyal'
|
||||
supported_platforms = ['windows', 'osx', 'linux']
|
||||
path_sep = '/'
|
||||
|
||||
FORMATS = ['epub', 'lrf', 'lrx', 'rtf', 'pdf', 'txt']
|
||||
|
||||
VENDOR_ID = [0x054c] #: SONY Vendor Id
|
||||
PRODUCT_ID = [0x031e] #: Product Id for the PRS 300/505/new 500
|
||||
BCD = [0x229, 0x1000, 0x22a]
|
||||
PRODUCT_ID = [0x031e]
|
||||
BCD = [0x229, 0x1000, 0x22a, 0x31a]
|
||||
|
||||
VENDOR_NAME = 'SONY'
|
||||
WINDOWS_MAIN_MEM = re.compile('PRS-(505|300|500)')
|
||||
WINDOWS_CARD_A_MEM = re.compile(r'PRS-(505|500)[#/]\S+:MS')
|
||||
WINDOWS_CARD_B_MEM = re.compile(r'PRS-(505|500)[#/]\S+:SD')
|
||||
WINDOWS_MAIN_MEM = re.compile(
|
||||
r'(PRS-(505|300|500))|'
|
||||
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'
|
||||
STORAGE_CARD_VOLUME_LABEL = 'Sony Reader Storage Card'
|
||||
@ -101,27 +104,12 @@ class PRS505(USBMS):
|
||||
opts = self.settings()
|
||||
collections = ['series', 'tags']
|
||||
if opts.extra_customization:
|
||||
collections = opts.extra_customization.split(',')
|
||||
collections = [x.strip() for x in
|
||||
opts.extra_customization.split(',')]
|
||||
|
||||
c.update(blists, collections)
|
||||
c.write()
|
||||
|
||||
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')
|
||||
|
@ -101,16 +101,25 @@ class XMLCache(object):
|
||||
|
||||
# Playlist management {{{
|
||||
def purge_broken_playlist_items(self, root):
|
||||
for item in root.xpath(
|
||||
'//*[local-name()="playlist"]/*[local-name()="item"]'):
|
||||
id_ = item.get('id', None)
|
||||
if id_ is None or not root.xpath(
|
||||
'//*[local-name()!="item" and @id="%s"]'%id_):
|
||||
if DEBUG:
|
||||
prints('Purging broken playlist item:',
|
||||
etree.tostring(item, with_tail=False))
|
||||
item.getparent().remove(item)
|
||||
|
||||
for pl in root.xpath('//*[local-name()="playlist"]'):
|
||||
seen = set([])
|
||||
for item in list(pl):
|
||||
id_ = item.get('id', None)
|
||||
if id_ is None or id_ in seen or not root.xpath(
|
||||
'//*[local-name()!="item" and @id="%s"]'%id_):
|
||||
if DEBUG:
|
||||
if id_ is None:
|
||||
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):
|
||||
for i, root in self.record_roots.items():
|
||||
@ -144,7 +153,7 @@ class XMLCache(object):
|
||||
self.ensure_unique_playlist_titles()
|
||||
self.prune_empty_playlists()
|
||||
for i, root in self.record_roots.items():
|
||||
ans[i] = {}
|
||||
ans[i] = []
|
||||
for playlist in root.xpath('//*[local-name()="playlist"]'):
|
||||
items = []
|
||||
for item in playlist:
|
||||
@ -153,7 +162,7 @@ class XMLCache(object):
|
||||
'//*[local-name()="text" and @id="%s"]'%id_)
|
||||
if records:
|
||||
items.append(records[0])
|
||||
ans[i] = {playlist.get('title'):items}
|
||||
ans[i].append((playlist.get('title'), items))
|
||||
return ans
|
||||
|
||||
def get_or_create_playlist(self, bl_idx, title):
|
||||
@ -175,6 +184,8 @@ class XMLCache(object):
|
||||
# }}}
|
||||
|
||||
def fix_ids(self): # {{{
|
||||
if DEBUG:
|
||||
prints('Running fix_ids()')
|
||||
|
||||
def ensure_numeric_ids(root):
|
||||
idmap = {}
|
||||
@ -255,7 +266,19 @@ class XMLCache(object):
|
||||
def update_booklist(self, bl, bl_index):
|
||||
if bl_index not in self.record_roots:
|
||||
return
|
||||
if DEBUG:
|
||||
prints('Updating JSON cache:', 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:
|
||||
record = self.book_by_lpath(book.lpath, root)
|
||||
if record is not None:
|
||||
@ -282,6 +305,10 @@ class XMLCache(object):
|
||||
book.thumbnail = raw
|
||||
break
|
||||
break
|
||||
if book.lpath in playlist_map:
|
||||
tags = playlist_map[book.lpath]
|
||||
book.device_collections = tags
|
||||
|
||||
# }}}
|
||||
|
||||
# Update XML from JSON {{{
|
||||
@ -290,7 +317,7 @@ class XMLCache(object):
|
||||
|
||||
for i, booklist in booklists.items():
|
||||
if DEBUG:
|
||||
prints('Updating booklist:', i)
|
||||
prints('Updating XML Cache:', i)
|
||||
root = self.record_roots[i]
|
||||
for book in booklist:
|
||||
path = os.path.join(self.prefixes[i], *(book.lpath.split('/')))
|
||||
@ -304,6 +331,10 @@ class XMLCache(object):
|
||||
|
||||
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,
|
||||
collections_attributes):
|
||||
collections = booklist.get_collections(collections_attributes)
|
||||
@ -340,7 +371,25 @@ class XMLCache(object):
|
||||
nsmap=playlist.nsmap, attrib={'id':id_})
|
||||
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):
|
||||
namespace = self.namespaces[bl_id]
|
||||
@ -395,8 +444,19 @@ class XMLCache(object):
|
||||
child.iterchildren(reversed=True).next().tail = '\n'+'\t'*level
|
||||
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):
|
||||
for i, path in self.paths.items():
|
||||
self.move_playlists_to_bottom()
|
||||
self.cleanup_whitespace(i)
|
||||
raw = etree.tostring(self.roots[i], encoding='UTF-8',
|
||||
xml_declaration=True)
|
||||
|
@ -14,14 +14,14 @@ from calibre import isbytestring
|
||||
|
||||
class Book(MetaInformation):
|
||||
|
||||
BOOK_ATTRS = ['lpath', 'size', 'mime']
|
||||
BOOK_ATTRS = ['lpath', 'size', 'mime', 'device_collections']
|
||||
|
||||
JSON_ATTRS = [
|
||||
'lpath', 'title', 'authors', 'mime', 'size', 'tags', 'author_sort',
|
||||
'title_sort', 'comments', 'category', 'publisher', 'series',
|
||||
'series_index', 'rating', 'isbn', 'language', 'application_id',
|
||||
'book_producer', 'lccn', 'lcc', 'ddc', 'rights', 'publication_type',
|
||||
'uuid'
|
||||
'uuid',
|
||||
]
|
||||
|
||||
def __init__(self, prefix, lpath, size=None, other=None):
|
||||
@ -29,6 +29,7 @@ class Book(MetaInformation):
|
||||
|
||||
MetaInformation.__init__(self, '')
|
||||
|
||||
self.device_collections = []
|
||||
self.path = os.path.join(prefix, lpath)
|
||||
if os.sep == '\\':
|
||||
self.path = self.path.replace('/', '\\')
|
||||
@ -45,27 +46,7 @@ class Book(MetaInformation):
|
||||
self.smart_update(other)
|
||||
|
||||
def __eq__(self, other):
|
||||
spath = self.path
|
||||
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
|
||||
return self.path == getattr(other, 'path', None)
|
||||
|
||||
@dynamic_property
|
||||
def db_id(self):
|
||||
@ -119,9 +100,6 @@ class BookList(_BookList):
|
||||
def supports_tags(self):
|
||||
return True
|
||||
|
||||
def set_tags(self, book, tags):
|
||||
book.tags = tags
|
||||
|
||||
def add_book(self, book, replace_metadata):
|
||||
if book not in self:
|
||||
self.append(book)
|
||||
@ -134,7 +112,9 @@ class BookList(_BookList):
|
||||
def get_collections(self, collection_attributes):
|
||||
collections = {}
|
||||
series_categories = set([])
|
||||
collection_attributes = list(collection_attributes)+['device_collections']
|
||||
for attr in collection_attributes:
|
||||
attr = attr.strip()
|
||||
for book in self:
|
||||
val = getattr(book, attr, None)
|
||||
if not val: continue
|
||||
@ -145,11 +125,17 @@ class BookList(_BookList):
|
||||
elif isinstance(val, unicode):
|
||||
val = [val]
|
||||
for category in val:
|
||||
if attr == 'tags' and len(category) > 1 and \
|
||||
category[0] == '[' and category[-1] == ']':
|
||||
continue
|
||||
if category not in collections:
|
||||
collections[category] = []
|
||||
collections[category].append(book)
|
||||
if attr == 'series':
|
||||
series_categories.add(category)
|
||||
if book not in collections[category]:
|
||||
collections[category].append(book)
|
||||
if attr == 'series':
|
||||
series_categories.add(category)
|
||||
|
||||
# Sort collections
|
||||
for category, books in collections.items():
|
||||
def tgetter(x):
|
||||
return getattr(x, 'title_sort', 'zzzz')
|
||||
|
@ -258,7 +258,7 @@ class MetaInformation(object):
|
||||
'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', 'tag_order',
|
||||
'rights', 'publication_type', 'uuid'
|
||||
):
|
||||
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', 'tag_order'):
|
||||
'publication_type', 'uuid'):
|
||||
if hasattr(mi, attr):
|
||||
val = getattr(mi, attr)
|
||||
if val is not None:
|
||||
|
@ -102,6 +102,9 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
def set_device_connected(self, is_connected):
|
||||
self.device_connected = is_connected
|
||||
self.db.refresh_ondevice()
|
||||
if is_connected and self.sorted_on[0] == 'ondevice':
|
||||
self.resort()
|
||||
|
||||
|
||||
def set_book_on_device_func(self, func):
|
||||
self.book_on_device = func
|
||||
@ -340,9 +343,6 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
ans = []
|
||||
for id in ids:
|
||||
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)
|
||||
return ans
|
||||
|
||||
@ -807,7 +807,7 @@ class DeviceBooksModel(BooksModel): # {{{
|
||||
'authors' : _('Author(s)'),
|
||||
'timestamp' : _('Date'),
|
||||
'size' : _('Size'),
|
||||
'tags' : _('Tags')
|
||||
'tags' : _('Collections')
|
||||
}
|
||||
self.marked_for_deletion = {}
|
||||
self.search_engine = OnDeviceSearch(self)
|
||||
@ -896,7 +896,8 @@ class DeviceBooksModel(BooksModel): # {{{
|
||||
x, y = int(self.db[x].size), int(self.db[y].size)
|
||||
return cmp(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)
|
||||
def libcmp(x, y):
|
||||
x, y = self.db[x].in_library, self.db[y].in_library
|
||||
@ -966,7 +967,7 @@ class DeviceBooksModel(BooksModel): # {{{
|
||||
data[_('Path')] = item.path
|
||||
dt = dt_factory(item.datetime, assume_utc=True)
|
||||
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)
|
||||
|
||||
def paths(self, rows):
|
||||
@ -1000,7 +1001,7 @@ class DeviceBooksModel(BooksModel): # {{{
|
||||
dt = dt_factory(dt, assume_utc=True, as_utc=False)
|
||||
return QVariant(strftime(TIME_FMT, dt.timetuple()))
|
||||
elif cname == 'tags':
|
||||
tags = self.db[self.map[row]].tags
|
||||
tags = self.db[self.map[row]].device_collections
|
||||
if tags:
|
||||
return QVariant(', '.join(tags))
|
||||
elif role == Qt.ToolTipRole and index.isValid():
|
||||
@ -1047,7 +1048,7 @@ class DeviceBooksModel(BooksModel): # {{{
|
||||
elif cname == 'tags':
|
||||
tags = [i.strip() for i in val.split(',')]
|
||||
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.booklist_dirtied.emit()
|
||||
done = True
|
||||
|
@ -578,12 +578,14 @@ class ResultCache(SearchQueryParser):
|
||||
self._map_filtered = list(self._map)
|
||||
|
||||
def seriescmp(self, x, y):
|
||||
sidx = self.FIELD_MAP['series']
|
||||
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
|
||||
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
|
||||
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):
|
||||
try:
|
||||
|
@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
|
||||
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__
|
||||
from itertools import repeat
|
||||
from logging.handlers import RotatingFileHandler
|
||||
@ -63,21 +63,21 @@ class LibraryServer(object):
|
||||
|
||||
BOOK = textwrap.dedent('''\
|
||||
<book xmlns:py="http://genshi.edgewall.org/"
|
||||
id="${r[0]}"
|
||||
title="${r[1]}"
|
||||
sort="${r[11]}"
|
||||
author_sort="${r[12]}"
|
||||
id="${r[FM['id']]}"
|
||||
title="${r[FM['title']]}"
|
||||
sort="${r[FM['sort']]}"
|
||||
author_sort="${r[FM['author_sort']]}"
|
||||
authors="${authors}"
|
||||
rating="${r[4]}"
|
||||
rating="${r[FM['rating']]}"
|
||||
timestamp="${timestamp}"
|
||||
pubdate="${pubdate}"
|
||||
size="${r[6]}"
|
||||
isbn="${r[14] if r[14] else ''}"
|
||||
formats="${r[13] if r[13] else ''}"
|
||||
series = "${r[9] if r[9] else ''}"
|
||||
series_index="${r[10]}"
|
||||
tags="${r[7] if r[7] else ''}"
|
||||
publisher="${r[3] if r[3] else ''}">${r[8] if r[8] else ''}
|
||||
size="${r[FM['size']]}"
|
||||
isbn="${r[FM['isbn']] if r[FM['isbn']] else ''}"
|
||||
formats="${r[FM['formats']] if r[FM['formats']] else ''}"
|
||||
series = "${r[FM['series']] if r[FM['series']] else ''}"
|
||||
series_index="${r[FM['series_index']]}"
|
||||
tags="${r[FM['tags']] if r[FM['tags']] else ''}"
|
||||
publisher="${r[FM['publisher']] if r[FM['publisher']] else ''}">${r[FM['comments']] if r[FM['comments']] else ''}
|
||||
</book>
|
||||
''')
|
||||
|
||||
@ -86,13 +86,13 @@ class LibraryServer(object):
|
||||
MOBILE_BOOK = textwrap.dedent('''\
|
||||
<tr xmlns:py="http://genshi.edgewall.org/">
|
||||
<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>
|
||||
<py:for each="format in r[13].split(',')">
|
||||
<span class="button"><a href="/get/${format}/${authors}-${r[1]}_${r[0]}.${format}">${format.lower()}</a></span>
|
||||
<py:for each="format in r[FM['formats']].split(',')">
|
||||
<span class="button"><a href="/get/${format}/${authors}-${r[FM['title']]}_${r[FM['id']]}.${format}">${format.lower()}</a></span>
|
||||
</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>
|
||||
</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()
|
||||
record_list = list(iter(self.db))
|
||||
|
||||
FM = self.db.FIELD_MAP
|
||||
# Sort the record list
|
||||
if sortby == "bytitle" or authorid or tagid:
|
||||
record_list.sort(lambda x, y:
|
||||
cmp(title_sort(x[self.db.FIELD_MAP['title']]),
|
||||
title_sort(y[self.db.FIELD_MAP['title']])))
|
||||
cmp(title_sort(x[FM['title']]),
|
||||
title_sort(y[FM['title']])))
|
||||
elif seriesid:
|
||||
record_list.sort(lambda x, y:
|
||||
cmp(x[self.db.FIELD_MAP['series_index']],
|
||||
y[self.db.FIELD_MAP['series_index']]))
|
||||
cmp(x[FM['series_index']],
|
||||
y[FM['series_index']]))
|
||||
else: # Sort by date
|
||||
record_list = reversed(record_list)
|
||||
|
||||
|
||||
fmts = self.db.FIELD_MAP['formats']
|
||||
fmts = FM['formats']
|
||||
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]
|
||||
next_offset = offset + self.max_stanza_items
|
||||
nrecord_list = record_list[offset:next_offset]
|
||||
@ -663,10 +664,10 @@ class LibraryServer(object):
|
||||
) % '&'.join(q)
|
||||
|
||||
for record in nrecord_list:
|
||||
r = record[self.db.FIELD_MAP['formats']]
|
||||
r = record[FM['formats']]
|
||||
r = r.upper() if r else ''
|
||||
|
||||
z = record[self.db.FIELD_MAP['authors']]
|
||||
z = record[FM['authors']]
|
||||
if not z:
|
||||
z = _('Unknown')
|
||||
authors = ' & '.join([i.replace('|', ',') for i in
|
||||
@ -674,19 +675,19 @@ class LibraryServer(object):
|
||||
|
||||
# Setup extra description
|
||||
extra = []
|
||||
rating = record[self.db.FIELD_MAP['rating']]
|
||||
rating = record[FM['rating']]
|
||||
if rating > 0:
|
||||
rating = ''.join(repeat('★', rating))
|
||||
extra.append('RATING: %s<br />'%rating)
|
||||
tags = record[self.db.FIELD_MAP['tags']]
|
||||
tags = record[FM['tags']]
|
||||
if tags:
|
||||
extra.append('TAGS: %s<br />'%\
|
||||
prepare_string_for_xml(', '.join(tags.split(','))))
|
||||
series = record[self.db.FIELD_MAP['series']]
|
||||
series = record[FM['series']]
|
||||
if series:
|
||||
extra.append('SERIES: %s [%s]<br />'%\
|
||||
(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'
|
||||
mimetype = guess_type('dummy.'+fmt)[0]
|
||||
@ -699,17 +700,18 @@ class LibraryServer(object):
|
||||
authors=authors,
|
||||
tags=tags,
|
||||
series=series,
|
||||
FM=self.db.FIELD_MAP,
|
||||
FM=FM,
|
||||
extra='\n'.join(extra),
|
||||
mimetype=mimetype,
|
||||
fmt=fmt,
|
||||
urn=record[self.db.FIELD_MAP['uuid']],
|
||||
timestamp=strftime('%Y-%m-%dT%H:%M:%S+00:00', record[5])
|
||||
urn=record[FM['uuid']],
|
||||
timestamp=strftime('%Y-%m-%dT%H:%M:%S+00:00',
|
||||
record[FM['timestamp']])
|
||||
)
|
||||
books.append(self.STANZA_ENTRY.generate(**data)\
|
||||
.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')
|
||||
|
||||
|
||||
@ -734,23 +736,25 @@ class LibraryServer(object):
|
||||
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 = 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:
|
||||
self.sort(items, sort, (order.lower().strip() == 'ascending'))
|
||||
|
||||
book, books = MarkupTemplate(self.MOBILE_BOOK), []
|
||||
for record in items[(start-1):(start-1)+num]:
|
||||
if record[13] is None:
|
||||
record[13] = ''
|
||||
if record[6] is None:
|
||||
record[6] = 0
|
||||
aus = record[2] if record[2] else __builtin__._('Unknown')
|
||||
if record[FM['formats']] is None:
|
||||
record[FM['formats']] = ''
|
||||
if record[FM['size']] is None:
|
||||
record[FM['size']] = 0
|
||||
aus = record[FM['authors']] if record[FM['authors']] else __builtin__._('Unknown')
|
||||
authors = '|'.join([i.replace('|', ',') for i in aus.split(',')])
|
||||
record[10] = fmt_sidx(float(record[10]))
|
||||
ts, pd = strftime('%Y/%m/%d %H:%M:%S', record[5]), \
|
||||
strftime('%Y/%m/%d %H:%M:%S', record[self.db.FIELD_MAP['pubdate']])
|
||||
record[FM['series_index']] = \
|
||||
fmt_sidx(float(record[FM['series_index']]))
|
||||
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,
|
||||
pubdate=pd).render('xml').decode('utf-8'))
|
||||
pubdate=pd, FM=FM).render('xml').decode('utf-8'))
|
||||
updated = self.db.last_modified()
|
||||
|
||||
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)
|
||||
|
||||
return self.MOBILE.generate(books=books, start=start, updated=updated, search=search, sort=sort, order=order, num=num,
|
||||
total=len(ids), url_base=url_base).render('html')
|
||||
return self.MOBILE.generate(books=books, start=start, updated=updated,
|
||||
search=search, sort=sort, order=order, num=num, FM=FM,
|
||||
total=len(ids), url_base=url_base).render('html')
|
||||
|
||||
|
||||
@expose
|
||||
@ -785,25 +790,27 @@ class LibraryServer(object):
|
||||
order = order.lower().strip() == 'ascending'
|
||||
ids = self.db.data.parse(search) if search and search.strip() else self.db.data.universal_set()
|
||||
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:
|
||||
self.sort(items, sort, order)
|
||||
|
||||
book, books = MarkupTemplate(self.BOOK), []
|
||||
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(',')])
|
||||
record[10] = fmt_sidx(float(record[10]))
|
||||
ts, pd = strftime('%Y/%m/%d %H:%M:%S', record[5]), \
|
||||
strftime('%Y/%m/%d %H:%M:%S', record[self.db.FIELD_MAP['pubdate']])
|
||||
record[FM['series_index']] = \
|
||||
fmt_sidx(float(record[FM['series_index']]))
|
||||
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,
|
||||
pubdate=pd).render('xml').decode('utf-8'))
|
||||
pubdate=pd, FM=FM).render('xml').decode('utf-8'))
|
||||
updated = self.db.last_modified()
|
||||
|
||||
cherrypy.response.headers['Content-Type'] = 'text/xml'
|
||||
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
|
||||
return self.LIBRARY.generate(books=books, start=start, updated=updated,
|
||||
total=len(ids)).render('xml')
|
||||
total=len(ids), FM=FM).render('xml')
|
||||
|
||||
@expose
|
||||
def index(self, **kwargs):
|
||||
|
Loading…
x
Reference in New Issue
Block a user