mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-13 05:19:04 +02:00
perf(library): stream normalized scan timestamp snapshots
This commit is contained in:
@@ -759,7 +759,7 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
final useSnapshotBridge =
|
||||
Platform.isAndroid && existingFiles.isNotEmpty;
|
||||
final snapshotPath = useSnapshotBridge
|
||||
? await _db.writeFileModTimesSnapshot(sourceId: activeSourceId)
|
||||
? await _db.writeFileModTimesSnapshot(existingFiles)
|
||||
: null;
|
||||
|
||||
Map<String, dynamic> result;
|
||||
@@ -1263,7 +1263,9 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
required Map<String, int> existingFiles,
|
||||
}) async {
|
||||
final legacyPaths = existingFiles.entries
|
||||
.where((entry) => entry.value <= 0)
|
||||
// Negative timestamps deliberately force a metadata-version rescan;
|
||||
// only a missing timestamp (zero) may be filled from the filesystem.
|
||||
.where((entry) => entry.value == 0)
|
||||
.map((entry) => entry.key)
|
||||
.toList();
|
||||
if (legacyPaths.isEmpty) {
|
||||
|
||||
@@ -2064,14 +2064,11 @@ class LibraryDatabase {
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<String> writeFileModTimesSnapshot({String? sourceId}) async {
|
||||
final db = await database;
|
||||
final rows = await db.rawQuery(
|
||||
'SELECT file_path, COALESCE(file_mod_time, 0) AS file_mod_time, '
|
||||
'audio_metadata_scan_version FROM library '
|
||||
'${sourceId == null ? '' : 'WHERE source_id = ?'}',
|
||||
sourceId == null ? const [] : [sourceId],
|
||||
);
|
||||
/// Uses the already normalized, backfilled timestamps from the scan caller.
|
||||
/// Bounded writes avoid a second query and a library-sized StringBuffer.
|
||||
Future<String> writeFileModTimesSnapshot(
|
||||
Map<String, int> fileModTimes,
|
||||
) async {
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final file = File(
|
||||
join(
|
||||
@@ -2079,24 +2076,30 @@ class LibraryDatabase {
|
||||
'library_file_mod_times_${DateTime.now().microsecondsSinceEpoch}.tsv',
|
||||
),
|
||||
);
|
||||
final buffer = StringBuffer();
|
||||
for (final row in rows) {
|
||||
final path = row['file_path'] as String?;
|
||||
if (path == null || path.isEmpty) continue;
|
||||
final modTime = (row['file_mod_time'] as num?)?.toInt() ?? 0;
|
||||
final scanVersion =
|
||||
(row['audio_metadata_scan_version'] as num?)?.toInt() ?? 0;
|
||||
buffer
|
||||
..write(
|
||||
libraryIncrementalSnapshotModTime(
|
||||
storedModTime: modTime,
|
||||
storedScanVersion: scanVersion,
|
||||
),
|
||||
)
|
||||
..write('\t')
|
||||
..writeln(path);
|
||||
try {
|
||||
final output = await file.open(mode: FileMode.write);
|
||||
try {
|
||||
final buffer = StringBuffer();
|
||||
for (final entry in fileModTimes.entries) {
|
||||
if (entry.key.isEmpty) continue;
|
||||
buffer
|
||||
..write(entry.value)
|
||||
..write('\t')
|
||||
..writeln(entry.key);
|
||||
if (buffer.length >= 64 * 1024) {
|
||||
await output.writeString(buffer.toString());
|
||||
buffer.clear();
|
||||
}
|
||||
}
|
||||
if (buffer.isNotEmpty) await output.writeString(buffer.toString());
|
||||
await output.flush();
|
||||
} finally {
|
||||
await output.close();
|
||||
}
|
||||
} catch (_) {
|
||||
if (await file.exists()) await file.delete();
|
||||
rethrow;
|
||||
}
|
||||
await file.writeAsString(buffer.toString(), flush: true);
|
||||
return file.path;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:spotiflac_android/services/library_database.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
test(
|
||||
'scan snapshot preserves normalized sentinel and backfilled timestamps across chunks',
|
||||
() async {
|
||||
final directory = await Directory.systemTemp.createTemp(
|
||||
'scan-snapshot-test-',
|
||||
);
|
||||
const channel = MethodChannel('plugins.flutter.io/path_provider');
|
||||
final messenger =
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
|
||||
messenger.setMockMethodCallHandler(channel, (_) async => directory.path);
|
||||
addTearDown(() async {
|
||||
messenger.setMockMethodCallHandler(channel, null);
|
||||
await directory.delete(recursive: true);
|
||||
});
|
||||
final timestamps = <String, int>{
|
||||
for (var i = 0; i < 4000; i++) '/music/歌曲 café $i.flac': i,
|
||||
'/music/legacy.flac': -1,
|
||||
'/music/backfilled.flac': 1720000000000,
|
||||
};
|
||||
final path = await LibraryDatabase.instance.writeFileModTimesSnapshot(
|
||||
timestamps,
|
||||
);
|
||||
final lines = await File(path).readAsLines();
|
||||
expect(
|
||||
lines,
|
||||
timestamps.entries
|
||||
.map((entry) => '${entry.value}\t${entry.key}')
|
||||
.toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user