403 Commits
Author SHA1 Message Date
zarzet 01c08322c1 fix(extensions): preserve explicit track metadata 2026-07-24 21:32:13 +07:00
zarzet a41074d271 chore(deps): update Flutter, Go, and native dependencies 2026-07-24 20:37:07 +07:00
zarzet 452a15b652 fix(lyrics): reject mismatched search results 2026-07-24 18:48:05 +07:00
zarzet 685392f073 fix(download): accelerate verified extension resumes 2026-07-23 09:56:39 +07:00
zarzet 0c558fb33b test(backend): use a valid descriptor in fd coverage 2026-07-23 09:56:22 +07:00
zarzet b7e96facbe fix(logging): keep cursors valid after rollover 2026-07-16 10:56:26 +07:00
zarzet 648f36244b perf(go): add runtime metrics and benchmarks 2026-07-16 10:53:26 +07:00
zarzet b5d02da759 perf(dedupe): batch isrc lookups under one lock 2026-07-16 10:48:57 +07:00
zarzet c13fdcc6e1 perf(library): scan flac and m4a in one pass 2026-07-16 10:47:29 +07:00
zarzet 5846dc2416 perf(extensions): invoke goja providers directly 2026-07-16 10:39:03 +07:00
zarzet ef89352d09 perf(memory): drop disposable caches on pressure 2026-07-16 10:32:33 +07:00
zarzet c75139450a fix(extensions): bound file reads 2026-07-16 10:26:43 +07:00
zarzet 07c9cf034f fix(extensions): bound cookie runtime state 2026-07-16 10:24:13 +07:00
zarzet b69f67a786 fix(download): name variants by measured quality 2026-07-16 09:20:35 +07:00
zarzet 0961c09a6d feat(download): preserve quality variants 2026-07-16 08:45:01 +07:00
zarzet bab954c6d9 fix(extensions): harden package and runtime lifecycle 2026-07-15 21:31:16 +07:00
zarzet ed9511b17f refactor(library-scan): reuse applyAudioMetadataToScan for M4A/MP3/APE
The M4A, MP3, and APE scanners hand-copied AudioMetadata into the scan result
field-for-field, identical to the existing applyAudioMetadataToScan helper
(same Date-else-Year fallback). Route them through it. Ogg is left as-is (it
intentionally has no Year fallback) and FLAC uses the separate Metadata type.
2026-07-14 19:00:34 +07:00
zarzet f0afb1382e refactor(backend): drop dead exports/availability path and dedup Go tag writers
Remove the never-called checkAvailability chain end to end (Dart bridge +
provider, Android/iOS MethodChannel handlers, Go exports) and stop restoring
its persistent lookup cache on every cold start; the legacy prefs key is
cleared on next cache reset.

Delete dead Go: romaji.go (test-only), the no-op pre-warm chain
(PreWarmTrackCache/PreWarmCache/…), retired Download stubs, and a batch of
exports with no Dart/Kotlin/Swift/Go caller. Kept GetTrackCacheSize/
ClearTrackIDCache (Settings cache screen), SetAllowedDownloadDirs and
Clear/GetItemProgress (live test seams).

Dedup: single updateFlacVorbis + replaceFlacPictures helper behind the six
FLAC tag writers; merge byte-identical extractCommentFrame/extractLyricsFrame
into extractLangTextFrame.

go build/vet + 237 go tests, flutter analyze + tests green.
2026-07-14 18:25:13 +07:00
zarzet f09c607aab fix(extensions): surface swallowed signed-session verification in provider fallback
Two holes let a provider's verification requirement vanish so the fallback
chain failed outright instead of opening the challenge:

- classifyDownloadErrorType did not recognize 'signed session expired'
  (the exact error ensureSignedSession returns), so an expired session
  failed generically.
- Extensions that catch the needsVerification response internally (e.g.
  qobuz-web's availability search) report a plain 'not available', which
  the fallback loop skipped past silently.

The runtime now remembers when a signed-session call inside the current
script invocation required verification; the provider wrapper consumes
that after checkAvailability/download and tags the failure as
verification_required, regardless of how the script handled it. The
source-extension path also classifies thrown errors now.
2026-07-14 16:34:43 +07:00
zarzet 3833a66bf6 fix(metadata): send Accept-Language on Deezer requests so names follow the app language
Deezer localizes artist and genre names by IP geolocation when no language
is given, so users behind e.g. Arabic-region IPs got Arabic metadata on an
English device (VPN 'fixed' it). The app's display language is now pushed
to the backend and sent as Accept-Language, with an en-US default.

Fixes #480
2026-07-14 15:46:33 +07:00
zarzet 9ac10dd6bb feat(player): ReplayGain/R128 volume normalization for the built-in player
Reads REPLAYGAIN_TRACK_GAIN (album gain fallback) from FLAC/MP3/M4A tags
and converts Opus R128_TRACK_GAIN/R128_ALBUM_GAIN (Q7.8, -23 LUFS) to the
same ReplayGain dB representation on the Go side, then attenuates the
internal player's volume per track. Off by default; toggle lives under
Library > Playback. Opus header output gain is left to the decoder, which
already applies it.

Closes #465
2026-07-14 15:18:42 +07:00
zarzet 4605517d55 fix(download): skip AC-4 repair for non-MP4 decrypt output
Decrypt can emit a raw FLAC stream; the AC-4 repair step fed it to the
MP4 box parser, which failed with a garbage atom type whose control
bytes then broke the unescaped Kotlin error JSON (FormatException in
Dart). Guard the repair call by extension, sniff the box type in Go as
a second layer, and build platform error JSON with JSONObject.quote.
2026-07-14 14:32:29 +07:00
zarzet cdc1ba0a68 feat(net): DoH fallback, uTLS pool flush on cleanup, retry hardening
- Resolve over DNS-over-HTTPS (1.1.1.1/8.8.8.8, cached, SSRF-filtered)
  when the OS resolver fails, on all transports and the uTLS dial path,
  so DNS-level ISP blocking no longer kills the connection outright
- Flush the pooled uTLS h2 conns from CloseIdleConnections so a network
  switch no longer leaves the first bypass request hanging on a dead conn
- Abort retry backoff sleeps on context cancellation
- Cap honored Retry-After at 2 minutes
- Restore the response body consumed by the 403/451 marker scan
2026-07-14 12:49:43 +07:00
zarzet bd9b98b942 fix(go): build with Go 1.26.5 for aligned cgo frames, safe on arm32
Fixes the random gobind crash 'bulkBarrierPreWrite: unaligned arguments'
(golang/go#46893; cgo aligns exported-function argument frames since
1.26.0). The earlier 1.26 rollback (cdc58367) was the arm32 SIGSYS from
probing futex_time64 under seccomp on Android <=10 — fixed in 1.26.3 by
gating time64 syscalls on kernel >= 5.1 instead of probing (verified in
the 1.26.5 runtime source, which names Android 8-10 explicitly).
go directive carries the full patch version so gomobile's synthesized
bind module picks the same toolchain.
2026-07-14 12:20:53 +07:00
zarzet b53146c599 fix(reenrich): keep release identity when the matched track is from another album
An ISRC identifies a recording, not a release, so identifier-first and
search matches could resolve to a compilation of the same song and
overwrite the file's album, cover, track position, and date with the
compilation's. When the file already knows its album and the candidate's
album doesn't match, those release-scoped fields are now preserved;
recording-level fields (title, artist, ISRC, genre) still enrich.
2026-07-14 09:10:14 +07:00
zarzet 3cdeeb2f5f fix(extensions): remove extension data dir on uninstall 2026-07-14 09:10:00 +07:00
zarzet 96eb412fe5 perf(memory): release Go heap and decoded image caches on OS memory pressure 2026-07-14 09:09:42 +07:00
zarzet f983a303fd perf(lyrics): bound the in-memory lyrics cache 2026-07-14 09:09:41 +07:00
zarzet 08007f493c perf(extensions): reuse isolated goja download runtimes via per-extension pool 2026-07-14 09:09:39 +07:00
zarzet e302fac390 perf(ogg): stream tag rewrites instead of buffering every page 2026-07-14 09:09:35 +07:00
zarzet 4d0f1120d2 perf(m4a): stream tag rewrites instead of buffering the whole file 2026-07-14 09:09:32 +07:00
zarzet 9b53d662f7 perf: hoist per-call regex compiles to package level 2026-07-14 09:09:30 +07:00
zarzet c4e266a5b9 refactor!: rename extension store bridge ABI to repo across all layers
Coordinated rename of the nine store-named bridge surfaces: Go
exports (InitExtensionStoreJSON -> InitExtensionRepoJSON etc.),
method-channel strings, the Kotlin and Swift handlers, and the Dart
PlatformBridge methods. Channel strings verified 1:1 across
Dart/Kotlin/Swift; gomobile bindings match the new export names.
Android MediaStore APIs untouched. store_registry_url pref key kept
for existing users. Local Android/iOS builds need the gobackend
AAR/xcframework rebuilt; CI does this in the release workflow.
2026-07-14 09:09:29 +07:00
zarzet 2273d9ba48 feat(download): resume interrupted downloads via Range/If-Range 2026-07-14 09:09:26 +07:00
zarzet 034b505d15 perf(network): enable gzip on the metadata transport 2026-07-14 09:09:25 +07:00
zarzet 122b16133a perf(tls): enable session resumption on the stdlib transports 2026-07-14 09:09:24 +07:00
zarzet fd4348b803 refactor(go): rename extension store files and internals to repo
extension_store.go -> extension_repo.go, exports_store.go ->
exports_repo.go; unexported types/functions/log tag renamed to match
the user-facing repo terminology. The gomobile-exported function
names (InitExtensionStoreJSON etc.) are the bridge ABI called from
Kotlin/Swift and stay unchanged, as does the store_registry_url pref
key that guards existing users' settings.
2026-07-14 09:09:20 +07:00
zarzet 34c7b33368 chore(l10n): finish store -> repo wording sweep
The es/pt/zh base locales still shipped the old Store labels, the
backup-restore hint said 'from the store' in every locale (boutique
in fr, store loanword in ko), and the Go store lookup error said
'not found in store'. All user-visible surfaces now say repo;
translator descriptions in the template updated to match.
2026-07-14 09:09:19 +07:00
zarzet 9b3b586006 fix(network): harden audit findings in transport, uTLS pool, and cover cache
- sharedTransport ResponseHeaderTimeout 45s -> 120s: downloads ride
  this transport and some providers prepare files server-side before
  the first byte; the stall watchdog still bounds dead transfers
- uTLS pool: a request that fails on a pooled (possibly silently
  dead) connection now re-dials once when the body is repeatable,
  matching the old dial-per-request reliability
- cover singleflight completes via defer with a default error so a
  panicking fetch can never strand waiters in (nil, nil) or leave a
  permanent in-flight entry
2026-07-13 08:45:30 +07:00
zarzet 2fa4aa5b70 perf(network): mobile stability and data-usage fixes from network audit
Stability:
- transient timeouts no longer classified as ISP blocking; they fall
  through to retry backoff (hard blocks - DNS/RST/cert - still abort)
- connectivity change now closes idle Go connections in every network
  mode (debounced), so pooled sockets from the old interface are not
  reused after a wifi/cellular handoff
- download body reads get a 60s stall watchdog that cancels and
  surfaces a retryable network error instead of hanging; distinct
  from user cancellation
- ResponseHeaderTimeout 45s on all transports; IdleConnTimeout 90->60s;
  dial timeout 30->10s; retry backoff gains full jitter; Retry-After
  honored on 5xx and Deezer 429

Data usage / speed:
- cover downloads deduplicated with singleflight plus a 24MB/15min
  in-memory cache keyed by final URL (album batches fetched the same
  1800px cover once per track before)
- song.link availability cached (30min positive / 5min negative)
  in front of the 9-req/min rate limiter
- uTLS Cloudflare path now pools one HTTP/2 connection per host with
  a shared TLS session cache instead of a full handshake per request

Deezer artist track-count N+1 kept: counts feed the discography
download UI and are already amortized by the artist TTL cache.
2026-07-13 08:39:05 +07:00
zarzet 85949eef8c refactor: unify extension call skeleton and download finalization paths
Go:
- callExtensionScript generic replaces the ~40-line perf/lock/run/
  timeout skeleton in ten wrapper methods; PostProcess V1/V2 share
  postProcessCommon (V1 keeps its postProcess-only probe)
- attemptExtensionDownload extracts the duplicated provider download
  attempt from the source-extension and priority-loop paths;
  overlayStr/overlayInt collapse the enrichment overlay chains;
  buildDownloadFilename shared by both output-path builders

Dart (download_queue_provider):
- _replaceSafFileVia wraps the SAF copy/operate/publish/cleanup
  roundtrip used at eight sites
- decrypt and external-LRC finalization unified between the inline
  and native-worker pipelines (divergences parametrized: AC-4 repair,
  per-stage error messages, base-name resolution, extension-state
  snapshot); HIGH/container conversion keep separate lenient/strict
  policies and share only the SAF mechanics
2026-07-12 20:19:54 +07:00
zarzet 4e0cae9c20 refactor: extract shared helpers for repeated low-level patterns
Dart:
- notification_service: single _details() builder replaces 13 copies
  of the NotificationDetails block
- platform_bridge: _invokeMap() for 34 invoke+decode call sites,
  _cachedInvoke() unifies the three TTL/in-flight cache scaffolds
- ffmpeg_service: _promoteTempOutput(), _appendCoverInputArgs(),
  single _writeReplayGainTags() and _convertToLossless() for the
  ALAC/FLAC twins
- sqlite_helpers.dart: shared openAppDatabase/path-key/migration
  helpers for the three database classes
- library_collections: parametrized wishlist/loved/favorite CRUD
- extension_provider: one predicate-based replacedBuiltIn* lookup

Go:
- extension runtime: parseGojaHeaders/coerceGojaBody/doExtensionHTTP
  shared by httpGet/httpPost/httpRequest/shortcuts/fetch
- exports_metadata: applyAudioMetadataToResult + successMethodJSON,
  APE edit path reuses audioMetadataFromEditFields
- lyrics: lrclibGet() for both LRCLib fetchers
- extension_store: drop hand-rolled strings helpers
2026-07-12 14:10:23 +07:00
zarzet aa2cdef1d2 refactor: remove dead code across Dart and Go layers
- drop unused widgets (AnimatedStateSwitcher, GridSkeleton,
  adjacentHorizontalPageRoute, buildRemovalAnimation, SwingIcon,
  BottomSheetOptionTile) and empty _buildInfoCard placeholders
- drop test-only Go wrapper methods (GetDownloadURL, MatchTrack,
  CheckAvailability 7-arg, CustomSearchForItemID) and their parsers
- drop unused httputil getters/validators and hand-rolled base64
  decoder in favor of encoding/base64
- remove unused riverpod_generator dev dependency
2026-07-12 09:35:11 +07:00
zarzet 8c2d2d5289 refactor(backend): dedupe response builders and MusicBrainz fetch
marshalJSONString collapses 62 identical marshal-and-return blocks in the
gomobile exports; jsError/jsSuccess collapse 135 goja extension-response
maps; the two MusicBrainz fetchers share one request+retry helper; the
extension-HTTP default User-Agent block and fileExists get single homes.
No exported signature changes.
2026-07-11 16:34:36 +07:00
zarzet 49ecfe261a feat(metadata): route MP3, Ogg, and M4A edits to the native writers
EditFileMetadata now answers native_mp3/native_ogg/native_m4a and only
falls back to method=ffmpeg when a native editor rejects the file, so
every editor UI switches off the remux path without Dart changes. The
download embed path does the same for flac/mp3/opus/m4a. Side effect:
ReplayGain for MP3/Opus was silently never written (the old dispatch
answered ffmpeg and the caller ignored it) - it is now written natively.
Covers all three editors with synthetic-file tests, including foreign-tag
preservation and stco offset tracking.
2026-07-11 12:52:01 +07:00
zarzet 89e5f4bde9 feat(metadata): add native Ogg/Opus vorbis comment editor
EditOggFields rebuilds only the OpusTags/vorbis comment packet and
repaginates the header pages; audio pages are copied verbatim and only
renumbered (with fresh CRCs) when the header page count changes. Cover art
is written as METADATA_BLOCK_PICTURE from the shared FLAC picture builder.
The field semantics are extracted from EditFlacFields into a shared
applyVorbisFieldEdits so FLAC and Ogg interpret edits identically.
Unsupported layouts (multiplexed streams, unknown codecs) return an error
so callers can fall back.
2026-07-11 12:46:51 +07:00
zarzet 2eab955f5f feat(metadata): add native M4A ilst tag editor
EditM4AFields edits standard iTunes atoms, freeform ISRC/LABEL, and
ReplayGain in a single atomic rewrite, preserving atoms it does not
control. Creates the moov>udta>meta>ilst chain (with an mdir hdlr) when a
raw stream lacks one, and shifts stco/co64 offsets for faststart files.
2026-07-11 12:45:38 +07:00
zarzet 2bd671d305 feat(metadata): add native MP3 ID3v2 tag editor
EditMP3Fields parses the existing ID3v2.2/2.3/2.4 tag into raw frames,
replaces only the frames the edit controls, and re-serializes as ID3v2.4
with the audio bytes streamed through untouched (temp+fsync+rename).
Unlike the ffmpeg remux this preserves foreign frames - POPM ratings,
SYLT synced lyrics, chapters, and other taggers' TXXX entries.
2026-07-11 12:45:21 +07:00
zarzet e5a1ee2ac3 fix(metadata): shift stco/co64 chunk offsets when the ilst resizes
writeM4AFreeformTags changed the ilst size without updating chunk offsets,
which broke playback for faststart files (moov before mdat) after any
ISRC/label/ReplayGain write. Reuses the AC-4 path's shiftChunkOffsets.
2026-07-11 12:45:20 +07:00
zarzet 2192e68684 fix(download): fsync staged downloads before the promote rename
The staged-write-then-rename protocol was atomic against process kills but
not against power loss: f2fs/ext4 can persist the rename before the data,
leaving a zero-length or truncated file under the final name. Sync the
staged file before promoting and fsync the directory after.
2026-07-11 12:44:19 +07:00