mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-28 20:32:15 +02:00
fix(library): preserve cached covers across directory aliases
This commit is contained in:
@@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:spotiflac_android/providers/settings_provider.dart';
|
||||
import 'package:spotiflac_android/services/library_cleanup.dart';
|
||||
import 'package:spotiflac_android/services/library_database.dart';
|
||||
import 'package:spotiflac_android/services/notification_service.dart';
|
||||
import 'package:spotiflac_android/services/platform_bridge.dart';
|
||||
@@ -1203,23 +1204,10 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
offset += pageSize;
|
||||
}
|
||||
|
||||
var deletedCount = 0;
|
||||
await for (final entity in libraryCoverDir.list(
|
||||
recursive: true,
|
||||
followLinks: false,
|
||||
)) {
|
||||
if (entity is! File || referencedCoverPaths.contains(entity.path)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
await entity.delete();
|
||||
deletedCount++;
|
||||
} catch (e) {
|
||||
_log.w(
|
||||
'Failed deleting stale library cover cache ${entity.path}: $e',
|
||||
);
|
||||
}
|
||||
}
|
||||
final deletedCount = await pruneUnreferencedLibraryCovers(
|
||||
libraryCoverDir,
|
||||
referencedCoverPaths,
|
||||
);
|
||||
|
||||
if (deletedCount > 0) {
|
||||
_log.i('Pruned $deletedCount stale library cover cache files');
|
||||
|
||||
@@ -1,5 +1,39 @@
|
||||
import 'dart:io';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:spotiflac_android/utils/file_access.dart';
|
||||
import 'package:spotiflac_android/utils/logger.dart';
|
||||
|
||||
Future<int> pruneUnreferencedLibraryCovers(
|
||||
Directory directory,
|
||||
Set<String> referencedPaths,
|
||||
) async {
|
||||
// Native scans may return canonical paths while Dart lists a directory alias.
|
||||
final retainedPaths = {...referencedPaths};
|
||||
for (final path in referencedPaths) {
|
||||
try {
|
||||
retainedPaths.add(await File(path).resolveSymbolicLinks());
|
||||
} on FileSystemException {
|
||||
// Missing cache files can remain referenced until the next full scan.
|
||||
}
|
||||
}
|
||||
var deleted = 0;
|
||||
await for (final entity in directory.list(
|
||||
recursive: true,
|
||||
followLinks: false,
|
||||
)) {
|
||||
if (entity is! File || retainedPaths.contains(entity.path)) continue;
|
||||
try {
|
||||
if (retainedPaths.contains(await entity.resolveSymbolicLinks())) continue;
|
||||
await entity.delete();
|
||||
deleted++;
|
||||
} catch (error) {
|
||||
final message =
|
||||
'Failed deleting stale library cover cache ${entity.path}: $error';
|
||||
AppLogger('LocalLibrary').w(message);
|
||||
}
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/// Pages by stable ID (not OFFSET, since rows are removed during traversal).
|
||||
/// A high-water mark bounds the run if a concurrent scan adds new rows.
|
||||
|
||||
@@ -102,6 +102,42 @@ void main() {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
|
||||
tearDown(() => messenger.setMockMethodCallHandler(channel, null));
|
||||
|
||||
test(
|
||||
'cover cleanup preserves references through directory aliases',
|
||||
() async {
|
||||
final root = await Directory.systemTemp.createTemp(
|
||||
'library-cover-cleanup-',
|
||||
);
|
||||
addTearDown(() => root.delete(recursive: true));
|
||||
final directory = await Directory('${root.path}/covers').create();
|
||||
final alias = await Link('${root.path}/alias').create(directory.path);
|
||||
final canonical = await File(
|
||||
'${directory.path}/canonical.jpg',
|
||||
).writeAsBytes([1]);
|
||||
final legacy = await File(
|
||||
'${directory.path}/legacy.jpg',
|
||||
).writeAsBytes([2]);
|
||||
final orphan = await File(
|
||||
'${directory.path}/orphan.jpg',
|
||||
).writeAsBytes([3]);
|
||||
final outside = await File('${root.path}/outside.jpg').writeAsBytes([4]);
|
||||
await Link('${directory.path}/outside.jpg').create(outside.path);
|
||||
|
||||
final deleted =
|
||||
await pruneUnreferencedLibraryCovers(Directory(alias.path), {
|
||||
await canonical.resolveSymbolicLinks(),
|
||||
'${alias.path}/legacy.jpg',
|
||||
'${alias.path}/already-missing.jpg',
|
||||
});
|
||||
|
||||
expect(await canonical.exists(), isTrue);
|
||||
expect(await legacy.exists(), isTrue);
|
||||
expect(await orphan.exists(), isFalse);
|
||||
expect(await outside.readAsBytes(), [4]);
|
||||
expect(deleted, 1);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'cleanup pages without skipping after deletions and preserves unknown',
|
||||
() async {
|
||||
|
||||
Reference in New Issue
Block a user