Commit graph

24 commits

Author SHA1 Message Date
KhooLy
fdf91ceb19 perf(headless-engine): replace clone-and-diff dispatch with dirty tracking
Per the robustness plan's §9 (flagged as the single highest-leverage
perf change in the crate): headless_engine_dispatch_json used to clone
the entire EngineState before the reducer ran, clone it again after,
then StatePatch::diff did a deep PartialEq walk across all 16 domains
and cloned each changed one a third time — cost scaled with everything
the user has ever loaded (full catalogs, episode lists), not with what
the action actually touched.

Introduces Tracked<T> (src/headless_engine/state.rs): wraps each
EngineState domain field so any mutable access — a field write, a
whole-value replacement via DerefMut, a method call taking &mut —
flips a dirty bit automatically. Serialize/Deserialize delegate
straight to the wrapped value, so the wire format is byte-for-byte
unchanged (verified by the golden wire fixtures added in the prior
commit, which pass unmodified). EngineState::diff_dirty() replaces
StatePatch::diff(before, after): it clones only domains whose dirty
bit is set and clears it, so both the pre-dispatch full clone and the
post-dispatch PartialEq walk are gone entirely. The ~16 call sites that
did whole-domain replacement (`engine.state.detail = DetailState {
...}`) needed `*engine.state.detail = ...` instead — the compiler
catches any site the migration missed as a type error, so there's no
way for this rewrite to silently skip a domain the way a hand-added
mark_dirty() call could.

Also fixes headless_engine_snapshot_json, which used to serialize
while still holding the engines mutex; it now clones the state and
drops the lock before calling serde_json::to_string.

Adds benches/ (criterion, `--features bench` dev-only surface via a
new bench_targets re-export module mirroring the existing fuzz_targets
pattern): headless_engine_dispatch benches a NavigationRequested
dispatch against a 500-stream detail state (a domain the action
doesn't touch, so the win is directly visible), and stream_ranking
benches player_source_sidebar_plan_json grouping 500 streams. CI gets
a bench-check job compiling and smoke-running both (`--test`) without
paying for full timing runs on every push.
2026-07-07 14:46:52 +03:00
KhooLy
b171adf721 feat(core-error): add CoreError + host-pollable log sink, harden effect completion status
Per the robustness plan's §2/§3: introduce a pub(crate) CoreError enum
(BadInput/NotFound) plus a LogAndDiscard trait converting
Result<T, CoreError> back to the existing Option<T> FFI shims while
recording the failure. Rather than a push callback (no such pattern
exists anywhere in this codebase — checked jni.rs/uniffi.rs/ffi.rs and
the streaming engine, which all use poll-based Arc<Mutex<..>> state
instead), the sink is a bounded ring buffer drained via a new
`core.drainErrorLog` core_invoke method, a UniFFI
`drain_core_error_log_json` export, and a JNI
`drainCoreErrorLogJsonNative` extern — the host polls it (e.g. once per
dispatch tick) and forwards lines to logcat/desktop logs. This follows
the codebase's own established poll-not-push convention instead of
inventing a new one.

Wired into headless_engine's two real FFI boundary functions
(headless_engine_dispatch_json / headless_engine_complete_effect_json)
for both bad JSON and unknown-handle cases, and applied end-to-end
across the player_policy module (6 functions) as the first fully
migrated module, demonstrating the pattern for the remaining ~290
Option<String> functions to pick up incrementally.

Also hardens EffectResultInput per §3: effect_id is now a required
field (previously #[serde(default)] silently produced effect_id: ""
that matched no in-flight effect), and status is a closed EffectStatus
enum (Ok/Error/Cancelled) instead of a bare String — an unrecognized
status string now fails deserialization (and gets logged) instead of
comparing false against every "== \"ok\"" call site. Updated the ~39
`result.status == "ok"` / `!= "ok"` comparisons across headless_engine
to `result.status.is_ok()`; the wire strings themselves are unchanged
(serde's rename_all = "camelCase" already produces "ok"/"error").
2026-07-07 14:35:49 +03:00
KhooLy
2439bb3ba6 test: add golden wire fixtures and a headless-engine dispatch fuzz target
Per the robustness plan's §8: a tests/wire/ directory holds one real
AppAction input per representative action family, each paired with a
checked-in golden DispatchResult captured from an actual dispatch (a
new #[cfg(test)] wire_fixtures_match_golden_dispatch_output test
compares against it, with UPDATE_WIRE_FIXTURES=1 to regenerate when a
change is intentional). Any future camelCase/field drift on the
dispatch/completeEffect wire now fails in this repo instead of surfacing
as a silent Android/desktop regression.

Also add fuzz/fuzz_targets/engine_dispatch.rs, feeding arbitrary bytes
into both headless_engine_dispatch_json and
headless_engine_complete_effect_json against one engine handle — this
is the one path in the crate that runs global engine-mutating logic
without a catch_unwind guard on the desktop call path, so it's the
highest-value fuzz target missing from fuzz/. Exposes the four
headless_engine entry points as `pub` (still unreachable outside the
crate except through the fuzzing-gated `fuzz_targets` re-export module)
following the same pattern already used for parse_manifest and the
content_identity helpers.
2026-07-07 14:28:48 +03:00
KhooLy
e05afb2761 chore(ci): add GitHub Actions workflow and clippy lint policy
Add warn-level unsafe_op_in_unsafe_fn / unwrap_used / expect_used /
indexing_slicing / panic clippy lints (test code exempted via cfg_attr),
per the robustness plan's ratchet-to-deny strategy. Add a CI workflow
covering workspace test/clippy/fmt plus a wasm-only cargo check. Run
`cargo fmt` to clear pre-existing formatting drift so the fmt gate starts
clean, and drop a now-unnecessary `mut` flagged by clippy.
2026-07-07 14:22:15 +03:00
KhooLy
cf701723be fix(player-policy): update stale HLS DV test for segment RPU convert
b0bb52a added real HLS segment-level RPU conversion (fluxa-streaming-engine's
OkHttp interceptor) for P7 streams with a DV decoder, but left behind a test
that still expected the old manifest-only passthrough. Split it into two
cases: decoder present -> hls_rpu_convert, decoder absent -> manifest_handled.
2026-07-07 14:19:13 +03:00
KhooLy
689f024d3d docs: robustness & performance improvement plan from full crate review 2026-07-07 14:05:52 +03:00
KhooLy
f00a09a4ce fix(search-plan): JSON-encode transport-url/genre FFI returns
resolve_transport_url_json and resolve_feed_option_genre_json feed
opt_json() at the FFI boundary, which requires valid JSON — a bare
URL/genre string isn't, so resolveTransportUrl/resolveFeedOptionGenre
would fail from the frontend whenever they had a result. Finishes an
in-progress fix that JSON-encoded the transport-url return but missed
the early-return genre branch, and updates home_ranking.rs's direct
(non-FFI) callers to unwrap the now JSON-encoded string before
embedding it in a json!() literal, since json!() already does its own
string encoding.
2026-07-07 13:58:18 +03:00
KhooLy
ca749a8d00 fix(torrent): prevent stale stop from killing a replayed session
start_torrent_server is a process-wide singleton that fully tears
down and recreates the engine on every play. A stop invoked for an
old session could race a fast replay's start and land after the new
session was already up, silently killing the just-started server and
leaving mpv with a dead socket (MPV_ERROR_LOADING_FAILED / "Loading
failed" on the same torrent). Each server generation is now tagged;
stop is a no-op unless it targets the currently active generation.
2026-07-07 13:14:31 +03:00
KhooLy
4db5701d80 chore(companion): document required OAuth env vars for the web companion server
.env.example lists the Trakt/Simkl/MAL client id/secret vars companion_server
and oauth_proxy read at runtime; ignore fluxa-streaming-engine/.env so a real
copy never gets committed.
2026-07-06 21:56:32 +03:00
KhooLy
d614de3341 fix(torrent): fix startup ordering, add metadata timeouts, track real streamed bytes
Bind the listener only after the torrent session is up (with an 18s init
timeout) instead of before, wrap add_lock/api_add_torrent in a 45s timeout
so a stuck peer/tracker can't hang requests forever, and stop stat-only
polls from triggering ensure_torrent so they can't contend with the real
stream request. Also track bytes actually streamed to the client
(CountingReader) so the preload/stat readiness check reflects playback
progress past a seek, not just bytes written to disk.
2026-07-06 21:56:26 +03:00
KhooLy
6ebd09a101 style: rustfmt fluxa-streaming-engine
No behavioral changes, pure formatting.
2026-07-06 21:51:32 +03:00
KhooLy
ffe5f0478c refactor: value-native args and typed anilist structs for the new methods
The methods added for the desktop migration (detectAnimePlayback,
anilistEntriesToSync, mergeLibraryItemsById, tmdb people helpers) now
take plain nested JSON instead of stringified-JSON-in-JSON, so callers
encode once and the router hands Values straight to the domain
functions. The AniList mapping deserializes into AnilistEntry/
AnilistMedia serde structs instead of spelunking Value paths.
2026-07-06 17:23:39 +03:00
KhooLy
fc95d541f5 feat: anime playback detection, anilist sync mapping, tmdb people image helpers
Ports pure logic the desktop shell was keeping in TypeScript: the anime
playback confidence heuristic, AniList entry classification into
watchlist/completed/dropped/watching buckets with watched keys and
progress records, an id-keyed library item merge, and the TMDB
find/credits URL planning plus credits-to-people-images mapping.
tmdb_language now also accepts the desktop pref values english_us and
tr_tr.
2026-07-06 17:07:46 +03:00
KhooLy
c81f7546ce feat: stremio library mappers for watchlist and watched state
Maps Stremio datastore library items to watchlist entries (content-level,
skipping removed/temp and per-episode rows) and to the watched-ids map
(flaggedWatched/timesWatched, episode _ids already match the id:s:e key
format). Exposed as stremioWatchlistToItems / stremioWatchedToIds for the
desktop shell; Android keeps using the JNI library_state entry points.
2026-07-06 13:59:22 +03:00
KhooLy
a651e4fa38 feat: add unwatchedAhead count to continue watching badges 2026-07-05 00:15:52 +03:00
KhooLy
b0bb52a6a3 feat: update desktop core API support 2026-07-03 16:05:52 +03:00
KhooLy
5fb580f782 feat: update core casting and streaming support 2026-06-27 21:04:20 +03:00
KhooLy
32e98ae2c7 style: restore for-the-badge reference-style badges in README 2026-06-17 20:12:39 +03:00
KhooLy
7e75cd2e40 style: redesign README to match fluxa-desktop's layout
Monochrome flat-square badges, quick-nav line, and a Legal/Related
projects close instead of the colorful for-the-badge shields.
2026-06-17 20:07:36 +03:00
KhooLy
894ca2c81f feat: companion server, FFI cleanup, and pre-release hardening
- Add fluxa-streaming-engine companion server (torrent start/stop,
  ffmpeg transcode/probe, OAuth token exchange) for the web build
- Add SSRF guard restricting transcode/probe url param to http(s)
  loopback hosts, blocking file:// and remote-host abuse via ffmpeg
- Add src/ffi.rs string-routed RPC dispatcher and wasm bindings
- Add headless engine profile module and several FFI surface
  additions (scrobble plans, episode navigation, collections import/export)
- Remove dead runtime::msg/update scaffolding and addon_transport trait
- Remove fluxa_play.rs dev scratch binary (hardcoded scraper, not
  part of the product)
- Reorder dv_rewrite.rs so implementation precedes its test module;
  strip decorative section dividers
- Add docs/, fuzz/ targets, and a rewritten README
- Fix .gitignore to cover fluxa-streaming-engine/target
2026-06-17 20:01:42 +03:00
KhooLy
2f510a6a4b feat: complete core engine, FFI layer, and streaming engine
- Add headless engine with full action/effect dispatch (detail, player,
  home, library, search, discover, calendar, offline, auth, sync)
- Add ffi.rs RPC dispatch table used by UniFFI (iOS) and WASM bindings
- Add wasm.rs binding surface (core_invoke + fluxa_core_version)
- Extend FluxaCore public API with tmdb, intro_segments, external_sync,
  library_state, player_policy, watchlist, and search plan methods
- Fix torrent server race: replace AtomicBool + Mutex dual-state with
  mutex-only source of truth for running status
- Document result_json drain contract in headless engine
- Remove decorative section dividers from core_api.rs (style)
- Remove unused normalize_skip_time function (dead code warning)
- Remove generated doc block from FluxaCore struct (style)
- Clarify app_state comment in ffi.rs (not legacy, used by Android JNI)
2026-06-16 01:44:59 +03:00
KhooLy
b0c0b93b8e feat: add fluxa-streaming-engine as subdirectory 2026-06-09 17:00:14 +03:00
KhooLy
3909fbfbf6
Create LICENSE 2026-06-09 14:26:15 +03:00
KhooLy
504422871f Initial commit 2026-06-09 14:24:41 +03:00