feat: use rust rqbit to replace aria2c

This commit is contained in:
xkeyC
2025-12-05 16:32:53 +08:00
parent c5de9e2252
commit 4315e36cbe
30 changed files with 4789 additions and 1052 deletions

View File

@@ -1,222 +0,0 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:starcitizen_doctor/common/conf/binary_conf.dart';
import 'dart:io';
import 'dart:math';
import 'package:aria2/aria2.dart';
import 'package:flutter/foundation.dart';
import 'package:hive_ce/hive.dart';
import 'package:starcitizen_doctor/api/api.dart';
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
import 'package:starcitizen_doctor/common/rust/api/rs_process.dart' as rs_process;
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/common/utils/provider.dart';
import 'package:starcitizen_doctor/ui/home/downloader/home_downloader_ui_model.dart';
part 'aria2c.g.dart';
part 'aria2c.freezed.dart';
@freezed
abstract class Aria2cModelState with _$Aria2cModelState {
const factory Aria2cModelState({required String aria2cDir, Aria2c? aria2c, Aria2GlobalStat? aria2globalStat}) =
_Aria2cModelState;
}
extension Aria2cModelExt on Aria2cModelState {
bool get isRunning => aria2c != null;
bool get hasDownloadTask => aria2globalStat != null && aria2TotalTaskNum > 0;
int get aria2TotalTaskNum =>
aria2globalStat == null ? 0 : ((aria2globalStat!.numActive ?? 0) + (aria2globalStat!.numWaiting ?? 0));
}
@riverpod
class Aria2cModel extends _$Aria2cModel {
bool _disposed = false;
@override
Aria2cModelState build() {
if (appGlobalState.applicationBinaryModuleDir == null) {
throw Exception("applicationBinaryModuleDir is null");
}
ref.onDispose(() {
_disposed = true;
});
ref.keepAlive();
final aria2cDir = "${appGlobalState.applicationBinaryModuleDir}\\aria2c";
// LazyLoad init
() async {
try {
final sessionFile = File("$aria2cDir\\aria2.session");
// 有下载任务则第一时间初始化
if (await sessionFile.exists ()
&& (await sessionFile.readAsString()).trim().isNotEmpty) {
dPrint("launch Aria2c daemon");
await launchDaemon(appGlobalState.applicationBinaryModuleDir!);
} else {
dPrint("LazyLoad Aria2c daemon");
}
} catch (e) {
dPrint("Aria2cManager.checkLazyLoad Error:$e");
}
}();
return Aria2cModelState(aria2cDir: aria2cDir);
}
Future launchDaemon(String applicationBinaryModuleDir) async {
if (state.aria2c != null) return;
await BinaryModuleConf.extractModule(["aria2c"], applicationBinaryModuleDir);
/// skip for debug hot reload
if (kDebugMode) {
if ((await SystemHelper.getPID("aria2c")).isNotEmpty) {
dPrint("[Aria2cManager] debug skip for hot reload");
return;
}
}
final sessionFile = File("${state.aria2cDir}\\aria2.session");
if (!await sessionFile.exists()) {
await sessionFile.create(recursive: true);
}
final exePath = "${state.aria2cDir}\\aria2c.exe";
final port = await getFreePort();
final pwd = generateRandomPassword(16);
dPrint("pwd === $pwd");
final trackerList = await Api.getTorrentTrackerList();
dPrint("trackerList === $trackerList");
dPrint("Aria2cManager .----- aria2c start $port------");
final stream = rs_process.start(
executable: exePath,
arguments: [
"-V",
"-c",
"-x 16",
"--dir=${state.aria2cDir}\\downloads",
"--disable-ipv6",
"--enable-rpc",
"--pause",
"--rpc-listen-port=$port",
"--rpc-secret=$pwd",
"--input-file=${sessionFile.absolute.path.trim()}",
"--save-session=${sessionFile.absolute.path.trim()}",
"--save-session-interval=60",
"--file-allocation=trunc",
"--seed-time=0",
],
workingDirectory: state.aria2cDir,
);
String launchError = "";
stream.listen((event) {
dPrint("Aria2cManager.rs_process event === [${event.rsPid}] ${event.dataType} >> ${event.data}");
switch (event.dataType) {
case rs_process.RsProcessStreamDataType.output:
if (event.data.contains("IPv4 RPC: listening on TCP port")) {
_onLaunch(port, pwd, trackerList);
}
break;
case rs_process.RsProcessStreamDataType.error:
launchError = event.data;
state = state.copyWith(aria2c: null);
break;
case rs_process.RsProcessStreamDataType.exit:
launchError = event.data;
state = state.copyWith(aria2c: null);
break;
}
});
while (true) {
if (state.aria2c != null) return;
if (launchError.isNotEmpty) throw launchError;
await Future.delayed(const Duration(milliseconds: 100));
}
}
Future<int> getFreePort() async {
final serverSocket = await ServerSocket.bind("127.0.0.1", 0);
final port = serverSocket.port;
await serverSocket.close();
return port;
}
String generateRandomPassword(int length) {
const String charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = Random();
StringBuffer buffer = StringBuffer();
for (int i = 0; i < length; i++) {
int randomIndex = random.nextInt(charset.length);
buffer.write(charset[randomIndex]);
}
return buffer.toString();
}
int textToByte(String text) {
if (text.length == 1) {
return 0;
}
if (int.tryParse(text) != null) {
return int.parse(text);
}
if (text.endsWith("k")) {
return int.parse(text.substring(0, text.length - 1)) * 1024;
}
if (text.endsWith("m")) {
return int.parse(text.substring(0, text.length - 1)) * 1024 * 1024;
}
return 0;
}
Future<void> _onLaunch(int port, String pwd, String trackerList) async {
final aria2c = Aria2c("ws://127.0.0.1:$port/jsonrpc", "websocket", pwd);
state = state.copyWith(aria2c: aria2c);
aria2c.getVersion().then((value) {
dPrint("Aria2cManager.connected! version == ${value.version}");
_listenState(aria2c);
});
final box = await Hive.openBox("app_conf");
aria2c.changeGlobalOption(
Aria2Option()
..maxOverallUploadLimit = textToByte(box.get("downloader_up_limit", defaultValue: "0"))
..maxOverallDownloadLimit = textToByte(box.get("downloader_down_limit", defaultValue: "0"))
..btTracker = trackerList,
);
}
Future<void> _listenState(Aria2c aria2c) async {
dPrint("Aria2cModel._listenState start");
while (true) {
if (_disposed || state.aria2c == null) {
dPrint("Aria2cModel._listenState end");
return;
}
try {
final aria2globalStat = await aria2c.getGlobalStat();
state = state.copyWith(aria2globalStat: aria2globalStat);
} catch (e) {
dPrint("aria2globalStat update error:$e");
}
await Future.delayed(const Duration(seconds: 1));
}
}
Future<bool> isNameInTask(String name) async {
final aria2c = state.aria2c;
if (aria2c == null) return false;
for (var value in [...await aria2c.tellActive(), ...await aria2c.tellWaiting(0, 100000)]) {
final t = HomeDownloaderUIModel.getTaskTypeAndName(value);
if (t.key == "torrent" && t.value.contains(name)) {
return true;
}
}
return false;
}
}

View File

@@ -1,289 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'aria2c.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$Aria2cModelState implements DiagnosticableTreeMixin {
String get aria2cDir; Aria2c? get aria2c; Aria2GlobalStat? get aria2globalStat;
/// Create a copy of Aria2cModelState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$Aria2cModelStateCopyWith<Aria2cModelState> get copyWith => _$Aria2cModelStateCopyWithImpl<Aria2cModelState>(this as Aria2cModelState, _$identity);
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'Aria2cModelState'))
..add(DiagnosticsProperty('aria2cDir', aria2cDir))..add(DiagnosticsProperty('aria2c', aria2c))..add(DiagnosticsProperty('aria2globalStat', aria2globalStat));
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is Aria2cModelState&&(identical(other.aria2cDir, aria2cDir) || other.aria2cDir == aria2cDir)&&(identical(other.aria2c, aria2c) || other.aria2c == aria2c)&&(identical(other.aria2globalStat, aria2globalStat) || other.aria2globalStat == aria2globalStat));
}
@override
int get hashCode => Object.hash(runtimeType,aria2cDir,aria2c,aria2globalStat);
@override
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
return 'Aria2cModelState(aria2cDir: $aria2cDir, aria2c: $aria2c, aria2globalStat: $aria2globalStat)';
}
}
/// @nodoc
abstract mixin class $Aria2cModelStateCopyWith<$Res> {
factory $Aria2cModelStateCopyWith(Aria2cModelState value, $Res Function(Aria2cModelState) _then) = _$Aria2cModelStateCopyWithImpl;
@useResult
$Res call({
String aria2cDir, Aria2c? aria2c, Aria2GlobalStat? aria2globalStat
});
}
/// @nodoc
class _$Aria2cModelStateCopyWithImpl<$Res>
implements $Aria2cModelStateCopyWith<$Res> {
_$Aria2cModelStateCopyWithImpl(this._self, this._then);
final Aria2cModelState _self;
final $Res Function(Aria2cModelState) _then;
/// Create a copy of Aria2cModelState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? aria2cDir = null,Object? aria2c = freezed,Object? aria2globalStat = freezed,}) {
return _then(_self.copyWith(
aria2cDir: null == aria2cDir ? _self.aria2cDir : aria2cDir // ignore: cast_nullable_to_non_nullable
as String,aria2c: freezed == aria2c ? _self.aria2c : aria2c // ignore: cast_nullable_to_non_nullable
as Aria2c?,aria2globalStat: freezed == aria2globalStat ? _self.aria2globalStat : aria2globalStat // ignore: cast_nullable_to_non_nullable
as Aria2GlobalStat?,
));
}
}
/// Adds pattern-matching-related methods to [Aria2cModelState].
extension Aria2cModelStatePatterns on Aria2cModelState {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _Aria2cModelState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _Aria2cModelState() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _Aria2cModelState value) $default,){
final _that = this;
switch (_that) {
case _Aria2cModelState():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _Aria2cModelState value)? $default,){
final _that = this;
switch (_that) {
case _Aria2cModelState() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String aria2cDir, Aria2c? aria2c, Aria2GlobalStat? aria2globalStat)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _Aria2cModelState() when $default != null:
return $default(_that.aria2cDir,_that.aria2c,_that.aria2globalStat);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String aria2cDir, Aria2c? aria2c, Aria2GlobalStat? aria2globalStat) $default,) {final _that = this;
switch (_that) {
case _Aria2cModelState():
return $default(_that.aria2cDir,_that.aria2c,_that.aria2globalStat);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String aria2cDir, Aria2c? aria2c, Aria2GlobalStat? aria2globalStat)? $default,) {final _that = this;
switch (_that) {
case _Aria2cModelState() when $default != null:
return $default(_that.aria2cDir,_that.aria2c,_that.aria2globalStat);case _:
return null;
}
}
}
/// @nodoc
class _Aria2cModelState with DiagnosticableTreeMixin implements Aria2cModelState {
const _Aria2cModelState({required this.aria2cDir, this.aria2c, this.aria2globalStat});
@override final String aria2cDir;
@override final Aria2c? aria2c;
@override final Aria2GlobalStat? aria2globalStat;
/// Create a copy of Aria2cModelState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$Aria2cModelStateCopyWith<_Aria2cModelState> get copyWith => __$Aria2cModelStateCopyWithImpl<_Aria2cModelState>(this, _$identity);
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'Aria2cModelState'))
..add(DiagnosticsProperty('aria2cDir', aria2cDir))..add(DiagnosticsProperty('aria2c', aria2c))..add(DiagnosticsProperty('aria2globalStat', aria2globalStat));
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Aria2cModelState&&(identical(other.aria2cDir, aria2cDir) || other.aria2cDir == aria2cDir)&&(identical(other.aria2c, aria2c) || other.aria2c == aria2c)&&(identical(other.aria2globalStat, aria2globalStat) || other.aria2globalStat == aria2globalStat));
}
@override
int get hashCode => Object.hash(runtimeType,aria2cDir,aria2c,aria2globalStat);
@override
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
return 'Aria2cModelState(aria2cDir: $aria2cDir, aria2c: $aria2c, aria2globalStat: $aria2globalStat)';
}
}
/// @nodoc
abstract mixin class _$Aria2cModelStateCopyWith<$Res> implements $Aria2cModelStateCopyWith<$Res> {
factory _$Aria2cModelStateCopyWith(_Aria2cModelState value, $Res Function(_Aria2cModelState) _then) = __$Aria2cModelStateCopyWithImpl;
@override @useResult
$Res call({
String aria2cDir, Aria2c? aria2c, Aria2GlobalStat? aria2globalStat
});
}
/// @nodoc
class __$Aria2cModelStateCopyWithImpl<$Res>
implements _$Aria2cModelStateCopyWith<$Res> {
__$Aria2cModelStateCopyWithImpl(this._self, this._then);
final _Aria2cModelState _self;
final $Res Function(_Aria2cModelState) _then;
/// Create a copy of Aria2cModelState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? aria2cDir = null,Object? aria2c = freezed,Object? aria2globalStat = freezed,}) {
return _then(_Aria2cModelState(
aria2cDir: null == aria2cDir ? _self.aria2cDir : aria2cDir // ignore: cast_nullable_to_non_nullable
as String,aria2c: freezed == aria2c ? _self.aria2c : aria2c // ignore: cast_nullable_to_non_nullable
as Aria2c?,aria2globalStat: freezed == aria2globalStat ? _self.aria2globalStat : aria2globalStat // ignore: cast_nullable_to_non_nullable
as Aria2GlobalStat?,
));
}
}
// dart format on

View File

@@ -1,63 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'aria2c.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(Aria2cModel)
const aria2cModelProvider = Aria2cModelProvider._();
final class Aria2cModelProvider
extends $NotifierProvider<Aria2cModel, Aria2cModelState> {
const Aria2cModelProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'aria2cModelProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$aria2cModelHash();
@$internal
@override
Aria2cModel create() => Aria2cModel();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(Aria2cModelState value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<Aria2cModelState>(value),
);
}
}
String _$aria2cModelHash() => r'17956c60a79c68ae13b8b8e700ebbafb70e93194';
abstract class _$Aria2cModel extends $Notifier<Aria2cModelState> {
Aria2cModelState build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<Aria2cModelState, Aria2cModelState>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<Aria2cModelState, Aria2cModelState>,
Aria2cModelState,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}

View File

@@ -0,0 +1,198 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'dart:io';
import 'package:starcitizen_doctor/common/rust/api/downloader_api.dart' as downloader_api;
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/common/utils/provider.dart';
part 'download_manager.g.dart';
part 'download_manager.freezed.dart';
@freezed
abstract class DownloadManagerState with _$DownloadManagerState {
const factory DownloadManagerState({
required String downloadDir,
@Default(false) bool isInitialized,
downloader_api.DownloadGlobalStat? globalStat,
}) = _DownloadManagerState;
}
extension DownloadManagerStateExt on DownloadManagerState {
bool get isRunning => isInitialized;
bool get hasDownloadTask => globalStat != null && (globalStat!.numActive + globalStat!.numWaiting) > BigInt.zero;
int get totalTaskNum => globalStat == null ? 0 : (globalStat!.numActive + globalStat!.numWaiting).toInt();
}
@riverpod
class DownloadManager extends _$DownloadManager {
bool _disposed = false;
@override
DownloadManagerState build() {
if (appGlobalState.applicationBinaryModuleDir == null) {
throw Exception("applicationBinaryModuleDir is null");
}
ref.onDispose(() {
_disposed = true;
});
ref.keepAlive();
final downloadDir = "${appGlobalState.applicationBinaryModuleDir}${Platform.pathSeparator}downloads";
// Lazy load init
() async {
try {
// Check if there are existing tasks
final dir = Directory(downloadDir);
if (await dir.exists()) {
dPrint("Launch download manager");
await initDownloader();
} else {
dPrint("LazyLoad download manager");
}
} catch (e) {
dPrint("DownloadManager.checkLazyLoad Error:$e");
}
}();
return DownloadManagerState(downloadDir: downloadDir);
}
Future<void> initDownloader() async {
if (state.isInitialized) return;
try {
// Create download directory if it doesn't exist
final dir = Directory(state.downloadDir);
if (!await dir.exists()) {
await dir.create(recursive: true);
}
// Initialize the Rust downloader
downloader_api.downloaderInit(downloadDir: state.downloadDir);
state = state.copyWith(isInitialized: true);
// Start listening to state updates
_listenState();
dPrint("DownloadManager initialized");
} catch (e) {
dPrint("DownloadManager.initDownloader Error: $e");
rethrow;
}
}
Future<void> _listenState() async {
dPrint("DownloadManager._listenState start");
while (true) {
if (_disposed || !state.isInitialized) {
dPrint("DownloadManager._listenState end");
return;
}
try {
final globalStat = await downloader_api.downloaderGetGlobalStats();
state = state.copyWith(globalStat: globalStat);
} catch (e) {
dPrint("globalStat update error:$e");
}
await Future.delayed(const Duration(seconds: 1));
}
}
/// Add a torrent from base64 encoded bytes
Future<int> addTorrent(List<int> torrentBytes, {String? outputFolder, List<String>? trackers}) async {
await initDownloader();
final taskId = await downloader_api.downloaderAddTorrent(
torrentBytes: torrentBytes,
outputFolder: outputFolder,
trackers: trackers,
);
return taskId.toInt();
}
/// Add a torrent from magnet link
Future<int> addMagnet(String magnetLink, {String? outputFolder, List<String>? trackers}) async {
await initDownloader();
final taskId = await downloader_api.downloaderAddMagnet(
magnetLink: magnetLink,
outputFolder: outputFolder,
trackers: trackers,
);
return taskId.toInt();
}
/// Add a torrent from URL (only .torrent file URLs are supported)
/// HTTP downloads are NOT supported - will throw an exception
Future<int> addUrl(String url, {String? outputFolder, List<String>? trackers}) async {
await initDownloader();
final taskId = await downloader_api.downloaderAddUrl(url: url, outputFolder: outputFolder, trackers: trackers);
return taskId.toInt();
}
Future<void> pauseTask(int taskId) async {
await downloader_api.downloaderPause(taskId: BigInt.from(taskId));
}
Future<void> resumeTask(int taskId) async {
await downloader_api.downloaderResume(taskId: BigInt.from(taskId));
}
Future<void> removeTask(int taskId, {bool deleteFiles = false}) async {
await downloader_api.downloaderRemove(taskId: BigInt.from(taskId), deleteFiles: deleteFiles);
}
Future<downloader_api.DownloadTaskInfo> getTaskInfo(int taskId) async {
return await downloader_api.downloaderGetTaskInfo(taskId: BigInt.from(taskId));
}
Future<List<downloader_api.DownloadTaskInfo>> getAllTasks() async {
if (!state.isInitialized) {
return [];
}
return await downloader_api.downloaderGetAllTasks();
}
Future<bool> isNameInTask(String name) async {
if (!state.isInitialized) {
return false;
}
return await downloader_api.downloaderIsNameInTask(name: name);
}
Future<void> pauseAll() async {
await downloader_api.downloaderPauseAll();
}
Future<void> resumeAll() async {
await downloader_api.downloaderResumeAll();
}
Future<void> stop() async {
await downloader_api.downloaderStop();
state = state.copyWith(isInitialized: false, globalStat: null);
}
/// Convert speed limit text to bytes per second
/// Supports formats like: "1", "100k", "10m", "0"
int textToByte(String text) {
if (text.isEmpty || text == "0") {
return 0;
}
final trimmed = text.trim().toLowerCase();
if (int.tryParse(trimmed) != null) {
return int.parse(trimmed);
}
if (trimmed.endsWith("k")) {
return int.parse(trimmed.substring(0, trimmed.length - 1)) * 1024;
}
if (trimmed.endsWith("m")) {
return int.parse(trimmed.substring(0, trimmed.length - 1)) * 1024 * 1024;
}
return 0;
}
}

View File

@@ -0,0 +1,277 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'download_manager.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$DownloadManagerState {
String get downloadDir; bool get isInitialized; downloader_api.DownloadGlobalStat? get globalStat;
/// Create a copy of DownloadManagerState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$DownloadManagerStateCopyWith<DownloadManagerState> get copyWith => _$DownloadManagerStateCopyWithImpl<DownloadManagerState>(this as DownloadManagerState, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is DownloadManagerState&&(identical(other.downloadDir, downloadDir) || other.downloadDir == downloadDir)&&(identical(other.isInitialized, isInitialized) || other.isInitialized == isInitialized)&&(identical(other.globalStat, globalStat) || other.globalStat == globalStat));
}
@override
int get hashCode => Object.hash(runtimeType,downloadDir,isInitialized,globalStat);
@override
String toString() {
return 'DownloadManagerState(downloadDir: $downloadDir, isInitialized: $isInitialized, globalStat: $globalStat)';
}
}
/// @nodoc
abstract mixin class $DownloadManagerStateCopyWith<$Res> {
factory $DownloadManagerStateCopyWith(DownloadManagerState value, $Res Function(DownloadManagerState) _then) = _$DownloadManagerStateCopyWithImpl;
@useResult
$Res call({
String downloadDir, bool isInitialized, downloader_api.DownloadGlobalStat? globalStat
});
}
/// @nodoc
class _$DownloadManagerStateCopyWithImpl<$Res>
implements $DownloadManagerStateCopyWith<$Res> {
_$DownloadManagerStateCopyWithImpl(this._self, this._then);
final DownloadManagerState _self;
final $Res Function(DownloadManagerState) _then;
/// Create a copy of DownloadManagerState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? downloadDir = null,Object? isInitialized = null,Object? globalStat = freezed,}) {
return _then(_self.copyWith(
downloadDir: null == downloadDir ? _self.downloadDir : downloadDir // ignore: cast_nullable_to_non_nullable
as String,isInitialized: null == isInitialized ? _self.isInitialized : isInitialized // ignore: cast_nullable_to_non_nullable
as bool,globalStat: freezed == globalStat ? _self.globalStat : globalStat // ignore: cast_nullable_to_non_nullable
as downloader_api.DownloadGlobalStat?,
));
}
}
/// Adds pattern-matching-related methods to [DownloadManagerState].
extension DownloadManagerStatePatterns on DownloadManagerState {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _DownloadManagerState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _DownloadManagerState() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _DownloadManagerState value) $default,){
final _that = this;
switch (_that) {
case _DownloadManagerState():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _DownloadManagerState value)? $default,){
final _that = this;
switch (_that) {
case _DownloadManagerState() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String downloadDir, bool isInitialized, downloader_api.DownloadGlobalStat? globalStat)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _DownloadManagerState() when $default != null:
return $default(_that.downloadDir,_that.isInitialized,_that.globalStat);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String downloadDir, bool isInitialized, downloader_api.DownloadGlobalStat? globalStat) $default,) {final _that = this;
switch (_that) {
case _DownloadManagerState():
return $default(_that.downloadDir,_that.isInitialized,_that.globalStat);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String downloadDir, bool isInitialized, downloader_api.DownloadGlobalStat? globalStat)? $default,) {final _that = this;
switch (_that) {
case _DownloadManagerState() when $default != null:
return $default(_that.downloadDir,_that.isInitialized,_that.globalStat);case _:
return null;
}
}
}
/// @nodoc
class _DownloadManagerState implements DownloadManagerState {
const _DownloadManagerState({required this.downloadDir, this.isInitialized = false, this.globalStat});
@override final String downloadDir;
@override@JsonKey() final bool isInitialized;
@override final downloader_api.DownloadGlobalStat? globalStat;
/// Create a copy of DownloadManagerState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$DownloadManagerStateCopyWith<_DownloadManagerState> get copyWith => __$DownloadManagerStateCopyWithImpl<_DownloadManagerState>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DownloadManagerState&&(identical(other.downloadDir, downloadDir) || other.downloadDir == downloadDir)&&(identical(other.isInitialized, isInitialized) || other.isInitialized == isInitialized)&&(identical(other.globalStat, globalStat) || other.globalStat == globalStat));
}
@override
int get hashCode => Object.hash(runtimeType,downloadDir,isInitialized,globalStat);
@override
String toString() {
return 'DownloadManagerState(downloadDir: $downloadDir, isInitialized: $isInitialized, globalStat: $globalStat)';
}
}
/// @nodoc
abstract mixin class _$DownloadManagerStateCopyWith<$Res> implements $DownloadManagerStateCopyWith<$Res> {
factory _$DownloadManagerStateCopyWith(_DownloadManagerState value, $Res Function(_DownloadManagerState) _then) = __$DownloadManagerStateCopyWithImpl;
@override @useResult
$Res call({
String downloadDir, bool isInitialized, downloader_api.DownloadGlobalStat? globalStat
});
}
/// @nodoc
class __$DownloadManagerStateCopyWithImpl<$Res>
implements _$DownloadManagerStateCopyWith<$Res> {
__$DownloadManagerStateCopyWithImpl(this._self, this._then);
final _DownloadManagerState _self;
final $Res Function(_DownloadManagerState) _then;
/// Create a copy of DownloadManagerState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? downloadDir = null,Object? isInitialized = null,Object? globalStat = freezed,}) {
return _then(_DownloadManagerState(
downloadDir: null == downloadDir ? _self.downloadDir : downloadDir // ignore: cast_nullable_to_non_nullable
as String,isInitialized: null == isInitialized ? _self.isInitialized : isInitialized // ignore: cast_nullable_to_non_nullable
as bool,globalStat: freezed == globalStat ? _self.globalStat : globalStat // ignore: cast_nullable_to_non_nullable
as downloader_api.DownloadGlobalStat?,
));
}
}
// dart format on

View File

@@ -0,0 +1,63 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'download_manager.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(DownloadManager)
const downloadManagerProvider = DownloadManagerProvider._();
final class DownloadManagerProvider
extends $NotifierProvider<DownloadManager, DownloadManagerState> {
const DownloadManagerProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'downloadManagerProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$downloadManagerHash();
@$internal
@override
DownloadManager create() => DownloadManager();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(DownloadManagerState value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<DownloadManagerState>(value),
);
}
}
String _$downloadManagerHash() => r'adc9a147522afbfcfc8a2e16310649220a75d6a3';
abstract class _$DownloadManager extends $Notifier<DownloadManagerState> {
DownloadManagerState build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<DownloadManagerState, DownloadManagerState>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<DownloadManagerState, DownloadManagerState>,
DownloadManagerState,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}