feature: convert downloaded file to cbz file & read cbz file

This commit is contained in:
kodjomoustapha 2023-06-04 20:10:40 +01:00
parent 05bada5661
commit 4f0df88bc2
10 changed files with 294 additions and 36 deletions

View file

@ -50,10 +50,10 @@ bool _isImageFile(String path) {
}
bool _isArchiveFile(String path) {
List<String> imageExtensions = ['.cbz', '.zip', 'cbt', 'tar'];
List<String> archiveExtensions = ['.cbz', '.zip', 'cbt', 'tar'];
String extension = path.toLowerCase();
for (String imageExtension in imageExtensions) {
if (extension.endsWith(imageExtension)) {
for (String archiveExtension in archiveExtensions) {
if (extension.endsWith(archiveExtension)) {
return true;
}
}

View file

@ -0,0 +1,54 @@
import 'dart:io';
import 'package:archive/archive_io.dart';
import 'package:flutter/foundation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'convert_to_cbz.g.dart';
@riverpod
Future<List<String>> convertToCBZ(ConvertToCBZRef ref, String chapterDir,
String mangaDir, String chapterName, List<String> pageList) async {
Map<String, dynamic> data = {
"chapterDir": chapterDir,
"mangaDir": mangaDir,
"chapterName": chapterName,
"pageList": pageList
};
return compute(_convertToCBZ, data);
}
List<String> _convertToCBZ(Map<String, dynamic> data) {
List<String> imagesPaths = [];
String chapterDir = data["chapterDir"]!;
String mangaDir = data["mangaDir"]!;
String chapterName = data["chapterName"]!;
List<String> pageList = data["pageList"]!;
if (Directory(chapterDir).existsSync()) {
List<FileSystemEntity> entities = Directory(chapterDir).listSync();
for (FileSystemEntity entity in entities) {
if (entity is File) {
String path = entity.path;
if (path.endsWith('.jpg')) {
imagesPaths.add(path);
}
}
}
imagesPaths.sort(
(a, b) {
return a.toString().compareTo(b.toString());
},
);
}
if (imagesPaths.isNotEmpty && pageList.length == imagesPaths.length) {
var encoder = ZipFileEncoder();
encoder.create("$mangaDir/$chapterName.cbz");
for (var path in imagesPaths) {
encoder.addFile(File(path));
}
encoder.close();
Directory(chapterDir).deleteSync(recursive: true);
}
return imagesPaths;
}

View file

@ -0,0 +1,137 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'convert_to_cbz.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$convertToCBZHash() => r'b421d288e9cd1fca3079ccb5d5702ee2ad4cdfe3';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
typedef ConvertToCBZRef = AutoDisposeFutureProviderRef<List<String>>;
/// See also [convertToCBZ].
@ProviderFor(convertToCBZ)
const convertToCBZProvider = ConvertToCBZFamily();
/// See also [convertToCBZ].
class ConvertToCBZFamily extends Family<AsyncValue<List<String>>> {
/// See also [convertToCBZ].
const ConvertToCBZFamily();
/// See also [convertToCBZ].
ConvertToCBZProvider call(
String chapterDir,
String mangaDir,
String chapterName,
List<String> pageList,
) {
return ConvertToCBZProvider(
chapterDir,
mangaDir,
chapterName,
pageList,
);
}
@override
ConvertToCBZProvider getProviderOverride(
covariant ConvertToCBZProvider provider,
) {
return call(
provider.chapterDir,
provider.mangaDir,
provider.chapterName,
provider.pageList,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'convertToCBZProvider';
}
/// See also [convertToCBZ].
class ConvertToCBZProvider extends AutoDisposeFutureProvider<List<String>> {
/// See also [convertToCBZ].
ConvertToCBZProvider(
this.chapterDir,
this.mangaDir,
this.chapterName,
this.pageList,
) : super.internal(
(ref) => convertToCBZ(
ref,
chapterDir,
mangaDir,
chapterName,
pageList,
),
from: convertToCBZProvider,
name: r'convertToCBZProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$convertToCBZHash,
dependencies: ConvertToCBZFamily._dependencies,
allTransitiveDependencies:
ConvertToCBZFamily._allTransitiveDependencies,
);
final String chapterDir;
final String mangaDir;
final String chapterName;
final List<String> pageList;
@override
bool operator ==(Object other) {
return other is ConvertToCBZProvider &&
other.chapterDir == chapterDir &&
other.mangaDir == mangaDir &&
other.chapterName == chapterName &&
other.pageList == pageList;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, chapterDir.hashCode);
hash = _SystemHash.combine(hash, mangaDir.hashCode);
hash = _SystemHash.combine(hash, chapterName.hashCode);
hash = _SystemHash.combine(hash, pageList.hashCode);
return _SystemHash.finish(hash);
}
}
// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions

View file

@ -4,6 +4,7 @@ import 'package:isar/isar.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/download.dart';
import 'package:mangayomi/modules/manga/download/providers/convert_to_cbz.dart';
import 'package:mangayomi/modules/more/settings/downloads/providers/downloads_state_provider.dart';
import 'package:mangayomi/providers/storage_provider.dart';
import 'package:mangayomi/services/get_chapter_url.dart';
@ -24,6 +25,7 @@ Future<List<String>> downloadChapter(
final StorageProvider storageProvider = StorageProvider();
await storageProvider.requestPermission();
final tempDir = await getTemporaryDirectory();
final mangaDir = await storageProvider.getMangaMainDirectory(chapter);
bool onlyOnWifi = useWifi ?? ref.watch(onlyOnWifiStateProvider);
Directory? path;
bool isOk = false;
@ -125,14 +127,21 @@ Future<List<String>> downloadChapter(
taskIds: pageUrls,
isStartDownload: false,
chapterId: chapter.id);
await ref.watch(convertToCBZProvider(
path.path, mangaDir!.path, chapter.name!, pageUrls)
.future);
isar.writeTxnSync(() {
isar.downloads.putSync(model..chapter.value = chapter);
});
} else {
await FileDownloader().downloadBatch(
tasks,
batchProgressCallback: (succeeded, failed) {
batchProgressCallback: (succeeded, failed) async {
if (succeeded == tasks.length) {
await ref.watch(convertToCBZProvider(
path!.path, mangaDir!.path, chapter.name!, pageUrls)
.future);
}
bool isEmpty = isar.downloads
.filter()
.chapterIdEqualTo(chapter.id!)
@ -165,12 +174,14 @@ Future<List<String>> downloadChapter(
},
taskProgressCallback: (taskProgress) async {
if (taskProgress.progress == 1.0) {
await File(
"${tempDir.path}/${taskProgress.task.directory}/${taskProgress.task.filename}")
.copy("${path!.path}/${taskProgress.task.filename}");
await File(
"${tempDir.path}/${taskProgress.task.directory}/${taskProgress.task.filename}")
.delete();
if (Platform.isAndroid) {
await File(
"${tempDir.path}/${taskProgress.task.directory}/${taskProgress.task.filename}")
.copy("${path!.path}/${taskProgress.task.filename}");
await File(
"${tempDir.path}/${taskProgress.task.directory}/${taskProgress.task.filename}")
.delete();
}
}
},
);

View file

@ -6,7 +6,7 @@ part of 'download_provider.dart';
// RiverpodGenerator
// **************************************************************************
String _$downloadChapterHash() => r'07e6c9782618562329005e15ce973061bf70d441';
String _$downloadChapterHash() => r'b5c9bab624536bc4ad0e0c067773f5e591aa0cc1';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -1,4 +1,5 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:extended_image/extended_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -14,6 +15,7 @@ class ImageViewHorizontal extends ConsumerWidget {
final String chapter;
final Directory path;
final bool isLocale;
final Uint8List? localImage;
final Widget? Function(ExtendedImageState state) loadStateChanged;
final Function(ExtendedImageGestureState state) onDoubleTap;
final GestureConfig Function(ExtendedImageState state)
@ -31,20 +33,31 @@ class ImageViewHorizontal extends ConsumerWidget {
required this.onDoubleTap,
required this.initGestureConfigHandler,
required this.isLocale,
this.localImage,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
return isLocale
? ExtendedImage.file(
File("${path.path}" "${padIndex(index + 1)}.jpg"),
clearMemoryCacheWhenDispose: true,
enableMemoryCache: false,
mode: ExtendedImageMode.gesture,
initGestureConfigHandler: initGestureConfigHandler,
onDoubleTap: onDoubleTap,
loadStateChanged: loadStateChanged,
)
? localImage != null
? ExtendedImage.memory(
localImage!,
clearMemoryCacheWhenDispose: true,
enableMemoryCache: false,
mode: ExtendedImageMode.gesture,
initGestureConfigHandler: initGestureConfigHandler,
onDoubleTap: onDoubleTap,
loadStateChanged: loadStateChanged,
)
: ExtendedImage.file(
File("${path.path}" "${padIndex(index + 1)}.jpg"),
clearMemoryCacheWhenDispose: true,
enableMemoryCache: false,
mode: ExtendedImageMode.gesture,
initGestureConfigHandler: initGestureConfigHandler,
onDoubleTap: onDoubleTap,
loadStateChanged: loadStateChanged,
)
: ExtendedImage.network(
url,
headers: ref.watch(headersProvider(source: source)),

View file

@ -1,4 +1,5 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:extended_image/extended_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -17,6 +18,7 @@ class ImageViewVertical extends ConsumerWidget {
final String source;
final String chapter;
final Directory path;
final Uint8List? localImage;
const ImageViewVertical({
super.key,
@ -28,6 +30,7 @@ class ImageViewVertical extends ConsumerWidget {
required this.source,
required this.length,
required this.isLocale,
this.localImage,
});
@override
@ -42,11 +45,18 @@ class ImageViewVertical extends ConsumerWidget {
height: MediaQuery.of(context).padding.top,
),
isLocale
? ExtendedImage.file(
fit: BoxFit.contain,
clearMemoryCacheWhenDispose: true,
enableMemoryCache: false,
File('${path.path}${padIndex(index + 1)}.jpg'))
? localImage != null
? ExtendedImage.memory(
localImage!,
fit: BoxFit.contain,
clearMemoryCacheWhenDispose: true,
enableMemoryCache: false,
)
: ExtendedImage.file(
fit: BoxFit.contain,
clearMemoryCacheWhenDispose: true,
enableMemoryCache: false,
File('${path.path}${padIndex(index + 1)}.jpg'))
: ExtendedImage.network(url,
headers: ref.watch(headersProvider(source: source)),
handleLoadingProgress: true,

View file

@ -38,7 +38,7 @@ class MangaReaderView extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky,
overlays: []);
final chapterData = ref.watch(GetChapterUrlProvider(
final chapterData = ref.watch(getChapterUrlProvider(
chapter: chapter,
));
final readerController =
@ -77,6 +77,7 @@ class MangaReaderView extends ConsumerWidget {
readerController: readerController,
isLocaleList: data.isLocaleList,
chapter: chapter,
localImages: data.localImages,
);
},
error: (error, stackTrace) => Scaffold(
@ -138,12 +139,14 @@ class MangaChapterPageGallery extends ConsumerStatefulWidget {
required this.url,
required this.readerController,
required this.isLocaleList,
required this.chapter});
required this.chapter,
required this.localImages});
final ReaderController readerController;
final Directory path;
final List url;
final List<bool> isLocaleList;
final Chapter chapter;
final List<Uint8List> localImages;
@override
ConsumerState createState() {
@ -921,6 +924,9 @@ class _MangaChapterPageGalleryState
},
onDoubleTap: () {},
child: ImageViewVertical(
localImage: widget.localImages.isNotEmpty
? widget.localImages[index]
: null,
titleManga: widget.readerController.getMangaName(),
source: widget.readerController
.getSourceName()
@ -960,6 +966,9 @@ class _MangaChapterPageGalleryState
},
itemBuilder: (BuildContext context, int index) {
return ImageViewHorizontal(
localImage: widget.localImages.isNotEmpty
? widget.localImages[index]
: null,
titleManga: widget.readerController.getMangaName(),
source: widget.readerController
.getSourceName()

View file

@ -1,10 +1,12 @@
// ignore_for_file: depend_o
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/local_reader/providers/local_reader_providers.dart';
import 'package:mangayomi/providers/storage_provider.dart';
import 'package:mangayomi/sources/multisrc/heancms/heancms.dart';
import 'package:mangayomi/sources/multisrc/madara/src/madara.dart';
@ -24,8 +26,12 @@ class GetChapterUrlModel {
Directory? path;
List<String> pageUrls = [];
List<bool> isLocaleList = [];
List<Uint8List> localImages = [];
GetChapterUrlModel(
{required this.path, required this.pageUrls, required this.isLocaleList});
{required this.path,
required this.pageUrls,
required this.isLocaleList,
required this.localImages});
}
@riverpod
@ -42,8 +48,11 @@ Future<GetChapterUrlModel> getChapterUrl(
final isarPageUrls = settings!.chapterPageUrlsList!
.where((element) => element.chapterId == chapter.id);
final incognitoMode = ref.watch(incognitoModeStateProvider);
path = await StorageProvider().getMangaChapterDirectory(chapter);
final storageProvider = StorageProvider();
path = await storageProvider.getMangaChapterDirectory(chapter);
final mangaDirectory = await storageProvider.getMangaMainDirectory(chapter);
List<Uint8List> localImages = [];
if (isarPageUrls.isNotEmpty &&
isarPageUrls.first.urls != null &&
isarPageUrls.first.urls!.isNotEmpty) {
@ -105,6 +114,7 @@ Future<GetChapterUrlModel> getChapterUrl(
else if (getMangaTypeSource(source) == TypeSource.heancms) {
pageUrls = await HeanCms().getChapterUrl(chapter: chapter, ref: ref);
}
/***********/
/*madara*/
/***********/
@ -127,15 +137,29 @@ Future<GetChapterUrlModel> getChapterUrl(
isar.writeTxnSync(() => isar.settings
.putSync(settings..chapterPageUrlsList = chapterPageUrls));
}
for (var i = 0; i < pageUrls.length; i++) {
if (await File("${path!.path}" "${padIndex(i + 1)}.jpg").exists()) {
if (await File("${mangaDirectory!.path}${chapter.name}.cbz").exists()) {
final local = await ref.watch(getArchiveDataFromFileProvider(
"${mangaDirectory.path}${chapter.name}.cbz")
.future);
for (var image in local.images!) {
localImages.add(image.image!);
isLocaleList.add(true);
} else {
isLocaleList.add(false);
}
} else {
for (var i = 0; i < pageUrls.length; i++) {
if (await File("${path!.path}" "${padIndex(i + 1)}.jpg").exists()) {
isLocaleList.add(true);
} else {
isLocaleList.add(false);
}
}
}
}
return GetChapterUrlModel(
path: path, pageUrls: pageUrls, isLocaleList: isLocaleList);
path: path,
pageUrls: pageUrls,
isLocaleList: isLocaleList,
localImages: localImages);
}

View file

@ -6,7 +6,7 @@ part of 'get_chapter_url.dart';
// RiverpodGenerator
// **************************************************************************
String _$getChapterUrlHash() => r'2ac689860d288da631afbf39ab7ad2df7e2b99f3';
String _$getChapterUrlHash() => r'37d070779ae79c31b82820c3a78af825db45fc1d';
/// Copied from Dart SDK
class _SystemHash {