From bfd769b349da2f49ee903325a9baae3211ae6b4f Mon Sep 17 00:00:00 2001 From: zarzet Date: Fri, 27 Feb 2026 15:05:14 +0700 Subject: [PATCH] fix(library): exclude downloaded tracks from local scan reliably --- lib/providers/local_library_provider.dart | 64 +++++++++++++++++++---- 1 file changed, 53 insertions(+), 11 deletions(-) diff --git a/lib/providers/local_library_provider.dart b/lib/providers/local_library_provider.dart index 30e8500b..afa25f66 100644 --- a/lib/providers/local_library_provider.dart +++ b/lib/providers/local_library_provider.dart @@ -198,7 +198,7 @@ class LocalLibraryNotifier extends Notifier { if (raw.isEmpty) return const {}; final cleaned = raw.startsWith('EXISTS:') ? raw.substring(7) : raw; - final keys = {cleaned}; + final keys = {}; void addNormalized(String value) { final trimmed = value.trim(); @@ -217,18 +217,42 @@ class LocalLibraryNotifier extends Notifier { keys.add(decoded.toLowerCase()); } catch (_) {} } + + Uri? parsed; + try { + parsed = Uri.parse(trimmed); + } catch (_) {} + + if (parsed != null && parsed.hasScheme) { + final noQueryOrFragment = parsed.replace(query: null, fragment: null); + keys.add(noQueryOrFragment.toString()); + keys.add(noQueryOrFragment.toString().toLowerCase()); + + if (parsed.scheme == 'file') { + try { + final fileOnly = parsed.toFilePath(); + if (fileOnly.isNotEmpty) { + keys.add(fileOnly); + keys.add(fileOnly.toLowerCase()); + if (fileOnly.contains('\\')) { + final slash = fileOnly.replaceAll('\\', '/'); + keys.add(slash); + keys.add(slash.toLowerCase()); + } + } + } catch (_) {} + } + } else if (trimmed.startsWith('/')) { + try { + final asFileUri = Uri.file(trimmed).toString(); + keys.add(asFileUri); + keys.add(asFileUri.toLowerCase()); + } catch (_) {} + } } addNormalized(cleaned); - if (cleaned.startsWith('content://')) { - try { - final uri = Uri.parse(cleaned); - addNormalized(uri.toString()); - addNormalized(uri.replace(query: null, fragment: null).toString()); - } catch (_) {} - } - return keys; } @@ -345,7 +369,11 @@ class LocalLibraryNotifier extends Notifier { _log.i('Skipped $skippedDownloads files already in download history'); } - await _db.upsertBatch(items.map((e) => e.toJson()).toList()); + // Full scan should replace library index entirely. + await _db.clearAll(); + if (items.isNotEmpty) { + await _db.upsertBatch(items.map((e) => e.toJson()).toList()); + } final now = DateTime.now(); try { @@ -437,10 +465,24 @@ class LocalLibraryNotifier extends Notifier { final currentByPath = { for (final item in state.items) item.filePath: item, }; + final existingDownloadedPaths = []; + currentByPath.removeWhere((path, _) { + final shouldExclude = _isDownloadedPath(path, downloadedPathKeys); + if (shouldExclude) { + existingDownloadedPaths.add(path); + } + return shouldExclude; + }); + if (existingDownloadedPaths.isNotEmpty) { + final removed = await _db.deleteByPaths(existingDownloadedPaths); + _log.i( + 'Removed $removed downloaded tracks already present in local library index', + ); + } // Upsert new/modified items (excluding downloaded files) final updatedItems = []; - int skippedDownloads = 0; + int skippedDownloads = existingDownloadedPaths.length; if (scannedList.isNotEmpty) { for (final json in scannedList) { final map = json as Map;