mobile ux
This commit is contained in:
@@ -168,6 +168,7 @@ def save_preferences(data: dict, user_id: int | None = None):
|
||||
port_check_enabled = data.get("port_check_enabled")
|
||||
footer_items_json = data.get("footer_items_json")
|
||||
title_speed_enabled = data.get("title_speed_enabled")
|
||||
tracker_favicons_enabled = data.get("tracker_favicons_enabled")
|
||||
with connect() as conn:
|
||||
now = utcnow()
|
||||
if allowed_theme:
|
||||
@@ -187,6 +188,9 @@ def save_preferences(data: dict, user_id: int | None = None):
|
||||
if title_speed_enabled is not None:
|
||||
# Notatka: preferencja steruje wyświetlaniem bieżącego DL/UL w tytule karty przeglądarki.
|
||||
conn.execute("UPDATE user_preferences SET title_speed_enabled=?, updated_at=? WHERE user_id=?", (1 if title_speed_enabled else 0, now, user_id))
|
||||
if tracker_favicons_enabled is not None:
|
||||
# Note: Enables optional tracker favicon display without changing tracker filtering itself.
|
||||
conn.execute("UPDATE user_preferences SET tracker_favicons_enabled=?, updated_at=? WHERE user_id=?", (1 if tracker_favicons_enabled else 0, now, user_id))
|
||||
if footer_items_json is not None:
|
||||
# Note: Store only JSON objects so footer visibility can be extended without schema churn.
|
||||
value = footer_items_json if isinstance(footer_items_json, str) else json.dumps(footer_items_json)
|
||||
|
||||
@@ -922,6 +922,49 @@ def _call_first(c: ScgiRtorrentClient, candidates: list[tuple[str, tuple]]) -> d
|
||||
raise RuntimeError("; ".join(errors))
|
||||
|
||||
|
||||
|
||||
def _tracker_domain(url: str) -> str:
|
||||
raw = str(url or '').strip()
|
||||
if not raw:
|
||||
return ''
|
||||
parsed = urlparse(raw if '://' in raw else f'http://{raw}')
|
||||
host = (parsed.hostname or '').lower().strip('.')
|
||||
if host.startswith('www.'):
|
||||
host = host[4:]
|
||||
return host
|
||||
|
||||
|
||||
def tracker_summary(profile: dict, torrent_hashes: list[str] | None = None, limit: int = 1000) -> dict:
|
||||
"""Return tracker domains grouped by torrent for the sidebar filter."""
|
||||
# Note: Tracker summary is read-only and isolated from the normal torrent snapshot, so slow tracker RPC calls cannot break the main list.
|
||||
hashes = [str(h or '').strip() for h in (torrent_hashes or []) if str(h or '').strip()]
|
||||
if not hashes:
|
||||
hashes = [t.get('hash') for t in list_torrents(profile) if t.get('hash')]
|
||||
hashes = hashes[:max(1, int(limit or 1000))]
|
||||
by_hash: dict[str, list[dict]] = {}
|
||||
counts: dict[str, dict] = {}
|
||||
errors = []
|
||||
for h in hashes:
|
||||
try:
|
||||
items = []
|
||||
seen = set()
|
||||
for tr in torrent_trackers(profile, h):
|
||||
url = str(tr.get('url') or '')
|
||||
domain = _tracker_domain(url)
|
||||
if not domain or domain in seen:
|
||||
continue
|
||||
seen.add(domain)
|
||||
item = {'domain': domain, 'url': url}
|
||||
items.append(item)
|
||||
row = counts.setdefault(domain, {'domain': domain, 'url': url, 'count': 0})
|
||||
row['count'] += 1
|
||||
by_hash[h] = items
|
||||
except Exception as exc:
|
||||
errors.append({'hash': h, 'error': str(exc)})
|
||||
by_hash[h] = []
|
||||
trackers = sorted(counts.values(), key=lambda x: (-int(x.get('count') or 0), str(x.get('domain') or '')))
|
||||
return {'hashes': by_hash, 'trackers': trackers, 'errors': errors, 'scanned': len(hashes)}
|
||||
|
||||
def _safe_tracker_call(c: ScgiRtorrentClient, method: str, target: str, default=None):
|
||||
try:
|
||||
return c.call(method, target)
|
||||
|
||||
Reference in New Issue
Block a user