mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-03-11 17:25:32 +00:00
misc changes
- remove cronet_http & cupertino_http - use rhttp package as default http client - fix #198 #200 crashes on multiple downloads - fix #162 #102 unable to download with forbidden characters in the name (as it is fixed this can cause reading problems concerning chapters downloaded before this version) - now supports all features on all platforms such as VPNs and HTTP proxies thanks to rhttp package
This commit is contained in:
parent
d177900394
commit
b10c3f3a22
53 changed files with 370 additions and 1224 deletions
11
.gitignore
vendored
11
.gitignore
vendored
|
|
@ -32,6 +32,11 @@ migrate_working_dir/
|
|||
.pub/
|
||||
/build/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
.vscode/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
|
|
@ -42,3 +47,9 @@ app.*.map.json
|
|||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
|
||||
# FVM Version Cache
|
||||
.fvm/
|
||||
|
||||
# FVM Version
|
||||
.fvmrc
|
||||
23
.vscode/settings.json
vendored
23
.vscode/settings.json
vendored
|
|
@ -1,13 +1,14 @@
|
|||
{
|
||||
"java.configuration.updateBuildConfiguration": "automatic",
|
||||
"githubPullRequests.ignoredPullRequestBranches": [
|
||||
"main"
|
||||
],
|
||||
"rust-analyzer.linkedProjects": [
|
||||
".\\rust\\Cargo.toml"
|
||||
],
|
||||
"files.associations": {
|
||||
"*.html.erb": "erb",
|
||||
"cstring": "cpp"
|
||||
}
|
||||
"java.configuration.updateBuildConfiguration": "automatic",
|
||||
"githubPullRequests.ignoredPullRequestBranches": [
|
||||
"main"
|
||||
],
|
||||
"rust-analyzer.linkedProjects": [
|
||||
".\\rust\\Cargo.toml"
|
||||
],
|
||||
"files.associations": {
|
||||
"*.html.erb": "erb",
|
||||
"cstring": "cpp"
|
||||
},
|
||||
"dart.flutterSdkPath": ".fvm/versions/3.22.3"
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import 'package:mangayomi/modules/more/settings/appearance/providers/theme_mode_
|
|||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:mangayomi/src/rust/frb_generated.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:rhttp/rhttp.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
late Isar isar;
|
||||
|
|
@ -33,6 +34,7 @@ void main(List<String> args) async {
|
|||
}
|
||||
MediaKit.ensureInitialized();
|
||||
await RustLib.init();
|
||||
await Rhttp.init();
|
||||
if (!(Platform.isAndroid || Platform.isIOS)) {
|
||||
await windowManager.ensureInitialized();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -188,8 +188,6 @@ class Settings {
|
|||
|
||||
bool? useLibass;
|
||||
|
||||
bool? useNativeHttpClient;
|
||||
|
||||
Settings(
|
||||
{this.id = 227,
|
||||
this.displayType = DisplayType.compactGrid,
|
||||
|
|
@ -272,8 +270,7 @@ class Settings {
|
|||
this.mangaGridSize,
|
||||
this.animeGridSize,
|
||||
this.disableSectionType = SectionType.all,
|
||||
this.useLibass = true,
|
||||
this.useNativeHttpClient = false});
|
||||
this.useLibass = true});
|
||||
|
||||
Settings.fromJson(Map<String, dynamic> json) {
|
||||
animatePageTransitions = json['animatePageTransitions'];
|
||||
|
|
@ -423,7 +420,6 @@ class Settings {
|
|||
disableSectionType =
|
||||
SectionType.values[json['disableSectionType'] ?? SectionType.all];
|
||||
useLibass = json['useLibass'];
|
||||
useNativeHttpClient = json['useNativeHttpClient'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
|
|
@ -532,8 +528,7 @@ class Settings {
|
|||
'mangaGridSize': mangaGridSize,
|
||||
'animeGridSize': animeGridSize,
|
||||
'disableSectionType': disableSectionType.index,
|
||||
'useLibass': useLibass,
|
||||
'useNativeHttpClient': useNativeHttpClient
|
||||
'useLibass': useLibass
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -456,18 +456,13 @@ const SettingsSchema = CollectionSchema(
|
|||
name: r'useLibass',
|
||||
type: IsarType.bool,
|
||||
),
|
||||
r'useNativeHttpClient': PropertySchema(
|
||||
id: 83,
|
||||
name: r'useNativeHttpClient',
|
||||
type: IsarType.bool,
|
||||
),
|
||||
r'usePageTapZones': PropertySchema(
|
||||
id: 84,
|
||||
id: 83,
|
||||
name: r'usePageTapZones',
|
||||
type: IsarType.bool,
|
||||
),
|
||||
r'userAgent': PropertySchema(
|
||||
id: 85,
|
||||
id: 84,
|
||||
name: r'userAgent',
|
||||
type: IsarType.string,
|
||||
)
|
||||
|
|
@ -922,9 +917,8 @@ void _settingsSerialize(
|
|||
writer.writeBool(offsets[80], object.themeIsDark);
|
||||
writer.writeBool(offsets[81], object.updateProgressAfterReading);
|
||||
writer.writeBool(offsets[82], object.useLibass);
|
||||
writer.writeBool(offsets[83], object.useNativeHttpClient);
|
||||
writer.writeBool(offsets[84], object.usePageTapZones);
|
||||
writer.writeString(offsets[85], object.userAgent);
|
||||
writer.writeBool(offsets[83], object.usePageTapZones);
|
||||
writer.writeString(offsets[84], object.userAgent);
|
||||
}
|
||||
|
||||
Settings _settingsDeserialize(
|
||||
|
|
@ -1086,9 +1080,8 @@ Settings _settingsDeserialize(
|
|||
themeIsDark: reader.readBoolOrNull(offsets[80]),
|
||||
updateProgressAfterReading: reader.readBoolOrNull(offsets[81]),
|
||||
useLibass: reader.readBoolOrNull(offsets[82]),
|
||||
useNativeHttpClient: reader.readBoolOrNull(offsets[83]),
|
||||
usePageTapZones: reader.readBoolOrNull(offsets[84]),
|
||||
userAgent: reader.readStringOrNull(offsets[85]),
|
||||
usePageTapZones: reader.readBoolOrNull(offsets[83]),
|
||||
userAgent: reader.readStringOrNull(offsets[84]),
|
||||
);
|
||||
object.chapterFilterBookmarkedList =
|
||||
reader.readObjectList<ChapterFilterBookmarked>(
|
||||
|
|
@ -1382,8 +1375,6 @@ P _settingsDeserializeProp<P>(
|
|||
case 83:
|
||||
return (reader.readBoolOrNull(offset)) as P;
|
||||
case 84:
|
||||
return (reader.readBoolOrNull(offset)) as P;
|
||||
case 85:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
default:
|
||||
throw IsarError('Unknown property with id $propertyId');
|
||||
|
|
@ -6849,34 +6840,6 @@ extension SettingsQueryFilter
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterFilterCondition>
|
||||
useNativeHttpClientIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
property: r'useNativeHttpClient',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterFilterCondition>
|
||||
useNativeHttpClientIsNotNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNotNull(
|
||||
property: r'useNativeHttpClient',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterFilterCondition>
|
||||
useNativeHttpClientEqualTo(bool? value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'useNativeHttpClient',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterFilterCondition>
|
||||
usePageTapZonesIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
|
|
@ -8092,19 +8055,6 @@ extension SettingsQuerySortBy on QueryBuilder<Settings, Settings, QSortBy> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterSortBy> sortByUseNativeHttpClient() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'useNativeHttpClient', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterSortBy>
|
||||
sortByUseNativeHttpClientDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'useNativeHttpClient', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterSortBy> sortByUsePageTapZones() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'usePageTapZones', Sort.asc);
|
||||
|
|
@ -9005,19 +8955,6 @@ extension SettingsQuerySortThenBy
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterSortBy> thenByUseNativeHttpClient() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'useNativeHttpClient', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterSortBy>
|
||||
thenByUseNativeHttpClientDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'useNativeHttpClient', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterSortBy> thenByUsePageTapZones() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'usePageTapZones', Sort.asc);
|
||||
|
|
@ -9486,12 +9423,6 @@ extension SettingsQueryWhereDistinct
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QDistinct> distinctByUseNativeHttpClient() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'useNativeHttpClient');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QDistinct> distinctByUsePageTapZones() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'usePageTapZones');
|
||||
|
|
@ -10068,13 +9999,6 @@ extension SettingsQueryProperty
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, bool?, QQueryOperations>
|
||||
useNativeHttpClientProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'useNativeHttpClient');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, bool?, QQueryOperations> usePageTapZonesProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'usePageTapZones');
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:mangayomi/models/page.dart';
|
||||
import 'package:mangayomi/modules/more/settings/advanced/providers/native_http_client.dart';
|
||||
import 'package:mangayomi/services/background_downloader/background_downloader.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
|
|
@ -38,14 +37,16 @@ Future<List<PageUrl>> downloadChapter(
|
|||
bool isOk = false;
|
||||
final manga = chapter.manga.value!;
|
||||
final path1 = await storageProvider.getDirectory();
|
||||
final regExp = RegExp(r'[^a-zA-Z0-9 .()\-\s]');
|
||||
final forbiddenCharacters =
|
||||
RegExp(r'[\\/:*?"<>|\0]|(^CON$|^PRN$|^AUX$|^NUL$|^COM[1-9]$|^LPT[1-9]$)');
|
||||
String scanlator = chapter.scanlator!.isNotEmpty
|
||||
? "${chapter.scanlator!.replaceAll(regExp, '_')}_"
|
||||
? "${chapter.scanlator!.replaceAll(forbiddenCharacters, '_')}_"
|
||||
: "";
|
||||
final chapterName = chapter.name!.replaceAll(forbiddenCharacters, ' ');
|
||||
|
||||
final isManga = chapter.manga.value!.isManga!;
|
||||
final finalPath =
|
||||
"downloads/${isManga ? "Manga" : "Anime"}/${manga.source} (${manga.lang!.toUpperCase()})/${manga.name!.replaceAll(regExp, '_')}${isManga ? "/$scanlator${chapter.name!.replaceAll(regExp, '_')}" : ""}";
|
||||
"downloads/${isManga ? "Manga" : "Anime"}/${manga.source} (${manga.lang!.toUpperCase()})/${manga.name!.replaceAll(forbiddenCharacters, '_')}${isManga ? "/$scanlator${chapter.name!.replaceAll(forbiddenCharacters, '_')}" : ""}";
|
||||
path = Directory("${path1!.path}$finalPath/");
|
||||
Map<String, String> videoHeader = {};
|
||||
|
||||
|
|
@ -110,8 +111,7 @@ Future<List<PageUrl>> downloadChapter(
|
|||
bool cbzFileExist =
|
||||
await File("${mangaDir!.path}${chapter.name}.cbz").exists() &&
|
||||
ref.watch(saveAsCBZArchiveStateProvider);
|
||||
bool mp4FileExist =
|
||||
await File("${mangaDir.path}${chapter.name}.mp4").exists();
|
||||
bool mp4FileExist = await File("${mangaDir.path}$chapterName.mp4").exists();
|
||||
if (!cbzFileExist && isManga || !mp4FileExist && !isManga) {
|
||||
for (var index = 0; index < pageUrls.length; index++) {
|
||||
final path2 = Directory("${path1.path}downloads/");
|
||||
|
|
@ -119,8 +119,8 @@ Future<List<PageUrl>> downloadChapter(
|
|||
Directory("${path1.path}downloads/${isManga ? "Manga" : "Anime"}/");
|
||||
final path4 = Directory(
|
||||
"${path5.path}${manga.source} (${manga.lang!.toUpperCase()})/");
|
||||
final path3 =
|
||||
Directory("${path4.path}${manga.name!.replaceAll(regExp, '_')}/");
|
||||
final path3 = Directory(
|
||||
"${path4.path}${manga.name!.replaceAll(forbiddenCharacters, '_')}/");
|
||||
|
||||
if (!(await path1.exists())) {
|
||||
await path1.create();
|
||||
|
|
@ -153,80 +153,89 @@ Future<List<PageUrl>> downloadChapter(
|
|||
headers.addAll(cookie);
|
||||
headers[HttpHeaders.userAgentHeader] = userAgent;
|
||||
}
|
||||
Map<String, String> pageHeaders = headers;
|
||||
pageHeaders.addAll(page.headers ?? {});
|
||||
|
||||
if (isManga) {
|
||||
if ((await path.exists())) {
|
||||
if (await File("${path.path}" "${padIndex(index + 1)}.jpg")
|
||||
.exists()) {
|
||||
} else {
|
||||
Map<String, String> pageHeaders = headers;
|
||||
pageHeaders.addAll(page.headers ?? {});
|
||||
tasks.add(DownloadTask(
|
||||
taskId: page.url,
|
||||
headers: pageHeaders,
|
||||
url: page.url.trim().trimLeft().trimRight(),
|
||||
filename: "${padIndex(index + 1)}.jpg",
|
||||
baseDirectory: BaseDirectory.temporary,
|
||||
directory: 'Mangayomi/$finalPath',
|
||||
updates: Updates.statusAndProgress,
|
||||
retries: 3,
|
||||
allowPause: true,
|
||||
requiresWiFi: onlyOnWifi));
|
||||
}
|
||||
final file = File(
|
||||
"${tempDir.path}/Mangayomi/$finalPath/${padIndex(index + 1)}.jpg");
|
||||
if (file.existsSync()) {
|
||||
await file.copy("${path.path}${padIndex(index + 1)}.jpg");
|
||||
await file.delete();
|
||||
} else {
|
||||
await path.create();
|
||||
if (await File("${path.path}" "${padIndex(index + 1)}.jpg")
|
||||
.exists()) {
|
||||
if ((await path.exists())) {
|
||||
if (await File("${path.path}${padIndex(index + 1)}.jpg")
|
||||
.exists()) {
|
||||
} else {
|
||||
tasks.add(DownloadTask(
|
||||
taskId: page.url,
|
||||
headers: pageHeaders,
|
||||
url: page.url.trim().trimLeft().trimRight(),
|
||||
filename: "${padIndex(index + 1)}.jpg",
|
||||
baseDirectory: BaseDirectory.temporary,
|
||||
directory: 'Mangayomi/$finalPath',
|
||||
updates: Updates.statusAndProgress,
|
||||
retries: 3,
|
||||
allowPause: true,
|
||||
requiresWiFi: onlyOnWifi));
|
||||
}
|
||||
} else {
|
||||
Map<String, String> pageHeaders = headers;
|
||||
pageHeaders.addAll(page.headers ?? {});
|
||||
tasks.add(DownloadTask(
|
||||
taskId: page.url,
|
||||
headers: pageHeaders,
|
||||
url: page.url.trim().trimLeft().trimRight(),
|
||||
filename: "${padIndex(index + 1)}.jpg",
|
||||
baseDirectory: BaseDirectory.temporary,
|
||||
directory: 'Mangayomi/$finalPath',
|
||||
updates: Updates.statusAndProgress,
|
||||
allowPause: true,
|
||||
retries: 3,
|
||||
requiresWiFi: onlyOnWifi));
|
||||
await path.create();
|
||||
if (await File("${path.path}" "${padIndex(index + 1)}.jpg")
|
||||
.exists()) {
|
||||
} else {
|
||||
tasks.add(DownloadTask(
|
||||
taskId: page.url,
|
||||
headers: pageHeaders,
|
||||
url: page.url.trim().trimLeft().trimRight(),
|
||||
filename: "${padIndex(index + 1)}.jpg",
|
||||
baseDirectory: BaseDirectory.temporary,
|
||||
directory: 'Mangayomi/$finalPath',
|
||||
updates: Updates.statusAndProgress,
|
||||
allowPause: true,
|
||||
retries: 3,
|
||||
requiresWiFi: onlyOnWifi));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((await path.exists())) {
|
||||
if (await File("${path.path}${chapter.name}.mp4").exists()) {
|
||||
} else {
|
||||
Map<String, String> pageHeaders = headers;
|
||||
pageHeaders.addAll(page.headers ?? {});
|
||||
tasks.add(DownloadTask(
|
||||
taskId: page.url,
|
||||
headers: pageHeaders,
|
||||
url: page.url.trim().trimLeft().trimRight(),
|
||||
filename: "${chapter.name}.mp4",
|
||||
baseDirectory: BaseDirectory.temporary,
|
||||
directory: 'Mangayomi/$finalPath',
|
||||
updates: Updates.statusAndProgress,
|
||||
allowPause: true,
|
||||
retries: 3,
|
||||
requiresWiFi: onlyOnWifi));
|
||||
}
|
||||
final file =
|
||||
File("${tempDir.path}/Mangayomi/$finalPath/$chapterName.mp4");
|
||||
if (file.existsSync()) {
|
||||
await file.copy("${path.path}$chapterName.mp4");
|
||||
await file.delete();
|
||||
} else {
|
||||
await path.create();
|
||||
if (await File("${path.path}${chapter.name}.mp4").exists()) {
|
||||
if ((await path.exists())) {
|
||||
if (await File("${path.path}$chapterName.mp4").exists()) {
|
||||
} else {
|
||||
tasks.add(DownloadTask(
|
||||
taskId: page.url,
|
||||
headers: pageHeaders,
|
||||
url: page.url.trim().trimLeft().trimRight(),
|
||||
filename: "$chapterName.mp4",
|
||||
baseDirectory: BaseDirectory.temporary,
|
||||
directory: 'Mangayomi/$finalPath',
|
||||
updates: Updates.statusAndProgress,
|
||||
allowPause: true,
|
||||
retries: 3,
|
||||
requiresWiFi: onlyOnWifi));
|
||||
}
|
||||
} else {
|
||||
Map<String, String> pageHeaders = headers;
|
||||
pageHeaders.addAll(page.headers ?? {});
|
||||
tasks.add(DownloadTask(
|
||||
taskId: page.url,
|
||||
headers: pageHeaders,
|
||||
url: page.url.trim().trimLeft().trimRight(),
|
||||
filename: "${chapter.name}.mp4",
|
||||
baseDirectory: BaseDirectory.temporary,
|
||||
directory: 'Mangayomi/$finalPath',
|
||||
updates: Updates.statusAndProgress,
|
||||
allowPause: true,
|
||||
retries: 3,
|
||||
requiresWiFi: onlyOnWifi));
|
||||
await path.create();
|
||||
if (await File("${path.path}$chapterName.mp4").exists()) {
|
||||
} else {
|
||||
tasks.add(DownloadTask(
|
||||
taskId: page.url,
|
||||
headers: pageHeaders,
|
||||
url: page.url.trim().trimLeft().trimRight(),
|
||||
filename: "$chapterName.mp4",
|
||||
baseDirectory: BaseDirectory.temporary,
|
||||
directory: 'Mangayomi/$finalPath',
|
||||
updates: Updates.statusAndProgress,
|
||||
allowPause: true,
|
||||
retries: 3,
|
||||
requiresWiFi: onlyOnWifi));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -249,24 +258,16 @@ Future<List<PageUrl>> downloadChapter(
|
|||
isar.downloads.putSync(download..chapter.value = chapter);
|
||||
});
|
||||
} else {
|
||||
if (isManga) {
|
||||
await FileDownloader(
|
||||
useNativeHttpClient:
|
||||
ref.watch(useNativeHttpClientStateProvider))
|
||||
.downloadBatch(
|
||||
tasks,
|
||||
batchProgressCallback: (succeeded, failed) async {
|
||||
await FileDownloader().downloadBatch(
|
||||
tasks,
|
||||
batchProgressCallback: (succeeded, failed) async {
|
||||
if (isManga) {
|
||||
if (succeeded == tasks.length) {
|
||||
if (isManga) {
|
||||
savePageUrls();
|
||||
if (ref.watch(saveAsCBZArchiveStateProvider)) {
|
||||
await ref.watch(convertToCBZProvider(
|
||||
path!.path,
|
||||
mangaDir.path,
|
||||
chapter.name!,
|
||||
pageUrls.map((e) => e.url).toList())
|
||||
.future);
|
||||
}
|
||||
savePageUrls();
|
||||
if (ref.watch(saveAsCBZArchiveStateProvider)) {
|
||||
await ref.watch(convertToCBZProvider(path!.path, mangaDir.path,
|
||||
chapter.name!, pageUrls.map((e) => e.url).toList())
|
||||
.future);
|
||||
}
|
||||
}
|
||||
bool isEmpty = isar.downloads
|
||||
|
|
@ -298,20 +299,11 @@ Future<List<PageUrl>> downloadChapter(
|
|||
..isDownload = (succeeded == tasks.length));
|
||||
});
|
||||
}
|
||||
},
|
||||
taskProgressCallback: (taskProgress) async {
|
||||
if (taskProgress.progress == 1.0) {
|
||||
final file = File(
|
||||
"${tempDir.path}/${taskProgress.task.directory}/${taskProgress.task.filename}");
|
||||
await file.copy("${path!.path}/${taskProgress.task.filename}");
|
||||
await file.delete();
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
await FileDownloader(useNativeHttpClient: false).download(
|
||||
tasks.first,
|
||||
onProgress: (progress) async {
|
||||
}
|
||||
},
|
||||
taskProgressCallback: (taskProgress) async {
|
||||
final progress = taskProgress.progress;
|
||||
if (!isManga) {
|
||||
bool isEmpty = isar.downloads
|
||||
.filter()
|
||||
.chapterIdEqualTo(chapter.id!)
|
||||
|
|
@ -341,17 +333,15 @@ Future<List<PageUrl>> downloadChapter(
|
|||
..isDownload = (progress == 1.0));
|
||||
});
|
||||
}
|
||||
},
|
||||
onStatus: (status) async {
|
||||
if (status == TaskStatus.complete) {
|
||||
final file = File(
|
||||
"${tempDir.path}/${tasks.first.directory}/${tasks.first.filename}");
|
||||
await file.copy("${path!.path}/${tasks.first.filename}");
|
||||
await file.delete();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
if (progress == 1.0) {
|
||||
final file = File(
|
||||
"${tempDir.path}/${taskProgress.task.directory}/${taskProgress.task.filename}");
|
||||
await file.copy("${path!.path}${taskProgress.task.filename}");
|
||||
await file.delete();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
return pageUrls;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'download_provider.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$downloadChapterHash() => r'a2ba0ce07800518f35f47fa2272049357141a854';
|
||||
String _$downloadChapterHash() => r'eca8ccbe5f93f07c3471af81355fe9b3a8ec11e8';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -149,8 +149,8 @@ class DownloadQueueScreen extends ConsumerWidget {
|
|||
});
|
||||
} else if (value.toString() == 'CancelAll') {
|
||||
final chapterIds = entries
|
||||
.where((element) =>
|
||||
element.chapter.value!.name ==
|
||||
.where((e) =>
|
||||
e.chapter.value!.name ==
|
||||
element.chapter.value!.name)
|
||||
.map((e) => e.chapterId)
|
||||
.toList();
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mangayomi/modules/more/settings/advanced/providers/native_http_client.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||
|
||||
class AdvancedScreen extends ConsumerWidget {
|
||||
const AdvancedScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
final useNativeHttpClient = ref.watch(useNativeHttpClientStateProvider);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.advanced),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: SwitchListTile(
|
||||
title: Text(l10n.use_native_http_client),
|
||||
subtitle: Text(
|
||||
l10n.use_native_http_client_info,
|
||||
style:
|
||||
TextStyle(fontSize: 11, color: context.secondaryColor),
|
||||
),
|
||||
value: useNativeHttpClient,
|
||||
onChanged: (value) {
|
||||
ref
|
||||
.read(useNativeHttpClientStateProvider.notifier)
|
||||
.set(value);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/settings.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
part 'native_http_client.g.dart';
|
||||
|
||||
@riverpod
|
||||
class UseNativeHttpClientState extends _$UseNativeHttpClientState {
|
||||
@override
|
||||
bool build() {
|
||||
return isar.settings.getSync(227)!.useNativeHttpClient ?? false;
|
||||
}
|
||||
|
||||
void set(bool value) {
|
||||
final settings = isar.settings.getSync(227);
|
||||
state = value;
|
||||
isar.writeTxnSync(
|
||||
() => isar.settings.putSync(settings!..useNativeHttpClient = value));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'native_http_client.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$useNativeHttpClientStateHash() =>
|
||||
r'477bc54ed73067db40dc291532818108a2a37536';
|
||||
|
||||
/// See also [UseNativeHttpClientState].
|
||||
@ProviderFor(UseNativeHttpClientState)
|
||||
final useNativeHttpClientStateProvider =
|
||||
AutoDisposeNotifierProvider<UseNativeHttpClientState, bool>.internal(
|
||||
UseNativeHttpClientState.new,
|
||||
name: r'useNativeHttpClientStateProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$useNativeHttpClientStateHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$UseNativeHttpClientState = AutoDisposeNotifier<bool>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:mangayomi/modules/more/widgets/list_tile_widget.dart';
|
||||
|
|
@ -48,12 +46,6 @@ class SettingsScreen extends StatelessWidget {
|
|||
subtitle: l10n.browse_subtitle,
|
||||
icon: Icons.explore_rounded,
|
||||
onTap: () => context.push('/browseS')),
|
||||
if (Platform.isAndroid || Platform.isIOS || Platform.isMacOS)
|
||||
ListTileWidget(
|
||||
title: l10n.advanced,
|
||||
subtitle: l10n.browse_subtitle,
|
||||
icon: Icons.code_rounded,
|
||||
onTap: () => context.push('/advancedScreen')),
|
||||
ListTileWidget(
|
||||
onTap: () {
|
||||
context.push('/about');
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:draggable_menu/draggable_menu.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
|
@ -119,7 +121,7 @@ Future<void> customDraggableTabBar(
|
|||
}
|
||||
: 0;
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
backgroundColor: Platform.isLinux ? null : Colors.transparent,
|
||||
body: Container(
|
||||
width: context.width(1) - width,
|
||||
decoration: BoxDecoration(
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ import 'package:permission_handler/permission_handler.dart';
|
|||
import 'package:path/path.dart' as path;
|
||||
|
||||
class StorageProvider {
|
||||
final RegExp _regExpChar = RegExp(r'[^a-zA-Z0-9 .()\-\s]');
|
||||
final _forbiddenCharacters =
|
||||
RegExp(r'[\\/:*?"<>|\0]|(^CON$|^PRN$|^AUX$|^NUL$|^COM[1-9]$|^LPT[1-9]$)');
|
||||
|
||||
Future<bool> requestPermission() async {
|
||||
Permission permission = Permission.manageExternalStorage;
|
||||
if (Platform.isAndroid) {
|
||||
|
|
@ -78,12 +80,12 @@ class StorageProvider {
|
|||
) async {
|
||||
final manga = chapter.manga.value!;
|
||||
String scanlator = chapter.scanlator?.isNotEmpty ?? false
|
||||
? "${chapter.scanlator!.replaceAll(_regExpChar, '_')}_"
|
||||
? "${chapter.scanlator!.replaceAll(_forbiddenCharacters, '_')}_"
|
||||
: "";
|
||||
final isManga = chapter.manga.value!.isManga!;
|
||||
final dir = await getDirectory();
|
||||
return Directory(
|
||||
"${dir!.path}/downloads/${isManga ? "Manga" : "Anime"}/${manga.source} (${manga.lang!.toUpperCase()})/${manga.name!.replaceAll(_regExpChar, '_')}/$scanlator${chapter.name!.replaceAll(_regExpChar, '_')}/");
|
||||
"${dir!.path}/downloads/${isManga ? "Manga" : "Anime"}/${manga.source} (${manga.lang!.toUpperCase()})/${manga.name!.replaceAll(_forbiddenCharacters, '_')}/$scanlator${chapter.name!.replaceAll(_forbiddenCharacters, '_')}/");
|
||||
}
|
||||
|
||||
Future<Directory?> getMangaMainDirectory(Chapter chapter) async {
|
||||
|
|
@ -91,7 +93,7 @@ class StorageProvider {
|
|||
final isManga = chapter.manga.value!.isManga!;
|
||||
final dir = await getDirectory();
|
||||
return Directory(
|
||||
"${dir!.path}/downloads/${isManga ? "Manga" : "Anime"}/${manga.source} (${manga.lang!.toUpperCase()})/${manga.name!.replaceAll(_regExpChar, '_')}/");
|
||||
"${dir!.path}/downloads/${isManga ? "Manga" : "Anime"}/${manga.source} (${manga.lang!.toUpperCase()})/${manga.name!.replaceAll(_forbiddenCharacters, '_')}/");
|
||||
}
|
||||
|
||||
Future<Directory?> getDatabaseDirectory() async {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import 'package:mangayomi/modules/browse/extension/widgets/create_extension.dart
|
|||
import 'package:mangayomi/modules/browse/sources/sources_filter_screen.dart';
|
||||
import 'package:mangayomi/modules/more/backup_and_restore/backup_and_restore.dart';
|
||||
import 'package:mangayomi/modules/more/categories/categories_screen.dart';
|
||||
import 'package:mangayomi/modules/more/settings/advanced/advanced.dart';
|
||||
import 'package:mangayomi/modules/more/settings/downloads/downloads_screen.dart';
|
||||
import 'package:mangayomi/modules/more/settings/player/player_screen.dart';
|
||||
import 'package:mangayomi/modules/more/settings/track/track.dart';
|
||||
|
|
@ -534,19 +533,6 @@ class RouterNotifier extends ChangeNotifier {
|
|||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: "/advancedScreen",
|
||||
name: "advancedScreen",
|
||||
builder: (context, state) {
|
||||
return const AdvancedScreen();
|
||||
},
|
||||
pageBuilder: (context, state) {
|
||||
return transitionPage(
|
||||
key: state.pageKey,
|
||||
child: const AdvancedScreen(),
|
||||
);
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ class DoodExtractor {
|
|||
String? quality,
|
||||
bool redirect = true,
|
||||
}) async {
|
||||
final InterceptedClient client = MClient.init();
|
||||
final InterceptedClient client =
|
||||
MClient.init(reqcopyWith: {'useDartHttpClient': true});
|
||||
final newQuality = quality ?? ('Doodstream ${redirect ? ' mirror' : ''}');
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ import 'package:mangayomi/utils/extensions/string_extensions.dart';
|
|||
import 'package:mangayomi/utils/xpath_selector.dart';
|
||||
|
||||
class FilemoonExtractor {
|
||||
final InterceptedClient client = MClient.init();
|
||||
final InterceptedClient client =
|
||||
MClient.init(reqcopyWith: {'useDartHttpClient': true});
|
||||
|
||||
Future<List<Video>> videosFromUrl(
|
||||
String url, String prefix, String suffix) async {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ import 'package:mangayomi/services/http/m_client.dart';
|
|||
import 'package:mangayomi/utils/extensions/string_extensions.dart';
|
||||
|
||||
class GogoCdnExtractor {
|
||||
final InterceptedClient client = MClient.init();
|
||||
final InterceptedClient client =
|
||||
MClient.init(reqcopyWith: {'useDartHttpClient': true});
|
||||
final JsonCodec json = const JsonCodec();
|
||||
|
||||
Future<List<Video>> videosFromUrl(String serverUrl) async {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ import 'package:mangayomi/utils/xpath_selector.dart';
|
|||
class Mp4uploadExtractor {
|
||||
static final RegExp qualityRegex = RegExp(r'\WHEIGHT=(\d+)');
|
||||
static const String referer = "https://mp4upload.com/";
|
||||
final InterceptedClient client = MClient.init();
|
||||
final InterceptedClient client =
|
||||
MClient.init(reqcopyWith: {'useDartHttpClient': true});
|
||||
Future<List<Video>> videosFromUrl(String url, Map<String, String> headers,
|
||||
{String prefix = '', String suffix = ''}) async {
|
||||
final newHeaders = Map<String, String>.from(headers)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ class MyStreamExtractor {
|
|||
String url, Map<String, String> headers) async {
|
||||
final host = url.substringBefore("/watch");
|
||||
|
||||
final InterceptedClient client = MClient.init();
|
||||
final InterceptedClient client =
|
||||
MClient.init(reqcopyWith: {'useDartHttpClient': true});
|
||||
|
||||
try {
|
||||
final response = await client.get(Uri.parse(url), headers: headers);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import 'package:mangayomi/services/http/m_client.dart';
|
|||
import 'package:mangayomi/utils/extensions/string_extensions.dart';
|
||||
|
||||
class MytvExtractor {
|
||||
final InterceptedClient client = MClient.init();
|
||||
final InterceptedClient client =
|
||||
MClient.init(reqcopyWith: {'useDartHttpClient': true});
|
||||
|
||||
Future<List<Video>> videosFromUrl(String url) async {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import 'package:mangayomi/utils/extensions/dom_extensions.dart';
|
|||
import 'package:mangayomi/utils/extensions/string_extensions.dart';
|
||||
|
||||
class OkruExtractor {
|
||||
final InterceptedClient client = MClient.init();
|
||||
final InterceptedClient client =
|
||||
MClient.init(reqcopyWith: {'useDartHttpClient': true});
|
||||
|
||||
Future<List<Video>> videosFromUrl(String url,
|
||||
{String prefix = "", bool fixQualities = true}) async {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import 'package:mangayomi/services/http/m_client.dart';
|
|||
import 'package:mangayomi/utils/extensions/string_extensions.dart';
|
||||
|
||||
class SendvidExtractor {
|
||||
final InterceptedClient client = MClient.init();
|
||||
final InterceptedClient client =
|
||||
MClient.init(reqcopyWith: {'useDartHttpClient': true});
|
||||
final Map<String, String> headers;
|
||||
|
||||
SendvidExtractor(this.headers);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import 'package:mangayomi/services/http/m_client.dart';
|
|||
import 'package:mangayomi/utils/extensions/string_extensions.dart';
|
||||
|
||||
class SibnetExtractor {
|
||||
final InterceptedClient client = MClient.init();
|
||||
final InterceptedClient client =
|
||||
MClient.init(reqcopyWith: {'useDartHttpClient': true});
|
||||
|
||||
Future<List<Video>> videosFromUrl(String url, {String prefix = ""}) async {
|
||||
List<Video> videoList = [];
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import 'package:mangayomi/utils/extensions/others.dart';
|
|||
import 'package:mangayomi/utils/extensions/string_extensions.dart';
|
||||
|
||||
class StreamlareExtractor {
|
||||
final InterceptedClient client = MClient.init();
|
||||
final InterceptedClient client =
|
||||
MClient.init(reqcopyWith: {'useDartHttpClient': true});
|
||||
|
||||
Future<List<Video>> videosFromUrl(String url,
|
||||
{String prefix = "", String suffix = ""}) async {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ import 'package:mangayomi/utils/extensions/string_extensions.dart';
|
|||
class StreamTapeExtractor {
|
||||
Future<List<Video>> videosFromUrl(String url,
|
||||
{String quality = "StreamTape"}) async {
|
||||
final InterceptedClient client = MClient.init();
|
||||
final InterceptedClient client =
|
||||
MClient.init(reqcopyWith: {'useDartHttpClient': true});
|
||||
try {
|
||||
const baseUrl = "https://streamtape.com/e/";
|
||||
final newUrl =
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ import 'package:mangayomi/utils/extensions/string_extensions.dart';
|
|||
import 'package:mangayomi/utils/xpath_selector.dart';
|
||||
|
||||
class StreamWishExtractor {
|
||||
final InterceptedClient client = MClient.init();
|
||||
final InterceptedClient client =
|
||||
MClient.init(reqcopyWith: {'useDartHttpClient': true});
|
||||
final Map<String, String> headers = {};
|
||||
|
||||
Future<List<Video>> videosFromUrl(String url, String prefix) async {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import 'package:mangayomi/utils/extensions/string_extensions.dart';
|
|||
import 'package:mangayomi/utils/xpath_selector.dart';
|
||||
|
||||
class VidBomExtractor {
|
||||
final InterceptedClient client = MClient.init();
|
||||
final InterceptedClient client =
|
||||
MClient.init(reqcopyWith: {'useDartHttpClient': true});
|
||||
|
||||
Future<List<Video>> videosFromUrl(String url) async {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import 'package:mangayomi/utils/extensions/string_extensions.dart';
|
|||
import 'package:mangayomi/utils/xpath_selector.dart';
|
||||
|
||||
class VoeExtractor {
|
||||
final InterceptedClient client = MClient.init();
|
||||
final InterceptedClient client =
|
||||
MClient.init(reqcopyWith: {'useDartHttpClient': true});
|
||||
final linkRegex = RegExp(
|
||||
r'(http|https)://([\w_-]+(?:\.[\w_-]+)+)([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])');
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import 'package:mangayomi/utils/extensions/string_extensions.dart';
|
|||
import 'package:mangayomi/utils/xpath_selector.dart';
|
||||
|
||||
class YourUploadExtractor {
|
||||
final InterceptedClient client = MClient.init();
|
||||
final InterceptedClient client =
|
||||
MClient.init(reqcopyWith: {'useDartHttpClient': true});
|
||||
|
||||
Future<List<Video>> videosFromUrl(String url, Map<String, String> headers,
|
||||
{String name = "YourUpload", String prefix = ""}) async {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ import 'dart:math';
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:mangayomi/services/background_downloader/src/desktop/desktop_downloader_http_client.dart';
|
||||
import 'package:mangayomi/services/background_downloader/src/desktop/desktop_downloader_native_http_client.dart.dart';
|
||||
import 'package:mangayomi/services/background_downloader/src/downloader/downloader_http_client.dart';
|
||||
import 'database.dart';
|
||||
import 'exceptions.dart';
|
||||
import 'models.dart';
|
||||
|
|
@ -100,11 +99,9 @@ abstract base class BaseDownloader {
|
|||
|
||||
BaseDownloader();
|
||||
|
||||
factory BaseDownloader.instance(PersistentStorage persistentStorage,
|
||||
Database database, bool useNativeHttpClient) {
|
||||
final instance = !useNativeHttpClient
|
||||
? DesktopDownloaderHttpClient()
|
||||
: DesktopDownloaderNativeHttpClient();
|
||||
factory BaseDownloader.instance(
|
||||
PersistentStorage persistentStorage, Database database) {
|
||||
final instance = DownloaderHttpClient();
|
||||
instance._storage = persistentStorage;
|
||||
instance.database = database;
|
||||
unawaited(instance.initialize());
|
||||
|
|
|
|||
|
|
@ -1,604 +0,0 @@
|
|||
// ignore_for_file: depend_on_referenced_packages
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'package:async/async.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:http/io_client.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:mangayomi/services/http/m_client.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import '../base_downloader.dart';
|
||||
import '../chunk.dart';
|
||||
import '../exceptions.dart';
|
||||
import '../file_downloader.dart';
|
||||
import '../models.dart';
|
||||
import '../task.dart';
|
||||
import '../utils.dart';
|
||||
import 'isolate.dart';
|
||||
|
||||
const okResponses = [200, 201, 202, 203, 204, 205, 206];
|
||||
|
||||
/// Implementation of download functionality for desktop platforms
|
||||
///
|
||||
/// On desktop (MacOS, Linux, Windows) the download and upload are implemented
|
||||
/// in Dart, as there is no native platform equivalent of URLSession or
|
||||
/// WorkManager as there is on iOS and Android
|
||||
final class DesktopDownloaderNativeHttpClient extends BaseDownloader {
|
||||
static final _log = Logger('DesktopDownloaderNativeHttpClient');
|
||||
static const unlimited = 1 << 20;
|
||||
var maxConcurrent = 10;
|
||||
var maxConcurrentByHost = unlimited;
|
||||
var maxConcurrentByGroup = unlimited;
|
||||
static final DesktopDownloaderNativeHttpClient _singleton =
|
||||
DesktopDownloaderNativeHttpClient._internal();
|
||||
final _queue = PriorityQueue<Task>();
|
||||
final _running = Queue<Task>(); // subset that is running
|
||||
final _resume = <Task>{};
|
||||
final _isolateSendPorts =
|
||||
<Task, SendPort?>{}; // isolate SendPort for running task
|
||||
static var httpClient = MClient.nativeHttpClient();
|
||||
static Duration? _requestTimeout;
|
||||
static var _proxy = <String, dynamic>{}; // 'address' and 'port'
|
||||
static var _bypassTLSCertificateValidation = false;
|
||||
|
||||
factory DesktopDownloaderNativeHttpClient() => _singleton;
|
||||
|
||||
DesktopDownloaderNativeHttpClient._internal();
|
||||
|
||||
@override
|
||||
Future<bool> enqueue(Task task) async {
|
||||
try {
|
||||
Uri.decodeFull(task.url);
|
||||
} catch (e) {
|
||||
_log.fine('Invalid url: ${task.url} error: $e');
|
||||
return false;
|
||||
}
|
||||
super.enqueue(task);
|
||||
_queue.add(task);
|
||||
processStatusUpdate(TaskStatusUpdate(task, TaskStatus.enqueued));
|
||||
_advanceQueue();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Advance the queue if it's not empty and there is room in the run queue
|
||||
void _advanceQueue() {
|
||||
while (_running.length < maxConcurrent && _queue.isNotEmpty) {
|
||||
final task = _getNextTask();
|
||||
if (task != null) {
|
||||
_running.add(task);
|
||||
_executeTask(task).then((_) {
|
||||
_remove(task);
|
||||
_advanceQueue();
|
||||
});
|
||||
} else {
|
||||
return; // if no suitable task, done
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a [Task] to run, or null if no suitable task is available
|
||||
Task? _getNextTask() {
|
||||
final tasksThatHaveToWait = <Task>[];
|
||||
while (_queue.isNotEmpty) {
|
||||
final task = _queue.removeFirst();
|
||||
if (_numActiveWithHostname(task.hostName) < maxConcurrentByHost &&
|
||||
_numActiveWithGroup(task.group) < maxConcurrentByGroup) {
|
||||
_queue.addAll(tasksThatHaveToWait); // put back in queue
|
||||
return task;
|
||||
}
|
||||
tasksThatHaveToWait.add(task);
|
||||
}
|
||||
_queue.addAll(tasksThatHaveToWait); // put back in queue
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Returns number of tasks active with this [hostname]
|
||||
int _numActiveWithHostname(String hostname) => _running.fold(
|
||||
0,
|
||||
(previousValue, task) =>
|
||||
task.hostName == hostname ? previousValue + 1 : previousValue);
|
||||
|
||||
/// Returns number of tasks active with this [group]
|
||||
int _numActiveWithGroup(String group) => _running.fold(
|
||||
0,
|
||||
(previousValue, task) =>
|
||||
task.group == group ? previousValue + 1 : previousValue);
|
||||
|
||||
/// Execute this task
|
||||
///
|
||||
/// The task runs on an Isolate, which is sent the task information and
|
||||
/// which will emit status and progress updates. These updates will be
|
||||
/// 'forwarded' to the [backgroundChannel] and processed by the
|
||||
/// [FileDownloader]
|
||||
Future<void> _executeTask(Task task) async {
|
||||
final resumeData = await getResumeData(task.taskId);
|
||||
if (resumeData != null) {
|
||||
await removeResumeData(task.taskId);
|
||||
}
|
||||
final isResume = _resume.remove(task) && resumeData != null;
|
||||
final filePath = await task.filePath(); // "" for MultiUploadTask
|
||||
// spawn an isolate to do the task
|
||||
final receivePort = ReceivePort();
|
||||
final errorPort = ReceivePort();
|
||||
errorPort.listen((message) {
|
||||
final exceptionDescription = (message as List).first as String;
|
||||
final stackTrace = message.last;
|
||||
logError(task, exceptionDescription);
|
||||
log.fine('Stack trace: $stackTrace');
|
||||
processStatusUpdate(TaskStatusUpdate(
|
||||
task, TaskStatus.failed, TaskException(exceptionDescription)));
|
||||
receivePort.close(); // also ends listener at the end
|
||||
});
|
||||
RootIsolateToken? rootIsolateToken = RootIsolateToken.instance;
|
||||
if (rootIsolateToken == null) {
|
||||
processStatusUpdate(TaskStatusUpdate(task, TaskStatus.failed,
|
||||
TaskException('Could not obtain rootIsolateToken')));
|
||||
return;
|
||||
}
|
||||
log.finer('${isResume ? "Resuming" : "Starting"} taskId ${task.taskId}');
|
||||
await Isolate.spawn(doTask, (rootIsolateToken, receivePort.sendPort, true),
|
||||
onError: errorPort.sendPort);
|
||||
final messagesFromIsolate = StreamQueue<dynamic>(receivePort);
|
||||
final sendPort = await messagesFromIsolate.next as SendPort;
|
||||
sendPort.send((
|
||||
task,
|
||||
filePath,
|
||||
resumeData,
|
||||
isResume,
|
||||
requestTimeout,
|
||||
proxy,
|
||||
bypassTLSCertificateValidation
|
||||
));
|
||||
if (_isolateSendPorts.keys.contains(task)) {
|
||||
// if already registered with null value, cancel immediately
|
||||
sendPort.send('cancel');
|
||||
}
|
||||
// store the isolate's sendPort so we can send it messages for
|
||||
// cancellation, and for managing parallel downloads
|
||||
_isolateSendPorts[task] = sendPort;
|
||||
// listen for messages sent back from the isolate, until 'done'
|
||||
// note that the task sent by the isolate may have changed. Therefore, we
|
||||
// use updatedTask instead of task from here on
|
||||
while (await messagesFromIsolate.hasNext) {
|
||||
final message = await messagesFromIsolate.next;
|
||||
switch (message) {
|
||||
case 'done':
|
||||
receivePort.close();
|
||||
|
||||
case (
|
||||
'statusUpdate',
|
||||
Task updatedTask,
|
||||
TaskStatus status,
|
||||
TaskException? exception,
|
||||
String? responseBody,
|
||||
Map<String, String>? responseHeaders,
|
||||
int? responseCode,
|
||||
String? mimeType,
|
||||
String? charSet
|
||||
):
|
||||
final taskStatusUpdate = TaskStatusUpdate(
|
||||
updatedTask,
|
||||
status,
|
||||
exception,
|
||||
responseBody,
|
||||
responseHeaders,
|
||||
responseCode,
|
||||
mimeType,
|
||||
charSet);
|
||||
if (updatedTask.group != BaseDownloader.chunkGroup) {
|
||||
if (status.isFinalState) {
|
||||
_remove(updatedTask);
|
||||
}
|
||||
processStatusUpdate(taskStatusUpdate);
|
||||
} else {
|
||||
_parallelTaskSendPort(Chunk.getParentTaskId(updatedTask))
|
||||
?.send(taskStatusUpdate);
|
||||
}
|
||||
|
||||
case (
|
||||
'progressUpdate',
|
||||
Task updatedTask,
|
||||
double progress,
|
||||
int expectedFileSize,
|
||||
double downloadSpeed,
|
||||
Duration timeRemaining
|
||||
):
|
||||
final taskProgressUpdate = TaskProgressUpdate(updatedTask, progress,
|
||||
expectedFileSize, downloadSpeed, timeRemaining);
|
||||
if (updatedTask.group != BaseDownloader.chunkGroup) {
|
||||
processProgressUpdate(taskProgressUpdate);
|
||||
} else {
|
||||
_parallelTaskSendPort(Chunk.getParentTaskId(updatedTask))
|
||||
?.send(taskProgressUpdate);
|
||||
}
|
||||
|
||||
case ('taskCanResume', bool taskCanResume):
|
||||
setCanResume(task, taskCanResume);
|
||||
|
||||
case ('resumeData', String data, int requiredStartByte, String? eTag):
|
||||
setResumeData(ResumeData(task, data, requiredStartByte, eTag));
|
||||
|
||||
// from [ParallelDownloadTask]
|
||||
case ('enqueueChild', DownloadTask childTask):
|
||||
await FileDownloader().enqueue(childTask);
|
||||
|
||||
// from [ParallelDownloadTask]
|
||||
case ('cancelTasksWithId', List<String> taskIds):
|
||||
await FileDownloader().cancelTasksWithIds(taskIds);
|
||||
|
||||
// from [ParallelDownloadTask]
|
||||
case ('pauseTasks', List<DownloadTask> tasks):
|
||||
for (final chunkTask in tasks) {
|
||||
await FileDownloader().pause(chunkTask);
|
||||
}
|
||||
|
||||
case ('log', String logMessage):
|
||||
_log.finest(logMessage);
|
||||
|
||||
default:
|
||||
_log.warning('Received message with unknown type '
|
||||
'$message from Isolate');
|
||||
}
|
||||
}
|
||||
errorPort.close();
|
||||
_isolateSendPorts.remove(task);
|
||||
}
|
||||
|
||||
// intercept the status and progress updates for tasks that are 'chunks', i.e.
|
||||
// part of a [ParallelDownloadTask]. Updates for these tasks are sent to the
|
||||
// isolate running the [ParallelDownloadTask] instead
|
||||
|
||||
@override
|
||||
void processStatusUpdate(TaskStatusUpdate update) {
|
||||
// Regular update if task's group is not chunkGroup
|
||||
if (update.task.group != FileDownloader.chunkGroup) {
|
||||
return super.processStatusUpdate(update);
|
||||
}
|
||||
// If chunkGroup, send update to task's parent isolate.
|
||||
// The task's metadata contains taskId of parent
|
||||
_parallelTaskSendPort(Chunk.getParentTaskId(update.task))?.send(update);
|
||||
}
|
||||
|
||||
@override
|
||||
void processProgressUpdate(TaskProgressUpdate update) {
|
||||
// Regular update if task's group is not chunkGroup
|
||||
if (update.task.group != FileDownloader.chunkGroup) {
|
||||
return super.processProgressUpdate(update);
|
||||
}
|
||||
// If chunkGroup, send update to task's parent isolate.
|
||||
// The task's metadata contains taskId of parent
|
||||
_parallelTaskSendPort(Chunk.getParentTaskId(update.task))?.send(update);
|
||||
}
|
||||
|
||||
/// Return the [SendPort] for the [ParallelDownloadTask] represented by [taskId]
|
||||
/// or null if not a [ParallelDownloadTask] or not found
|
||||
SendPort? _parallelTaskSendPort(String taskId) => _isolateSendPorts.entries
|
||||
.firstWhereOrNull((entry) =>
|
||||
entry.key is ParallelDownloadTask && entry.key.taskId == taskId)
|
||||
?.value;
|
||||
|
||||
@override
|
||||
Future<int> reset(String group) async {
|
||||
final retryAndPausedTaskCount = await super.reset(group);
|
||||
final inQueueIds = _queue.unorderedElements
|
||||
.where((task) => task.group == group)
|
||||
.map((task) => task.taskId);
|
||||
final runningIds = _running
|
||||
.where((task) => task.group == group)
|
||||
.map((task) => task.taskId);
|
||||
final taskIds = [...inQueueIds, ...runningIds];
|
||||
if (taskIds.isNotEmpty) {
|
||||
await cancelTasksWithIds(taskIds);
|
||||
}
|
||||
return retryAndPausedTaskCount + taskIds.length;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Task>> allTasks(
|
||||
String group, bool includeTasksWaitingToRetry) async {
|
||||
final retryAndPausedTasks =
|
||||
await super.allTasks(group, includeTasksWaitingToRetry);
|
||||
final inQueue =
|
||||
_queue.unorderedElements.where((task) => task.group == group);
|
||||
final running = _running.where((task) => task.group == group);
|
||||
return [...retryAndPausedTasks, ...inQueue, ...running];
|
||||
}
|
||||
|
||||
/// Cancels ongoing platform tasks whose taskId is in the list provided
|
||||
///
|
||||
/// Returns true if all cancellations were successful
|
||||
@override
|
||||
Future<bool> cancelPlatformTasksWithIds(List<String> taskIds) async {
|
||||
final inQueue = _queue.unorderedElements
|
||||
.where((task) => taskIds.contains(task.taskId))
|
||||
.toList(growable: false);
|
||||
for (final task in inQueue) {
|
||||
processStatusUpdate(TaskStatusUpdate(task, TaskStatus.canceled));
|
||||
_remove(task);
|
||||
}
|
||||
final running = _running.where((task) => taskIds.contains(task.taskId));
|
||||
for (final task in running) {
|
||||
final sendPort = _isolateSendPorts[task];
|
||||
if (sendPort != null) {
|
||||
sendPort.send('cancel');
|
||||
_isolateSendPorts.remove(task);
|
||||
} else {
|
||||
// register task for cancellation even if sendPort does not yet exist:
|
||||
// this will lead to immediate cancellation when the Isolate starts
|
||||
_isolateSendPorts[task] = null;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Task?> taskForId(String taskId) async {
|
||||
var task = await super.taskForId(taskId);
|
||||
if (task != null) {
|
||||
return task;
|
||||
}
|
||||
try {
|
||||
return _running.where((task) => task.taskId == taskId).first;
|
||||
} on StateError {
|
||||
try {
|
||||
return _queue.unorderedElements
|
||||
.where((task) => task.taskId == taskId)
|
||||
.first;
|
||||
} on StateError {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> pause(Task task) async {
|
||||
final sendPort = _isolateSendPorts[task];
|
||||
if (sendPort != null) {
|
||||
sendPort.send('pause');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> resume(Task task) async {
|
||||
if (await super.resume(task)) {
|
||||
task = awaitTasks.containsKey(task)
|
||||
? awaitTasks.keys
|
||||
.firstWhere((awaitTask) => awaitTask.taskId == task.taskId)
|
||||
: task;
|
||||
_resume.add(task);
|
||||
if (await enqueue(task)) {
|
||||
if (task is ParallelDownloadTask) {
|
||||
final resumeData = await getResumeData(task.taskId);
|
||||
if (resumeData == null) {
|
||||
return false;
|
||||
}
|
||||
return resumeChunkTasks(task, resumeData);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, String>> popUndeliveredData(Undelivered dataType) =>
|
||||
Future.value({});
|
||||
|
||||
@override
|
||||
Future<String?> moveToSharedStorage(String filePath,
|
||||
SharedStorage destination, String directory, String? mimeType) async {
|
||||
final destDirectoryPath =
|
||||
await getDestinationDirectoryPath(destination, directory);
|
||||
if (destDirectoryPath == null) {
|
||||
return null;
|
||||
}
|
||||
if (!await Directory(destDirectoryPath).exists()) {
|
||||
await Directory(destDirectoryPath).create(recursive: true);
|
||||
}
|
||||
final fileName = path.basename(filePath);
|
||||
final destFilePath = path.join(destDirectoryPath, fileName);
|
||||
try {
|
||||
await File(filePath).rename(destFilePath);
|
||||
} on FileSystemException catch (e) {
|
||||
_log.warning('Error moving $filePath to shared storage: $e');
|
||||
return null;
|
||||
}
|
||||
return destFilePath;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> pathInSharedStorage(
|
||||
String filePath, SharedStorage destination, String directory) async {
|
||||
final destDirectoryPath =
|
||||
await getDestinationDirectoryPath(destination, directory);
|
||||
if (destDirectoryPath == null) {
|
||||
return null;
|
||||
}
|
||||
final fileName = path.basename(filePath);
|
||||
return path.join(destDirectoryPath, fileName);
|
||||
}
|
||||
|
||||
/// Returns the path of the destination directory in shared storage, or null
|
||||
///
|
||||
/// Only the .Downloads directory is supported on desktop.
|
||||
/// The [directory] is appended to the base Downloads directory.
|
||||
/// The directory at the returned path is not guaranteed to exist.
|
||||
Future<String?> getDestinationDirectoryPath(
|
||||
SharedStorage destination, String directory) async {
|
||||
if (destination != SharedStorage.downloads) {
|
||||
_log.finer('Desktop only supports .downloads destination');
|
||||
return null;
|
||||
}
|
||||
final downloadsDirectory = await getDownloadsDirectory();
|
||||
if (downloadsDirectory == null) {
|
||||
_log.warning('Could not obtain downloads directory');
|
||||
return null;
|
||||
}
|
||||
// remove leading and trailing slashes from [directory]
|
||||
var cleanDirectory = directory.replaceAll(RegExp(r'^/+'), '');
|
||||
cleanDirectory = cleanDirectory.replaceAll(RegExp(r'/$'), '');
|
||||
return cleanDirectory.isEmpty
|
||||
? downloadsDirectory.path
|
||||
: path.join(downloadsDirectory.path, cleanDirectory);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> openFile(Task? task, String? filePath, String? mimeType) async {
|
||||
final executable = Platform.isLinux
|
||||
? 'xdg-open'
|
||||
: Platform.isMacOS
|
||||
? 'open'
|
||||
: 'start';
|
||||
filePath ??= await task!.filePath();
|
||||
if (!await File(filePath).exists()) {
|
||||
_log.fine('File to open does not exist: $filePath');
|
||||
return false;
|
||||
}
|
||||
final result = await Process.run(executable, [filePath], runInShell: true);
|
||||
if (result.exitCode != 0) {
|
||||
_log.fine(
|
||||
'openFile command $executable returned exit code ${result.exitCode}');
|
||||
}
|
||||
return result.exitCode == 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Duration> getTaskTimeout() => Future.value(const Duration(days: 1));
|
||||
|
||||
@override
|
||||
Future<void> setForceFailPostOnBackgroundChannel(bool value) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> testSuggestedFilename(
|
||||
DownloadTask task, String contentDisposition) async {
|
||||
final h = contentDisposition.isNotEmpty
|
||||
? {'Content-disposition': contentDisposition}
|
||||
: <String, String>{};
|
||||
final t = await taskWithSuggestedFilename(task, h, false);
|
||||
return t.filename;
|
||||
}
|
||||
|
||||
@override
|
||||
dynamic platformConfig(
|
||||
{dynamic globalConfig,
|
||||
dynamic androidConfig,
|
||||
dynamic iOSConfig,
|
||||
dynamic desktopConfig}) =>
|
||||
desktopConfig;
|
||||
|
||||
@override
|
||||
Future<(String, String)> configureItem((String, dynamic) configItem) async {
|
||||
switch (configItem) {
|
||||
case (Config.requestTimeout, Duration? duration):
|
||||
requestTimeout = duration;
|
||||
|
||||
case (Config.proxy, (String address, int port)):
|
||||
proxy = {'address': address, 'port': port};
|
||||
|
||||
case (Config.proxy, false):
|
||||
proxy = {};
|
||||
|
||||
case (Config.bypassTLSCertificateValidation, bool bypass):
|
||||
bypassTLSCertificateValidation = bypass;
|
||||
|
||||
case (
|
||||
Config.holdingQueue,
|
||||
(
|
||||
int? maxConcurrentParam,
|
||||
int? maxConcurrentByHostParam,
|
||||
int? maxConcurrentByGroupParam
|
||||
)
|
||||
):
|
||||
maxConcurrent = maxConcurrentParam ?? 10;
|
||||
maxConcurrentByHost = maxConcurrentByHostParam ?? unlimited;
|
||||
maxConcurrentByGroup = maxConcurrentByGroupParam ?? unlimited;
|
||||
|
||||
default:
|
||||
return (
|
||||
configItem.$1,
|
||||
'not implemented'
|
||||
); // this method did not process this configItem
|
||||
}
|
||||
return (configItem.$1, ''); // normal result
|
||||
}
|
||||
|
||||
/// Sets requestTimeout and recreates HttpClient
|
||||
static set requestTimeout(Duration? value) {
|
||||
_requestTimeout = value;
|
||||
_recreateClient();
|
||||
}
|
||||
|
||||
static Duration? get requestTimeout => _requestTimeout;
|
||||
|
||||
/// Sets proxy and recreates HttpClient
|
||||
///
|
||||
/// Value must be dict containing 'address' and 'port'
|
||||
/// or empty for no proxy
|
||||
static set proxy(Map<String, dynamic> value) {
|
||||
_proxy = value;
|
||||
_recreateClient();
|
||||
}
|
||||
|
||||
static Map<String, dynamic> get proxy => _proxy;
|
||||
|
||||
/// Set or resets bypass for TLS certificate validation
|
||||
static set bypassTLSCertificateValidation(bool value) {
|
||||
_bypassTLSCertificateValidation = value;
|
||||
_recreateClient();
|
||||
}
|
||||
|
||||
static bool get bypassTLSCertificateValidation =>
|
||||
_bypassTLSCertificateValidation;
|
||||
|
||||
/// Set the HTTP Client to use, with the given parameters
|
||||
///
|
||||
/// This is a convenience method, bundling the [requestTimeout],
|
||||
/// [proxy] and [bypassTLSCertificateValidation]
|
||||
static void setHttpClient(Duration? requestTimeout,
|
||||
Map<String, dynamic> proxy, bool bypassTLSCertificateValidation) {
|
||||
_requestTimeout = requestTimeout;
|
||||
_proxy = proxy;
|
||||
_bypassTLSCertificateValidation = bypassTLSCertificateValidation;
|
||||
_recreateClient();
|
||||
}
|
||||
|
||||
/// Recreates the [httpClient] used for Requests and isolate downloads/uploads
|
||||
static _recreateClient() async {
|
||||
if (Platform.isWindows || Platform.isLinux) {
|
||||
final client = HttpClient();
|
||||
client.connectionTimeout = requestTimeout;
|
||||
client.findProxy = proxy.isNotEmpty
|
||||
? (_) => 'PROXY ${_proxy['address']}:${_proxy['port']}'
|
||||
: null;
|
||||
client.badCertificateCallback =
|
||||
(X509Certificate cert, String host, int port) => true;
|
||||
httpClient = IOClient(client);
|
||||
} else {
|
||||
httpClient = MClient.nativeHttpClient();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void destroy() {
|
||||
super.destroy();
|
||||
_queue.clear();
|
||||
_running.clear();
|
||||
_isolateSendPorts.clear();
|
||||
}
|
||||
|
||||
/// Remove all references to [task]
|
||||
void _remove(Task task) {
|
||||
_queue.remove(task);
|
||||
_running.remove(task);
|
||||
_isolateSendPorts.remove(task);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,7 @@ import 'dart:async';
|
|||
import 'dart:isolate';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:mangayomi/services/background_downloader/src/desktop/desktop_downloader_http_client.dart';
|
||||
import 'package:mangayomi/services/background_downloader/src/desktop/desktop_downloader_native_http_client.dart.dart'
|
||||
as desktop_downloader_native;
|
||||
import 'package:mangayomi/services/background_downloader/src/downloader/downloader_http_client.dart';
|
||||
import 'package:mangayomi/services/background_downloader/background_downloader.dart';
|
||||
import 'download_isolate.dart';
|
||||
import 'isolate.dart';
|
||||
|
|
@ -13,11 +11,8 @@ import 'isolate.dart';
|
|||
///
|
||||
/// Sends updates via the [sendPort] and can be commanded to cancel via
|
||||
/// the [messagesToIsolate] queue
|
||||
Future<void> doDataTask(
|
||||
DataTask task, SendPort sendPort, bool useNativeHttpClient) async {
|
||||
final client = useNativeHttpClient
|
||||
? desktop_downloader_native.DesktopDownloaderNativeHttpClient.httpClient
|
||||
: DesktopDownloaderHttpClient.httpClient;
|
||||
Future<void> doDataTask(DataTask task, SendPort sendPort) async {
|
||||
final client = DownloaderHttpClient.httpClient;
|
||||
var request = http.Request(task.httpRequestMethod, Uri.parse(task.url));
|
||||
request.headers.addAll(task.headers);
|
||||
if (task.post is String) {
|
||||
|
|
@ -6,9 +6,7 @@ import 'dart:isolate';
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:mangayomi/services/background_downloader/src/desktop/desktop_downloader_http_client.dart';
|
||||
import 'package:mangayomi/services/background_downloader/src/desktop/desktop_downloader_native_http_client.dart.dart'
|
||||
as desktop_downloader_native;
|
||||
import 'package:mangayomi/services/background_downloader/src/downloader/downloader_http_client.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
|
|
@ -32,8 +30,7 @@ Future<void> doDownloadTask(
|
|||
ResumeData? resumeData,
|
||||
bool isResume,
|
||||
Duration requestTimeout,
|
||||
SendPort sendPort,
|
||||
bool useNativeHttpClient) async {
|
||||
SendPort sendPort) async {
|
||||
// use downloadTask from here on as a 'global' variable in this isolate,
|
||||
// as we may change the filename of the task
|
||||
downloadTask = task;
|
||||
|
|
@ -48,9 +45,7 @@ Future<void> doDownloadTask(
|
|||
final eTag = resumeData?.eTag;
|
||||
isResume = isResume &&
|
||||
await determineIfResumeIsPossible(tempFilePath, requiredStartByte);
|
||||
final client = useNativeHttpClient
|
||||
? desktop_downloader_native.DesktopDownloaderNativeHttpClient.httpClient
|
||||
: DesktopDownloaderHttpClient.httpClient;
|
||||
final client = DownloaderHttpClient.httpClient;
|
||||
var request =
|
||||
http.Request(downloadTask.httpRequestMethod, Uri.parse(downloadTask.url));
|
||||
request.headers.addAll(downloadTask.headers);
|
||||
|
|
@ -7,10 +7,11 @@ import 'dart:isolate';
|
|||
import 'package:async/async.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:http/io_client.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:mangayomi/services/http/m_client.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:rhttp/rhttp.dart';
|
||||
import '../base_downloader.dart';
|
||||
import '../chunk.dart';
|
||||
import '../exceptions.dart';
|
||||
|
|
@ -27,29 +28,30 @@ const okResponses = [200, 201, 202, 203, 204, 205, 206];
|
|||
/// On desktop (MacOS, Linux, Windows) the download and upload are implemented
|
||||
/// in Dart, as there is no native platform equivalent of URLSession or
|
||||
/// WorkManager as there is on iOS and Android
|
||||
final class DesktopDownloaderHttpClient extends BaseDownloader {
|
||||
static final _log = Logger('DesktopDownloaderHttpClient');
|
||||
final class DownloaderHttpClient extends BaseDownloader {
|
||||
static final _log = Logger('DownloaderHttpClient');
|
||||
static const unlimited = 1 << 20;
|
||||
var maxConcurrent = 10;
|
||||
var maxConcurrentByHost = unlimited;
|
||||
var maxConcurrentByGroup = unlimited;
|
||||
static final DesktopDownloaderHttpClient _singleton =
|
||||
DesktopDownloaderHttpClient._internal();
|
||||
static final DownloaderHttpClient _singleton =
|
||||
DownloaderHttpClient._internal();
|
||||
final _queue = PriorityQueue<Task>();
|
||||
final _running = Queue<Task>(); // subset that is running
|
||||
final _resume = <Task>{};
|
||||
final _isolateSendPorts =
|
||||
<Task, SendPort?>{}; // isolate SendPort for running task
|
||||
static var httpClient = IOClient(HttpClient()
|
||||
..badCertificateCallback =
|
||||
(X509Certificate cert, String host, int port) => true);
|
||||
static var httpClient = MClient.httpClient(
|
||||
settings: const ClientSettings(
|
||||
throwOnStatusCode: false,
|
||||
tlsSettings: TlsSettings(verifyCertificates: false)));
|
||||
static Duration? _requestTimeout;
|
||||
static var _proxy = <String, dynamic>{}; // 'address' and 'port'
|
||||
static var _bypassTLSCertificateValidation = false;
|
||||
|
||||
factory DesktopDownloaderHttpClient() => _singleton;
|
||||
factory DownloaderHttpClient() => _singleton;
|
||||
|
||||
DesktopDownloaderHttpClient._internal();
|
||||
DownloaderHttpClient._internal();
|
||||
|
||||
@override
|
||||
Future<bool> enqueue(Task task) async {
|
||||
|
|
@ -142,7 +144,7 @@ final class DesktopDownloaderHttpClient extends BaseDownloader {
|
|||
return;
|
||||
}
|
||||
log.finer('${isResume ? "Resuming" : "Starting"} taskId ${task.taskId}');
|
||||
await Isolate.spawn(doTask, (rootIsolateToken, receivePort.sendPort, false),
|
||||
await Isolate.spawn(doTask, (rootIsolateToken, receivePort.sendPort),
|
||||
onError: errorPort.sendPort);
|
||||
final messagesFromIsolate = StreamQueue<dynamic>(receivePort);
|
||||
final sendPort = await messagesFromIsolate.next as SendPort;
|
||||
|
|
@ -574,14 +576,11 @@ final class DesktopDownloaderHttpClient extends BaseDownloader {
|
|||
|
||||
/// Recreates the [httpClient] used for Requests and isolate downloads/uploads
|
||||
static _recreateClient() async {
|
||||
final client = HttpClient();
|
||||
client.connectionTimeout = requestTimeout;
|
||||
client.findProxy = proxy.isNotEmpty
|
||||
? (_) => 'PROXY ${_proxy['address']}:${_proxy['port']}'
|
||||
: null;
|
||||
client.badCertificateCallback =
|
||||
(X509Certificate cert, String host, int port) => true;
|
||||
httpClient = IOClient(client);
|
||||
await Rhttp.init();
|
||||
httpClient = MClient.httpClient(
|
||||
settings: const ClientSettings(
|
||||
throwOnStatusCode: false,
|
||||
tlsSettings: TlsSettings(verifyCertificates: false)));
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -7,8 +7,7 @@ import 'dart:isolate';
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:mangayomi/services/background_downloader/src/desktop/desktop_downloader_http_client.dart';
|
||||
import 'package:mangayomi/services/background_downloader/src/desktop/desktop_downloader_native_http_client.dart.dart';
|
||||
import 'package:mangayomi/services/background_downloader/src/downloader/downloader_http_client.dart';
|
||||
import 'package:mangayomi/services/background_downloader/src/exceptions.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
|
@ -51,8 +50,8 @@ final log = Logger('FileDownloader');
|
|||
///
|
||||
/// The first message sent back is a [ReceivePort] that is the command port
|
||||
/// for the isolate. The first command must be the arguments: task and filePath.
|
||||
Future<void> doTask((RootIsolateToken, SendPort, bool) isolateArguments) async {
|
||||
final (rootIsolateToken, sendPort, useNativeHttpClient) = isolateArguments;
|
||||
Future<void> doTask((RootIsolateToken, SendPort) isolateArguments) async {
|
||||
final (rootIsolateToken, sendPort) = isolateArguments;
|
||||
BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
|
||||
final receivePort = ReceivePort();
|
||||
// send the receive port back to the main Isolate
|
||||
|
|
@ -68,11 +67,8 @@ Future<void> doTask((RootIsolateToken, SendPort, bool) isolateArguments) async {
|
|||
Map<String, dynamic> proxy,
|
||||
bool bypassTLSCertificateValidation
|
||||
) = await messagesToIsolate.next;
|
||||
useNativeHttpClient
|
||||
? DesktopDownloaderNativeHttpClient.setHttpClient(
|
||||
requestTimeout, proxy, bypassTLSCertificateValidation)
|
||||
: DesktopDownloaderHttpClient.setHttpClient(
|
||||
requestTimeout, proxy, bypassTLSCertificateValidation);
|
||||
DownloaderHttpClient.setHttpClient(
|
||||
requestTimeout, proxy, bypassTLSCertificateValidation);
|
||||
Logger.root.level = Level.ALL;
|
||||
Logger.root.onRecord.listen((LogRecord rec) {
|
||||
if (kDebugMode) {
|
||||
|
|
@ -99,18 +95,11 @@ Future<void> doTask((RootIsolateToken, SendPort, bool) isolateArguments) async {
|
|||
resumeData,
|
||||
isResume,
|
||||
requestTimeout ?? const Duration(seconds: 60),
|
||||
sendPort,
|
||||
useNativeHttpClient),
|
||||
DownloadTask() => doDownloadTask(
|
||||
task,
|
||||
filePath,
|
||||
resumeData,
|
||||
isResume,
|
||||
requestTimeout ?? const Duration(seconds: 60),
|
||||
sendPort,
|
||||
useNativeHttpClient),
|
||||
UploadTask() => doUploadTask(task, filePath, sendPort,useNativeHttpClient),
|
||||
DataTask() => doDataTask(task, sendPort, useNativeHttpClient)
|
||||
sendPort),
|
||||
DownloadTask() => doDownloadTask(task, filePath, resumeData, isResume,
|
||||
requestTimeout ?? const Duration(seconds: 60), sendPort),
|
||||
UploadTask() => doUploadTask(task, filePath, sendPort),
|
||||
DataTask() => doDataTask(task, sendPort)
|
||||
};
|
||||
}
|
||||
receivePort.close();
|
||||
|
|
@ -7,9 +7,7 @@ import 'dart:isolate';
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:mangayomi/services/background_downloader/src/desktop/desktop_downloader_http_client.dart';
|
||||
import 'package:mangayomi/services/background_downloader/src/desktop/desktop_downloader_native_http_client.dart.dart'
|
||||
as desktop_downloader_native;
|
||||
import 'package:mangayomi/services/background_downloader/src/downloader/downloader_http_client.dart';
|
||||
import '../chunk.dart';
|
||||
import '../exceptions.dart';
|
||||
import '../models.dart';
|
||||
|
|
@ -65,15 +63,11 @@ Future<void> doParallelDownloadTask(
|
|||
ResumeData? resumeData,
|
||||
bool isResume,
|
||||
Duration requestTimeout,
|
||||
SendPort sendPort,
|
||||
bool useNativeHttpClient) async {
|
||||
SendPort sendPort) async {
|
||||
parentTask = task;
|
||||
if (!isResume) {
|
||||
// start the download by creating [Chunk]s and enqueuing chunk tasks
|
||||
final response = await (useNativeHttpClient
|
||||
? desktop_downloader_native
|
||||
.DesktopDownloaderNativeHttpClient.httpClient
|
||||
: DesktopDownloaderHttpClient.httpClient)
|
||||
final response = await (DownloaderHttpClient.httpClient)
|
||||
.head(Uri.parse(task.url), headers: task.headers);
|
||||
responseHeaders = response.headers;
|
||||
responseStatusCode = response.statusCode;
|
||||
|
|
@ -11,9 +11,7 @@ import 'package:path/path.dart' as p;
|
|||
|
||||
import '../models.dart';
|
||||
import '../task.dart';
|
||||
import 'package:mangayomi/services/background_downloader/src/desktop/desktop_downloader_http_client.dart';
|
||||
import 'package:mangayomi/services/background_downloader/src/desktop/desktop_downloader_native_http_client.dart.dart'
|
||||
as desktop_downloader_native;
|
||||
import 'package:mangayomi/services/background_downloader/src/downloader/downloader_http_client.dart';
|
||||
import 'isolate.dart';
|
||||
|
||||
const boundary = '-----background_downloader-akjhfw281onqciyhnIk';
|
||||
|
|
@ -23,11 +21,11 @@ const lineFeed = '\r\n';
|
|||
///
|
||||
/// Sends updates via the [sendPort] and can be commanded to cancel via
|
||||
/// the [messagesToIsolate] queue
|
||||
Future<void> doUploadTask(UploadTask task, String filePath, SendPort sendPort,
|
||||
bool useNativeHttpClient) async {
|
||||
Future<void> doUploadTask(
|
||||
UploadTask task, String filePath, SendPort sendPort) async {
|
||||
final resultStatus = task.post == 'binary'
|
||||
? await binaryUpload(task, filePath, sendPort, useNativeHttpClient)
|
||||
: await multipartUpload(task, filePath, sendPort, useNativeHttpClient);
|
||||
? await binaryUpload(task, filePath, sendPort)
|
||||
: await multipartUpload(task, filePath, sendPort);
|
||||
processStatusUpdateInIsolate(task, resultStatus, sendPort);
|
||||
}
|
||||
|
||||
|
|
@ -35,8 +33,8 @@ Future<void> doUploadTask(UploadTask task, String filePath, SendPort sendPort,
|
|||
///
|
||||
/// Sends updates via the [sendPort] and can be commanded to cancel via
|
||||
/// the [messagesToIsolate] queue
|
||||
Future<TaskStatus> binaryUpload(UploadTask task, String filePath,
|
||||
SendPort sendPort, bool useNativeHttpClient) async {
|
||||
Future<TaskStatus> binaryUpload(
|
||||
UploadTask task, String filePath, SendPort sendPort) async {
|
||||
final inFile = File(filePath);
|
||||
if (!inFile.existsSync()) {
|
||||
logError(task, 'file to upload does not exist: $filePath');
|
||||
|
|
@ -47,9 +45,7 @@ Future<TaskStatus> binaryUpload(UploadTask task, String filePath,
|
|||
final fileSize = inFile.lengthSync();
|
||||
var resultStatus = TaskStatus.failed;
|
||||
try {
|
||||
final client = useNativeHttpClient
|
||||
? desktop_downloader_native.DesktopDownloaderNativeHttpClient.httpClient
|
||||
: DesktopDownloaderHttpClient.httpClient;
|
||||
final client = DownloaderHttpClient.httpClient;
|
||||
final request =
|
||||
http.StreamedRequest(task.httpRequestMethod, Uri.parse(task.url));
|
||||
request.headers.addAll(task.headers);
|
||||
|
|
@ -100,8 +96,8 @@ Future<TaskStatus> binaryUpload(UploadTask task, String filePath,
|
|||
///
|
||||
/// Sends updates via the [sendPort] and can be commanded to cancel via
|
||||
/// the [messagesToIsolate] queue
|
||||
Future<TaskStatus> multipartUpload(UploadTask task, String filePath,
|
||||
SendPort sendPort, bool useNativeHttpClient) async {
|
||||
Future<TaskStatus> multipartUpload(
|
||||
UploadTask task, String filePath, SendPort sendPort) async {
|
||||
// field portion of the multipart, all in one string
|
||||
// multiple values should be encoded as '"value1", "value2", ...'
|
||||
final multiValueRegEx = RegExp(r'^(?:"[^"]+"\s*,\s*)+"[^"]+"$');
|
||||
|
|
@ -156,9 +152,7 @@ Future<TaskStatus> multipartUpload(UploadTask task, String filePath,
|
|||
var resultStatus = TaskStatus.failed;
|
||||
try {
|
||||
// setup the connection
|
||||
final client = useNativeHttpClient
|
||||
? desktop_downloader_native.DesktopDownloaderNativeHttpClient.httpClient
|
||||
: DesktopDownloaderHttpClient.httpClient;
|
||||
final client = DownloaderHttpClient.httpClient;
|
||||
final request =
|
||||
http.StreamedRequest(task.httpRequestMethod, Uri.parse(task.url));
|
||||
request.contentLength = contentLength;
|
||||
|
|
@ -6,9 +6,7 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:http/http.dart' as http;
|
||||
import 'base_downloader.dart';
|
||||
import 'localstore/localstore.dart';
|
||||
import 'package:mangayomi/services/background_downloader/src/desktop/desktop_downloader_http_client.dart';
|
||||
import 'package:mangayomi/services/background_downloader/src/desktop/desktop_downloader_native_http_client.dart.dart'
|
||||
as desktop_downloader_native;
|
||||
import 'package:mangayomi/services/background_downloader/src/downloader/downloader_http_client.dart';
|
||||
|
||||
/// Provides access to all functions of the plugin in a single place.
|
||||
interface class FileDownloader {
|
||||
|
|
@ -33,24 +31,20 @@ interface class FileDownloader {
|
|||
@visibleForTesting
|
||||
BaseDownloader get downloaderForTesting => _downloader;
|
||||
|
||||
factory FileDownloader(
|
||||
{PersistentStorage? persistentStorage,
|
||||
bool useNativeHttpClient = false}) {
|
||||
factory FileDownloader({PersistentStorage? persistentStorage}) {
|
||||
assert(
|
||||
_singleton == null || persistentStorage == null,
|
||||
'You can only supply a persistentStorage on the very first call to '
|
||||
'FileDownloader()');
|
||||
_singleton ??= FileDownloader._internal(
|
||||
persistentStorage ?? LocalStorePersistentStorage(),
|
||||
useNativeHttpClient);
|
||||
persistentStorage ?? LocalStorePersistentStorage(),
|
||||
);
|
||||
return _singleton!;
|
||||
}
|
||||
|
||||
FileDownloader._internal(
|
||||
PersistentStorage persistentStorage, bool useNativeHttpClient) {
|
||||
FileDownloader._internal(PersistentStorage persistentStorage) {
|
||||
database = Database(persistentStorage);
|
||||
_downloader = BaseDownloader.instance(
|
||||
persistentStorage, database, useNativeHttpClient);
|
||||
_downloader = BaseDownloader.instance(persistentStorage, database);
|
||||
}
|
||||
|
||||
/// True when initialization is complete and downloader ready for use
|
||||
|
|
@ -779,25 +773,12 @@ interface class FileDownloader {
|
|||
/// the downloader. If not set, the default [http.Client] will be used.
|
||||
/// The request is executed on an Isolate, to ensure minimal interference
|
||||
/// with the main Isolate
|
||||
Future<http.Response> request(Request request, bool useNativeHttpClient) {
|
||||
if (useNativeHttpClient) {
|
||||
return compute(_doRequest, (
|
||||
request,
|
||||
desktop_downloader_native
|
||||
.DesktopDownloaderNativeHttpClient.requestTimeout,
|
||||
desktop_downloader_native.DesktopDownloaderNativeHttpClient.proxy,
|
||||
desktop_downloader_native
|
||||
.DesktopDownloaderNativeHttpClient.bypassTLSCertificateValidation,
|
||||
true
|
||||
));
|
||||
}
|
||||
|
||||
Future<http.Response> request(Request request) {
|
||||
return compute(_doRequest, (
|
||||
request,
|
||||
DesktopDownloaderHttpClient.requestTimeout,
|
||||
DesktopDownloaderHttpClient.proxy,
|
||||
DesktopDownloaderHttpClient.bypassTLSCertificateValidation,
|
||||
false
|
||||
DownloaderHttpClient.requestTimeout,
|
||||
DownloaderHttpClient.proxy,
|
||||
DownloaderHttpClient.bypassTLSCertificateValidation
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -910,26 +891,14 @@ interface class FileDownloader {
|
|||
/// This function is run on an Isolate to ensure performance on the main
|
||||
/// Isolate is not affected
|
||||
Future<http.Response> _doRequest(
|
||||
(Request, Duration?, Map<String, dynamic>, bool, bool) params) async {
|
||||
final (
|
||||
request,
|
||||
requestTimeout,
|
||||
proxy,
|
||||
bypassTLSCertificateValidation,
|
||||
useNativeHttpClient
|
||||
) = params;
|
||||
(Request, Duration?, Map<String, dynamic>, bool) params) async {
|
||||
final (request, requestTimeout, proxy, bypassTLSCertificateValidation) =
|
||||
params;
|
||||
|
||||
if (useNativeHttpClient) {
|
||||
desktop_downloader_native.DesktopDownloaderNativeHttpClient.setHttpClient(
|
||||
requestTimeout, proxy, bypassTLSCertificateValidation);
|
||||
} else {
|
||||
DesktopDownloaderHttpClient.setHttpClient(
|
||||
requestTimeout, proxy, bypassTLSCertificateValidation);
|
||||
}
|
||||
DownloaderHttpClient.setHttpClient(
|
||||
requestTimeout, proxy, bypassTLSCertificateValidation);
|
||||
|
||||
final client = useNativeHttpClient
|
||||
? desktop_downloader_native.DesktopDownloaderNativeHttpClient.httpClient
|
||||
: DesktopDownloaderHttpClient.httpClient;
|
||||
final client = DownloaderHttpClient.httpClient;
|
||||
var response = http.Response('', 499,
|
||||
reasonPhrase: 'Not attempted'); // dummy to start with
|
||||
while (request.retriesRemaining >= 0) {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@ Future<(List<Video>, bool, String?)> getVideoList(
|
|||
final mangaDirectory = await storageProvider.getMangaMainDirectory(episode);
|
||||
final isLocalArchive = episode.manga.value!.isLocalArchive! &&
|
||||
episode.manga.value!.source != "torrent";
|
||||
final mp4animePath = "${mangaDirectory!.path}${episode.name}.mp4";
|
||||
final forbiddenCharacters =
|
||||
RegExp(r'[\\/:*?"<>|\0]|(^CON$|^PRN$|^AUX$|^NUL$|^COM[1-9]$|^LPT[1-9]$)');
|
||||
final mp4animePath =
|
||||
"${mangaDirectory!.path}${episode.name!.replaceAll(forbiddenCharacters, ' ')}.mp4";
|
||||
|
||||
if (await File(mp4animePath).exists() || isLocalArchive) {
|
||||
final path = isLocalArchive ? episode.archivePath : mp4animePath;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import 'package:cupertino_http/cupertino_http.dart';
|
||||
import 'package:http_interceptor/http_interceptor.dart';
|
||||
import 'package:mangayomi/eval/dart/model/m_bridge.dart';
|
||||
import 'dart:async';
|
||||
|
|
@ -8,33 +7,32 @@ import 'package:mangayomi/main.dart';
|
|||
import 'package:flutter_inappwebview/flutter_inappwebview.dart'
|
||||
as flutter_inappwebview;
|
||||
import 'package:mangayomi/models/settings.dart';
|
||||
import 'package:cronet_http/cronet_http.dart';
|
||||
import 'package:http/io_client.dart';
|
||||
import 'package:mangayomi/utils/log/log.dart';
|
||||
import 'package:rhttp/rhttp.dart' as rhttp;
|
||||
|
||||
class MClient {
|
||||
MClient();
|
||||
static Client httpClient() {
|
||||
if (isar.settings.getSync(227)?.useNativeHttpClient ?? false) {
|
||||
return nativeHttpClient();
|
||||
}
|
||||
return IOClient(HttpClient());
|
||||
}
|
||||
|
||||
static Client nativeHttpClient() {
|
||||
if (Platform.isAndroid) {
|
||||
final engine = CronetEngine.build(
|
||||
enablePublicKeyPinningBypassForLocalTrustAnchors: true,
|
||||
enableHttp2: true,
|
||||
enableBrotli: true,
|
||||
cacheMode: CacheMode.memory,
|
||||
cacheMaxSize: 5 * 1024 * 1024);
|
||||
return CronetClient.fromCronetEngine(engine, closeEngine: true);
|
||||
}
|
||||
if (Platform.isIOS || Platform.isMacOS) {
|
||||
final config = URLSessionConfiguration.ephemeralSessionConfiguration()
|
||||
..cache = URLCache.withCapacity(memoryCapacity: 5 * 1024 * 1024);
|
||||
return CupertinoClient.fromSessionConfiguration(config);
|
||||
static Client httpClient(
|
||||
{Map<String, dynamic>? reqcopyWith, rhttp.ClientSettings? settings}) {
|
||||
if (!(reqcopyWith?["useDartHttpClient"] ?? false)) {
|
||||
try {
|
||||
settings ??= rhttp.ClientSettings(
|
||||
throwOnStatusCode: false,
|
||||
proxySettings: reqcopyWith?["noProxy"] ?? false
|
||||
? const rhttp.ProxySettings.noProxy()
|
||||
: null,
|
||||
timeout: reqcopyWith?["timeout"] != null
|
||||
? Duration(seconds: reqcopyWith?["timeout"])
|
||||
: null,
|
||||
connectTimeout: reqcopyWith?["connectTimeout"] != null
|
||||
? Duration(seconds: reqcopyWith?["connectTimeout"])
|
||||
: null,
|
||||
tlsSettings: rhttp.TlsSettings(
|
||||
verifyCertificates:
|
||||
reqcopyWith?["verifyCertificates"] ?? false));
|
||||
return rhttp.RhttpCompatibleClient.createSync(settings: settings);
|
||||
} catch (_) {}
|
||||
}
|
||||
return IOClient(HttpClient());
|
||||
}
|
||||
|
|
@ -42,7 +40,7 @@ class MClient {
|
|||
static InterceptedClient init(
|
||||
{MSource? source, Map<String, dynamic>? reqcopyWith}) {
|
||||
return InterceptedClient.build(
|
||||
client: httpClient(),
|
||||
client: httpClient(reqcopyWith: reqcopyWith),
|
||||
interceptors: [MCookieManager(reqcopyWith), LoggerInterceptor()]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// This file is automatically generated, so please do not edit it.
|
||||
// Generated by `flutter_rust_bridge`@ 2.2.0.
|
||||
// Generated by `flutter_rust_bridge`@ 2.3.0.
|
||||
|
||||
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// This file is automatically generated, so please do not edit it.
|
||||
// Generated by `flutter_rust_bridge`@ 2.2.0.
|
||||
// Generated by `flutter_rust_bridge`@ 2.3.0.
|
||||
|
||||
// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
|
||||
|
||||
|
|
@ -31,6 +31,16 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
|
|||
);
|
||||
}
|
||||
|
||||
/// Initialize flutter_rust_bridge in mock mode.
|
||||
/// No libraries for FFI are loaded.
|
||||
static void initMock({
|
||||
required RustLibApi api,
|
||||
}) {
|
||||
instance.initMockImpl(
|
||||
api: api,
|
||||
);
|
||||
}
|
||||
|
||||
/// Dispose flutter_rust_bridge
|
||||
///
|
||||
/// The call to this function is optional, since flutter_rust_bridge (and everything else)
|
||||
|
|
@ -53,7 +63,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
|
|||
kDefaultExternalLibraryLoaderConfig;
|
||||
|
||||
@override
|
||||
String get codegenVersion => '2.2.0';
|
||||
String get codegenVersion => '2.3.0';
|
||||
|
||||
@override
|
||||
int get rustContentHash => -287789500;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// This file is automatically generated, so please do not edit it.
|
||||
// Generated by `flutter_rust_bridge`@ 2.2.0.
|
||||
// Generated by `flutter_rust_bridge`@ 2.3.0.
|
||||
|
||||
// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// This file is automatically generated, so please do not edit it.
|
||||
// Generated by `flutter_rust_bridge`@ 2.2.0.
|
||||
// Generated by `flutter_rust_bridge`@ 2.3.0.
|
||||
|
||||
// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'headers.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$headersHash() => r'c01fac2f30fa852565e9fca36f3a921f9bf3c112';
|
||||
String _$headersHash() => r'9ec5e31ef5ee097be7de6bed89a65b0afa5a51da';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
jni
|
||||
media_kit_native_event_loop
|
||||
rhttp
|
||||
rust_lib_mangayomi
|
||||
)
|
||||
|
||||
|
|
|
|||
152
pubspec.lock
152
pubspec.lock
|
|
@ -5,23 +5,23 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834
|
||||
sha256: "5aaf60d96c4cd00fe7f21594b5ad6a1b699c80a27420f8a837f4d68473ef09e3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "72.0.0"
|
||||
version: "68.0.0"
|
||||
_macros:
|
||||
dependency: transitive
|
||||
description: dart
|
||||
source: sdk
|
||||
version: "0.3.2"
|
||||
version: "0.1.0"
|
||||
analyzer:
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139
|
||||
sha256: "21f1d3720fd1c70316399d5e2bccaebb415c434592d778cce8acb967b8578808"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.7.0"
|
||||
version: "6.5.0"
|
||||
analyzer_plugin:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -138,18 +138,18 @@ packages:
|
|||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04
|
||||
sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.12"
|
||||
version: "2.4.11"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0
|
||||
sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.3.2"
|
||||
version: "7.3.1"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -230,14 +230,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
cronet_http:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cronet_http
|
||||
sha256: "3af9c4d57bf07ef4b307e77b22be4ad61bea19ee6ff65e62184863f3a09f1415"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -250,10 +242,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: crypto
|
||||
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||
sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
version: "3.0.5"
|
||||
csslib:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -262,14 +254,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
cupertino_http:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_http
|
||||
sha256: "7e75c45a27cc13a886ab0a1e4d8570078397057bd612de9d24fe5df0d9387717"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -282,10 +266,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: custom_lint_core
|
||||
sha256: a85e8f78f4c52f6c63cdaf8c872eb573db0231dcdf3c3a5906d493c1f8bc20e6
|
||||
sha256: "4ddbbdaa774265de44c97054dcec058a83d9081d071785ece601e348c18c267d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.3"
|
||||
version: "0.6.5"
|
||||
dart_eval:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -395,10 +379,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: extended_image_library
|
||||
sha256: c9caee8fe9b6547bd41c960c4f2d1ef8e34321804de6a1777f1d614a24247ad6
|
||||
sha256: "9a94ec9314aa206cfa35f16145c3cd6e2c924badcc670eaaca8a3a8063a68cd7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.4"
|
||||
version: "4.0.5"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -411,10 +395,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
|
||||
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.3"
|
||||
ffigen:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -542,10 +526,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_rust_bridge
|
||||
sha256: "7beb9cb4690916a6c4fd151d91dba53555ea258dbc029dd5f1bfba2e7bd32b86"
|
||||
sha256: b0271cc147d5afccf9774809e4eef52b7357babe1a1a31db649df6f02dd27580
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.3.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
|
|
@ -617,10 +601,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: go_router
|
||||
sha256: ddc16d34b0d74cb313986918c0f0885a7ba2fc24d8fb8419de75f0015144ccfe
|
||||
sha256: "48d03a1e4887b00fe622695139246e3c778ac814eeb32421467b56d23fa64034"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.3"
|
||||
version: "14.2.6"
|
||||
google_fonts:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -709,14 +693,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
http_profile:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_profile
|
||||
sha256: "7e679e355b09aaee2ab5010915c932cce3f2d1c11c3b2dc177891687014ffa78"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.0"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -781,14 +757,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0+1"
|
||||
jni:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: jni
|
||||
sha256: f377c585ea9c08d48b427dc2e03780af2889d1bb094440da853c6883c1acba4b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.1"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -841,18 +809,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.5"
|
||||
version: "10.0.4"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
version: "3.0.3"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -889,10 +857,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: macros
|
||||
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
|
||||
sha256: "12e8a9842b5a7390de7a781ec63d793527582398d16ea26c60fed58833c9ae79"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.2-main.4"
|
||||
version: "0.1.0-main.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -905,10 +873,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
version: "0.8.0"
|
||||
maybe_just_nothing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1036,10 +1004,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: "4de6c36df77ffbcef0a5aefe04669d33f2d18397fea228277b852a2d4e58e860"
|
||||
sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.0.1"
|
||||
version: "8.0.2"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1068,10 +1036,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "490539678396d4c3c0b06efdaab75ae60675c3e0c66f72bc04c2e2c1e0e2abeb"
|
||||
sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.9"
|
||||
version: "2.2.10"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1116,10 +1084,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: eaf2a1ec4472775451e88ca6a7b86559ef2f1d1ed903942ed135e38ea0097dca
|
||||
sha256: "76e4ab092c1b240d31177bb64d2b0bea43f43d0e23541ec866151b9f7b2490fa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "12.0.8"
|
||||
version: "12.0.12"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1132,10 +1100,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_html
|
||||
sha256: "6cac773d389e045a8d4f85418d07ad58ef9e42a56e063629ce14c4c26344de24"
|
||||
sha256: d220eb8476b466d58b161e10b3001d93999010a26228a3fb89c4280db1249546
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.2"
|
||||
version: "0.1.3+1"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1248,6 +1216,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
rhttp:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: rhttp
|
||||
sha256: "0390216e898cf2ebd353594715724ace0101e26f6fb84dac68293d4883432fb8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.4"
|
||||
riverpod:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1260,10 +1236,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: riverpod_analyzer_utils
|
||||
sha256: ee72770090078e6841d51355292335f1bc254907c6694283389dcb8156d99a4d
|
||||
sha256: ac28d7bc678471ec986b42d88e5a0893513382ff7542c7ac9634463b044ac72c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.3"
|
||||
version: "0.5.4"
|
||||
riverpod_annotation:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -1276,10 +1252,10 @@ packages:
|
|||
dependency: "direct dev"
|
||||
description:
|
||||
name: riverpod_generator
|
||||
sha256: "1ad626afbd8b01d168870b13c0b036f8a5bdb57c14cd426dc5b4595466bd6e2f"
|
||||
sha256: "63311e361ffc578d655dfc31b48dfa4ed3bc76fd06f9be845e9bf97c5c11a429"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
version: "2.4.3"
|
||||
rust_lib_mangayomi:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -1496,10 +1472,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
version: "0.7.0"
|
||||
time:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1560,10 +1536,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "94d8ad05f44c6d4e2ffe5567ab4d741b82d62e3c8e288cc1fcea45965edf47c9"
|
||||
sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.8"
|
||||
version: "6.3.9"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1576,10 +1552,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
|
||||
sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
version: "3.2.0"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1632,26 +1608,26 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
|
||||
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.4"
|
||||
version: "14.2.1"
|
||||
volume_controller:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: volume_controller
|
||||
sha256: "189bdc7a554f476b412e4c8b2f474562b09d74bc458c23667356bce3ca1d48c9"
|
||||
sha256: c71d4c62631305df63b72da79089e078af2659649301807fa746088f365cb48e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.7"
|
||||
version: "2.0.8"
|
||||
wakelock_plus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: wakelock_plus
|
||||
sha256: "4fa83a128b4127619e385f686b4f080a5d2de46cff8e8c94eccac5fcf76550e5"
|
||||
sha256: bf4ee6f17a2fa373ed3753ad0e602b7603f8c75af006d5b9bdade263928c0484
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.7"
|
||||
version: "1.2.8"
|
||||
wakelock_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1696,10 +1672,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: "015002c060f1ae9f41a818f2d5640389cc05283e368be19dc8d77cecb43c40c9"
|
||||
sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.5.3"
|
||||
version: "5.5.4"
|
||||
window_manager:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -1773,5 +1749,5 @@ packages:
|
|||
source: hosted
|
||||
version: "2.2.1"
|
||||
sdks:
|
||||
dart: ">=3.5.0 <4.0.0"
|
||||
dart: ">=3.4.4 <4.0.0"
|
||||
flutter: ">=3.22.0"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ publish_to: "none"
|
|||
version: 0.2.9+65
|
||||
|
||||
environment:
|
||||
sdk: ">=3.5.0 <4.0.0"
|
||||
sdk: ">=3.4.4 <4.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
|
|
@ -64,18 +64,17 @@ dependencies:
|
|||
git:
|
||||
url: https://github.com/kodjodevf/flutter_qjs.git
|
||||
ref: main
|
||||
cronet_http: ^1.3.1
|
||||
cupertino_http: ^1.5.0
|
||||
http: ^1.2.1
|
||||
flutter_code_editor: ^0.3.1
|
||||
flutter_highlight: ^0.7.0
|
||||
highlight: ^0.7.0
|
||||
json_view: ^0.4.2
|
||||
super_sliver_list: ^0.4.1
|
||||
flutter_rust_bridge: ^2.2.0
|
||||
flutter_rust_bridge: ^2.3.0
|
||||
rust_lib_mangayomi:
|
||||
path: rust_builder
|
||||
pseudom: ^1.0.1
|
||||
rhttp: ^0.5.4
|
||||
|
||||
dependency_overrides:
|
||||
http: ^1.2.1
|
||||
|
|
|
|||
9
rust/Cargo.lock
generated
9
rust/Cargo.lock
generated
|
|
@ -403,9 +403,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "flutter_rust_bridge"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5db18c05aac3922abfe24282af9128947d2b27856f3d6a4f30888bffa976855a"
|
||||
checksum = "f13c1e57b460f7adbd6cbf8b4cd0a1d14238ed64f5cc2a6c2ccb7a605ac01354"
|
||||
dependencies = [
|
||||
"allo-isolate",
|
||||
"android_logger",
|
||||
|
|
@ -420,6 +420,7 @@ dependencies = [
|
|||
"futures",
|
||||
"js-sys",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"oslog",
|
||||
"threadpool",
|
||||
"tokio",
|
||||
|
|
@ -430,9 +431,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "flutter_rust_bridge_macros"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f83de8013ddf893c420794e8a722f941eeca7f4939b46434282d66b676a0997c"
|
||||
checksum = "16c85e62d6d34f5c1590af004ccd3dc45b1c726dba6721b632b164c19894fab4"
|
||||
dependencies = [
|
||||
"hex",
|
||||
"md-5",
|
||||
|
|
|
|||
|
|
@ -7,5 +7,5 @@ edition = "2021"
|
|||
crate-type = ["cdylib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
flutter_rust_bridge = "=2.2.0"
|
||||
flutter_rust_bridge = "=2.3.0"
|
||||
image = "0.25.0"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// This file is automatically generated, so please do not edit it.
|
||||
// Generated by `flutter_rust_bridge`@ 2.2.0.
|
||||
// Generated by `flutter_rust_bridge`@ 2.3.0.
|
||||
|
||||
#![allow(
|
||||
non_camel_case_types,
|
||||
|
|
@ -36,7 +36,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
|
|||
default_rust_opaque = RustOpaqueMoi,
|
||||
default_rust_auto_opaque = RustAutoOpaqueMoi,
|
||||
);
|
||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.2.0";
|
||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.3.0";
|
||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -287789500;
|
||||
|
||||
// Section: executor
|
||||
|
|
@ -184,7 +184,7 @@ impl SseEncode for bool {
|
|||
#[cfg(not(target_family = "wasm"))]
|
||||
mod io {
|
||||
// This file is automatically generated, so please do not edit it.
|
||||
// Generated by `flutter_rust_bridge`@ 2.2.0.
|
||||
// Generated by `flutter_rust_bridge`@ 2.3.0.
|
||||
|
||||
// Section: imports
|
||||
|
||||
|
|
@ -206,7 +206,7 @@ pub use io::*;
|
|||
#[cfg(target_family = "wasm")]
|
||||
mod web {
|
||||
// This file is automatically generated, so please do not edit it.
|
||||
// Generated by `flutter_rust_bridge`@ 2.2.0.
|
||||
// Generated by `flutter_rust_bridge`@ 2.3.0.
|
||||
|
||||
// Section: imports
|
||||
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
jni
|
||||
media_kit_native_event_loop
|
||||
rhttp
|
||||
rust_lib_mangayomi
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue