diff --git a/lib/common/rust/api/downloader_api.dart b/lib/common/rust/api/downloader_api.dart index b7c0d65..87a346e 100644 --- a/lib/common/rust/api/downloader_api.dart +++ b/lib/common/rust/api/downloader_api.dart @@ -6,12 +6,27 @@ import '../frb_generated.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; -// These functions are ignored because they are not marked as `pub`: `get_task_status` -// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `clone`, `clone`, `fmt`, `fmt`, `fmt` +// These functions are ignored because they are not marked as `pub`: `get_session`, `get_task_status` +// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `clone`, `clone`, `eq`, `fmt`, `fmt`, `fmt` -/// Initialize the download manager session -void downloaderInit({required String downloadDir}) => RustLib.instance.api - .crateApiDownloaderApiDownloaderInit(downloadDir: downloadDir); +/// Initialize the download manager session with persistence enabled +/// +/// Parameters: +/// - working_dir: The directory to store session data (persistence, DHT, etc.) +/// - default_download_dir: The default directory to store downloads +/// - upload_limit_bps: Upload speed limit in bytes per second (0 = unlimited) +/// - download_limit_bps: Download speed limit in bytes per second (0 = unlimited) +void downloaderInit({ + required String workingDir, + required String defaultDownloadDir, + int? uploadLimitBps, + int? downloadLimitBps, +}) => RustLib.instance.api.crateApiDownloaderApiDownloaderInit( + workingDir: workingDir, + defaultDownloadDir: defaultDownloadDir, + uploadLimitBps: uploadLimitBps, + downloadLimitBps: downloadLimitBps, +); /// Check if the downloader is initialized bool downloaderIsInitialized() => @@ -95,10 +110,34 @@ Future downloaderPauseAll() => Future downloaderResumeAll() => RustLib.instance.api.crateApiDownloaderApiDownloaderResumeAll(); -/// Stop the downloader session +/// Stop the downloader session (pauses all tasks but keeps session) Future downloaderStop() => RustLib.instance.api.crateApiDownloaderApiDownloaderStop(); +/// Shutdown the downloader session completely (allows restart with new settings) +Future downloaderShutdown() => + RustLib.instance.api.crateApiDownloaderApiDownloaderShutdown(); + +/// Update global speed limits +/// Note: rqbit Session doesn't support runtime limit changes, +/// this function is a placeholder that returns an error. +/// Speed limits should be set during downloader_init. +Future downloaderUpdateSpeedLimits({ + int? uploadLimitBps, + int? downloadLimitBps, +}) => RustLib.instance.api.crateApiDownloaderApiDownloaderUpdateSpeedLimits( + uploadLimitBps: uploadLimitBps, + downloadLimitBps: downloadLimitBps, +); + +/// Remove all completed tasks (equivalent to aria2's --seed-time=0 behavior) +Future downloaderRemoveCompletedTasks() => + RustLib.instance.api.crateApiDownloaderApiDownloaderRemoveCompletedTasks(); + +/// Check if there are any active (non-completed) tasks +Future downloaderHasActiveTasks() => + RustLib.instance.api.crateApiDownloaderApiDownloaderHasActiveTasks(); + /// Global statistics class DownloadGlobalStat { final BigInt downloadSpeed; diff --git a/lib/common/rust/frb_generated.dart b/lib/common/rust/frb_generated.dart index 8dcddb1..69295d4 100644 --- a/lib/common/rust/frb_generated.dart +++ b/lib/common/rust/frb_generated.dart @@ -72,7 +72,7 @@ class RustLib extends BaseEntrypoint { String get codegenVersion => '2.11.1'; @override - int get rustContentHash => -1465039096; + int get rustContentHash => -641930410; static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( @@ -126,7 +126,14 @@ abstract class RustLibApi extends BaseApi { required BigInt taskId, }); - void crateApiDownloaderApiDownloaderInit({required String downloadDir}); + Future crateApiDownloaderApiDownloaderHasActiveTasks(); + + void crateApiDownloaderApiDownloaderInit({ + required String workingDir, + required String defaultDownloadDir, + int? uploadLimitBps, + int? downloadLimitBps, + }); bool crateApiDownloaderApiDownloaderIsInitialized(); @@ -143,12 +150,21 @@ abstract class RustLibApi extends BaseApi { required bool deleteFiles, }); + Future crateApiDownloaderApiDownloaderRemoveCompletedTasks(); + Future crateApiDownloaderApiDownloaderResume({required BigInt taskId}); Future crateApiDownloaderApiDownloaderResumeAll(); + Future crateApiDownloaderApiDownloaderShutdown(); + Future crateApiDownloaderApiDownloaderStop(); + Future crateApiDownloaderApiDownloaderUpdateSpeedLimits({ + int? uploadLimitBps, + int? downloadLimitBps, + }); + Future crateApiHttpApiFetch({ required MyMethod method, required String url, @@ -700,19 +716,64 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); @override - void crateApiDownloaderApiDownloaderInit({required String downloadDir}) { + Future crateApiDownloaderApiDownloaderHasActiveTasks() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + return wire + .wire__crate__api__downloader_api__downloader_has_active_tasks( + port_, + ); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_bool, + decodeErrorData: null, + ), + constMeta: kCrateApiDownloaderApiDownloaderHasActiveTasksConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiDownloaderApiDownloaderHasActiveTasksConstMeta => + const TaskConstMeta( + debugName: "downloader_has_active_tasks", + argNames: [], + ); + + @override + void crateApiDownloaderApiDownloaderInit({ + required String workingDir, + required String defaultDownloadDir, + int? uploadLimitBps, + int? downloadLimitBps, + }) { return handler.executeSync( SyncTask( callFfi: () { - var arg0 = cst_encode_String(downloadDir); - return wire.wire__crate__api__downloader_api__downloader_init(arg0); + var arg0 = cst_encode_String(workingDir); + var arg1 = cst_encode_String(defaultDownloadDir); + var arg2 = cst_encode_opt_box_autoadd_u_32(uploadLimitBps); + var arg3 = cst_encode_opt_box_autoadd_u_32(downloadLimitBps); + return wire.wire__crate__api__downloader_api__downloader_init( + arg0, + arg1, + arg2, + arg3, + ); }, codec: DcoCodec( decodeSuccessData: dco_decode_unit, decodeErrorData: dco_decode_AnyhowException, ), constMeta: kCrateApiDownloaderApiDownloaderInitConstMeta, - argValues: [downloadDir], + argValues: [ + workingDir, + defaultDownloadDir, + uploadLimitBps, + downloadLimitBps, + ], apiImpl: this, ), ); @@ -721,7 +782,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateApiDownloaderApiDownloaderInitConstMeta => const TaskConstMeta( debugName: "downloader_init", - argNames: ["downloadDir"], + argNames: [ + "workingDir", + "defaultDownloadDir", + "uploadLimitBps", + "downloadLimitBps", + ], ); @override @@ -858,6 +924,35 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: ["taskId", "deleteFiles"], ); + @override + Future crateApiDownloaderApiDownloaderRemoveCompletedTasks() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + return wire + .wire__crate__api__downloader_api__downloader_remove_completed_tasks( + port_, + ); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_u_32, + decodeErrorData: dco_decode_AnyhowException, + ), + constMeta: + kCrateApiDownloaderApiDownloaderRemoveCompletedTasksConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateApiDownloaderApiDownloaderRemoveCompletedTasksConstMeta => + const TaskConstMeta( + debugName: "downloader_remove_completed_tasks", + argNames: [], + ); + @override Future crateApiDownloaderApiDownloaderResume({required BigInt taskId}) { return handler.executeNormal( @@ -906,6 +1001,29 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateApiDownloaderApiDownloaderResumeAllConstMeta => const TaskConstMeta(debugName: "downloader_resume_all", argNames: []); + @override + Future crateApiDownloaderApiDownloaderShutdown() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + return wire.wire__crate__api__downloader_api__downloader_shutdown( + port_, + ); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_unit, + decodeErrorData: dco_decode_AnyhowException, + ), + constMeta: kCrateApiDownloaderApiDownloaderShutdownConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiDownloaderApiDownloaderShutdownConstMeta => + const TaskConstMeta(debugName: "downloader_shutdown", argNames: []); + @override Future crateApiDownloaderApiDownloaderStop() { return handler.executeNormal( @@ -927,6 +1045,41 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateApiDownloaderApiDownloaderStopConstMeta => const TaskConstMeta(debugName: "downloader_stop", argNames: []); + @override + Future crateApiDownloaderApiDownloaderUpdateSpeedLimits({ + int? uploadLimitBps, + int? downloadLimitBps, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + var arg0 = cst_encode_opt_box_autoadd_u_32(uploadLimitBps); + var arg1 = cst_encode_opt_box_autoadd_u_32(downloadLimitBps); + return wire + .wire__crate__api__downloader_api__downloader_update_speed_limits( + port_, + arg0, + arg1, + ); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_unit, + decodeErrorData: dco_decode_AnyhowException, + ), + constMeta: kCrateApiDownloaderApiDownloaderUpdateSpeedLimitsConstMeta, + argValues: [uploadLimitBps, downloadLimitBps], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateApiDownloaderApiDownloaderUpdateSpeedLimitsConstMeta => + const TaskConstMeta( + debugName: "downloader_update_speed_limits", + argNames: ["uploadLimitBps", "downloadLimitBps"], + ); + @override Future crateApiHttpApiFetch({ required MyMethod method, @@ -2386,6 +2539,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return dco_decode_rsi_launcher_asar_data(raw); } + @protected + int dco_decode_box_autoadd_u_32(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as int; + } + @protected BigInt dco_decode_box_autoadd_u_64(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -2537,6 +2696,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return raw == null ? null : dco_decode_box_autoadd_bool(raw); } + @protected + int? dco_decode_opt_box_autoadd_u_32(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null ? null : dco_decode_box_autoadd_u_32(raw); + } + @protected BigInt? dco_decode_opt_box_autoadd_u_64(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -2797,6 +2962,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return (sse_decode_rsi_launcher_asar_data(deserializer)); } + @protected + int sse_decode_box_autoadd_u_32(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_u_32(deserializer)); + } + @protected BigInt sse_decode_box_autoadd_u_64(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -3027,6 +3198,17 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + int? sse_decode_opt_box_autoadd_u_32(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_box_autoadd_u_32(deserializer)); + } else { + return null; + } + } + @protected BigInt? sse_decode_opt_box_autoadd_u_64(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -3407,6 +3589,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_rsi_launcher_asar_data(self, serializer); } + @protected + void sse_encode_box_autoadd_u_32(int self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_u_32(self, serializer); + } + @protected void sse_encode_box_autoadd_u_64(BigInt self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -3619,6 +3807,16 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + void sse_encode_opt_box_autoadd_u_32(int? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_box_autoadd_u_32(self, serializer); + } + } + @protected void sse_encode_opt_box_autoadd_u_64(BigInt? self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs diff --git a/lib/common/rust/frb_generated.io.dart b/lib/common/rust/frb_generated.io.dart index 0fac834..5ee8b65 100644 --- a/lib/common/rust/frb_generated.io.dart +++ b/lib/common/rust/frb_generated.io.dart @@ -50,6 +50,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { dynamic raw, ); + @protected + int dco_decode_box_autoadd_u_32(dynamic raw); + @protected BigInt dco_decode_box_autoadd_u_64(dynamic raw); @@ -115,6 +118,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected bool? dco_decode_opt_box_autoadd_bool(dynamic raw); + @protected + int? dco_decode_opt_box_autoadd_u_32(dynamic raw); + @protected BigInt? dco_decode_opt_box_autoadd_u_64(dynamic raw); @@ -203,6 +209,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + int sse_decode_box_autoadd_u_32(SseDeserializer deserializer); + @protected BigInt sse_decode_box_autoadd_u_64(SseDeserializer deserializer); @@ -282,6 +291,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected bool? sse_decode_opt_box_autoadd_bool(SseDeserializer deserializer); + @protected + int? sse_decode_opt_box_autoadd_u_32(SseDeserializer deserializer); + @protected BigInt? sse_decode_opt_box_autoadd_u_64(SseDeserializer deserializer); @@ -408,6 +420,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { return ptr; } + @protected + ffi.Pointer cst_encode_box_autoadd_u_32(int raw) { + // Codec=Cst (C-struct based), see doc to use other codecs + return wire.cst_new_box_autoadd_u_32(cst_encode_u_32(raw)); + } + @protected ffi.Pointer cst_encode_box_autoadd_u_64(BigInt raw) { // Codec=Cst (C-struct based), see doc to use other codecs @@ -538,6 +556,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { return raw == null ? ffi.nullptr : cst_encode_box_autoadd_bool(raw); } + @protected + ffi.Pointer cst_encode_opt_box_autoadd_u_32(int? raw) { + // Codec=Cst (C-struct based), see doc to use other codecs + return raw == null ? ffi.nullptr : cst_encode_box_autoadd_u_32(raw); + } + @protected ffi.Pointer cst_encode_opt_box_autoadd_u_64(BigInt? raw) { // Codec=Cst (C-struct based), see doc to use other codecs @@ -829,6 +853,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_box_autoadd_u_32(int self, SseSerializer serializer); + @protected void sse_encode_box_autoadd_u_64(BigInt self, SseSerializer serializer); @@ -925,6 +952,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_opt_box_autoadd_bool(bool? self, SseSerializer serializer); + @protected + void sse_encode_opt_box_autoadd_u_32(int? self, SseSerializer serializer); + @protected void sse_encode_opt_box_autoadd_u_64(BigInt? self, SseSerializer serializer); @@ -1337,10 +1367,34 @@ class RustLibWire implements BaseWire { _wire__crate__api__downloader_api__downloader_get_task_infoPtr .asFunction(); - WireSyncRust2DartDco wire__crate__api__downloader_api__downloader_init( - ffi.Pointer download_dir, + void wire__crate__api__downloader_api__downloader_has_active_tasks( + int port_, ) { - return _wire__crate__api__downloader_api__downloader_init(download_dir); + return _wire__crate__api__downloader_api__downloader_has_active_tasks( + port_, + ); + } + + late final _wire__crate__api__downloader_api__downloader_has_active_tasksPtr = + _lookup>( + 'frbgen_starcitizen_doctor_wire__crate__api__downloader_api__downloader_has_active_tasks', + ); + late final _wire__crate__api__downloader_api__downloader_has_active_tasks = + _wire__crate__api__downloader_api__downloader_has_active_tasksPtr + .asFunction(); + + WireSyncRust2DartDco wire__crate__api__downloader_api__downloader_init( + ffi.Pointer working_dir, + ffi.Pointer default_download_dir, + ffi.Pointer upload_limit_bps, + ffi.Pointer download_limit_bps, + ) { + return _wire__crate__api__downloader_api__downloader_init( + working_dir, + default_download_dir, + upload_limit_bps, + download_limit_bps, + ); } late final _wire__crate__api__downloader_api__downloader_initPtr = @@ -1348,6 +1402,9 @@ class RustLibWire implements BaseWire { ffi.NativeFunction< WireSyncRust2DartDco Function( ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, ) > >( @@ -1358,6 +1415,9 @@ class RustLibWire implements BaseWire { .asFunction< WireSyncRust2DartDco Function( ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, ) >(); @@ -1450,6 +1510,22 @@ class RustLibWire implements BaseWire { _wire__crate__api__downloader_api__downloader_removePtr .asFunction(); + void wire__crate__api__downloader_api__downloader_remove_completed_tasks( + int port_, + ) { + return _wire__crate__api__downloader_api__downloader_remove_completed_tasks( + port_, + ); + } + + late final _wire__crate__api__downloader_api__downloader_remove_completed_tasksPtr = + _lookup>( + 'frbgen_starcitizen_doctor_wire__crate__api__downloader_api__downloader_remove_completed_tasks', + ); + late final _wire__crate__api__downloader_api__downloader_remove_completed_tasks = + _wire__crate__api__downloader_api__downloader_remove_completed_tasksPtr + .asFunction(); + void wire__crate__api__downloader_api__downloader_resume( int port_, int task_id, @@ -1477,6 +1553,18 @@ class RustLibWire implements BaseWire { _wire__crate__api__downloader_api__downloader_resume_allPtr .asFunction(); + void wire__crate__api__downloader_api__downloader_shutdown(int port_) { + return _wire__crate__api__downloader_api__downloader_shutdown(port_); + } + + late final _wire__crate__api__downloader_api__downloader_shutdownPtr = + _lookup>( + 'frbgen_starcitizen_doctor_wire__crate__api__downloader_api__downloader_shutdown', + ); + late final _wire__crate__api__downloader_api__downloader_shutdown = + _wire__crate__api__downloader_api__downloader_shutdownPtr + .asFunction(); + void wire__crate__api__downloader_api__downloader_stop(int port_) { return _wire__crate__api__downloader_api__downloader_stop(port_); } @@ -1489,6 +1577,36 @@ class RustLibWire implements BaseWire { _wire__crate__api__downloader_api__downloader_stopPtr .asFunction(); + void wire__crate__api__downloader_api__downloader_update_speed_limits( + int port_, + ffi.Pointer _upload_limit_bps, + ffi.Pointer _download_limit_bps, + ) { + return _wire__crate__api__downloader_api__downloader_update_speed_limits( + port_, + _upload_limit_bps, + _download_limit_bps, + ); + } + + late final _wire__crate__api__downloader_api__downloader_update_speed_limitsPtr = + _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Int64, + ffi.Pointer, + ffi.Pointer, + ) + > + >( + 'frbgen_starcitizen_doctor_wire__crate__api__downloader_api__downloader_update_speed_limits', + ); + late final _wire__crate__api__downloader_api__downloader_update_speed_limits = + _wire__crate__api__downloader_api__downloader_update_speed_limitsPtr + .asFunction< + void Function(int, ffi.Pointer, ffi.Pointer) + >(); + void wire__crate__api__http_api__fetch( int port_, int method, @@ -2755,6 +2873,17 @@ class RustLibWire implements BaseWire { ffi.Pointer Function() >(); + ffi.Pointer cst_new_box_autoadd_u_32(int value) { + return _cst_new_box_autoadd_u_32(value); + } + + late final _cst_new_box_autoadd_u_32Ptr = + _lookup Function(ffi.Uint32)>>( + 'frbgen_starcitizen_doctor_cst_new_box_autoadd_u_32', + ); + late final _cst_new_box_autoadd_u_32 = _cst_new_box_autoadd_u_32Ptr + .asFunction Function(int)>(); + ffi.Pointer cst_new_box_autoadd_u_64(int value) { return _cst_new_box_autoadd_u_64(value); } diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 731f918..4a7f04b 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -614,6 +614,12 @@ class MessageLookup extends MessageLookupByLibrary { "downloader_action_pause_download": MessageLookupByLibrary.simpleMessage( "Pause Download", ), + "downloader_action_restart_later": MessageLookupByLibrary.simpleMessage( + "Apply Later", + ), + "downloader_action_restart_now": MessageLookupByLibrary.simpleMessage( + "Restart Now", + ), "downloader_action_resume_all": MessageLookupByLibrary.simpleMessage( "Resume All", ), @@ -645,6 +651,14 @@ class MessageLookup extends MessageLookupByLibrary { "SCToolbox uses p2p network to accelerate file downloads. If you have limited bandwidth, you can set the upload bandwidth to 1(byte) here.", ), "downloader_info_paused": MessageLookupByLibrary.simpleMessage("Paused"), + "downloader_info_restart_manager_to_apply": + MessageLookupByLibrary.simpleMessage( + "Speed limit settings saved. Restart the download manager now to apply new settings?", + ), + "downloader_info_speed_limit_saved_restart_required": + MessageLookupByLibrary.simpleMessage( + "Speed limit settings saved. Will apply on next downloader start.", + ), "downloader_info_status": m22, "downloader_info_total_size": m23, "downloader_info_uploaded": m24, diff --git a/lib/generated/intl/messages_ja.dart b/lib/generated/intl/messages_ja.dart index 09d1fc8..a19842b 100644 --- a/lib/generated/intl/messages_ja.dart +++ b/lib/generated/intl/messages_ja.dart @@ -551,6 +551,12 @@ class MessageLookup extends MessageLookupByLibrary { "downloader_action_pause_download": MessageLookupByLibrary.simpleMessage( "ダウンロードを一時停止", ), + "downloader_action_restart_later": MessageLookupByLibrary.simpleMessage( + "後で適用", + ), + "downloader_action_restart_now": MessageLookupByLibrary.simpleMessage( + "今すぐ再起動", + ), "downloader_action_resume_all": MessageLookupByLibrary.simpleMessage( "すべて再開", ), @@ -582,6 +588,14 @@ class MessageLookup extends MessageLookupByLibrary { "SCToolboxはファイルダウンロードを高速化するためにP2Pネットワークを使用しています。通信量に制限がある場合は、アップロード帯域幅を1(byte)に設定できます。", ), "downloader_info_paused": MessageLookupByLibrary.simpleMessage("一時停止中"), + "downloader_info_restart_manager_to_apply": + MessageLookupByLibrary.simpleMessage( + "速度制限設定が保存されました。新しい設定を適用するためにダウンロードマネージャーを今すぐ再起動しますか?", + ), + "downloader_info_speed_limit_saved_restart_required": + MessageLookupByLibrary.simpleMessage( + "速度制限設定が保存されました。次回のダウンローダー起動時に適用されます。", + ), "downloader_info_status": m22, "downloader_info_total_size": m23, "downloader_info_uploaded": m24, diff --git a/lib/generated/intl/messages_ru.dart b/lib/generated/intl/messages_ru.dart index 06f62fb..e0a076c 100644 --- a/lib/generated/intl/messages_ru.dart +++ b/lib/generated/intl/messages_ru.dart @@ -591,6 +591,12 @@ class MessageLookup extends MessageLookupByLibrary { "downloader_action_pause_download": MessageLookupByLibrary.simpleMessage( "Приостановить загрузку", ), + "downloader_action_restart_later": MessageLookupByLibrary.simpleMessage( + "Применить позже", + ), + "downloader_action_restart_now": MessageLookupByLibrary.simpleMessage( + "Перезапустить сейчас", + ), "downloader_action_resume_all": MessageLookupByLibrary.simpleMessage( "Возобновить все", ), @@ -624,6 +630,14 @@ class MessageLookup extends MessageLookupByLibrary { "downloader_info_paused": MessageLookupByLibrary.simpleMessage( "Приостановлено", ), + "downloader_info_restart_manager_to_apply": + MessageLookupByLibrary.simpleMessage( + "Настройки ограничения скорости сохранены. Перезапустить менеджер загрузок сейчас для применения новых настроек?", + ), + "downloader_info_speed_limit_saved_restart_required": + MessageLookupByLibrary.simpleMessage( + "Настройки ограничения скорости сохранены. Будут применены при следующем запуске загрузчика.", + ), "downloader_info_status": m22, "downloader_info_total_size": m23, "downloader_info_uploaded": m24, diff --git a/lib/generated/intl/messages_zh_CN.dart b/lib/generated/intl/messages_zh_CN.dart index 7bd248c..2ff7d38 100644 --- a/lib/generated/intl/messages_zh_CN.dart +++ b/lib/generated/intl/messages_zh_CN.dart @@ -538,6 +538,12 @@ class MessageLookup extends MessageLookupByLibrary { "downloader_action_pause_download": MessageLookupByLibrary.simpleMessage( "暂停下载", ), + "downloader_action_restart_later": MessageLookupByLibrary.simpleMessage( + "下次启动时生效", + ), + "downloader_action_restart_now": MessageLookupByLibrary.simpleMessage( + "立即重启下载管理器", + ), "downloader_action_resume_all": MessageLookupByLibrary.simpleMessage( "恢复全部", ), @@ -565,6 +571,10 @@ class MessageLookup extends MessageLookupByLibrary { "SC 汉化盒子使用 p2p 网络来加速文件下载,如果您流量有限,可在此处将上传带宽设置为 1(byte)。", ), "downloader_info_paused": MessageLookupByLibrary.simpleMessage("已暂停"), + "downloader_info_restart_manager_to_apply": + MessageLookupByLibrary.simpleMessage("限速设置已保存。是否立即重启下载管理器以应用新设置?"), + "downloader_info_speed_limit_saved_restart_required": + MessageLookupByLibrary.simpleMessage("限速设置已保存,将在下次启动下载器时生效。"), "downloader_info_status": m22, "downloader_info_total_size": m23, "downloader_info_uploaded": m24, diff --git a/lib/generated/intl/messages_zh_TW.dart b/lib/generated/intl/messages_zh_TW.dart index f8ab50d..f168096 100644 --- a/lib/generated/intl/messages_zh_TW.dart +++ b/lib/generated/intl/messages_zh_TW.dart @@ -522,6 +522,12 @@ class MessageLookup extends MessageLookupByLibrary { "downloader_action_pause_download": MessageLookupByLibrary.simpleMessage( "暫停下載", ), + "downloader_action_restart_later": MessageLookupByLibrary.simpleMessage( + "稍後套用", + ), + "downloader_action_restart_now": MessageLookupByLibrary.simpleMessage( + "立即重新啟動", + ), "downloader_action_resume_all": MessageLookupByLibrary.simpleMessage( "全部繼續", ), @@ -549,6 +555,10 @@ class MessageLookup extends MessageLookupByLibrary { "SC 工具箱使用 p2p 網路來加速文件下載,如果您流量有限,可在此處將上傳頻寬設定為 1(byte)。", ), "downloader_info_paused": MessageLookupByLibrary.simpleMessage("已暫停"), + "downloader_info_restart_manager_to_apply": + MessageLookupByLibrary.simpleMessage("限速設定已儲存。是否立即重新啟動下載管理器以套用新設定?"), + "downloader_info_speed_limit_saved_restart_required": + MessageLookupByLibrary.simpleMessage("限速設定已儲存,將在下次啟動下載器時生效。"), "downloader_info_status": m22, "downloader_info_total_size": m23, "downloader_info_uploaded": m24, diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 9b6fe88..2ec562b 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -28,10 +28,9 @@ class S { static const AppLocalizationDelegate delegate = AppLocalizationDelegate(); static Future load(Locale locale) { - final name = - (locale.countryCode?.isEmpty ?? false) - ? locale.languageCode - : locale.toString(); + final name = (locale.countryCode?.isEmpty ?? false) + ? locale.languageCode + : locale.toString(); final localeName = Intl.canonicalizedLocale(name); return initializeMessages(localeName).then((_) { Intl.defaultLocale = localeName; @@ -715,6 +714,46 @@ class S { ); } + /// `Speed limit settings saved. Will apply on next downloader start.` + String get downloader_info_speed_limit_saved_restart_required { + return Intl.message( + 'Speed limit settings saved. Will apply on next downloader start.', + name: 'downloader_info_speed_limit_saved_restart_required', + desc: '', + args: [], + ); + } + + /// `Restart Now` + String get downloader_action_restart_now { + return Intl.message( + 'Restart Now', + name: 'downloader_action_restart_now', + desc: '', + args: [], + ); + } + + /// `Apply Later` + String get downloader_action_restart_later { + return Intl.message( + 'Apply Later', + name: 'downloader_action_restart_later', + desc: '', + args: [], + ); + } + + /// `Speed limit settings saved. Restart the download manager now to apply new settings?` + String get downloader_info_restart_manager_to_apply { + return Intl.message( + 'Speed limit settings saved. Restart the download manager now to apply new settings?', + name: 'downloader_info_restart_manager_to_apply', + desc: '', + args: [], + ); + } + /// `One-Click Diagnosis -> {v0}` String doctor_title_one_click_diagnosis(Object v0) { return Intl.message( diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 5b6b1eb..d3a6987 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -135,6 +135,14 @@ "@downloader_input_download_speed_limit": {}, "downloader_input_info_p2p_upload_note": "* P2P upload only occurs when downloading files and will close p2p connections after download completion. If you want to participate in seeding, please contact us through the About page.", "@downloader_input_info_p2p_upload_note": {}, + "downloader_info_speed_limit_saved_restart_required": "Speed limit settings saved. Will apply on next downloader start.", + "@downloader_info_speed_limit_saved_restart_required": {}, + "downloader_action_restart_now": "Restart Now", + "@downloader_action_restart_now": {}, + "downloader_action_restart_later": "Apply Later", + "@downloader_action_restart_later": {}, + "downloader_info_restart_manager_to_apply": "Speed limit settings saved. Restart the download manager now to apply new settings?", + "@downloader_info_restart_manager_to_apply": {}, "doctor_title_one_click_diagnosis": "One-Click Diagnosis -> {v0}", "@doctor_title_one_click_diagnosis": {}, "doctor_action_rsi_launcher_log": "RSI Launcher log", diff --git a/lib/l10n/intl_ja.arb b/lib/l10n/intl_ja.arb index c9cbf6c..448a271 100644 --- a/lib/l10n/intl_ja.arb +++ b/lib/l10n/intl_ja.arb @@ -135,6 +135,14 @@ "@downloader_input_download_speed_limit": {}, "downloader_input_info_p2p_upload_note": "* P2Pアップロードはファイルのダウンロード中にのみ行われ、ダウンロードが完了するとP2P接続は閉じられます。シードに参加したい場合は、「アバウト」ページから私たちにご連絡ください。", "@downloader_input_info_p2p_upload_note": {}, + "downloader_info_speed_limit_saved_restart_required": "速度制限設定が保存されました。次回のダウンローダー起動時に適用されます。", + "@downloader_info_speed_limit_saved_restart_required": {}, + "downloader_action_restart_now": "今すぐ再起動", + "@downloader_action_restart_now": {}, + "downloader_action_restart_later": "後で適用", + "@downloader_action_restart_later": {}, + "downloader_info_restart_manager_to_apply": "速度制限設定が保存されました。新しい設定を適用するためにダウンロードマネージャーを今すぐ再起動しますか?", + "@downloader_info_restart_manager_to_apply": {}, "doctor_title_one_click_diagnosis": "ワンクリック診断 -> {v0}", "@doctor_title_one_click_diagnosis": {}, "doctor_action_rsi_launcher_log": "RSIランチャーログ", diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index ff0a99d..2699860 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -135,6 +135,14 @@ "@downloader_input_download_speed_limit": {}, "downloader_input_info_p2p_upload_note": "* P2P отдача происходит только во время загрузки файла и отключается после завершения. Если вы хотите участвовать в раздаче, пожалуйста, свяжитесь с нами через страницу 'О программе'.", "@downloader_input_info_p2p_upload_note": {}, + "downloader_info_speed_limit_saved_restart_required": "Настройки ограничения скорости сохранены. Будут применены при следующем запуске загрузчика.", + "@downloader_info_speed_limit_saved_restart_required": {}, + "downloader_action_restart_now": "Перезапустить сейчас", + "@downloader_action_restart_now": {}, + "downloader_action_restart_later": "Применить позже", + "@downloader_action_restart_later": {}, + "downloader_info_restart_manager_to_apply": "Настройки ограничения скорости сохранены. Перезапустить менеджер загрузок сейчас для применения новых настроек?", + "@downloader_info_restart_manager_to_apply": {}, "doctor_title_one_click_diagnosis": "Быстрая диагностика -> {v0}", "@doctor_title_one_click_diagnosis": {}, "doctor_action_rsi_launcher_log": "Лог RSI Launcher", diff --git a/lib/l10n/intl_zh_CN.arb b/lib/l10n/intl_zh_CN.arb index b238fa2..1f43df6 100644 --- a/lib/l10n/intl_zh_CN.arb +++ b/lib/l10n/intl_zh_CN.arb @@ -134,6 +134,14 @@ "@downloader_input_download_speed_limit": {}, "downloader_input_info_p2p_upload_note": "* P2P 上传仅在下载文件时进行,下载完成后会关闭 p2p 连接。如您想参与做种,请通过关于页面联系我们。", "@downloader_input_info_p2p_upload_note": {}, + "downloader_info_speed_limit_saved_restart_required": "限速设置已保存,将在下次启动下载器时生效。", + "@downloader_info_speed_limit_saved_restart_required": {}, + "downloader_action_restart_now": "立即重启下载管理器", + "@downloader_action_restart_now": {}, + "downloader_action_restart_later": "下次启动时生效", + "@downloader_action_restart_later": {}, + "downloader_info_restart_manager_to_apply": "限速设置已保存。是否立即重启下载管理器以应用新设置?", + "@downloader_info_restart_manager_to_apply": {}, "doctor_title_one_click_diagnosis": "一键诊断 -> {v0}", "@doctor_title_one_click_diagnosis": {}, "doctor_action_rsi_launcher_log": "RSI启动器log", diff --git a/lib/l10n/intl_zh_TW.arb b/lib/l10n/intl_zh_TW.arb index ea80738..14ba749 100644 --- a/lib/l10n/intl_zh_TW.arb +++ b/lib/l10n/intl_zh_TW.arb @@ -135,6 +135,14 @@ "@downloader_input_download_speed_limit": {}, "downloader_input_info_p2p_upload_note": "* P2P 上傳僅在下載文件時進行,下載完成後會關閉 p2p 連接。如您想參與製作種子文件,請透過關於頁面聯絡我們。", "@downloader_input_info_p2p_upload_note": {}, + "downloader_info_speed_limit_saved_restart_required": "限速設定已儲存,將在下次啟動下載器時生效。", + "@downloader_info_speed_limit_saved_restart_required": {}, + "downloader_action_restart_now": "立即重新啟動", + "@downloader_action_restart_now": {}, + "downloader_action_restart_later": "稍後套用", + "@downloader_action_restart_later": {}, + "downloader_info_restart_manager_to_apply": "限速設定已儲存。是否立即重新啟動下載管理器以套用新設定?", + "@downloader_info_restart_manager_to_apply": {}, "doctor_title_one_click_diagnosis": "疑難排解 -> {v0}", "@doctor_title_one_click_diagnosis": {}, "doctor_action_rsi_launcher_log": "RSI啟動器log", diff --git a/lib/provider/download_manager.dart b/lib/provider/download_manager.dart index 4874257..25ca104 100644 --- a/lib/provider/download_manager.dart +++ b/lib/provider/download_manager.dart @@ -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 initDownloader() async { + Future 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 shutdown() async { + await downloader_api.downloaderShutdown(); + state = state.copyWith(isInitialized: false, globalStat: null); + } + + /// Restart the downloader with new speed limit settings + Future 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 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 hasActiveTasks() async { + if (!state.isInitialized) { + return false; + } + return await downloader_api.downloaderHasActiveTasks(); + } } diff --git a/lib/provider/download_manager.freezed.dart b/lib/provider/download_manager.freezed.dart index 34db254..56cf0b1 100644 --- a/lib/provider/download_manager.freezed.dart +++ b/lib/provider/download_manager.freezed.dart @@ -14,7 +14,7 @@ T _$identity(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 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 Function( String downloadDir, bool isInitialized, downloader_api.DownloadGlobalStat? globalStat)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(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 Function( String downloadDir, bool isInitialized, downloader_api.DownloadGlobalStat? globalStat) $default,) {final _that = this; +@optionalTypeArgs TResult when(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? Function( String downloadDir, bool isInitialized, downloader_api.DownloadGlobalStat? globalStat)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(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?, diff --git a/lib/provider/download_manager.g.dart b/lib/provider/download_manager.g.dart index c1905c8..e729429 100644 --- a/lib/provider/download_manager.g.dart +++ b/lib/provider/download_manager.g.dart @@ -41,7 +41,7 @@ final class DownloadManagerProvider } } -String _$downloadManagerHash() => r'adc9a147522afbfcfc8a2e16310649220a75d6a3'; +String _$downloadManagerHash() => r'f0fd818851be0d1c9e6774803aae465c33843cde'; abstract class _$DownloadManager extends $Notifier { DownloadManagerState build(); diff --git a/lib/ui/home/downloader/home_downloader_ui_model.dart b/lib/ui/home/downloader/home_downloader_ui_model.dart index f44863c..f56f190 100644 --- a/lib/ui/home/downloader/home_downloader_ui_model.dart +++ b/lib/ui/home/downloader/home_downloader_ui_model.dart @@ -267,14 +267,51 @@ class HomeDownloaderUIModel extends _$HomeDownloaderUIModel { ), ); if (ok == true) { - // Note: rqbit doesn't support dynamic speed limit changes yet - // Just save the settings for now + // Save the settings await box.put('downloader_up_limit', upCtrl.text.trim()); await box.put('downloader_down_limit', downCtrl.text.trim()); - // Show info that speed limits will apply on next restart + // Ask user if they want to restart the download manager now if (context.mounted) { - showToast(context, "Speed limit settings saved. Will apply on next download."); + final restartNow = await showDialog( + context: context, + builder: (context) => ContentDialog( + title: Text(S.current.downloader_speed_limit_settings), + content: Text(S.current.downloader_info_restart_manager_to_apply), + actions: [ + Button( + child: Text(S.current.downloader_action_restart_later), + onPressed: () => Navigator.of(context).pop(false), + ), + FilledButton( + child: Text(S.current.downloader_action_restart_now), + onPressed: () => Navigator.of(context).pop(true), + ), + ], + ), + ); + + if (restartNow == true) { + // Get the download manager and restart it with new settings + final downloadManager = ref.read(downloadManagerProvider.notifier); + final upLimit = downloadManager.textToByte(upCtrl.text.trim()); + final downLimit = downloadManager.textToByte(downCtrl.text.trim()); + + try { + await downloadManager.restart( + uploadLimitBps: upLimit > 0 ? upLimit : null, + downloadLimitBps: downLimit > 0 ? downLimit : null, + ); + if (context.mounted) { + showToast(context, S.current.downloader_info_speed_limit_saved_restart_required); + } + } catch (e) { + dPrint("Failed to restart download manager: $e"); + if (context.mounted) { + showToast(context, "Failed to restart: $e"); + } + } + } } } } diff --git a/lib/ui/home/downloader/home_downloader_ui_model.g.dart b/lib/ui/home/downloader/home_downloader_ui_model.g.dart index f386c68..062c554 100644 --- a/lib/ui/home/downloader/home_downloader_ui_model.g.dart +++ b/lib/ui/home/downloader/home_downloader_ui_model.g.dart @@ -42,7 +42,7 @@ final class HomeDownloaderUIModelProvider } String _$homeDownloaderUIModelHash() => - r'27e2e4b7a5103eee9d489a347410131edef46be4'; + r'3dd2ca0b1c03113d577c322de81078faa378230b'; abstract class _$HomeDownloaderUIModel extends $Notifier { diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 7b3c3ab..ddc0de3 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2994,7 +2994,7 @@ dependencies = [ [[package]] name = "librqbit" version = "9.0.0-beta.1" -source = "git+https://github.com/StarCitizenToolBox/rqbit?tag=webseed-v0.0.1#e5ac0ac75cceffe017e247ea265dc7402c226638" +source = "git+https://github.com/StarCitizenToolBox/rqbit?tag=webseed-v0.0.2#7a9b4d7db84b7b9cccc424e294610cc800a9baa4" dependencies = [ "anyhow", "arc-swap", @@ -3058,7 +3058,7 @@ dependencies = [ [[package]] name = "librqbit-bencode" version = "3.1.0" -source = "git+https://github.com/StarCitizenToolBox/rqbit?tag=webseed-v0.0.1#e5ac0ac75cceffe017e247ea265dc7402c226638" +source = "git+https://github.com/StarCitizenToolBox/rqbit?tag=webseed-v0.0.2#7a9b4d7db84b7b9cccc424e294610cc800a9baa4" dependencies = [ "anyhow", "arrayvec", @@ -3074,7 +3074,7 @@ dependencies = [ [[package]] name = "librqbit-buffers" version = "4.2.0" -source = "git+https://github.com/StarCitizenToolBox/rqbit?tag=webseed-v0.0.1#e5ac0ac75cceffe017e247ea265dc7402c226638" +source = "git+https://github.com/StarCitizenToolBox/rqbit?tag=webseed-v0.0.2#7a9b4d7db84b7b9cccc424e294610cc800a9baa4" dependencies = [ "bytes", "librqbit-clone-to-owned", @@ -3085,7 +3085,7 @@ dependencies = [ [[package]] name = "librqbit-clone-to-owned" version = "3.0.1" -source = "git+https://github.com/StarCitizenToolBox/rqbit?tag=webseed-v0.0.1#e5ac0ac75cceffe017e247ea265dc7402c226638" +source = "git+https://github.com/StarCitizenToolBox/rqbit?tag=webseed-v0.0.2#7a9b4d7db84b7b9cccc424e294610cc800a9baa4" dependencies = [ "bytes", ] @@ -3093,7 +3093,7 @@ dependencies = [ [[package]] name = "librqbit-core" version = "5.0.0" -source = "git+https://github.com/StarCitizenToolBox/rqbit?tag=webseed-v0.0.1#e5ac0ac75cceffe017e247ea265dc7402c226638" +source = "git+https://github.com/StarCitizenToolBox/rqbit?tag=webseed-v0.0.2#7a9b4d7db84b7b9cccc424e294610cc800a9baa4" dependencies = [ "anyhow", "bytes", @@ -3122,7 +3122,7 @@ dependencies = [ [[package]] name = "librqbit-dht" version = "5.3.0" -source = "git+https://github.com/StarCitizenToolBox/rqbit?tag=webseed-v0.0.1#e5ac0ac75cceffe017e247ea265dc7402c226638" +source = "git+https://github.com/StarCitizenToolBox/rqbit?tag=webseed-v0.0.2#7a9b4d7db84b7b9cccc424e294610cc800a9baa4" dependencies = [ "anyhow", "backon", @@ -3169,7 +3169,7 @@ dependencies = [ [[package]] name = "librqbit-lsd" version = "0.1.0" -source = "git+https://github.com/StarCitizenToolBox/rqbit?tag=webseed-v0.0.1#e5ac0ac75cceffe017e247ea265dc7402c226638" +source = "git+https://github.com/StarCitizenToolBox/rqbit?tag=webseed-v0.0.2#7a9b4d7db84b7b9cccc424e294610cc800a9baa4" dependencies = [ "anyhow", "atoi", @@ -3189,7 +3189,7 @@ dependencies = [ [[package]] name = "librqbit-peer-protocol" version = "4.3.0" -source = "git+https://github.com/StarCitizenToolBox/rqbit?tag=webseed-v0.0.1#e5ac0ac75cceffe017e247ea265dc7402c226638" +source = "git+https://github.com/StarCitizenToolBox/rqbit?tag=webseed-v0.0.2#7a9b4d7db84b7b9cccc424e294610cc800a9baa4" dependencies = [ "anyhow", "bitvec", @@ -3209,7 +3209,7 @@ dependencies = [ [[package]] name = "librqbit-sha1-wrapper" version = "4.1.0" -source = "git+https://github.com/StarCitizenToolBox/rqbit?tag=webseed-v0.0.1#e5ac0ac75cceffe017e247ea265dc7402c226638" +source = "git+https://github.com/StarCitizenToolBox/rqbit?tag=webseed-v0.0.2#7a9b4d7db84b7b9cccc424e294610cc800a9baa4" dependencies = [ "assert_cfg", "aws-lc-rs", @@ -3218,7 +3218,7 @@ dependencies = [ [[package]] name = "librqbit-tracker-comms" version = "3.0.0" -source = "git+https://github.com/StarCitizenToolBox/rqbit?tag=webseed-v0.0.1#e5ac0ac75cceffe017e247ea265dc7402c226638" +source = "git+https://github.com/StarCitizenToolBox/rqbit?tag=webseed-v0.0.2#7a9b4d7db84b7b9cccc424e294610cc800a9baa4" dependencies = [ "anyhow", "async-stream", @@ -3246,7 +3246,7 @@ dependencies = [ [[package]] name = "librqbit-upnp" version = "1.0.0" -source = "git+https://github.com/StarCitizenToolBox/rqbit?tag=webseed-v0.0.1#e5ac0ac75cceffe017e247ea265dc7402c226638" +source = "git+https://github.com/StarCitizenToolBox/rqbit?tag=webseed-v0.0.2#7a9b4d7db84b7b9cccc424e294610cc800a9baa4" dependencies = [ "anyhow", "bstr", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 2ea2e84..500bca5 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -35,7 +35,7 @@ unp4k_rs = { git = "https://github.com/StarCitizenToolBox/unp4k_rs", tag = "V0.0 uuid = { version = "1.19.0", features = ["v4"] } parking_lot = "0.12.5" crossbeam-channel = "0.5.15" -librqbit = { git = "https://github.com/StarCitizenToolBox/rqbit", tag = "webseed-v0.0.1" } +librqbit = { git = "https://github.com/StarCitizenToolBox/rqbit", tag = "webseed-v0.0.2" } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } bytes = "1.10" diff --git a/rust/src/api/downloader_api.rs b/rust/src/api/downloader_api.rs index 584747c..e8099d0 100644 --- a/rust/src/api/downloader_api.rs +++ b/rust/src/api/downloader_api.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::num::NonZeroU32; use std::path::PathBuf; use std::sync::Arc; @@ -7,9 +8,11 @@ use bytes::Bytes; use flutter_rust_bridge::frb; use librqbit::{ AddTorrent, AddTorrentOptions, AddTorrentResponse, Session, SessionOptions, - TorrentStats, ManagedTorrent, TorrentStatsState, + SessionPersistenceConfig, TorrentStats, ManagedTorrent, TorrentStatsState, + api::TorrentIdOrHash, + dht::PersistentDhtConfig, + limits::LimitsConfig, }; -use once_cell::sync::OnceCell; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; use tokio::sync::Mutex; @@ -17,8 +20,9 @@ use tokio::sync::Mutex; // Type alias for ManagedTorrentHandle type ManagedTorrentHandle = Arc; -// Global session instance -static SESSION: OnceCell> = OnceCell::new(); +// Global session instance - using RwLock to allow restart +static SESSION: once_cell::sync::Lazy>>> = + once_cell::sync::Lazy::new(|| RwLock::new(None)); static SESSION_INIT_LOCK: once_cell::sync::Lazy> = once_cell::sync::Lazy::new(|| Mutex::new(())); @@ -31,7 +35,7 @@ static TASK_OUTPUT_FOLDERS: once_cell::sync::Lazy> once_cell::sync::Lazy::new(|| RwLock::new(HashMap::new())); /// Download task status -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum DownloadTaskStatus { Initializing, Live, @@ -65,11 +69,22 @@ pub struct DownloadGlobalStat { pub num_waiting: usize, } -/// Initialize the download manager session +/// Initialize the download manager session with persistence enabled +/// +/// Parameters: +/// - working_dir: The directory to store session data (persistence, DHT, etc.) +/// - default_download_dir: The default directory to store downloads +/// - upload_limit_bps: Upload speed limit in bytes per second (0 = unlimited) +/// - download_limit_bps: Download speed limit in bytes per second (0 = unlimited) #[frb(sync)] -pub fn downloader_init(download_dir: String) -> Result<()> { +pub fn downloader_init( + working_dir: String, + default_download_dir: String, + upload_limit_bps: Option, + download_limit_bps: Option, +) -> Result<()> { // Already initialized - if SESSION.get().is_some() { + if SESSION.read().is_some() { return Ok(()); } @@ -78,28 +93,52 @@ pub fn downloader_init(download_dir: String) -> Result<()> { let _lock = SESSION_INIT_LOCK.lock().await; // Double check after acquiring lock - if SESSION.get().is_some() { + if SESSION.read().is_some() { return Ok(()); } - let output_folder = PathBuf::from(&download_dir); + // Working directory for persistence and session data + let working_folder = PathBuf::from(&working_dir); + std::fs::create_dir_all(&working_folder)?; + + // Default download folder + let output_folder = PathBuf::from(&default_download_dir); std::fs::create_dir_all(&output_folder)?; + + // Create persistence folder for session state in working directory + let persistence_folder = working_folder.join("rqbit-session"); + std::fs::create_dir_all(&persistence_folder)?; + + // DHT persistence file in working directory + let dht_persistence_file = working_folder.join("dht.json"); let session = Session::new_with_opts( output_folder, SessionOptions { disable_dht: false, - disable_dht_persistence: true, - persistence: None, + disable_dht_persistence: false, + // Configure DHT persistence to use working directory + dht_config: Some(PersistentDhtConfig { + config_filename: Some(dht_persistence_file), + ..Default::default() + }), + // Enable JSON-based session persistence for task recovery + persistence: Some(SessionPersistenceConfig::Json { + folder: Some(persistence_folder), + }), + fastresume: false, + // Configure rate limits + ratelimits: LimitsConfig { + upload_bps: upload_limit_bps.and_then(NonZeroU32::new), + download_bps: download_limit_bps.and_then(NonZeroU32::new), + }, ..Default::default() }, ) .await .context("Failed to create rqbit session")?; - SESSION - .set(session) - .map_err(|_| anyhow::anyhow!("Session already initialized"))?; + *SESSION.write() = Some(session); Ok(()) }) @@ -108,7 +147,14 @@ pub fn downloader_init(download_dir: String) -> Result<()> { /// Check if the downloader is initialized #[frb(sync)] pub fn downloader_is_initialized() -> bool { - SESSION.get().is_some() + SESSION.read().is_some() +} + +/// Helper function to get session +fn get_session() -> Result> { + SESSION.read() + .clone() + .context("Downloader not initialized. Call downloader_init first.") } /// Add a torrent from bytes (e.g., .torrent file content) @@ -117,9 +163,7 @@ pub async fn downloader_add_torrent( output_folder: Option, trackers: Option>, ) -> Result { - let session = SESSION - .get() - .context("Downloader not initialized. Call downloader_init first.")?; + let session = get_session()?; let bytes = Bytes::from(torrent_bytes); let add_torrent = AddTorrent::from_bytes(bytes); @@ -171,9 +215,7 @@ pub async fn downloader_add_magnet( output_folder: Option, trackers: Option>, ) -> Result { - let session = SESSION - .get() - .context("Downloader not initialized. Call downloader_init first.")?; + let session = get_session()?; // Check if it's a magnet link if !magnet_link.starts_with("magnet:") { @@ -260,9 +302,7 @@ pub async fn downloader_add_url( /// Pause a download task pub async fn downloader_pause(task_id: usize) -> Result<()> { - let session = SESSION - .get() - .context("Downloader not initialized")?; + let session = get_session()?; let handle = { let handles = TORRENT_HANDLES.read(); @@ -279,9 +319,7 @@ pub async fn downloader_pause(task_id: usize) -> Result<()> { /// Resume a download task pub async fn downloader_resume(task_id: usize) -> Result<()> { - let session = SESSION - .get() - .context("Downloader not initialized")?; + let session = get_session()?; let handle = { let handles = TORRENT_HANDLES.read(); @@ -298,9 +336,7 @@ pub async fn downloader_resume(task_id: usize) -> Result<()> { /// Remove a download task pub async fn downloader_remove(task_id: usize, delete_files: bool) -> Result<()> { - let session = SESSION - .get() - .context("Downloader not initialized")?; + let session = get_session()?; session .delete(librqbit::api::TorrentIdOrHash::Id(task_id), delete_files) @@ -382,11 +418,12 @@ fn get_task_status(stats: &TorrentStats) -> DownloadTaskStatus { /// Get all tasks pub async fn downloader_get_all_tasks() -> Result> { - let session = SESSION.get(); - if session.is_none() { - return Ok(vec![]); - } - let session = session.unwrap(); + let session_guard = SESSION.read(); + let session = match session_guard.as_ref() { + Some(s) => s.clone(), + None => return Ok(vec![]), + }; + drop(session_guard); // Use RwLock to collect tasks since with_torrents takes Fn (not FnMut) let tasks: RwLock> = RwLock::new(Vec::new()); @@ -474,9 +511,7 @@ pub async fn downloader_is_name_in_task(name: String) -> bool { /// Pause all tasks pub async fn downloader_pause_all() -> Result<()> { - let session = SESSION - .get() - .context("Downloader not initialized")?; + let session = get_session()?; let handles: Vec<_> = TORRENT_HANDLES.read().values().cloned().collect(); @@ -489,9 +524,7 @@ pub async fn downloader_pause_all() -> Result<()> { /// Resume all tasks pub async fn downloader_resume_all() -> Result<()> { - let session = SESSION - .get() - .context("Downloader not initialized")?; + let session = get_session()?; let handles: Vec<_> = TORRENT_HANDLES.read().values().cloned().collect(); @@ -502,12 +535,80 @@ pub async fn downloader_resume_all() -> Result<()> { Ok(()) } -/// Stop the downloader session +/// Stop the downloader session (pauses all tasks but keeps session) pub async fn downloader_stop() -> Result<()> { - if let Some(session) = SESSION.get() { + let session = SESSION.read().clone(); + if let Some(session) = session { session.stop().await; } TORRENT_HANDLES.write().clear(); TASK_OUTPUT_FOLDERS.write().clear(); Ok(()) } + +/// Shutdown the downloader session completely (allows restart with new settings) +pub async fn downloader_shutdown() -> Result<()> { + let session_opt = { + let mut guard = SESSION.write(); + guard.take() + }; + + if let Some(session) = session_opt { + session.stop().await; + } + + TORRENT_HANDLES.write().clear(); + TASK_OUTPUT_FOLDERS.write().clear(); + Ok(()) +} + +/// Update global speed limits +/// Note: rqbit Session doesn't support runtime limit changes, +/// this function is a placeholder that returns an error. +/// Speed limits should be set during downloader_init. +pub async fn downloader_update_speed_limits( + _upload_limit_bps: Option, + _download_limit_bps: Option, +) -> Result<()> { + // rqbit Session is created with limits but doesn't support runtime updates + // To change limits, the session needs to be recreated + anyhow::bail!("Runtime speed limit changes not supported. Please restart the downloader.") +} + +/// Remove all completed tasks (equivalent to aria2's --seed-time=0 behavior) +pub async fn downloader_remove_completed_tasks() -> Result { + let session = get_session()?; + + let tasks = downloader_get_all_tasks().await?; + let mut removed_count = 0u32; + + for task in tasks { + if task.status == DownloadTaskStatus::Finished { + // Check if handle exists (drop lock before await) + let has_handle = TORRENT_HANDLES.read().contains_key(&task.id); + if has_handle { + // Use TorrentIdOrHash::Id for deletion (TorrentId is just usize) + if session.delete(TorrentIdOrHash::Id(task.id), false).await.is_ok() { + TORRENT_HANDLES.write().remove(&task.id); + TASK_OUTPUT_FOLDERS.write().remove(&task.id); + removed_count += 1; + } + } + } + } + + Ok(removed_count) +} + +/// Check if there are any active (non-completed) tasks +pub async fn downloader_has_active_tasks() -> bool { + if let Ok(tasks) = downloader_get_all_tasks().await { + for task in tasks { + if task.status != DownloadTaskStatus::Finished + && task.status != DownloadTaskStatus::Error { + return true; + } + } + } + false +} diff --git a/rust/src/frb_generated.rs b/rust/src/frb_generated.rs index 2e8372a..ffb0151 100644 --- a/rust/src/frb_generated.rs +++ b/rust/src/frb_generated.rs @@ -37,7 +37,7 @@ flutter_rust_bridge::frb_generated_boilerplate!( default_rust_auto_opaque = RustAutoOpaqueNom, ); pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.11.1"; -pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -1465039096; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -641930410; // Section: executor @@ -376,8 +376,35 @@ fn wire__crate__api__downloader_api__downloader_get_task_info_impl( }, ) } +fn wire__crate__api__downloader_api__downloader_has_active_tasks_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "downloader_has_active_tasks", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + move |context| async move { + transform_result_dco::<_, _, ()>( + (move || async move { + let output_ok = Result::<_, ()>::Ok( + crate::api::downloader_api::downloader_has_active_tasks().await, + )?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} fn wire__crate__api__downloader_api__downloader_init_impl( - download_dir: impl CstDecode, + working_dir: impl CstDecode, + default_download_dir: impl CstDecode, + upload_limit_bps: impl CstDecode>, + download_limit_bps: impl CstDecode>, ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( flutter_rust_bridge::for_generated::TaskInfo { @@ -386,10 +413,18 @@ fn wire__crate__api__downloader_api__downloader_init_impl( mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, }, move || { - let api_download_dir = download_dir.cst_decode(); + let api_working_dir = working_dir.cst_decode(); + let api_default_download_dir = default_download_dir.cst_decode(); + let api_upload_limit_bps = upload_limit_bps.cst_decode(); + let api_download_limit_bps = download_limit_bps.cst_decode(); transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>( (move || { - let output_ok = crate::api::downloader_api::downloader_init(api_download_dir)?; + let output_ok = crate::api::downloader_api::downloader_init( + api_working_dir, + api_default_download_dir, + api_upload_limit_bps, + api_download_limit_bps, + )?; Ok(output_ok) })(), ) @@ -516,6 +551,29 @@ fn wire__crate__api__downloader_api__downloader_remove_impl( }, ) } +fn wire__crate__api__downloader_api__downloader_remove_completed_tasks_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "downloader_remove_completed_tasks", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + move |context| async move { + transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::api::downloader_api::downloader_remove_completed_tasks().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} fn wire__crate__api__downloader_api__downloader_resume_impl( port_: flutter_rust_bridge::for_generated::MessagePort, task_id: impl CstDecode, @@ -563,6 +621,28 @@ fn wire__crate__api__downloader_api__downloader_resume_all_impl( }, ) } +fn wire__crate__api__downloader_api__downloader_shutdown_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "downloader_shutdown", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + move |context| async move { + transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::api::downloader_api::downloader_shutdown().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} fn wire__crate__api__downloader_api__downloader_stop_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ) { @@ -585,6 +665,36 @@ fn wire__crate__api__downloader_api__downloader_stop_impl( }, ) } +fn wire__crate__api__downloader_api__downloader_update_speed_limits_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + _upload_limit_bps: impl CstDecode>, + _download_limit_bps: impl CstDecode>, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "downloader_update_speed_limits", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let api__upload_limit_bps = _upload_limit_bps.cst_decode(); + let api__download_limit_bps = _download_limit_bps.cst_decode(); + move |context| async move { + transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::api::downloader_api::downloader_update_speed_limits( + api__upload_limit_bps, + api__download_limit_bps, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} fn wire__crate__api__http_api__fetch_impl( port_: flutter_rust_bridge::for_generated::MessagePort, method: impl CstDecode, @@ -2152,6 +2262,17 @@ impl SseDecode for Option { } } +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some(::sse_decode(deserializer)); + } else { + return None; + } + } +} + impl SseDecode for Option { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -3081,6 +3202,16 @@ impl SseEncode for Option { } } +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + impl SseEncode for Option { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -3374,6 +3505,12 @@ mod io { CstDecode::::cst_decode(*wrap).into() } } + impl CstDecode for *mut u32 { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> u32 { + unsafe { *flutter_rust_bridge::for_generated::box_from_leak_ptr(self) } + } + } impl CstDecode for *mut u64 { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> u64 { @@ -3947,11 +4084,26 @@ mod io { wire__crate__api__downloader_api__downloader_get_task_info_impl(port_, task_id) } + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__downloader_api__downloader_has_active_tasks( + port_: i64, + ) { + wire__crate__api__downloader_api__downloader_has_active_tasks_impl(port_) + } + #[unsafe(no_mangle)] pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__downloader_api__downloader_init( - download_dir: *mut wire_cst_list_prim_u_8_strict, + working_dir: *mut wire_cst_list_prim_u_8_strict, + default_download_dir: *mut wire_cst_list_prim_u_8_strict, + upload_limit_bps: *mut u32, + download_limit_bps: *mut u32, ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { - wire__crate__api__downloader_api__downloader_init_impl(download_dir) + wire__crate__api__downloader_api__downloader_init_impl( + working_dir, + default_download_dir, + upload_limit_bps, + download_limit_bps, + ) } #[unsafe(no_mangle)] @@ -3992,6 +4144,13 @@ mod io { wire__crate__api__downloader_api__downloader_remove_impl(port_, task_id, delete_files) } + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__downloader_api__downloader_remove_completed_tasks( + port_: i64, + ) { + wire__crate__api__downloader_api__downloader_remove_completed_tasks_impl(port_) + } + #[unsafe(no_mangle)] pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__downloader_api__downloader_resume( port_: i64, @@ -4007,6 +4166,13 @@ mod io { wire__crate__api__downloader_api__downloader_resume_all_impl(port_) } + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__downloader_api__downloader_shutdown( + port_: i64, + ) { + wire__crate__api__downloader_api__downloader_shutdown_impl(port_) + } + #[unsafe(no_mangle)] pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__downloader_api__downloader_stop( port_: i64, @@ -4014,6 +4180,19 @@ mod io { wire__crate__api__downloader_api__downloader_stop_impl(port_) } + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__downloader_api__downloader_update_speed_limits( + port_: i64, + _upload_limit_bps: *mut u32, + _download_limit_bps: *mut u32, + ) { + wire__crate__api__downloader_api__downloader_update_speed_limits_impl( + port_, + _upload_limit_bps, + _download_limit_bps, + ) + } + #[unsafe(no_mangle)] pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__http_api__fetch( port_: i64, @@ -4441,6 +4620,11 @@ mod io { ) } + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_cst_new_box_autoadd_u_32(value: u32) -> *mut u32 { + flutter_rust_bridge::for_generated::new_leak_box_ptr(value) + } + #[unsafe(no_mangle)] pub extern "C" fn frbgen_starcitizen_doctor_cst_new_box_autoadd_u_64(value: u64) -> *mut u64 { flutter_rust_bridge::for_generated::new_leak_box_ptr(value)