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

@ -6,12 +6,27 @@
import '../frb_generated.dart'; import '../frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_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 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`, `fmt`, `fmt`, `fmt` // 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 /// Initialize the download manager session with persistence enabled
void downloaderInit({required String downloadDir}) => RustLib.instance.api ///
.crateApiDownloaderApiDownloaderInit(downloadDir: downloadDir); /// 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 /// Check if the downloader is initialized
bool downloaderIsInitialized() => bool downloaderIsInitialized() =>
@ -95,10 +110,34 @@ Future<void> downloaderPauseAll() =>
Future<void> downloaderResumeAll() => Future<void> downloaderResumeAll() =>
RustLib.instance.api.crateApiDownloaderApiDownloaderResumeAll(); RustLib.instance.api.crateApiDownloaderApiDownloaderResumeAll();
/// Stop the downloader session /// Stop the downloader session (pauses all tasks but keeps session)
Future<void> downloaderStop() => Future<void> downloaderStop() =>
RustLib.instance.api.crateApiDownloaderApiDownloaderStop(); RustLib.instance.api.crateApiDownloaderApiDownloaderStop();
/// Shutdown the downloader session completely (allows restart with new settings)
Future<void> 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<void> 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<int> downloaderRemoveCompletedTasks() =>
RustLib.instance.api.crateApiDownloaderApiDownloaderRemoveCompletedTasks();
/// Check if there are any active (non-completed) tasks
Future<bool> downloaderHasActiveTasks() =>
RustLib.instance.api.crateApiDownloaderApiDownloaderHasActiveTasks();
/// Global statistics /// Global statistics
class DownloadGlobalStat { class DownloadGlobalStat {
final BigInt downloadSpeed; final BigInt downloadSpeed;

View File

@ -72,7 +72,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
String get codegenVersion => '2.11.1'; String get codegenVersion => '2.11.1';
@override @override
int get rustContentHash => -1465039096; int get rustContentHash => -641930410;
static const kDefaultExternalLibraryLoaderConfig = static const kDefaultExternalLibraryLoaderConfig =
ExternalLibraryLoaderConfig( ExternalLibraryLoaderConfig(
@ -126,7 +126,14 @@ abstract class RustLibApi extends BaseApi {
required BigInt taskId, required BigInt taskId,
}); });
void crateApiDownloaderApiDownloaderInit({required String downloadDir}); Future<bool> crateApiDownloaderApiDownloaderHasActiveTasks();
void crateApiDownloaderApiDownloaderInit({
required String workingDir,
required String defaultDownloadDir,
int? uploadLimitBps,
int? downloadLimitBps,
});
bool crateApiDownloaderApiDownloaderIsInitialized(); bool crateApiDownloaderApiDownloaderIsInitialized();
@ -143,12 +150,21 @@ abstract class RustLibApi extends BaseApi {
required bool deleteFiles, required bool deleteFiles,
}); });
Future<int> crateApiDownloaderApiDownloaderRemoveCompletedTasks();
Future<void> crateApiDownloaderApiDownloaderResume({required BigInt taskId}); Future<void> crateApiDownloaderApiDownloaderResume({required BigInt taskId});
Future<void> crateApiDownloaderApiDownloaderResumeAll(); Future<void> crateApiDownloaderApiDownloaderResumeAll();
Future<void> crateApiDownloaderApiDownloaderShutdown();
Future<void> crateApiDownloaderApiDownloaderStop(); Future<void> crateApiDownloaderApiDownloaderStop();
Future<void> crateApiDownloaderApiDownloaderUpdateSpeedLimits({
int? uploadLimitBps,
int? downloadLimitBps,
});
Future<RustHttpResponse> crateApiHttpApiFetch({ Future<RustHttpResponse> crateApiHttpApiFetch({
required MyMethod method, required MyMethod method,
required String url, required String url,
@ -700,19 +716,64 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
); );
@override @override
void crateApiDownloaderApiDownloaderInit({required String downloadDir}) { Future<bool> 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( return handler.executeSync(
SyncTask( SyncTask(
callFfi: () { callFfi: () {
var arg0 = cst_encode_String(downloadDir); var arg0 = cst_encode_String(workingDir);
return wire.wire__crate__api__downloader_api__downloader_init(arg0); 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( codec: DcoCodec(
decodeSuccessData: dco_decode_unit, decodeSuccessData: dco_decode_unit,
decodeErrorData: dco_decode_AnyhowException, decodeErrorData: dco_decode_AnyhowException,
), ),
constMeta: kCrateApiDownloaderApiDownloaderInitConstMeta, constMeta: kCrateApiDownloaderApiDownloaderInitConstMeta,
argValues: [downloadDir], argValues: [
workingDir,
defaultDownloadDir,
uploadLimitBps,
downloadLimitBps,
],
apiImpl: this, apiImpl: this,
), ),
); );
@ -721,7 +782,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
TaskConstMeta get kCrateApiDownloaderApiDownloaderInitConstMeta => TaskConstMeta get kCrateApiDownloaderApiDownloaderInitConstMeta =>
const TaskConstMeta( const TaskConstMeta(
debugName: "downloader_init", debugName: "downloader_init",
argNames: ["downloadDir"], argNames: [
"workingDir",
"defaultDownloadDir",
"uploadLimitBps",
"downloadLimitBps",
],
); );
@override @override
@ -858,6 +924,35 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
argNames: ["taskId", "deleteFiles"], argNames: ["taskId", "deleteFiles"],
); );
@override
Future<int> 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 @override
Future<void> crateApiDownloaderApiDownloaderResume({required BigInt taskId}) { Future<void> crateApiDownloaderApiDownloaderResume({required BigInt taskId}) {
return handler.executeNormal( return handler.executeNormal(
@ -906,6 +1001,29 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
TaskConstMeta get kCrateApiDownloaderApiDownloaderResumeAllConstMeta => TaskConstMeta get kCrateApiDownloaderApiDownloaderResumeAllConstMeta =>
const TaskConstMeta(debugName: "downloader_resume_all", argNames: []); const TaskConstMeta(debugName: "downloader_resume_all", argNames: []);
@override
Future<void> 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 @override
Future<void> crateApiDownloaderApiDownloaderStop() { Future<void> crateApiDownloaderApiDownloaderStop() {
return handler.executeNormal( return handler.executeNormal(
@ -927,6 +1045,41 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
TaskConstMeta get kCrateApiDownloaderApiDownloaderStopConstMeta => TaskConstMeta get kCrateApiDownloaderApiDownloaderStopConstMeta =>
const TaskConstMeta(debugName: "downloader_stop", argNames: []); const TaskConstMeta(debugName: "downloader_stop", argNames: []);
@override
Future<void> 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 @override
Future<RustHttpResponse> crateApiHttpApiFetch({ Future<RustHttpResponse> crateApiHttpApiFetch({
required MyMethod method, required MyMethod method,
@ -2386,6 +2539,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return dco_decode_rsi_launcher_asar_data(raw); 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 @protected
BigInt dco_decode_box_autoadd_u_64(dynamic raw) { BigInt dco_decode_box_autoadd_u_64(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs // 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); 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 @protected
BigInt? dco_decode_opt_box_autoadd_u_64(dynamic raw) { BigInt? dco_decode_opt_box_autoadd_u_64(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs // 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)); 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 @protected
BigInt sse_decode_box_autoadd_u_64(SseDeserializer deserializer) { BigInt sse_decode_box_autoadd_u_64(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs // 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 @protected
BigInt? sse_decode_opt_box_autoadd_u_64(SseDeserializer deserializer) { BigInt? sse_decode_opt_box_autoadd_u_64(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs // 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); 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 @protected
void sse_encode_box_autoadd_u_64(BigInt self, SseSerializer serializer) { void sse_encode_box_autoadd_u_64(BigInt self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs // 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 @protected
void sse_encode_opt_box_autoadd_u_64(BigInt? self, SseSerializer serializer) { void sse_encode_opt_box_autoadd_u_64(BigInt? self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs

View File

@ -50,6 +50,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
dynamic raw, dynamic raw,
); );
@protected
int dco_decode_box_autoadd_u_32(dynamic raw);
@protected @protected
BigInt dco_decode_box_autoadd_u_64(dynamic raw); BigInt dco_decode_box_autoadd_u_64(dynamic raw);
@ -115,6 +118,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
bool? dco_decode_opt_box_autoadd_bool(dynamic raw); bool? dco_decode_opt_box_autoadd_bool(dynamic raw);
@protected
int? dco_decode_opt_box_autoadd_u_32(dynamic raw);
@protected @protected
BigInt? dco_decode_opt_box_autoadd_u_64(dynamic raw); BigInt? dco_decode_opt_box_autoadd_u_64(dynamic raw);
@ -203,6 +209,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseDeserializer deserializer, SseDeserializer deserializer,
); );
@protected
int sse_decode_box_autoadd_u_32(SseDeserializer deserializer);
@protected @protected
BigInt sse_decode_box_autoadd_u_64(SseDeserializer deserializer); BigInt sse_decode_box_autoadd_u_64(SseDeserializer deserializer);
@ -282,6 +291,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
bool? sse_decode_opt_box_autoadd_bool(SseDeserializer deserializer); bool? sse_decode_opt_box_autoadd_bool(SseDeserializer deserializer);
@protected
int? sse_decode_opt_box_autoadd_u_32(SseDeserializer deserializer);
@protected @protected
BigInt? sse_decode_opt_box_autoadd_u_64(SseDeserializer deserializer); BigInt? sse_decode_opt_box_autoadd_u_64(SseDeserializer deserializer);
@ -408,6 +420,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
return ptr; return ptr;
} }
@protected
ffi.Pointer<ffi.Uint32> 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 @protected
ffi.Pointer<ffi.Uint64> cst_encode_box_autoadd_u_64(BigInt raw) { ffi.Pointer<ffi.Uint64> cst_encode_box_autoadd_u_64(BigInt raw) {
// Codec=Cst (C-struct based), see doc to use other codecs // Codec=Cst (C-struct based), see doc to use other codecs
@ -538,6 +556,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
return raw == null ? ffi.nullptr : cst_encode_box_autoadd_bool(raw); return raw == null ? ffi.nullptr : cst_encode_box_autoadd_bool(raw);
} }
@protected
ffi.Pointer<ffi.Uint32> 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 @protected
ffi.Pointer<ffi.Uint64> cst_encode_opt_box_autoadd_u_64(BigInt? raw) { ffi.Pointer<ffi.Uint64> cst_encode_opt_box_autoadd_u_64(BigInt? raw) {
// Codec=Cst (C-struct based), see doc to use other codecs // Codec=Cst (C-struct based), see doc to use other codecs
@ -829,6 +853,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer, SseSerializer serializer,
); );
@protected
void sse_encode_box_autoadd_u_32(int self, SseSerializer serializer);
@protected @protected
void sse_encode_box_autoadd_u_64(BigInt self, SseSerializer serializer); void sse_encode_box_autoadd_u_64(BigInt self, SseSerializer serializer);
@ -925,6 +952,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
void sse_encode_opt_box_autoadd_bool(bool? self, SseSerializer serializer); 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 @protected
void sse_encode_opt_box_autoadd_u_64(BigInt? self, SseSerializer serializer); 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 _wire__crate__api__downloader_api__downloader_get_task_infoPtr
.asFunction<void Function(int, int)>(); .asFunction<void Function(int, int)>();
WireSyncRust2DartDco wire__crate__api__downloader_api__downloader_init( void wire__crate__api__downloader_api__downloader_has_active_tasks(
ffi.Pointer<wire_cst_list_prim_u_8_strict> download_dir, 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<ffi.NativeFunction<ffi.Void Function(ffi.Int64)>>(
'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<void Function(int)>();
WireSyncRust2DartDco wire__crate__api__downloader_api__downloader_init(
ffi.Pointer<wire_cst_list_prim_u_8_strict> working_dir,
ffi.Pointer<wire_cst_list_prim_u_8_strict> default_download_dir,
ffi.Pointer<ffi.Uint32> upload_limit_bps,
ffi.Pointer<ffi.Uint32> 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 = late final _wire__crate__api__downloader_api__downloader_initPtr =
@ -1348,6 +1402,9 @@ class RustLibWire implements BaseWire {
ffi.NativeFunction< ffi.NativeFunction<
WireSyncRust2DartDco Function( WireSyncRust2DartDco Function(
ffi.Pointer<wire_cst_list_prim_u_8_strict>, ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<ffi.Uint32>,
ffi.Pointer<ffi.Uint32>,
) )
> >
>( >(
@ -1358,6 +1415,9 @@ class RustLibWire implements BaseWire {
.asFunction< .asFunction<
WireSyncRust2DartDco Function( WireSyncRust2DartDco Function(
ffi.Pointer<wire_cst_list_prim_u_8_strict>, ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<ffi.Uint32>,
ffi.Pointer<ffi.Uint32>,
) )
>(); >();
@ -1450,6 +1510,22 @@ class RustLibWire implements BaseWire {
_wire__crate__api__downloader_api__downloader_removePtr _wire__crate__api__downloader_api__downloader_removePtr
.asFunction<void Function(int, int, bool)>(); .asFunction<void Function(int, int, bool)>();
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<ffi.NativeFunction<ffi.Void Function(ffi.Int64)>>(
'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 Function(int)>();
void wire__crate__api__downloader_api__downloader_resume( void wire__crate__api__downloader_api__downloader_resume(
int port_, int port_,
int task_id, int task_id,
@ -1477,6 +1553,18 @@ class RustLibWire implements BaseWire {
_wire__crate__api__downloader_api__downloader_resume_allPtr _wire__crate__api__downloader_api__downloader_resume_allPtr
.asFunction<void Function(int)>(); .asFunction<void Function(int)>();
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<ffi.NativeFunction<ffi.Void Function(ffi.Int64)>>(
'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 Function(int)>();
void wire__crate__api__downloader_api__downloader_stop(int port_) { void wire__crate__api__downloader_api__downloader_stop(int port_) {
return _wire__crate__api__downloader_api__downloader_stop(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 _wire__crate__api__downloader_api__downloader_stopPtr
.asFunction<void Function(int)>(); .asFunction<void Function(int)>();
void wire__crate__api__downloader_api__downloader_update_speed_limits(
int port_,
ffi.Pointer<ffi.Uint32> _upload_limit_bps,
ffi.Pointer<ffi.Uint32> _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.Uint32>,
ffi.Pointer<ffi.Uint32>,
)
>
>(
'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.Uint32>, ffi.Pointer<ffi.Uint32>)
>();
void wire__crate__api__http_api__fetch( void wire__crate__api__http_api__fetch(
int port_, int port_,
int method, int method,
@ -2755,6 +2873,17 @@ class RustLibWire implements BaseWire {
ffi.Pointer<wire_cst_rsi_launcher_asar_data> Function() ffi.Pointer<wire_cst_rsi_launcher_asar_data> Function()
>(); >();
ffi.Pointer<ffi.Uint32> 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<ffi.NativeFunction<ffi.Pointer<ffi.Uint32> 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<ffi.Pointer<ffi.Uint32> Function(int)>();
ffi.Pointer<ffi.Uint64> cst_new_box_autoadd_u_64(int value) { ffi.Pointer<ffi.Uint64> cst_new_box_autoadd_u_64(int value) {
return _cst_new_box_autoadd_u_64(value); return _cst_new_box_autoadd_u_64(value);
} }

View File

@ -614,6 +614,12 @@ class MessageLookup extends MessageLookupByLibrary {
"downloader_action_pause_download": MessageLookupByLibrary.simpleMessage( "downloader_action_pause_download": MessageLookupByLibrary.simpleMessage(
"Pause Download", "Pause Download",
), ),
"downloader_action_restart_later": MessageLookupByLibrary.simpleMessage(
"Apply Later",
),
"downloader_action_restart_now": MessageLookupByLibrary.simpleMessage(
"Restart Now",
),
"downloader_action_resume_all": MessageLookupByLibrary.simpleMessage( "downloader_action_resume_all": MessageLookupByLibrary.simpleMessage(
"Resume All", "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.", "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_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_status": m22,
"downloader_info_total_size": m23, "downloader_info_total_size": m23,
"downloader_info_uploaded": m24, "downloader_info_uploaded": m24,

View File

@ -551,6 +551,12 @@ class MessageLookup extends MessageLookupByLibrary {
"downloader_action_pause_download": MessageLookupByLibrary.simpleMessage( "downloader_action_pause_download": MessageLookupByLibrary.simpleMessage(
"ダウンロードを一時停止", "ダウンロードを一時停止",
), ),
"downloader_action_restart_later": MessageLookupByLibrary.simpleMessage(
"後で適用",
),
"downloader_action_restart_now": MessageLookupByLibrary.simpleMessage(
"今すぐ再起動",
),
"downloader_action_resume_all": MessageLookupByLibrary.simpleMessage( "downloader_action_resume_all": MessageLookupByLibrary.simpleMessage(
"すべて再開", "すべて再開",
), ),
@ -582,6 +588,14 @@ class MessageLookup extends MessageLookupByLibrary {
"SCToolboxはファイルダウンロードを高速化するためにP2Pネットワークを使用しています。通信量に制限がある場合は、アップロード帯域幅を1(byte)に設定できます。", "SCToolboxはファイルダウンロードを高速化するためにP2Pネットワークを使用しています。通信量に制限がある場合は、アップロード帯域幅を1(byte)に設定できます。",
), ),
"downloader_info_paused": MessageLookupByLibrary.simpleMessage("一時停止中"), "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_status": m22,
"downloader_info_total_size": m23, "downloader_info_total_size": m23,
"downloader_info_uploaded": m24, "downloader_info_uploaded": m24,

View File

@ -591,6 +591,12 @@ class MessageLookup extends MessageLookupByLibrary {
"downloader_action_pause_download": MessageLookupByLibrary.simpleMessage( "downloader_action_pause_download": MessageLookupByLibrary.simpleMessage(
"Приостановить загрузку", "Приостановить загрузку",
), ),
"downloader_action_restart_later": MessageLookupByLibrary.simpleMessage(
"Применить позже",
),
"downloader_action_restart_now": MessageLookupByLibrary.simpleMessage(
"Перезапустить сейчас",
),
"downloader_action_resume_all": MessageLookupByLibrary.simpleMessage( "downloader_action_resume_all": MessageLookupByLibrary.simpleMessage(
"Возобновить все", "Возобновить все",
), ),
@ -624,6 +630,14 @@ class MessageLookup extends MessageLookupByLibrary {
"downloader_info_paused": MessageLookupByLibrary.simpleMessage( "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_status": m22,
"downloader_info_total_size": m23, "downloader_info_total_size": m23,
"downloader_info_uploaded": m24, "downloader_info_uploaded": m24,

View File

@ -538,6 +538,12 @@ class MessageLookup extends MessageLookupByLibrary {
"downloader_action_pause_download": MessageLookupByLibrary.simpleMessage( "downloader_action_pause_download": MessageLookupByLibrary.simpleMessage(
"暂停下载", "暂停下载",
), ),
"downloader_action_restart_later": MessageLookupByLibrary.simpleMessage(
"下次启动时生效",
),
"downloader_action_restart_now": MessageLookupByLibrary.simpleMessage(
"立即重启下载管理器",
),
"downloader_action_resume_all": MessageLookupByLibrary.simpleMessage( "downloader_action_resume_all": MessageLookupByLibrary.simpleMessage(
"恢复全部", "恢复全部",
), ),
@ -565,6 +571,10 @@ class MessageLookup extends MessageLookupByLibrary {
"SC 汉化盒子使用 p2p 网络来加速文件下载,如果您流量有限,可在此处将上传带宽设置为 1(byte)。", "SC 汉化盒子使用 p2p 网络来加速文件下载,如果您流量有限,可在此处将上传带宽设置为 1(byte)。",
), ),
"downloader_info_paused": MessageLookupByLibrary.simpleMessage("已暂停"), "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_status": m22,
"downloader_info_total_size": m23, "downloader_info_total_size": m23,
"downloader_info_uploaded": m24, "downloader_info_uploaded": m24,

View File

@ -522,6 +522,12 @@ class MessageLookup extends MessageLookupByLibrary {
"downloader_action_pause_download": MessageLookupByLibrary.simpleMessage( "downloader_action_pause_download": MessageLookupByLibrary.simpleMessage(
"暫停下載", "暫停下載",
), ),
"downloader_action_restart_later": MessageLookupByLibrary.simpleMessage(
"稍後套用",
),
"downloader_action_restart_now": MessageLookupByLibrary.simpleMessage(
"立即重新啟動",
),
"downloader_action_resume_all": MessageLookupByLibrary.simpleMessage( "downloader_action_resume_all": MessageLookupByLibrary.simpleMessage(
"全部繼續", "全部繼續",
), ),
@ -549,6 +555,10 @@ class MessageLookup extends MessageLookupByLibrary {
"SC 工具箱使用 p2p 網路來加速文件下載,如果您流量有限,可在此處將上傳頻寬設定為 1(byte)。", "SC 工具箱使用 p2p 網路來加速文件下載,如果您流量有限,可在此處將上傳頻寬設定為 1(byte)。",
), ),
"downloader_info_paused": MessageLookupByLibrary.simpleMessage("已暫停"), "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_status": m22,
"downloader_info_total_size": m23, "downloader_info_total_size": m23,
"downloader_info_uploaded": m24, "downloader_info_uploaded": m24,

View File

@ -28,10 +28,9 @@ class S {
static const AppLocalizationDelegate delegate = AppLocalizationDelegate(); static const AppLocalizationDelegate delegate = AppLocalizationDelegate();
static Future<S> load(Locale locale) { static Future<S> load(Locale locale) {
final name = final name = (locale.countryCode?.isEmpty ?? false)
(locale.countryCode?.isEmpty ?? false) ? locale.languageCode
? locale.languageCode : locale.toString();
: locale.toString();
final localeName = Intl.canonicalizedLocale(name); final localeName = Intl.canonicalizedLocale(name);
return initializeMessages(localeName).then((_) { return initializeMessages(localeName).then((_) {
Intl.defaultLocale = localeName; 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}` /// `One-Click Diagnosis -> {v0}`
String doctor_title_one_click_diagnosis(Object v0) { String doctor_title_one_click_diagnosis(Object v0) {
return Intl.message( return Intl.message(

View File

@ -135,6 +135,14 @@
"@downloader_input_download_speed_limit": {}, "@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": "* 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_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": "One-Click Diagnosis -> {v0}",
"@doctor_title_one_click_diagnosis": {}, "@doctor_title_one_click_diagnosis": {},
"doctor_action_rsi_launcher_log": "RSI Launcher log", "doctor_action_rsi_launcher_log": "RSI Launcher log",

View File

@ -135,6 +135,14 @@
"@downloader_input_download_speed_limit": {}, "@downloader_input_download_speed_limit": {},
"downloader_input_info_p2p_upload_note": "* P2Pアップロードはファイルのダウンロード中にのみ行われ、ダウンロードが完了するとP2P接続は閉じられます。シードに参加したい場合は、「アバウト」ページから私たちにご連絡ください。", "downloader_input_info_p2p_upload_note": "* P2Pアップロードはファイルのダウンロード中にのみ行われ、ダウンロードが完了するとP2P接続は閉じられます。シードに参加したい場合は、「アバウト」ページから私たちにご連絡ください。",
"@downloader_input_info_p2p_upload_note": {}, "@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": "ワンクリック診断 -> {v0}",
"@doctor_title_one_click_diagnosis": {}, "@doctor_title_one_click_diagnosis": {},
"doctor_action_rsi_launcher_log": "RSIランチャーログ", "doctor_action_rsi_launcher_log": "RSIランチャーログ",

View File

@ -135,6 +135,14 @@
"@downloader_input_download_speed_limit": {}, "@downloader_input_download_speed_limit": {},
"downloader_input_info_p2p_upload_note": "* P2P отдача происходит только во время загрузки файла и отключается после завершения. Если вы хотите участвовать в раздаче, пожалуйста, свяжитесь с нами через страницу 'О программе'.", "downloader_input_info_p2p_upload_note": "* P2P отдача происходит только во время загрузки файла и отключается после завершения. Если вы хотите участвовать в раздаче, пожалуйста, свяжитесь с нами через страницу 'О программе'.",
"@downloader_input_info_p2p_upload_note": {}, "@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": "Быстрая диагностика -> {v0}",
"@doctor_title_one_click_diagnosis": {}, "@doctor_title_one_click_diagnosis": {},
"doctor_action_rsi_launcher_log": "Лог RSI Launcher", "doctor_action_rsi_launcher_log": "Лог RSI Launcher",

View File

@ -134,6 +134,14 @@
"@downloader_input_download_speed_limit": {}, "@downloader_input_download_speed_limit": {},
"downloader_input_info_p2p_upload_note": "* P2P 上传仅在下载文件时进行,下载完成后会关闭 p2p 连接。如您想参与做种,请通过关于页面联系我们。", "downloader_input_info_p2p_upload_note": "* P2P 上传仅在下载文件时进行,下载完成后会关闭 p2p 连接。如您想参与做种,请通过关于页面联系我们。",
"@downloader_input_info_p2p_upload_note": {}, "@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": "一键诊断 -> {v0}",
"@doctor_title_one_click_diagnosis": {}, "@doctor_title_one_click_diagnosis": {},
"doctor_action_rsi_launcher_log": "RSI启动器log", "doctor_action_rsi_launcher_log": "RSI启动器log",

View File

@ -135,6 +135,14 @@
"@downloader_input_download_speed_limit": {}, "@downloader_input_download_speed_limit": {},
"downloader_input_info_p2p_upload_note": "* P2P 上傳僅在下載文件時進行,下載完成後會關閉 p2p 連接。如您想參與製作種子文件,請透過關於頁面聯絡我們。", "downloader_input_info_p2p_upload_note": "* P2P 上傳僅在下載文件時進行,下載完成後會關閉 p2p 連接。如您想參與製作種子文件,請透過關於頁面聯絡我們。",
"@downloader_input_info_p2p_upload_note": {}, "@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": "疑難排解 -> {v0}",
"@doctor_title_one_click_diagnosis": {}, "@doctor_title_one_click_diagnosis": {},
"doctor_action_rsi_launcher_log": "RSI啟動器log", "doctor_action_rsi_launcher_log": "RSI啟動器log",

View File

@ -13,6 +13,7 @@ part 'download_manager.freezed.dart';
@freezed @freezed
abstract class DownloadManagerState with _$DownloadManagerState { abstract class DownloadManagerState with _$DownloadManagerState {
const factory DownloadManagerState({ const factory DownloadManagerState({
required String workingDir,
required String downloadDir, required String downloadDir,
@Default(false) bool isInitialized, @Default(false) bool isInitialized,
downloader_api.DownloadGlobalStat? globalStat, downloader_api.DownloadGlobalStat? globalStat,
@ -36,18 +37,24 @@ class DownloadManager extends _$DownloadManager {
if (appGlobalState.applicationBinaryModuleDir == null) { if (appGlobalState.applicationBinaryModuleDir == null) {
throw Exception("applicationBinaryModuleDir is null"); throw Exception("applicationBinaryModuleDir is null");
} }
if (appGlobalState.applicationSupportDir == null) {
throw Exception("applicationSupportDir is null");
}
ref.onDispose(() { ref.onDispose(() {
_disposed = true; _disposed = true;
}); });
ref.keepAlive(); 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"; final downloadDir = "${appGlobalState.applicationBinaryModuleDir}${Platform.pathSeparator}downloads";
// Lazy load init // Lazy load init
() async { () async {
try { try {
// Check if there are existing tasks // Check if there are existing tasks (check working dir for session data)
final dir = Directory(downloadDir); final dir = Directory(workingDir);
if (await dir.exists()) { if (await dir.exists()) {
dPrint("Launch download manager"); dPrint("Launch download manager");
await initDownloader(); 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; if (state.isInitialized) return;
try { try {
// Create download directory if it doesn't exist // Create working directory if it doesn't exist
final dir = Directory(state.downloadDir); final workingDir = Directory(state.workingDir);
if (!await dir.exists()) { if (!await workingDir.exists()) {
await dir.create(recursive: true); await workingDir.create(recursive: true);
} }
// Initialize the Rust downloader // Create download directory if it doesn't exist
downloader_api.downloaderInit(downloadDir: state.downloadDir); 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); state = state.copyWith(isInitialized: true);
@ -97,6 +115,9 @@ class DownloadManager extends _$DownloadManager {
try { try {
final globalStat = await downloader_api.downloaderGetGlobalStats(); final globalStat = await downloader_api.downloaderGetGlobalStats();
state = state.copyWith(globalStat: globalStat); state = state.copyWith(globalStat: globalStat);
// Auto-remove completed tasks (no seeding behavior)
await removeCompletedTasks();
} catch (e) { } catch (e) {
dPrint("globalStat update error:$e"); dPrint("globalStat update error:$e");
} }
@ -177,6 +198,18 @@ class DownloadManager extends _$DownloadManager {
state = state.copyWith(isInitialized: false, globalStat: null); 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 /// Convert speed limit text to bytes per second
/// Supports formats like: "1", "100k", "10m", "0" /// Supports formats like: "1", "100k", "10m", "0"
int textToByte(String text) { int textToByte(String text) {
@ -195,4 +228,22 @@ class DownloadManager extends _$DownloadManager {
} }
return 0; 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 /// @nodoc
mixin _$DownloadManagerState { 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 /// Create a copy of DownloadManagerState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@ -25,16 +25,16 @@ $DownloadManagerStateCopyWith<DownloadManagerState> get copyWith => _$DownloadMa
@override @override
bool operator ==(Object other) { 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 @override
int get hashCode => Object.hash(runtimeType,downloadDir,isInitialized,globalStat); int get hashCode => Object.hash(runtimeType,workingDir,downloadDir,isInitialized,globalStat);
@override @override
String toString() { 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; factory $DownloadManagerStateCopyWith(DownloadManagerState value, $Res Function(DownloadManagerState) _then) = _$DownloadManagerStateCopyWithImpl;
@useResult @useResult
$Res call({ $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 /// Create a copy of DownloadManagerState
/// with the given fields replaced by the non-null parameter values. /// 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( 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 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 bool,globalStat: freezed == globalStat ? _self.globalStat : globalStat // ignore: cast_nullable_to_non_nullable
as downloader_api.DownloadGlobalStat?, 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) { switch (_that) {
case _DownloadManagerState() when $default != null: 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(); 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) { switch (_that) {
case _DownloadManagerState(): 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'); 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) { switch (_that) {
case _DownloadManagerState() when $default != null: 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; return null;
} }
@ -208,9 +209,10 @@ return $default(_that.downloadDir,_that.isInitialized,_that.globalStat);case _:
class _DownloadManagerState implements DownloadManagerState { 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 final String downloadDir;
@override@JsonKey() final bool isInitialized; @override@JsonKey() final bool isInitialized;
@override final downloader_api.DownloadGlobalStat? globalStat; @override final downloader_api.DownloadGlobalStat? globalStat;
@ -225,16 +227,16 @@ _$DownloadManagerStateCopyWith<_DownloadManagerState> get copyWith => __$Downloa
@override @override
bool operator ==(Object other) { 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 @override
int get hashCode => Object.hash(runtimeType,downloadDir,isInitialized,globalStat); int get hashCode => Object.hash(runtimeType,workingDir,downloadDir,isInitialized,globalStat);
@override @override
String toString() { 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; factory _$DownloadManagerStateCopyWith(_DownloadManagerState value, $Res Function(_DownloadManagerState) _then) = __$DownloadManagerStateCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $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 /// Create a copy of DownloadManagerState
/// with the given fields replaced by the non-null parameter values. /// 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( 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 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 bool,globalStat: freezed == globalStat ? _self.globalStat : globalStat // ignore: cast_nullable_to_non_nullable
as downloader_api.DownloadGlobalStat?, 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> { abstract class _$DownloadManager extends $Notifier<DownloadManagerState> {
DownloadManagerState build(); DownloadManagerState build();

View File

@ -267,14 +267,51 @@ class HomeDownloaderUIModel extends _$HomeDownloaderUIModel {
), ),
); );
if (ok == true) { if (ok == true) {
// Note: rqbit doesn't support dynamic speed limit changes yet // Save the settings
// Just save the settings for now
await box.put('downloader_up_limit', upCtrl.text.trim()); await box.put('downloader_up_limit', upCtrl.text.trim());
await box.put('downloader_down_limit', downCtrl.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) { if (context.mounted) {
showToast(context, "Speed limit settings saved. Will apply on next download."); final restartNow = await showDialog<bool>(
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");
}
}
}
} }
} }
} }

View File

@ -42,7 +42,7 @@ final class HomeDownloaderUIModelProvider
} }
String _$homeDownloaderUIModelHash() => String _$homeDownloaderUIModelHash() =>
r'27e2e4b7a5103eee9d489a347410131edef46be4'; r'3dd2ca0b1c03113d577c322de81078faa378230b';
abstract class _$HomeDownloaderUIModel abstract class _$HomeDownloaderUIModel
extends $Notifier<HomeDownloaderUIState> { extends $Notifier<HomeDownloaderUIState> {

22
rust/Cargo.lock generated
View File

@ -2994,7 +2994,7 @@ dependencies = [
[[package]] [[package]]
name = "librqbit" name = "librqbit"
version = "9.0.0-beta.1" 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 = [ dependencies = [
"anyhow", "anyhow",
"arc-swap", "arc-swap",
@ -3058,7 +3058,7 @@ dependencies = [
[[package]] [[package]]
name = "librqbit-bencode" name = "librqbit-bencode"
version = "3.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"arrayvec", "arrayvec",
@ -3074,7 +3074,7 @@ dependencies = [
[[package]] [[package]]
name = "librqbit-buffers" name = "librqbit-buffers"
version = "4.2.0" 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 = [ dependencies = [
"bytes", "bytes",
"librqbit-clone-to-owned", "librqbit-clone-to-owned",
@ -3085,7 +3085,7 @@ dependencies = [
[[package]] [[package]]
name = "librqbit-clone-to-owned" name = "librqbit-clone-to-owned"
version = "3.0.1" 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 = [ dependencies = [
"bytes", "bytes",
] ]
@ -3093,7 +3093,7 @@ dependencies = [
[[package]] [[package]]
name = "librqbit-core" name = "librqbit-core"
version = "5.0.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -3122,7 +3122,7 @@ dependencies = [
[[package]] [[package]]
name = "librqbit-dht" name = "librqbit-dht"
version = "5.3.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"backon", "backon",
@ -3169,7 +3169,7 @@ dependencies = [
[[package]] [[package]]
name = "librqbit-lsd" name = "librqbit-lsd"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"atoi", "atoi",
@ -3189,7 +3189,7 @@ dependencies = [
[[package]] [[package]]
name = "librqbit-peer-protocol" name = "librqbit-peer-protocol"
version = "4.3.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"bitvec", "bitvec",
@ -3209,7 +3209,7 @@ dependencies = [
[[package]] [[package]]
name = "librqbit-sha1-wrapper" name = "librqbit-sha1-wrapper"
version = "4.1.0" 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 = [ dependencies = [
"assert_cfg", "assert_cfg",
"aws-lc-rs", "aws-lc-rs",
@ -3218,7 +3218,7 @@ dependencies = [
[[package]] [[package]]
name = "librqbit-tracker-comms" name = "librqbit-tracker-comms"
version = "3.0.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-stream", "async-stream",
@ -3246,7 +3246,7 @@ dependencies = [
[[package]] [[package]]
name = "librqbit-upnp" name = "librqbit-upnp"
version = "1.0.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"bstr", "bstr",

View File

@ -35,7 +35,7 @@ unp4k_rs = { git = "https://github.com/StarCitizenToolBox/unp4k_rs", tag = "V0.0
uuid = { version = "1.19.0", features = ["v4"] } uuid = { version = "1.19.0", features = ["v4"] }
parking_lot = "0.12.5" parking_lot = "0.12.5"
crossbeam-channel = "0.5.15" 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 = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
bytes = "1.10" bytes = "1.10"

View File

@ -1,4 +1,5 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::num::NonZeroU32;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
@ -7,9 +8,11 @@ use bytes::Bytes;
use flutter_rust_bridge::frb; use flutter_rust_bridge::frb;
use librqbit::{ use librqbit::{
AddTorrent, AddTorrentOptions, AddTorrentResponse, Session, SessionOptions, 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 parking_lot::RwLock;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::sync::Mutex; use tokio::sync::Mutex;
@ -17,8 +20,9 @@ use tokio::sync::Mutex;
// Type alias for ManagedTorrentHandle // Type alias for ManagedTorrentHandle
type ManagedTorrentHandle = Arc<ManagedTorrent>; type ManagedTorrentHandle = Arc<ManagedTorrent>;
// Global session instance // Global session instance - using RwLock to allow restart
static SESSION: OnceCell<Arc<Session>> = OnceCell::new(); static SESSION: once_cell::sync::Lazy<RwLock<Option<Arc<Session>>>> =
once_cell::sync::Lazy::new(|| RwLock::new(None));
static SESSION_INIT_LOCK: once_cell::sync::Lazy<Mutex<()>> = static SESSION_INIT_LOCK: once_cell::sync::Lazy<Mutex<()>> =
once_cell::sync::Lazy::new(|| Mutex::new(())); once_cell::sync::Lazy::new(|| Mutex::new(()));
@ -31,7 +35,7 @@ static TASK_OUTPUT_FOLDERS: once_cell::sync::Lazy<RwLock<HashMap<usize, String>>
once_cell::sync::Lazy::new(|| RwLock::new(HashMap::new())); once_cell::sync::Lazy::new(|| RwLock::new(HashMap::new()));
/// Download task status /// Download task status
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum DownloadTaskStatus { pub enum DownloadTaskStatus {
Initializing, Initializing,
Live, Live,
@ -65,11 +69,22 @@ pub struct DownloadGlobalStat {
pub num_waiting: usize, 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)] #[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<u32>,
download_limit_bps: Option<u32>,
) -> Result<()> {
// Already initialized // Already initialized
if SESSION.get().is_some() { if SESSION.read().is_some() {
return Ok(()); return Ok(());
} }
@ -78,28 +93,52 @@ pub fn downloader_init(download_dir: String) -> Result<()> {
let _lock = SESSION_INIT_LOCK.lock().await; let _lock = SESSION_INIT_LOCK.lock().await;
// Double check after acquiring lock // Double check after acquiring lock
if SESSION.get().is_some() { if SESSION.read().is_some() {
return Ok(()); 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)?; 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( let session = Session::new_with_opts(
output_folder, output_folder,
SessionOptions { SessionOptions {
disable_dht: false, disable_dht: false,
disable_dht_persistence: true, disable_dht_persistence: false,
persistence: None, // 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() ..Default::default()
}, },
) )
.await .await
.context("Failed to create rqbit session")?; .context("Failed to create rqbit session")?;
SESSION *SESSION.write() = Some(session);
.set(session)
.map_err(|_| anyhow::anyhow!("Session already initialized"))?;
Ok(()) Ok(())
}) })
@ -108,7 +147,14 @@ pub fn downloader_init(download_dir: String) -> Result<()> {
/// Check if the downloader is initialized /// Check if the downloader is initialized
#[frb(sync)] #[frb(sync)]
pub fn downloader_is_initialized() -> bool { pub fn downloader_is_initialized() -> bool {
SESSION.get().is_some() SESSION.read().is_some()
}
/// Helper function to get session
fn get_session() -> Result<Arc<Session>> {
SESSION.read()
.clone()
.context("Downloader not initialized. Call downloader_init first.")
} }
/// Add a torrent from bytes (e.g., .torrent file content) /// Add a torrent from bytes (e.g., .torrent file content)
@ -117,9 +163,7 @@ pub async fn downloader_add_torrent(
output_folder: Option<String>, output_folder: Option<String>,
trackers: Option<Vec<String>>, trackers: Option<Vec<String>>,
) -> Result<usize> { ) -> Result<usize> {
let session = SESSION let session = get_session()?;
.get()
.context("Downloader not initialized. Call downloader_init first.")?;
let bytes = Bytes::from(torrent_bytes); let bytes = Bytes::from(torrent_bytes);
let add_torrent = AddTorrent::from_bytes(bytes); let add_torrent = AddTorrent::from_bytes(bytes);
@ -171,9 +215,7 @@ pub async fn downloader_add_magnet(
output_folder: Option<String>, output_folder: Option<String>,
trackers: Option<Vec<String>>, trackers: Option<Vec<String>>,
) -> Result<usize> { ) -> Result<usize> {
let session = SESSION let session = get_session()?;
.get()
.context("Downloader not initialized. Call downloader_init first.")?;
// Check if it's a magnet link // Check if it's a magnet link
if !magnet_link.starts_with("magnet:") { if !magnet_link.starts_with("magnet:") {
@ -260,9 +302,7 @@ pub async fn downloader_add_url(
/// Pause a download task /// Pause a download task
pub async fn downloader_pause(task_id: usize) -> Result<()> { pub async fn downloader_pause(task_id: usize) -> Result<()> {
let session = SESSION let session = get_session()?;
.get()
.context("Downloader not initialized")?;
let handle = { let handle = {
let handles = TORRENT_HANDLES.read(); let handles = TORRENT_HANDLES.read();
@ -279,9 +319,7 @@ pub async fn downloader_pause(task_id: usize) -> Result<()> {
/// Resume a download task /// Resume a download task
pub async fn downloader_resume(task_id: usize) -> Result<()> { pub async fn downloader_resume(task_id: usize) -> Result<()> {
let session = SESSION let session = get_session()?;
.get()
.context("Downloader not initialized")?;
let handle = { let handle = {
let handles = TORRENT_HANDLES.read(); let handles = TORRENT_HANDLES.read();
@ -298,9 +336,7 @@ pub async fn downloader_resume(task_id: usize) -> Result<()> {
/// Remove a download task /// Remove a download task
pub async fn downloader_remove(task_id: usize, delete_files: bool) -> Result<()> { pub async fn downloader_remove(task_id: usize, delete_files: bool) -> Result<()> {
let session = SESSION let session = get_session()?;
.get()
.context("Downloader not initialized")?;
session session
.delete(librqbit::api::TorrentIdOrHash::Id(task_id), delete_files) .delete(librqbit::api::TorrentIdOrHash::Id(task_id), delete_files)
@ -382,11 +418,12 @@ fn get_task_status(stats: &TorrentStats) -> DownloadTaskStatus {
/// Get all tasks /// Get all tasks
pub async fn downloader_get_all_tasks() -> Result<Vec<DownloadTaskInfo>> { pub async fn downloader_get_all_tasks() -> Result<Vec<DownloadTaskInfo>> {
let session = SESSION.get(); let session_guard = SESSION.read();
if session.is_none() { let session = match session_guard.as_ref() {
return Ok(vec![]); Some(s) => s.clone(),
} None => return Ok(vec![]),
let session = session.unwrap(); };
drop(session_guard);
// Use RwLock to collect tasks since with_torrents takes Fn (not FnMut) // Use RwLock to collect tasks since with_torrents takes Fn (not FnMut)
let tasks: RwLock<Vec<DownloadTaskInfo>> = RwLock::new(Vec::new()); let tasks: RwLock<Vec<DownloadTaskInfo>> = RwLock::new(Vec::new());
@ -474,9 +511,7 @@ pub async fn downloader_is_name_in_task(name: String) -> bool {
/// Pause all tasks /// Pause all tasks
pub async fn downloader_pause_all() -> Result<()> { pub async fn downloader_pause_all() -> Result<()> {
let session = SESSION let session = get_session()?;
.get()
.context("Downloader not initialized")?;
let handles: Vec<_> = TORRENT_HANDLES.read().values().cloned().collect(); let handles: Vec<_> = TORRENT_HANDLES.read().values().cloned().collect();
@ -489,9 +524,7 @@ pub async fn downloader_pause_all() -> Result<()> {
/// Resume all tasks /// Resume all tasks
pub async fn downloader_resume_all() -> Result<()> { pub async fn downloader_resume_all() -> Result<()> {
let session = SESSION let session = get_session()?;
.get()
.context("Downloader not initialized")?;
let handles: Vec<_> = TORRENT_HANDLES.read().values().cloned().collect(); let handles: Vec<_> = TORRENT_HANDLES.read().values().cloned().collect();
@ -502,12 +535,80 @@ pub async fn downloader_resume_all() -> Result<()> {
Ok(()) Ok(())
} }
/// Stop the downloader session /// Stop the downloader session (pauses all tasks but keeps session)
pub async fn downloader_stop() -> Result<()> { 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; session.stop().await;
} }
TORRENT_HANDLES.write().clear(); TORRENT_HANDLES.write().clear();
TASK_OUTPUT_FOLDERS.write().clear(); TASK_OUTPUT_FOLDERS.write().clear();
Ok(()) 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<u32>,
_download_limit_bps: Option<u32>,
) -> 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<u32> {
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
}

View File

@ -37,7 +37,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
default_rust_auto_opaque = RustAutoOpaqueNom, default_rust_auto_opaque = RustAutoOpaqueNom,
); );
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.11.1"; 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 // 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::DcoCodec, _, _, _>(
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( fn wire__crate__api__downloader_api__downloader_init_impl(
download_dir: impl CstDecode<String>, working_dir: impl CstDecode<String>,
default_download_dir: impl CstDecode<String>,
upload_limit_bps: impl CstDecode<Option<u32>>,
download_limit_bps: impl CstDecode<Option<u32>>,
) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::<flutter_rust_bridge::for_generated::DcoCodec, _>( FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::<flutter_rust_bridge::for_generated::DcoCodec, _>(
flutter_rust_bridge::for_generated::TaskInfo { 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, mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync,
}, },
move || { 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>( transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>(
(move || { (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) 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::DcoCodec, _, _, _>(
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( fn wire__crate__api__downloader_api__downloader_resume_impl(
port_: flutter_rust_bridge::for_generated::MessagePort, port_: flutter_rust_bridge::for_generated::MessagePort,
task_id: impl CstDecode<usize>, task_id: impl CstDecode<usize>,
@ -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::DcoCodec, _, _, _>(
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( fn wire__crate__api__downloader_api__downloader_stop_impl(
port_: flutter_rust_bridge::for_generated::MessagePort, 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<Option<u32>>,
_download_limit_bps: impl CstDecode<Option<u32>>,
) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::DcoCodec, _, _, _>(
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( fn wire__crate__api__http_api__fetch_impl(
port_: flutter_rust_bridge::for_generated::MessagePort, port_: flutter_rust_bridge::for_generated::MessagePort,
method: impl CstDecode<crate::api::http_api::MyMethod>, method: impl CstDecode<crate::api::http_api::MyMethod>,
@ -2152,6 +2262,17 @@ impl SseDecode for Option<bool> {
} }
} }
impl SseDecode for Option<u32> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
if (<bool>::sse_decode(deserializer)) {
return Some(<u32>::sse_decode(deserializer));
} else {
return None;
}
}
}
impl SseDecode for Option<u64> { impl SseDecode for Option<u64> {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
@ -3081,6 +3202,16 @@ impl SseEncode for Option<bool> {
} }
} }
impl SseEncode for Option<u32> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
<bool>::sse_encode(self.is_some(), serializer);
if let Some(value) = self {
<u32>::sse_encode(value, serializer);
}
}
}
impl SseEncode for Option<u64> { impl SseEncode for Option<u64> {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
@ -3374,6 +3505,12 @@ mod io {
CstDecode::<crate::api::asar_api::RsiLauncherAsarData>::cst_decode(*wrap).into() CstDecode::<crate::api::asar_api::RsiLauncherAsarData>::cst_decode(*wrap).into()
} }
} }
impl CstDecode<u32> 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<u64> for *mut u64 { impl CstDecode<u64> for *mut u64 {
// Codec=Cst (C-struct based), see doc to use other codecs // Codec=Cst (C-struct based), see doc to use other codecs
fn cst_decode(self) -> u64 { fn cst_decode(self) -> u64 {
@ -3947,11 +4084,26 @@ mod io {
wire__crate__api__downloader_api__downloader_get_task_info_impl(port_, task_id) 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)] #[unsafe(no_mangle)]
pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__downloader_api__downloader_init( 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 { ) -> 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)] #[unsafe(no_mangle)]
@ -3992,6 +4144,13 @@ mod io {
wire__crate__api__downloader_api__downloader_remove_impl(port_, task_id, delete_files) 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)] #[unsafe(no_mangle)]
pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__downloader_api__downloader_resume( pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__downloader_api__downloader_resume(
port_: i64, port_: i64,
@ -4007,6 +4166,13 @@ mod io {
wire__crate__api__downloader_api__downloader_resume_all_impl(port_) 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)] #[unsafe(no_mangle)]
pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__downloader_api__downloader_stop( pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__downloader_api__downloader_stop(
port_: i64, port_: i64,
@ -4014,6 +4180,19 @@ mod io {
wire__crate__api__downloader_api__downloader_stop_impl(port_) 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)] #[unsafe(no_mangle)]
pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__http_api__fetch( pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__http_api__fetch(
port_: i64, 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)] #[unsafe(no_mangle)]
pub extern "C" fn frbgen_starcitizen_doctor_cst_new_box_autoadd_u_64(value: u64) -> *mut u64 { 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) flutter_rust_bridge::for_generated::new_leak_box_ptr(value)