From edb11dbd28098a7c23e1783a0413e5360416a4ad Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 2 May 2016 14:32:30 +0530 Subject: [PATCH] Store MathJax in the db --- src/pyj/read_book/db.pyj | 23 +++++++- src/pyj/read_book/ui.pyj | 120 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 136 insertions(+), 7 deletions(-) diff --git a/src/pyj/read_book/db.pyj b/src/pyj/read_book/db.pyj index 6b683c80db..fa0b9a26e1 100644 --- a/src/pyj/read_book/db.pyj +++ b/src/pyj/read_book/db.pyj @@ -13,6 +13,8 @@ def upgrade_schema(idb, old_version, new_version): idb.createObjectStore('books', {'keyPath':'key'}) if not idb.objectStoreNames.contains('files'): idb.createObjectStore('files') + if not idb.objectStoreNames.contains('mathjax'): + idb.createObjectStore('mathjax') if not idb.objectStoreNames.contains('objects'): idb.createObjectStore('objects', {'keyPath':'key'}) @@ -27,7 +29,7 @@ def get_error_details(event): desc = desc.errorCode DB_NAME = 'calibre-books-db-testing' # TODO: Remove test suffix and change version back to 1 -DB_VERSION = 1 +DB_VERSION = 2 class DB: @@ -96,6 +98,11 @@ class DB: }) ) + def get_mathjax_info(self, proceed): + self.do_op(['objects'], 'mathjax-info', _('Failed to read from the objects database'), def(result): + proceed(result or {'key':'mathjax-info'}) + ) + def save_manifest(self, book, manifest, proceed): book.manifest = manifest book.metadata = manifest.metadata @@ -106,7 +113,7 @@ class DB: self.do_op(['books'], book, _('Failed to write to the books database'), proceed, op='put') def store_file(self, book, name, xhr, proceed, is_cover): - store_as_text = xhr.responseType is 'text' + store_as_text = xhr.responseType is 'text' or not xhr.responseType fname = file_store_name(book, name) needs_encoding = not store_as_text and not self.supports_blobs book.stored_files[fname] = {'encoded':needs_encoding, 'mimetype':book.manifest.files[name].mimetype, 'store_as_text':store_as_text} @@ -148,10 +155,22 @@ class DB: req.onerror = def(event): proceed(_('Failed to store book data ({0}) with error: {1}').format(name, get_error_details(event))) + def store_mathjax_file(self, name, xhr, proceed): + data = xhr.response + if not self.supports_blobs: + data = base64encode(Uint8Array(data)) + req = self.idb.transaction(['mathjax'], 'readwrite').objectStore('mathjax').put(data, name) + req.onsuccess = def(event): proceed() + req.onerror = def(event): + proceed(_('Failed to store mathjax file ({0}) with error: {1}').format(name, get_error_details(event))) + def finish_book(self, book, proceed): book.is_complete = True self.do_op(['books'], book, _('Failed to write to the books database'), proceed, op='put') + def finish_mathjax(self, mathjax_info, proceed): + self.do_op(['objects'], mathjax_info, _('Failed to write to the objects database'), proceed, op='put') + def update_last_read_time(self, book): book.last_read = Date() self.do_op(['books'], book, _('Failed to write to the books database'), op='put') diff --git a/src/pyj/read_book/ui.pyj b/src/pyj/read_book/ui.pyj index 432edc826e..8d8ec5f3a9 100644 --- a/src/pyj/read_book/ui.pyj +++ b/src/pyj/read_book/ui.pyj @@ -141,7 +141,7 @@ class ReadUI: return if end_type is not 'load': return self.show_error(_('Failed to load book manifest'), - _('Could not open {title} as book manifest failed to load, click "Show Details" for more information.').format(title=self.current_metadata.title), + _('Could not open {title} as the book manifest failed to load, click "Show Details" for more information.').format(title=self.current_metadata.title), xhr.error_html) try: manifest = JSON.parse(xhr.responseText) @@ -201,6 +201,11 @@ class ReadUI: pbar.setAttribute('value', x + '') progress.lastChild.textContent = _('Downloaded {0}, {1} left').format(human_readable(x), human_readable(total - x)) + def show_failure(): + det = ['

{}

{}

'.format(fname, err_html) for fname, err_html in failed_files].join('') + self.show_error(_('Could not download book'), _( + 'Failed to download some book data, click "Show details" for more information'), det) + def on_stored(err): files_left.discard(this) if err: @@ -208,10 +213,7 @@ class ReadUI: if len(files_left): return if failed_files.length: - det = ['

{}

{}

'.format(fname, err_html) for fname, err_html in failed_files].join('') - self.show_error(_('Could not download book'), _( - 'Failed to download some book data, click "Show details" for more information'), det) - return + return show_failure() self.db.finish_book(book, self.display_book.bind(self, book)) def on_complete(end_type, xhr, ev): @@ -226,6 +228,8 @@ class ReadUI: else: failed_files.append([this, xhr.error_html]) files_left.discard(this) + if not len(files_left): + show_failure() def on_progress(loaded, ftotal): nonlocal total, cover_total_updated, raster_cover_size @@ -252,6 +256,112 @@ class ReadUI: if fname is not raster_cover_name: start_download(fname, base_path + encodeURIComponent(fname)) + def ensure_maths(self, proceed): + self.db.get_mathjax_info(def(mathjax_info): + if mathjax_info.version is MATHJAX_VERSION: + return proceed() + print('Upgrading MathJax, previous version:', mathjax_info.version) + self.get_mathjax_manifest(mathjax_info, proceed) + ) + + def get_mathjax_manifest(self, mathjax_info, proceed): + ajax('mathjax', def(end_type, xhr, event): + if end_type is 'abort': + return + if end_type is not 'load': + return self.show_error(_('Failed to load MathJax manifest'), + _('Could not open {title} as the MathJax manifest failed to load, click "Show Details" for more information.').format(title=self.current_metadata.title), + xhr.error_html) + try: + manifest = JSON.parse(xhr.responseText) + except Exception: + return self.show_error(_('Failed to load MathJax manifest'), + _('The MathJax manifest is not valid'), traceback.format_exc()) + if manifest.etag is not MATHJAX_VERSION: + print('calibre upgraded: MATHJAX_VERSION={} manifest.etag={}'.format(MATHJAX_VERSION, manifest.etag)) + return self.show_error(_('calibre upgraded!'), _( + 'A newer version of calibre is available, please click the reload button in your browser.')) + mathjax_info.version = manifest.etag + mathjax_info.files = manifest.files + self.download_mathjax(mathjax_info, proceed) + ).send() + + def download_mathjax(self, mathjax_info, proceed): + files = mathjax_info.files + total = 0 + progress_track = {} + files_left = set() + failed_files = [] + for key in files: + total += files[key] + progress_track[key] = 0 + files_left.add(key) + progress = document.getElementById(self.progress_id) + progress.firstChild.textContent = _( + 'Downloading MathJax to render mathematics in this book...') + pbar = progress.firstChild.nextSibling + pbar.setAttribute('max', total + '') + for xhr in self.downloads_in_progress: + xhr.abort() + self.downloads_in_progress = [] + + def update_progress(): + x = 0 + for name in progress_track: + x += progress_track[name] + pbar.setAttribute('value', x + '') + progress.lastChild.textContent = _('Downloaded {0}, {1} left').format(human_readable(x), human_readable(total - x)) + + def on_progress(loaded, ftotal): + progress_track[this] = loaded + update_progress() + + def show_failure(): + det = ['

{}

{}

'.format(fname, err_html) for fname, err_html in failed_files].join('') + self.show_error(_('Could not download MathJax'), _( + 'Failed to download some MathJax data, click "Show details" for more information'), det) + + def on_complete(end_type, xhr, ev): + self.downloads_in_progress.remove(xhr) + progress_track[this] = files[this] + update_progress() + if end_type is 'abort': + files_left.discard(this) + return + if end_type is 'load': + self.db.store_mathjax_file(this, xhr, on_stored.bind(this)) + else: + failed_files.append([this, xhr.error_html]) + files_left.discard(this) + if not len(files_left): + show_failure() + + def on_stored(err): + files_left.discard(this) + if err: + failed_files.append([this, err]) + if len(files_left): + return + if failed_files.length: + return show_failure() + self.db.finish_mathjax(mathjax_info, proceed) + + def start_download(name): + path = 'mathjax/' + name + xhr = ajax(path, on_complete.bind(name), on_progress=on_progress.bind(name), progress_totals_needed=False) + xhr.responseType = 'blob' if self.db.supports_blobs else 'arraybuffer' + xhr.send() + self.downloads_in_progress.push(xhr) + + for fname in files_left: + start_download(fname) + def display_book(self, book): + if book.manifest.has_maths: + self.ensure_maths(self.display_book_stage2.bind(self, book)) + else: + self.display_book_stage2(book) + + def display_book_stage2(self, book): self.show_stack(self.display_id) self.view.display_book(book)