ditch the awkward periodic service thing, just use window.postMessage to pass info between file:// origins
This commit is contained in:
parent
fa5e5a1f6a
commit
b66e1c5372
|
@ -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()
|
||||
|
||||
|
|
|
@ -170,3 +170,8 @@ body {
|
|||
#suggested-subheader {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
#store-iframe {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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}`;
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
const ROOT = "{{ root() }}";
|
||||
const INDEX = "{{ index() }}";
|
||||
const WORKS = {{ works | tojson }};
|
||||
const TOKEN = "{{ token }}";
|
||||
</script>
|
||||
<script src="{{ root() }}/reading.js"></script>
|
||||
<script src="{{ root() }}/static/index.js"></script>
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
|
@ -27,4 +27,5 @@
|
|||
</div>
|
||||
<div id="main-listing" class="card-listing">
|
||||
</div>
|
||||
<iframe id="store-iframe" src="{{ root() }}/store.html"></iframe>
|
||||
{% endblock %}
|
||||
|
|
35
dlibrary/templates/store.html
Normal file
35
dlibrary/templates/store.html
Normal file
|
@ -0,0 +1,35 @@
|
|||
{% from 'utils.html' import root with context -%}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="manifest" href="{{ root() }}/static/manifest.json" crossorigin="use-credentials">
|
||||
<script type="text/javascript">
|
||||
const TOKEN = "{{ token }}";
|
||||
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.data.token !== TOKEN) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.data.operation) {
|
||||
case 'set':
|
||||
localStorage.setItem(event.data.key, event.data.value);
|
||||
break;
|
||||
case 'get':
|
||||
event.source.postMessage({
|
||||
token: TOKEN,
|
||||
key: event.data.key,
|
||||
value: localStorage.getItem(event.data.key),
|
||||
}, "*");
|
||||
break;
|
||||
case 'remove':
|
||||
localStorage.removeItem(event.data.key);
|
||||
break;
|
||||
case 'getAll':
|
||||
event.source.postMessage({ token: TOKEN, store: {...localStorage} }, "*");
|
||||
break;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
</html>
|
|
@ -5,6 +5,7 @@
|
|||
<script>
|
||||
const WORK_ID = "{{ work['id'] }}";
|
||||
const INDEX = "{{ index() }}";
|
||||
const TOKEN = "{{ token }}";
|
||||
const IMAGES = [
|
||||
{% for filename in work['images'] %}
|
||||
"{{ root() }}/images/{{ work['id'] }}/{{ filename }}",
|
||||
|
@ -48,4 +49,5 @@
|
|||
</div>
|
||||
<div class="image-container" id="current-image"></div>
|
||||
<div class="image-container" id="preload-images"></div>
|
||||
<iframe id="store-iframe" src="{{ root() }}/store.html"></iframe>
|
||||
{% endblock %}
|
||||
|
|
Loading…
Reference in a new issue