fix(backup): close archive outputs when extraction fails

This commit is contained in:
zarzet committed 2026-10-06 22:00:45 +07:00
1 parent ca747f8ce2
commit 10db2f272c
2 files changed
+45 -12

No files matched your search

+14 -12
View File
@@ -3,6 +3,7 @@ import 'dart:io';
import 'dart:isolate';
import 'package:archive/archive_io.dart';
import 'package:flutter/foundation.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:spotiflac_android/constants/app_info.dart';
@@ -371,9 +372,7 @@ class BackupService {
String? historyPath;
if (historyEntry != null) {
historyPath = p.join(extractionDir.path, 'history.ndjson');
final historyOutput = OutputFileStream(historyPath);
historyEntry.writeContent(historyOutput);
historyOutput.closeSync();
writeArchiveEntry(historyEntry, OutputFileStream(historyPath));
}
final restoredCovers = <String, dynamic>{};
@@ -399,9 +398,7 @@ class BackupService {
var ext = cover['ext']?.toString() ?? '.jpg';
if (!RegExp(r'^\.[a-z0-9]{1,8}$').hasMatch(ext)) ext = '.jpg';
final coverPath = p.join(extractionDir.path, 'cover_${index++}$ext');
final output = OutputFileStream(coverPath);
archiveEntry.writeContent(output);
output.closeSync();
writeArchiveEntry(archiveEntry, OutputFileStream(coverPath));
extractedCoverBytes += archiveEntry.size;
restoredCovers[manifestEntry.key.toString()] = {
'ext': ext,
@@ -428,12 +425,7 @@ class BackupService {
throw const FormatException('Missing or oversized profile photo');
}
photoPath = p.join(extractionDir.path, 'profile.png');
final output = OutputFileStream(photoPath);
try {
photoEntry.writeContent(output);
} finally {
output.closeSync();
}
writeArchiveEntry(photoEntry, OutputFileStream(photoPath));
}
profile = UserProfile(
name: rawProfile['name'] as String,
@@ -469,6 +461,16 @@ class BackupService {
}
}
/// Consumes and closes the output even when an archive payload cannot be read.
@visibleForTesting
static void writeArchiveEntry(ArchiveFile entry, OutputFileStream output) {
try {
entry.writeContent(output);
} finally {
output.closeSync();
}
}
static BackupBundle? parse(String content) {
dynamic decoded;
try {
+31
View File
@@ -6,7 +6,38 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:path/path.dart' as p;
import 'package:spotiflac_android/services/backup_service.dart';
class _UnreadableArchiveFile extends ArchiveFile {
_UnreadableArchiveFile(this.failure) : super('history.ndjson', 0, <int>[]);
final FormatException failure;
@override
void writeContent(OutputStream output, {bool freeMemory = true}) {
output.writeBytes([1, 2, 3]);
throw failure;
}
}
void main() {
test('archive output closes when reading its payload throws', () async {
final root = await Directory.systemTemp.createTemp('unreadable-backup-');
addTearDown(() => root.delete(recursive: true));
final path = p.join(root.path, 'history.ndjson');
final output = OutputFileStream(path);
const failure = FormatException('Invalid compressed archive payload');
expect(
() => BackupService.writeArchiveEntry(
_UnreadableArchiveFile(failure),
output,
),
throwsA(same(failure)),
);
expect(output.isOpen, isFalse);
expect(await File(path).readAsBytes(), [1, 2, 3]);
});
test(
'ZIP backup pages history and streams cover files during restore',
() async {