mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Use a LRU cache to avoid extra HTTP requests when destroying/re-building the books list page
This commit is contained in:
parent
4875060c50
commit
9c156f9ba2
@ -6,7 +6,7 @@ from dom import clear, set_css, build_rule
|
|||||||
from elementmaker import E
|
from elementmaker import E
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
|
|
||||||
from book_list.library_data import cover_url, book_metadata
|
from book_list.library_data import thumbnail_cache, book_metadata
|
||||||
|
|
||||||
COVER_GRID_CLASS = 'book-list-cover-grid'
|
COVER_GRID_CLASS = 'book-list-cover-grid'
|
||||||
THUMBNAIL_MAX_WIDTH = 300
|
THUMBNAIL_MAX_WIDTH = 300
|
||||||
@ -41,26 +41,26 @@ def init(container):
|
|||||||
for i in range(12):
|
for i in range(12):
|
||||||
container.lastChild.appendChild(E.div(class_='cover-grid-filler'))
|
container.lastChild.appendChild(E.div(class_='cover-grid-filler'))
|
||||||
|
|
||||||
def on_img_load_error(err):
|
def on_img_load(img, load_type):
|
||||||
img = err.target
|
|
||||||
div = img.parentNode
|
div = img.parentNode
|
||||||
if not div:
|
if not div:
|
||||||
return
|
return
|
||||||
clear(div)
|
if load_type is not 'load':
|
||||||
div.appendChild(E.div(
|
metadata = book_metadata(int(img.dataset.bookId))
|
||||||
E.h2(img.getAttribute('data-title'), style='text-align:center; font-size:larger; font-weight: bold'),
|
if metadata:
|
||||||
E.div(_('by'), style='text-align: center'),
|
clear(div)
|
||||||
E.h2(img.getAttribute('data-authors'), style='text-align:center; font-size:larger; font-weight: bold')
|
div.appendChild(E.div(
|
||||||
))
|
E.h2(metadata.title, style='text-align:center; font-size:larger; font-weight: bold'),
|
||||||
set_css(div, border='dashed 1px currentColor', border_radius=BORDER_RADIUS+'px')
|
E.div(_('by'), style='text-align: center'),
|
||||||
|
E.h2(metadata.authors.join(' & '), style='text-align:center; font-size:larger; font-weight: bold')
|
||||||
|
))
|
||||||
|
set_css(div, border='dashed 1px currentColor', border_radius=BORDER_RADIUS+'px')
|
||||||
|
|
||||||
def create_item(book_id, show_book_details):
|
def create_item(book_id, show_book_details):
|
||||||
curl = cover_url(book_id, THUMBNAIL_MAX_WIDTH, THUMBNAIL_MAX_HEIGHT)
|
|
||||||
metadata = book_metadata(book_id)
|
metadata = book_metadata(book_id)
|
||||||
alt = _('{} by {}').format(metadata.title, metadata.authors.join(' & '))
|
authors = metadata.authors.join(' & ')
|
||||||
img = E.img(src=curl, alt=alt, title=alt, data_title=metadata.title, data_authors=metadata.authors.join(' & '))
|
img = thumbnail_cache.get(book_id, THUMBNAIL_MAX_WIDTH, THUMBNAIL_MAX_HEIGHT, on_img_load)
|
||||||
img.onerror = on_img_load_error
|
img.setAttribute('alt', _('{} by {}').format(metadata.title, authors))
|
||||||
|
|
||||||
ans = E.div(img, data_book_id=str(book_id), onclick=show_book_details)
|
ans = E.div(img, data_book_id=str(book_id), onclick=show_book_details)
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
from __python__ import hash_literals, bound_methods
|
from __python__ import hash_literals, bound_methods
|
||||||
|
|
||||||
from ajax import ajax
|
from ajax import ajax
|
||||||
|
from lru_cache import LRUCache
|
||||||
from session import get_interface_data
|
from session import get_interface_data
|
||||||
from utils import parse_url_params
|
from utils import parse_url_params
|
||||||
|
|
||||||
@ -77,7 +78,7 @@ def fetch_init_data():
|
|||||||
load_status.current_fetch.send()
|
load_status.current_fetch.send()
|
||||||
|
|
||||||
|
|
||||||
def cover_url(book_id, width, height):
|
def thumbnail_url(book_id, width, height):
|
||||||
return 'get/thumb/{}/{}?sz={}x{}'.format(book_id, loaded_books_query().library_id, Math.ceil(width * window.devicePixelRatio), Math.ceil(height * window.devicePixelRatio))
|
return 'get/thumb/{}/{}?sz={}x{}'.format(book_id, loaded_books_query().library_id, Math.ceil(width * window.devicePixelRatio), Math.ceil(height * window.devicePixelRatio))
|
||||||
|
|
||||||
|
|
||||||
@ -95,3 +96,41 @@ def ensure_current_library_data():
|
|||||||
break
|
break
|
||||||
if not matches:
|
if not matches:
|
||||||
fetch_init_data()
|
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 item is None:
|
||||||
|
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()
|
||||||
|
70
src/pyj/lru_cache.pyj
Normal file
70
src/pyj/lru_cache.pyj
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
from __python__ import hash_literals, bound_methods
|
||||||
|
|
||||||
|
|
||||||
|
def lru_node(key, val):
|
||||||
|
return {'key': key, 'val':val, 'prev':None, 'next':None}
|
||||||
|
|
||||||
|
|
||||||
|
class LRUCache:
|
||||||
|
|
||||||
|
def __init__(self, size):
|
||||||
|
self.limit = 200
|
||||||
|
self.clear(size)
|
||||||
|
|
||||||
|
def set_head(self, node):
|
||||||
|
node.next = self.head
|
||||||
|
node.prev = None
|
||||||
|
if self.head is not None:
|
||||||
|
self.head.prev = node
|
||||||
|
self.head = node
|
||||||
|
if self.tail is None:
|
||||||
|
self.tail = node
|
||||||
|
self.size += 1
|
||||||
|
self.map[node.key] = node
|
||||||
|
|
||||||
|
def pop(self, key):
|
||||||
|
node = self.map[key]
|
||||||
|
if not node:
|
||||||
|
return
|
||||||
|
if node.prev is not None:
|
||||||
|
node.prev.next = node.next
|
||||||
|
else:
|
||||||
|
self.head = node.next
|
||||||
|
if node.next is not None:
|
||||||
|
node.next.prev = node.prev
|
||||||
|
else:
|
||||||
|
self.tail = node.prev
|
||||||
|
v'delete self.map[key]'
|
||||||
|
self.size -= 1
|
||||||
|
|
||||||
|
def set(self, key, val):
|
||||||
|
node = lru_node(key, val)
|
||||||
|
existing = self.map[key]
|
||||||
|
if existing:
|
||||||
|
existing.value = node.value
|
||||||
|
self.pop(node.key)
|
||||||
|
elif self.size > self.limit:
|
||||||
|
v'delete self.map[self.tail.key]'
|
||||||
|
self.size -= 1
|
||||||
|
self.tail = self.tail.prev
|
||||||
|
self.tail.next = None
|
||||||
|
self.set_head(node)
|
||||||
|
|
||||||
|
def get(self, key, defval):
|
||||||
|
existing = self.map[key]
|
||||||
|
if not existing:
|
||||||
|
return None if defval is undefined else defval
|
||||||
|
val = existing.value
|
||||||
|
node = lru_node(key, val)
|
||||||
|
self.pop(key)
|
||||||
|
self.set_head(node)
|
||||||
|
return val
|
||||||
|
|
||||||
|
def clear(self, size):
|
||||||
|
self.size = 0
|
||||||
|
self.map = {}
|
||||||
|
self.head = self.tail = None
|
||||||
|
if jstype(size) is 'number':
|
||||||
|
self.limit = size
|
Loading…
x
Reference in New Issue
Block a user