perf: harden download and persistence lifecycle

This commit is contained in:
zarzet
2026-08-28 12:51:03 +07:00
parent 51f61c9c95
commit 45d9bbd7b2
22 changed files with 1656 additions and 231 deletions
+94 -8
View File
@@ -169,18 +169,76 @@ void main() {
final source = File(
'lib/services/app_state_database.dart',
).readAsStringSync();
final playerSource = File(
'lib/services/music_player_service.dart',
).readAsStringSync();
test('v1 to v2 tolerates an existing playback session table', () {
test('v4 normalizes playback queue and scalar state', () {
expect(source, contains('const _dbVersion = 4;'));
expect(source, contains('await _migratePlaybackSessionToV4(db);'));
expect(source, contains('media_json TEXT NOT NULL'));
expect(source, contains('position_ms INTEGER NOT NULL DEFAULT 0'));
expect(source, contains('updatePlaybackSessionState'));
});
test('periodic playback updates do not serialize an unchanged queue', () {
final scalarUpdate = RegExp(
r'updatePlaybackSessionState\([\s\S]*?if \(updated\) return;',
).firstMatch(playerSource);
expect(scalarUpdate, isNotNull);
expect(scalarUpdate!.group(0), isNot(contains("'media':")));
expect(playerSource, contains('_scheduledSessionQueueRevision'));
});
});
group('iOS background download lifecycle', () {
final appDelegateSource = File(
'ios/Runner/AppDelegate.swift',
).readAsStringSync();
final queueProviderSource = File(
'lib/providers/download_queue_provider.dart',
).readAsStringSync();
test('acquires background time before starting long-running work', () {
final beginCase = RegExp(
r'case "beginBackgroundDownloadTask":[\s\S]*?result\(nil\)',
).firstMatch(appDelegateSource);
expect(beginCase, isNotNull);
expect(
source,
contains('CREATE TABLE IF NOT EXISTS \$_playbackSessionTable'),
beginCase!.group(0)!.indexOf('beginBackgroundDownloadTask()'),
lessThan(beginCase.group(0)!.indexOf('result(nil)')),
);
});
test('expiration safely defers and foreground resumes the queue', () {
expect(appDelegateSource, contains('iosBackgroundDownloadExpired'));
final expirationHandler = RegExp(
r'if self\?\.downloadsActive == true \{[\s\S]*?\n\s*\}',
).firstMatch(appDelegateSource);
expect(expirationHandler, isNotNull);
expect(
expirationHandler!
.group(0)!
.indexOf('GobackendCancelAllActiveDownloads()'),
lessThan(
expirationHandler.group(0)!.indexOf('iosBackgroundDownloadExpired'),
),
);
expect(
RegExp(
r'if \(oldVersion < 2\)\s*\{\s*'
r'await _createPlaybackSessionTable\(db\);',
).hasMatch(source),
isTrue,
queueProviderSource,
contains('pauseQueue(persistAcrossRestarts: false)'),
);
expect(queueProviderSource, contains('_iosBackgroundExecutionExpired'));
expect(queueProviderSource, contains('final requeueItemIds ='));
expect(
queueProviderSource,
contains('if (!state.isProcessing && requeueItemIds.isEmpty) return;'),
);
expect(queueProviderSource, contains('if (alreadyForeground) {'));
expect(
queueProviderSource,
contains('resumePendingDownloadsOnForeground();'),
);
});
});
@@ -193,6 +251,13 @@ void main() {
final historyDatabaseSource = File(
'lib/services/history_database.dart',
).readAsStringSync();
final workerSnapshotSource = File(
'android/app/src/main/kotlin/com/zarz/spotiflac/'
'DownloadServiceSnapshot.kt',
).readAsStringSync();
final nativeWorkerProviderSource = File(
'lib/providers/download_queue_provider_native_worker.dart',
).readAsStringSync();
int kotlinConstant(String name) {
final match = RegExp(
@@ -216,6 +281,27 @@ void main() {
);
});
test('polls one progress delta stream and preserves concurrent items', () {
expect(
RegExp(
r'Gobackend\.getAllDownloadProgressDelta\(',
).allMatches(workerSnapshotSource),
hasLength(1),
);
expect(
workerSnapshotSource,
isNot(contains('Gobackend.getAllDownloadProgress()')),
);
expect(workerSnapshotSource, contains('"item_deltas"'));
expect(
workerSnapshotSource,
contains('progressItemIds = orderedItemIds'),
);
expect(workerSnapshotSource, contains('progressCoordinatorEpoch'));
expect(workerSnapshotSource, contains('nativeWorkerProgressEpoch.get()'));
expect(nativeWorkerProviderSource, contains("snapshot['item_deltas']"));
});
Set<String> historyTableColumns(String source) {
final match = RegExp(
r'CREATE TABLE(?: IF NOT EXISTS)? history\s*\(([\s\S]*?)\n\s*\)',
+16
View File
@@ -87,5 +87,21 @@ void main() {
);
expect(autoVacuumMatches.single.start, lessThan(onCreateIndex));
});
test('enables delete triggers for replace-backed FTS synchronization', () {
expect(source, contains('PRAGMA recursive_triggers = ON'));
});
});
group('FTS5 search query', () {
test(
'quotes literal input and leaves unsupported short terms to fallback',
() {
expect(ftsPhraseSearchQuery('ab'), isNull);
expect(ftsPhraseSearchQuery('beat'), '"beat"');
expect(ftsPhraseSearchQuery('foo"bar'), '"foo""bar"');
expect(ftsPhraseSearchQuery('a\u0000b'), isNull);
},
);
});
}