mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-21 06:52:18 +02:00
perf(network): share artwork by content hash
This commit is contained in:
1 parent
27ed971782
commit
2ac3220407
2 files changed
+72
-6
No files matched your search
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user