perf(network): share artwork by content hash

This commit is contained in:
zarzet committed 2026-10-04 01:41:36 +07:00
1 parent 27ed971782
commit 2ac3220407
2 files changed
+72 -6

No files matched your search

+19 -6
View File
@@ -117,16 +117,29 @@ class NetworkLibraryScanner {
if (cover != null &&
cover.isNotEmpty &&
await File(cover).exists()) {
final target = File('${covers.path}/$id.image');
final staged = File('${target.path}.tmp');
// Identical embedded artwork shares an immutable file, including
// across albums/sources. Cleanup retains all DB cover references.
File? target;
File? staged;
try {
await File(cover).copy(staged.path);
await staged.rename(target.path);
final digest = await sha256.bind(File(cover).openRead()).first;
target = File('${covers.path}/network_cover_$digest.image');
staged = File(
'${target.path}.${NetworkStorageService.newId()}.tmp',
);
if (!await target.exists() || await target.length() == 0) {
await File(cover).copy(staged.path);
await staged.rename(target.path);
}
coverPath = target.path;
} catch (_) {
if (await target.exists()) coverPath = target.path;
if (target != null && await target.exists()) {
coverPath = target.path;
}
} finally {
if (await staged.exists()) await staged.delete();
if (staged != null && await staged.exists()) {
await staged.delete();
}
}
}
await checkpoint();
+53
View File
@@ -3,6 +3,7 @@ import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:spotiflac_android/services/network_library_scanner.dart';
import 'package:spotiflac_android/services/network_storage_service.dart';
import 'package:spotiflac_android/services/library_cleanup.dart';
import 'package:spotiflac_android/utils/file_access.dart';
import 'package:spotiflac_android/utils/logger.dart';
@@ -49,6 +50,58 @@ void main() {
temporaryDirectory: () async => directory,
);
test(
'identical artwork shares storage until the last reference is gone',
() async {
final shared = await File(
'${directory.path}/shared.jpg',
).writeAsBytes([1, 2]);
final different = await File(
'${directory.path}/different.jpg',
).writeAsBytes([3]);
final scan =
await scanner(
_Storage({
'': [
const NetworkEntry('one.flac', 'one.flac'),
const NetworkEntry('two.flac', 'two.flac'),
const NetworkEntry('three.flac', 'three.flac'),
],
}),
(source) async => {
'title': 'Track',
'cover_path': source.endsWith('three.flac')
? different.path
: shared.path,
},
).scan(
NetworkStorageService.source('nas', ''),
checkpoint: () async {},
onProgress: (_, _, _) {},
);
final rows = await scan.rows().toList();
expect(rows[0]['id'], isNot(rows[1]['id']));
expect(rows[0]['coverPath'], rows[1]['coverPath']);
expect(rows[0]['coverPath'], isNot(rows[2]['coverPath']));
final covers = Directory('${directory.path}/library_covers');
expect(await covers.list().length, 2);
// Removing one track retains its sibling's shared cover.
expect(
await pruneUnreferencedLibraryCovers(covers, {
for (final row in rows.skip(1)) row['coverPath'] as String,
}),
0,
);
expect(
await pruneUnreferencedLibraryCovers(covers, {
rows[2]['coverPath'] as String,
}),
1,
);
expect(await File(rows[2]['coverPath'] as String).readAsBytes(), [3]);
},
);
test(
'subfolders keep original metadata, stable identities and persistent artwork',
() async {