From 76c56964a52673fc7b269d5c83db00e1c3407e54 Mon Sep 17 00:00:00 2001 From: nyne Date: Sun, 2 Mar 2025 17:40:04 +0800 Subject: [PATCH] Fix archive download when using custom download path on Android. --- lib/network/download.dart | 32 +++++++++++++++++++++++--------- lib/utils/io.dart | 19 +++++++++++++++++++ 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/lib/network/download.dart b/lib/network/download.dart index 39d77c0..d21017c 100644 --- a/lib/network/download.dart +++ b/lib/network/download.dart @@ -2,6 +2,8 @@ import 'dart:async'; import 'dart:isolate'; import 'package:flutter/widgets.dart' show ChangeNotifier; +import 'package:flutter_saf/flutter_saf.dart'; +import 'package:venera/foundation/app.dart'; import 'package:venera/foundation/appdata.dart'; import 'package:venera/foundation/comic_source/comic_source.dart'; import 'package:venera/foundation/comic_type.dart'; @@ -739,11 +741,12 @@ class ArchiveDownloadTask extends DownloadTask { path = dir.path; } - var resultFile = File(FilePath.join(path!, "archive.zip")); + var archiveFile = + File(FilePath.join(App.dataPath, "archive_downloading.zip")); Log.info("Download", "Downloading $archiveUrl"); - _downloader = FileDownloader(archiveUrl, resultFile.path); + _downloader = FileDownloader(archiveUrl, archiveFile.path); bool isDownloaded = false; @@ -772,22 +775,33 @@ class ArchiveDownloadTask extends DownloadTask { } try { - await extractArchive(path!); + await _extractArchive(archiveFile.path, path!); } catch (e) { _setError("Failed to extract archive: $e"); return; } - await resultFile.deleteIgnoreError(); + await archiveFile.deleteIgnoreError(); LocalManager().completeTask(this); } - static Future extractArchive(String path) async { - var resultFile = FilePath.join(path, "archive.zip"); - await Isolate.run(() { - ZipFile.openAndExtract(resultFile, path); - }); + static Future _extractArchive(String archive, String outDir) async { + var out = Directory(outDir); + if (out is AndroidDirectory) { + // Saf directory can't be accessed by native code. + var cacheDir = FilePath.join(App.cachePath, "archive_downloading"); + Directory(cacheDir).forceCreateSync(); + await Isolate.run(() { + ZipFile.openAndExtract(archive, cacheDir); + }); + await copyDirectoryIsolate(Directory(cacheDir), Directory(outDir)); + await Directory(cacheDir).deleteIgnoreError(recursive: true); + } else { + await Isolate.run(() { + ZipFile.openAndExtract(archive, outDir); + }); + } } @override diff --git a/lib/utils/io.dart b/lib/utils/io.dart index d5ec62b..98b39dc 100644 --- a/lib/utils/io.dart +++ b/lib/utils/io.dart @@ -40,6 +40,7 @@ extension FileSystemEntityExt on FileSystemEntity { return p.basename(path); } + /// Delete the file or directory and ignore errors. Future deleteIgnoreError({bool recursive = false}) async { try { await delete(recursive: recursive); @@ -48,12 +49,14 @@ extension FileSystemEntityExt on FileSystemEntity { } } + /// Delete the file or directory if it exists. Future deleteIfExists({bool recursive = false}) async { if (existsSync()) { await delete(recursive: recursive); } } + /// Delete the file or directory if it exists. void deleteIfExistsSync({bool recursive = false}) { if (existsSync()) { deleteSync(recursive: recursive); @@ -74,12 +77,14 @@ extension FileExtension on File { await newFile.writeAsBytes(await readAsBytes()); } + /// Get the base name of the file without the extension. String get basenameWithoutExt { return p.basenameWithoutExtension(path); } } extension DirectoryExtension on Directory { + /// Calculate the size of the directory. Future get size async { if (!existsSync()) return 0; int total = 0; @@ -91,6 +96,7 @@ extension DirectoryExtension on Directory { return total; } + /// Change the base name of the directory. Directory renameX(String newName) { newName = sanitizeFileName(newName); return renameSync(path.replaceLast(name, newName)); @@ -100,6 +106,7 @@ extension DirectoryExtension on Directory { return File(FilePath.join(path, name)); } + /// Delete the contents of the directory. void deleteContentsSync({recursive = true}) { if (!existsSync()) return; for (var f in listSync()) { @@ -107,14 +114,24 @@ extension DirectoryExtension on Directory { } } + /// Delete the contents of the directory. Future deleteContents({recursive = true}) async { if (!existsSync()) return; for (var f in listSync()) { await f.deleteIfExists(recursive: recursive); } } + + /// Create the directory. If the directory already exists, delete it first. + void forceCreateSync() { + if (existsSync()) { + deleteSync(recursive: true); + } + createSync(recursive: true); + } } +/// Sanitize the file name. Remove invalid characters and trim the file name. String sanitizeFileName(String fileName) { if (fileName.endsWith('.')) { fileName = fileName.substring(0, fileName.length - 1); @@ -157,6 +174,8 @@ Future copyDirectory(Directory source, Directory destination) async { } } +/// Copy the **contents** of the source directory to the destination directory. +/// This function is executed in an isolate to prevent the UI from freezing. Future copyDirectoryIsolate( Directory source, Directory destination) async { await Isolate.run(() => overrideIO(() => copyDirectory(source, destination)));