Commit Graph
44 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 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 db85ef0547 docs(rust): mark the Unicode case table as frozen and generated
The table is pinned to Unicode 15.0.0 for matching parity and its generator
is no longer in the repository. Say so in the header and mark the file as
generated for linguist.
2026-10-18 22:02:52 +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 d63d95db63 fix(lyrics): restore QQ Music search after the desktop CGI broke
The desktop search CGI now answers HTTP 500 for every query, so QQ Music
never reached its lyrics. Search through the suggestion box instead, rank
title and artist matches, and check the length of the best few through
the song detail CGI so a live or remixed cut is skipped. The lyrics CGI
is unchanged and still answers.
2026-10-18 20:50:36 +07:00
zarzet 56ad01ec2c feat(lyrics): add lrc.red and BetterLyrics, split SpotiFLAC-Mobile Lyrics
Add two keyless providers that serve Apple Music TTML: lrc.red, matched by
recording, and BetterLyrics, which answers songs it has already cached.
TTML is read into the first-party Apple line structure, so vocal sides,
backing groups, word timing, translations and romanization share one eLRC
formatter. The XML depth guard moves to core so both readers use it.

SpotiFLAC-Mobile Lyrics becomes its own provider instead of a fallback
inside Apple Music. New defaults are Apple Music, lrc.red, SpotiFLAC-Mobile
Lyrics, LyricsPlus and LRCLIB. Migration 13 moves untouched defaults to the
new list and keeps the former fallback right after Apple Music in custom
orders. Replace the dead LyricsPlus Workers mirror.
2026-10-18 19:24:31 +07:00
zarzet e049604b24 feat(lyrics): add SpotiFLAC fallback for Apple Music outages 2026-10-14 20:20:44 +07:00
zarzet ac8523d789 feat(metadata): add per-field download tag separators 2026-10-12 12:11:06 +07:00
zarzet 02901a0c91 feat(lyrics): use Apple Music as the default lyrics provider 2026-10-09 18:08:03 +07:00
zarzet 4b07607884 test(progress): keep batch tests after production helpers 2026-10-08 17:53:55 +07:00
zarzet 2ded1cff68 perf(downloads): batch queue cancellation and progress cleanup 2026-10-08 08:47:47 +07:00
zarzet c67d2a2dd2 perf(downloads): batch cancellation resets for retry 2026-10-08 06:55:26 +07:00
zarzet dc1e7c1273 fix(lyrics): treat placeholder zero timestamps as unsynced 2026-10-08 00:49:42 +07:00
zarzet fb21c5c390 perf(audio): analyze AutoMix grids and spectral cutoffs natively
Move PCM beat analysis and spectrum cutoff estimation to cancellable Rust workers with the existing algorithms and isolated Dart fallbacks. Keep visual rendering and playback behavior unchanged.
2026-10-06 23:28:01 +07:00
zarzet d693e1a479 perf(theme): extract cover palettes with native quantization
Port the pinned Material quantizer and Dart RNG compatibility to Rust while keeping Material Score and final color schemes in Dart. Include exact palette fixtures, licenses, and Kotlin worker ABI coverage.
2026-10-06 23:27:38 +07:00
zarzet 5019851903 perf(hires): analyze every channel in one audio decode pass 2026-10-04 01:41:00 +07:00
zarzet 3b553b38a3 perf(cover): avoid copying originals rejected by cache budget 2026-10-04 01:41:00 +07:00
zarzet 644d035661 perf(isrc): bound retained indexes and stream FLAC comments 2026-10-04 01:40:58 +07:00
zarzet 4eef5542be fix(metadata): recognize featured artist credits consistently
Handle feature and featured separators across display, Android finalization and Rust tag writing. Split conjunction credits only when separate artist IDs confirm them, and offer a picker for multi-artist navigation.
2026-10-02 18:10:00 +07:00
zarzet 41340a4a21 feat(metadata): write primary-artist tags in native writers
Native tag writers accept artist_tag_mode=primary and keep only the first credited artist in ARTIST and ALBUMARTIST for every format: FLAC download embedding, FLAC/Opus edits, and the ID3, MP4, APE and RIFF writers. The Android finalizer's FFmpeg fallback applies the same rule.

Rust and Kotlin share one separator set (comma, semicolon, ampersand, a spaced x, feat/ft/featuring/with) and test the same fixtures. The Go-parity split_vorbis pattern is unchanged. Refs #618.
2026-10-01 16:12:44 +07:00
zarzet 9051f37cf5 perf(library): reduce scan allocations and keep SAF workers busy 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 6decce0f8b fix(lyrics): keep V3 collaborations on the primary side 2026-09-27 15:26:11 +07:00
zarzet 7e32951eac fix(lyrics): preserve group alignment in eLRC fallback 2026-09-27 11:39:33 +07:00
zarzet 34cda0a802 fix(lyrics): preserve Apple vocal sides for group parts 2026-09-27 04:06:15 +07:00
zarzet 95e0e3197d feat(lyrics): support multiple vocalists and aligned lyrics 2026-09-26 14:50:05 +07:00
zarzet 1bae8156bb fix: avoid inferring Hi-Res upsampling from bandwidth alone 2026-09-25 19:57:44 +07:00
zarzet 9d530cea9e feat(metadata): remove ReplayGain from individual and selected tracks 2026-09-25 17:12:21 +07:00
zarzet b7d4ceb7e1 Merge pull request #589 with lossless replacement safeguards
Keep automatic Hi-Res replacement off by default. Require exact full-duration PCM equivalence across every channel before and after processing. Retain the original through publication and history persistence, restore it on failure or cancellation, and bound WAV format parsing.

Validated with 30 Rust tests, 46 Dart tests, clean analyzer/Clippy checks, Android ARM64 and iOS builds, and iOS simulator startup.
2026-09-25 01:02:39 +07:00
Bartolo 14ce4bc868 Add Hi-Res authenticity check
Introduce a per-file Hi-Res authenticity checker and optional auto re-download for flagged fake Hi-Res downloads. Adds Rust checker modules (fft, evidence, FLAC/WAV decoding, tests) and a mobile API (check_hires_authenticity). Wire platform bindings for Android/iOS and PlatformBridge. Add Dart HiResCheckService, UI HiResCheckCard, settings toggle (redownloadFakeHiRes) and integration in the download pipeline to quarantine and replace suspicious files with LOSSLESS when safe. Adds claxon FLAC dependency.
2026-09-23 22:29:15 +02:00
zarzet ba44bafa92 fix: sync lyric romanization and clear stale selection menus
Preserve romanization and translation metadata through Rust exports and cache, match rounded line times, and retain word endings. Highlight romanization with the main lyric clock in Material and Mornye.

Clear selection overlay ownership safely when collection screens are disposed. Add parser, rendering, cache and overlay lifecycle regression coverage.
2026-09-23 12:42:08 +07:00
zarzet 31ef7c7c88 perf(rust): stream discarded FLAC artwork during replacement
Consume superseded picture blocks with a bounded 64 KiB buffer instead of retaining them in memory. Preserve other blocks, audio, EOF behavior and metadata limits while checking cancellation between reads.

Cover rewrite/enrichment output parity, retained artwork, truncated input, cancellation before writes and block-count limits.
2026-09-20 17:21:11 +07:00
zarzet 3b662cc114 perf(rust): reduce peak allocations when resizing cover artwork
Write JPEG resize output directly into RGB and release the original decoded image before encoding. Preserve PNG alpha handling and existing image quality.

Verified byte-identical output for 336 image fixtures and lower isolated peak heap allocation.
2026-09-20 17:20:52 +07:00
zarzet 96b037bb34 perf(rust): skip audio rewrites for identical FLAC metadata 2026-09-17 02:59:14 +07:00
zarzet e063e6d5da feat(metadata): display saved ReplayGain values and refresh after analysis 2026-09-15 21:44:59 +07:00
zarzet 097af1532f fix(metadata): read tags and covers from MP4 files named opus 2026-09-15 21:44:45 +07:00
zarzet aa99726439 feat(backend): migrate production backend to Rust
Replace the Go backend with the Rust workspace and route Android/iOS through
UniFFI bindings. Include the migrated extension runtime, network, providers,
media metadata, downloads and library operations, with version 5.0.0+147.

Remove Go sources, adapters, native build selection and CI dependencies.
Build Rust unconditionally and lock the iOS Rust pod using a relative path.
Retain legacy source and migration evidence in a local ignored archive.

Validation: Android Kotlin compile and 53 native tests; Swift Rust-branch
equivalence and syntax; CocoaPods install; workflow, shell, Ruby, plist and
diff checks. Reuse the preceding 100 Rust tests, fmt/Clippy and five-ABI
build checkpoint; no new APK or iOS application build for this cleanup.

Known follow-up: URL/URLSearchParams globals are missing from the Rust JS
runtime. A controlled extension replay confirms a URL-resolution regression;
this commit does not fix that runtime gap.
2026-09-14 22:58:27 +07:00