mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-19 22:17:29 +02:00
fix(backup): close archive outputs when extraction fails
This commit is contained in:
1 parent
ca747f8ce2
commit
10db2f272c
2 files changed
+45
-12
No files matched your search
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user