feat: downloader update

This commit is contained in:
xkeyC
2025-12-05 17:12:40 +08:00
parent 4315e36cbe
commit 289691896d
23 changed files with 1001 additions and 118 deletions

View File

@@ -13,6 +13,7 @@ part 'download_manager.freezed.dart';
@freezed
abstract class DownloadManagerState with _$DownloadManagerState {
const factory DownloadManagerState({
required String workingDir,
required String downloadDir,
@Default(false) bool isInitialized,
downloader_api.DownloadGlobalStat? globalStat,
@@ -36,18 +37,24 @@ class DownloadManager extends _$DownloadManager {
if (appGlobalState.applicationBinaryModuleDir == null) {
throw Exception("applicationBinaryModuleDir is null");
}
if (appGlobalState.applicationSupportDir == null) {
throw Exception("applicationSupportDir is null");
}
ref.onDispose(() {
_disposed = true;
});
ref.keepAlive();
// Working directory for session data (in appSupport)
final workingDir = "${appGlobalState.applicationSupportDir}${Platform.pathSeparator}downloader";
// Default download directory (can be customized)
final downloadDir = "${appGlobalState.applicationBinaryModuleDir}${Platform.pathSeparator}downloads";
// Lazy load init
() async {
try {
// Check if there are existing tasks
final dir = Directory(downloadDir);
// Check if there are existing tasks (check working dir for session data)
final dir = Directory(workingDir);
if (await dir.exists()) {
dPrint("Launch download manager");
await initDownloader();
@@ -59,21 +66,32 @@ class DownloadManager extends _$DownloadManager {
}
}();
return DownloadManagerState(downloadDir: downloadDir);
return DownloadManagerState(workingDir: workingDir, downloadDir: downloadDir);
}
Future<void> initDownloader() async {
Future<void> initDownloader({int? uploadLimitBps, int? downloadLimitBps}) 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);
// Create working directory if it doesn't exist
final workingDir = Directory(state.workingDir);
if (!await workingDir.exists()) {
await workingDir.create(recursive: true);
}
// Initialize the Rust downloader
downloader_api.downloaderInit(downloadDir: state.downloadDir);
// Create download directory if it doesn't exist
final downloadDir = Directory(state.downloadDir);
if (!await downloadDir.exists()) {
await downloadDir.create(recursive: true);
}
// Initialize the Rust downloader with optional speed limits
downloader_api.downloaderInit(
workingDir: state.workingDir,
defaultDownloadDir: state.downloadDir,
uploadLimitBps: uploadLimitBps,
downloadLimitBps: downloadLimitBps,
);
state = state.copyWith(isInitialized: true);
@@ -97,6 +115,9 @@ class DownloadManager extends _$DownloadManager {
try {
final globalStat = await downloader_api.downloaderGetGlobalStats();
state = state.copyWith(globalStat: globalStat);
// Auto-remove completed tasks (no seeding behavior)
await removeCompletedTasks();
} catch (e) {
dPrint("globalStat update error:$e");
}
@@ -177,6 +198,18 @@ class DownloadManager extends _$DownloadManager {
state = state.copyWith(isInitialized: false, globalStat: null);
}
/// Shutdown the downloader completely (allows restart with new settings)
Future<void> shutdown() async {
await downloader_api.downloaderShutdown();
state = state.copyWith(isInitialized: false, globalStat: null);
}
/// Restart the downloader with new speed limit settings
Future<void> restart({int? uploadLimitBps, int? downloadLimitBps}) async {
await shutdown();
await initDownloader(uploadLimitBps: uploadLimitBps, downloadLimitBps: downloadLimitBps);
}
/// Convert speed limit text to bytes per second
/// Supports formats like: "1", "100k", "10m", "0"
int textToByte(String text) {
@@ -195,4 +228,22 @@ class DownloadManager extends _$DownloadManager {
}
return 0;
}
/// Remove all completed tasks (equivalent to aria2's --seed-time=0 behavior)
/// Returns the number of tasks removed
Future<int> removeCompletedTasks() async {
if (!state.isInitialized) {
return 0;
}
final removed = await downloader_api.downloaderRemoveCompletedTasks();
return removed;
}
/// Check if there are any active (non-completed) download tasks
Future<bool> hasActiveTasks() async {
if (!state.isInitialized) {
return false;
}
return await downloader_api.downloaderHasActiveTasks();
}
}

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$DownloadManagerState {
String get downloadDir; bool get isInitialized; downloader_api.DownloadGlobalStat? get globalStat;
String get workingDir; 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)
@@ -25,16 +25,16 @@ $DownloadManagerStateCopyWith<DownloadManagerState> get copyWith => _$DownloadMa
@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));
return identical(this, other) || (other.runtimeType == runtimeType&&other is DownloadManagerState&&(identical(other.workingDir, workingDir) || other.workingDir == workingDir)&&(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);
int get hashCode => Object.hash(runtimeType,workingDir,downloadDir,isInitialized,globalStat);
@override
String toString() {
return 'DownloadManagerState(downloadDir: $downloadDir, isInitialized: $isInitialized, globalStat: $globalStat)';
return 'DownloadManagerState(workingDir: $workingDir, downloadDir: $downloadDir, isInitialized: $isInitialized, globalStat: $globalStat)';
}
@@ -45,7 +45,7 @@ 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
String workingDir, String downloadDir, bool isInitialized, downloader_api.DownloadGlobalStat? globalStat
});
@@ -62,9 +62,10 @@ class _$DownloadManagerStateCopyWithImpl<$Res>
/// 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,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? workingDir = null,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
workingDir: null == workingDir ? _self.workingDir : workingDir // ignore: cast_nullable_to_non_nullable
as String,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?,
@@ -152,10 +153,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String downloadDir, bool isInitialized, downloader_api.DownloadGlobalStat? globalStat)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String workingDir, 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 $default(_that.workingDir,_that.downloadDir,_that.isInitialized,_that.globalStat);case _:
return orElse();
}
@@ -173,10 +174,10 @@ return $default(_that.downloadDir,_that.isInitialized,_that.globalStat);case _:
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String downloadDir, bool isInitialized, downloader_api.DownloadGlobalStat? globalStat) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String workingDir, 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 _:
return $default(_that.workingDir,_that.downloadDir,_that.isInitialized,_that.globalStat);case _:
throw StateError('Unexpected subclass');
}
@@ -193,10 +194,10 @@ return $default(_that.downloadDir,_that.isInitialized,_that.globalStat);case _:
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String downloadDir, bool isInitialized, downloader_api.DownloadGlobalStat? globalStat)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String workingDir, 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 $default(_that.workingDir,_that.downloadDir,_that.isInitialized,_that.globalStat);case _:
return null;
}
@@ -208,9 +209,10 @@ return $default(_that.downloadDir,_that.isInitialized,_that.globalStat);case _:
class _DownloadManagerState implements DownloadManagerState {
const _DownloadManagerState({required this.downloadDir, this.isInitialized = false, this.globalStat});
const _DownloadManagerState({required this.workingDir, required this.downloadDir, this.isInitialized = false, this.globalStat});
@override final String workingDir;
@override final String downloadDir;
@override@JsonKey() final bool isInitialized;
@override final downloader_api.DownloadGlobalStat? globalStat;
@@ -225,16 +227,16 @@ _$DownloadManagerStateCopyWith<_DownloadManagerState> get copyWith => __$Downloa
@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));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DownloadManagerState&&(identical(other.workingDir, workingDir) || other.workingDir == workingDir)&&(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);
int get hashCode => Object.hash(runtimeType,workingDir,downloadDir,isInitialized,globalStat);
@override
String toString() {
return 'DownloadManagerState(downloadDir: $downloadDir, isInitialized: $isInitialized, globalStat: $globalStat)';
return 'DownloadManagerState(workingDir: $workingDir, downloadDir: $downloadDir, isInitialized: $isInitialized, globalStat: $globalStat)';
}
@@ -245,7 +247,7 @@ abstract mixin class _$DownloadManagerStateCopyWith<$Res> implements $DownloadMa
factory _$DownloadManagerStateCopyWith(_DownloadManagerState value, $Res Function(_DownloadManagerState) _then) = __$DownloadManagerStateCopyWithImpl;
@override @useResult
$Res call({
String downloadDir, bool isInitialized, downloader_api.DownloadGlobalStat? globalStat
String workingDir, String downloadDir, bool isInitialized, downloader_api.DownloadGlobalStat? globalStat
});
@@ -262,9 +264,10 @@ class __$DownloadManagerStateCopyWithImpl<$Res>
/// 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,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? workingDir = null,Object? downloadDir = null,Object? isInitialized = null,Object? globalStat = freezed,}) {
return _then(_DownloadManagerState(
downloadDir: null == downloadDir ? _self.downloadDir : downloadDir // ignore: cast_nullable_to_non_nullable
workingDir: null == workingDir ? _self.workingDir : workingDir // ignore: cast_nullable_to_non_nullable
as String,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?,

View File

@@ -41,7 +41,7 @@ final class DownloadManagerProvider
}
}
String _$downloadManagerHash() => r'adc9a147522afbfcfc8a2e16310649220a75d6a3';
String _$downloadManagerHash() => r'f0fd818851be0d1c9e6774803aae465c33843cde';
abstract class _$DownloadManager extends $Notifier<DownloadManagerState> {
DownloadManagerState build();