Use the HTML 5 appcache to make the browser e-book viewer fully offline-able

This commit is contained in:
Kovid Goyal 2017-04-06 19:55:51 +05:30
parent 00147c8e6c
commit 17a5977e4f
5 changed files with 63 additions and 14 deletions

1
.gitignore vendored
View File

@ -22,6 +22,7 @@ resources/template-functions.json
resources/editor-functions.json resources/editor-functions.json
resources/user-manual-translation-stats.json resources/user-manual-translation-stats.json
resources/content-server/index-generated.html resources/content-server/index-generated.html
resources/content-server/calibre.appcache
resources/content-server/locales.zip resources/content-server/locales.zip
resources/content-server/mathjax.zip.xz resources/content-server/mathjax.zip.xz
resources/content-server/mathjax.version resources/content-server/mathjax.version

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html manifest="calibre.appcache">
<head> <head>
<title>calibre</title> <title>calibre</title>
<meta charset="utf-8"> <meta charset="utf-8">

View File

@ -1,23 +1,28 @@
#!/usr/bin/env python2 #!/usr/bin/env python2
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
from __future__ import (unicode_literals, division, absolute_import, # License: GPLv3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
print_function) from __future__ import absolute_import, division, print_function, unicode_literals
__license__ = 'GPL v3' import atexit
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>' import errno
import glob
import os, sys, atexit, errno, subprocess, glob, shutil, json, re import json
from io import BytesIO import os
from threading import local import re
import shutil
import subprocess
import sys
from functools import partial from functools import partial
from threading import Thread from io import BytesIO
from Queue import Queue, Empty from Queue import Empty, Queue
from threading import Thread, local
from calibre import force_unicode
from calibre.constants import __appname__, __version__, cache_dir
from calibre.utils.terminal import ANSIStream
from duktape import Context, JSError, to_python from duktape import Context, JSError, to_python
from lzma.xz import compress, decompress from lzma.xz import compress, decompress
from calibre import force_unicode
from calibre.constants import cache_dir, __appname__, __version__
from calibre.utils.terminal import ANSIStream
COMPILER_PATH = 'rapydscript/compiler.js.xz' COMPILER_PATH = 'rapydscript/compiler.js.xz'
@ -47,6 +52,8 @@ def update_rapydscript():
# }}} # }}}
# Compiler {{{ # Compiler {{{
tls = local() tls = local()
@ -72,6 +79,7 @@ class CompileFailure(ValueError):
def default_lib_dir(): def default_lib_dir():
return P('rapydscript/lib', allow_user_override=False) return P('rapydscript/lib', allow_user_override=False)
_cache_dir = None _cache_dir = None
@ -125,6 +133,7 @@ def compile_pyj(data, filename='<stdin>', beautify=True, private_scope=True, lib
raise CompileFailure(result.stack) raise CompileFailure(result.stack)
raise CompileFailure(repr(presult)) raise CompileFailure(repr(presult))
has_external_compiler = None has_external_compiler = None
@ -170,6 +179,20 @@ def compile_fast(data, filename=None, beautify=True, private_scope=True, libdir=
return js.decode('utf-8') return js.decode('utf-8')
def create_manifest(html):
import hashlib
from calibre.library.field_metadata import category_icon_map
h = hashlib.sha256(html)
for ci in category_icon_map.itervalues():
h.update(I(ci, data=True))
icons = {'icon/' + x for x in category_icon_map.itervalues()}
icons.add('favicon.png')
h.update(I('lt.png', data=True))
manifest = '\n'.join(sorted(icons))
return 'CACHE MANIFEST\n# {}\n{}\n\nNETWORK:\n*'.format(
h.hexdigest(), manifest).encode('utf-8')
def compile_srv(): def compile_srv():
d = os.path.dirname d = os.path.dirname
base = d(d(d(d(os.path.abspath(__file__))))) base = d(d(d(d(os.path.abspath(__file__)))))
@ -195,8 +218,12 @@ def compile_srv():
js = compile_fast(f.read(), fname).replace('__RENDER_VERSION__', rv, 1).replace('__MATHJAX_VERSION__', mathjax_version, 1).encode('utf-8') js = compile_fast(f.read(), fname).replace('__RENDER_VERSION__', rv, 1).replace('__MATHJAX_VERSION__', mathjax_version, 1).encode('utf-8')
with lopen(os.path.join(base, 'index.html'), 'rb') as f: with lopen(os.path.join(base, 'index.html'), 'rb') as f:
html = f.read().replace(b'RESET_STYLES', reset, 1).replace(b'ICONS', icons, 1).replace(b'MAIN_JS', js, 1) html = f.read().replace(b'RESET_STYLES', reset, 1).replace(b'ICONS', icons, 1).replace(b'MAIN_JS', js, 1)
manifest = create_manifest(html)
with lopen(os.path.join(base, 'index-generated.html'), 'wb') as f: with lopen(os.path.join(base, 'index-generated.html'), 'wb') as f:
f.write(html) f.write(html)
with lopen(os.path.join(base, 'calibre.appcache'), 'wb') as f:
f.write(manifest)
# }}} # }}}
@ -420,5 +447,6 @@ def main(args=sys.argv):
def entry(): def entry():
main(sys.argv[1:]) main(sys.argv[1:])
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -46,6 +46,18 @@ class Watcher:
self.reload_app() self.reload_app()
def reload_app(self): def reload_app(self):
appcache = window.top.applicationCache
for which in 'cached error noupdate obsolete updateready'.split(' '):
appcache.addEventListener(which, self.cache_update_done, False)
try:
appcache.update()
except: # In chrome with devtools open, appcache is sometimes disabled/fails
window.location.reload(True)
def cache_update_done(self):
appcache = window.top.applicationCache
if appcache.status is appcache.UPDATEREADY:
appcache.swapCache()
window.location.reload(True) window.location.reload(True)

View File

@ -2,6 +2,8 @@
# License: GPL v3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
from __python__ import hash_literals from __python__ import hash_literals
from gettext import gettext as _
import initialize # noqa: unused-import import initialize # noqa: unused-import
from ajax import ajax from ajax import ajax
from autoreload import create_auto_reload_watcher from autoreload import create_auto_reload_watcher
@ -14,6 +16,12 @@ is_running_in_iframe = False # Changed before script is loaded in the iframe
if is_running_in_iframe: if is_running_in_iframe:
init() init()
else: else:
window.applicationCache.addEventListener('updateready', def():
if window.applicationCache.status is window.applicationCache.UPDATEREADY:
window.applicationCache.swapCache()
if window.confirm(_('The calibre web application has been updated. Do you want reload the site?')):
window.location.reload()
, False)
script = document.currentScript or document.scripts[0] script = document.currentScript or document.scripts[0]
main_js(script.textContent) main_js(script.textContent)
script.parentNode.removeChild(script) # save some memory script.parentNode.removeChild(script) # save some memory