diff --git a/lib/common/picker.dart b/lib/common/picker.dart index fbb08f18..3e85b4e6 100644 --- a/lib/common/picker.dart +++ b/lib/common/picker.dart @@ -6,6 +6,18 @@ import 'package:fl_clash/common/common.dart'; import 'package:image_picker/image_picker.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; +Future copyPickedFileToPath( + PlatformFile file, + String destinationPath, +) async { + final sourcePath = file.path; + if (sourcePath != null) { + await File(sourcePath).safeCopy(destinationPath); + return; + } + await File(destinationPath).safeWriteAsBytes(await file.readBytes()); +} + class Picker { Future pickerFile() async { return FilePicker.pickFile(initialDirectory: await appPath.downloadDirPath); diff --git a/lib/views/backup_and_restore.dart b/lib/views/backup_and_restore.dart index c2183a17..ef0ffe6e 100644 --- a/lib/views/backup_and_restore.dart +++ b/lib/views/backup_and_restore.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:dynamic_color/dynamic_color.dart'; import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/dav_client.dart'; @@ -139,9 +137,8 @@ class _BackupAndRestoreState extends ConsumerState Future _restoreOnLocal(RestoreOption option) async { final appLocalizations = context.appLocalizations; final file = await picker.pickerFile(); - final path = file?.path; - if (path == null) return; - await File(path).safeCopy(await appPath.backupFilePath); + if (file == null) return; + await copyPickedFileToPath(file, await appPath.backupFilePath); final res = await globalState.loadingRun( () async { await globalState.container diff --git a/test/common/picker_test.dart b/test/common/picker_test.dart index 5843ba79..75bede03 100644 --- a/test/common/picker_test.dart +++ b/test/common/picker_test.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'dart:typed_data'; import 'package:file_picker/file_picker.dart'; import 'package:fl_clash/common/picker.dart'; @@ -25,5 +26,47 @@ void main() { expect(String.fromCharCodes(bytes), 'mixed-port: 7890'); }); + + test( + 'copies a bytes-only picked file to the restore destination', + () async { + final directory = await Directory.systemTemp.createTemp( + 'fl_clash_picker_restore_test_', + ); + addTearDown(() => directory.delete(recursive: true)); + final destination = File('${directory.path}/backup.zip'); + final platformFile = PlatformFile( + name: 'backup.zip', + size: 3, + bytes: Uint8List.fromList([1, 2, 3]), + ); + + await copyPickedFileToPath(platformFile, destination.path); + + expect(await destination.readAsBytes(), [1, 2, 3]); + }, + ); + + test( + 'copies a path-backed picked file to the restore destination', + () async { + final directory = await Directory.systemTemp.createTemp( + 'fl_clash_picker_restore_path_test_', + ); + addTearDown(() => directory.delete(recursive: true)); + final source = File('${directory.path}/source.zip'); + final destination = File('${directory.path}/backup.zip'); + await source.writeAsBytes([4, 5, 6]); + final platformFile = PlatformFile( + name: 'source.zip', + path: source.path, + size: 3, + ); + + await copyPickedFileToPath(platformFile, destination.path); + + expect(await destination.readAsBytes(), [4, 5, 6]); + }, + ); }); }