Commit Graph
55 Commits
Author SHA1 Message Date
zarzet c10c0ab5f5 docs(rust): add module docs to large modules without one
More than half of the crate files had no module doc, so a contributor had to
read a large file top to bottom to learn what it owns. Every file over 300
lines now states its responsibility in one or two lines.
2026-10-19 00:56:51 +07:00
zarzet affa63142c refactor(rust): rename mod.rs modules to the foo.rs + foo/ layout
Six modules used mod.rs while the rest of the workspace uses foo.rs next to a foo/ directory. Pure renames, no content change.
2026-10-19 00:56:21 +07:00
zarzet 190801c398 refactor(core): add fs::write_atomic and use it for the atomic file writes
Six writers repeated the same publication sequence: create a temporary file
in the target directory, write it, sync_all, optionally run a cancellation
check, then NamedTempFile::persist over the target. `core::fs::write_atomic`
runs exactly that order and optionally fsyncs the directory afterwards
(best effort, as extension storage did).

Each site keeps its temporary-file prefix (`.spotiflac-` for extension
storage, `.spotiflac-m3u-` for M3U export, tempfile's `.tmp` default,
exposed as `DEFAULT_TEMP_PREFIX`, for the registry cache, lyrics cache and
both network cover writers), its check placement between fsync and rename,
and its error text: `WriteAtomicError` displays a persist failure with
tempfile's own message, and converts to the underlying io::Error where the
site previously used `PersistError::error`.

Not converted, because their publication differs: playlist staging and
collection snapshots use persist_noclobber (collections also skips fsync),
the backup restore stages into a temporary directory, and the extension
package download maps each step to its own error context.

spotiflac-core now depends on the workspace `tempfile` already used by the
other crates; the lockfile only gains that edge.
2026-10-19 00:36:29 +07:00
zarzet f7a15cbd43 refactor(rust): consolidate small duplicate helpers
Keep one copy of helpers that were implemented several times, each proven
equivalent to the copies it replaces by a test that runs the removed
implementation (kept inline in the test module) against the survivor:

- Go-style lexical path clean: new `core::fs::clean`, used by
  `isrc::native_files` and re-exported as `extensions::files::clean`.
- Dart `String.trim` whitespace: `path_keys::dart_whitespace` and
  `dart_trim` now back the lyrics parser's explicit character list and the
  playlist importer's private `trim`; the test compares every Unicode
  scalar value.
- `normalized()` (Dart trim then simple lowercase): the native backup
  importer uses the identical `native_library::normalized`.
- Lowercase hex encoding: `spotiflac_core::lower_hex` replaces the per-byte
  `format!` collectors in data_jobs and network_tags, the signed-session
  `hex` and the `hex` arm of `binary::encode`; checked for all 256 bytes.
- ID3 syncsafe decode: the tag writer's `size` reuses the reader's
  `tags::id3::syncsafe`; checked across the 0x7F/0x80 boundaries per byte.
- Bounded body reads: `HttpStream::read_limited` replaces the read-to-limit
  loops of the lyrics HTTP client, the resolver HTTP client and the
  extension registry reader. Callers keep their buffer sizes (16 KiB,
  16 KiB, 64 KiB), their own error mapping and their messages; a loopback
  test compares it with the previous loop at and around the limit.

The mobile ID3 `synchsafe` is not merged: it validates the length and the
high bits and returns `None`, unlike the core decoder.
2026-10-19 00:36:29 +07:00
zarzet 088bd875dd refactor(core): move the lenient Go-style JSON decoder to core::json
The case-folding, null-tolerant struct decoder (`go_deserialize!` with
`field_name`, `KeySeed`, `Update` and `Field`) lived in `core::lyrics::json`
although downloads, metadata and resolver payloads depend on it. Move it
unchanged to a neutral `core::json` module and name the macro after its
behavior, `lenient_deserialize!`. All users are inside spotiflac-core, so no
compatibility re-export is kept. The lyrics module keeps only its
surrogate-normalizing `decode` helper.

`field_name` is now public so the extension registry decoder reuses it
instead of its own copy of the same folding. Its matcher folds the key once
and compares ASCII case-insensitively against the wire name, which a new
test checks against the previous matcher on edge inputs (empty, long s,
Kelvin sign, combining marks, non-ASCII letters, BOM, NUL).

The visitor's `expecting` text is unchanged so decode errors stay
byte-identical.
2026-10-19 00:36:29 +07:00
zarzet 12d422afa3 refactor(extensions): split manager.rs into install, lifecycle, info and tests modules
manager.rs mixed package install/upgrade/remove, the runtime lifecycle,
listing/info helpers and tests in one 1245-line file (audit RS-14). Move
each group into a sibling module under manager/, next to the existing
downloads, metadata, postprocess and providers modules:

- manager/install.rs: install, install_or_upgrade, stage, candidate,
  install_package, remove, upgrade, upgrade_package, check_upgrade and the
  install-only helpers error_from_rollback and remove_directory.
- manager/lifecycle.rs: prepare_with_lease, validate, lifecycle,
  lifecycle_with_lease, teardown_runtime, teardown, failed, ready and
  ready_with_lease.
- manager/info.rs: load_all, load_directory, installed,
  installed_versions, provider_ids, summary, asset and information.
- manager/tests.rs: the existing unit tests.

Method bodies are moved verbatim (whitespace-normalized token streams
match the base), so the engine, status and download pool lock order is
unchanged. Methods called from other manager submodules become
pub(super) because method privacy follows the module of the impl block.
No public path changes.
2026-10-19 00:00:04 +07:00
zarzet 380b5cb872 refactor(extensions): split backend/downloads.rs helpers into submodules and move its sibling tests
Move the prepared-download cache, metadata mapping, path building,
result matching and response envelopes into backend/downloads/ siblings.
The three #[path] test files become plain test modules next to them, so
test names are unchanged. download_attempt and the orchestration stay in
downloads.rs.
2026-10-18 23:40:19 +07:00
zarzet 1b2efc76cc refactor(extensions): split runtime.rs into control, vm and tests modules
Move Control and ControlWait, the worker loop and QuickJS VM, and the
unit tests into runtime/ siblings. ExtensionRuntime and the crate::runtime
paths used by the host modules are unchanged.
2026-10-18 23:36:13 +07:00
zarzet 3f904004ff refactor(rust): move inline tests and the test-only hires decoder into submodules
Inline test modules of tags/write.rs, lyrics/payloads.rs and
extensions/src/files.rs become sibling tests.rs files, and the
cfg(test) reference decoder of media/hires moves into hires/reference.rs.
The artist-mode helpers of tags/write.rs move to write/artists.rs.
No behavior or public API change; the supplement_tests module of
payloads.rs is now named tests.
2026-10-18 23:28:19 +07:00
zarzet 2debaa5363 refactor(providers): remove the unused Deezer catalogue search
DeezerClient::search_all was reachable only through
Backend::search_deezer, which had no production caller: no UniFFI
export, platform channel or extension API wraps it, and no built-in
Deezer catalogue search is planned. Remove the chain together with the
items that existed only for it: the core search result types, the Deezer
search page types and their API error wrappers, the cached Search value,
and the latency test that exercised the search fan-out.

The Search cache bucket stays because the coalesced Deezer lookups use
it. The shared error context helper moves into the deezer module now
that search.rs is gone. searchDeezerByISRC is a separate, live method
and is untouched.
2026-10-18 22:22:57 +07:00
zarzet d60baee4a7 refactor(rust): restrict test-only functions
check_from_deezer and the lyrics cache_size accessor are only called by
unit tests in their own crate, so compile them only for tests and narrow
them to the crate.

call_download is only used by the extensions integration tests, which
can only reach public items; the download manager goes through
call_download_provider. Keep it public but hide it from the docs.

RequestLease::wait_cancelled stays public: the extensions crate's unit
tests call it across the crate boundary and a later coalescer change is
expected to use it.
2026-10-18 22:22:57 +07:00
zarzet ef5fa1cb46 perf(lyrics): compile credit regexes once
with_credits runs for every parse_lyrics data job and compiled up to
seven regexes per call. Hoist them into LazyLock statics next to the
other lyrics patterns, with the same pattern text, so each is compiled
once per process. Every other regex in the workspace is already static.

storage_failure also lowercased the error message once per probe on the
download failure path; lowercase it once and reuse it.
2026-10-18 22:22:57 +07:00
zarzet 5dbf9e6c29 refactor(network): drop the ignored insecure_tls parameter inside Rust
NetworkService::set_network_compatibility_options ignored the
insecure_tls flag by design because TLS verification is never disabled.
Remove the parameter from the network service and the extension
environment so the dead flag no longer travels through internal layers.

The UniFFI method keeps its signature: Kotlin and Swift still pass the
flag, and the mobile binding accepts and ignores it until the binding
and channel cleanup lands separately.
2026-10-18 22:22:57 +07:00
zarzet 43f7257d3d docs(rust): say that Go is the retired backend in crate docs
Rust is the only backend and routes the app on Android and iOS. Rewrite
the migration-era crate, script and comment wording that implied Go still
runs, and note in each crate doc that Go mentions describe the retired
backend whose observable behavior remains a compatibility contract.
2026-10-18 22:02:52 +07:00
zarzet 5254760bbd build(rust): drop unused direct dependencies and centralize pins
Remove tokio from spotiflac-extensions and thiserror and the direct hyper
entry from spotiflac-network (hyper stays resolved through hyper-util).
Move roxmltree, rusqlite and libc into workspace.dependencies. libc already
resolves to 0.2.190, so the workspace pin uses that exact version. No
resolved version or enabled feature changes.
2026-10-18 22:02:35 +07:00
zarzet 186c66aeb8 fix(lyrics): refresh LRC sidecars in their actual SAF folder
Resolve opaque SAF document ancestry before writing adjacent lyrics. Propagate failed requested LRC writes and cover native word-synced refresh in regression tests.
2026-10-13 19:38:00 +07:00
zarzet ac8523d789 feat(metadata): add per-field download tag separators 2026-10-12 12:11:06 +07:00
zarzet 879e86b20e test(rust): remove TCP timing dependence from chunked fixture 2026-10-11 16:14:01 +07:00
zarzet 08032f1877 chore(rust): update toolchain and dependencies with preserved vendor sources 2026-10-11 14:33:45 +07:00
zarzet 1b9c35f322 fix(downloads): preserve browser verification and queues across restarts 2026-10-09 18:07:49 +07:00
zarzet 461d5325d8 fix(extensions): retain packages with unreadable settings
Keep installed extensions visible with an error and disable execution when their encrypted settings cannot be read. Preserve settings and credentials so restoring the correct key remains possible.

Validation: 80 extension Rust tests passed (2 ignored), Clippy and formatting; Android profile integration verified repository initialization and preservation of encrypted settings.
2026-10-08 20:29:57 +07:00
zarzet 6325c4769c fix(downloads): preserve actual provider audio format 2026-10-08 01:50:36 +07:00
zarzet dc1e7c1273 fix(lyrics): treat placeholder zero timestamps as unsynced 2026-10-08 00:49:42 +07:00
zarzet 7f7e9c0e39 perf(logging): batch UI updates and gate native log copies 2026-10-04 01:41:38 +07:00
zarzet c69f2cef9f perf(library): scan SAF CUE audio through live descriptors 2026-10-04 01:41:35 +07:00
zarzet 644d035661 perf(isrc): bound retained indexes and stream FLAC comments 2026-10-04 01:40:58 +07:00
zarzet 3bc12d4483 perf(extensions): pass metadata values without internal JSON round trips 2026-10-04 01:40:57 +07:00
zarzet 70f8bd7f35 feat(extensions): support personal account forms and direct downloads 2026-10-02 19:21:58 +07:00
zarzet fe36f08918 feat(backend): hold library scans in place while paused
Path and SAF scans wait at their next file checkpoint while paused and keep their position in memory. Cancel, backend shutdown and released request leases still end a paused scan.
2026-10-01 21:00:42 +07:00
zarzet 7ef0a1875e perf(library): stat each walked entry once
The direct-path walk re-validated every path component and stat'ed each file twice more. Children of an already validated directory now take one lstat of their own name, which still rejects symlinks and special files. Tag reads may use up to six workers on slow storage; artwork decoding stays serialized.

Tests cover walk order, size and modification time, and symlink rejection; they pass on the previous walk as well.
2026-10-01 19:06:25 +07:00
zarzet 44b7ceb8e8 perf(download): reuse connections across probes and retries 2026-09-30 23:39:22 +07:00
zarzet fe88048db2 refactor(backend): retire unused native bridge entry points 2026-09-30 21:47:15 +07:00
zarzet 88508c806a perf(library): reuse bounded artwork thumbnails during scans 2026-09-30 20:57:59 +07:00
zarzet 608675a255 feat(player): add direct USB audio and DSD transport 2026-09-27 15:26:10 +07:00
zarzet 7d7716cd6f feat(albums): show formatted editorial descriptions 2026-09-26 03:50:37 +07:00
zarzet 1d53609b51 fix(android): avoid blocked statx probes on legacy ARM32 devices
Vendor the pinned Rustix package with an Android-only weak libc lookup so missing statx returns ENOSYS and uses the existing stat64 fallback. Add an isolated Android smoke test with a hidden symbol and a seccomp filter that kills raw statx probes.

Verified 177 workspace tests, host Clippy, and ARM64 Android sandbox smoke test. ARM32 cross-target Clippy passes with the existing needless_return warning in binary.rs exempted; physical Android 10 ARM32 validation remains required.
2026-09-25 17:24:54 +07:00
zarzet 9d530cea9e feat(metadata): remove ReplayGain from individual and selected tracks 2026-09-25 17:12:21 +07:00
zarzet abb8dc4a42 feat(concerts): show artwork-themed event details in app 2026-09-25 12:00:28 +07:00
zarzet a9898e8b30 feat(artists): show provider-neutral upcoming concerts 2026-09-25 10:58:19 +07:00
zarzet c87c9ff848 feat(library): add Mornye browsing and contextual compact navigation
Add category browsing, a two-column Recently Added grid, and a dedicated download queue. Keep compact navigation icons and their animations aligned with the active tab.

Keep profile avatars circular and report lyric enrichment outcomes accurately, preserving existing lyrics and supporting instrumental markers.
2026-09-21 23:02:52 +07:00
zarzet ace42351ad perf(extensions): avoid copying unwrapped Base64 input
Decode borrowed input directly when it contains no CR/LF, retaining normalization for wrapped input. Verify trailing-bit compatibility, malformed-input errors, Unicode rejection and large payload parity.
2026-09-20 17:21:11 +07:00
zarzet cb5ce3df62 perf(metadata): move normalized Rust values into response envelopes
Move normalized maps and track arrays into response envelopes instead of serializing them into another Value tree. Cover JSON parity, Unicode, track numbering and cancellation.
2026-09-20 17:21:01 +07:00
zarzet dc25370350 feat(artists): add branded headers and incremental release loading 2026-09-20 11:46:16 +07:00
zarzet 054041e40d perf(extensions): collect unreachable JavaScript cycles after idle bursts 2026-09-17 18:27:46 +07:00
zarzet 26a97b44b9 perf(extensions): share retained source text across manager and VMs 2026-09-17 18:20:34 +07:00
zarzet e984c1ddba perf(extensions): reuse owned UTF-8 buffers at text boundaries 2026-09-17 18:20:27 +07:00
zarzet a9a395b9f8 perf(metadata): preserve typed values across the Rust extension bridge 2026-09-17 18:07:56 +07:00
zarzet 2c4e391643 perf(extensions): validate JSON without allocating argument trees 2026-09-17 18:07:22 +07:00
zarzet 91ef538c87 perf(cue): parse SAF previews without copying audio 2026-09-17 17:38:49 +07:00
zarzet 96b037bb34 perf(rust): skip audio rewrites for identical FLAC metadata 2026-09-17 02:59:14 +07:00