mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-30 05:12:00 +02:00
fix(library): preserve indexed tracks when a full scan is incomplete
This commit is contained in:
@@ -1907,19 +1907,20 @@ class MainActivity: FlutterFragmentActivity() {
|
||||
"scanLibraryFolderToNDJSONFile" -> {
|
||||
val folderPath = call.argument<String>("folder_path") ?: ""
|
||||
val outputPath = call.argument<String>("output_path") ?: ""
|
||||
val count = withContext(Dispatchers.IO) {
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
safScanActive = false
|
||||
coreBackend.scanLibraryFolderToNdjsonFile(
|
||||
val count = coreBackend.scanLibraryFolderToNdjsonFile(
|
||||
folderPath,
|
||||
outputPath,
|
||||
)
|
||||
}
|
||||
result.success(
|
||||
mapOf(
|
||||
"path" to outputPath,
|
||||
"count" to count,
|
||||
"error_count" to JSONObject(coreBackend.getLibraryScanProgress())
|
||||
.getInt("error_count"),
|
||||
)
|
||||
)
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"scanLibraryFolderIncremental" -> {
|
||||
val folderPath = call.argument<String>("folder_path") ?: ""
|
||||
|
||||
@@ -734,7 +734,7 @@ internal fun MainActivity.scanSafTree(
|
||||
}
|
||||
File(ndjsonOutputPath).writeText("", Charsets.UTF_8)
|
||||
try { File(safScanCheckpointPath(ndjsonOutputPath)).delete() } catch (_: Exception) {}
|
||||
return mapOf("path" to ndjsonOutputPath, "count" to 0)
|
||||
return mapOf("path" to ndjsonOutputPath, "count" to 0, "error_count" to 0)
|
||||
}
|
||||
|
||||
fun cancelledResult(): Any {
|
||||
@@ -1152,7 +1152,7 @@ internal fun MainActivity.scanSafTree(
|
||||
if (ndjsonWriter != null) {
|
||||
ndjsonWriter.close()
|
||||
checkpointWriter?.close()
|
||||
return mapOf("path" to outputPath, "count" to resultCount)
|
||||
return mapOf("path" to outputPath, "count" to resultCount, "error_count" to errors)
|
||||
}
|
||||
spill!!.raw(if (resultCount == 0) "[]" else "]")
|
||||
return spill.result()
|
||||
|
||||
@@ -556,7 +556,11 @@ import UniformTypeIdentifiers
|
||||
throw invalidArgumentsError(call.method)
|
||||
}
|
||||
let count = try coreBackend.scanLibraryFolderToNdjsonFile(folder: folderPath, output: outputPath)
|
||||
return ["path": outputPath, "count": count]
|
||||
guard let progress = parseJsonPayload(try coreBackend.getLibraryScanProgress()) as? [String: Any],
|
||||
let errorCount = progress["error_count"] as? Int else {
|
||||
throw invalidArgumentsError("scanLibraryFolderToNDJSONFile: missing scan summary")
|
||||
}
|
||||
return ["path": outputPath, "count": count, "error_count": errorCount]
|
||||
|
||||
case "scanLibraryFolderIncremental":
|
||||
let args = call.arguments as! [String: Any]
|
||||
|
||||
@@ -802,7 +802,7 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
}
|
||||
}
|
||||
|
||||
if (_scanCancelRequested) {
|
||||
if (_scanCancelRequested || result['cancelled'] == true) {
|
||||
state = state.copyWith(
|
||||
isScanning: false,
|
||||
scanIsFinalizing: false,
|
||||
|
||||
@@ -2328,6 +2328,18 @@ class PlatformBridge {
|
||||
if (result['cancelled'] == true) {
|
||||
throw FormatException('$method returned a cancelled partial scan');
|
||||
}
|
||||
final errors = result['error_count'];
|
||||
if (errors is! num ||
|
||||
!errors.isFinite ||
|
||||
errors < 0 ||
|
||||
errors != errors.toInt()) {
|
||||
throw FormatException('$method returned an invalid error count');
|
||||
}
|
||||
if (errors > 0) {
|
||||
throw FormatException(
|
||||
'Library scan could not read $errors files; the existing Library was kept',
|
||||
);
|
||||
}
|
||||
final pathValue = result['path'];
|
||||
final countValue = result['count'];
|
||||
if (pathValue is! String || pathValue.trim().isEmpty) {
|
||||
|
||||
@@ -68,7 +68,7 @@ void main() {
|
||||
resumed.add(hasCheckpoint);
|
||||
await output.writeAsString('{"id":"track"}\n');
|
||||
await checkpoint.writeAsString('track\t123\n');
|
||||
return {'path': output.path, 'count': 1};
|
||||
return {'path': output.path, 'count': 1, 'error_count': 0};
|
||||
});
|
||||
|
||||
final first = await scan();
|
||||
@@ -99,7 +99,7 @@ void main() {
|
||||
final output = File((call.arguments as Map)['output_path'] as String);
|
||||
await output.writeAsString('{"id":"old"}\n');
|
||||
await File('${output.path}.state').writeAsString('old\t123\n');
|
||||
return {'path': output.path, 'count': 1};
|
||||
return {'path': output.path, 'count': 1, 'error_count': 0};
|
||||
});
|
||||
final previous = await scan();
|
||||
await expectLater(
|
||||
@@ -116,6 +116,64 @@ void main() {
|
||||
);
|
||||
}
|
||||
|
||||
for (final saf in [false, true]) {
|
||||
Future<LibraryScanNDJSONFile> scan() => saf
|
||||
? PlatformBridge.scanSafTreeToNDJSONFile('content://library/tree/music')
|
||||
: PlatformBridge.scanLibraryFolderToNDJSONFile('/music');
|
||||
|
||||
for (final count in [0, 2]) {
|
||||
test(
|
||||
'partial scan cannot replace the index ($count rows, SAF=$saf)',
|
||||
() async {
|
||||
messenger.setMockMethodCallHandler(backend, (call) async {
|
||||
final output = File(
|
||||
(call.arguments as Map)['output_path'] as String,
|
||||
);
|
||||
await output.writeAsString('{"id":"readable"}\n' * count);
|
||||
return {'path': output.path, 'count': count, 'error_count': 3};
|
||||
});
|
||||
await expectLater(
|
||||
scan(),
|
||||
throwsA(
|
||||
isA<FormatException>().having(
|
||||
(error) => error.message,
|
||||
'reason',
|
||||
contains('existing Library was kept'),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
test('a confirmed empty folder can clear its index (SAF=$saf)', () async {
|
||||
messenger.setMockMethodCallHandler(backend, (call) async {
|
||||
final output = File((call.arguments as Map)['output_path'] as String);
|
||||
await output.writeAsString('');
|
||||
return {'path': output.path, 'count': 0, 'error_count': 0};
|
||||
});
|
||||
final empty = await scan();
|
||||
expect(empty.expectedCount, 0);
|
||||
expect(await empty.rows().toList(), isEmpty);
|
||||
});
|
||||
|
||||
for (final errors in [null, -1, 0.5, '0']) {
|
||||
test(
|
||||
'missing or invalid error summary is rejected ($errors, SAF=$saf)',
|
||||
() async {
|
||||
messenger.setMockMethodCallHandler(backend, (call) async {
|
||||
final output = File(
|
||||
(call.arguments as Map)['output_path'] as String,
|
||||
);
|
||||
await output.writeAsString('');
|
||||
return {'path': output.path, 'count': 0, 'error_count': errors};
|
||||
});
|
||||
await expectLater(scan(), throwsFormatException);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
test('cache setup failure stops forced scan before native reads', () async {
|
||||
final calls = <String>[];
|
||||
messenger.setMockMethodCallHandler(backend, (call) async {
|
||||
|
||||
Reference in New Issue
Block a user