base tag + LRU cache for RNS browse, sync status fixes

This commit is contained in:
blankie 2026-06-19 02:17:11 +00:00
parent 8f46878151
commit ba1b8a783a
2 changed files with 79 additions and 46 deletions

View file

@ -1,10 +1,54 @@
import json
import time
import threading
import traceback
from tinyweb.db import get_db, return_db
from tinyweb.rns_client import fetch_remote_page
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():
db = get_db()
try:
@ -71,54 +115,22 @@ def handle_rns_delete_hash(body):
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):
prefix = f"/rns/{dest_hash}"
sub_path = path[len(prefix):] if path.startswith(prefix) else "/"
if not 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:
resp = fetch_remote_page(dest_hash, sub_path)
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>"
except (json.JSONDecodeError, TypeError):
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 {
"status": 200,

View file

@ -1,4 +1,5 @@
import threading
import time
from datetime import datetime
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_starts = {}
_SYNC_TIMEOUT = 120
MAX_API_SITES = 5000
MAX_BROWSE = 5000
@ -142,6 +145,13 @@ def handle_subscriptions(msg=""):
subs = db.execute("SELECT * FROM subscriptions ORDER BY id DESC").fetchall()
finally:
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 = ""
for s in subs:
sub_id = s["id"]
@ -152,6 +162,9 @@ def handle_subscriptions(msg=""):
if is_syncing:
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:"):
err_msg = sync_status[6:]
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):
db = None
try:
set_setting(f"sync_status_{sub_id}", "syncing")
db = get_db()
try:
sub = db.execute("SELECT * FROM subscriptions WHERE id = ?", (sub_id,)).fetchone()
if not sub:
set_setting(f"sync_status_{sub_id}", "error:Subscription not found.")
@ -407,13 +421,17 @@ def _sync_subscription(sub_id):
except Exception as e:
set_setting(f"sync_status_{sub_id}", f"error:{e}")
finally:
if db:
return_db(db)
_sync_threads.pop(sub_id, None)
_sync_starts.pop(sub_id, None)
def handle_subscription_sync(sub_id):
if sub_id in _sync_threads and _sync_threads[sub_id].is_alive():
return _redirect("/subscriptions")
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)
_sync_threads[sub_id] = t
t.start()
@ -454,6 +472,7 @@ def handle_subscription_syncall():
if sub_id in _sync_threads and _sync_threads[sub_id].is_alive():
continue
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)
_sync_threads[sub_id] = t
t.start()