base tag + LRU cache for RNS browse, sync status fixes
This commit is contained in:
parent
8f46878151
commit
ba1b8a783a
2 changed files with 79 additions and 46 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
set_setting(f"sync_status_{sub_id}", "syncing")
|
||||
db = get_db()
|
||||
db = None
|
||||
try:
|
||||
set_setting(f"sync_status_{sub_id}", "syncing")
|
||||
db = get_db()
|
||||
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:
|
||||
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):
|
||||
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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue