diff --git a/dlibrary/dlibrary.py b/dlibrary/dlibrary.py index d26dba9..787f68f 100755 --- a/dlibrary/dlibrary.py +++ b/dlibrary/dlibrary.py @@ -2,7 +2,6 @@ import argparse import asyncio -from configparser import ConfigParser import importlib_resources as resources from io import BytesIO from pathlib import Path @@ -14,6 +13,7 @@ import readline import shutil import sqlite3 import stat +import string import textwrap import time import unicodedata @@ -1219,8 +1219,11 @@ def generate(args): categorization_template = jenv.get_template("categorization.html") work_template = jenv.get_template("work.html") index_template = jenv.get_template("index.html") + store_template = jenv.get_template("store.html") debug('templates loaded') + store_token = ''.join(random.choices(string.ascii_letters + string.digits, k=32)) + debug('opening main database') con = sqlite3.connect(args.destdir / 'meta.db') cur = con.cursor() @@ -1313,7 +1316,7 @@ def generate(args): suggested=[works[suggested] for suggested in cached_suggestions[work['id']]], )) with open(viewer_dir / 'index.html', 'w') as f: - f.write(viewer_template.render(depth=3, work=work, title=work['title'])) + f.write(viewer_template.render(depth=3, work=work, title=work['title'], token=store_token)) count_progress(idx, len(works), 'works processed') @@ -1378,9 +1381,14 @@ def generate(args): debug('writing index page') with open(site_dir / 'index.html', 'w') as f: - f.write(index_template.render(depth=0, works=list(works.values()))) + f.write(index_template.render(depth=0, works=list(works.values()), token=store_token)) debug('index page written') + debug('writing store iframe page') + with open(site_dir / 'store.html', 'w') as f: + f.write(store_template.render(depth=0, token=store_token)) + debug('store iframe page written') + debug('closing cache database') cache_con.close() debug('cache database closed') @@ -1390,48 +1398,6 @@ def generate(args): debug('main database closed') -def update_reading(args): - debug('updating list of currently-reading works') - - firefox_data_dir = Path.home() / '.mozilla' / 'firefox' - debug('finding firefox profiles') - firefox_profiles_parser = ConfigParser() - firefox_profiles_parser.read(firefox_data_dir / 'profiles.ini') - default_profile = next( - section['Path'] - for section in firefox_profiles_parser.values() - if section.get('Default') == '1' - ) - debug(f'selecting default profile {default_profile}') - storage_path = firefox_data_dir / default_profile / 'storage' / 'default' - dlibrary_site_works_path = args.destdir / 'site' / 'works' - origin_storage_glob = ( - 'file+++' + - str(dlibrary_site_works_path.absolute()).replace('/', '+') + - '*+view+index.html/ls/data.sqlite' - ) - debug(f'searching for local storage matching glob {origin_storage_glob}') - - reading_works = [] - for local_storage_db in storage_path.glob(origin_storage_glob): - debug(f'reading db {local_storage_db}') - try: - with sqlite3.connect(local_storage_db, timeout=0.1) as con: - cur = con.cursor() - for (key,) in cur.execute("SELECT key FROM data WHERE key LIKE '%-currentPage' AND VALUE != CAST('0' AS BLOB)"): - work_id = key[:-len('-currentPage')] - reading_works.append(work_id) - except sqlite3.OperationalError: - debug(f'database {local_storage_db} locked, skipping') - - debug(f'reading works: {reading_works}') - output_file = args.destdir / 'site' / 'reading.js' - with open(output_file, 'w') as f: - f.write('const READING_WORKS = [\n') - for work_id in reading_works: - f.write(f' "{work_id}",\n') - f.write('];') - argparser = argparse.ArgumentParser( prog='dlibrary', formatter_class=argparse.RawDescriptionHelpFormatter, @@ -1621,22 +1587,6 @@ parser_generate = subparsers.add_parser( ) parser_generate.set_defaults(func=generate) -parser_update_reading = subparsers.add_parser( - 'update-reading', - help='update list of currently-reading works (firefox-only)', - formatter_class=argparse.RawDescriptionHelpFormatter, - description=textwrap.dedent("""\ - If accessing dlibrary via file:// URLs, every distinct filepath is - its own opaque origin, so individual works can't share - localStorage with the main overview page, making it impossible to - sort works by currently-reading status. This subcommand can be run - periodically to work around the same-origin limitations by - directly accessing data from firefox's localStorage and putting a - list of currently-reading works in a JS file that the main page can read. - """), -) -parser_update_reading.set_defaults(func=update_reading) - def main(): args = argparser.parse_args() diff --git a/dlibrary/static/dlibrary.css b/dlibrary/static/dlibrary.css index 0ee5bcf..63fe03c 100644 --- a/dlibrary/static/dlibrary.css +++ b/dlibrary/static/dlibrary.css @@ -170,3 +170,8 @@ body { #suggested-subheader { text-align: center; } + + +#store-iframe { + display: none; +} diff --git a/dlibrary/static/index.js b/dlibrary/static/index.js index eb54c29..de74bb0 100644 --- a/dlibrary/static/index.js +++ b/dlibrary/static/index.js @@ -28,11 +28,17 @@ function newSeed() { return Math.floor(Math.random() * LCG_M); } +let receivedStore = JSON.parse(localStorage.getItem('receivedStore')) || {}; + +const isFileUrl = (window.location.protocol === 'file:'); + function isReading(work) { - return READING_WORKS.indexOf(work.id) !== -1 || !!parseInt(localStorage.getItem(`${work.id}-currentPage`)); + const key = `${work.id}-currentPage`; + const value = isFileUrl ? receivedStore[key] : localStorage.getItem(key); + return !!parseInt(value); } -document.addEventListener('DOMContentLoaded', () => { +function setupIndex() { const shuffleButton = document.getElementById('shuffle'); const sortButton = document.getElementById('sort'); const readingButton = document.getElementById('reading'); @@ -165,4 +171,30 @@ document.addEventListener('DOMContentLoaded', () => { }; searchBox.oninput = applyView; -}); +} + +if (isFileUrl) { + let initialized = false; + window.addEventListener('message', (event) => { + if (event.data.token !== TOKEN) { + return; + } + + receivedStore = event.data.store; + localStorage.setItem('receivedStore', JSON.stringify(receivedStore)); + + if (!initialized) { + initialized = true; + setupIndex(); + } + }); + + window.addEventListener('load', () => { + document.getElementById('store-iframe').contentWindow.postMessage({ + token: TOKEN, + operation: 'getAll', + }, "*"); + }); +} else { + document.addEventListener('DOMContentLoaded', setupIndex); +} diff --git a/dlibrary/static/viewer.js b/dlibrary/static/viewer.js index 9d4e1e2..721a148 100644 --- a/dlibrary/static/viewer.js +++ b/dlibrary/static/viewer.js @@ -5,6 +5,8 @@ const COMMAND_SEQUENCE_MAX_INTERVAL = 400; const ACCEL_PAGE_MOVEMENT = 10; +const isFileUrl = (window.location.protocol === 'file:'); + document.addEventListener('DOMContentLoaded', () => { const currentImage = document.getElementById('current-image'); const preloadImages = document.getElementById('preload-images'); @@ -102,6 +104,14 @@ document.addEventListener('DOMContentLoaded', () => { currentPage = pageNum; localStorage.setItem(`${WORK_ID}-currentPage`, currentPage); + if (isFileUrl) { + document.getElementById('store-iframe').contentWindow.postMessage({ + token: TOKEN, + operation: 'set', + key: `${WORK_ID}-currentPage`, + value: currentPage, + }, "*"); + } currentImage.replaceChildren(imageSrc(current)); @@ -171,6 +181,13 @@ document.addEventListener('DOMContentLoaded', () => { changeDuration(duration, true); if (currentPage === IMAGES.length - 1 || currentPage === 0) { localStorage.removeItem(`${WORK_ID}-currentPage`); + if (isFileUrl) { + document.getElementById('store-iframe').contentWindow.postMessage({ + token: TOKEN, + operation: 'remove', + key: `${WORK_ID}-currentPage`, + }, "*"); + } } window.location.href = `../${INDEX}`; } diff --git a/dlibrary/templates/index.html b/dlibrary/templates/index.html index fa632f8..b7e9afc 100644 --- a/dlibrary/templates/index.html +++ b/dlibrary/templates/index.html @@ -5,8 +5,8 @@ const ROOT = "{{ root() }}"; const INDEX = "{{ index() }}"; const WORKS = {{ works | tojson }}; + const TOKEN = "{{ token }}"; - {% endblock %} {% block body %} @@ -27,4 +27,5 @@
+ {% endblock %} diff --git a/dlibrary/templates/store.html b/dlibrary/templates/store.html new file mode 100644 index 0000000..70e1f56 --- /dev/null +++ b/dlibrary/templates/store.html @@ -0,0 +1,35 @@ +{% from 'utils.html' import root with context -%} + + + + + + + diff --git a/dlibrary/templates/viewer.html b/dlibrary/templates/viewer.html index 9125ab2..582549d 100644 --- a/dlibrary/templates/viewer.html +++ b/dlibrary/templates/viewer.html @@ -5,6 +5,7 @@