queue_stopped #3
2
.gitignore
vendored
2
.gitignore
vendored
@@ -34,6 +34,8 @@ storage/*
|
|||||||
*.sqlite3-shm
|
*.sqlite3-shm
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
data/*
|
data/*
|
||||||
|
!data/tracker_favicons
|
||||||
|
data/tracker_favicons/*.ico
|
||||||
logs/*
|
logs/*
|
||||||
|
|
||||||
todo.txt
|
todo.txt
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from pathlib import Path
|
|||||||
|
|
||||||
from ..config import BASE_DIR, USE_OFFLINE_LIBS
|
from ..config import BASE_DIR, USE_OFFLINE_LIBS
|
||||||
|
|
||||||
# Notatka: jeden manifest utrzymuje spójne adresy CDN i ścieżki lokalne dla trybu offline.
|
|
||||||
LIBS_STATIC_DIR = "libs"
|
LIBS_STATIC_DIR = "libs"
|
||||||
LIBS_DIR = BASE_DIR / "pytorrent" / "static" / LIBS_STATIC_DIR
|
LIBS_DIR = BASE_DIR / "pytorrent" / "static" / LIBS_STATIC_DIR
|
||||||
BOOTSTRAP_VERSION = "5.3.3"
|
BOOTSTRAP_VERSION = "5.3.3"
|
||||||
@@ -84,7 +83,6 @@ def required_offline_paths() -> list[Path]:
|
|||||||
|
|
||||||
def missing_offline_paths() -> list[Path]:
|
def missing_offline_paths() -> list[Path]:
|
||||||
missing = [path for path in required_offline_paths() if not path.is_file() or path.stat().st_size <= 0]
|
missing = [path for path in required_offline_paths() if not path.is_file() or path.stat().st_size <= 0]
|
||||||
# Notatka: sprawdzane są też zasoby referencjonowane przez CSS, np. fonty ikon i pliki flag.
|
|
||||||
required_dirs = [
|
required_dirs = [
|
||||||
LIBS_DIR / f"fontawesome/{FONTAWESOME_VERSION}/webfonts",
|
LIBS_DIR / f"fontawesome/{FONTAWESOME_VERSION}/webfonts",
|
||||||
LIBS_DIR / f"flag-icons/{FLAG_ICONS_VERSION}/flags/4x3",
|
LIBS_DIR / f"flag-icons/{FLAG_ICONS_VERSION}/flags/4x3",
|
||||||
@@ -97,7 +95,6 @@ def missing_offline_paths() -> list[Path]:
|
|||||||
|
|
||||||
|
|
||||||
def validate_offline_assets() -> None:
|
def validate_offline_assets() -> None:
|
||||||
# Notatka: aplikacja zatrzymuje start, gdy tryb offline jest aktywny, a pliki nie są zainstalowane.
|
|
||||||
if not USE_OFFLINE_LIBS:
|
if not USE_OFFLINE_LIBS:
|
||||||
return
|
return
|
||||||
missing = missing_offline_paths()
|
missing = missing_offline_paths()
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ FONT_FAMILIES = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def bootstrap_css_url(theme: str | None) -> str:
|
def bootstrap_css_url(theme: str | None) -> str:
|
||||||
# Notatka: zachowana funkcja zwraca aktualny adres motywu, ale źródło wybiera konfiguracja offline.
|
|
||||||
from .frontend_assets import bootstrap_css_path
|
from .frontend_assets import bootstrap_css_path
|
||||||
|
|
||||||
return bootstrap_css_path(theme)
|
return bootstrap_css_path(theme)
|
||||||
@@ -186,10 +185,8 @@ def save_preferences(data: dict, user_id: int | None = None):
|
|||||||
if port_check_enabled is not None:
|
if port_check_enabled is not None:
|
||||||
conn.execute("UPDATE user_preferences SET port_check_enabled=?, updated_at=? WHERE user_id=?", (1 if port_check_enabled else 0, now, user_id))
|
conn.execute("UPDATE user_preferences SET port_check_enabled=?, updated_at=? WHERE user_id=?", (1 if port_check_enabled else 0, now, user_id))
|
||||||
if title_speed_enabled is not 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))
|
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:
|
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))
|
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:
|
if footer_items_json is not None:
|
||||||
# Note: Store only JSON objects so footer visibility can be extended without schema churn.
|
# Note: Store only JSON objects so footer visibility can be extended without schema churn.
|
||||||
|
|||||||
@@ -169,10 +169,17 @@ def favicon_public_url(domain: str, enabled: bool = True, create: bool = False)
|
|||||||
favicon_path(clean, enabled=True)
|
favicon_path(clean, enabled=True)
|
||||||
cached = _cached_favicon(clean)
|
cached = _cached_favicon(clean)
|
||||||
now = _now_epoch()
|
now = _now_epoch()
|
||||||
if not cached or now - float(cached.get("updated_epoch") or 0) >= FAVICON_CACHE_TTL_SECONDS:
|
path = None
|
||||||
return ""
|
if cached and now - float(cached.get("updated_epoch") or 0) < FAVICON_CACHE_TTL_SECONDS:
|
||||||
path = Path(str(cached.get("file_path") or ""))
|
cached_path = Path(str(cached.get("file_path") or ""))
|
||||||
if not path.exists() or not path.is_file():
|
if cached_path.exists() and cached_path.is_file():
|
||||||
|
path = cached_path
|
||||||
|
if path is None:
|
||||||
|
# Note: Existing symlinked .ico files are still linked directly even when the DB favicon row is missing or stale.
|
||||||
|
direct_path = FAVICON_DIR / f"{_safe_filename(clean)}.ico"
|
||||||
|
if direct_path.exists() and direct_path.is_file():
|
||||||
|
path = direct_path
|
||||||
|
if path is None:
|
||||||
return ""
|
return ""
|
||||||
try:
|
try:
|
||||||
rel = path.resolve().relative_to(FAVICON_DIR.resolve())
|
rel = path.resolve().relative_to(FAVICON_DIR.resolve())
|
||||||
|
|||||||
@@ -239,8 +239,9 @@
|
|||||||
function trackerFavicon(tracker){
|
function trackerFavicon(tracker){
|
||||||
const domain=typeof tracker==='string'?tracker:(tracker?.domain||'');
|
const domain=typeof tracker==='string'?tracker:(tracker?.domain||'');
|
||||||
if(!trackerFaviconsEnabled || !domain) return '<i class="fa-solid fa-bullseye"></i>';
|
if(!trackerFaviconsEnabled || !domain) return '<i class="fa-solid fa-bullseye"></i>';
|
||||||
// Note: Cached favicons are served from the static/tracker_favicons symlink; the API path is only a one-time cache warmer fallback.
|
const safeName=String(domain).toLowerCase().replace(/[^a-z0-9_.-]+/g,'_').replace(/^[._]+|[._]+$/g,'')||'tracker';
|
||||||
const src=(typeof tracker==='object' && tracker?.favicon_url) ? tracker.favicon_url : `/api/trackers/favicon/${encodeURIComponent(domain)}`;
|
// Note: Tracker favicon links are direct static URLs matching the tracker_favicons symlink.
|
||||||
|
const src=(typeof tracker==='object' && tracker?.favicon_url) ? tracker.favicon_url : `/static/tracker_favicons/${encodeURIComponent(safeName)}.ico`;
|
||||||
return `<img class="tracker-favicon" src="${esc(src)}" alt="" loading="lazy" onerror="this.classList.add('d-none')"><i class="fa-solid fa-bullseye tracker-fallback-icon"></i>`;
|
return `<img class="tracker-favicon" src="${esc(src)}" alt="" loading="lazy" onerror="this.classList.add('d-none')"><i class="fa-solid fa-bullseye tracker-fallback-icon"></i>`;
|
||||||
}
|
}
|
||||||
function trackerFilterPlaceholder(){
|
function trackerFilterPlaceholder(){
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
../../data/tracker_favicons
|
|
||||||
1
pytorrent/static/tracker_favicons
Symbolic link
1
pytorrent/static/tracker_favicons
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../data/tracker_favicons
|
||||||
Reference in New Issue
Block a user