mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-03-11 17:25:32 +00:00
getMangaChapterDirectory() now accepts an optional mangaMainDirectory to avoid calling getMangaMainDirectory() and thus getDirectory() twice in places where both methods are called successively.
341 lines
12 KiB
Dart
341 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,
|
|
mangaMainDirectory: mangaMainDirectory,
|
|
))!;
|
|
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) {
|
|
final mainDirectory = (await storageProvider.getDirectory())!;
|
|
for (var index = 0; index < pageUrls.length; index++) {
|
|
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);
|
|
});
|
|
}
|
|
}
|