mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
Fix #2273 (Little bug in Calibra Server)
This commit is contained in:
parent
d8ed8c0c07
commit
ead9de4002
@ -157,35 +157,35 @@ class BookHeader(object):
|
|||||||
class MetadataHeader(BookHeader):
|
class MetadataHeader(BookHeader):
|
||||||
def __init__(self, stream):
|
def __init__(self, stream):
|
||||||
self.stream = stream
|
self.stream = stream
|
||||||
|
|
||||||
self.ident = self.identity()
|
self.ident = self.identity()
|
||||||
self.num_sections = self.section_count()
|
self.num_sections = self.section_count()
|
||||||
|
|
||||||
if self.num_sections >= 2:
|
if self.num_sections >= 2:
|
||||||
header = self.header()
|
header = self.header()
|
||||||
BookHeader.__init__(self, header, self.ident, None)
|
BookHeader.__init__(self, header, self.ident)
|
||||||
else:
|
else:
|
||||||
self.exth = None
|
self.exth = None
|
||||||
|
|
||||||
def identity(self):
|
def identity(self):
|
||||||
self.stream.seek(60)
|
self.stream.seek(60)
|
||||||
ident = self.stream.read(8).upper()
|
ident = self.stream.read(8).upper()
|
||||||
|
|
||||||
if ident not in ['BOOKMOBI', 'TEXTREAD']:
|
if ident not in ['BOOKMOBI', 'TEXTREAD']:
|
||||||
raise MobiError('Unknown book type: %s' % ident)
|
raise MobiError('Unknown book type: %s' % ident)
|
||||||
return ident
|
return ident
|
||||||
|
|
||||||
def section_count(self):
|
def section_count(self):
|
||||||
self.stream.seek(76)
|
self.stream.seek(76)
|
||||||
return struct.unpack('>H', self.stream.read(2))[0]
|
return struct.unpack('>H', self.stream.read(2))[0]
|
||||||
|
|
||||||
def section_offset(self, number):
|
def section_offset(self, number):
|
||||||
self.stream.seek(78+number*8)
|
self.stream.seek(78+number*8)
|
||||||
return struct.unpack('>LBBBB', self.stream.read(8))[0]
|
return struct.unpack('>LBBBB', self.stream.read(8))[0]
|
||||||
|
|
||||||
def header(self):
|
def header(self):
|
||||||
section_headers = []
|
section_headers = []
|
||||||
|
|
||||||
# First section with the metadata
|
# First section with the metadata
|
||||||
section_headers.append(self.section_offset(0))
|
section_headers.append(self.section_offset(0))
|
||||||
# Second section used to get the lengh of the first
|
# Second section used to get the lengh of the first
|
||||||
@ -193,20 +193,20 @@ class MetadataHeader(BookHeader):
|
|||||||
|
|
||||||
end_off = section_headers[1]
|
end_off = section_headers[1]
|
||||||
off = section_headers[0]
|
off = section_headers[0]
|
||||||
|
|
||||||
self.stream.seek(off)
|
self.stream.seek(off)
|
||||||
return self.stream.read(end_off - off)
|
return self.stream.read(end_off - off)
|
||||||
|
|
||||||
def section_data(self, number):
|
def section_data(self, number):
|
||||||
start = self.section_offset(number)
|
start = self.section_offset(number)
|
||||||
|
|
||||||
if number == self.num_sections -1:
|
if number == self.num_sections -1:
|
||||||
end = os.stat(self.stream.name).st_size
|
end = os.stat(self.stream.name).st_size
|
||||||
else:
|
else:
|
||||||
end = self.section_offset(number + 1)
|
end = self.section_offset(number + 1)
|
||||||
|
|
||||||
self.stream.seek(start)
|
self.stream.seek(start)
|
||||||
|
|
||||||
return self.stream.read(end - start)
|
return self.stream.read(end - start)
|
||||||
|
|
||||||
|
|
||||||
@ -618,7 +618,7 @@ class MobiReader(object):
|
|||||||
self.image_names.append(os.path.basename(path))
|
self.image_names.append(os.path.basename(path))
|
||||||
im.convert('RGB').save(open(path, 'wb'), format='JPEG')
|
im.convert('RGB').save(open(path, 'wb'), format='JPEG')
|
||||||
|
|
||||||
def get_metadata(stream):
|
def get_metadata(stream):
|
||||||
mi = MetaInformation(os.path.basename(stream.name), [_('Unknown')])
|
mi = MetaInformation(os.path.basename(stream.name), [_('Unknown')])
|
||||||
try:
|
try:
|
||||||
mh = MetadataHeader(stream)
|
mh = MetadataHeader(stream)
|
||||||
@ -632,7 +632,7 @@ def get_metadata(stream):
|
|||||||
mr.extract_content(tdir)
|
mr.extract_content(tdir)
|
||||||
if mr.embedded_mi is not None:
|
if mr.embedded_mi is not None:
|
||||||
mi = mr.embedded_mi
|
mi = mr.embedded_mi
|
||||||
|
|
||||||
if hasattr(mh.exth, 'cover_offset'):
|
if hasattr(mh.exth, 'cover_offset'):
|
||||||
cover_index = mh.first_image_index + mh.exth.cover_offset
|
cover_index = mh.first_image_index + mh.exth.cover_offset
|
||||||
data = mh.section_data(int(cover_index))
|
data = mh.section_data(int(cover_index))
|
||||||
@ -646,7 +646,7 @@ def get_metadata(stream):
|
|||||||
except:
|
except:
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
return mi
|
return mi
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,31 +30,31 @@ build_time = datetime.strptime(build_time, '%d %m %Y %H%M%S')
|
|||||||
server_resources['jquery.js'] = jquery
|
server_resources['jquery.js'] = jquery
|
||||||
|
|
||||||
def expose(func):
|
def expose(func):
|
||||||
|
|
||||||
def do(self, *args, **kwargs):
|
def do(self, *args, **kwargs):
|
||||||
dict.update(cherrypy.response.headers, {'Server':self.server_name})
|
dict.update(cherrypy.response.headers, {'Server':self.server_name})
|
||||||
return func(self, *args, **kwargs)
|
return func(self, *args, **kwargs)
|
||||||
|
|
||||||
return cherrypy.expose(do)
|
return cherrypy.expose(do)
|
||||||
|
|
||||||
log_access_file = os.path.join(config_dir, 'server_access_log.txt')
|
log_access_file = os.path.join(config_dir, 'server_access_log.txt')
|
||||||
log_error_file = os.path.join(config_dir, 'server_error_log.txt')
|
log_error_file = os.path.join(config_dir, 'server_error_log.txt')
|
||||||
|
|
||||||
|
|
||||||
class LibraryServer(object):
|
class LibraryServer(object):
|
||||||
|
|
||||||
server_name = __appname__ + '/' + __version__
|
server_name = __appname__ + '/' + __version__
|
||||||
|
|
||||||
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[0]}"
|
||||||
title="${r[1]}"
|
title="${r[1]}"
|
||||||
sort="${r[11]}"
|
sort="${r[11]}"
|
||||||
author_sort="${r[12]}"
|
author_sort="${r[12]}"
|
||||||
authors="${authors}"
|
authors="${authors}"
|
||||||
rating="${r[4]}"
|
rating="${r[4]}"
|
||||||
timestamp="${r[5].strftime('%Y/%m/%d %H:%M:%S')}"
|
timestamp="${r[5].strftime('%Y/%m/%d %H:%M:%S')}"
|
||||||
size="${r[6]}"
|
size="${r[6]}"
|
||||||
isbn="${r[14] if r[14] else ''}"
|
isbn="${r[14] if r[14] else ''}"
|
||||||
formats="${r[13] if r[13] else ''}"
|
formats="${r[13] if r[13] else ''}"
|
||||||
series = "${r[9] if r[9] else ''}"
|
series = "${r[9] if r[9] else ''}"
|
||||||
@ -63,7 +63,7 @@ class LibraryServer(object):
|
|||||||
publisher="${r[3] if r[3] else ''}">${r[8] if r[8] else ''}
|
publisher="${r[3] if r[3] else ''}">${r[8] if r[8] else ''}
|
||||||
</book>
|
</book>
|
||||||
''')
|
''')
|
||||||
|
|
||||||
LIBRARY = MarkupTemplate(textwrap.dedent('''\
|
LIBRARY = MarkupTemplate(textwrap.dedent('''\
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<library xmlns:py="http://genshi.edgewall.org/" start="$start" num="${len(books)}" total="$total" updated="${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}">
|
<library xmlns:py="http://genshi.edgewall.org/" start="$start" num="${len(books)}" total="$total" updated="${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}">
|
||||||
@ -72,7 +72,7 @@ class LibraryServer(object):
|
|||||||
</py:for>
|
</py:for>
|
||||||
</library>
|
</library>
|
||||||
'''))
|
'''))
|
||||||
|
|
||||||
STANZA_ENTRY=MarkupTemplate(textwrap.dedent('''\
|
STANZA_ENTRY=MarkupTemplate(textwrap.dedent('''\
|
||||||
<entry xmlns:py="http://genshi.edgewall.org/">
|
<entry xmlns:py="http://genshi.edgewall.org/">
|
||||||
<title>${record[FM['title']]}</title>
|
<title>${record[FM['title']]}</title>
|
||||||
@ -87,7 +87,7 @@ class LibraryServer(object):
|
|||||||
</content>
|
</content>
|
||||||
</entry>
|
</entry>
|
||||||
'''))
|
'''))
|
||||||
|
|
||||||
STANZA = MarkupTemplate(textwrap.dedent('''\
|
STANZA = MarkupTemplate(textwrap.dedent('''\
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:py="http://genshi.edgewall.org/">
|
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:py="http://genshi.edgewall.org/">
|
||||||
@ -107,7 +107,7 @@ class LibraryServer(object):
|
|||||||
</feed>
|
</feed>
|
||||||
'''))
|
'''))
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, db, opts, embedded=False, show_tracebacks=True):
|
def __init__(self, db, opts, embedded=False, show_tracebacks=True):
|
||||||
self.db = db
|
self.db = db
|
||||||
for item in self.db:
|
for item in self.db:
|
||||||
@ -116,7 +116,7 @@ class LibraryServer(object):
|
|||||||
self.opts = opts
|
self.opts = opts
|
||||||
self.max_cover_width, self.max_cover_height = \
|
self.max_cover_width, self.max_cover_height = \
|
||||||
map(int, self.opts.max_cover.split('x'))
|
map(int, self.opts.max_cover.split('x'))
|
||||||
|
|
||||||
cherrypy.config.update({
|
cherrypy.config.update({
|
||||||
'log.screen' : opts.develop,
|
'log.screen' : opts.develop,
|
||||||
'engine.autoreload_on' : opts.develop,
|
'engine.autoreload_on' : opts.develop,
|
||||||
@ -141,10 +141,10 @@ class LibraryServer(object):
|
|||||||
'tools.digest_auth.realm' : (_('Password to access your calibre library. Username is ') + opts.username.strip()).encode('ascii', 'replace'),
|
'tools.digest_auth.realm' : (_('Password to access your calibre library. Username is ') + opts.username.strip()).encode('ascii', 'replace'),
|
||||||
'tools.digest_auth.users' : {opts.username.strip():opts.password.strip()},
|
'tools.digest_auth.users' : {opts.username.strip():opts.password.strip()},
|
||||||
}
|
}
|
||||||
|
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
self.exception = None
|
self.exception = None
|
||||||
|
|
||||||
def setup_loggers(self):
|
def setup_loggers(self):
|
||||||
access_file = log_access_file
|
access_file = log_access_file
|
||||||
error_file = log_error_file
|
error_file = log_error_file
|
||||||
@ -152,20 +152,20 @@ class LibraryServer(object):
|
|||||||
|
|
||||||
maxBytes = getattr(log, "rot_maxBytes", 10000000)
|
maxBytes = getattr(log, "rot_maxBytes", 10000000)
|
||||||
backupCount = getattr(log, "rot_backupCount", 1000)
|
backupCount = getattr(log, "rot_backupCount", 1000)
|
||||||
|
|
||||||
# Make a new RotatingFileHandler for the error log.
|
# Make a new RotatingFileHandler for the error log.
|
||||||
h = RotatingFileHandler(error_file, 'a', maxBytes, backupCount)
|
h = RotatingFileHandler(error_file, 'a', maxBytes, backupCount)
|
||||||
h.setLevel(logging.DEBUG)
|
h.setLevel(logging.DEBUG)
|
||||||
h.setFormatter(cherrypy._cplogging.logfmt)
|
h.setFormatter(cherrypy._cplogging.logfmt)
|
||||||
log.error_log.addHandler(h)
|
log.error_log.addHandler(h)
|
||||||
|
|
||||||
# Make a new RotatingFileHandler for the access log.
|
# Make a new RotatingFileHandler for the access log.
|
||||||
h = RotatingFileHandler(access_file, 'a', maxBytes, backupCount)
|
h = RotatingFileHandler(access_file, 'a', maxBytes, backupCount)
|
||||||
h.setLevel(logging.DEBUG)
|
h.setLevel(logging.DEBUG)
|
||||||
h.setFormatter(cherrypy._cplogging.logfmt)
|
h.setFormatter(cherrypy._cplogging.logfmt)
|
||||||
log.access_log.addHandler(h)
|
log.access_log.addHandler(h)
|
||||||
|
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
self.setup_loggers()
|
self.setup_loggers()
|
||||||
@ -173,7 +173,7 @@ class LibraryServer(object):
|
|||||||
try:
|
try:
|
||||||
cherrypy.engine.start()
|
cherrypy.engine.start()
|
||||||
self.is_running = True
|
self.is_running = True
|
||||||
publish_zeroconf('Books in calibre', '_stanza._tcp',
|
publish_zeroconf('Books in calibre', '_stanza._tcp',
|
||||||
self.opts.port, {'path':'/stanza'})
|
self.opts.port, {'path':'/stanza'})
|
||||||
cherrypy.engine.block()
|
cherrypy.engine.block()
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
@ -181,10 +181,10 @@ class LibraryServer(object):
|
|||||||
finally:
|
finally:
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
stop_zeroconf()
|
stop_zeroconf()
|
||||||
|
|
||||||
def exit(self):
|
def exit(self):
|
||||||
cherrypy.engine.exit()
|
cherrypy.engine.exit()
|
||||||
|
|
||||||
def get_cover(self, id, thumbnail=False):
|
def get_cover(self, id, thumbnail=False):
|
||||||
cover = self.db.cover(id, index_is_id=True, as_file=False)
|
cover = self.db.cover(id, index_is_id=True, as_file=False)
|
||||||
if cover is None:
|
if cover is None:
|
||||||
@ -196,14 +196,14 @@ class LibraryServer(object):
|
|||||||
try:
|
try:
|
||||||
if QApplication.instance() is None:
|
if QApplication.instance() is None:
|
||||||
QApplication([])
|
QApplication([])
|
||||||
|
|
||||||
im = QImage()
|
im = QImage()
|
||||||
im.loadFromData(cover)
|
im.loadFromData(cover)
|
||||||
if im.isNull():
|
if im.isNull():
|
||||||
raise cherrypy.HTTPError(404, 'No valid cover found')
|
raise cherrypy.HTTPError(404, 'No valid cover found')
|
||||||
width, height = im.width(), im.height()
|
width, height = im.width(), im.height()
|
||||||
scaled, width, height = fit_image(width, height,
|
scaled, width, height = fit_image(width, height,
|
||||||
60 if thumbnail else self.max_cover_width,
|
60 if thumbnail else self.max_cover_width,
|
||||||
80 if thumbnail else self.max_cover_height)
|
80 if thumbnail else self.max_cover_height)
|
||||||
if not scaled:
|
if not scaled:
|
||||||
return cover
|
return cover
|
||||||
@ -217,7 +217,7 @@ class LibraryServer(object):
|
|||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise cherrypy.HTTPError(404, 'Failed to generate cover: %s'%err)
|
raise cherrypy.HTTPError(404, 'Failed to generate cover: %s'%err)
|
||||||
|
|
||||||
def get_format(self, id, format):
|
def get_format(self, id, format):
|
||||||
format = format.upper()
|
format = format.upper()
|
||||||
fmt = self.db.format(id, format, index_is_id=True, as_file=True, mode='rb')
|
fmt = self.db.format(id, format, index_is_id=True, as_file=True, mode='rb')
|
||||||
@ -232,7 +232,7 @@ class LibraryServer(object):
|
|||||||
updated = datetime.utcfromtimestamp(os.stat(path).st_mtime)
|
updated = datetime.utcfromtimestamp(os.stat(path).st_mtime)
|
||||||
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
|
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
|
||||||
return fmt.read()
|
return fmt.read()
|
||||||
|
|
||||||
def sort(self, items, field, order):
|
def sort(self, items, field, order):
|
||||||
field = field.lower().strip()
|
field = field.lower().strip()
|
||||||
if field == 'author':
|
if field == 'author':
|
||||||
@ -243,10 +243,23 @@ class LibraryServer(object):
|
|||||||
raise cherrypy.HTTPError(400, '%s is not a valid sort field'%field)
|
raise cherrypy.HTTPError(400, '%s is not a valid sort field'%field)
|
||||||
cmpf = cmp if field in ('rating', 'size', 'timestamp') else \
|
cmpf = cmp if field in ('rating', 'size', 'timestamp') else \
|
||||||
lambda x, y: cmp(x.lower() if x else '', y.lower() if y else '')
|
lambda x, y: cmp(x.lower() if x else '', y.lower() if y else '')
|
||||||
field = FIELD_MAP[field]
|
if field == 'series':
|
||||||
getter = operator.itemgetter(field)
|
items.sort(cmp=self.seriescmp, reverse=not order)
|
||||||
items.sort(cmp=lambda x, y: cmpf(getter(x), getter(y)), reverse=not order)
|
else:
|
||||||
|
field = FIELD_MAP[field]
|
||||||
|
getter = operator.itemgetter(field)
|
||||||
|
items.sort(cmp=lambda x, y: cmpf(getter(x), getter(y)), reverse=not order)
|
||||||
|
|
||||||
|
def seriescmp(self, x, y):
|
||||||
|
si = FIELD_MAP['series']
|
||||||
|
try:
|
||||||
|
ans = cmp(x[si].lower(), y[si].lower())
|
||||||
|
except AttributeError: # Some entries may be None
|
||||||
|
ans = cmp(x[si], y[si])
|
||||||
|
if ans != 0: return ans
|
||||||
|
return cmp(x[FIELD_MAP['series_index']], y[FIELD_MAP['series_index']])
|
||||||
|
|
||||||
|
|
||||||
def last_modified(self, updated):
|
def last_modified(self, updated):
|
||||||
lm = updated.strftime('day, %d month %Y %H:%M:%S GMT')
|
lm = updated.strftime('day, %d month %Y %H:%M:%S GMT')
|
||||||
day ={0:'Sun', 1:'Mon', 2:'Tue', 3:'Wed', 4:'Thu', 5:'Fri', 6:'Sat'}
|
day ={0:'Sun', 1:'Mon', 2:'Tue', 3:'Wed', 4:'Thu', 5:'Fri', 6:'Sat'}
|
||||||
@ -254,8 +267,8 @@ class LibraryServer(object):
|
|||||||
month = {1:'Jan', 2:'Feb', 3:'Mar', 4:'Apr', 5:'May', 6:'Jun', 7:'Jul',
|
month = {1:'Jan', 2:'Feb', 3:'Mar', 4:'Apr', 5:'May', 6:'Jun', 7:'Jul',
|
||||||
8:'Aug', 9:'Sep', 10:'Oct', 11:'Nov', 12:'Dec'}
|
8:'Aug', 9:'Sep', 10:'Oct', 11:'Nov', 12:'Dec'}
|
||||||
return lm.replace('month', month[updated.month])
|
return lm.replace('month', month[updated.month])
|
||||||
|
|
||||||
|
|
||||||
@expose
|
@expose
|
||||||
def stanza(self):
|
def stanza(self):
|
||||||
' Feeds to read calibre books on a ipod with stanza.'
|
' Feeds to read calibre books on a ipod with stanza.'
|
||||||
@ -264,7 +277,7 @@ class LibraryServer(object):
|
|||||||
r = record[FIELD_MAP['formats']]
|
r = record[FIELD_MAP['formats']]
|
||||||
r = r.upper() if r else ''
|
r = r.upper() if r else ''
|
||||||
if 'EPUB' in r or 'PDB' in r:
|
if 'EPUB' in r or 'PDB' in r:
|
||||||
authors = ' & '.join([i.replace('|', ',') for i in
|
authors = ' & '.join([i.replace('|', ',') for i in
|
||||||
record[FIELD_MAP['authors']].split(',')])
|
record[FIELD_MAP['authors']].split(',')])
|
||||||
extra = []
|
extra = []
|
||||||
rating = record[FIELD_MAP['rating']]
|
rating = record[FIELD_MAP['rating']]
|
||||||
@ -276,7 +289,7 @@ class LibraryServer(object):
|
|||||||
extra.append('TAGS: %s<br />'%', '.join(tags.split(',')))
|
extra.append('TAGS: %s<br />'%', '.join(tags.split(',')))
|
||||||
series = record[FIELD_MAP['series']]
|
series = record[FIELD_MAP['series']]
|
||||||
if series:
|
if series:
|
||||||
extra.append('SERIES: %s [%d]<br />'%(series,
|
extra.append('SERIES: %s [%d]<br />'%(series,
|
||||||
record[FIELD_MAP['series_index']]))
|
record[FIELD_MAP['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]
|
||||||
@ -288,24 +301,24 @@ class LibraryServer(object):
|
|||||||
mimetype=mimetype,
|
mimetype=mimetype,
|
||||||
fmt=fmt,
|
fmt=fmt,
|
||||||
).render('xml').decode('utf8'))
|
).render('xml').decode('utf8'))
|
||||||
|
|
||||||
updated = self.db.last_modified()
|
updated = self.db.last_modified()
|
||||||
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
|
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
|
||||||
cherrypy.response.headers['Content-Type'] = 'text/xml'
|
cherrypy.response.headers['Content-Type'] = 'text/xml'
|
||||||
|
|
||||||
return self.STANZA.generate(subtitle='', data=books, FM=FIELD_MAP,
|
return self.STANZA.generate(subtitle='', data=books, FM=FIELD_MAP,
|
||||||
updated=updated, id='urn:calibre:main').render('xml')
|
updated=updated, id='urn:calibre:main').render('xml')
|
||||||
|
|
||||||
@expose
|
@expose
|
||||||
def library(self, start='0', num='50', sort=None, search=None,
|
def library(self, start='0', num='50', sort=None, search=None,
|
||||||
_=None, order='ascending'):
|
_=None, order='ascending'):
|
||||||
'''
|
'''
|
||||||
Serves metadata from the calibre database as XML.
|
Serves metadata from the calibre database as XML.
|
||||||
|
|
||||||
:param sort: Sort results by ``sort``. Can be one of `title,author,rating`.
|
:param sort: Sort results by ``sort``. Can be one of `title,author,rating`.
|
||||||
:param search: Filter results by ``search`` query. See :class:`SearchQueryParser` for query syntax
|
:param search: Filter results by ``search`` query. See :class:`SearchQueryParser` for query syntax
|
||||||
:param start,num: Return the slice `[start:start+num]` of the sorted and filtered results
|
:param start,num: Return the slice `[start:start+num]` of the sorted and filtered results
|
||||||
:param _: Firefox seems to sometimes send this when using XMLHttpRequest with no caching
|
:param _: Firefox seems to sometimes send this when using XMLHttpRequest with no caching
|
||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
start = int(start)
|
start = int(start)
|
||||||
@ -321,19 +334,19 @@ class LibraryServer(object):
|
|||||||
items = [r for r in iter(self.db) if r[0] in ids]
|
items = [r for r in iter(self.db) if r[0] 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 _('Unknown')
|
aus = record[2] if record[2] else _('Unknown')
|
||||||
authors = '|'.join([i.replace('|', ',') for i in aus.split(',')])
|
authors = '|'.join([i.replace('|', ',') for i in aus.split(',')])
|
||||||
books.append(book.generate(r=record, authors=authors).render('xml').decode('utf-8'))
|
books.append(book.generate(r=record, authors=authors).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)).render('xml')
|
||||||
|
|
||||||
@expose
|
@expose
|
||||||
def index(self, **kwargs):
|
def index(self, **kwargs):
|
||||||
'The / URL'
|
'The / URL'
|
||||||
@ -341,8 +354,8 @@ class LibraryServer(object):
|
|||||||
if stanza == 919:
|
if stanza == 919:
|
||||||
return self.static('index.html')
|
return self.static('index.html')
|
||||||
return self.stanza()
|
return self.stanza()
|
||||||
|
|
||||||
|
|
||||||
@expose
|
@expose
|
||||||
def get(self, what, id):
|
def get(self, what, id):
|
||||||
'Serves files, covers, thumbnails from the calibre database'
|
'Serves files, covers, thumbnails from the calibre database'
|
||||||
@ -361,7 +374,7 @@ class LibraryServer(object):
|
|||||||
if what == 'cover':
|
if what == 'cover':
|
||||||
return self.get_cover(id)
|
return self.get_cover(id)
|
||||||
return self.get_format(id, what)
|
return self.get_format(id, what)
|
||||||
|
|
||||||
@expose
|
@expose
|
||||||
def static(self, name):
|
def static(self, name):
|
||||||
'Serves static content'
|
'Serves static content'
|
||||||
@ -392,11 +405,11 @@ def start_threaded_server(db, opts):
|
|||||||
server.thread.setDaemon(True)
|
server.thread.setDaemon(True)
|
||||||
server.thread.start()
|
server.thread.start()
|
||||||
return server
|
return server
|
||||||
|
|
||||||
def stop_threaded_server(server):
|
def stop_threaded_server(server):
|
||||||
server.exit()
|
server.exit()
|
||||||
server.thread = None
|
server.thread = None
|
||||||
|
|
||||||
def option_parser():
|
def option_parser():
|
||||||
return config().option_parser('%prog '+ _('[options]\n\nStart the calibre content server.'))
|
return config().option_parser('%prog '+ _('[options]\n\nStart the calibre content server.'))
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user