feat: use rust rqbit to replace aria2c

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

View File

@@ -2,6 +2,7 @@ import 'package:fluent_ui/fluent_ui.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'package:file_sizes/file_sizes.dart';
import 'package:starcitizen_doctor/common/rust/api/downloader_api.dart';
import 'home_downloader_ui_model.dart';
@@ -13,59 +14,51 @@ class HomeDownloaderUI extends HookConsumerWidget {
final state = ref.watch(homeDownloaderUIModelProvider);
final model = ref.read(homeDownloaderUIModelProvider.notifier);
return makeDefaultPage(context,
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 12),
Row(
children: [
const Spacer(),
const SizedBox(width: 24),
const SizedBox(width: 12),
for (final item in <MapEntry<String, IconData>, String>{
const MapEntry("settings", FluentIcons.settings):
S.current.downloader_speed_limit_settings,
if (state.tasks.isNotEmpty)
const MapEntry("pause_all", FluentIcons.pause):
S.current.downloader_action_pause_all,
if (state.waitingTasks.isNotEmpty)
const MapEntry("resume_all", FluentIcons.download):
S.current.downloader_action_resume_all,
if (state.tasks.isNotEmpty || state.waitingTasks.isNotEmpty)
const MapEntry("cancel_all", FluentIcons.cancel):
S.current.downloader_action_cancel_all,
}.entries)
Padding(
padding: const EdgeInsets.only(left: 6, right: 6),
child: Button(
child: Padding(
padding: const EdgeInsets.all(4),
child: Row(
children: [
Icon(item.key.value),
const SizedBox(width: 6),
Text(item.value),
],
),
),
onPressed: () =>
model.onTapButton(context, item.key.key)),
return makeDefaultPage(
context,
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 12),
Row(
children: [
const Spacer(),
const SizedBox(width: 24),
const SizedBox(width: 12),
for (final item in <MapEntry<String, IconData>, String>{
const MapEntry("settings", FluentIcons.settings): S.current.downloader_speed_limit_settings,
if (state.activeTasks.isNotEmpty)
const MapEntry("pause_all", FluentIcons.pause): S.current.downloader_action_pause_all,
if (state.waitingTasks.isNotEmpty)
const MapEntry("resume_all", FluentIcons.download): S.current.downloader_action_resume_all,
if (state.activeTasks.isNotEmpty || state.waitingTasks.isNotEmpty)
const MapEntry("cancel_all", FluentIcons.cancel): S.current.downloader_action_cancel_all,
}.entries)
Padding(
padding: const EdgeInsets.only(left: 6, right: 6),
child: Button(
child: Padding(
padding: const EdgeInsets.all(4),
child: Row(children: [Icon(item.key.value), const SizedBox(width: 6), Text(item.value)]),
),
onPressed: () => model.onTapButton(context, item.key.key),
),
const SizedBox(width: 12),
],
),
if (model.getTasksLen() == 0)
Expanded(
child: Center(
child: Text(S.current.downloader_info_no_download_tasks),
))
else
Expanded(
child: ListView.builder(
),
const SizedBox(width: 12),
],
),
if (model.getTasksLen() == 0)
Expanded(child: Center(child: Text(S.current.downloader_info_no_download_tasks)))
else
Expanded(
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
final (task, type, isFirstType) = model.getTaskAndType(index);
final nt = HomeDownloaderUIModel.getTaskTypeAndName(task);
final statusStr = model.getStatusString(task.status);
final isActive = task.status == DownloadTaskStatus.live;
final isPaused = task.status == DownloadTaskStatus.paused;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -73,39 +66,30 @@ class HomeDownloaderUI extends HookConsumerWidget {
Column(
children: [
Container(
padding: EdgeInsets.only(
left: 24,
right: 24,
top: index == 0 ? 0 : 12,
bottom: 12),
padding: EdgeInsets.only(left: 24, right: 24, top: index == 0 ? 0 : 12, bottom: 12),
margin: const EdgeInsets.only(top: 6, bottom: 6),
child: Row(
children: [
Expanded(
child: Row(
children: [
Text(
"${model.listHeaderStatusMap[type]}",
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold),
),
],
)),
child: Row(
children: [
Text(
"${model.listHeaderStatusMap[type]}",
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
],
),
),
],
),
),
],
),
Container(
padding: const EdgeInsets.only(
left: 12, right: 12, top: 12, bottom: 12),
margin: const EdgeInsets.only(
left: 12, right: 12, top: 6, bottom: 6),
padding: const EdgeInsets.only(left: 12, right: 12, top: 12, bottom: 12),
margin: const EdgeInsets.only(left: 12, right: 12, top: 6, bottom: 6),
decoration: BoxDecoration(
color: FluentTheme.of(context)
.cardColor
.withValues(alpha: .06),
color: FluentTheme.of(context).cardColor.withValues(alpha: .06),
borderRadius: BorderRadius.circular(7),
),
child: Row(
@@ -113,47 +97,26 @@ class HomeDownloaderUI extends HookConsumerWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
nt.value,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold),
),
Text(nt.value, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 6),
Row(
children: [
Text(
S.current.downloader_info_total_size(
FileSize.getSize(
task.totalLength ?? 0)),
S.current.downloader_info_total_size(FileSize.getSize(task.totalBytes.toInt())),
style: const TextStyle(fontSize: 14),
),
const SizedBox(width: 12),
if (nt.key == "torrent" &&
task.verifiedLength != null &&
task.verifiedLength != 0)
if (isActive)
Text(
S.current.downloader_info_verifying(
FileSize.getSize(
task.verifiedLength)),
style: const TextStyle(fontSize: 14),
S.current.downloader_info_downloading((task.progress * 100).toStringAsFixed(2)),
)
else if (task.status == "active")
Text(S.current
.downloader_info_downloading(
((task.completedLength ?? 0) *
100 /
(task.totalLength ?? 1))
.toStringAsFixed(4)))
else
Text(S.current.downloader_info_status(
model.statusMap[task.status] ??
"Unknown")),
Text(S.current.downloader_info_status(model.statusMap[statusStr] ?? "Unknown")),
const SizedBox(width: 24),
if (task.status == "active" &&
task.verifiedLength == null)
if (isActive)
Text(
"ETA: ${model.formatter.format(DateTime.now().add(Duration(seconds: model.getETA(task))))}"),
"ETA: ${model.formatter.format(DateTime.now().add(Duration(seconds: model.getETA(task))))}",
),
],
),
],
@@ -162,20 +125,18 @@ class HomeDownloaderUI extends HookConsumerWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(S.current.downloader_info_uploaded(
FileSize.getSize(task.uploadLength))),
Text(S.current.downloader_info_downloaded(
FileSize.getSize(task.completedLength))),
Text(S.current.downloader_info_uploaded(FileSize.getSize(task.uploadedBytes.toInt()))),
Text(
S.current.downloader_info_downloaded(FileSize.getSize(task.downloadedBytes.toInt())),
),
],
),
const SizedBox(width: 18),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${FileSize.getSize(task.uploadSpeed)}/s"),
Text(
"↓:${FileSize.getSize(task.downloadSpeed)}/s"),
Text("↑:${FileSize.getSize(task.uploadSpeed.toInt())}/s"),
Text("${FileSize.getSize(task.downloadSpeed.toInt())}/s"),
],
),
const SizedBox(width: 32),
@@ -184,42 +145,32 @@ class HomeDownloaderUI extends HookConsumerWidget {
closeAfterClick: false,
title: Padding(
padding: const EdgeInsets.all(3),
child:
Text(S.current.downloader_action_options),
child: Text(S.current.downloader_action_options),
),
items: [
if (task.status == "paused")
if (isPaused)
MenuFlyoutItem(
leading:
const Icon(FluentIcons.download),
text: Text(S.current
.downloader_action_continue_download),
onPressed: () =>
model.resumeTask(task.gid))
else if (task.status == "active")
leading: const Icon(FluentIcons.download),
text: Text(S.current.downloader_action_continue_download),
onPressed: () => model.resumeTask(task.id.toInt()),
)
else if (isActive)
MenuFlyoutItem(
leading: const Icon(FluentIcons.pause),
text: Text(S.current
.downloader_action_pause_download),
onPressed: () =>
model.pauseTask(task.gid)),
leading: const Icon(FluentIcons.pause),
text: Text(S.current.downloader_action_pause_download),
onPressed: () => model.pauseTask(task.id.toInt()),
),
const MenuFlyoutSeparator(),
MenuFlyoutItem(
leading: const Icon(
FluentIcons.chrome_close,
size: 14,
),
text: Text(S.current
.downloader_action_cancel_download),
onPressed: () =>
model.cancelTask(context, task.gid)),
leading: const Icon(FluentIcons.chrome_close, size: 14),
text: Text(S.current.downloader_action_cancel_download),
onPressed: () => model.cancelTask(context, task.id.toInt()),
),
MenuFlyoutItem(
leading: const Icon(
FluentIcons.folder_open,
size: 14,
),
text: Text(S.current.action_open_folder),
onPressed: () => model.openFolder(task)),
leading: const Icon(FluentIcons.folder_open, size: 14),
text: Text(S.current.action_open_folder),
onPressed: () => model.openFolder(task),
),
],
),
const SizedBox(width: 12),
@@ -230,31 +181,36 @@ class HomeDownloaderUI extends HookConsumerWidget {
);
},
itemCount: model.getTasksLen(),
)),
Container(
color: FluentTheme.of(context).cardColor.withValues(alpha: .06),
child: Padding(
padding: const EdgeInsets.only(left: 12, bottom: 3, top: 3),
child: Row(
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: state.isAvailable ? Colors.green : Colors.white,
borderRadius: BorderRadius.circular(1000),
),
),
const SizedBox(width: 12),
Text(S.current.downloader_info_download_upload_speed(
FileSize.getSize(state.globalStat?.downloadSpeed ?? 0),
FileSize.getSize(state.globalStat?.uploadSpeed ?? 0)))
],
),
),
),
],
),
useBodyContainer: true);
Container(
color: FluentTheme.of(context).cardColor.withValues(alpha: .06),
child: Padding(
padding: const EdgeInsets.only(left: 12, bottom: 3, top: 3),
child: Row(
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: state.isAvailable ? Colors.green : Colors.white,
borderRadius: BorderRadius.circular(1000),
),
),
const SizedBox(width: 12),
Text(
S.current.downloader_info_download_upload_speed(
FileSize.getSize((state.globalStat?.downloadSpeed ?? BigInt.zero).toInt()),
FileSize.getSize((state.globalStat?.uploadSpeed ?? BigInt.zero).toInt()),
),
),
],
),
),
),
],
),
useBodyContainer: true,
);
}
}

View File

@@ -1,7 +1,5 @@
// ignore_for_file: avoid_build_context_in_providers, avoid_public_notifier_properties
import 'dart:io';
import 'package:aria2/aria2.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/services.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@@ -9,9 +7,9 @@ import 'package:hive_ce/hive.dart';
import 'package:intl/intl.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
import 'package:starcitizen_doctor/common/rust/api/downloader_api.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/common/utils/provider.dart';
import 'package:starcitizen_doctor/provider/aria2c.dart';
import 'package:starcitizen_doctor/provider/download_manager.dart';
import '../../../widgets/widgets.dart';
@@ -22,10 +20,10 @@ part 'home_downloader_ui_model.freezed.dart';
@freezed
abstract class HomeDownloaderUIState with _$HomeDownloaderUIState {
factory HomeDownloaderUIState({
@Default([]) List<Aria2Task> tasks,
@Default([]) List<Aria2Task> waitingTasks,
@Default([]) List<Aria2Task> stoppedTasks,
Aria2GlobalStat? globalStat,
@Default([]) List<DownloadTaskInfo> activeTasks,
@Default([]) List<DownloadTaskInfo> waitingTasks,
@Default([]) List<DownloadTaskInfo> stoppedTasks,
DownloadGlobalStat? globalStat,
}) = _HomeDownloaderUIState;
}
@@ -40,12 +38,11 @@ class HomeDownloaderUIModel extends _$HomeDownloaderUIModel {
bool _disposed = false;
final statusMap = {
"active": S.current.downloader_info_downloading_status,
"waiting": S.current.downloader_info_waiting,
"live": S.current.downloader_info_downloading_status,
"initializing": S.current.downloader_info_waiting,
"paused": S.current.downloader_info_paused,
"error": S.current.downloader_info_download_failed,
"complete": S.current.downloader_info_download_completed,
"removed": S.current.downloader_info_deleted,
"finished": S.current.downloader_info_download_completed,
};
final listHeaderStatusMap = {
@@ -65,17 +62,17 @@ class HomeDownloaderUIModel extends _$HomeDownloaderUIModel {
}
Future<void> onTapButton(BuildContext context, String key) async {
final aria2cState = ref.read(aria2cModelProvider);
final downloadManagerState = ref.read(downloadManagerProvider);
final downloadManager = ref.read(downloadManagerProvider.notifier);
switch (key) {
case "pause_all":
if (!aria2cState.isRunning) return;
await aria2cState.aria2c?.pauseAll();
await aria2cState.aria2c?.saveSession();
if (!downloadManagerState.isRunning) return;
await downloadManager.pauseAll();
return;
case "resume_all":
if (!aria2cState.isRunning) return;
await aria2cState.aria2c?.unpauseAll();
await aria2cState.aria2c?.saveSession();
if (!downloadManagerState.isRunning) return;
await downloadManager.resumeAll();
return;
case "cancel_all":
final userOK = await showConfirmDialogs(
@@ -84,12 +81,12 @@ class HomeDownloaderUIModel extends _$HomeDownloaderUIModel {
Text(S.current.downloader_info_manual_file_deletion_note),
);
if (userOK == true) {
if (!aria2cState.isRunning) return;
if (!downloadManagerState.isRunning) return;
try {
for (var value in [...state.tasks, ...state.waitingTasks]) {
await aria2cState.aria2c?.remove(value.gid!);
final allTasks = [...state.activeTasks, ...state.waitingTasks];
for (var task in allTasks) {
await downloadManager.removeTask(task.id.toInt(), deleteFiles: false);
}
await aria2cState.aria2c?.saveSession();
} catch (e) {
dPrint("DownloadsUIModel cancel_all Error: $e");
}
@@ -102,91 +99,77 @@ class HomeDownloaderUIModel extends _$HomeDownloaderUIModel {
}
int getTasksLen() {
return state.tasks.length + state.waitingTasks.length + state.stoppedTasks.length;
return state.activeTasks.length + state.waitingTasks.length + state.stoppedTasks.length;
}
(Aria2Task, String, bool) getTaskAndType(int index) {
final tempList = <Aria2Task>[...state.tasks, ...state.waitingTasks, ...state.stoppedTasks];
if (index >= 0 && index < state.tasks.length) {
(DownloadTaskInfo, String, bool) getTaskAndType(int index) {
final tempList = <DownloadTaskInfo>[...state.activeTasks, ...state.waitingTasks, ...state.stoppedTasks];
if (index >= 0 && index < state.activeTasks.length) {
return (tempList[index], "active", index == 0);
}
if (index >= state.tasks.length && index < state.tasks.length + state.waitingTasks.length) {
return (tempList[index], "waiting", index == state.tasks.length);
if (index >= state.activeTasks.length && index < state.activeTasks.length + state.waitingTasks.length) {
return (tempList[index], "waiting", index == state.activeTasks.length);
}
if (index >= state.tasks.length + state.waitingTasks.length && index < tempList.length) {
return (tempList[index], "stopped", index == state.tasks.length + state.waitingTasks.length);
if (index >= state.activeTasks.length + state.waitingTasks.length && index < tempList.length) {
return (tempList[index], "stopped", index == state.activeTasks.length + state.waitingTasks.length);
}
throw Exception("Index out of range or element is null");
}
static MapEntry<String, String> getTaskTypeAndName(Aria2Task task) {
if (task.bittorrent == null) {
String uri = task.files?[0]['uris'][0]['uri'] as String;
return MapEntry("url", uri.split('/').last);
} else if (task.bittorrent != null) {
if (task.bittorrent!.containsKey('info')) {
var btName = task.bittorrent?["info"]["name"];
return MapEntry("torrent", btName ?? 'torrent');
} else {
return MapEntry("magnet", '[METADATA]${task.infoHash}');
}
} else {
return const MapEntry("metaLink", '==========metaLink============');
static MapEntry<String, String> getTaskTypeAndName(DownloadTaskInfo task) {
// All tasks in rqbit are torrent-based
return MapEntry("torrent", task.name);
}
int getETA(DownloadTaskInfo task) {
if (task.downloadSpeed == BigInt.zero) return 0;
final remainingBytes = task.totalBytes - task.downloadedBytes;
return (remainingBytes ~/ task.downloadSpeed).toInt();
}
String getStatusString(DownloadTaskStatus status) {
switch (status) {
case DownloadTaskStatus.live:
return "live";
case DownloadTaskStatus.initializing:
return "initializing";
case DownloadTaskStatus.paused:
return "paused";
case DownloadTaskStatus.error:
return "error";
case DownloadTaskStatus.finished:
return "finished";
}
}
int getETA(Aria2Task task) {
if (task.downloadSpeed == null || task.downloadSpeed == 0) return 0;
final remainingBytes = (task.totalLength ?? 0) - (task.completedLength ?? 0);
return remainingBytes ~/ (task.downloadSpeed!);
Future<void> resumeTask(int taskId) async {
final downloadManager = ref.read(downloadManagerProvider.notifier);
await downloadManager.resumeTask(taskId);
}
Future<void> resumeTask(String? gid) async {
final aria2c = ref.read(aria2cModelProvider).aria2c;
if (gid != null) {
await aria2c?.unpause(gid);
}
Future<void> pauseTask(int taskId) async {
final downloadManager = ref.read(downloadManagerProvider.notifier);
await downloadManager.pauseTask(taskId);
}
Future<void> pauseTask(String? gid) async {
final aria2c = ref.read(aria2cModelProvider).aria2c;
if (gid != null) {
await aria2c?.pause(gid);
}
}
Future<void> cancelTask(BuildContext context, String? gid) async {
Future<void> cancelTask(BuildContext context, int taskId) async {
await Future.delayed(const Duration(milliseconds: 300));
if (gid != null) {
if (!context.mounted) return;
final ok = await showConfirmDialogs(
context,
S.current.downloader_action_confirm_cancel_download,
Text(S.current.downloader_info_manual_file_deletion_note),
);
if (ok == true) {
final aria2c = ref.read(aria2cModelProvider).aria2c;
await aria2c?.remove(gid);
await aria2c?.saveSession();
}
if (!context.mounted) return;
final ok = await showConfirmDialogs(
context,
S.current.downloader_action_confirm_cancel_download,
Text(S.current.downloader_info_manual_file_deletion_note),
);
if (ok == true) {
final downloadManager = ref.read(downloadManagerProvider.notifier);
await downloadManager.removeTask(taskId, deleteFiles: false);
}
}
List<Aria2File> getFilesFormTask(Aria2Task task) {
List<Aria2File> l = [];
if (task.files != null) {
for (var element in task.files!) {
final f = Aria2File.fromJson(element);
l.add(f);
}
}
return l;
}
void openFolder(Aria2Task task) {
final f = getFilesFormTask(task).firstOrNull;
if (f != null) {
SystemHelper.openDir(File(f.path!).absolute.path.replaceAll("/", "\\"));
void openFolder(DownloadTaskInfo task) {
final outputFolder = task.outputFolder;
if (outputFolder.isNotEmpty) {
SystemHelper.openDir(outputFolder.replaceAll("/", "\\"));
}
}
@@ -194,21 +177,39 @@ class HomeDownloaderUIModel extends _$HomeDownloaderUIModel {
try {
while (true) {
if (_disposed) return;
final aria2cState = ref.read(aria2cModelProvider);
if (aria2cState.isRunning) {
final aria2c = aria2cState.aria2c!;
final tasks = await aria2c.tellActive();
final waitingTasks = await aria2c.tellWaiting(0, 1000000);
final stoppedTasks = await aria2c.tellStopped(0, 1000000);
final globalStat = await aria2c.getGlobalStat();
final downloadManagerState = ref.read(downloadManagerProvider);
if (downloadManagerState.isRunning) {
final downloadManager = ref.read(downloadManagerProvider.notifier);
final allTasks = await downloadManager.getAllTasks();
final activeTasks = <DownloadTaskInfo>[];
final waitingTasks = <DownloadTaskInfo>[];
final stoppedTasks = <DownloadTaskInfo>[];
for (var task in allTasks) {
switch (task.status) {
case DownloadTaskStatus.live:
activeTasks.add(task);
break;
case DownloadTaskStatus.initializing:
case DownloadTaskStatus.paused:
waitingTasks.add(task);
break;
case DownloadTaskStatus.finished:
case DownloadTaskStatus.error:
stoppedTasks.add(task);
break;
}
}
state = state.copyWith(
tasks: tasks,
activeTasks: activeTasks,
waitingTasks: waitingTasks,
stoppedTasks: stoppedTasks,
globalStat: globalStat,
globalStat: downloadManagerState.globalStat,
);
} else {
state = state.copyWith(tasks: [], waitingTasks: [], stoppedTasks: [], globalStat: null);
state = state.copyWith(activeTasks: [], waitingTasks: [], stoppedTasks: [], globalStat: null);
}
await Future.delayed(const Duration(seconds: 1));
}
@@ -266,22 +267,14 @@ class HomeDownloaderUIModel extends _$HomeDownloaderUIModel {
),
);
if (ok == true) {
final aria2cState = ref.read(aria2cModelProvider);
final aria2cModel = ref.read(aria2cModelProvider.notifier);
await aria2cModel.launchDaemon(appGlobalState.applicationBinaryModuleDir!);
final aria2c = aria2cState.aria2c!;
final upByte = aria2cModel.textToByte(upCtrl.text.trim());
final downByte = aria2cModel.textToByte(downCtrl.text.trim());
final r = await aria2c
.changeGlobalOption(
Aria2Option()
..maxOverallUploadLimit = upByte
..maxOverallDownloadLimit = downByte,
)
.unwrap();
if (r != null) {
await box.put('downloader_up_limit', upCtrl.text.trim());
await box.put('downloader_down_limit', downCtrl.text.trim());
// Note: rqbit doesn't support dynamic speed limit changes yet
// Just save the settings for now
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
if (context.mounted) {
showToast(context, "Speed limit settings saved. Will apply on next download.");
}
}
}

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$HomeDownloaderUIState {
List<Aria2Task> get tasks; List<Aria2Task> get waitingTasks; List<Aria2Task> get stoppedTasks; Aria2GlobalStat? get globalStat;
List<DownloadTaskInfo> get activeTasks; List<DownloadTaskInfo> get waitingTasks; List<DownloadTaskInfo> get stoppedTasks; DownloadGlobalStat? get globalStat;
/// Create a copy of HomeDownloaderUIState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $HomeDownloaderUIStateCopyWith<HomeDownloaderUIState> get copyWith => _$HomeDown
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is HomeDownloaderUIState&&const DeepCollectionEquality().equals(other.tasks, tasks)&&const DeepCollectionEquality().equals(other.waitingTasks, waitingTasks)&&const DeepCollectionEquality().equals(other.stoppedTasks, stoppedTasks)&&(identical(other.globalStat, globalStat) || other.globalStat == globalStat));
return identical(this, other) || (other.runtimeType == runtimeType&&other is HomeDownloaderUIState&&const DeepCollectionEquality().equals(other.activeTasks, activeTasks)&&const DeepCollectionEquality().equals(other.waitingTasks, waitingTasks)&&const DeepCollectionEquality().equals(other.stoppedTasks, stoppedTasks)&&(identical(other.globalStat, globalStat) || other.globalStat == globalStat));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(tasks),const DeepCollectionEquality().hash(waitingTasks),const DeepCollectionEquality().hash(stoppedTasks),globalStat);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(activeTasks),const DeepCollectionEquality().hash(waitingTasks),const DeepCollectionEquality().hash(stoppedTasks),globalStat);
@override
String toString() {
return 'HomeDownloaderUIState(tasks: $tasks, waitingTasks: $waitingTasks, stoppedTasks: $stoppedTasks, globalStat: $globalStat)';
return 'HomeDownloaderUIState(activeTasks: $activeTasks, waitingTasks: $waitingTasks, stoppedTasks: $stoppedTasks, globalStat: $globalStat)';
}
@@ -45,7 +45,7 @@ abstract mixin class $HomeDownloaderUIStateCopyWith<$Res> {
factory $HomeDownloaderUIStateCopyWith(HomeDownloaderUIState value, $Res Function(HomeDownloaderUIState) _then) = _$HomeDownloaderUIStateCopyWithImpl;
@useResult
$Res call({
List<Aria2Task> tasks, List<Aria2Task> waitingTasks, List<Aria2Task> stoppedTasks, Aria2GlobalStat? globalStat
List<DownloadTaskInfo> activeTasks, List<DownloadTaskInfo> waitingTasks, List<DownloadTaskInfo> stoppedTasks, DownloadGlobalStat? globalStat
});
@@ -62,13 +62,13 @@ class _$HomeDownloaderUIStateCopyWithImpl<$Res>
/// Create a copy of HomeDownloaderUIState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? tasks = null,Object? waitingTasks = null,Object? stoppedTasks = null,Object? globalStat = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? activeTasks = null,Object? waitingTasks = null,Object? stoppedTasks = null,Object? globalStat = freezed,}) {
return _then(_self.copyWith(
tasks: null == tasks ? _self.tasks : tasks // ignore: cast_nullable_to_non_nullable
as List<Aria2Task>,waitingTasks: null == waitingTasks ? _self.waitingTasks : waitingTasks // ignore: cast_nullable_to_non_nullable
as List<Aria2Task>,stoppedTasks: null == stoppedTasks ? _self.stoppedTasks : stoppedTasks // ignore: cast_nullable_to_non_nullable
as List<Aria2Task>,globalStat: freezed == globalStat ? _self.globalStat : globalStat // ignore: cast_nullable_to_non_nullable
as Aria2GlobalStat?,
activeTasks: null == activeTasks ? _self.activeTasks : activeTasks // ignore: cast_nullable_to_non_nullable
as List<DownloadTaskInfo>,waitingTasks: null == waitingTasks ? _self.waitingTasks : waitingTasks // ignore: cast_nullable_to_non_nullable
as List<DownloadTaskInfo>,stoppedTasks: null == stoppedTasks ? _self.stoppedTasks : stoppedTasks // ignore: cast_nullable_to_non_nullable
as List<DownloadTaskInfo>,globalStat: freezed == globalStat ? _self.globalStat : globalStat // ignore: cast_nullable_to_non_nullable
as DownloadGlobalStat?,
));
}
@@ -153,10 +153,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<Aria2Task> tasks, List<Aria2Task> waitingTasks, List<Aria2Task> stoppedTasks, Aria2GlobalStat? globalStat)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<DownloadTaskInfo> activeTasks, List<DownloadTaskInfo> waitingTasks, List<DownloadTaskInfo> stoppedTasks, DownloadGlobalStat? globalStat)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _HomeDownloaderUIState() when $default != null:
return $default(_that.tasks,_that.waitingTasks,_that.stoppedTasks,_that.globalStat);case _:
return $default(_that.activeTasks,_that.waitingTasks,_that.stoppedTasks,_that.globalStat);case _:
return orElse();
}
@@ -174,10 +174,10 @@ return $default(_that.tasks,_that.waitingTasks,_that.stoppedTasks,_that.globalSt
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<Aria2Task> tasks, List<Aria2Task> waitingTasks, List<Aria2Task> stoppedTasks, Aria2GlobalStat? globalStat) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<DownloadTaskInfo> activeTasks, List<DownloadTaskInfo> waitingTasks, List<DownloadTaskInfo> stoppedTasks, DownloadGlobalStat? globalStat) $default,) {final _that = this;
switch (_that) {
case _HomeDownloaderUIState():
return $default(_that.tasks,_that.waitingTasks,_that.stoppedTasks,_that.globalStat);case _:
return $default(_that.activeTasks,_that.waitingTasks,_that.stoppedTasks,_that.globalStat);case _:
throw StateError('Unexpected subclass');
}
@@ -194,10 +194,10 @@ return $default(_that.tasks,_that.waitingTasks,_that.stoppedTasks,_that.globalSt
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<Aria2Task> tasks, List<Aria2Task> waitingTasks, List<Aria2Task> stoppedTasks, Aria2GlobalStat? globalStat)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<DownloadTaskInfo> activeTasks, List<DownloadTaskInfo> waitingTasks, List<DownloadTaskInfo> stoppedTasks, DownloadGlobalStat? globalStat)? $default,) {final _that = this;
switch (_that) {
case _HomeDownloaderUIState() when $default != null:
return $default(_that.tasks,_that.waitingTasks,_that.stoppedTasks,_that.globalStat);case _:
return $default(_that.activeTasks,_that.waitingTasks,_that.stoppedTasks,_that.globalStat);case _:
return null;
}
@@ -209,31 +209,31 @@ return $default(_that.tasks,_that.waitingTasks,_that.stoppedTasks,_that.globalSt
class _HomeDownloaderUIState implements HomeDownloaderUIState {
_HomeDownloaderUIState({final List<Aria2Task> tasks = const [], final List<Aria2Task> waitingTasks = const [], final List<Aria2Task> stoppedTasks = const [], this.globalStat}): _tasks = tasks,_waitingTasks = waitingTasks,_stoppedTasks = stoppedTasks;
_HomeDownloaderUIState({final List<DownloadTaskInfo> activeTasks = const [], final List<DownloadTaskInfo> waitingTasks = const [], final List<DownloadTaskInfo> stoppedTasks = const [], this.globalStat}): _activeTasks = activeTasks,_waitingTasks = waitingTasks,_stoppedTasks = stoppedTasks;
final List<Aria2Task> _tasks;
@override@JsonKey() List<Aria2Task> get tasks {
if (_tasks is EqualUnmodifiableListView) return _tasks;
final List<DownloadTaskInfo> _activeTasks;
@override@JsonKey() List<DownloadTaskInfo> get activeTasks {
if (_activeTasks is EqualUnmodifiableListView) return _activeTasks;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_tasks);
return EqualUnmodifiableListView(_activeTasks);
}
final List<Aria2Task> _waitingTasks;
@override@JsonKey() List<Aria2Task> get waitingTasks {
final List<DownloadTaskInfo> _waitingTasks;
@override@JsonKey() List<DownloadTaskInfo> get waitingTasks {
if (_waitingTasks is EqualUnmodifiableListView) return _waitingTasks;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_waitingTasks);
}
final List<Aria2Task> _stoppedTasks;
@override@JsonKey() List<Aria2Task> get stoppedTasks {
final List<DownloadTaskInfo> _stoppedTasks;
@override@JsonKey() List<DownloadTaskInfo> get stoppedTasks {
if (_stoppedTasks is EqualUnmodifiableListView) return _stoppedTasks;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_stoppedTasks);
}
@override final Aria2GlobalStat? globalStat;
@override final DownloadGlobalStat? globalStat;
/// Create a copy of HomeDownloaderUIState
/// with the given fields replaced by the non-null parameter values.
@@ -245,16 +245,16 @@ _$HomeDownloaderUIStateCopyWith<_HomeDownloaderUIState> get copyWith => __$HomeD
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _HomeDownloaderUIState&&const DeepCollectionEquality().equals(other._tasks, _tasks)&&const DeepCollectionEquality().equals(other._waitingTasks, _waitingTasks)&&const DeepCollectionEquality().equals(other._stoppedTasks, _stoppedTasks)&&(identical(other.globalStat, globalStat) || other.globalStat == globalStat));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _HomeDownloaderUIState&&const DeepCollectionEquality().equals(other._activeTasks, _activeTasks)&&const DeepCollectionEquality().equals(other._waitingTasks, _waitingTasks)&&const DeepCollectionEquality().equals(other._stoppedTasks, _stoppedTasks)&&(identical(other.globalStat, globalStat) || other.globalStat == globalStat));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_tasks),const DeepCollectionEquality().hash(_waitingTasks),const DeepCollectionEquality().hash(_stoppedTasks),globalStat);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_activeTasks),const DeepCollectionEquality().hash(_waitingTasks),const DeepCollectionEquality().hash(_stoppedTasks),globalStat);
@override
String toString() {
return 'HomeDownloaderUIState(tasks: $tasks, waitingTasks: $waitingTasks, stoppedTasks: $stoppedTasks, globalStat: $globalStat)';
return 'HomeDownloaderUIState(activeTasks: $activeTasks, waitingTasks: $waitingTasks, stoppedTasks: $stoppedTasks, globalStat: $globalStat)';
}
@@ -265,7 +265,7 @@ abstract mixin class _$HomeDownloaderUIStateCopyWith<$Res> implements $HomeDownl
factory _$HomeDownloaderUIStateCopyWith(_HomeDownloaderUIState value, $Res Function(_HomeDownloaderUIState) _then) = __$HomeDownloaderUIStateCopyWithImpl;
@override @useResult
$Res call({
List<Aria2Task> tasks, List<Aria2Task> waitingTasks, List<Aria2Task> stoppedTasks, Aria2GlobalStat? globalStat
List<DownloadTaskInfo> activeTasks, List<DownloadTaskInfo> waitingTasks, List<DownloadTaskInfo> stoppedTasks, DownloadGlobalStat? globalStat
});
@@ -282,13 +282,13 @@ class __$HomeDownloaderUIStateCopyWithImpl<$Res>
/// Create a copy of HomeDownloaderUIState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? tasks = null,Object? waitingTasks = null,Object? stoppedTasks = null,Object? globalStat = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? activeTasks = null,Object? waitingTasks = null,Object? stoppedTasks = null,Object? globalStat = freezed,}) {
return _then(_HomeDownloaderUIState(
tasks: null == tasks ? _self._tasks : tasks // ignore: cast_nullable_to_non_nullable
as List<Aria2Task>,waitingTasks: null == waitingTasks ? _self._waitingTasks : waitingTasks // ignore: cast_nullable_to_non_nullable
as List<Aria2Task>,stoppedTasks: null == stoppedTasks ? _self._stoppedTasks : stoppedTasks // ignore: cast_nullable_to_non_nullable
as List<Aria2Task>,globalStat: freezed == globalStat ? _self.globalStat : globalStat // ignore: cast_nullable_to_non_nullable
as Aria2GlobalStat?,
activeTasks: null == activeTasks ? _self._activeTasks : activeTasks // ignore: cast_nullable_to_non_nullable
as List<DownloadTaskInfo>,waitingTasks: null == waitingTasks ? _self._waitingTasks : waitingTasks // ignore: cast_nullable_to_non_nullable
as List<DownloadTaskInfo>,stoppedTasks: null == stoppedTasks ? _self._stoppedTasks : stoppedTasks // ignore: cast_nullable_to_non_nullable
as List<DownloadTaskInfo>,globalStat: freezed == globalStat ? _self.globalStat : globalStat // ignore: cast_nullable_to_non_nullable
as DownloadGlobalStat?,
));
}

View File

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

View File

@@ -1,6 +1,5 @@
// ignore_for_file: avoid_build_context_in_providers, use_build_context_synchronously
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/services.dart';
@@ -14,7 +13,7 @@ import 'package:starcitizen_doctor/common/utils/async.dart';
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/common/utils/provider.dart';
import 'package:starcitizen_doctor/provider/aria2c.dart';
import 'package:starcitizen_doctor/provider/download_manager.dart';
import 'package:starcitizen_doctor/ui/home/localization/localization_ui_model.dart';
import 'package:starcitizen_doctor/common/rust/api/ort_api.dart' as ort;
@@ -240,11 +239,10 @@ class InputMethodDialogUIModel extends _$InputMethodDialogUIModel {
Future<String> doDownloadTranslateModel() async {
state = state.copyWith(isAutoTranslateWorking: true);
try {
final aria2cManager = ref.read(aria2cModelProvider.notifier);
await aria2cManager.launchDaemon(appGlobalState.applicationBinaryModuleDir!);
final aria2c = ref.read(aria2cModelProvider).aria2c!;
final downloadManager = ref.read(downloadManagerProvider.notifier);
await downloadManager.initDownloader();
if (await aria2cManager.isNameInTask(_localTranslateModelName)) {
if (await downloadManager.isNameInTask(_localTranslateModelName)) {
throw Exception("Model is already downloading");
}
@@ -259,9 +257,8 @@ class InputMethodDialogUIModel extends _$InputMethodDialogUIModel {
}
// get torrent Data
final data = await RSHttp.get(torrentUrl!);
final b64Str = base64Encode(data.data!);
final gid = await aria2c.addTorrent(b64Str, extraParams: {"dir": _localTranslateModelDir});
return gid;
final taskId = await downloadManager.addTorrent(data.data!, outputFolder: _localTranslateModelDir);
return taskId.toString();
} catch (e) {
dPrint("[InputMethodDialogUIModel] doDownloadTranslateModel error: $e");
rethrow;
@@ -330,8 +327,8 @@ class InputMethodDialogUIModel extends _$InputMethodDialogUIModel {
}
Future<bool> isTranslateModelDownloading() async {
final aria2cManager = ref.read(aria2cModelProvider.notifier);
return await aria2cManager.isNameInTask(_localTranslateModelName);
final downloadManager = ref.read(downloadManagerProvider.notifier);
return await downloadManager.isNameInTask(_localTranslateModelName);
}
}

View File

@@ -43,7 +43,7 @@ final class InputMethodDialogUIModelProvider
}
String _$inputMethodDialogUIModelHash() =>
r'bd96c85ef2073d80de6eba71748b41adb8861e1c';
r'5c2989faf94d43bb814e5b80e10d68416c8241ec';
abstract class _$InputMethodDialogUIModel
extends $Notifier<InputMethodDialogUIState> {