refactor(library): remove unused history lookups and replacement paths

This commit is contained in:
zarzet committed 2026-09-30 21:43:54 +07:00
1 parent 377710ec22
commit b3f7f59e2e
3 files changed
+4 -168

No files matched your search

+4 -103
View File
@@ -622,10 +622,7 @@ class DownloadHistoryNotifier extends Notifier<DownloadHistoryState> {
'.mp4',
];
Future<String?> _findConvertedSibling(
String originalPath, {
bool includeAlternateExtensions = true,
}) async {
Future<String?> _findConvertedSibling(String originalPath) async {
final dotIndex = originalPath.lastIndexOf('.');
if (dotIndex < 0) return null;
final directoryPrefix = originalPath.substring(
@@ -638,7 +635,7 @@ class DownloadHistoryNotifier extends Notifier<DownloadHistoryState> {
for (final candidateName in _conversionRenameCandidates(
fileName,
includeAlternateExtensions: includeAlternateExtensions,
includeAlternateExtensions: true,
)) {
final candidatePath = '$directoryPrefix$candidateName';
if (candidatePath == originalPath) continue;
@@ -649,67 +646,6 @@ class DownloadHistoryNotifier extends Notifier<DownloadHistoryState> {
return null;
}
Future<bool> verifyOrRepairHistoryItem(DownloadHistoryItem item) async {
if (await fileExists(item.filePath)) return true;
DownloadHistoryItem? repaired;
if (item.storageMode == 'saf' &&
item.downloadTreeUri != null &&
item.downloadTreeUri!.isNotEmpty) {
var fileName = (item.safFileName ?? '').trim();
if (fileName.isEmpty && isContentUri(item.filePath)) {
fileName = _fileNameFromUri(item.filePath);
}
for (final candidate in _conversionRenameCandidates(fileName)) {
try {
final resolved = await PlatformBridge.resolveSafFile(
treeUri: item.downloadTreeUri!,
relativeDir: item.safRelativeDir ?? '',
fileName: candidate,
);
final uri = (resolved['uri'] as String? ?? '').trim();
if (uri.isEmpty || !await fileExists(uri)) continue;
final relativeDir = (resolved['relative_dir'] as String? ?? '')
.trim();
repaired = item.copyWith(
filePath: uri,
safFileName: candidate,
safRelativeDir: relativeDir.isEmpty
? item.safRelativeDir
: relativeDir,
safRepaired: true,
);
break;
} catch (error) {
_historyLog.w('Failed to resolve renamed SAF file: $error');
}
}
} else if (!isContentUri(item.filePath)) {
final sibling = await _findConvertedSibling(
item.filePath,
includeAlternateExtensions: false,
);
if (sibling != null) repaired = item.copyWith(filePath: sibling);
}
if (repaired == null) return false;
await _db.upsert(repaired.toJson());
final updatedItems = state.items
.map((entry) => entry.id == repaired!.id ? repaired : entry)
.toList(growable: false);
final updatedLookupItems = state.lookupItems
.map((entry) => entry.id == repaired!.id ? repaired : entry)
.toList(growable: false);
state = state.copyWith(
items: updatedItems,
lookupItems: updatedLookupItems,
);
_historyLog.i(
'Reconciled renamed conversion: ${item.filePath} -> ${repaired.filePath}',
);
return true;
}
Future<
({
List<String> orphanedIds,
@@ -1090,28 +1026,6 @@ final downloadHistoryProvider =
DownloadHistoryNotifier.new,
);
class DownloadHistoryGroupedCounts {
final int albumCount;
final int singleTrackCount;
const DownloadHistoryGroupedCounts({
required this.albumCount,
required this.singleTrackCount,
});
}
final downloadHistoryGroupedCountsProvider =
FutureProvider<DownloadHistoryGroupedCounts>((ref) async {
ref.watch(
downloadHistoryProvider.select((state) => state.loadedIndexVersion),
);
final counts = await HistoryDatabase.instance.getGroupedCounts();
return DownloadHistoryGroupedCounts(
albumCount: counts['albums'] ?? 0,
singleTrackCount: counts['singles'] ?? 0,
);
});
HistoryLookupRequest historyLookupForTrack(Track track) {
return HistoryLookupRequest(
spotifyId: track.id,
@@ -1121,21 +1035,8 @@ HistoryLookupRequest historyLookupForTrack(Track track) {
);
}
final downloadHistoryExistsProvider = FutureProvider.autoDispose
.family<bool, HistoryLookupRequest>((ref, request) async {
ref.watch(
downloadHistoryProvider.select((state) => state.loadedIndexVersion),
);
final notifier = ref.read(downloadHistoryProvider.notifier);
final row = await HistoryDatabase.instance.findExistingTrack(request);
if (row == null) return false;
return notifier.verifyOrRepairHistoryItem(
DownloadHistoryItem.fromJson(row),
);
});
// Batch lookups deliberately avoid per-row SAF verification. Startup repair
// reconciles stale rows; the single-track provider above keeps strict checks.
// Batch lookups deliberately avoid per-row SAF verification; startup repair
// reconciles stale rows.
final downloadHistoryBatchExistsProvider = FutureProvider.autoDispose
.family<Set<String>, HistoryBatchLookupRequest>((ref, request) async {
ref.watch(
-19
View File
@@ -1078,25 +1078,6 @@ class HistoryDatabase {
return Sqflite.firstIntValue(result) ?? 0;
}
Future<Map<String, int>> getGroupedCounts() async {
final db = await database;
final rows = await db.rawQuery('''
SELECT
SUM(CASE WHEN track_count > 1 THEN 1 ELSE 0 END) AS albums,
SUM(CASE WHEN track_count = 1 THEN 1 ELSE 0 END) AS singles
FROM (
SELECT COUNT(*) AS track_count
FROM history
GROUP BY album_key
)
''');
final row = rows.isEmpty ? const <String, Object?>{} : rows.first;
return {
'albums': (row['albums'] as num?)?.toInt() ?? 0,
'singles': (row['singles'] as num?)?.toInt() ?? 0,
};
}
Future<Map<String, dynamic>?> findExisting({
String? spotifyId,
String? isrc,
-46
View File
@@ -1006,52 +1006,6 @@ class LibraryDatabase {
_log.i('Replaced library with ${items.length} items');
}
/// Atomically replaces the Library while consuming bounded scan batches.
/// The stream may represent tens of thousands of tracks without requiring a
/// second full list of models/maps on the Dart heap.
Future<int> replaceAllStream(
Stream<Map<String, dynamic>> items, {
int batchSize = 300,
}) async {
if (batchSize <= 0) {
throw ArgumentError.value(batchSize, 'batchSize', 'Must be positive');
}
final db = await database;
var inserted = 0;
await db.transaction((txn) async {
await txn.delete('library_path_keys');
await txn.delete('library');
var batch = txn.batch();
var pending = 0;
Future<void> flush() async {
if (pending == 0) return;
await batch.commit(noResult: true);
batch = txn.batch();
pending = 0;
}
await for (final json in items) {
final id = json['id'] as String?;
if (id == null || id.trim().isEmpty) {
throw const FormatException('Library scan row has no valid id');
}
batch.insert(
'library',
_jsonToDbRow(json),
conflictAlgorithm: ConflictAlgorithm.replace,
);
_putPathKeysInBatch(batch, id, json['filePath'] as String?);
inserted++;
pending++;
if (pending >= batchSize) await flush();
}
await flush();
});
_log.i('Stream-replaced library with $inserted items');
return inserted;
}
/// Stages scan rows in bounded, independently committed batches, then swaps
/// only this source in one short transaction. Download-history exclusion is
/// an indexed SQLite anti-join, avoiding a full History path set in Dart.