diff --git a/lib/common/io/aria2c.dart b/lib/common/io/aria2c.dart new file mode 100644 index 0000000..715f348 --- /dev/null +++ b/lib/common/io/aria2c.dart @@ -0,0 +1,90 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:aria2/aria2.dart'; +import 'package:flutter/foundation.dart'; +import 'package:starcitizen_doctor/base/ui.dart'; +import 'package:starcitizen_doctor/common/conf/app_conf.dart'; +import 'package:starcitizen_doctor/common/helper/system_helper.dart'; + +class Aria2cManager { + static bool _isDaemonRunning = false; + + static final String _aria2cDir = + "${AppConf.applicationSupportDir}\\modules\\aria2c"; + + static Aria2c? _aria2c; + + static Aria2c get aria2c { + if (!_isDaemonRunning) throw Exception("Aria2c Daemon not running!"); + if (_aria2c == null) { + _aria2c = Aria2c( + "ws://127.0.0.1:64664/jsonrpc", "websocket", "ScToolbox_64664"); + _aria2c!.getVersion().then((value) { + dPrint("Aria2cManager.connected! version == ${value.version}"); + }); + } + return _aria2c!; + } + + static Future launchDaemon() async { + if (_isDaemonRunning) return; + + /// skip for debug hot reload + if (kDebugMode) { + if ((await SystemHelper.getPID("aria2c")).isNotEmpty) { + dPrint("[Aria2cManager] debug skip for hot reload"); + _isDaemonRunning = true; + return; + } + } + + final sessionFile = File("$_aria2cDir\\aria2.session"); + if (!await sessionFile.exists()) { + await sessionFile.create(recursive: true); + } + + final exePath = "$_aria2cDir\\aria2c.exe"; + dPrint("Aria2cManager .----- aria2c start ------"); + final p = await Process.start( + exePath, + [ + "-V", + "-c", + "-x 10", + "--dir=$_aria2cDir\\downloads", + "--disable-ipv6", + "--enable-rpc", + "--pause", + "--rpc-listen-port=64664", + "--rpc-secret=ScToolbox_64664", + "--input-file=${sessionFile.absolute.path.trim()}", + "--save-session=${sessionFile.absolute.path.trim()}", + "--save-session-interval=60", + "--file-allocation=trunc", + ], + workingDirectory: _aria2cDir, + runInShell: false); + p.stdout.transform(utf8.decoder).listen((event) { + if (event.trim().isEmpty) return; + dPrint("[aria2c]: $event"); + if (event.contains("IPv4 RPC: listening on TCP port")) { + _isDaemonRunning = true; + aria2c; + } + }, onDone: () { + dPrint("[aria2c] onDone: "); + _isDaemonRunning = false; + }, onError: (e) { + dPrint("[aria2c] stdout ERROR: $e"); + _isDaemonRunning = false; + }); + p.stderr.transform(utf8.decoder).listen((event) { + dPrint("[aria2c] stderr ERROR : $event"); + }); + while (true) { + if (_isDaemonRunning) return; + await Future.delayed(const Duration(milliseconds: 100)); + } + } +} diff --git a/lib/ui/index_ui.dart b/lib/ui/index_ui.dart index 518d921..13af9ca 100644 --- a/lib/ui/index_ui.dart +++ b/lib/ui/index_ui.dart @@ -41,9 +41,45 @@ class IndexUI extends BaseUI { ), ); }(), - actions: const Row( + actions: Row( mainAxisAlignment: MainAxisAlignment.end, - children: [WindowButtons()], + children: [ + IconButton( + icon: Stack( + children: [ + Padding( + padding: const EdgeInsets.all(6), + child: Icon( + FluentIcons.installation, + size: 22, + color: Colors.white.withOpacity(.6), + ), + ), + if (model.aria2TotalTaskNum != 0) + Positioned( + bottom: 0, + right: 0, + child: Container( + decoration: BoxDecoration( + color: Colors.red, + borderRadius: BorderRadius.circular(12), + ), + padding: const EdgeInsets.only( + left: 6, right: 6, bottom: 1.5, top: 1.5), + child: Text( + "${model.aria2TotalTaskNum}", + style: const TextStyle( + fontSize: 8, + color: Colors.white, + ), + ), + )) + ], + ), + onPressed: model.goDownloader), + const SizedBox(width: 24), + const WindowButtons() + ], )), pane: NavigationPane( selected: model.curIndex, diff --git a/lib/ui/index_ui_model.dart b/lib/ui/index_ui_model.dart index c9a4186..61c34d5 100644 --- a/lib/ui/index_ui_model.dart +++ b/lib/ui/index_ui_model.dart @@ -1,8 +1,10 @@ import 'dart:io'; +import 'package:aria2/models/aria2GlobalStat.dart'; import 'package:starcitizen_doctor/api/analytics.dart'; import 'package:starcitizen_doctor/base/ui_model.dart'; import 'package:starcitizen_doctor/common/helper/system_helper.dart'; +import 'package:starcitizen_doctor/common/io/aria2c.dart'; import 'package:starcitizen_doctor/global_ui_model.dart'; import 'package:starcitizen_doctor/ui/about/about_ui_model.dart'; import 'package:starcitizen_doctor/ui/home/home_ui_model.dart'; @@ -15,9 +17,17 @@ import 'party_room/party_room_home_ui_model.dart'; class IndexUIModel extends BaseUIModel { int curIndex = 0; + Aria2GlobalStat? aria2globalStat; + + int get aria2TotalTaskNum => aria2globalStat == null + ? 0 + : ((aria2globalStat!.numActive ?? 0) + + (aria2globalStat!.numWaiting ?? 0)); + @override void initModel() { - _checkRunTime(); + _checkRuntime(); + _listenAria2c(); Future.delayed(const Duration(milliseconds: 300)) .then((value) => globalUIModel.doCheckUpdate(context!)); super.initModel(); @@ -66,7 +76,7 @@ class IndexUIModel extends BaseUIModel { notifyListeners(); } - Future _checkRunTime() async { + Future _checkRuntime() async { Future onError() async { await showToast(context!, "运行环境出错,请检查系统环境变量 (PATH)!"); await launchUrlString( @@ -87,4 +97,20 @@ class IndexUIModel extends BaseUIModel { onError(); } } + + void goDownloader() { + + } + + void _listenAria2c() async { + while (true) { + try { + aria2globalStat = await Aria2cManager.aria2c.getGlobalStat(); + notifyListeners(); + } catch (e) { + dPrint("aria2globalStat update error:$e"); + } + await Future.delayed(const Duration(seconds: 10)); + } + } } diff --git a/lib/ui/splash_ui_model.dart b/lib/ui/splash_ui_model.dart index a3869be..7311f0c 100644 --- a/lib/ui/splash_ui_model.dart +++ b/lib/ui/splash_ui_model.dart @@ -2,6 +2,7 @@ import 'package:starcitizen_doctor/api/analytics.dart'; import 'package:starcitizen_doctor/base/ui_model.dart'; import 'package:starcitizen_doctor/common/conf/binary_conf.dart'; import 'package:starcitizen_doctor/common/conf/url_conf.dart'; +import 'package:starcitizen_doctor/common/io/aria2c.dart'; import 'package:starcitizen_doctor/ui/index_ui.dart'; import 'package:starcitizen_doctor/ui/index_ui_model.dart'; @@ -29,7 +30,7 @@ class SplashUIModel extends BaseUIModel { step = 2; notifyListeners(); await handleError(() => BinaryModuleConf.extractModel()); - Future.delayed(const Duration(milliseconds: 300)); + await handleError(() => Aria2cManager.launchDaemon()); Navigator.pushAndRemoveUntil( context!, BaseUIContainer( diff --git a/lib/ui/tools/tools_ui_model.dart b/lib/ui/tools/tools_ui_model.dart index 5313b2b..d7f4a92 100644 --- a/lib/ui/tools/tools_ui_model.dart +++ b/lib/ui/tools/tools_ui_model.dart @@ -1,11 +1,14 @@ import 'dart:convert'; import 'dart:io'; +import 'package:file_picker/file_picker.dart'; import 'package:flutter/foundation.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:starcitizen_doctor/base/ui_model.dart'; +import 'package:starcitizen_doctor/common/conf/app_conf.dart'; import 'package:starcitizen_doctor/common/helper/log_helper.dart'; import 'package:starcitizen_doctor/common/helper/system_helper.dart'; +import 'package:starcitizen_doctor/common/io/aria2c.dart'; import 'package:xml/xml.dart'; class ToolsUIModel extends BaseUIModel { @@ -334,76 +337,53 @@ class ToolsUIModel extends BaseUIModel { } Future _downloadP4k() async { - // String savePath = scInstalledPath; - // String fileName = "Data.p4k"; - // bool isResumeDownload = false; - // final box = await Hive.openBox("p4k_cache"); - // var downloadUrl = AppConf.networkVersionData?.p4kDownloadUrl; - // if (downloadUrl == null || downloadUrl.isEmpty) { - // showToast(context!, "该功能维护中,请稍后再试!"); - // return; - // } - // if ((await SystemHelper.getPID("\"RSI Launcher\"")).isNotEmpty) { - // showToast(context!, "RSI启动器正在运行,请先关闭启动器再使用此功能!", - // constraints: BoxConstraints( - // maxWidth: MediaQuery.of(context!).size.width * .35)); - // return; - // } - // final lastSavePath = (box.get("last_save_dir", defaultValue: {}) as Map); - // dPrint("lastSavePath === $lastSavePath"); - // if (lastSavePath.isNotEmpty) { - // final s = lastSavePath["save_path"] ?? ""; - // final f = lastSavePath["file_name"] ?? ""; - // if ((await File("$s/$f.downloading").exists()) && - // (await File("$s/$f.downloading.bson").exists())) { - // final ok = await showConfirmDialogs(context!, "是否恢复下载?", - // const Text("检测到未完成的下载,点击确认即可恢复下载,点击取消将会删除之前的临时文件,并开始一个新的下载。")); - // if (ok) { - // savePath = s; - // fileName = f; - // isResumeDownload = true; - // } else { - // // del last cache and del file - // await box.delete("last_save_dir"); - // await File("$s/$f.downloading").delete(); - // await File("$s/$f.downloading.bson").delete(); - // } - // } else { - // // del last cache - // await box.delete("last_save_dir"); - // } - // } else { - // await showToast( - // context!, - // "P4k 是星际公民的核心游戏文件,高达近 100GB,盒子提供的离线下载是为了帮助一些p4k文件下载超级慢的用户。" - // "\n\n接下来会弹窗询问您保存位置(可以选择星际公民文件夹也可以选择别处),下载完成后请确保 P4K 文件夹位于 LIVE 文件夹内,之后使用星际公民启动器校验更新即可。"); - // AnalyticsApi.touch("p4k_download"); - // } - // final r = await showDialog( - // context: context!, - // dismissWithEsc: false, - // builder: (context) { - // return BaseUIContainer( - // uiCreate: () => DownloaderDialogUI(), - // modelCreate: () => DownloaderDialogUIModel( - // fileName, savePath, downloadUrl, - // showChangeSavePathDialog: !isResumeDownload, - // threadCount: 10, - // isP4kDownload: true)); - // }); - // - // if (r != null) { - // if (r == "cancel") { - // return showToast(context!, "下载进度已保留,您可以再次点击此功能恢复下载。"); - // } else { - // final ok = await showConfirmDialogs( - // context!, "下载完毕!", Text("文件已保存到:$r\n\n是否查看P4K操作教程?")); - // if (ok == true) { - // launchUrlString( - // "https://citizenwiki.cn/SC%E6%B1%89%E5%8C%96%E7%9B%92%E5%AD%90#%E5%88%86%E6%B5%81%E4%B8%8B%E8%BD%BD%E6%95%99%E7%A8%8B"); - // } - // } - // } + String savePath = scInstalledPath; + String fileName = "Data.p4k"; + + var downloadUrl = AppConf.networkVersionData?.p4kDownloadUrl; + if (downloadUrl == null || downloadUrl.isEmpty) { + showToast(context!, "该功能维护中,请稍后再试!"); + return; + } + if ((await SystemHelper.getPID("\"RSI Launcher\"")).isNotEmpty) { + showToast(context!, "RSI启动器正在运行!请先关闭启动器再使用此功能!", + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context!).size.width * .35)); + return; + } + + await showToast( + context!, + "P4k 是星际公民的核心游戏文件,高达 100GB+,盒子提供的离线下载是为了帮助一些p4k文件下载超级慢的用户。" + "\n\n接下来会弹窗询问您保存位置(可以选择星际公民文件夹也可以选择别处),下载完成后请确保 P4K 文件夹位于 LIVE 文件夹内,之后使用星际公民启动器校验更新即可。"); + // AnalyticsApi.touch("p4k_download"); + + final userSelect = await FilePicker.platform.saveFile( + initialDirectory: savePath, fileName: fileName, lockParentWindow: true); + if (userSelect == null) { + Navigator.pop(context!); + return; + } + final f = File(userSelect); + if (await f.exists()) { + await f.delete(); + } + savePath = userSelect; + dPrint(savePath); + notifyListeners(); + if (savePath.endsWith("\\$fileName")) { + savePath = savePath.substring(0, savePath.length - fileName.length - 1); + } + + final gid = await Aria2cManager.aria2c.addUri( + ["https://release.scbox.org/data_3.22.0A-LIVE.9035564.p4k.torrent"], + extraParams: {"dir": savePath}); + + dPrint("Aria2cManager.aria2c.addUri resp === $gid"); + + showToast(context!, "添加下载任务成功!请留意盒子右上角的下载管理器。"); + + // launchUrlString("https://citizenwiki.cn/SC%E6%B1%89%E5%8C%96%E7%9B%92%E5%AD%90#%E5%88%86%E6%B5%81%E4%B8%8B%E8%BD%BD%E6%95%99%E7%A8%8B"); } Future _checkPhotographyStatus({bool? setMode}) async { diff --git a/pubspec.yaml b/pubspec.yaml index a93b01d..3362edb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -72,6 +72,8 @@ dependencies: grpc: ^3.2.4 rust_builder: path: rust_builder + aria2: + path: ../../xkeyC/dart_aria2_rpc dependency_overrides: http: ^1.1.2