calibre/src/pyj/ajax.pyj
2019-12-23 09:23:06 +05:30

155 lines
6.0 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
from __python__ import hash_literals
from gettext import gettext as _
default_timeout = 60
def set_default_timeout(val):
nonlocal default_timeout
default_timeout = val
def encode_query_component(x):
ans = encodeURIComponent(x)
# The following exceptions are to make epubcfi() look better
ans = ans.replace(/%2[fF]/g, '/')
ans = ans.replace(/%40/g, '@')
ans = ans.replace(/%5[bB]/g, '[')
ans = ans.replace(/%5[dD]/g, ']')
ans = ans.replace(/%5[eE]/g, '^')
ans = ans.replace(/%3[aA]/g, ':')
return ans
def encode_query(query, qchar):
if not query:
return ''
qchar = qchar or '?'
keys = Object.keys(query).sort()
ans = ''
if keys.length:
for k in keys:
val = query[k]
if val is undefined or val is None or val is '':
continue
ans += qchar + encodeURIComponent(k) + '=' + encode_query_component(val.toString())
qchar = '&'
return ans
def absolute_path(path):
if path[0] is not '/':
slash = '' if window.location.pathname[-1] is '/' else '/'
path = window.location.pathname + slash + path
return path
def workaround_qt_bug(xhr, end_type, ok_code=200):
if end_type is 'error' and xhr.status is ok_code and window.navigator.userAgent.indexOf('calibre-viewer') is 0:
end_type = 'load'
return end_type
def ajax(path, on_complete, on_progress=None, bypass_cache=True, method='GET', query=None, timeout=None, ok_code=200, progress_totals_needed=True):
# Run an AJAX request. on_complete must be a function that accepts three
# arguments: end_type, xhr, ev where end_type is one of 'abort', 'error',
# 'load', 'timeout'. In case end_type is anything other than 'load' you can
# get a descriptive error message via `xhr.error_html`. on_progress, if
# provided must be a function accepting three arguments: loaded, total, xhr
# where loaded is the number of bytes received, total is the total size.
#
# Returns the xhr object, call xhr.send() to start the request.
if timeout is None:
timeout = default_timeout
query = query or {}
xhr = XMLHttpRequest()
eq = encode_query(query)
has_query = eq.length > 0
path = absolute_path(path) + eq
if bypass_cache:
path += ('&' if has_query else '?') + Date().getTime()
xhr.request_path = path
xhr.error_html = ''
def set_error(event, is_network_error):
if is_network_error:
xhr.error_html = str.format(_('Failed to communicate with "{}", network error. Is the server running and accessible?'), xhr.request_path)
elif event is 'timeout':
xhr.error_html = str.format(_('Failed to communicate with "{}", timed out after: {} seconds'), xhr.request_path, timeout)
elif event is 'abort':
xhr.error_html = str.format(_('Failed to communicate with "{}", aborted'), xhr.request_path)
else:
try:
rtext = xhr.responseText or ''
except:
rtext = ''
xhr.error_html = str.format(_('Failed to communicate with "{}", with status: [{} ({})] {}<br><br>{}'), xhr.request_path, xhr.status, event, xhr.statusText, rtext[:200])
def progress_callback(ev):
if ev.lengthComputable:
on_progress(ev.loaded, ev.total, xhr)
elif ev.loaded:
if not progress_totals_needed:
on_progress(ev.loaded, undefined, xhr)
return
ul = xhr.getResponseHeader('Calibre-Uncompressed-Length')
if ul:
try:
ul = int(ul)
except Exception:
return
on_progress(ev.loaded, ul, xhr)
def complete_callback(end_type, ev):
is_network_error = ev if end_type is 'error' else False
if xhr.status is not ok_code and end_type is 'load':
end_type = 'error'
end_type = workaround_qt_bug(xhr, end_type, ok_code)
if end_type is not 'load':
set_error(end_type, is_network_error)
on_complete(end_type, xhr, ev)
if on_progress:
xhr.addEventListener('progress', progress_callback)
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 * 1000 # IE requires timeout to be set after open
return xhr
def ajax_send(path, data, on_complete, on_progress=None, query=None, timeout=None, 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
def ajax_send_file(path, file, on_complete, on_progress, timeout=None, ok_code=200):
xhr = ajax(path, on_complete, on_progress, False, 'POST', timeout, ok_code)
if file.type:
xhr.overrideMimeType(file.type)
xhr.send(file)
return xhr
def console_print(*args):
ρσ_print(*args) # noqa: undef
data = ' '.join(map(str, args)) + '\n'
xhr = ajax('console-print', def():pass;, method='POST', progress_totals_needed=False)
xhr.send(data)
# TODO: Implement AJAX based switch user by:
# 1) POST to a logout URL with an incorrect username and password
# 2) Have the server accept that incorrect username/password
# 3) Navigate to / which should cause the browser to re-prompt for username and password
# (only problem is login dialog will likely pre-show the wrong username) To workaround that,
# You can consider using a dedicated ajax based login form, see http://www.peej.co.uk/articles/http-auth-with-html-forms.html)