# vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2017, Kovid Goyal 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 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'[]' 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 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()