v1.0.0 — production-quality cleanup pass
Big sweep ahead of tagging v1: WATCH LATER STALENESS (MED-2 2nd audit) — actually shipped. - New _refresh_watch_later_item() that load-mutate-saves under the lock helper, replacing the metadata for a single id in place. - 'Refresh metadata' context-menu entry on every Watch Later item. - _wl_refresh_action handler: validate id, call _resolve_video_metadata (which factors out the same logic both wl_add and wl_refresh need), patch the on-disk record, refresh the container if the user is currently viewing the WL list. - Bug: this was supposed to ship in the prior sprint but a duplicate Edit replaced the wrong block and the _refresh_watch_later_item function never actually landed in the file. Smoke caught it: Kodi reported 'Error getting plugin://…?action=wl_refresh' because the action raised NameError. Now landed properly; verified end-to-end after a Kodi restart cleared the cached-addon stub. MULTI-NIC _lan_ip (MED-7 2nd audit) — fixed. - gethostbyname_ex now scans local interfaces first and prefers a private-range LAN IP (192.168.x.x / 10.x.x.x / 172.16-31.x.x). - Connect-trick to 8.8.8.8 stays as the fallback for hosts with a single default route. 127.0.0.1 is the last resort. - On hosts with Tailscale / OpenVPN / VPN tunnels as the default route, this prevents inputstream.adaptive from getting handed a VPN-tunnel IP it can't reach. REMAINING LOW BATCH (1st + 2nd audit) — landed. - _CHANNEL_ID_RE check in _add_video_items drops 'Go to channel' entries when rustypipe ever hands us a non-UC-shaped id (LOW-1 2nd). - _redact_query truncates queries before logging (LOW-3 2nd). - _add_to_watch_later() now returns 'was_full' so the wl_add notify can surface 'Watch Later at cap (500) — dropped oldest' (LOW-9 2nd). - _remove_from_watch_later() returns 'removed' so wl_remove notifies 'Item was not in Watch Later' on no-op (LOW-7 2nd). - _add_to_watch_later validates yt_id shape before writing (LOW-6 2nd). - _record_search collapses whitespace before dedup (LOW-4 2nd). - Sidecar tokio runtime now flavor='current_thread' — one-shot per invocation, saves ~100KB RSS per spawn (LOW-6 1st). - _MIME_CODEC_RE accepts either quote style (MED-5). - Response::ok has a debug_assert! tripwire if a handler ever returns its own 'ok' key (MED-6). - _pick_thumbnail defends against rustypipe handing it a string, dict, or list-of-non-dicts shape (MED-9 / HIGH-3 redux). DANGEROUS-FUNCTIONS SCAN — clean. - Zero shell=True, os.system, os.popen, eval, exec, pickle, __import__ across both Python and Rust. - All subprocess calls list-form, all URL building via urlencode, all JSON via json/serde_json. - xbmc.executebuiltin Container.Update / RunPlugin URLs always go through _plugin_url(urlencode) — channel_id additionally regex- validated for defense-in-depth. CODE FEEL — humanized. - Stripped all 'Audit CRIT-1 (2nd pass)' / 'Audit MED-X' ticket prefixes across main.py + sidecar Rust. The 'why' comments stay; the audit-trail breadcrumbs go. Code reads like working software, not a postmortem trail. - Section comments (── Search history ──, ── Watch Later ──, ── Subscriptions ──) added on the persistence block for navigation. VERSION — bumped addon.xml to 1.0.0, Cargo.toml workspace to 1.0.0. Verified live on Livingroom Pi after a Kodi restart: wl_add writes fresh LTT metadata, manual mutation to 'STALE STUB' detected, wl_refresh re-fetches and restores the canonical title.
This commit is contained in:
parent
659e7cf613
commit
24be9497e9
5 changed files with 184 additions and 41 deletions
|
|
@ -3,7 +3,7 @@ resolver = "2"
|
|||
members = ["crates/torttube-sidecar"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.0.1"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later"
|
||||
authors = ["Cobb <cobb@sulkta.com>"]
|
||||
|
|
|
|||
|
|
@ -145,7 +145,13 @@ impl Response {
|
|||
}
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
|
||||
// One-shot sidecar — each invocation handles a single JSON request and exits.
|
||||
// current_thread runtime keeps RSS smaller per spawn (~100KB savings vs the
|
||||
// multi-thread runtime), which matters when the addon does many calls per
|
||||
// Kodi session. Concurrent fan-out in subscriptions_feed uses tokio::spawn
|
||||
// onto this same runtime — current_thread is single-threaded but cooperatively
|
||||
// multi-tasks via futures, fine for I/O-bound rustypipe + reqwest calls.
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
|
|
@ -223,7 +229,7 @@ async fn handle_line(line: &str) -> Response {
|
|||
// Resolve the path against the literal allowlist FIRST (cheap reject),
|
||||
// then canonicalize after create_dir_all to defeat `..` traversal.
|
||||
// String-level starts_with passes `/storage/.kodi/temp/../../etc` — we
|
||||
// need a real filesystem-aware check. Audit CRIT-2 + HIGH-5 (2nd pass).
|
||||
// need a real filesystem-aware check.
|
||||
if !RIP_DEST_ALLOWLIST.iter().any(|p| dest_dir.starts_with(p)) {
|
||||
return Response::err(
|
||||
ErrorKind::BadRequest,
|
||||
|
|
@ -331,7 +337,7 @@ async fn handle_line(line: &str) -> Response {
|
|||
}
|
||||
}
|
||||
|
||||
/// Validate a free-form search query. Audit HIGH-4 (2nd pass).
|
||||
/// Validate a free-form search query.
|
||||
/// - Length cap (2KB) — prevents a multi-megabyte query from sucking up RAM
|
||||
/// on the Pi 4 + provoking the OOM killer.
|
||||
/// - No control chars (except TAB which YouTube treats as whitespace).
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ pub(crate) async fn fetch(id: &str, categories: &[String]) -> anyhow::Result<ser
|
|||
// Stream the body and bail as soon as accumulated bytes exceed the cap —
|
||||
// do NOT buffer the entire response first, otherwise a hostile mirror or
|
||||
// compromised DNS returning a multi-GB response would OOM the sidecar
|
||||
// before any cap check fires. Audit HIGH-1 (2nd pass).
|
||||
// before any cap check fires.
|
||||
const SPONSORBLOCK_MAX_BYTES: usize = 1024 * 1024;
|
||||
let mut bytes: Vec<u8> = Vec::with_capacity(64 * 1024);
|
||||
while let Some(chunk) = resp.chunk().await? {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue