Commit Graph
442 Commits
Author SHA1 Message Date
zarzet 09c31d9dba refactor(batch): share local track actions and phase settings
Use shared coordinators for queueing FLAC replacements and batch metadata enrichment from album and queue screens. Keep selection lifecycle handling in each screen and synchronize a settings snapshot once per phase.

Cover cancellation, partial failure, screen disposal, and settings changes between preview and apply.
2026-09-06 14:04:05 +07:00
zarzet 49c3c66f15 fix(metadata): preserve Track fields during enrichment
Apply resolved metadata with copyWith and retain the existing field precedence. Keep UPC, preview URLs, and source-only fields when enrichment updates other tags.

Extract the pure merge helpers and add regression tests for field preservation.
2026-09-06 14:03:49 +07:00
zarzet 099e211f5a fix(conversion): reserve FLAC outputs without overwriting files
Reuse the conversion output lifecycle for M4A conversion and native FLAC suffix correction. Reserve destination names atomically, reject empty output, and clean partial files while preserving the input on failure.

Add regressions for existing outputs, concurrent conversions, staging, and cleanup.
2026-09-06 14:03:49 +07:00
zarzet 62ef538690 perf(library): page missing-file cleanup with safe SAF batch checks 2026-09-06 02:34:49 +07:00
zarzet d95d070565 perf(library): stream normalized scan timestamp snapshots 2026-09-06 02:34:49 +07:00
zarzet dae509078e perf(queue): apply sparse progress updates with chunked snapshots 2026-09-06 02:33:42 +07:00
zarzet 4ba3ab2fc7 perf(library): optimize CIFS scan recovery 2026-09-01 12:00:37 +07:00
zarzet a1ee346f2f chore(logging): sanitize diagnostics and reduce noise 2026-08-31 01:49:49 +07:00
zarzet 897fb7d523 feat(backup): stream versioned ZIP archives 2026-08-30 23:19:09 +07:00
zarzet 2e0a5f7b9c fix(downloads): hide unknown byte totals in notifications 2026-08-30 18:58:19 +07:00
zarzet 29a86b0dc3 feat(player): add sleep timer 2026-08-30 17:23:16 +07:00
zarzet 7141ca980f fix(library): sync track playback controls 2026-08-30 00:43:13 +07:00
zarzet d5465e9768 fix(download): throttle progress diagnostics 2026-08-30 00:17:27 +07:00
zarzet f1fb01604d perf(download): reduce progress wakeups and harden retries 2026-08-29 23:47:58 +07:00
zarzet 740ce173b2 fix(security): prevent extension network sandbox bypasses 2026-08-29 18:56:46 +07:00
zarzet 29db940d63 fix(security): encrypt extension storage at rest 2026-08-29 18:52:16 +07:00
zarzet 5dac33dfa4 fix(download): preserve finalization metadata and library labels 2026-08-29 00:31:25 +07:00
zarzet c3b2214e2a perf: optimize library scans and extension runtime 2026-08-28 21:00:11 +07:00
zarzet 887a8ede12 feat(metadata): limit embedded cover resolution 2026-08-28 13:21:05 +07:00
zarzet 45d9bbd7b2 perf: harden download and persistence lifecycle 2026-08-28 12:51:03 +07:00
zarzet 433f8ed685 perf: reduce queue and extension runtime overhead 2026-08-28 01:47:51 +07:00
zarzet 2b117415ae feat: add file format library quality label 2026-08-27 16:50:46 +07:00
zarzet 4609e59806 fix(library): show play actions during batch downloads 2026-08-27 13:56:48 +07:00
zarzet 1a7c20111a feat(library): add lyrics filtering and metadata actions 2026-08-27 01:44:06 +07:00
zarzet 04c42bd511 perf(downloads): bound native queue state 2026-08-27 00:17:25 +07:00
zarzet 0e3fca9967 perf(downloads): stream concurrent native queue work 2026-08-26 18:37:56 +07:00
zarzet e149ee5358 fix(storage): publish finalized SAF output once 2026-08-26 18:37:15 +07:00
zarzet c6437d8428 feat(library): add bit depth quality label mode 2026-08-23 11:42:47 +07:00
zarzet 19591bdf19 feat(metadata): show explicit badges in track titles 2026-08-22 23:08:40 +07:00
zarzet 75e1cf17b8 fix(home): clear recent activity permanently 2026-08-22 22:45:06 +07:00
zarzet 60624436a6 feat(library): support multiple storage folders 2026-08-22 11:15:42 +07:00
zarzet 89d39c4074 fix(metadata): show readable SAF file locations 2026-08-21 01:02:02 +07:00
zarzet ea63291db8 refactor(metadata): move cover resolution to extensions 2026-08-19 13:12:29 +07:00
zarzet 4fef287a7b feat(metadata): add download attribution to comments 2026-08-18 21:17:36 +07:00
zarzet 8b231be19a fix(download): unblock queue after verification cancel 2026-08-18 20:50:48 +07:00
zarzet 290923e88f feat(tagging): write release identity tags 2026-08-18 19:04:26 +07:00
zarzet 48883d51d4 feat(extensions): preserve provider metadata 2026-08-18 12:28:35 +07:00
zarzet 7fc27e8314 perf(library): stream scans and optimize queue queries 2026-08-18 11:03:05 +07:00
zarzet 2e115308b7 feat(filename): add provider traceability tags 2026-08-17 20:51:27 +07:00
zarzet efc03db308 fix(metadata): keep collaboration albums grouped 2026-08-11 20:58:27 +07:00
zarzet 5374883875 fix(download): normalize MP4 audio extension by codec (#512) 2026-08-11 19:07:00 +07:00
Abelardo Ramirez bed95efb29 fix(playlists): thread the source provider id through to recent playlists
Fixes #368.

Reopening a Spotify playlist from "recent access" showed no tracks,
while the first view (right after pasting the URL) worked fine.

Root cause traced across two layers:

1. Go: ExtURLHandleResult (the parsed shape of an extension's handleUrl()
   return value) never captured a top-level `id` for the handled
   resource. Track/album/artist results carry their own id inside their
   nested metadata, but a plain playlist result has no such object, so
   its id was silently dropped everywhere from the goja parser through
   to the JSON the Dart side receives.

2. Dart: TrackState had nowhere to put that id even if it existed
   (only playlistName), so recording a "recent" playlist access stored
   the playlist's *name* as if it were its id. Reopening it later fed
   that name into PlaylistScreen's provider-guessing logic
   (legacyProviderIdFromResourceId, which only recognizes legacy
   "provider:id" prefixes), which naturally failed and fell through to
   a hardcoded Deezer metadata fetch using a Spotify playlist's name as
   the resource id - guaranteed to return nothing.

Fix, matching the "generic API, not per-provider checks" architecture
in CONTRIBUTING.md:

- go_backend/extension_provider_wrapper.go: add ExtURLHandleResult.ID.
- go_backend/extension_goja_convert.go: parse it from the handler's
  return value.
- go_backend/exports_extensions.go: surface it in the JSON response.
- lib/providers/track_provider.dart: add TrackState.playlistId,
  populated from the response's `id` field for playlist results.
- lib/screens/home_tab.dart: record the real playlist id (falling back
  to the name only if a provider never supplies one) and pass both the
  id and the already-known provider id forward to PlaylistScreen.
- lib/screens/playlist_screen.dart: PlaylistScreen gains a
  metadataProviderId param that takes priority over guessing from the
  id's shape.
- lib/screens/home_tab_recent.dart: pass the recent-access entry's
  stored providerId through when reopening a playlist.
- lib/utils/provider_resource_ids.dart: extract the
  known-id-vs-guessed-id preference into a small, directly testable
  resolvePreferredMetadataProviderId helper.

Note: this fixes the common case (an extension already reported its
own id as the source provider for the URL it handled). If a provider
never supplies an id for its handleUrl() playlist result, playlistId
stays null and behavior is unchanged from before this fix - no
regression, just not a complete fix for that narrower case, since
that would require changes in extension-side JS code outside this
repo.

Tests:
- go_backend/extension_goja_convert_url_handle_test.go: the new ID
  field round-trips from a handler's return value, and stays empty
  when the handler doesn't supply one.
- test/provider_resource_ids_test.dart: resolvePreferredMetadataProviderId
  prefers a known provider id, falls back to the legacy-prefix guess,
  treats a blank known id as unknown, and returns null for the
  unprefixed-id-with-no-known-provider case from #368 itself.

Verification: go build/vet/test and flutter analyze/test all green.
2026-08-11 17:29:41 +07:00
Abelardo Ramirez 555fd77cc8 fix(ios): stop dropping the security-scoped download folder bookmark
Fixes #439, likely also #302.

Reported: on iOS, the download folder silently reverts to the default
SpotiFLAC folder after starting a download (and stays reverted after
closing and reopening the app).

_processQueue ran an iCloud/writable-path shape check against the
persisted downloadDirectory string and, if it looked like iCloud Drive
or failed the structural writable-path check, called
setDownloadDirectory() to reset it to the default folder. That call
passes no iosBookmark argument, so it also wipes the user's
security-scoped bookmark in the persisted settings - permanently, not
just for the current run.

This check ran unconditionally, even when a bookmark was already
present for that folder. A folder picked from Files can legitimately
have a persisted path that looks like iCloud Drive, or that fails this
generic structural check, while the bookmark itself still grants real
write access - the bookmark is the actual source of truth, and the
queue already has a separate, correct bookmark-resolution step right
after this one (StartAccessingIosBookmark, with its own proper
failure handling that fails the queued items with a clear message
instead of destroying the setting). The earlier path-shape check just
never deferred to it.

lib/utils/file_access.dart: extract the run/skip decision into
shouldValidateIosOutputDir(isIOS, isSafMode, outputDir,
downloadDirectoryBookmark) - skips whenever a bookmark is present -
and use it in download_queue_provider.dart in place of the inline
condition. Kept as a plain function taking isIOS as a parameter since
Platform.isIOS itself can't be exercised from a host test run.

test/file_access_ios_test.dart covers: runs for a plain app-folder
path, skips when a bookmark is present (the exact #439 case), skips
off iOS, skips in SAF mode, and skips when there's no output dir yet.

Verification: flutter analyze and flutter test (224 tests, all green,
including the 5 new ones) both clean.
2026-08-11 17:28:56 +07:00
zarzet 95f2879110 fix(download): recover queue after worker timeout (#524) 2026-08-11 17:23:29 +07:00
zarzet 1d363d5166 fix(playlist): ignore container title during album verification #519 2026-08-11 17:02:17 +07:00
zarzet f7439c6793 feat(library): add combined quality label #511 2026-08-11 16:56:12 +07:00
zarzet 6dbca64a80 perf(history): batch SAF orphan inspection #511 2026-08-11 16:50:35 +07:00
zarzet 33db0cf297 fix(queue): restore user-paused downloads #511 2026-08-11 15:23:21 +07:00
zarzet cd9f43f5a0 fix(cover): preserve best max-quality candidate #511 2026-08-11 15:20:57 +07:00
zarzet 5237aed25e feat(download): add automatic lossy conversion 2026-08-11 15:19:45 +07:00