base tag + LRU cache for RNS browse, sync status fixes
This commit is contained in:
parent
025db00858
commit
68a92a677a
2 changed files with 79 additions and 46 deletions
|
|
@ -1,10 +1,54 @@
|
||||||
import json
|
import json
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
from tinyweb.db import get_db, return_db
|
from tinyweb.db import get_db, return_db
|
||||||
from tinyweb.rns_client import fetch_remote_page
|
from tinyweb.rns_client import fetch_remote_page
|
||||||
from tinyweb.templates import esc
|
from tinyweb.templates import esc
|
||||||
|
|
||||||
|
|
||||||
|
class _PageCache:
|
||||||
|
def __init__(self, maxsize=50, ttl=300):
|
||||||
|
self._maxsize = maxsize
|
||||||
|
self._ttl = ttl
|
||||||
|
self._cache = {}
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
|
||||||
|
def get(self, key):
|
||||||
|
with self._lock:
|
||||||
|
entry = self._cache.get(key)
|
||||||
|
if entry is None:
|
||||||
|
return None
|
||||||
|
if time.time() - entry["time"] > self._ttl:
|
||||||
|
del self._cache[key]
|
||||||
|
return None
|
||||||
|
self._cache.pop(key)
|
||||||
|
self._cache[key] = entry
|
||||||
|
return entry["value"]
|
||||||
|
|
||||||
|
def put(self, key, value):
|
||||||
|
with self._lock:
|
||||||
|
if key in self._cache:
|
||||||
|
self._cache.pop(key)
|
||||||
|
elif len(self._cache) >= self._maxsize:
|
||||||
|
oldest = next(iter(self._cache))
|
||||||
|
del self._cache[oldest]
|
||||||
|
self._cache[key] = {"value": value, "time": time.time()}
|
||||||
|
|
||||||
|
|
||||||
|
_page_cache = _PageCache()
|
||||||
|
|
||||||
|
|
||||||
|
def _inject_base_tag(html, dest_hash):
|
||||||
|
base = f'<base href="/rns/{dest_hash}/">'
|
||||||
|
head_start = html.find("<head")
|
||||||
|
if head_start >= 0:
|
||||||
|
close = html.find(">", head_start)
|
||||||
|
if close >= 0:
|
||||||
|
return html[:close + 1] + base + html[close + 1:]
|
||||||
|
return f"<head>{base}</head>{html}"
|
||||||
|
|
||||||
|
|
||||||
def _get_mesh_sites():
|
def _get_mesh_sites():
|
||||||
db = get_db()
|
db = get_db()
|
||||||
try:
|
try:
|
||||||
|
|
@ -71,54 +115,22 @@ def handle_rns_delete_hash(body):
|
||||||
return _redirect("/")
|
return _redirect("/")
|
||||||
|
|
||||||
|
|
||||||
def _rewrite_links(html, dest_hash):
|
|
||||||
out = []
|
|
||||||
i = 0
|
|
||||||
while i < len(html):
|
|
||||||
href_start = html.find('href="', i)
|
|
||||||
src_start = html.find('src="', i)
|
|
||||||
action_start = html.find('action="', i)
|
|
||||||
|
|
||||||
candidates = []
|
|
||||||
if href_start >= 0:
|
|
||||||
candidates.append((href_start, "href", 'href="'))
|
|
||||||
if src_start >= 0:
|
|
||||||
candidates.append((src_start, "src", 'src="'))
|
|
||||||
if action_start >= 0:
|
|
||||||
candidates.append((action_start, "action", 'action="'))
|
|
||||||
|
|
||||||
if not candidates:
|
|
||||||
out.append(html[i:])
|
|
||||||
break
|
|
||||||
|
|
||||||
candidates.sort()
|
|
||||||
pos, attr, prefix = candidates[0]
|
|
||||||
out.append(html[i:pos + len(prefix)])
|
|
||||||
|
|
||||||
value_start = pos + len(prefix)
|
|
||||||
value_end = html.find('"', value_start)
|
|
||||||
if value_end < 0:
|
|
||||||
out.append(html[value_start:])
|
|
||||||
break
|
|
||||||
value = html[value_start:value_end]
|
|
||||||
|
|
||||||
if value.startswith("/"):
|
|
||||||
out.append(f"/rns/{dest_hash}{value}")
|
|
||||||
else:
|
|
||||||
out.append(value)
|
|
||||||
|
|
||||||
out.append('"')
|
|
||||||
i = value_end + 1
|
|
||||||
|
|
||||||
return "".join(out)
|
|
||||||
|
|
||||||
|
|
||||||
def handle_rns_browse(path, dest_hash):
|
def handle_rns_browse(path, dest_hash):
|
||||||
prefix = f"/rns/{dest_hash}"
|
prefix = f"/rns/{dest_hash}"
|
||||||
sub_path = path[len(prefix):] if path.startswith(prefix) else "/"
|
sub_path = path[len(prefix):] if path.startswith(prefix) else "/"
|
||||||
if not sub_path:
|
if not sub_path:
|
||||||
sub_path = "/"
|
sub_path = "/"
|
||||||
|
|
||||||
|
cache_key = (dest_hash, sub_path)
|
||||||
|
cached = _page_cache.get(cache_key)
|
||||||
|
if cached is not None:
|
||||||
|
return {
|
||||||
|
"status": 200,
|
||||||
|
"content_type": "text/html; charset=utf-8",
|
||||||
|
"body": cached,
|
||||||
|
"headers": {},
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resp = fetch_remote_page(dest_hash, sub_path)
|
resp = fetch_remote_page(dest_hash, sub_path)
|
||||||
except ConnectionError as e:
|
except ConnectionError as e:
|
||||||
|
|
@ -152,8 +164,10 @@ def handle_rns_browse(path, dest_hash):
|
||||||
body = f"<pre>{esc(json.dumps(data, indent=2))}</pre>"
|
body = f"<pre>{esc(json.dumps(data, indent=2))}</pre>"
|
||||||
except (json.JSONDecodeError, TypeError):
|
except (json.JSONDecodeError, TypeError):
|
||||||
body = f"<pre>{esc(body[:2000])}</pre>"
|
body = f"<pre>{esc(body[:2000])}</pre>"
|
||||||
|
else:
|
||||||
|
body = _inject_base_tag(body, dest_hash)
|
||||||
|
|
||||||
body = _rewrite_links(body, dest_hash)
|
_page_cache.put(cache_key, body)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"status": 200,
|
"status": 200,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import threading
|
import threading
|
||||||
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from tinyweb.db import get_db, return_db, get_setting, set_setting, get_site_name, index_url, clean_url
|
from tinyweb.db import get_db, return_db, get_setting, set_setting, get_site_name, index_url, clean_url
|
||||||
|
|
@ -10,6 +11,8 @@ from ._helpers import (
|
||||||
)
|
)
|
||||||
|
|
||||||
_sync_threads = {}
|
_sync_threads = {}
|
||||||
|
_sync_starts = {}
|
||||||
|
_SYNC_TIMEOUT = 120
|
||||||
|
|
||||||
MAX_API_SITES = 5000
|
MAX_API_SITES = 5000
|
||||||
MAX_BROWSE = 5000
|
MAX_BROWSE = 5000
|
||||||
|
|
@ -142,6 +145,13 @@ def handle_subscriptions(msg=""):
|
||||||
subs = db.execute("SELECT * FROM subscriptions ORDER BY id DESC").fetchall()
|
subs = db.execute("SELECT * FROM subscriptions ORDER BY id DESC").fetchall()
|
||||||
finally:
|
finally:
|
||||||
return_db(db)
|
return_db(db)
|
||||||
|
now_t = time.time()
|
||||||
|
for sub_id, start_t in list(_sync_starts.items()):
|
||||||
|
if now_t - start_t > _SYNC_TIMEOUT:
|
||||||
|
set_setting(f"sync_status_{sub_id}", "error:Timed out")
|
||||||
|
_sync_threads.pop(sub_id, None)
|
||||||
|
_sync_starts.pop(sub_id, None)
|
||||||
|
|
||||||
cards = ""
|
cards = ""
|
||||||
for s in subs:
|
for s in subs:
|
||||||
sub_id = s["id"]
|
sub_id = s["id"]
|
||||||
|
|
@ -152,6 +162,9 @@ def handle_subscriptions(msg=""):
|
||||||
|
|
||||||
if is_syncing:
|
if is_syncing:
|
||||||
status_html = '<div style="margin-top:0.4rem;font-size:0.85rem;color:#2070c0">syncing...</div>'
|
status_html = '<div style="margin-top:0.4rem;font-size:0.85rem;color:#2070c0">syncing...</div>'
|
||||||
|
elif sync_status.startswith("done:"):
|
||||||
|
count = sync_status[5:]
|
||||||
|
status_html = f'<div style="margin-top:0.4rem;font-size:0.85rem;color:#30a030">synced {esc(count)} site(s)</div>'
|
||||||
elif sync_status.startswith("error:"):
|
elif sync_status.startswith("error:"):
|
||||||
err_msg = sync_status[6:]
|
err_msg = sync_status[6:]
|
||||||
status_html = f'<div style="margin-top:0.4rem;font-size:0.85rem;color:#c03030">{esc(err_msg)}</div>'
|
status_html = f'<div style="margin-top:0.4rem;font-size:0.85rem;color:#c03030">{esc(err_msg)}</div>'
|
||||||
|
|
@ -349,9 +362,10 @@ def handle_subscription_pick(body):
|
||||||
|
|
||||||
|
|
||||||
def _sync_subscription(sub_id):
|
def _sync_subscription(sub_id):
|
||||||
set_setting(f"sync_status_{sub_id}", "syncing")
|
db = None
|
||||||
db = get_db()
|
|
||||||
try:
|
try:
|
||||||
|
set_setting(f"sync_status_{sub_id}", "syncing")
|
||||||
|
db = get_db()
|
||||||
sub = db.execute("SELECT * FROM subscriptions WHERE id = ?", (sub_id,)).fetchone()
|
sub = db.execute("SELECT * FROM subscriptions WHERE id = ?", (sub_id,)).fetchone()
|
||||||
if not sub:
|
if not sub:
|
||||||
set_setting(f"sync_status_{sub_id}", "error:Subscription not found.")
|
set_setting(f"sync_status_{sub_id}", "error:Subscription not found.")
|
||||||
|
|
@ -407,13 +421,17 @@ def _sync_subscription(sub_id):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
set_setting(f"sync_status_{sub_id}", f"error:{e}")
|
set_setting(f"sync_status_{sub_id}", f"error:{e}")
|
||||||
finally:
|
finally:
|
||||||
return_db(db)
|
if db:
|
||||||
|
return_db(db)
|
||||||
|
_sync_threads.pop(sub_id, None)
|
||||||
|
_sync_starts.pop(sub_id, None)
|
||||||
|
|
||||||
|
|
||||||
def handle_subscription_sync(sub_id):
|
def handle_subscription_sync(sub_id):
|
||||||
if sub_id in _sync_threads and _sync_threads[sub_id].is_alive():
|
if sub_id in _sync_threads and _sync_threads[sub_id].is_alive():
|
||||||
return _redirect("/subscriptions")
|
return _redirect("/subscriptions")
|
||||||
set_setting(f"sync_status_{sub_id}", "syncing")
|
set_setting(f"sync_status_{sub_id}", "syncing")
|
||||||
|
_sync_starts[sub_id] = time.time()
|
||||||
t = threading.Thread(target=_sync_subscription, args=(sub_id,), daemon=True)
|
t = threading.Thread(target=_sync_subscription, args=(sub_id,), daemon=True)
|
||||||
_sync_threads[sub_id] = t
|
_sync_threads[sub_id] = t
|
||||||
t.start()
|
t.start()
|
||||||
|
|
@ -454,6 +472,7 @@ def handle_subscription_syncall():
|
||||||
if sub_id in _sync_threads and _sync_threads[sub_id].is_alive():
|
if sub_id in _sync_threads and _sync_threads[sub_id].is_alive():
|
||||||
continue
|
continue
|
||||||
set_setting(f"sync_status_{sub_id}", "syncing")
|
set_setting(f"sync_status_{sub_id}", "syncing")
|
||||||
|
_sync_starts[sub_id] = time.time()
|
||||||
t = threading.Thread(target=_sync_subscription, args=(sub_id,), daemon=True)
|
t = threading.Thread(target=_sync_subscription, args=(sub_id,), daemon=True)
|
||||||
_sync_threads[sub_id] = t
|
_sync_threads[sub_id] = t
|
||||||
t.start()
|
t.start()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue