mirror of
https://github.com/kovidgoyal/calibre.git
synced 2026-02-17 00:30:08 -05:00
294 lines
8.9 KiB
Plaintext
294 lines
8.9 KiB
Plaintext
# vim:fileencoding=utf-8
|
|
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
|
from __python__ import bound_methods, hash_literals
|
|
|
|
from gettext import gettext as _
|
|
|
|
from ajax import absolute_path, ajax
|
|
from book_list.globals import get_session_data
|
|
from lru_cache import LRUCache
|
|
from session import get_interface_data
|
|
from utils import parse_url_params
|
|
|
|
load_status = {'loading':True, 'ok':False, 'error_html':None, 'current_fetch': None}
|
|
library_data = {'metadata':{}, 'previous_book_ids': v'[]', 'force_refresh': False}
|
|
|
|
|
|
def current_library_id():
|
|
q = parse_url_params()
|
|
return q.library_id or get_interface_data().default_library_id
|
|
|
|
|
|
def all_libraries():
|
|
interface_data = get_interface_data()
|
|
lids = sorted(interface_data.library_map, key=def(x): return interface_data.library_map[x];)
|
|
return [(lid, interface_data.library_map[lid]) for lid in lids]
|
|
|
|
|
|
def current_virtual_library():
|
|
q = parse_url_params()
|
|
return q.vl or ''
|
|
|
|
|
|
def last_virtual_library_for(library_id):
|
|
if last_virtual_library_for.library_id is library_id:
|
|
return last_virtual_library_for.vl or ''
|
|
return ''
|
|
|
|
|
|
def url_books_query(sd):
|
|
q = parse_url_params()
|
|
sd = sd or get_session_data()
|
|
lid = current_library_id()
|
|
return {
|
|
'library_id': lid,
|
|
'sort': q.sort or sd.get_library_option(lid, 'sort'),
|
|
'search': q.search,
|
|
'vl': current_virtual_library(),
|
|
}
|
|
|
|
|
|
def loaded_books_query():
|
|
sr = library_data.search_result
|
|
sort = None
|
|
if sr:
|
|
sort = [s + '.' + o for s, o in zip(sr.sort.split(','), sr.sort_order.split(','))].join(',')
|
|
return {
|
|
'library_id': sr.library_id if sr else None,
|
|
'sort': sort,
|
|
'search': sr?.query,
|
|
'vl':sr?.vl
|
|
}
|
|
|
|
|
|
def current_sorted_field():
|
|
if library_data.search_result:
|
|
return library_data.search_result.sort, library_data.search_result.sort_order
|
|
sort = url_books_query().sort.partition(',')[0]
|
|
csf = sort.partition('.')[0]
|
|
csfo = sort.partition('.')[2] or 'asc'
|
|
return csf, csfo
|
|
|
|
|
|
def all_virtual_libraries():
|
|
return library_data.virtual_libraries or {}
|
|
|
|
|
|
def update_library_data(data):
|
|
load_status.loading = False
|
|
load_status.ok = True
|
|
load_status.error_html = None
|
|
library_data.previous_book_ids = v'[]'
|
|
if library_data.for_library is not current_library_id():
|
|
library_data.field_names = {}
|
|
library_data.for_library = current_library_id()
|
|
for key in 'search_result sortable_fields field_metadata metadata virtual_libraries book_display_fields'.split(' '):
|
|
library_data[key] = data[key]
|
|
sr = library_data.search_result
|
|
if sr:
|
|
last_virtual_library_for.library_id = sr.library_id
|
|
last_virtual_library_for.vl = sr.vl
|
|
else:
|
|
last_virtual_library_for.library_id = None
|
|
last_virtual_library_for.vl = None
|
|
|
|
|
|
def add_more_books(data):
|
|
for key in data.metadata:
|
|
library_data.metadata[key] = data.metadata[key]
|
|
sr = library_data.search_result
|
|
if sr and sr.book_ids and sr.book_ids.length > 0:
|
|
library_data.previous_book_ids = library_data.previous_book_ids.concat(sr.book_ids)
|
|
library_data.search_result = data.search_result
|
|
|
|
|
|
def current_book_ids():
|
|
return library_data.previous_book_ids.concat(library_data.search_result.book_ids)
|
|
|
|
|
|
def book_after(book_id):
|
|
ids = current_book_ids()
|
|
idx = ids.indexOf(int(book_id))
|
|
if idx > -1:
|
|
if idx < ids.length - 1:
|
|
return ids[idx + 1]
|
|
if idx > 0: # wrap around
|
|
return ids[0]
|
|
|
|
|
|
def on_data_loaded(end_type, xhr, ev):
|
|
load_status.current_fetch = None
|
|
def bad_load(msg):
|
|
load_status.ok = False
|
|
load_status.loading = False
|
|
load_status.error_html = msg or xhr.error_html
|
|
|
|
if end_type is 'load':
|
|
data = JSON.parse(xhr.responseText)
|
|
if data.bad_restriction:
|
|
bad_load(_('The library restriction for the current user is invalid: {}').format(data.bad_restriction))
|
|
else:
|
|
update_library_data(data)
|
|
sd = get_session_data()
|
|
q = loaded_books_query()
|
|
sd.set_library_option(q.library_id, 'sort', q.sort)
|
|
elif end_type is 'abort':
|
|
pass
|
|
else:
|
|
bad_load()
|
|
|
|
|
|
def fetch_init_data():
|
|
if load_status.current_fetch:
|
|
load_status.current_fetch.abort()
|
|
query = url_books_query()
|
|
load_status.loading = True
|
|
load_status.ok = False
|
|
load_status.error_html = None
|
|
load_status.current_fetch = ajax('interface-data/books-init', on_data_loaded, query=query)
|
|
load_status.current_fetch.send()
|
|
|
|
|
|
def field_names_received(library_id, field, proceed, end_type, xhr, event):
|
|
if library_id is not current_library_id():
|
|
return
|
|
if end_type is not 'load':
|
|
if end_type is not 'abort':
|
|
proceed(False, field, xhr.error_html)
|
|
return
|
|
try:
|
|
names = JSON.parse(xhr.responseText)
|
|
except Exception:
|
|
import traceback
|
|
traceback.print_exc()
|
|
proceed(False, field, 'Invalid JSON from server')
|
|
return
|
|
library_data.field_names[field] = {
|
|
'loading': False,
|
|
'loaded': True,
|
|
'last_updated_at': Date.now(),
|
|
'names': names
|
|
}
|
|
proceed(True, field, names)
|
|
|
|
|
|
def field_names_for(field, proceed):
|
|
if library_data.field_names[field] and library_data.field_names[field].loaded:
|
|
proceed(True, field, library_data.field_names[field].names)
|
|
if not library_data.field_names[field] or (not library_data.field_names[field].loading and Date.now() - library_data.field_names[field].last_updated_at > 3600 * 1000):
|
|
|
|
ajax(f'interface-data/field-names/{encodeURIComponent(field)}', field_names_received.bind(None, current_library_id(), field, proceed), query={'library_id': current_library_id()}).send()
|
|
if not library_data.field_names[field]:
|
|
library_data.field_names[field] = {'last_updated_at': 0, 'loaded': False, 'names': v'[]'}
|
|
library_data.field_names[field].loading = True
|
|
|
|
|
|
def thumbnail_url(book_id, width, height):
|
|
return absolute_path(
|
|
'get/thumb/{}/{}?sz={}x{}'.format(
|
|
book_id, loaded_books_query().library_id,
|
|
Math.ceil(width * window.devicePixelRatio), Math.ceil(height * window.devicePixelRatio)
|
|
))
|
|
|
|
|
|
def cover_url(book_id):
|
|
lid = current_library_id()
|
|
return absolute_path(f'get/cover/{book_id}/{lid}')
|
|
|
|
|
|
def download_url(book_id, fmt):
|
|
lid = current_library_id()
|
|
return absolute_path(f'get/{fmt}/{book_id}/{lid}')
|
|
|
|
|
|
def book_metadata(book_id):
|
|
return library_data.metadata[book_id]
|
|
|
|
|
|
def set_book_metadata(book_id, value):
|
|
library_data.metadata[book_id] = value
|
|
|
|
|
|
def loaded_book_ids():
|
|
return Object.keys(library_data.metadata)
|
|
|
|
|
|
def force_refresh_on_next_load():
|
|
library_data.force_refresh = True
|
|
|
|
|
|
def ensure_current_library_data():
|
|
fr = library_data.force_refresh
|
|
library_data.force_refresh = False
|
|
|
|
def is_same(a, b):
|
|
if not a and not b:
|
|
return True
|
|
return a is b
|
|
|
|
|
|
if fr:
|
|
matches = False
|
|
else:
|
|
q = url_books_query()
|
|
loaded = loaded_books_query()
|
|
matches = True
|
|
|
|
for key in q:
|
|
if not is_same(q[key], loaded[key]):
|
|
matches = False
|
|
break
|
|
if not matches:
|
|
fetch_init_data()
|
|
|
|
|
|
class ThumbnailCache:
|
|
|
|
# Cache to prevent browser from issuing HTTP requests when thumbnails pages
|
|
# are destroyed/rebuilt.
|
|
|
|
def __init__(self, size=250):
|
|
self.cache = LRUCache(size)
|
|
|
|
def get(self, book_id, width, height, callback):
|
|
url = thumbnail_url(book_id, width, height)
|
|
item = self.cache.get(url)
|
|
if not item:
|
|
img = new Image()
|
|
item = {'img':img, 'load_type':None, 'callbacks':v'[callback]'}
|
|
img.onerror = self.load_finished.bind(None, item, 'error')
|
|
img.onload = self.load_finished.bind(None, item, 'load')
|
|
img.onabort = self.load_finished.bind(None, item, 'abort')
|
|
img.dataset.bookId = str(book_id)
|
|
img.src = url
|
|
self.cache.set(url, item)
|
|
return img
|
|
if item.load_type is None:
|
|
if item.callbacks.indexOf(callback) < 0:
|
|
item.callbacks.push(callback)
|
|
else:
|
|
callback(item.img, item.load_type)
|
|
return item.img
|
|
|
|
def load_finished(self, item, load_type):
|
|
item.load_type = load_type
|
|
img = item.img
|
|
img.onload = img.onerror = img.onabort = None
|
|
for callback in item.callbacks:
|
|
callback(img, load_type)
|
|
|
|
thumbnail_cache = ThumbnailCache()
|
|
|
|
|
|
def sync_library_books(library_id, to_sync, callback):
|
|
url = f'book-get-last-read-position/{library_id}/'
|
|
which = v'[]'
|
|
lrmap = {}
|
|
for key, last_read in to_sync:
|
|
library_id, book_id, fmt = key
|
|
fmt = fmt.upper()
|
|
which.push(f'{book_id}-{fmt}')
|
|
lrmap[f'{book_id}:{fmt}'] = last_read
|
|
url += which.join('_')
|
|
ajax(url, callback.bind(None, library_id, lrmap)).send()
|