mangayomi-mirror/lib/modules/manga/download/providers/download_provider.dart
Enbiya Olgun 5e5f4d34cf Pass the correct list to convertToCBZ
e.url are the original remote URLs
Now the correct list of local image file paths is being passed to convertToCBZ.
2025-04-07 19:17:39 +02:00

338 lines
12 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:mangayomi/eval/model/m_bridge.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/page.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/download.dart';
import 'package:mangayomi/models/settings.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/l10n_providers.dart';
import 'package:mangayomi/providers/storage_provider.dart';
import 'package:mangayomi/router/router.dart';
import 'package:mangayomi/services/download_manager/m_downloader.dart';
import 'package:mangayomi/services/get_video_list.dart';
import 'package:mangayomi/services/get_chapter_pages.dart';
import 'package:mangayomi/services/http/m_client.dart';
import 'package:mangayomi/services/download_manager/m3u8/m3u8_downloader.dart';
import 'package:mangayomi/services/download_manager/m3u8/models/download.dart';
import 'package:mangayomi/utils/extensions/string_extensions.dart';
import 'package:mangayomi/utils/headers.dart';
import 'package:mangayomi/utils/reg_exp_matcher.dart';
import 'package:path/path.dart' as p;
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
part 'download_provider.g.dart';
@riverpod
Future<void> downloadChapter(
Ref ref, {
required Chapter chapter,
bool? useWifi,
}) async {
bool onlyOnWifi = useWifi ?? ref.watch(onlyOnWifiStateProvider);
final connectivity = await Connectivity().checkConnectivity();
final isOnWifi = connectivity.contains(ConnectivityResult.wifi) || connectivity.contains(ConnectivityResult.ethernet);
if (onlyOnWifi && !isOnWifi) {
botToast(navigatorKey.currentContext!.l10n.downloads_are_limited_to_wifi);
return;
}
final http = MClient.init(
reqcopyWith: {'useDartHttpClient': true, 'followRedirects': false},
);
List<PageUrl> pageUrls = [];
List<PageUrl> pages = [];
final StorageProvider storageProvider = StorageProvider();
await storageProvider.requestPermission();
final mangaMainDirectory = await storageProvider.getMangaMainDirectory(
chapter,
);
bool isOk = false;
final manga = chapter.manga.value!;
final chapterName = chapter.name!.replaceForbiddenCharacters(' ');
final itemType = chapter.manga.value!.itemType;
final chapterDirectory =
(await storageProvider.getMangaChapterDirectory(chapter))!;
await Directory(chapterDirectory.path).create(recursive: true);
Map<String, String> videoHeader = {};
Map<String, String> htmlHeader = {
"Priority": "u=0, i",
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36",
};
bool hasM3U8File = false;
bool nonM3U8File = false;
M3u8Downloader? m3u8Downloader;
Future<void> processConvert() async {
if (ref.watch(saveAsCBZArchiveStateProvider)) {
await ref.watch(
convertToCBZProvider(
chapterDirectory.path,
mangaMainDirectory!.path,
chapter.name!,
pages.map((e) => e.fileName!).toList(),
).future,
);
}
}
Future<void> setProgress(DownloadProgress progress) async {
if (progress.isCompleted && itemType == ItemType.manga) {
await processConvert();
}
final download = isar.downloads.getSync(chapter.id!);
if (download == null) {
final download = Download(
id: chapter.id,
succeeded:
progress.completed == 0
? 0
: (progress.completed / progress.total * 100).toInt(),
failed: 0,
total: 100,
isDownload: progress.isCompleted,
isStartDownload: true,
);
isar.writeTxnSync(() {
isar.downloads.putSync(download..chapter.value = chapter);
});
} else {
final download = isar.downloads.getSync(chapter.id!);
if (download != null && progress.total != 0) {
isar.writeTxnSync(() {
isar.downloads.putSync(
download
..succeeded =
progress.completed == 0
? 0
: (progress.completed / progress.total * 100).toInt()
..total = 100
..failed = 0
..isDownload = progress.isCompleted,
);
});
}
}
}
setProgress(DownloadProgress(0, 0, itemType));
void savePageUrls() {
final settings = isar.settings.getSync(227)!;
List<ChapterPageurls>? chapterPageUrls = [];
for (var chapterPageUrl in settings.chapterPageUrlsList ?? []) {
if (chapterPageUrl.chapterId != chapter.id) {
chapterPageUrls.add(chapterPageUrl);
}
}
final chapterPageHeaders =
pageUrls
.map((e) => e.headers == null ? null : jsonEncode(e.headers))
.toList();
chapterPageUrls.add(
ChapterPageurls()
..chapterId = chapter.id
..urls = pageUrls.map((e) => e.url).toList()
..chapterUrl = chapter.url
..headers =
chapterPageHeaders.first != null
? chapterPageHeaders.map((e) => e.toString()).toList()
: null,
);
isar.writeTxnSync(
() => isar.settings.putSync(
settings..chapterPageUrlsList = chapterPageUrls,
),
);
}
if (itemType == ItemType.manga) {
ref.read(getChapterPagesProvider(chapter: chapter).future).then((value) {
if (value.pageUrls.isNotEmpty) {
pageUrls = value.pageUrls;
isOk = true;
}
});
} else if (itemType == ItemType.anime) {
ref.read(getVideoListProvider(episode: chapter).future).then((value) async {
final m3u8Urls =
value.$1
.where(
(element) =>
element.originalUrl.endsWith(".m3u8") ||
element.originalUrl.endsWith(".m3u"),
)
.toList();
final nonM3u8Urls =
value.$1
.where((element) => element.originalUrl.isMediaVideo())
.toList();
nonM3U8File = nonM3u8Urls.isNotEmpty;
hasM3U8File = nonM3U8File ? false : m3u8Urls.isNotEmpty;
final videosUrls = nonM3U8File ? nonM3u8Urls : m3u8Urls;
if (videosUrls.isNotEmpty) {
if (hasM3U8File) {
m3u8Downloader = M3u8Downloader(
m3u8Url: videosUrls.first.url,
downloadDir: chapterDirectory.path,
headers: videosUrls.first.headers ?? {},
fileName: p.join(mangaMainDirectory!.path, "$chapterName.mp4"),
chapter: chapter,
);
} else {
pageUrls = [PageUrl(videosUrls.first.url)];
}
videoHeader.addAll(videosUrls.first.headers ?? {});
isOk = true;
}
});
} else if (itemType == ItemType.novel && chapter.url != null) {
final cookie = MClient.getCookiesPref(chapter.url!);
final headers =
itemType == ItemType.manga
? ref.watch(
headersProvider(source: manga.source!, lang: manga.lang!),
)
: itemType == ItemType.anime
? videoHeader
: htmlHeader;
if (cookie.isNotEmpty) {
final userAgent = isar.settings.getSync(227)!.userAgent!;
headers.addAll(cookie);
headers[HttpHeaders.userAgentHeader] = userAgent;
}
final res = await http.get(Uri.parse(chapter.url!), headers: headers);
if (res.headers.containsKey("Location")) {
pageUrls = [PageUrl(res.headers["Location"]!)];
} else {
pageUrls = [PageUrl(chapter.url!)];
}
isOk = true;
}
await Future.doWhile(() async {
await Future.delayed(const Duration(seconds: 1));
if (isOk == true) {
return false;
}
return true;
});
if (pageUrls.isNotEmpty) {
bool cbzFileExist =
await File(
p.join(mangaMainDirectory!.path, "${chapter.name}.cbz"),
).exists() &&
ref.watch(saveAsCBZArchiveStateProvider);
bool mp4FileExist =
await File(
p.join(mangaMainDirectory.path, "$chapterName.mp4"),
).exists();
bool htmlFileExist =
await File(
p.join(mangaMainDirectory.path, "$chapterName.html"),
).exists();
if (!cbzFileExist && itemType == ItemType.manga ||
!mp4FileExist && itemType == ItemType.anime ||
!htmlFileExist && itemType == ItemType.novel) {
for (var index = 0; index < pageUrls.length; index++) {
final mainDirectory = (await storageProvider.getDirectory())!;
if (Platform.isAndroid) {
if (!(await File(p.join(mainDirectory.path, ".nomedia")).exists())) {
await File(p.join(mainDirectory.path, ".nomedia")).create();
}
}
final page = pageUrls[index];
final cookie = MClient.getCookiesPref(page.url);
final headers =
itemType == ItemType.manga
? ref.watch(
headersProvider(source: manga.source!, lang: manga.lang!),
)
: itemType == ItemType.anime
? videoHeader
: htmlHeader;
if (cookie.isNotEmpty) {
final userAgent = isar.settings.getSync(227)!.userAgent!;
headers.addAll(cookie);
headers[HttpHeaders.userAgentHeader] = userAgent;
}
Map<String, String> pageHeaders = headers;
pageHeaders.addAll(page.headers ?? {});
if (itemType == ItemType.manga) {
final file = File(
p.join(chapterDirectory.path, "${padIndex(index + 1)}.jpg"),
);
if (!file.existsSync()) {
pages.add(
PageUrl(
page.url.trim().trimLeft().trimRight(),
headers: pageHeaders,
fileName: p.join(
chapterDirectory.path,
"${padIndex(index + 1)}.jpg",
),
),
);
}
} else if (itemType == ItemType.anime) {
final file = File(
p.join(mangaMainDirectory.path, "$chapterName.mp4"),
);
if (!file.existsSync()) {
pages.add(
PageUrl(
page.url.trim().trimLeft().trimRight(),
headers: pageHeaders,
fileName: p.join(mangaMainDirectory.path, "$chapterName.mp4"),
),
);
}
} else {
final file = File(p.join(chapterDirectory.path, "$chapterName.html"));
if (!file.existsSync()) {
pages.add(
PageUrl(
page.url.trim().trimLeft().trimRight(),
headers: pageHeaders,
fileName: p.join(chapterDirectory.path, "$chapterName.html"),
),
);
}
}
}
}
if (pages.isEmpty && pageUrls.isNotEmpty) {
await processConvert();
savePageUrls();
final download = Download(
id: chapter.id,
succeeded: 0,
failed: 0,
total: 0,
isDownload: true,
isStartDownload: false,
);
isar.writeTxnSync(() {
isar.downloads.putSync(download..chapter.value = chapter);
});
} else {
savePageUrls();
await MDownloader(chapter: chapter, pageUrls: pages).download((progress) {
setProgress(progress);
});
}
} else if (hasM3U8File) {
await m3u8Downloader?.download((progress) {
setProgress(progress);
});
}
}