calibre/src/pyj/book_list/library_data.pyj

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()