refactor(providers): remove dead lookup/history APIs and dedup provider resolution

Delete the verbatim-duplicate LocalLibraryLookup class + provider, the unused
localLibrarySummaryProvider, notifier search()/getCount()/existsInLibrary
wrapper, and the now-orphaned LibraryDatabase.search(); the notifier's async
getById/findExisting lookups (the real ones) stay. Drop dead
download-history removeBySpotifyId/getDatabaseCount and the orphaned
history deleteBySpotifyId, and fold the byte-identical addToHistory/
adoptNativeHistoryItem into one _persistHistoryItem keeping distinct log text.
Delete dead extension_provider members (ensureSpotifyWebExtensionReady,
enabledExtensions, getExtension, resolveProviderDisplayName, URLHandler.matchesURL,
Extension.hasBrowseCategories) and collapse resolveEffectiveDownloadService/
MetadataProvider into one predicate-parameterized helper.
This commit is contained in:
zarzet
2026-07-14 18:50:52 +07:00
parent 87bf33321b
commit cd6cf05227
5 changed files with 32 additions and 286 deletions
+7 -37
View File
@@ -1029,26 +1029,20 @@ class DownloadHistoryNotifier extends Notifier<DownloadHistoryState> {
return byId.values.toList(growable: false);
}
void addToHistory(DownloadHistoryItem item) {
unawaited(
() async {
final mergedItem = await _putInMemoryHistory(item);
await _db.upsert(mergedItem.toJson());
_bumpHistoryRevision();
}().catchError((Object e, StackTrace stack) {
_historyLog.e('Failed to save to database: $e', e, stack);
}),
);
}
void addToHistory(DownloadHistoryItem item) =>
_persistHistoryItem(item, 'save to database');
void adoptNativeHistoryItem(DownloadHistoryItem item) {
void adoptNativeHistoryItem(DownloadHistoryItem item) =>
_persistHistoryItem(item, 'adopt native history item');
void _persistHistoryItem(DownloadHistoryItem item, String action) {
unawaited(
() async {
final mergedItem = await _putInMemoryHistory(item);
await _db.upsert(mergedItem.toJson());
_bumpHistoryRevision();
}().catchError((Object e, StackTrace stack) {
_historyLog.e('Failed to adopt native history item: $e', e, stack);
_historyLog.e('Failed to $action: $e', e, stack);
}),
);
}
@@ -1073,26 +1067,6 @@ class DownloadHistoryNotifier extends Notifier<DownloadHistoryState> {
});
}
void removeBySpotifyId(String spotifyId) {
state = state.copyWith(
items: state.items.where((item) => item.spotifyId != spotifyId).toList(),
lookupItems: state.lookupItems
.where((item) => item.spotifyId != spotifyId)
.toList(growable: false),
);
unawaited(
() async {
final deleted = await _db.deleteBySpotifyId(spotifyId);
final totalCount = await _db.getCount();
state = state.copyWith(totalCount: totalCount);
_bumpHistoryRevision();
_historyLog.d('Removed $deleted item(s) with spotifyId: $spotifyId');
}().catchError((Object e, StackTrace stack) {
_historyLog.e('Failed to delete from database: $e', e, stack);
}),
);
}
DownloadHistoryItem? getBySpotifyId(String spotifyId) {
return state.getBySpotifyId(spotifyId);
}
@@ -1521,10 +1495,6 @@ class DownloadHistoryNotifier extends Notifier<DownloadHistoryState> {
});
}
Future<int> getDatabaseCount() async {
return await _db.getCount();
}
/// Replaces all download history with [items] (each in the
/// [DownloadHistoryItem.toJson] shape) from a restored backup, then reloads
/// the in-memory state from storage.
+25 -135
View File
@@ -13,7 +13,6 @@ final _log = AppLogger('ExtensionProvider');
const _metadataProviderPriorityKey = 'metadata_provider_priority';
const _providerPriorityKey = 'provider_priority';
const _spotifyWebExtensionId = 'spotify-web';
const _storeRegistryUrlPrefKey = 'store_registry_url';
/// Result of restoring extensions from a backup.
@@ -240,7 +239,6 @@ class Extension {
bool get hasPostProcessing => postProcessing?.enabled ?? false;
bool get hasServiceHealth => serviceHealth.isNotEmpty;
bool get hasHomeFeed => capabilities['homeFeed'] == true;
bool get hasBrowseCategories => capabilities['browseCategories'] == true;
bool get requiresNativeContainerConversion =>
capabilities['requiresContainerConversion'] == true ||
capabilities['requiresNativeContainerConversion'] == true;
@@ -283,61 +281,50 @@ class Extension {
String resolveEffectiveDownloadService(
String requestedService,
ExtensionState extensionState,
) {
final normalizedRequested = requestedService.trim().toLowerCase();
final enabledDownloadExtensions = extensionState.extensions
.where((ext) => ext.enabled && ext.hasDownloadProvider)
.toList(growable: false);
if (normalizedRequested.isNotEmpty) {
final matchingExtension = enabledDownloadExtensions
.where((ext) => ext.id.trim().toLowerCase() == normalizedRequested)
.firstOrNull;
if (matchingExtension != null) {
return matchingExtension.id;
}
final replacementExtension = enabledDownloadExtensions
.where(
(ext) => ext.replacesBuiltInProviders.contains(normalizedRequested),
)
.firstOrNull;
if (replacementExtension != null) {
return replacementExtension.id;
}
}
return enabledDownloadExtensions.firstOrNull?.id ?? '';
}
) => _resolveEffectiveProvider(
requestedService,
extensionState,
(ext) => ext.hasDownloadProvider,
);
String resolveEffectiveMetadataProvider(
String requestedProvider,
ExtensionState extensionState,
) => _resolveEffectiveProvider(
requestedProvider,
extensionState,
(ext) => ext.hasMetadataProvider,
);
String _resolveEffectiveProvider(
String requested,
ExtensionState extensionState,
bool Function(Extension) hasProvider,
) {
final normalizedRequested = requestedProvider.trim().toLowerCase();
final enabledMetadataExtensions = extensionState.extensions
.where((ext) => ext.enabled && ext.hasMetadataProvider)
final normalizedRequested = requested.trim().toLowerCase();
final enabled = extensionState.extensions
.where((ext) => ext.enabled && hasProvider(ext))
.toList(growable: false);
if (normalizedRequested.isNotEmpty) {
final matchingExtension = enabledMetadataExtensions
final matching = enabled
.where((ext) => ext.id.trim().toLowerCase() == normalizedRequested)
.firstOrNull;
if (matchingExtension != null) {
return matchingExtension.id;
if (matching != null) {
return matching.id;
}
final replacementExtension = enabledMetadataExtensions
final replacement = enabled
.where(
(ext) => ext.replacesBuiltInProviders.contains(normalizedRequested),
)
.firstOrNull;
if (replacementExtension != null) {
return replacementExtension.id;
if (replacement != null) {
return replacement.id;
}
}
return enabledMetadataExtensions.firstOrNull?.id ?? '';
return enabled.firstOrNull?.id ?? '';
}
bool isDeezerCompatibleDownloadService(
@@ -358,19 +345,6 @@ bool isDeezerCompatibleDownloadService(
);
}
String resolveProviderDisplayName(
String providerId, {
Iterable<Extension> extensions = const [],
}) {
for (final extension in extensions) {
if (extension.id == providerId) {
return extension.displayName;
}
}
return providerId;
}
class SearchFilter {
final String id;
final String? label;
@@ -495,16 +469,6 @@ class URLHandler {
);
}
bool matchesURL(String url) {
if (!enabled || patterns.isEmpty) return false;
final lowerUrl = url.toLowerCase();
for (final pattern in patterns) {
if (lowerUrl.contains(pattern.toLowerCase())) {
return true;
}
}
return false;
}
}
class ExtensionServiceHealthCheck {
@@ -1438,68 +1402,6 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
}
}
Future<bool> ensureSpotifyWebExtensionReady({
bool setAsSearchProvider = true,
}) async {
try {
await refreshExtensions();
var ext = state.extensions
.where((e) => e.id == _spotifyWebExtensionId)
.firstOrNull;
if (ext == null) {
final cacheDir = await getTemporaryDirectory();
await PlatformBridge.initExtensionRepo(cacheDir.path);
final tempRoot = await getTemporaryDirectory();
final installDir = await Directory(
'${tempRoot.path}/spotiflac_bootstrap_spotify_web',
).create(recursive: true);
final downloadPath = await PlatformBridge.downloadRepoExtension(
_spotifyWebExtensionId,
installDir.path,
);
final installed = await installExtension(downloadPath);
if (!installed) {
_log.w('Failed to install spotify-web extension from store');
return false;
}
await refreshExtensions();
ext = state.extensions
.where((e) => e.id == _spotifyWebExtensionId)
.firstOrNull;
}
if (ext == null) {
_log.w('spotify-web extension is still not available after install');
return false;
}
if (!ext.enabled) {
await setExtensionEnabled(_spotifyWebExtensionId, true);
}
if (setAsSearchProvider) {
final settings = ref.read(settingsProvider);
if (settings.searchProvider != _spotifyWebExtensionId) {
ref
.read(settingsProvider.notifier)
.setSearchProvider(_spotifyWebExtensionId);
}
}
_log.i('spotify-web extension is ready');
return true;
} catch (e) {
_log.w('Failed to ensure spotify-web extension is ready: $e');
return false;
}
}
Future<Map<String, dynamic>> getExtensionSettings(String extensionId) async {
try {
return await PlatformBridge.getExtensionSettings(extensionId);
@@ -1674,18 +1576,6 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
await _cleanupExtensions(reason: 'manual');
}
Extension? getExtension(String extensionId) {
try {
return state.extensions.firstWhere((ext) => ext.id == extensionId);
} catch (_) {
return null;
}
}
List<Extension> enabledExtensions() {
return state.extensions.where((ext) => ext.enabled).toList();
}
List<String> getAllDownloadProviders() {
return _distinctProviderIds(
state.extensions
-72
View File
@@ -857,14 +857,6 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
await _refreshSummaryFromStorage();
}
bool existsInLibrary({String? isrc, String? trackName, String? artistName}) {
return state.existsInLibrary(
isrc: isrc,
trackName: trackName,
artistName: artistName,
);
}
Future<LocalLibraryItem?> getById(String id) async {
final json = await _db.getById(id);
return json == null ? null : LocalLibraryItem.fromJson(json);
@@ -903,17 +895,6 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
return null;
}
Future<List<LocalLibraryItem>> search(String query) async {
if (query.isEmpty) return [];
final results = await _db.search(query);
return results.map((e) => LocalLibraryItem.fromJson(e)).toList();
}
Future<int> getCount() async {
return await _db.getCount();
}
Future<Map<String, int>> _backfillLegacyFileModTimes({
required bool isSaf,
required Map<String, int> existingFiles,
@@ -992,59 +973,6 @@ final localLibraryProvider =
LocalLibraryNotifier.new,
);
final localLibrarySummaryProvider = Provider<LocalLibraryState>((ref) {
return ref.watch(localLibraryProvider);
});
class LocalLibraryLookup {
final LibraryDatabase _db;
const LocalLibraryLookup(this._db);
Future<LocalLibraryItem?> byId(String id) async {
final json = await _db.getById(id);
return json == null ? null : LocalLibraryItem.fromJson(json);
}
Future<LocalLibraryItem?> byIsrc(String isrc) async {
final json = await _db.getByIsrc(isrc);
return json == null ? null : LocalLibraryItem.fromJson(json);
}
Future<LocalLibraryItem?> byTrackAndArtist(
String trackName,
String artistName,
) async {
final json = await _db.findFirstByTrackAndArtist(trackName, artistName);
return json == null ? null : LocalLibraryItem.fromJson(json);
}
Future<LocalLibraryItem?> existing({
String? id,
String? isrc,
String? trackName,
String? artistName,
}) async {
if (id != null && id.isNotEmpty) {
final item = await byId(id);
if (item != null) return item;
}
if (isrc != null && isrc.isNotEmpty) {
final item = await byIsrc(isrc);
if (item != null) return item;
}
if (trackName != null && artistName != null) {
return byTrackAndArtist(trackName, artistName);
}
return null;
}
}
final localLibraryLookupProvider = Provider<LocalLibraryLookup>((ref) {
ref.watch(localLibraryProvider.select((state) => state.loadedIndexVersion));
return LocalLibraryLookup(LibraryDatabase.instance);
});
class LocalLibraryCoverRequest {
final String? isrc;
final String trackName;
-25
View File
@@ -765,31 +765,6 @@ class HistoryDatabase {
});
}
Future<int> deleteBySpotifyId(String spotifyId) async {
final db = await database;
final rows = await db.query(
'history',
columns: ['id'],
where: 'spotify_id = ?',
whereArgs: [spotifyId],
);
final ids = rows.map((row) => row['id'] as String).toList(growable: false);
return db.transaction<int>((txn) async {
for (final id in ids) {
await txn.delete(
'history_path_keys',
where: 'item_id = ?',
whereArgs: [id],
);
}
return txn.delete(
'history',
where: 'spotify_id = ?',
whereArgs: [spotifyId],
);
});
}
Future<void> clearAll() async {
final db = await database;
await db.transaction((txn) async {
-17
View File
@@ -1862,23 +1862,6 @@ class LibraryDatabase {
return Sqflite.firstIntValue(result) ?? 0;
}
Future<List<Map<String, dynamic>>> search(
String query, {
int limit = 50,
}) async {
final db = await database;
final searchQuery = '%${_escapeLikePattern(query.toLowerCase())}%';
final rows = await db.query(
'library',
where:
"LOWER(track_name) LIKE ? ESCAPE '\\' OR LOWER(artist_name) LIKE ? ESCAPE '\\' OR LOWER(album_name) LIKE ? ESCAPE '\\'",
whereArgs: [searchQuery, searchQuery, searchQuery],
orderBy: 'track_name',
limit: limit,
);
return rows.map(_dbRowToJson).toList();
}
Future<void> close() async {
final db = await database;
await db.close();