fix download problem
This commit is contained in:
parent
01af13d5ee
commit
ba8ac10cc2
13 changed files with 181 additions and 128 deletions
|
|
@ -15,7 +15,6 @@ import 'package:mangayomi/modules/manga/detail/manga_detail_view.dart';
|
|||
import 'package:mangayomi/modules/manga/detail/providers/state_providers.dart';
|
||||
import 'package:mangayomi/modules/manga/detail/widgets/chapter_filter_list_tile_widget.dart';
|
||||
import 'package:mangayomi/modules/more/providers/incognito_mode_state_provider.dart';
|
||||
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
||||
import 'package:mangayomi/utils/extensions/chapter.dart';
|
||||
|
||||
class MangaDetailsView extends ConsumerStatefulWidget {
|
||||
|
|
@ -59,6 +58,9 @@ class _MangaDetailsViewState extends ConsumerState<MangaDetailsView> {
|
|||
(q) => q.isMangaEqualTo(widget.manga.isManga!)))
|
||||
.watch(fireImmediately: true),
|
||||
builder: (context, snapshot) {
|
||||
final isFr =
|
||||
ref.watch(l10nLocaleStateProvider).languageCode ==
|
||||
"fr";
|
||||
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
|
||||
final incognitoMode =
|
||||
ref.watch(incognitoModeStateProvider);
|
||||
|
|
@ -68,9 +70,7 @@ class _MangaDetailsViewState extends ConsumerState<MangaDetailsView> {
|
|||
.toList()
|
||||
.reversed
|
||||
.toList();
|
||||
final isFr =
|
||||
ref.watch(l10nLocaleStateProvider).languageCode ==
|
||||
"fr";
|
||||
|
||||
if (entries.isNotEmpty && !incognitoMode) {
|
||||
final chap = entries.first.chapter.value!;
|
||||
return CustomFloatingActionBtn(
|
||||
|
|
@ -98,7 +98,20 @@ class _MangaDetailsViewState extends ConsumerState<MangaDetailsView> {
|
|||
width: isFr ? 130 : 90,
|
||||
);
|
||||
}
|
||||
return const ProgressCenter();
|
||||
return CustomFloatingActionBtn(
|
||||
isExtended: !isExtended,
|
||||
label: l10n.read,
|
||||
onPressed: () {
|
||||
widget.manga.chapters
|
||||
.toList()
|
||||
.reversed
|
||||
.toList()
|
||||
.last
|
||||
.pushToReaderView(context);
|
||||
},
|
||||
textWidth: isFr ? 80 : 40,
|
||||
width: isFr ? 130 : 90,
|
||||
);
|
||||
},
|
||||
)
|
||||
: Container();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
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';
|
||||
|
|
@ -20,8 +21,6 @@ import 'package:path_provider/path_provider.dart';
|
|||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
part 'download_provider.g.dart';
|
||||
|
||||
FileDownloader _mangaFileDownloader = FileDownloader(isAnime: false);
|
||||
FileDownloader _animeFileDownloader = FileDownloader(isAnime: true);
|
||||
@riverpod
|
||||
Future<List<PageUrl>> downloadChapter(
|
||||
DownloadChapterRef ref, {
|
||||
|
|
@ -251,7 +250,10 @@ Future<List<PageUrl>> downloadChapter(
|
|||
});
|
||||
} else {
|
||||
if (isManga) {
|
||||
await _mangaFileDownloader.downloadBatch(
|
||||
await FileDownloader(
|
||||
useNativeHttpClient:
|
||||
ref.watch(useNativeHttpClientStateProvider))
|
||||
.downloadBatch(
|
||||
tasks,
|
||||
batchProgressCallback: (succeeded, failed) async {
|
||||
if (succeeded == tasks.length) {
|
||||
|
|
@ -307,7 +309,7 @@ Future<List<PageUrl>> downloadChapter(
|
|||
},
|
||||
);
|
||||
} else {
|
||||
await _animeFileDownloader.download(
|
||||
await FileDownloader(useNativeHttpClient: false).download(
|
||||
tasks.first,
|
||||
onProgress: (progress) async {
|
||||
bool isEmpty = isar.downloads
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ 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.dart';
|
||||
import 'package:mangayomi/services/background_downloader/src/desktop/desktop_downloader_anime.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 'database.dart';
|
||||
import 'exceptions.dart';
|
||||
import 'models.dart';
|
||||
|
|
@ -100,9 +100,11 @@ abstract base class BaseDownloader {
|
|||
|
||||
BaseDownloader();
|
||||
|
||||
factory BaseDownloader.instance(
|
||||
PersistentStorage persistentStorage, Database database, bool isAnime) {
|
||||
final instance = isAnime ? DesktopDownloaderAnime() : DesktopDownloader();
|
||||
factory BaseDownloader.instance(PersistentStorage persistentStorage,
|
||||
Database database, bool useNativeHttpClient) {
|
||||
final instance = !useNativeHttpClient
|
||||
? DesktopDownloaderHttpClient()
|
||||
: DesktopDownloaderNativeHttpClient();
|
||||
instance._storage = persistentStorage;
|
||||
instance.database = database;
|
||||
unawaited(instance.initialize());
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ 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/background_downloader.dart';
|
||||
import 'desktop_downloader.dart';
|
||||
import 'download_isolate.dart';
|
||||
import 'isolate.dart';
|
||||
|
||||
|
|
@ -12,8 +13,11 @@ 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) async {
|
||||
final client = DesktopDownloader.httpClient;
|
||||
Future<void> doDataTask(
|
||||
DataTask task, SendPort sendPort, bool useNativeHttpClient) async {
|
||||
final client = useNativeHttpClient
|
||||
? desktop_downloader_native.DesktopDownloaderNativeHttpClient.httpClient
|
||||
: DesktopDownloaderHttpClient.httpClient;
|
||||
var request = http.Request(task.httpRequestMethod, Uri.parse(task.url));
|
||||
request.headers.addAll(task.headers);
|
||||
if (task.post is String) {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ 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';
|
||||
|
|
@ -28,26 +27,29 @@ 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 DesktopDownloaderAnime extends BaseDownloader {
|
||||
static final _log = Logger('DesktopDownloaderAnime');
|
||||
final class DesktopDownloaderHttpClient extends BaseDownloader {
|
||||
static final _log = Logger('DesktopDownloaderHttpClient');
|
||||
static const unlimited = 1 << 20;
|
||||
var maxConcurrent = 10;
|
||||
var maxConcurrentByHost = unlimited;
|
||||
var maxConcurrentByGroup = unlimited;
|
||||
static final DesktopDownloaderAnime _singleton = DesktopDownloaderAnime._internal();
|
||||
static final DesktopDownloaderHttpClient _singleton =
|
||||
DesktopDownloaderHttpClient._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.httpClient();
|
||||
static var httpClient = IOClient(HttpClient()
|
||||
..badCertificateCallback =
|
||||
(X509Certificate cert, String host, int port) => true);
|
||||
static Duration? _requestTimeout;
|
||||
static var _proxy = <String, dynamic>{}; // 'address' and 'port'
|
||||
static var _bypassTLSCertificateValidation = false;
|
||||
|
||||
factory DesktopDownloaderAnime() => _singleton;
|
||||
factory DesktopDownloaderHttpClient() => _singleton;
|
||||
|
||||
DesktopDownloaderAnime._internal();
|
||||
DesktopDownloaderHttpClient._internal();
|
||||
|
||||
@override
|
||||
Future<bool> enqueue(Task task) async {
|
||||
|
|
@ -140,7 +142,7 @@ final class DesktopDownloaderAnime extends BaseDownloader {
|
|||
return;
|
||||
}
|
||||
log.finer('${isResume ? "Resuming" : "Starting"} taskId ${task.taskId}');
|
||||
await Isolate.spawn(doTask, (rootIsolateToken, receivePort.sendPort),
|
||||
await Isolate.spawn(doTask, (rootIsolateToken, receivePort.sendPort, false),
|
||||
onError: errorPort.sendPort);
|
||||
final messagesFromIsolate = StreamQueue<dynamic>(receivePort);
|
||||
final sendPort = await messagesFromIsolate.next as SendPort;
|
||||
|
|
@ -28,26 +28,27 @@ 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 DesktopDownloader extends BaseDownloader {
|
||||
static final _log = Logger('DesktopDownloader');
|
||||
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 DesktopDownloader _singleton = DesktopDownloader._internal();
|
||||
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.httpClient();
|
||||
static var httpClient = MClient.nativeHttpClient();
|
||||
static Duration? _requestTimeout;
|
||||
static var _proxy = <String, dynamic>{}; // 'address' and 'port'
|
||||
static var _bypassTLSCertificateValidation = false;
|
||||
|
||||
factory DesktopDownloader() => _singleton;
|
||||
factory DesktopDownloaderNativeHttpClient() => _singleton;
|
||||
|
||||
DesktopDownloader._internal();
|
||||
DesktopDownloaderNativeHttpClient._internal();
|
||||
|
||||
@override
|
||||
Future<bool> enqueue(Task task) async {
|
||||
|
|
@ -140,7 +141,7 @@ final class DesktopDownloader extends BaseDownloader {
|
|||
return;
|
||||
}
|
||||
log.finer('${isResume ? "Resuming" : "Starting"} taskId ${task.taskId}');
|
||||
await Isolate.spawn(doTask, (rootIsolateToken, receivePort.sendPort),
|
||||
await Isolate.spawn(doTask, (rootIsolateToken, receivePort.sendPort, true),
|
||||
onError: errorPort.sendPort);
|
||||
final messagesFromIsolate = StreamQueue<dynamic>(receivePort);
|
||||
final sendPort = await messagesFromIsolate.next as SendPort;
|
||||
|
|
@ -582,7 +583,7 @@ final class DesktopDownloader extends BaseDownloader {
|
|||
(X509Certificate cert, String host, int port) => true;
|
||||
httpClient = IOClient(client);
|
||||
} else {
|
||||
httpClient = MClient.httpClient();
|
||||
httpClient = MClient.nativeHttpClient();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -6,6 +6,9 @@ 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:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
|
|
@ -13,7 +16,6 @@ import '../exceptions.dart';
|
|||
import '../models.dart';
|
||||
import '../task.dart';
|
||||
import '../utils.dart';
|
||||
import 'desktop_downloader.dart';
|
||||
import 'isolate.dart';
|
||||
|
||||
var taskRangeStartByte = 0; // Start of the Task's download range
|
||||
|
|
@ -30,7 +32,8 @@ Future<void> doDownloadTask(
|
|||
ResumeData? resumeData,
|
||||
bool isResume,
|
||||
Duration requestTimeout,
|
||||
SendPort sendPort) async {
|
||||
SendPort sendPort,
|
||||
bool useNativeHttpClient) async {
|
||||
// use downloadTask from here on as a 'global' variable in this isolate,
|
||||
// as we may change the filename of the task
|
||||
downloadTask = task;
|
||||
|
|
@ -45,7 +48,9 @@ Future<void> doDownloadTask(
|
|||
final eTag = resumeData?.eTag;
|
||||
isResume = isResume &&
|
||||
await determineIfResumeIsPossible(tempFilePath, requiredStartByte);
|
||||
final client = DesktopDownloader.httpClient;
|
||||
final client = useNativeHttpClient
|
||||
? desktop_downloader_native.DesktopDownloaderNativeHttpClient.httpClient
|
||||
: DesktopDownloaderHttpClient.httpClient;
|
||||
var request =
|
||||
http.Request(downloadTask.httpRequestMethod, Uri.parse(downloadTask.url));
|
||||
request.headers.addAll(downloadTask.headers);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ 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/exceptions.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
|
@ -16,7 +18,6 @@ import 'package:logging/logging.dart';
|
|||
import '../models.dart';
|
||||
import '../task.dart';
|
||||
import 'data_isolate.dart';
|
||||
import 'desktop_downloader.dart';
|
||||
import 'download_isolate.dart';
|
||||
import 'parallel_download_isolate.dart';
|
||||
import 'upload_isolate.dart';
|
||||
|
|
@ -50,8 +51,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) isolateArguments) async {
|
||||
final (rootIsolateToken, sendPort) = isolateArguments;
|
||||
Future<void> doTask((RootIsolateToken, SendPort, bool) isolateArguments) async {
|
||||
final (rootIsolateToken, sendPort, useNativeHttpClient) = isolateArguments;
|
||||
BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
|
||||
final receivePort = ReceivePort();
|
||||
// send the receive port back to the main Isolate
|
||||
|
|
@ -67,8 +68,11 @@ Future<void> doTask((RootIsolateToken, SendPort) isolateArguments) async {
|
|||
Map<String, dynamic> proxy,
|
||||
bool bypassTLSCertificateValidation
|
||||
) = await messagesToIsolate.next;
|
||||
DesktopDownloader.setHttpClient(
|
||||
requestTimeout, proxy, bypassTLSCertificateValidation);
|
||||
useNativeHttpClient
|
||||
? DesktopDownloaderNativeHttpClient.setHttpClient(
|
||||
requestTimeout, proxy, bypassTLSCertificateValidation)
|
||||
: DesktopDownloaderHttpClient.setHttpClient(
|
||||
requestTimeout, proxy, bypassTLSCertificateValidation);
|
||||
Logger.root.level = Level.ALL;
|
||||
Logger.root.onRecord.listen((LogRecord rec) {
|
||||
if (kDebugMode) {
|
||||
|
|
@ -95,11 +99,18 @@ Future<void> doTask((RootIsolateToken, SendPort) isolateArguments) async {
|
|||
resumeData,
|
||||
isResume,
|
||||
requestTimeout ?? const Duration(seconds: 60),
|
||||
sendPort),
|
||||
DownloadTask() => doDownloadTask(task, filePath, resumeData, isResume,
|
||||
requestTimeout ?? const Duration(seconds: 60), sendPort),
|
||||
UploadTask() => doUploadTask(task, filePath, sendPort),
|
||||
DataTask() => doDataTask(task, sendPort)
|
||||
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)
|
||||
};
|
||||
}
|
||||
receivePort.close();
|
||||
|
|
|
|||
|
|
@ -7,13 +7,14 @@ 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 '../chunk.dart';
|
||||
import '../exceptions.dart';
|
||||
import '../models.dart';
|
||||
import '../task.dart';
|
||||
import '../utils.dart';
|
||||
import 'desktop_downloader.dart';
|
||||
import 'download_isolate.dart';
|
||||
import 'isolate.dart';
|
||||
|
||||
|
|
@ -64,11 +65,15 @@ Future<void> doParallelDownloadTask(
|
|||
ResumeData? resumeData,
|
||||
bool isResume,
|
||||
Duration requestTimeout,
|
||||
SendPort sendPort) async {
|
||||
SendPort sendPort,
|
||||
bool useNativeHttpClient) async {
|
||||
parentTask = task;
|
||||
if (!isResume) {
|
||||
// start the download by creating [Chunk]s and enqueuing chunk tasks
|
||||
final response = await DesktopDownloader.httpClient
|
||||
final response = await (useNativeHttpClient
|
||||
? desktop_downloader_native
|
||||
.DesktopDownloaderNativeHttpClient.httpClient
|
||||
: DesktopDownloaderHttpClient.httpClient)
|
||||
.head(Uri.parse(task.url), headers: task.headers);
|
||||
responseHeaders = response.headers;
|
||||
responseStatusCode = response.statusCode;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ import 'package:path/path.dart' as p;
|
|||
|
||||
import '../models.dart';
|
||||
import '../task.dart';
|
||||
import 'desktop_downloader.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 'isolate.dart';
|
||||
|
||||
const boundary = '-----background_downloader-akjhfw281onqciyhnIk';
|
||||
|
|
@ -21,11 +23,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) async {
|
||||
Future<void> doUploadTask(UploadTask task, String filePath, SendPort sendPort,
|
||||
bool useNativeHttpClient) async {
|
||||
final resultStatus = task.post == 'binary'
|
||||
? await binaryUpload(task, filePath, sendPort)
|
||||
: await multipartUpload(task, filePath, sendPort);
|
||||
? await binaryUpload(task, filePath, sendPort, useNativeHttpClient)
|
||||
: await multipartUpload(task, filePath, sendPort, useNativeHttpClient);
|
||||
processStatusUpdateInIsolate(task, resultStatus, sendPort);
|
||||
}
|
||||
|
||||
|
|
@ -33,8 +35,8 @@ Future<void> doUploadTask(
|
|||
///
|
||||
/// Sends updates via the [sendPort] and can be commanded to cancel via
|
||||
/// the [messagesToIsolate] queue
|
||||
Future<TaskStatus> binaryUpload(
|
||||
UploadTask task, String filePath, SendPort sendPort) async {
|
||||
Future<TaskStatus> binaryUpload(UploadTask task, String filePath,
|
||||
SendPort sendPort, bool useNativeHttpClient) async {
|
||||
final inFile = File(filePath);
|
||||
if (!inFile.existsSync()) {
|
||||
logError(task, 'file to upload does not exist: $filePath');
|
||||
|
|
@ -45,7 +47,9 @@ Future<TaskStatus> binaryUpload(
|
|||
final fileSize = inFile.lengthSync();
|
||||
var resultStatus = TaskStatus.failed;
|
||||
try {
|
||||
final client = DesktopDownloader.httpClient;
|
||||
final client = useNativeHttpClient
|
||||
? desktop_downloader_native.DesktopDownloaderNativeHttpClient.httpClient
|
||||
: DesktopDownloaderHttpClient.httpClient;
|
||||
final request =
|
||||
http.StreamedRequest(task.httpRequestMethod, Uri.parse(task.url));
|
||||
request.headers.addAll(task.headers);
|
||||
|
|
@ -96,8 +100,8 @@ Future<TaskStatus> binaryUpload(
|
|||
///
|
||||
/// Sends updates via the [sendPort] and can be commanded to cancel via
|
||||
/// the [messagesToIsolate] queue
|
||||
Future<TaskStatus> multipartUpload(
|
||||
UploadTask task, String filePath, SendPort sendPort) async {
|
||||
Future<TaskStatus> multipartUpload(UploadTask task, String filePath,
|
||||
SendPort sendPort, bool useNativeHttpClient) async {
|
||||
// field portion of the multipart, all in one string
|
||||
// multiple values should be encoded as '"value1", "value2", ...'
|
||||
final multiValueRegEx = RegExp(r'^(?:"[^"]+"\s*,\s*)+"[^"]+"$');
|
||||
|
|
@ -152,7 +156,9 @@ Future<TaskStatus> multipartUpload(
|
|||
var resultStatus = TaskStatus.failed;
|
||||
try {
|
||||
// setup the connection
|
||||
final client = DesktopDownloader.httpClient;
|
||||
final client = useNativeHttpClient
|
||||
? desktop_downloader_native.DesktopDownloaderNativeHttpClient.httpClient
|
||||
: DesktopDownloaderHttpClient.httpClient;
|
||||
final request =
|
||||
http.StreamedRequest(task.httpRequestMethod, Uri.parse(task.url));
|
||||
request.contentLength = contentLength;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:http/http.dart' as http;
|
||||
import 'base_downloader.dart';
|
||||
import 'localstore/localstore.dart';
|
||||
import 'desktop/desktop_downloader.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;
|
||||
|
||||
/// Provides access to all functions of the plugin in a single place.
|
||||
interface class FileDownloader {
|
||||
|
|
@ -32,19 +34,23 @@ interface class FileDownloader {
|
|||
BaseDownloader get downloaderForTesting => _downloader;
|
||||
|
||||
factory FileDownloader(
|
||||
{PersistentStorage? persistentStorage, bool isAnime = false}) {
|
||||
{PersistentStorage? persistentStorage,
|
||||
bool useNativeHttpClient = false}) {
|
||||
assert(
|
||||
_singleton == null || persistentStorage == null,
|
||||
'You can only supply a persistentStorage on the very first call to '
|
||||
'FileDownloader()');
|
||||
_singleton ??= FileDownloader._internal(
|
||||
persistentStorage ?? LocalStorePersistentStorage(), isAnime);
|
||||
persistentStorage ?? LocalStorePersistentStorage(),
|
||||
useNativeHttpClient);
|
||||
return _singleton!;
|
||||
}
|
||||
|
||||
FileDownloader._internal(PersistentStorage persistentStorage, bool isAnime) {
|
||||
FileDownloader._internal(
|
||||
PersistentStorage persistentStorage, bool useNativeHttpClient) {
|
||||
database = Database(persistentStorage);
|
||||
_downloader = BaseDownloader.instance(persistentStorage, database, isAnime);
|
||||
_downloader = BaseDownloader.instance(
|
||||
persistentStorage, database, useNativeHttpClient);
|
||||
}
|
||||
|
||||
/// True when initialization is complete and downloader ready for use
|
||||
|
|
@ -773,12 +779,27 @@ 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) => compute(_doRequest, (
|
||||
Future<http.Response> request(Request request, bool useNativeHttpClient) {
|
||||
if (useNativeHttpClient) {
|
||||
return compute(_doRequest, (
|
||||
request,
|
||||
DesktopDownloader.requestTimeout,
|
||||
DesktopDownloader.proxy,
|
||||
DesktopDownloader.bypassTLSCertificateValidation
|
||||
desktop_downloader_native
|
||||
.DesktopDownloaderNativeHttpClient.requestTimeout,
|
||||
desktop_downloader_native.DesktopDownloaderNativeHttpClient.proxy,
|
||||
desktop_downloader_native
|
||||
.DesktopDownloaderNativeHttpClient.bypassTLSCertificateValidation,
|
||||
true
|
||||
));
|
||||
}
|
||||
|
||||
return compute(_doRequest, (
|
||||
request,
|
||||
DesktopDownloaderHttpClient.requestTimeout,
|
||||
DesktopDownloaderHttpClient.proxy,
|
||||
DesktopDownloaderHttpClient.bypassTLSCertificateValidation,
|
||||
false
|
||||
));
|
||||
}
|
||||
|
||||
/// Move the file represented by the [task] to a shared storage
|
||||
/// [destination] and potentially a [directory] within that destination. If
|
||||
|
|
@ -889,13 +910,26 @@ 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) params) async {
|
||||
final (request, requestTimeout, proxy, bypassTLSCertificateValidation) =
|
||||
params;
|
||||
(Request, Duration?, Map<String, dynamic>, bool, bool) params) async {
|
||||
final (
|
||||
request,
|
||||
requestTimeout,
|
||||
proxy,
|
||||
bypassTLSCertificateValidation,
|
||||
useNativeHttpClient
|
||||
) = params;
|
||||
|
||||
DesktopDownloader.setHttpClient(
|
||||
requestTimeout, proxy, bypassTLSCertificateValidation);
|
||||
final client = DesktopDownloader.httpClient;
|
||||
if (useNativeHttpClient) {
|
||||
desktop_downloader_native.DesktopDownloaderNativeHttpClient.setHttpClient(
|
||||
requestTimeout, proxy, bypassTLSCertificateValidation);
|
||||
} else {
|
||||
DesktopDownloaderHttpClient.setHttpClient(
|
||||
requestTimeout, proxy, bypassTLSCertificateValidation);
|
||||
}
|
||||
|
||||
final client = useNativeHttpClient
|
||||
? desktop_downloader_native.DesktopDownloaderNativeHttpClient.httpClient
|
||||
: DesktopDownloaderHttpClient.httpClient;
|
||||
var response = http.Response('', 499,
|
||||
reasonPhrase: 'Not attempted'); // dummy to start with
|
||||
while (request.retriesRemaining >= 0) {
|
||||
|
|
|
|||
|
|
@ -8,11 +8,9 @@ import 'dart:typed_data';
|
|||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:mangayomi/services/background_downloader/src/desktop/desktop_downloader.dart';
|
||||
import 'package:mime/mime.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import 'file_downloader.dart';
|
||||
import 'models.dart';
|
||||
import 'utils.dart';
|
||||
|
|
@ -669,39 +667,6 @@ final class DownloadTask extends Task {
|
|||
///
|
||||
/// The suggested filename is obtained by making a HEAD request to the url
|
||||
/// represented by the [DownloadTask], including urlQueryParameters and headers
|
||||
Future<DownloadTask> withSuggestedFilename(
|
||||
{unique = false,
|
||||
Future<DownloadTask> Function(
|
||||
DownloadTask task, Map<String, String> headers, bool unique)
|
||||
taskWithFilenameBuilder = taskWithSuggestedFilename}) async {
|
||||
try {
|
||||
final response = await DesktopDownloader.httpClient
|
||||
.head(Uri.parse(url), headers: headers);
|
||||
if ([200, 201, 202, 203, 204, 205, 206].contains(response.statusCode)) {
|
||||
return taskWithFilenameBuilder(this, response.headers, unique);
|
||||
}
|
||||
} catch (e) {
|
||||
_log.finer('Error connecting to server');
|
||||
}
|
||||
return taskWithFilenameBuilder(this, {}, unique);
|
||||
}
|
||||
|
||||
/// Return the expected file size for this task, or -1 if unknown
|
||||
///
|
||||
/// The expected file size is obtained by making a HEAD request to the url
|
||||
/// represented by the [DownloadTask], including urlQueryParameters and headers
|
||||
Future<int> expectedFileSize() async {
|
||||
try {
|
||||
final response = await DesktopDownloader.httpClient
|
||||
.head(Uri.parse(url), headers: headers);
|
||||
if ([200, 201, 202, 203, 204, 205, 206].contains(response.statusCode)) {
|
||||
return getContentLength(response.headers, this);
|
||||
}
|
||||
} catch (e) {
|
||||
// no content length available
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// Constant used with `filename` field to indicate server suggestion requested
|
||||
static const suggestedFilename = '?';
|
||||
|
|
|
|||
|
|
@ -15,24 +15,27 @@ import 'package:mangayomi/utils/log/log.dart';
|
|||
class MClient {
|
||||
MClient();
|
||||
static Client httpClient() {
|
||||
if ((Platform.isAndroid || Platform.isIOS || Platform.isMacOS) &&
|
||||
(isar.settings.getSync(227)?.useNativeHttpClient ?? false)) {
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
return IOClient(HttpClient());
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue