Start work on refactoring client app to make it fully offline-able

This commit is contained in:
Kovid Goyal 2017-01-19 09:51:53 +05:30
parent dd89294c62
commit 341fcfd91b
6 changed files with 159 additions and 68 deletions

View File

@ -0,0 +1,7 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
from __python__ import hash_literals, bound_methods
book_list_container_id = 'book-list-container'
read_book_container_id = 'read-book-container'

View File

@ -28,3 +28,9 @@ def get_current_query():
def set_current_query(val):
nonlocal current_query
current_query = val
def main_js(newval):
if newval is not undefined:
main_js.ans = newval
return main_js.ans

View File

@ -0,0 +1,87 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
from __python__ import hash_literals, bound_methods
import traceback
from elementmaker import E
from ajax import ajax
from dom import set_css, get_widget_css
from modals import create_modal_container, error_dialog
from session import get_interface_data, UserSessionData, update_interface_data, get_translations
from gettext import gettext as _, install
from utils import parse_url_params
from book_list.constants import book_list_container_id, read_book_container_id
from book_list.theme import get_color
def remove_initial_progress_bar():
p = document.getElementById('page_load_progress')
if p:
p.parentNode.removeChild(p)
def onerror(msg, script_url, line_number, column_number, error_object):
console.log(error_object)
try:
fname = script_url.rpartition('/')[-1] or script_url
msg = msg + '<br><span style="font-size:smaller">' + 'Error at {}:{}:{}'.format(fname, line_number, column_number or '') + '</span>'
details = ''
if error_object:
details = traceback.format_exception(error_object).join('')
error_dialog(_('Unhandled error'), msg, details)
return True
except:
console.log('There was an error in the unhandled exception handler')
def init_ui():
window.onerror = onerror
translations = get_translations()
if translations:
install(translations)
remove_initial_progress_bar()
document.head.appendChild(E.style(get_widget_css()))
set_css(document.body, background_color=get_color('window-background'), color=get_color('window-foreground'))
document.body.appendChild(E.div(id='containers'))
document.body.lastChild.appendChild(E.div(id=book_list_container_id, style='display: none'))
document.body.lastChild.appendChild(E.div(id=read_book_container_id, style='display: none'))
create_modal_container()
def on_data_loaded(end_type, xhr, ev):
remove_initial_progress_bar()
if end_type is 'load':
data = JSON.parse(xhr.responseText)
update_interface_data(data)
if data.translations:
get_translations(data.translations)
init_ui()
else:
p = E.p(style='color:red; font-weight: bold; font-size:1.5em')
if xhr.status is 401:
p.innerHTML = _('You are not authorized to view this site')
else:
p.innerHTML = xhr.error_html
document.body.appendChild(p)
def on_data_load_progress(loaded, total):
p = document.querySelector('#page_load_progress > progress')
p.max = total
p.value = loaded
def load_interface_data():
temp = UserSessionData(None, {}) # So that settings for anonymous users are preserved
query = {}
library_id = temp.get('library_id')
if library_id:
query.library_id = library_id
query.sort = temp.get_library_option(library_id, 'sort')
url_query = parse_url_params()
for key in url_query:
query[key] = url_query[key]
ajax('interface-data/init', on_data_loaded, on_data_load_progress, query=query).send()
def main():
if get_interface_data().is_default:
load_interface_data()
else:
init_ui()

View File

@ -8,7 +8,7 @@ from gettext import gettext as _
from utils import html_escape
from modals import error_dialog, warning_dialog
from book_list.globals import get_session_data, get_boss
from book_list.globals import get_session_data, get_boss, main_js
from read_book.globals import messenger, iframe_id, current_book, set_current_spine_item
from read_book.resources import load_resources
from read_book.overlay import Overlay
@ -193,8 +193,8 @@ class View:
side_margin('left', margin_left), side_margin('right', margin_right)
def create_src_doc(self):
iframe_script = self.ui.interface_data.main_js.replace(/is_running_in_iframe\s*=\s*false/, 'is_running_in_iframe = true')
self.ui.interface_data.main_js = None
iframe_script = main_js().replace(/is_running_in_iframe\s*=\s*false/, 'is_running_in_iframe = true')
main_js(None)
self.src_doc = self.iframe.srcdoc = LOADING_DOC.replace(
'__BS__', _('Bootstrapping book reader...')).replace(
'__SCRIPT__', iframe_script)

View File

@ -1,6 +1,6 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
from __python__ import hash_literals
from __python__ import hash_literals, bound_methods
from ajax import ajax_send
@ -55,8 +55,6 @@ def storage_available(which):
except:
return False
session_storage = None
class FakeStorage:
def __init__(self):
@ -74,17 +72,16 @@ class FakeStorage:
self.data = {}
def get_session_storage():
nonlocal session_storage
if session_storage is None:
if not get_session_storage.ans:
if storage_available('localStorage'):
session_storage = window.localStorage
get_session_storage.ans = window.localStorage
elif storage_available('sessionStorage'):
session_storage = window.sessionStorage
get_session_storage.ans = window.sessionStorage
console.error('localStorage not available using sessionStorage instead')
else:
session_storage = FakeStorage()
get_session_storage.ans = FakeStorage()
console.error('sessionStorage and localStorage not available using a temp cache instead')
return session_storage
return get_session_storage.ans
class SessionData:
@ -130,11 +127,56 @@ class SessionData:
self.overflow_storage = {}
self.has_overflow = False
def local_storage():
if not local_storage.storage:
local_storage.storage = SessionData('calibre-local-')
return local_storage.storage
default_interface_data = {
'username': None,
'output_format': 'EPUB',
'input_formats': {'EPUB', 'MOBI', 'AZW3'},
'gui_pubdate_display_format': 'MMM yyyy',
'gui_timestamp_display_format': 'dd MMM yyyy',
'gui_last_modified_display_format': 'dd MMM yyyy',
'use_roman_numerals_for_series_number': True,
'allow_console_print':False,
}
def get_interface_data():
if not get_interface_data.storage:
get_interface_data.storage = SessionData('calibre-interface-data-')
ans = get_interface_data.storage.get('current')
if ans:
ans.is_default = False
else:
ans = {'is_default': True}
for k in default_interface_data:
ans[k] = default_interface_data[k]
return ans
def update_interface_data(new_data):
data = get_interface_data()
for k in default_interface_data:
nval = new_data[k]
if k is not undefined:
data[k] = nval
if not get_interface_data.storage:
get_interface_data.storage = SessionData('calibre-interface-data-')
get_interface_data.storage.set('current', data)
def get_translations(newval):
if not get_translations.storage:
get_translations.storage = SessionData('calibre-translations-')
if newval:
get_translations.storage.set('current', newval)
else:
return get_translations.storage.get('current')
class UserSessionData(SessionData):
def __init__(self, username, saved_data):

View File

@ -3,76 +3,25 @@
from __python__ import hash_literals
import initialize # noqa: unused-import
from ajax import ajax, set_allow_console_print
from elementmaker import E
from gettext import gettext as _, install
from session import UserSessionData
from utils import parse_url_params
from ajax import ajax
from book_list.boss import Boss
from book_list.globals import set_session_data
from book_list.globals import main_js
from book_list.main import main
from read_book.iframe import init
def on_library_loaded(end_type, xhr, ev):
nonlocal main_js
p = document.getElementById('page_load_progress')
p.parentNode.removeChild(p)
if end_type is 'load':
interface_data = JSON.parse(xhr.responseText)
interface_data.main_js = main_js
set_allow_console_print(interface_data.allow_console_print)
main_js = None
script = document.getElementById('main_js')
if script:
script.parentNode.removeChild(script) # Free up some memory
if interface_data.translations:
install(interface_data.translations)
sd = UserSessionData(interface_data.username, interface_data.user_session_data)
set_session_data(sd)
sd.set('library_id', interface_data.library_id)
Boss(interface_data)
else:
p = E.p(style='color:red; font-weight: bold; font-size:1.5em')
if xhr.status is 401:
p.innerHTML = _('You are not authorized to view this site')
else:
p.innerHTML = xhr.error_html
document.body.appendChild(p)
def on_library_load_progress(loaded, total):
p = document.querySelector('#page_load_progress > progress')
p.max = total
p.value = loaded
def load_book_list():
temp = UserSessionData(None, {}) # So that settings for anonymous users are preserved
query = {}
library_id = temp.get('library_id')
if library_id:
query.library_id = library_id
query.sort = temp.get_library_option(library_id, 'sort')
url_query = parse_url_params()
for key in url_query:
query[key] = url_query[key]
ajax('interface-data/init', on_library_loaded, on_library_load_progress, query=query).send()
def on_load():
print('calibre loaded at:', Date().toString())
load_book_list()
is_running_in_iframe = False # Changed before script is loaded in the iframe
if is_running_in_iframe:
init()
else:
script = document.currentScript or document.scripts[0]
main_js = script.textContent
main_js(script.textContent)
script.parentNode.removeChild(script) # save some memory
script = undefined
# We wait for all page elements to load, since this is a single page app
# with a largely empty starting document, we can use this to preload any resources
# we know are going to be needed immediately.
window.addEventListener('load', on_load)
window.addEventListener('load', main)
ajax('auto-reload', def(end_type, xhr, event):
if end_type is 'load':