From 5f36ef6ea3e9f7a1817656bef00981d87e700e73 Mon Sep 17 00:00:00 2001 From: nyne Date: Fri, 17 Jan 2025 22:30:25 +0800 Subject: [PATCH] support 7z; fix #137 --- assets/translation.json | 22 ++++++++++++---------- lib/pages/home_page.dart | 12 ++++++------ lib/utils/cbz.dart | 29 ++++++++++++++++++++++++++--- lib/utils/file_type.dart | 11 ++++++++++- lib/utils/import_comic.dart | 5 +++-- pubspec.lock | 9 +++++++++ pubspec.yaml | 4 ++++ 7 files changed, 70 insertions(+), 22 deletions(-) diff --git a/assets/translation.json b/assets/translation.json index 9017678..27b2a8a 100644 --- a/assets/translation.json +++ b/assets/translation.json @@ -154,8 +154,8 @@ "If the directory contains a file named 'cover.*', it will be used as the cover image. Otherwise the first image will be used." : "如果目录包含一个名为'cover.*'的文件,它将被用作封面图片。否则将使用第一张图片。", "The directory name will be used as the comic title. And the name of chapter directories will be used as the chapter titles.\n" : "目录名称将被用作漫画标题。章节目录的名称将被用作章节标题。\n", "Export as cbz": "导出为cbz", - "Select a cbz/zip file." : "选择一个cbz/zip文件", - "A cbz file" : "一个cbz文件", + "Select an archive file (cbz, zip, 7z, cb7)" : "选择一个归档文件 (cbz, zip, 7z, cb7)", + "An archive file" : "一个归档文件", "Fullscreen": "全屏", "Exit": "退出", "View more": "查看更多", @@ -297,8 +297,8 @@ "End": "末尾", "None": "无", "View Detail": "查看详情", - "Select a directory which contains multiple cbz/zip files." : "选择一个包含多个cbz/zip文件的目录", - "Multiple cbz files" : "多个cbz文件", + "Select a directory which contains multiple archive files." : "选择一个包含多个归档文件的目录", + "Multiple archive files" : "多个归档文件", "No valid comics found" : "未找到有效的漫画", "Enable DNS Overrides": "启用DNS覆写", "DNS Overrides": "DNS覆写", @@ -314,7 +314,8 @@ "Reset": "重置", "Tags": "标签", "Authors": "作者", - "Comics": "漫画" + "Comics": "漫画", + "Imported @a comics": "已导入 @a 本漫画" }, "zh_TW": { "Home": "首頁", @@ -470,8 +471,8 @@ "If the directory contains a file named 'cover.*', it will be used as the cover image. Otherwise the first image will be used." : "如果目錄包含一個名為'cover.*'的文件,它將被用作封面圖片。否則將使用第一張圖片。", "The directory name will be used as the comic title. And the name of chapter directories will be used as the chapter titles.\n" : "目錄名稱將被用作漫畫標題。章節目錄的名稱將被用作章節標題。\n", "Export as cbz": "匯出為cbz", - "Select a cbz/zip file." : "選擇一個cbz/zip文件", - "A cbz file" : "一個cbz文件", + "Select an archive file (cbz, zip, 7z, cb7)" : "選擇一個歸檔文件 (cbz, zip, 7z, cb7)", + "An archive file" : "一個歸檔文件", "Fullscreen": "全螢幕", "Exit": "退出", "View more": "查看更多", @@ -614,8 +615,8 @@ "End": "末尾", "None": "無", "View Detail": "查看詳情", - "Select a directory which contains multiple cbz/zip files." : "選擇一個包含多個cbz/zip文件的目錄", - "Multiple cbz files" : "多個cbz文件", + "Select a directory which contains multiple archive files." : "選擇一個包含多個歸檔文件的目錄", + "Multiple archive files" : "多個歸檔文件", "No valid comics found" : "未找到有效的漫畫", "Enable DNS Overrides": "啟用DNS覆寫", "DNS Overrides": "DNS覆寫", @@ -631,6 +632,7 @@ "Reset": "重置", "Tags": "標籤", "Authors": "作者", - "Comics": "漫畫" + "Comics": "漫畫", + "Imported @a comics": "已匯入 @a 部漫畫" } } \ No newline at end of file diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index fee7a82..595f3fe 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -453,15 +453,15 @@ class _ImportComicsWidgetState extends State<_ImportComicsWidget> { String info = [ "Select a directory which contains the comic files.".tl, "Select a directory which contains the comic directories.".tl, - "Select a cbz/zip file.".tl, - "Select a directory which contains multiple cbz/zip files.".tl, + "Select an archive file (cbz, zip, 7z, cb7)".tl, + "Select a directory which contains multiple archive files.".tl, "Select an EhViewer database and a download folder.".tl ][type]; List importMethods = [ "Single Comic".tl, "Multiple Comics".tl, - "A cbz file".tl, - "Multiple cbz files".tl, + "An archive file".tl, + "Multiple archive files".tl, "EhViewer downloads".tl ]; @@ -493,7 +493,7 @@ class _ImportComicsWidgetState extends State<_ImportComicsWidget> { }, ); }), - if (type != 3) + if (type != 4) ListTile( title: Text("Add to favorites".tl), trailing: Select( @@ -507,7 +507,7 @@ class _ImportComicsWidgetState extends State<_ImportComicsWidget> { }, ), ).paddingHorizontal(8), - if (!App.isIOS && !App.isMacOS) + if (!App.isIOS && !App.isMacOS && type != 2 && type != 3) CheckboxListTile( enabled: true, title: Text("Copy to app local path".tl), diff --git a/lib/utils/cbz.dart b/lib/utils/cbz.dart index 7001da9..16eee6a 100644 --- a/lib/utils/cbz.dart +++ b/lib/utils/cbz.dart @@ -1,9 +1,10 @@ import 'dart:convert'; - +import 'package:flutter_7zip/flutter_7zip.dart'; import 'package:venera/foundation/app.dart'; import 'package:venera/foundation/comic_type.dart'; import 'package:venera/foundation/local.dart'; import 'package:venera/utils/ext.dart'; +import 'package:venera/utils/file_type.dart'; import 'package:venera/utils/io.dart'; import 'package:zip_flutter/zip_flutter.dart'; @@ -57,12 +58,33 @@ class ComicChapter { ComicChapter({required this.title, required this.start, required this.end}); } +/// Comic Book Archive. Currently supports CBZ, ZIP and 7Z formats. abstract class CBZ { + static Future checkType(File file) async { + var header = []; + await for (var bytes in file.openRead()) { + header.addAll(bytes); + if (header.length >= 32) break; + } + return detectFileType(header); + } + + static Future extractArchive(File file, Directory out) async { + var fileType = await checkType(file); + if (fileType.mime == 'application/zip') { + await ZipFile.openAndExtractAsync(file.path, out.path, 4); + } else if (fileType.mime == "application/x-7z-compressed") { + await SZArchive.extractIsolates(file.path, out.path, 4); + } else { + throw Exception('Unsupported archive type'); + } + } + static Future import(File file) async { var cache = Directory(FilePath.join(App.cachePath, 'cbz_import')); if (cache.existsSync()) cache.deleteSync(recursive: true); cache.createSync(); - await ZipFile.openAndExtractAsync(file.path, cache.path, 4); + await extractArchive(file, cache); var metaDataFile = File(FilePath.join(cache.path, 'metadata.json')); ComicMetaData? metaData; if (metaDataFile.existsSync()) { @@ -72,7 +94,7 @@ abstract class CBZ { } catch (_) {} } metaData ??= ComicMetaData( - title: file.name.replaceLast('.cbz', ''), + title: file.name.substring(0, file.name.lastIndexOf('.')), author: "", tags: [], ); @@ -86,6 +108,7 @@ abstract class CBZ { return !['jpg', 'jpeg', 'png', 'webp', 'gif', 'jpe'].contains(ext); }); if(files.isEmpty) { + cache.deleteSync(recursive: true); throw Exception('No images found in the archive'); } files.sort((a, b) => a.path.compareTo(b.path)); diff --git a/lib/utils/file_type.dart b/lib/utils/file_type.dart index 86538e1..9356be0 100644 --- a/lib/utils/file_type.dart +++ b/lib/utils/file_type.dart @@ -21,8 +21,17 @@ class FileType { } } +final _resolver = MimeTypeResolver() + // zip + ..addMagicNumber([0x50, 0x4B], 'application/zip') + // 7z + ..addMagicNumber([0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C], 'application/x-7z-compressed') + // rar + ..addMagicNumber([0x52, 0x61, 0x72, 0x21, 0x1A, 0x07], 'application/vnd.rar') +; + FileType detectFileType(List data) { - var mime = lookupMimeType('no-file', headerBytes: data); + var mime = _resolver.lookup('no-file', headerBytes: data); var ext = mime == null ? '' : extensionFromMime(mime); if(ext == 'jpe') { ext = 'jpg'; diff --git a/lib/utils/import_comic.dart b/lib/utils/import_comic.dart index 6904fcc..7473c06 100644 --- a/lib/utils/import_comic.dart +++ b/lib/utils/import_comic.dart @@ -20,7 +20,7 @@ class ImportComic { const ImportComic({this.selectedFolder, this.copyToLocal = true}); Future cbz() async { - var file = await selectFile(ext: ['cbz', 'zip']); + var file = await selectFile(ext: ['cbz', 'zip', '7z', 'cb7']); Map> imported = {}; if (file == null) { return false; @@ -42,7 +42,8 @@ class ImportComic { var dir = await picker.pickDirectory(directAccess: true); if (dir != null) { var files = (await dir.list().toList()).whereType().toList(); - files.removeWhere((e) => e.extension != 'cbz' && e.extension != 'zip'); + const supportedExtensions = ['cbz', 'zip', '7z', 'cb7']; + files.removeWhere((e) => !supportedExtensions.contains(e.extension)); Map> imported = {}; var controller = showLoadingDialog(App.rootContext, allowCancel: false); var comics = []; diff --git a/pubspec.lock b/pubspec.lock index 915d1df..478f5c9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -303,6 +303,15 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_7zip: + dependency: "direct main" + description: + path: "." + ref: "841ef5f77e5fdfd79e3eb2fa07ece4d46787285b" + resolved-ref: "841ef5f77e5fdfd79e3eb2fa07ece4d46787285b" + url: "https://github.com/wgh136/flutter_7zip" + source: git + version: "0.0.1" flutter_file_dialog: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index cc7661f..a2054c6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -73,6 +73,10 @@ dependencies: flutter_memory_info: ^0.0.1 syntax_highlight: ^0.4.0 text_scroll: ^0.2.0 + flutter_7zip: + git: + url: https://github.com/wgh136/flutter_7zip + ref: 841ef5f77e5fdfd79e3eb2fa07ece4d46787285b dev_dependencies: flutter_test: