def get(ctx, rd, what, book_id, library_id): book_id, rest = book_id.partition('_')[::2] try: book_id = int(book_id) except Exception: raise HTTPNotFound('Book with id %r does not exist' % book_id) db = get_db(ctx, rd, library_id) if db is None: raise HTTPNotFound('Library %r not found' % library_id) with db.safe_read_lock: if not ctx.has_id(rd, db, book_id): raise BookNotFound(book_id, db) library_id = db.server_library_id # in case library_id was None if what == 'thumb': sz = rd.query.get('sz') w, h = 60, 80 if sz is None: if rest: try: w, h = map(int, rest.split('_')) except Exception: pass elif sz == 'full': w = h = None elif 'x' in sz: try: w, h = map(int, sz.partition('x')[::2]) except Exception: pass else: try: w = h = int(sz) except Exception: pass return cover(ctx, rd, library_id, db, book_id, width=w, height=h) elif what == 'cover': return cover(ctx, rd, library_id, db, book_id) elif what == 'opf': mi = db.get_metadata(book_id, get_cover=False) rd.outheaders[ 'Content-Type'] = 'application/oebps-package+xml; charset=UTF-8' rd.outheaders['Last-Modified'] = http_date( timestampfromdt(mi.last_modified)) return metadata_to_opf(mi) elif what == 'json': from calibre.srv.ajax import book_to_json data, last_modified = book_to_json(ctx, rd, db, book_id) rd.outheaders['Last-Modified'] = http_date( timestampfromdt(last_modified)) return json(ctx, rd, get, data) else: try: return book_fmt(ctx, rd, library_id, db, book_id, what.lower()) except NoSuchFormat: raise HTTPNotFound('No %s format for the book %r' % (what.lower(), book_id))
def get(ctx, rd, what, book_id, library_id): book_id, rest = book_id.partition('_')[::2] try: book_id = int(book_id) except Exception: raise HTTPNotFound('Book with id %r does not exist' % book_id) db = get_db(ctx, rd, library_id) if db is None: raise HTTPNotFound('Library %r not found' % library_id) with db.safe_read_lock: if not ctx.has_id(rd, db, book_id): raise BookNotFound(book_id, db) library_id = db.server_library_id # in case library_id was None if what == 'thumb': sz = rd.query.get('sz') w, h = 60, 80 if sz is None: if rest: try: w, h = map(int, rest.split('_')) except Exception: pass elif sz == 'full': w = h = None elif 'x' in sz: try: w, h = map(int, sz.partition('x')[::2]) except Exception: pass else: try: w = h = int(sz) except Exception: pass return cover(ctx, rd, library_id, db, book_id, width=w, height=h) elif what == 'cover': return cover(ctx, rd, library_id, db, book_id) elif what == 'opf': mi = db.get_metadata(book_id, get_cover=False) rd.outheaders['Content-Type'] = 'application/oebps-package+xml; charset=UTF-8' rd.outheaders['Last-Modified'] = http_date(timestampfromdt(mi.last_modified)) return metadata_to_opf(mi) elif what == 'json': from calibre.srv.ajax import book_to_json data, last_modified = book_to_json(ctx, rd, db, book_id) rd.outheaders['Last-Modified'] = http_date(timestampfromdt(last_modified)) return json(ctx, rd, get, data) else: try: return book_fmt(ctx, rd, library_id, db, book_id, what.lower()) except NoSuchFormat: raise HTTPNotFound('No %s format for the book %r' % (what.lower(), book_id))
def mobile(ctx, rd): db, library_id, library_map, default_library = get_library_data(ctx, rd) try: start = max(1, int(rd.query.get('start', 1))) except ValueError: raise HTTPBadRequest('start is not an integer') try: num = max(0, int(rd.query.get('num', 25))) except ValueError: raise HTTPBadRequest('num is not an integer') search = rd.query.get('search') or '' with db.safe_read_lock: book_ids = ctx.search(rd, db, search) total = len(book_ids) ascending = rd.query.get('order', '').lower().strip() == 'ascending' sort_by = sanitize_sort_field_name(db.field_metadata, rd.query.get('sort') or 'date') try: book_ids = db.multisort([(sort_by, ascending)], book_ids) except Exception: sort_by = 'date' book_ids = db.multisort([(sort_by, ascending)], book_ids) books = [db.get_metadata(book_id) for book_id in book_ids[(start-1):(start-1)+num]] rd.outheaders['Last-Modified'] = http_date(timestampfromdt(db.last_modified())) order = 'ascending' if ascending else 'descending' q = {b'search':search.encode('utf-8'), b'order':bytes(order), b'sort':sort_by.encode('utf-8'), b'num':bytes(num), 'library_id':library_id} url_base = ctx.url_for('/mobile') + '?' + urlencode(q) lm = {k:v for k, v in library_map.iteritems() if k != library_id} return build_index(books, num, search, sort_by, order, start, total, url_base, db.field_metadata, ctx, lm, library_id)
def opds(ctx, rd): rc = RequestContext(ctx, rd) db = rc.db try: categories = rc.get_categories(report_parse_errors=True) except ParseException as p: raise HTTPInternalServerError(p.msg) category_meta = db.field_metadata cats = [ (_('Newest'), _('Date'), 'Onewest'), (_('Title'), _('Title'), 'Otitle'), ] def getter(x): try: return category_meta[x]['name'].lower() except KeyError: return x fm = rc.db.field_metadata for category in sorted(categories, key=lambda x: sort_key(getter(x))): if fm.is_ignorable_field(category) and not rc.ctx.is_field_displayable(category): continue if len(categories[category]) == 0: continue if category in ('formats', 'identifiers'): continue meta = category_meta.get(category, None) if meta is None: continue cats.append((meta['name'], meta['name'], 'N'+category)) last_modified = db.last_modified() rd.outheaders['Last-Modified'] = http_date(timestampfromdt(last_modified)) return TopLevel(last_modified, cats, rc).root
def generated_cover(ctx, rd, library_id, db, book_id, width=None, height=None): prefix = 'generated-cover' if height is not None: prefix += '-%sx%s' % (width, height) mtime = timestampfromdt(db.field_for('last_modified', book_id)) return create_file_copy(ctx, rd, prefix, library_id, book_id, 'jpg', mtime, partial(write_generated_cover, db, book_id, width, height))
def mobile(ctx, rd): db, library_id, library_map, default_library = get_library_data(ctx, rd) try: start = max(1, int(rd.query.get('start', 1))) except ValueError: raise HTTPBadRequest('start is not an integer') try: num = max(0, int(rd.query.get('num', 25))) except ValueError: raise HTTPBadRequest('num is not an integer') search = rd.query.get('search') or '' with db.safe_read_lock: book_ids = ctx.search(rd, db, search) total = len(book_ids) ascending = rd.query.get('order', '').lower().strip() == 'ascending' sort_by = sanitize_sort_field_name(db.field_metadata, rd.query.get('sort') or 'date') try: book_ids = db.multisort([(sort_by, ascending)], book_ids) except Exception: sort_by = 'date' book_ids = db.multisort([(sort_by, ascending)], book_ids) books = [db.get_metadata(book_id) for book_id in book_ids[(start-1):(start-1)+num]] rd.outheaders['Last-Modified'] = http_date(timestampfromdt(db.last_modified())) order = 'ascending' if ascending else 'descending' q = {b'search':search.encode('utf-8'), b'order':bytes(order), b'sort':sort_by.encode('utf-8'), b'num':bytes(num), 'library_id':library_id} url_base = ctx.url_for('/mobile') + '?' + urlencode(q) lm = {k:v for k, v in iteritems(library_map) if k != library_id} return build_index(rd, books, num, search, sort_by, order, start, total, url_base, db.field_metadata, ctx, lm, library_id)
def create_file_copy(ctx, rd, prefix, library_id, book_id, ext, mtime, copy_func, extra_etag_data=''): ''' We cannot copy files directly from the library folder to the output socket, as this can potentially lock the library for an extended period. So instead we copy out the data from the library folder into a temp folder. We make sure to only do this copy once, using the previous copy, if there have been no changes to the data for the file since the last copy. ''' # Avoid too many items in a single directory for performance base = os.path.join(rd.tdir, 'fcache', (('%x' % book_id)[-3:])) library_id = library_id.replace('\\', '_').replace('/', '_') bname = '%s-%s-%s.%s' % (prefix, library_id, book_id, ext) fname = os.path.join(base, bname) do_copy = True mtime = timestampfromdt(mtime) try: ans = lopen(fname, 'r+b') do_copy = os.fstat(ans.fileno()).st_mtime < mtime except EnvironmentError: try: ans = lopen(fname, 'w+b') except EnvironmentError: try: os.makedirs(base) except EnvironmentError: pass ans = lopen(fname, 'w+b') do_copy = True if do_copy: copy_func(ans) ans.seek(0) if ctx.testing: rd.outheaders['Used-Cache'] = 'no' if do_copy else 'yes' return rd.filesystem_file_with_custom_etag(ans, prefix, library_id, book_id, mtime, extra_etag_data)
def get_acquisition_feed(rc, ids, offset, page_url, up_url, id_, sort_by='title', ascending=True, feed_title=None): if not ids: raise HTTPNotFound('No books found') with rc.db.safe_read_lock: sort_by = sanitize_sort_field_name(rc.db.field_metadata, sort_by) items = rc.db.multisort([(sort_by, ascending)], ids) max_items = rc.opts.max_opds_items offsets = Offsets(offset, max_items, len(items)) items = items[offsets.offset:offsets.offset + max_items] lm = rc.last_modified() rc.outheaders['Last-Modified'] = http_date(timestampfromdt(lm)) return AcquisitionFeed(id_, lm, rc, items, offsets, page_url, up_url, title=feed_title).root
def opds(ctx, rd): rc = RequestContext(ctx, rd) db = rc.db categories = rc.get_categories() category_meta = db.field_metadata cats = [ (_('Newest'), _('Date'), 'Onewest'), (_('Title'), _('Title'), 'Otitle'), ] def getter(x): try: return category_meta[x]['name'].lower() except KeyError: return x for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue if category in ('formats', 'identifiers'): continue meta = category_meta.get(category, None) if meta is None: continue cats.append((meta['name'], meta['name'], 'N' + category)) last_modified = db.last_modified() rd.outheaders['Last-Modified'] = http_date(timestampfromdt(last_modified)) return TopLevel(last_modified, cats, rc).root
def opds(ctx, rd): rc = RequestContext(ctx, rd) db = rc.db categories = rc.get_categories() category_meta = db.field_metadata cats = [(_("Newest"), _("Date"), "Onewest"), (_("Title"), _("Title"), "Otitle")] def getter(x): try: return category_meta[x]["name"].lower() except KeyError: return x for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue if category in ("formats", "identifiers"): continue meta = category_meta.get(category, None) if meta is None: continue cats.append((meta["name"], meta["name"], "N" + category)) last_modified = db.last_modified() rd.outheaders["Last-Modified"] = http_date(timestampfromdt(last_modified)) return TopLevel(last_modified, cats, rc).root
def opds(ctx, rd): rc = RequestContext(ctx, rd) db = rc.db categories = rc.get_categories() category_meta = db.field_metadata cats = [ (_('Newest'), _('Date'), 'Onewest'), (_('Title'), _('Title'), 'Otitle'), ] def getter(x): try: return category_meta[x]['name'].lower() except KeyError: return x for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue if category in ('formats', 'identifiers'): continue meta = category_meta.get(category, None) if meta is None: continue cats.append((meta['name'], meta['name'], 'N'+category)) last_modified = db.last_modified() rd.outheaders['Last-Modified'] = http_date(timestampfromdt(last_modified)) return TopLevel(last_modified, cats, rc).root
def get_navcatalog(request_context, which, page_url, up_url, offset=0): categories = request_context.get_categories() if which not in categories: raise HTTPNotFound('Category %r not found' % which) items = categories[which] updated = request_context.last_modified() category_meta = request_context.db.field_metadata meta = category_meta.get(which, {}) category_name = meta.get('name', which) feed_title = default_feed_title + ' :: ' + _('By %s') % category_name id_ = 'calibre-category-feed:' + which MAX_ITEMS = request_context.opts.max_opds_ungrouped_items if MAX_ITEMS > 0 and len(items) <= MAX_ITEMS: max_items = request_context.opts.max_opds_items offsets = Offsets(offset, max_items, len(items)) items = list(items)[offsets.offset:offsets.offset + max_items] ans = CategoryFeed(items, which, id_, updated, request_context, offsets, page_url, up_url, title=feed_title) else: Group = namedtuple('Group', 'text count') starts = set() for x in items: val = getattr(x, 'sort', x.name) if not val: val = 'A' starts.add(val[0].upper()) category_groups = OrderedDict() for x in sorted(starts, key=sort_key): category_groups[x] = len( [y for y in items if getattr(y, 'sort', y.name).startswith(x)]) items = [Group(x, y) for x, y in category_groups.items()] max_items = request_context.opts.max_opds_items offsets = Offsets(offset, max_items, len(items)) items = items[offsets.offset:offsets.offset + max_items] ans = CategoryGroupFeed(items, which, id_, updated, request_context, offsets, page_url, up_url, title=feed_title) request_context.outheaders['Last-Modified'] = http_date( timestampfromdt(updated)) return ans.root
def get(ctx, rd, what, book_id, library_id): db = ctx.get_library(library_id) if db is None: raise HTTPNotFound("Library %r not found" % library_id) with db.safe_read_lock: if book_id not in ctx.allowed_book_ids(rd, db): raise HTTPNotFound("Book with id %r does not exist" % book_id) library_id = db.server_library_id # in case library_id was None if what == "thumb": sz = rd.query.get("sz") w, h = 60, 80 if sz is None: pass elif sz == "full": w = h = None elif "x" in sz: try: w, h = map(int, sz.partition("x")[::2]) except Exception: pass else: try: w = h = int(sz) except Exception: pass return cover(ctx, rd, library_id, db, book_id, width=w, height=h) elif what == "cover": return cover(ctx, rd, library_id, db, book_id) elif what == "opf": mi = db.get_metadata(book_id, get_cover=False) rd.outheaders["Content-Type"] = "application/oebps-package+xml; charset=UTF-8" rd.outheaders["Last-Modified"] = http_date(timestampfromdt(mi.last_modified)) return metadata_to_opf(mi) elif what == "json": from calibre.srv.ajax import book_to_json data, last_modified = book_to_json(ctx, rd, db, book_id) rd.outheaders["Last-Modified"] = http_date(timestampfromdt(last_modified)) return json(ctx, rd, get, data) else: try: return book_fmt(ctx, rd, library_id, db, book_id, what.lower()) except NoSuchFormat: raise HTTPNotFound("No %r format for the book %r" % (what.lower(), book_id))
def books(ctx, rd, library_id): ''' Return the metadata for the books as a JSON dictionary. Query parameters: ?ids=all&category_urls=true&id_is_uuid=false&device_for_template=None If category_urls is true the returned dictionary also contains a mapping of category (field) names to URLs that return the list of books in the given category. If id_is_uuid is true then the book_id is assumed to be a book uuid instead. ''' db = get_db(ctx, rd, library_id) with db.safe_read_lock: id_is_uuid = rd.query.get('id_is_uuid', 'false') ids = rd.query.get('ids') if ids is None or ids == 'all': ids = db.all_book_ids() else: ids = ids.split(',') if id_is_uuid == 'true': ids = {db.lookup_by_uuid(x) for x in ids} ids.discard(None) else: try: ids = {int(x) for x in ids} except Exception: raise HTTPNotFound( 'ids must a comma separated list of integers') last_modified = None category_urls = rd.query.get('category_urls', 'true').lower() == 'true' device_compatible = rd.query.get('device_compatible', 'false').lower() == 'true' device_for_template = rd.query.get('device_for_template', None) ans = {} allowed_book_ids = ctx.allowed_book_ids(rd, db) for book_id in ids: if book_id not in allowed_book_ids: ans[book_id] = None continue data, lm = book_to_json(ctx, rd, db, book_id, get_category_urls=category_urls, device_compatible=device_compatible, device_for_template=device_for_template) last_modified = lm if last_modified is None else max( lm, last_modified) ans[book_id] = data if last_modified is not None: rd.outheaders['Last-Modified'] = http_date( timestampfromdt(last_modified)) return ans
def opds_categorygroup(ctx, rd, category, which): try: offset = int(rd.query.get('offset', 0)) except Exception: raise HTTPNotFound('Not found') if not which or not category: raise HTTPNotFound('Not found') rc = RequestContext(ctx, rd) categories = rc.get_categories() page_url = rc.url_for('/opds/categorygroup', category=category, which=which) category = from_hex_unicode(category) if category not in categories: raise HTTPNotFound('Category %r not found' % which) category_meta = rc.db.field_metadata meta = category_meta.get(category, {}) category_name = meta.get('name', which) which = from_hex_unicode(which) feed_title = default_feed_title + ' :: ' + (_('By {0} :: {1}').format( category_name, which)) owhich = as_hex_unicode('N' + which) up_url = rc.url_for('/opds/navcatalog', which=owhich) items = categories[category] def belongs(x, which): return getattr(x, 'sort', x.name).lower().startswith(which.lower()) items = [x for x in items if belongs(x, which)] if not items: raise HTTPNotFound('No items in group %r:%r' % (category, which)) updated = rc.last_modified() id_ = 'calibre-category-group-feed:' + category + ':' + which max_items = rc.opts.max_opds_items offsets = Offsets(offset, max_items, len(items)) items = list(items)[offsets.offset:offsets.offset + max_items] rc.outheaders['Last-Modified'] = http_date(timestampfromdt(updated)) return CategoryFeed(items, category, id_, updated, rc, offsets, page_url, up_url, title=feed_title).root
def get_acquisition_feed(rc, ids, offset, page_url, up_url, id_, sort_by='title', ascending=True, feed_title=None): if not ids: raise HTTPNotFound('No books found') with rc.db.safe_read_lock: items = rc.db.multisort([(sort_by, ascending)], ids) max_items = rc.opts.max_opds_items offsets = Offsets(offset, max_items, len(items)) items = items[offsets.offset:offsets.offset+max_items] lm = rc.last_modified() rc.outheaders['Last-Modified'] = http_date(timestampfromdt(lm)) return AcquisitionFeed(id_, lm, rc, items, offsets, page_url, up_url, title=feed_title).root
def books(ctx, rd, library_id): """ Return the metadata for the books as a JSON dictionary. Query parameters: ?ids=all&category_urls=true&id_is_uuid=false&device_for_template=None If category_urls is true the returned dictionary also contains a mapping of category (field) names to URLs that return the list of books in the given category. If id_is_uuid is true then the book_id is assumed to be a book uuid instead. """ db = get_db(ctx, library_id) with db.safe_read_lock: id_is_uuid = rd.query.get("id_is_uuid", "false") ids = rd.query.get("ids") if ids is None or ids == "all": ids = db.all_book_ids() else: ids = ids.split(",") if id_is_uuid == "true": ids = {db.lookup_by_uuid(x) for x in ids} ids.discard(None) else: try: ids = {int(x) for x in ids} except Exception: raise HTTPNotFound("ids must a comma separated list of integers") last_modified = None category_urls = rd.query.get("category_urls", "true").lower() == "true" device_compatible = rd.query.get("device_compatible", "false").lower() == "true" device_for_template = rd.query.get("device_for_template", None) ans = {} restricted_to = ctx.allowed_book_ids(rd, db) for book_id in ids: if book_id not in restricted_to: ans[book_id] = None continue data, lm = book_to_json( ctx, rd, db, book_id, get_category_urls=category_urls, device_compatible=device_compatible, device_for_template=device_for_template, ) last_modified = lm if last_modified is None else max(lm, last_modified) ans[book_id] = data if last_modified is not None: rd.outheaders["Last-Modified"] = http_date(timestampfromdt(last_modified)) return ans
def books(ctx, rd, library_id): ''' Return the metadata for the books as a JSON dictionary. Query parameters: ?ids=all&category_urls=true&id_is_uuid=false&device_for_template=None If category_urls is true the returned dictionary also contains a mapping of category (field) names to URLs that return the list of books in the given category. If id_is_uuid is true then the book_id is assumed to be a book uuid instead. ''' db = ctx.get_library(library_id) if db is None: raise HTTPNotFound('Library %r not found' % library_id) with db.safe_read_lock: id_is_uuid = rd.query.get('id_is_uuid', 'false') ids = rd.query.get('ids') if ids is None or ids == 'all': ids = db.all_book_ids() else: ids = ids.split(',') if id_is_uuid == 'true': ids = {db.lookup_by_uuid(x) for x in ids} ids.discard(None) else: try: ids = {int(x) for x in ids} except Exception: raise HTTPNotFound('ids must a comma separated list of integers') last_modified = None category_urls = rd.query.get('category_urls', 'true').lower() == 'true' device_compatible = rd.query.get('device_compatible', 'false').lower() == 'true' device_for_template = rd.query.get('device_for_template', None) ans = {} restricted_to = ctx.restrict_to_ids(db, rd) for book_id in ids: if book_id not in restricted_to: ans[book_id] = None continue data, lm = book_to_json( ctx, rd, db, book_id, get_category_urls=category_urls, device_compatible=device_compatible, device_for_template=device_for_template) last_modified = lm if last_modified is None else max(lm, last_modified) ans[book_id] = data if last_modified is not None: rd.outheaders['Last-Modified'] = http_date(timestampfromdt(last_modified)) return ans
def get_navcatalog(request_context, which, page_url, up_url, offset=0): categories = request_context.get_categories() if which not in categories: raise HTTPNotFound('Category %r not found'%which) items = categories[which] updated = request_context.last_modified() category_meta = request_context.db.field_metadata meta = category_meta.get(which, {}) category_name = meta.get('name', which) feed_title = default_feed_title + ' :: ' + _('By %s') % category_name id_ = 'calibre-category-feed:'+which MAX_ITEMS = request_context.opts.max_opds_ungrouped_items if MAX_ITEMS > 0 and len(items) <= MAX_ITEMS: max_items = request_context.opts.max_opds_items offsets = Offsets(offset, max_items, len(items)) items = list(items)[offsets.offset:offsets.offset+max_items] ans = CategoryFeed(items, which, id_, updated, request_context, offsets, page_url, up_url, title=feed_title) else: Group = namedtuple('Group', 'text count') starts = set() for x in items: val = getattr(x, 'sort', x.name) if not val: val = 'A' starts.add(val[0].upper()) category_groups = OrderedDict() for x in sorted(starts, key=sort_key): category_groups[x] = len([y for y in items if getattr(y, 'sort', y.name).startswith(x)]) items = [Group(x, y) for x, y in category_groups.items()] max_items = request_context.opts.max_opds_items offsets = Offsets(offset, max_items, len(items)) items = items[offsets.offset:offsets.offset+max_items] ans = CategoryGroupFeed(items, which, id_, updated, request_context, offsets, page_url, up_url, title=feed_title) request_context.outheaders['Last-Modified'] = http_date(timestampfromdt(updated)) return ans.root
def opds_categorygroup(ctx, rd, category, which): try: offset = int(rd.query.get("offset", 0)) except Exception: raise HTTPNotFound("Not found") if not which or not category: raise HTTPNotFound("Not found") rc = RequestContext(ctx, rd) categories = rc.get_categories() page_url = rc.url_for("/opds/categorygroup", category=category, which=which) category = unhexlify(category) if category not in categories: raise HTTPNotFound("Category %r not found" % which) category_meta = rc.db.field_metadata meta = category_meta.get(category, {}) category_name = meta.get("name", which) which = unhexlify(which) feed_title = default_feed_title + " :: " + (_("By {0} :: {1}").format(category_name, which)) owhich = hexlify("N" + which) up_url = rc.url_for("/opds/navcatalog", which=owhich) items = categories[category] def belongs(x, which): return getattr(x, "sort", x.name).lower().startswith(which.lower()) items = [x for x in items if belongs(x, which)] if not items: raise HTTPNotFound("No items in group %r:%r" % (category, which)) updated = rc.last_modified() id_ = "calibre-category-group-feed:" + category + ":" + which max_items = rc.opts.max_opds_items offsets = Offsets(offset, max_items, len(items)) items = list(items)[offsets.offset : offsets.offset + max_items] rc.outheaders["Last-Modified"] = http_date(timestampfromdt(updated)) return CategoryFeed(items, category, id_, updated, rc, offsets, page_url, up_url, title=feed_title).root
def book(ctx, rd, book_id, library_id): """ Return the metadata of the book as a JSON dictionary. Query parameters: ?category_urls=true&id_is_uuid=false&device_for_template=None If category_urls is true the returned dictionary also contains a mapping of category (field) names to URLs that return the list of books in the given category. If id_is_uuid is true then the book_id is assumed to be a book uuid instead. """ db = get_db(ctx, library_id) with db.safe_read_lock: id_is_uuid = rd.query.get("id_is_uuid", "false") oid = book_id if id_is_uuid == "true": book_id = db.lookup_by_uuid(book_id) else: try: book_id = int(book_id) if not db.has_id(book_id): book_id = None except Exception: book_id = None if book_id is None or book_id not in ctx.allowed_book_ids(rd, db): raise HTTPNotFound("Book with id %r does not exist" % oid) category_urls = rd.query.get("category_urls", "true").lower() device_compatible = rd.query.get("device_compatible", "false").lower() device_for_template = rd.query.get("device_for_template", None) data, last_modified = book_to_json( ctx, rd, db, book_id, get_category_urls=category_urls == "true", device_compatible=device_compatible == "true", device_for_template=device_for_template, ) rd.outheaders["Last-Modified"] = http_date(timestampfromdt(last_modified)) return data
def book(ctx, rd, book_id, library_id): ''' Return the metadata of the book as a JSON dictionary. Query parameters: ?category_urls=true&id_is_uuid=false&device_for_template=None If category_urls is true the returned dictionary also contains a mapping of category (field) names to URLs that return the list of books in the given category. If id_is_uuid is true then the book_id is assumed to be a book uuid instead. ''' db = get_db(ctx, rd, library_id) with db.safe_read_lock: id_is_uuid = rd.query.get('id_is_uuid', 'false') oid = book_id if id_is_uuid == 'true': book_id = db.lookup_by_uuid(book_id) else: try: book_id = int(book_id) if not db.has_id(book_id): book_id = None except Exception: book_id = None if book_id is None or not ctx.has_id(rd, db, book_id): raise BookNotFound(oid, db) category_urls = rd.query.get('category_urls', 'true').lower() device_compatible = rd.query.get('device_compatible', 'false').lower() device_for_template = rd.query.get('device_for_template', None) data, last_modified = book_to_json( ctx, rd, db, book_id, get_category_urls=category_urls == 'true', device_compatible=device_compatible == 'true', device_for_template=device_for_template) rd.outheaders['Last-Modified'] = http_date(timestampfromdt(last_modified)) return data
def opds_categorygroup(ctx, rd, category, which): try: offset = int(rd.query.get('offset', 0)) except Exception: raise HTTPNotFound('Not found') if not which or not category: raise HTTPNotFound('Not found') rc = RequestContext(ctx, rd) categories = rc.get_categories() page_url = rc.url_for('/opds/categorygroup', category=category, which=which) category = from_hex_unicode(category) if category not in categories: raise HTTPNotFound('Category %r not found'%which) category_meta = rc.db.field_metadata meta = category_meta.get(category, {}) category_name = meta.get('name', which) which = from_hex_unicode(which) feed_title = default_feed_title + ' :: ' + (_('By {0} :: {1}').format(category_name, which)) owhich = as_hex_unicode('N'+which) up_url = rc.url_for('/opds/navcatalog', which=owhich) items = categories[category] def belongs(x, which): return getattr(x, 'sort', x.name).lower().startswith(which.lower()) items = [x for x in items if belongs(x, which)] if not items: raise HTTPNotFound('No items in group %r:%r'%(category, which)) updated = rc.last_modified() id_ = 'calibre-category-group-feed:'+category+':'+which max_items = rc.opts.max_opds_items offsets = Offsets(offset, max_items, len(items)) items = list(items)[offsets.offset:offsets.offset+max_items] rc.outheaders['Last-Modified'] = http_date(timestampfromdt(updated)) return CategoryFeed(items, category, id_, updated, rc, offsets, page_url, up_url, title=feed_title).root
def get_item_data(self, book_id, col): mi = self.db.new_api.get_proxy_metadata(book_id) try: if col == 'title': return (mi.title, mi.title_sort, 0) elif col == 'authors': return (' & '.join(mi.authors), mi.author_sort, 0) elif col == 'series': series = mi.format_field('series')[1] if series is None: return ('', None, 0) else: return (series, mi.series, mi.series_index) elif col == 'size': v = mi.get('book_size') if v is not None: return (f'{v:n}', v, 0) else: return ('', None, 0) elif self.fm[col]['datatype'] == 'series': v = mi.format_field(col)[1] return (v, mi.get(col), mi.get(col + '_index')) elif self.fm[col]['datatype'] == 'datetime': v = mi.format_field(col)[1] d = mi.get(col) if d is None: d = UNDEFINED_DATE return (v, timestampfromdt(d), 0) elif self.fm[col]['datatype'] in ('float', 'int'): v = mi.format_field(col)[1] sort_val = mi.get(col) return (v, sort_val, 0) else: v = mi.format_field(col)[1] return (v, v, 0) except: traceback.print_exc() return (_('Something went wrong while filling in the table'), '', 0)
def book(ctx, rd, book_id, library_id): ''' Return the metadata of the book as a JSON dictionary. Query parameters: ?category_urls=true&id_is_uuid=false&device_for_template=None If category_urls is true the returned dictionary also contains a mapping of category (field) names to URLs that return the list of books in the given category. If id_is_uuid is true then the book_id is assumed to be a book uuid instead. ''' db = ctx.get_library(library_id) if db is None: raise HTTPNotFound('Library %r not found' % library_id) with db.safe_read_lock: id_is_uuid = rd.query.get('id_is_uuid', 'false') oid = book_id if id_is_uuid == 'true': book_id = db.lookup_by_uuid(book_id) else: try: book_id = int(book_id) if not db.has_id(book_id): book_id = None except Exception: book_id = None if book_id is None: raise HTTPNotFound('Book with id %r does not exist' % oid) category_urls = rd.query.get('category_urls', 'true').lower() device_compatible = rd.query.get('device_compatible', 'false').lower() device_for_template = rd.query.get('device_for_template', None) data, last_modified = book_to_json(ctx, rd, db, book_id, get_category_urls=category_urls == 'true', device_compatible=device_compatible == 'true', device_for_template=device_for_template) rd.outheaders['Last-Modified'] = http_date(timestampfromdt(last_modified)) return data
def fill_in_books_box(self, selected_item): ''' Given the selected row in the left-hand box, fill in the grid with the books that contain that data. ''' # Do a bit of fix-up on the items so that the search works. if selected_item.startswith('.'): sv = '.' + selected_item else: sv = selected_item sv = self.current_key + ':"=' + sv.replace('"', r'\"') + '"' if gprefs['qv_respects_vls']: books = self.db.search(sv, return_matches=True, sort_results=False) else: books = self.db.new_api.search(sv) self.books_table.setRowCount(len(books)) label_text = _('&Books with selected item "{0}": {1}') if self.is_pane: label_text = label_text.replace('&', '') self.books_label.setText(label_text.format(selected_item, len(books))) select_item = None self.books_table.setSortingEnabled(False) self.books_table.blockSignals(True) tt = ('<p>' + _( 'Double click on a book to change the selection in the library view or ' 'change the column shown in the left-hand pane. ' 'Shift- or Control- double click to edit the metadata of a book, ' 'which also changes the selected book.' ) + '</p>') for row, b in enumerate(books): mi = self.db.new_api.get_proxy_metadata(b) for col in self.column_order: try: if col == 'title': a = TableItem(mi.title, mi.title_sort) if b == self.current_book_id: select_item = a elif col == 'authors': a = TableItem(' & '.join(mi.authors), mi.author_sort) elif col == 'series': series = mi.format_field('series')[1] if series is None: a = TableItem('', '', 0) else: a = TableItem(series, mi.series, mi.series_index) elif col == 'size': v = mi.get('book_size') if v is not None: a = TableItem('{:n}'.format(v), v) a.setTextAlignment(Qt.AlignRight) else: a = TableItem(' ', None) elif self.fm[col]['datatype'] == 'series': v = mi.format_field(col)[1] a = TableItem(v, mi.get(col), mi.get(col+'_index')) elif self.fm[col]['datatype'] == 'datetime': v = mi.format_field(col)[1] d = mi.get(col) if d is None: d = UNDEFINED_DATE a = TableItem(v, timestampfromdt(d)) elif self.fm[col]['datatype'] in ('float', 'int'): v = mi.format_field(col)[1] sv = mi.get(col) a = TableItem(v, sv) else: v = mi.format_field(col)[1] a = TableItem(v, v) except: traceback.print_exc() a = TableItem(_('Something went wrong while filling in the table'), '') a.setData(Qt.UserRole, b) a.setToolTip(tt) self.books_table.setItem(row, self.key_to_table_widget_column(col), a) self.books_table.setRowHeight(row, self.books_table_row_height) self.books_table.blockSignals(False) self.books_table.setSortingEnabled(True) if select_item is not None: self.books_table.setCurrentItem(select_item) self.books_table.scrollToItem(select_item, QAbstractItemView.PositionAtCenter) self.set_search_text(sv)
def create_file_copy(ctx, rd, prefix, library_id, book_id, ext, mtime, copy_func, extra_etag_data=''): ''' We cannot copy files directly from the library folder to the output socket, as this can potentially lock the library for an extended period. So instead we copy out the data from the library folder into a temp folder. We make sure to only do this copy once, using the previous copy, if there have been no changes to the data for the file since the last copy. ''' global rename_counter # Avoid too many items in a single directory for performance base = os.path.join(rd.tdir, 'fcache', (('%x' % book_id)[-3:])) if iswindows: base = '\\\\?\\' + os.path.abspath( base) # Ensure fname is not too long for windows' API bname = '%s-%s-%x.%s' % (prefix, library_id, book_id, ext) if '\\' in bname or '/' in bname: raise ValueError('File components must not contain path separators') fname = os.path.join(base, bname) used_cache = 'no' def safe_mtime(): with suppress(OSError): return os.path.getmtime(fname) mt = mtime if isinstance(mtime, (int, float)) else timestampfromdt(mtime) with lock: previous_mtime = safe_mtime() if previous_mtime is None or previous_mtime < mt: if previous_mtime is not None: # File exists and may be open, so we cannot change its # contents, as that would lead to corrupted downloads in any # clients that are currently downloading the file. if iswindows: # On windows in order to re-use bname, we have to rename it # before deleting it rename_counter += 1 dname = os.path.join(base, '_%x' % rename_counter) atomic_rename(fname, dname) os.remove(dname) else: os.remove(fname) ans = open_for_write(fname) copy_func(ans) ans.seek(0) else: try: ans = share_open(fname, 'rb') used_cache = 'yes' except EnvironmentError as err: if err.errno != errno.ENOENT: raise ans = open_for_write(fname) copy_func(ans) ans.seek(0) if ctx.testing: rd.outheaders['Used-Cache'] = used_cache rd.outheaders['Tempfile'] = as_hex_unicode(fname) return rd.filesystem_file_with_custom_etag(ans, prefix, library_id, book_id, mt, extra_etag_data)
def fill_in_books_box(self, selected_item): ''' Given the selected row in the left-hand box, fill in the grid with the books that contain that data. ''' # Do a bit of fix-up on the items so that the search works. if selected_item.startswith('.'): sv = '.' + selected_item else: sv = selected_item sv = self.current_key + ':"=' + sv.replace('"', r'\"') + '"' if gprefs['qv_respects_vls']: books = self.db.search(sv, return_matches=True, sort_results=False) else: books = self.db.new_api.search(sv) self.books_table.setRowCount(len(books)) label_text = _('&Books with selected item "{0}": {1}') if self.is_pane: label_text = label_text.replace('&', '') self.books_label.setText(label_text.format(selected_item, len(books))) select_item = None self.books_table.setSortingEnabled(False) self.books_table.blockSignals(True) tt = ('<p>' + _( 'Double click on a book to change the selection in the library view or ' 'change the column shown in the left-hand pane. ' 'Shift- or Control- double click to edit the metadata of a book, ' 'which also changes the selected book.') + '</p>') for row, b in enumerate(books): mi = self.db.new_api.get_proxy_metadata(b) for col in self.column_order: try: if col == 'title': a = TableItem(mi.title, mi.title_sort) if b == self.current_book_id: select_item = a elif col == 'authors': a = TableItem(' & '.join(mi.authors), mi.author_sort) elif col == 'series': series = mi.format_field('series')[1] if series is None: a = TableItem('', '', 0) else: a = TableItem(series, mi.series, mi.series_index) elif col == 'size': v = mi.get('book_size') if v is not None: a = TableItem('{:n}'.format(v), v) a.setTextAlignment(Qt.AlignRight) else: a = TableItem(' ', None) elif self.fm[col]['datatype'] == 'series': v = mi.format_field(col)[1] a = TableItem(v, mi.get(col), mi.get(col + '_index')) elif self.fm[col]['datatype'] == 'datetime': v = mi.format_field(col)[1] d = mi.get(col) if d is None: d = UNDEFINED_DATE a = TableItem(v, timestampfromdt(d)) elif self.fm[col]['datatype'] in ('float', 'int'): v = mi.format_field(col)[1] sv = mi.get(col) a = TableItem(v, sv) else: v = mi.format_field(col)[1] a = TableItem(v, v) except: traceback.print_exc() a = TableItem( _('Something went wrong while filling in the table'), '') a.setData(Qt.UserRole, b) a.setToolTip(tt) self.books_table.setItem(row, self.key_to_table_widget_column(col), a) self.books_table.setRowHeight(row, self.books_table_row_height) self.books_table.blockSignals(False) self.books_table.setSortingEnabled(True) if select_item is not None: self.books_table.setCurrentItem(select_item) self.books_table.scrollToItem(select_item, QAbstractItemView.PositionAtCenter) self.set_search_text(sv)