From 786ab0ef804c271a7d84ae25a40f716d5c18018e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 24 May 2010 15:26:18 -0600 Subject: [PATCH 1/4] Fix search() behavior --- src/calibre/library/caches.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index 5e6c10c27b..e943a4141e 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -632,19 +632,21 @@ class ResultCache(SearchQueryParser): q = '' if not ignore_search_restriction: q = self.search_restriction - elif not ignore_search_restriction: - q = u'%s (%s)' % (self.search_restriction, query) + else: + if ignore_search_restriction: + q = u'%s' % query + else: + q = u'%s (%s)' % (self.search_restriction, query) if not q: if return_matches: return list(self._map) # when return_matches, do not update the maps! self._map_filtered = list(self._map) - return [] + return matches = sorted(self.parse(q)) ans = [id for id in self._map if id in matches] if return_matches: return ans self._map_filtered = ans - return [] def set_search_restriction(self, s): self.search_restriction = s From c0fbc32e4caf64715d7fb2014563d2aad947f05c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 24 May 2010 15:53:00 -0600 Subject: [PATCH 2/4] Fix search() behavior again --- src/calibre/library/caches.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index e943a4141e..10487af75a 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -628,14 +628,13 @@ class ResultCache(SearchQueryParser): def search(self, query, return_matches=False, ignore_search_restriction=False): + q = '' if not query or not query.strip(): - q = '' if not ignore_search_restriction: q = self.search_restriction else: - if ignore_search_restriction: - q = u'%s' % query - else: + q = query + if not ignore_search_restriction: q = u'%s (%s)' % (self.search_restriction, query) if not q: if return_matches: From cdbfc91effd0330dbb931addbc56fa5d9db1f8ff Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 25 May 2010 09:15:04 -0600 Subject: [PATCH 3/4] Fix cleanup of device jobs on device yank --- src/calibre/gui2/device.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 19d0c5f068..41abc6cb95 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -37,6 +37,7 @@ class DeviceJob(BaseJob): self.exception = None self.job_manager = job_manager self._details = _('No details available.') + self._aborted = False def start_work(self): self.start_time = time.time() @@ -55,7 +56,11 @@ class DeviceJob(BaseJob): self.start_work() try: self.result = self.func(*self.args, **self.kwargs) + if self._aborted: + return except (Exception, SystemExit), err: + if self._aborted: + return self.failed = True self._details = unicode(err) + '\n\n' + \ traceback.format_exc() @@ -63,6 +68,12 @@ class DeviceJob(BaseJob): finally: self.job_done() + def abort(self, err): + self._aborted = True + self.failed = True + self._details = unicode(err) + self.exception = err + @property def log_file(self): return cStringIO.StringIO(self._details.encode('utf-8')) From 0042f90879a2de50ace70f007e44b829921424da Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 25 May 2010 09:15:43 -0600 Subject: [PATCH 4/4] More work on the OPDS catalogs --- src/calibre/library/server/base.py | 1 - src/calibre/library/server/opds.py | 125 ++++++++++++++++++++++++----- 2 files changed, 104 insertions(+), 22 deletions(-) diff --git a/src/calibre/library/server/base.py b/src/calibre/library/server/base.py index a8d4ae899c..68d3a40bab 100644 --- a/src/calibre/library/server/base.py +++ b/src/calibre/library/server/base.py @@ -66,7 +66,6 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache): self.embedded = embedded self.max_cover_width, self.max_cover_height = \ map(int, self.opts.max_cover.split('x')) - self.max_stanza_items = opts.max_opds_items path = P('content_server') self.build_time = fromtimestamp(os.stat(path).st_mtime) self.default_cover = open(P('content_server/default_cover.jpg'), 'rb').read() diff --git a/src/calibre/library/server/opds.py b/src/calibre/library/server/opds.py index 23ee58da7f..d6702cbe75 100644 --- a/src/calibre/library/server/opds.py +++ b/src/calibre/library/server/opds.py @@ -36,10 +36,10 @@ def UPDATED(dt, *args, **kwargs): return E.updated(dt.strftime('%Y-%m-%dT%H:%M:%S+00:00'), *args, **kwargs) LINK = partial(E.link, type='application/atom+xml') -NAVLINK = partial(E.link, +NAVLINK = partial(E.link, rel='subsection', type='application/atom+xml;type=feed;profile=opds-catalog') -def SEARCH(base_href, *args, **kwargs): +def SEARCH_LINK(base_href, *args, **kwargs): kwargs['rel'] = 'search' kwargs['title'] = 'Search' kwargs['href'] = base_href+'/search/{searchTerms}' @@ -64,43 +64,105 @@ def NAVCATALOG_ENTRY(base_href, updated, title, description, query): NAVLINK(href=href) ) +START_LINK = partial(NAVLINK, rel='start') +UP_LINK = partial(NAVLINK, rel='up') +FIRST_LINK = partial(NAVLINK, rel='first') +LAST_LINK = partial(NAVLINK, rel='last') +NEXT_LINK = partial(NAVLINK, rel='next') +PREVIOUS_LINK = partial(NAVLINK, rel='previous') + # }}} class Feed(object): + def __init__(self, id_, updated, version, subtitle=None, + title=__appname__ + ' ' + _('Library'), + up_link=None, first_link=None, last_link=None, + next_link=None, previous_link=None): + self.base_href = BASE_HREFS[version] + + self.root = \ + FEED( + TITLE(title), + AUTHOR(__appname__, uri='http://calibre-ebook.com'), + ID(id_), + UPDATED(updated), + SEARCH_LINK(self.base_href), + START_LINK(self.base_href) + ) + if up_link: + self.root.append(UP_LINK(up_link)) + if first_link: + self.root.append(FIRST_LINK(first_link)) + if last_link: + self.root.append(LAST_LINK(last_link)) + if next_link: + self.root.append(NEXT_LINK(next_link)) + if previous_link: + self.root.append(PREVIOUS_LINK(previous_link)) + if subtitle: + self.root.insert(1, SUBTITLE(subtitle)) + def __str__(self): return etree.tostring(self.root, pretty_print=True, encoding='utf-8', xml_declaration=True) -class TopLevel(Feed): +class TopLevel(Feed): # {{{ def __init__(self, updated, # datetime object in UTC categories, version, id_ = 'urn:calibre:main', + subtitle = _('Books in your library') ): - base_href = BASE_HREFS[version] - self.base_href = base_href - subc = partial(NAVCATALOG_ENTRY, base_href, updated) + Feed.__init__(self, id_, updated, version, subtitle=subtitle) + subc = partial(NAVCATALOG_ENTRY, self.base_href, updated) subcatalogs = [subc(_('By ')+title, _('Books sorted by ') + desc, q) for title, desc, q in categories] + for x in subcatalogs: + self.root.append(x) +# }}} - self.root = \ - FEED( - TITLE(__appname__ + ' ' + _('Library')), - ID(id_), - UPDATED(updated), - SEARCH(base_href), - AUTHOR(__appname__, uri='http://calibre-ebook.com'), - SUBTITLE(_('Books in your library')), - *subcatalogs - ) +class AcquisitionFeed(Feed): + + def __init__(self, updated, id_, items, offsets, page_url, up_url, version): + kwargs = {'up_link': up_url} + kwargs['first_link'] = page_url + kwargs['last_link'] = page_url+'?offset=%d'%offsets.last_offset + if offsets.offset > 0: + kwargs['previous_link'] = \ + page_url+'?offset=%d'%offsets.previous_offset + if offsets.next_offset > -1: + kwargs['next_offset'] = \ + page_url+'?offset=%d'%offsets.next_offset + Feed.__init__(self, id_, updated, version, **kwargs) STANZA_FORMATS = frozenset(['epub', 'pdb']) +class OPDSOffsets(object): + + def __init__(self, offset, delta, total): + if offset < 0: + offset = 0 + if offset >= total: + raise cherrypy.HTTPError(404, 'Invalid offset: %r'%offset) + self.offset = offset + self.next_offset = offset + delta + if self.next_offset >= total: + self.next_offset = -1 + if self.next_offset >= total: + self.next_offset = -1 + self.previous_offset = self.offset - delta + if self.previous_offset < 0: + self.previous_offset = 0 + self.last_offset = total - delta + if self.last_offset < 0: + self.last_offset = 0 + + class OPDSServer(object): def add_routes(self, connect): @@ -110,18 +172,39 @@ class OPDSServer(object): connect(base, base_href, self.opds, version=version) connect('opdsnavcatalog_'+base, base_href+'/navcatalog/{which}', self.opds_navcatalog, version=version) - connect('opdssearch_'+base, base_href+'/search/{terms}', + connect('opdssearch_'+base, base_href+'/search/{query}', self.opds_search, version=version) def get_opds_allowed_ids_for_version(self, version): search = '' if version > 0 else ' '.join(['format:='+x for x in STANZA_FORMATS]) - self.seach_cache(search) + self.search_cache(search) - def opds_search(self, terms=None, version=0): - version = int(version) - if not terms or version not in BASE_HREFS: + def get_opds_acquisition_feed(self, ids, offset, page_url, up_url, id_, + sort_by='title', ascending=True, version=0): + idx = self.db.FIELD_MAP['id'] + ids &= self.get_opds_allowed_ids_for_version(version) + items = [x for x in self.db.data.iterall() if x[idx] in ids] + self.sort(items, sort_by, ascending) + max_items = self.opts.max_opds_items + offsets = OPDSOffsets(offset, max_items, len(items)) + items = items[offsets.offset:offsets.next_offset] + return str(AcquisitionFeed(self.db.last_modified(), id_, items, offsets, page_url, up_url, version)) + + def opds_search(self, query=None, version=0, offset=0): + try: + offset = int(offset) + version = int(version) + except: raise cherrypy.HTTPError(404, 'Not found') + if query is None or version not in BASE_HREFS: + raise cherrypy.HTTPError(404, 'Not found') + try: + ids = self.search_cache(query) + except: + raise cherrypy.HTTPError(404, 'Search: %r not understood'%query) + return self.get_opds_acquisition_feed(ids, + sort_by='title', version=version) def opds_navcatalog(self, which=None, version=0): version = int(version)