support 7z;

fix #137
This commit is contained in:
2025-01-17 22:30:25 +08:00
parent bfd115046d
commit 5f36ef6ea3
7 changed files with 70 additions and 22 deletions

View File

@@ -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<String> 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),

View File

@@ -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<FileType> checkType(File file) async {
var header = <int>[];
await for (var bytes in file.openRead()) {
header.addAll(bytes);
if (header.length >= 32) break;
}
return detectFileType(header);
}
static Future<void> 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<LocalComic> 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));

View File

@@ -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<int> 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';

View File

@@ -20,7 +20,7 @@ class ImportComic {
const ImportComic({this.selectedFolder, this.copyToLocal = true});
Future<bool> cbz() async {
var file = await selectFile(ext: ['cbz', 'zip']);
var file = await selectFile(ext: ['cbz', 'zip', '7z', 'cb7']);
Map<String?, List<LocalComic>> 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<File>().toList();
files.removeWhere((e) => e.extension != 'cbz' && e.extension != 'zip');
const supportedExtensions = ['cbz', 'zip', '7z', 'cb7'];
files.removeWhere((e) => !supportedExtensions.contains(e.extension));
Map<String?, List<LocalComic>> imported = {};
var controller = showLoadingDialog(App.rootContext, allowCancel: false);
var comics = <LocalComic>[];