mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Implement the "Get more books" button
Also refactor ajax.pyj to make error reporting easier
This commit is contained in:
parent
363be7c6f3
commit
7f6d2d786d
@ -4,7 +4,7 @@
|
||||
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
import re
|
||||
import re, httplib
|
||||
from functools import partial
|
||||
from threading import Lock
|
||||
from json import load as load_json_file
|
||||
@ -116,7 +116,7 @@ def interface_data(ctx, rd):
|
||||
|
||||
return ans
|
||||
|
||||
@endpoint('/interface-data/more-books', postprocess=json, methods={'GET', 'HEAD', 'POST'})
|
||||
@endpoint('/interface-data/more-books', postprocess=json, methods={'GET', 'POST', 'HEAD'}, ok_code=httplib.OK)
|
||||
def more_books(ctx, rd):
|
||||
'''
|
||||
Get more results from the specified search-query, which must
|
||||
@ -132,14 +132,14 @@ def more_books(ctx, rd):
|
||||
raise HTTPNotFound('Invalid number of books: %r' % rd.query.get('num'))
|
||||
try:
|
||||
search_query = load_json_file(rd.request_body_file)
|
||||
query, sorts, orders = search_query['query'], search_query['sort'], search_query['sort_order']
|
||||
query, offset, sorts, orders = search_query['query'], search_query['offset'], search_query['sort'], search_query['sort_order']
|
||||
except KeyError as err:
|
||||
raise HTTPBadRequest('Search query missing key: %s' % as_unicode(err))
|
||||
except Exception as err:
|
||||
raise HTTPBadRequest('Invalid query: %s' % as_unicode(err))
|
||||
ans = {}
|
||||
with db.safe_read_lock:
|
||||
ans['search_result'] = search_result(ctx, rd, db, query, num, 0, sorts, orders)
|
||||
ans['search_result'] = search_result(ctx, rd, db, query, num, offset, sorts, orders)
|
||||
mdata = ans['metadata'] = {}
|
||||
for book_id in ans['search_result']['book_ids']:
|
||||
data = book_as_json(db, book_id)
|
||||
|
@ -1,7 +1,9 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
def ajax(path, on_complete, on_progress=None, bypass_cache=True, method='GET', query=None):
|
||||
from gettext import gettext as _
|
||||
|
||||
def ajax(path, on_complete, on_progress=None, bypass_cache=True, method='GET', query=None, timeout=30*1000, ok_code=200):
|
||||
query = query or {}
|
||||
xhr = XMLHttpRequest()
|
||||
keys = Object.keys(query)
|
||||
@ -11,12 +13,22 @@ def ajax(path, on_complete, on_progress=None, bypass_cache=True, method='GET', q
|
||||
val = query[k]
|
||||
if val is undefined or val is None:
|
||||
continue
|
||||
path += ('&' if has_query else '?') + window.encodeURIComponent(k) + '=' + window.encodeURIComponent(val.toString())
|
||||
path += ('&' if has_query else '?') + encodeURIComponent(k) + '=' + encodeURIComponent(val.toString())
|
||||
has_query = True
|
||||
if bypass_cache:
|
||||
path += ('&' if has_query else '?') + Date().getTime()
|
||||
|
||||
xhr.request_path = path
|
||||
xhr.error_string = ''
|
||||
|
||||
def set_error(event):
|
||||
if event == 'timeout':
|
||||
xhr.error_string = str.format(_('Failed to download data from "{}", timed out after: {} seconds'), xhr.request_path, timeout/1000)
|
||||
elif event == 'abort':
|
||||
xhr.error_string = str.format(_('Failed to download data from "{}", aborted'), xhr.request_path)
|
||||
else:
|
||||
rtext = xhr.responseText or ''
|
||||
xhr.error_string = str.format(_('Failed to download data from "{}", with status: [{}] {}<br>{}'), xhr.request_path, xhr.status, xhr.statusText, rtext[:200])
|
||||
|
||||
def progress_callback(ev):
|
||||
if ev.lengthComputable:
|
||||
@ -31,19 +43,28 @@ def ajax(path, on_complete, on_progress=None, bypass_cache=True, method='GET', q
|
||||
on_progress(ev.loaded, ul)
|
||||
|
||||
def complete_callback(end_type, ev):
|
||||
if end_type != 'load':
|
||||
on_complete(end_type, xhr, ev)
|
||||
return
|
||||
if xhr.status != 200:
|
||||
if xhr.status != ok_code and end_type == 'load':
|
||||
end_type = 'error'
|
||||
if end_type != 'load':
|
||||
set_error(end_type)
|
||||
on_complete(end_type, xhr, ev)
|
||||
|
||||
if on_progress:
|
||||
xhr.addEventListener('progress', progress_callback)
|
||||
xhr.addEventListener('abort', def(ev): complete_callback('abort', xhr, ev);)
|
||||
xhr.addEventListener('error', def(ev): complete_callback('error', xhr, ev);)
|
||||
xhr.addEventListener('load', def(ev): complete_callback('load', xhr, ev);)
|
||||
xhr.addEventListener('abort', def(ev): complete_callback('abort', ev);)
|
||||
xhr.addEventListener('error', def(ev): complete_callback('error', ev);)
|
||||
xhr.addEventListener('load', def(ev): complete_callback('load', ev);)
|
||||
xhr.addEventListener('timeout', def(ev): complete_callback('timeout', ev);)
|
||||
xhr.open(method, path)
|
||||
xhr.timeout = timeout # IE requires timeout to be set after open
|
||||
return xhr
|
||||
|
||||
def ajax_send(path, data, on_complete, on_progress=None, query=None, timeout=30*1000, ok_code=200):
|
||||
# Unfortunately, browsers do not allow sending of data with HTTP GET, except
|
||||
# as query parameters, so we have to use POST
|
||||
xhr = ajax(path, on_complete, on_progress, False, 'POST', query, timeout, ok_code)
|
||||
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8')
|
||||
xhr.send(JSON.stringify(data))
|
||||
return xhr
|
||||
|
||||
# TODO: Implement AJAX based switch user by:
|
||||
|
@ -1,9 +1,11 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from ajax import ajax_send
|
||||
from dom import set_css, build_rule, clear
|
||||
from elementmaker import E
|
||||
from gettext import gettext as _
|
||||
from modals import error_dialog
|
||||
|
||||
from book_list.globals import get_session_data
|
||||
from widgets import create_button
|
||||
@ -19,8 +21,8 @@ class BooksView:
|
||||
nonlocal bv_counter
|
||||
bv_counter += 1
|
||||
self.interface_data = interface_data
|
||||
self.is_fetching = False
|
||||
self.shown_book_ids = {}
|
||||
self.is_fetching = None
|
||||
self.shown_book_ids = set()
|
||||
self.container_id = 'books-view-' + bv_counter
|
||||
# We have to apply the transform on the containing div not the img because of a bug in WebKit
|
||||
# that causes img aspect ratios to be messed up on window resize if the transform is specified
|
||||
@ -59,7 +61,7 @@ class BooksView:
|
||||
if mode == 'cover_grid':
|
||||
self.render_book = self.cover_grid_item.bind(self)
|
||||
self.init_grid = self.init_cover_grid.bind(self)
|
||||
self.init_grid()
|
||||
self.clear()
|
||||
self.render_ids()
|
||||
|
||||
@property
|
||||
@ -84,23 +86,30 @@ class BooksView:
|
||||
c = self.container
|
||||
c.removeChild(self.grid)
|
||||
c.insertBefore(E.div(), c.lastChild)
|
||||
self.shown_book_ids.clear()
|
||||
self.init_grid()
|
||||
|
||||
def render_ids(self):
|
||||
book_ids = self.interface_data['search_result']['book_ids']
|
||||
self.shown_book_ids = {}
|
||||
def render_id(self, book_id):
|
||||
l = self.shown_book_ids.length
|
||||
self.shown_book_ids.add(book_id)
|
||||
if l < self.shown_book_ids.length:
|
||||
return self.render_book(book_id)
|
||||
|
||||
def render_ids(self, book_ids):
|
||||
book_ids = book_ids or self.interface_data['search_result']['book_ids']
|
||||
div = self.grid
|
||||
for book_id in book_ids:
|
||||
child = self.render_id(book_id)
|
||||
if child is not None:
|
||||
div.appendChild(self.render_book(book_id))
|
||||
self.shown_book_ids[book_id] = True
|
||||
|
||||
def update_fetching_status(self):
|
||||
c = self.container
|
||||
more = c.lastChild
|
||||
if self.is_fetching:
|
||||
if self.is_fetching is not None:
|
||||
more.firstChild.style.display = 'none'
|
||||
more.lastChild.style.display = 'block'
|
||||
elif self.interface_data['search_result']['total_num'] > self.interface_data['search_result']['book_ids'].length:
|
||||
elif self.interface_data['search_result']['total_num'] > self.shown_book_ids.length:
|
||||
more.firstChild.style.display = 'block'
|
||||
more.lastChild.style.display = 'none'
|
||||
else:
|
||||
@ -108,8 +117,38 @@ class BooksView:
|
||||
more.lastChild.style.display = 'none'
|
||||
|
||||
def get_more_books(self):
|
||||
data = {'offset':self.shown_book_ids.length}
|
||||
for key in 'query', 'sort', 'sort_order':
|
||||
data[key] = self.interface_data['search_result'][key]
|
||||
self.is_fetching = ajax_send('interface-data/more-books', data, self.got_more_books.bind(self),
|
||||
query={'library_id':self.interface_data.library_id})
|
||||
self.update_fetching_status()
|
||||
|
||||
def abort_get_more_books(self):
|
||||
if self.is_fetching:
|
||||
a, self.is_fetching = self.is_fetching, None
|
||||
a.abort()
|
||||
self.update_fetching_status()
|
||||
|
||||
def got_more_books(self, end_type, xhr, event):
|
||||
if self.is_fetching is None or self.is_fetching is not xhr:
|
||||
return # Fetching was aborted
|
||||
self.is_fetching = None
|
||||
self.update_fetching_status()
|
||||
if end_type == 'load':
|
||||
try:
|
||||
data = JSON.parse(xhr.responseText)
|
||||
for key in data.metadata:
|
||||
self.interface_data.metadata[key] = data.metadata[key]
|
||||
if not data.search_result.book_ids:
|
||||
raise Exception('No books ids object in search result from server')
|
||||
self.render_ids(data.search_result.book_ids)
|
||||
self.interface_data.search_result = data.search_result
|
||||
except Exception as err:
|
||||
error_dialog(_('Could not get more books'), _('Server returned an invalid response'), err.stack or err.toString())
|
||||
elif end_type != 'abort':
|
||||
error_dialog(_('Could not get more books'), xhr.error_string)
|
||||
|
||||
# Cover grid {{{
|
||||
|
||||
def init_cover_grid(self):
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
from ajax import ajax
|
||||
from elementmaker import E
|
||||
from gettext import gettext as _
|
||||
from session import UserSessionData, SessionData
|
||||
from book_list.boss import Boss
|
||||
from book_list.globals import set_boss, set_session_data
|
||||
@ -19,9 +18,7 @@ def on_library_loaded(end_type, xhr, ev):
|
||||
boss = Boss(interface_data)
|
||||
set_boss(boss)
|
||||
else:
|
||||
document.body.appendChild(E.p(style='color:red', str.format(_(
|
||||
'Failed to download library data from "{}", with status: [{}] {}'),
|
||||
xhr.request_path, xhr.status, xhr.statusText)))
|
||||
document.body.appendChild(E.p(style='color:red', xhr.error_string))
|
||||
|
||||
def on_library_load_progress(loaded, total):
|
||||
p = document.querySelector('#page_load_progress > progress')
|
||||
|
Loading…
x
Reference in New Issue
Block a user