diff --git a/lib/api/analytics.dart b/lib/api/analytics.dart index a97fded..44906ef 100644 --- a/lib/api/analytics.dart +++ b/lib/api/analytics.dart @@ -1,4 +1,5 @@ import 'package:dio/dio.dart'; +import 'package:flutter/foundation.dart'; import 'package:starcitizen_doctor/common/conf.dart'; import 'package:starcitizen_doctor/common/utils/base_utils.dart'; @@ -6,6 +7,8 @@ class AnalyticsApi { static final Dio _dio = Dio(); static touch(String key) async { + // debug 不统计 + if (kDebugMode) return; dPrint("AnalyticsApi.touch === $key start"); try { await _dio.post("${AppConf.xkeycApiUrl}/analytics/$key"); diff --git a/lib/common/conf.dart b/lib/common/conf.dart index 52a5d36..c4893b7 100644 --- a/lib/common/conf.dart +++ b/lib/common/conf.dart @@ -6,6 +6,7 @@ import 'package:hive/hive.dart'; import 'package:path_provider/path_provider.dart'; import 'package:starcitizen_doctor/api/analytics.dart'; import 'package:starcitizen_doctor/api/api.dart'; +import 'package:starcitizen_doctor/common/rust/ffi.dart'; import 'package:starcitizen_doctor/data/app_version_data.dart'; import 'package:uuid/uuid.dart'; import 'package:window_manager/window_manager.dart'; @@ -65,6 +66,13 @@ class AppConf { exit(1); } + /// check Rust bridge + if (await rustFii.ping() != "PONG") { + dPrint("Rust bridge Error"); + exit(1); + } + dPrint("---- rust bridge inited -----"); + /// init windows await windowManager.ensureInitialized(); windowManager.waitUntilReadyToShow().then((_) async { diff --git a/lib/common/rust/bridge_definitions.dart b/lib/common/rust/bridge_definitions.dart index d5ba786..3022740 100644 --- a/lib/common/rust/bridge_definitions.dart +++ b/lib/common/rust/bridge_definitions.dart @@ -7,28 +7,61 @@ import 'dart:async'; import 'package:meta/meta.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge.dart'; import 'package:uuid/uuid.dart'; +import 'package:freezed_annotation/freezed_annotation.dart' hide protected; + +part 'bridge_definitions.freezed.dart'; abstract class Rust { - Future platform({dynamic hint}); + Future ping({dynamic hint}); - FlutterRustBridgeTaskConstMeta get kPlatformConstMeta; + FlutterRustBridgeTaskConstMeta get kPingConstMeta; - Future add({required int left, required int right, dynamic hint}); + Stream startDownload( + {required String url, + required String savePath, + required String fileName, + required int connectionCount, + dynamic hint}); - FlutterRustBridgeTaskConstMeta get kAddConstMeta; + FlutterRustBridgeTaskConstMeta get kStartDownloadConstMeta; - Future rustReleaseMode({dynamic hint}); + Future cancelDownload({required String id, dynamic hint}); - FlutterRustBridgeTaskConstMeta get kRustReleaseModeConstMeta; + FlutterRustBridgeTaskConstMeta get kCancelDownloadConstMeta; } -enum Platform { - Unknown, - Android, - Ios, - Windows, - Unix, - MacIntel, - MacApple, - Wasm, +class DownloadCallbackData { + final String id; + final int total; + final int progress; + final int speed; + final MyDownloaderStatus status; + + const DownloadCallbackData({ + required this.id, + required this.total, + required this.progress, + required this.speed, + required this.status, + }); +} + +@freezed +sealed class MyDownloaderStatus with _$MyDownloaderStatus { + const factory MyDownloaderStatus.noStart() = MyDownloaderStatus_NoStart; + const factory MyDownloaderStatus.running() = MyDownloaderStatus_Running; + const factory MyDownloaderStatus.pending( + MyNetworkItemPendingType field0, + ) = MyDownloaderStatus_Pending; + const factory MyDownloaderStatus.error( + String field0, + ) = MyDownloaderStatus_Error; + const factory MyDownloaderStatus.finished() = MyDownloaderStatus_Finished; +} + +enum MyNetworkItemPendingType { + QueueUp, + Starting, + Stopping, + Initializing, } diff --git a/lib/common/rust/bridge_definitions.freezed.dart b/lib/common/rust/bridge_definitions.freezed.dart new file mode 100644 index 0000000..b47427b --- /dev/null +++ b/lib/common/rust/bridge_definitions.freezed.dart @@ -0,0 +1,778 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'bridge_definitions.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$MyDownloaderStatus { + @optionalTypeArgs + TResult when({ + required TResult Function() noStart, + required TResult Function() running, + required TResult Function(MyNetworkItemPendingType field0) pending, + required TResult Function(String field0) error, + required TResult Function() finished, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? noStart, + TResult? Function()? running, + TResult? Function(MyNetworkItemPendingType field0)? pending, + TResult? Function(String field0)? error, + TResult? Function()? finished, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? noStart, + TResult Function()? running, + TResult Function(MyNetworkItemPendingType field0)? pending, + TResult Function(String field0)? error, + TResult Function()? finished, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(MyDownloaderStatus_NoStart value) noStart, + required TResult Function(MyDownloaderStatus_Running value) running, + required TResult Function(MyDownloaderStatus_Pending value) pending, + required TResult Function(MyDownloaderStatus_Error value) error, + required TResult Function(MyDownloaderStatus_Finished value) finished, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(MyDownloaderStatus_NoStart value)? noStart, + TResult? Function(MyDownloaderStatus_Running value)? running, + TResult? Function(MyDownloaderStatus_Pending value)? pending, + TResult? Function(MyDownloaderStatus_Error value)? error, + TResult? Function(MyDownloaderStatus_Finished value)? finished, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(MyDownloaderStatus_NoStart value)? noStart, + TResult Function(MyDownloaderStatus_Running value)? running, + TResult Function(MyDownloaderStatus_Pending value)? pending, + TResult Function(MyDownloaderStatus_Error value)? error, + TResult Function(MyDownloaderStatus_Finished value)? finished, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $MyDownloaderStatusCopyWith<$Res> { + factory $MyDownloaderStatusCopyWith( + MyDownloaderStatus value, $Res Function(MyDownloaderStatus) then) = + _$MyDownloaderStatusCopyWithImpl<$Res, MyDownloaderStatus>; +} + +/// @nodoc +class _$MyDownloaderStatusCopyWithImpl<$Res, $Val extends MyDownloaderStatus> + implements $MyDownloaderStatusCopyWith<$Res> { + _$MyDownloaderStatusCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; +} + +/// @nodoc +abstract class _$$MyDownloaderStatus_NoStartImplCopyWith<$Res> { + factory _$$MyDownloaderStatus_NoStartImplCopyWith( + _$MyDownloaderStatus_NoStartImpl value, + $Res Function(_$MyDownloaderStatus_NoStartImpl) then) = + __$$MyDownloaderStatus_NoStartImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$MyDownloaderStatus_NoStartImplCopyWithImpl<$Res> + extends _$MyDownloaderStatusCopyWithImpl<$Res, + _$MyDownloaderStatus_NoStartImpl> + implements _$$MyDownloaderStatus_NoStartImplCopyWith<$Res> { + __$$MyDownloaderStatus_NoStartImplCopyWithImpl( + _$MyDownloaderStatus_NoStartImpl _value, + $Res Function(_$MyDownloaderStatus_NoStartImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$MyDownloaderStatus_NoStartImpl implements MyDownloaderStatus_NoStart { + const _$MyDownloaderStatus_NoStartImpl(); + + @override + String toString() { + return 'MyDownloaderStatus.noStart()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$MyDownloaderStatus_NoStartImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() noStart, + required TResult Function() running, + required TResult Function(MyNetworkItemPendingType field0) pending, + required TResult Function(String field0) error, + required TResult Function() finished, + }) { + return noStart(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? noStart, + TResult? Function()? running, + TResult? Function(MyNetworkItemPendingType field0)? pending, + TResult? Function(String field0)? error, + TResult? Function()? finished, + }) { + return noStart?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? noStart, + TResult Function()? running, + TResult Function(MyNetworkItemPendingType field0)? pending, + TResult Function(String field0)? error, + TResult Function()? finished, + required TResult orElse(), + }) { + if (noStart != null) { + return noStart(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(MyDownloaderStatus_NoStart value) noStart, + required TResult Function(MyDownloaderStatus_Running value) running, + required TResult Function(MyDownloaderStatus_Pending value) pending, + required TResult Function(MyDownloaderStatus_Error value) error, + required TResult Function(MyDownloaderStatus_Finished value) finished, + }) { + return noStart(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(MyDownloaderStatus_NoStart value)? noStart, + TResult? Function(MyDownloaderStatus_Running value)? running, + TResult? Function(MyDownloaderStatus_Pending value)? pending, + TResult? Function(MyDownloaderStatus_Error value)? error, + TResult? Function(MyDownloaderStatus_Finished value)? finished, + }) { + return noStart?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(MyDownloaderStatus_NoStart value)? noStart, + TResult Function(MyDownloaderStatus_Running value)? running, + TResult Function(MyDownloaderStatus_Pending value)? pending, + TResult Function(MyDownloaderStatus_Error value)? error, + TResult Function(MyDownloaderStatus_Finished value)? finished, + required TResult orElse(), + }) { + if (noStart != null) { + return noStart(this); + } + return orElse(); + } +} + +abstract class MyDownloaderStatus_NoStart implements MyDownloaderStatus { + const factory MyDownloaderStatus_NoStart() = _$MyDownloaderStatus_NoStartImpl; +} + +/// @nodoc +abstract class _$$MyDownloaderStatus_RunningImplCopyWith<$Res> { + factory _$$MyDownloaderStatus_RunningImplCopyWith( + _$MyDownloaderStatus_RunningImpl value, + $Res Function(_$MyDownloaderStatus_RunningImpl) then) = + __$$MyDownloaderStatus_RunningImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$MyDownloaderStatus_RunningImplCopyWithImpl<$Res> + extends _$MyDownloaderStatusCopyWithImpl<$Res, + _$MyDownloaderStatus_RunningImpl> + implements _$$MyDownloaderStatus_RunningImplCopyWith<$Res> { + __$$MyDownloaderStatus_RunningImplCopyWithImpl( + _$MyDownloaderStatus_RunningImpl _value, + $Res Function(_$MyDownloaderStatus_RunningImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$MyDownloaderStatus_RunningImpl implements MyDownloaderStatus_Running { + const _$MyDownloaderStatus_RunningImpl(); + + @override + String toString() { + return 'MyDownloaderStatus.running()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$MyDownloaderStatus_RunningImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() noStart, + required TResult Function() running, + required TResult Function(MyNetworkItemPendingType field0) pending, + required TResult Function(String field0) error, + required TResult Function() finished, + }) { + return running(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? noStart, + TResult? Function()? running, + TResult? Function(MyNetworkItemPendingType field0)? pending, + TResult? Function(String field0)? error, + TResult? Function()? finished, + }) { + return running?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? noStart, + TResult Function()? running, + TResult Function(MyNetworkItemPendingType field0)? pending, + TResult Function(String field0)? error, + TResult Function()? finished, + required TResult orElse(), + }) { + if (running != null) { + return running(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(MyDownloaderStatus_NoStart value) noStart, + required TResult Function(MyDownloaderStatus_Running value) running, + required TResult Function(MyDownloaderStatus_Pending value) pending, + required TResult Function(MyDownloaderStatus_Error value) error, + required TResult Function(MyDownloaderStatus_Finished value) finished, + }) { + return running(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(MyDownloaderStatus_NoStart value)? noStart, + TResult? Function(MyDownloaderStatus_Running value)? running, + TResult? Function(MyDownloaderStatus_Pending value)? pending, + TResult? Function(MyDownloaderStatus_Error value)? error, + TResult? Function(MyDownloaderStatus_Finished value)? finished, + }) { + return running?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(MyDownloaderStatus_NoStart value)? noStart, + TResult Function(MyDownloaderStatus_Running value)? running, + TResult Function(MyDownloaderStatus_Pending value)? pending, + TResult Function(MyDownloaderStatus_Error value)? error, + TResult Function(MyDownloaderStatus_Finished value)? finished, + required TResult orElse(), + }) { + if (running != null) { + return running(this); + } + return orElse(); + } +} + +abstract class MyDownloaderStatus_Running implements MyDownloaderStatus { + const factory MyDownloaderStatus_Running() = _$MyDownloaderStatus_RunningImpl; +} + +/// @nodoc +abstract class _$$MyDownloaderStatus_PendingImplCopyWith<$Res> { + factory _$$MyDownloaderStatus_PendingImplCopyWith( + _$MyDownloaderStatus_PendingImpl value, + $Res Function(_$MyDownloaderStatus_PendingImpl) then) = + __$$MyDownloaderStatus_PendingImplCopyWithImpl<$Res>; + @useResult + $Res call({MyNetworkItemPendingType field0}); +} + +/// @nodoc +class __$$MyDownloaderStatus_PendingImplCopyWithImpl<$Res> + extends _$MyDownloaderStatusCopyWithImpl<$Res, + _$MyDownloaderStatus_PendingImpl> + implements _$$MyDownloaderStatus_PendingImplCopyWith<$Res> { + __$$MyDownloaderStatus_PendingImplCopyWithImpl( + _$MyDownloaderStatus_PendingImpl _value, + $Res Function(_$MyDownloaderStatus_PendingImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? field0 = null, + }) { + return _then(_$MyDownloaderStatus_PendingImpl( + null == field0 + ? _value.field0 + : field0 // ignore: cast_nullable_to_non_nullable + as MyNetworkItemPendingType, + )); + } +} + +/// @nodoc + +class _$MyDownloaderStatus_PendingImpl implements MyDownloaderStatus_Pending { + const _$MyDownloaderStatus_PendingImpl(this.field0); + + @override + final MyNetworkItemPendingType field0; + + @override + String toString() { + return 'MyDownloaderStatus.pending(field0: $field0)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$MyDownloaderStatus_PendingImpl && + (identical(other.field0, field0) || other.field0 == field0)); + } + + @override + int get hashCode => Object.hash(runtimeType, field0); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$MyDownloaderStatus_PendingImplCopyWith<_$MyDownloaderStatus_PendingImpl> + get copyWith => __$$MyDownloaderStatus_PendingImplCopyWithImpl< + _$MyDownloaderStatus_PendingImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() noStart, + required TResult Function() running, + required TResult Function(MyNetworkItemPendingType field0) pending, + required TResult Function(String field0) error, + required TResult Function() finished, + }) { + return pending(field0); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? noStart, + TResult? Function()? running, + TResult? Function(MyNetworkItemPendingType field0)? pending, + TResult? Function(String field0)? error, + TResult? Function()? finished, + }) { + return pending?.call(field0); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? noStart, + TResult Function()? running, + TResult Function(MyNetworkItemPendingType field0)? pending, + TResult Function(String field0)? error, + TResult Function()? finished, + required TResult orElse(), + }) { + if (pending != null) { + return pending(field0); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(MyDownloaderStatus_NoStart value) noStart, + required TResult Function(MyDownloaderStatus_Running value) running, + required TResult Function(MyDownloaderStatus_Pending value) pending, + required TResult Function(MyDownloaderStatus_Error value) error, + required TResult Function(MyDownloaderStatus_Finished value) finished, + }) { + return pending(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(MyDownloaderStatus_NoStart value)? noStart, + TResult? Function(MyDownloaderStatus_Running value)? running, + TResult? Function(MyDownloaderStatus_Pending value)? pending, + TResult? Function(MyDownloaderStatus_Error value)? error, + TResult? Function(MyDownloaderStatus_Finished value)? finished, + }) { + return pending?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(MyDownloaderStatus_NoStart value)? noStart, + TResult Function(MyDownloaderStatus_Running value)? running, + TResult Function(MyDownloaderStatus_Pending value)? pending, + TResult Function(MyDownloaderStatus_Error value)? error, + TResult Function(MyDownloaderStatus_Finished value)? finished, + required TResult orElse(), + }) { + if (pending != null) { + return pending(this); + } + return orElse(); + } +} + +abstract class MyDownloaderStatus_Pending implements MyDownloaderStatus { + const factory MyDownloaderStatus_Pending( + final MyNetworkItemPendingType field0) = _$MyDownloaderStatus_PendingImpl; + + MyNetworkItemPendingType get field0; + @JsonKey(ignore: true) + _$$MyDownloaderStatus_PendingImplCopyWith<_$MyDownloaderStatus_PendingImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$MyDownloaderStatus_ErrorImplCopyWith<$Res> { + factory _$$MyDownloaderStatus_ErrorImplCopyWith( + _$MyDownloaderStatus_ErrorImpl value, + $Res Function(_$MyDownloaderStatus_ErrorImpl) then) = + __$$MyDownloaderStatus_ErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({String field0}); +} + +/// @nodoc +class __$$MyDownloaderStatus_ErrorImplCopyWithImpl<$Res> + extends _$MyDownloaderStatusCopyWithImpl<$Res, + _$MyDownloaderStatus_ErrorImpl> + implements _$$MyDownloaderStatus_ErrorImplCopyWith<$Res> { + __$$MyDownloaderStatus_ErrorImplCopyWithImpl( + _$MyDownloaderStatus_ErrorImpl _value, + $Res Function(_$MyDownloaderStatus_ErrorImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? field0 = null, + }) { + return _then(_$MyDownloaderStatus_ErrorImpl( + null == field0 + ? _value.field0 + : field0 // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$MyDownloaderStatus_ErrorImpl implements MyDownloaderStatus_Error { + const _$MyDownloaderStatus_ErrorImpl(this.field0); + + @override + final String field0; + + @override + String toString() { + return 'MyDownloaderStatus.error(field0: $field0)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$MyDownloaderStatus_ErrorImpl && + (identical(other.field0, field0) || other.field0 == field0)); + } + + @override + int get hashCode => Object.hash(runtimeType, field0); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$MyDownloaderStatus_ErrorImplCopyWith<_$MyDownloaderStatus_ErrorImpl> + get copyWith => __$$MyDownloaderStatus_ErrorImplCopyWithImpl< + _$MyDownloaderStatus_ErrorImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() noStart, + required TResult Function() running, + required TResult Function(MyNetworkItemPendingType field0) pending, + required TResult Function(String field0) error, + required TResult Function() finished, + }) { + return error(field0); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? noStart, + TResult? Function()? running, + TResult? Function(MyNetworkItemPendingType field0)? pending, + TResult? Function(String field0)? error, + TResult? Function()? finished, + }) { + return error?.call(field0); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? noStart, + TResult Function()? running, + TResult Function(MyNetworkItemPendingType field0)? pending, + TResult Function(String field0)? error, + TResult Function()? finished, + required TResult orElse(), + }) { + if (error != null) { + return error(field0); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(MyDownloaderStatus_NoStart value) noStart, + required TResult Function(MyDownloaderStatus_Running value) running, + required TResult Function(MyDownloaderStatus_Pending value) pending, + required TResult Function(MyDownloaderStatus_Error value) error, + required TResult Function(MyDownloaderStatus_Finished value) finished, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(MyDownloaderStatus_NoStart value)? noStart, + TResult? Function(MyDownloaderStatus_Running value)? running, + TResult? Function(MyDownloaderStatus_Pending value)? pending, + TResult? Function(MyDownloaderStatus_Error value)? error, + TResult? Function(MyDownloaderStatus_Finished value)? finished, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(MyDownloaderStatus_NoStart value)? noStart, + TResult Function(MyDownloaderStatus_Running value)? running, + TResult Function(MyDownloaderStatus_Pending value)? pending, + TResult Function(MyDownloaderStatus_Error value)? error, + TResult Function(MyDownloaderStatus_Finished value)? finished, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class MyDownloaderStatus_Error implements MyDownloaderStatus { + const factory MyDownloaderStatus_Error(final String field0) = + _$MyDownloaderStatus_ErrorImpl; + + String get field0; + @JsonKey(ignore: true) + _$$MyDownloaderStatus_ErrorImplCopyWith<_$MyDownloaderStatus_ErrorImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$MyDownloaderStatus_FinishedImplCopyWith<$Res> { + factory _$$MyDownloaderStatus_FinishedImplCopyWith( + _$MyDownloaderStatus_FinishedImpl value, + $Res Function(_$MyDownloaderStatus_FinishedImpl) then) = + __$$MyDownloaderStatus_FinishedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$MyDownloaderStatus_FinishedImplCopyWithImpl<$Res> + extends _$MyDownloaderStatusCopyWithImpl<$Res, + _$MyDownloaderStatus_FinishedImpl> + implements _$$MyDownloaderStatus_FinishedImplCopyWith<$Res> { + __$$MyDownloaderStatus_FinishedImplCopyWithImpl( + _$MyDownloaderStatus_FinishedImpl _value, + $Res Function(_$MyDownloaderStatus_FinishedImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$MyDownloaderStatus_FinishedImpl implements MyDownloaderStatus_Finished { + const _$MyDownloaderStatus_FinishedImpl(); + + @override + String toString() { + return 'MyDownloaderStatus.finished()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$MyDownloaderStatus_FinishedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() noStart, + required TResult Function() running, + required TResult Function(MyNetworkItemPendingType field0) pending, + required TResult Function(String field0) error, + required TResult Function() finished, + }) { + return finished(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? noStart, + TResult? Function()? running, + TResult? Function(MyNetworkItemPendingType field0)? pending, + TResult? Function(String field0)? error, + TResult? Function()? finished, + }) { + return finished?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? noStart, + TResult Function()? running, + TResult Function(MyNetworkItemPendingType field0)? pending, + TResult Function(String field0)? error, + TResult Function()? finished, + required TResult orElse(), + }) { + if (finished != null) { + return finished(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(MyDownloaderStatus_NoStart value) noStart, + required TResult Function(MyDownloaderStatus_Running value) running, + required TResult Function(MyDownloaderStatus_Pending value) pending, + required TResult Function(MyDownloaderStatus_Error value) error, + required TResult Function(MyDownloaderStatus_Finished value) finished, + }) { + return finished(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(MyDownloaderStatus_NoStart value)? noStart, + TResult? Function(MyDownloaderStatus_Running value)? running, + TResult? Function(MyDownloaderStatus_Pending value)? pending, + TResult? Function(MyDownloaderStatus_Error value)? error, + TResult? Function(MyDownloaderStatus_Finished value)? finished, + }) { + return finished?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(MyDownloaderStatus_NoStart value)? noStart, + TResult Function(MyDownloaderStatus_Running value)? running, + TResult Function(MyDownloaderStatus_Pending value)? pending, + TResult Function(MyDownloaderStatus_Error value)? error, + TResult Function(MyDownloaderStatus_Finished value)? finished, + required TResult orElse(), + }) { + if (finished != null) { + return finished(this); + } + return orElse(); + } +} + +abstract class MyDownloaderStatus_Finished implements MyDownloaderStatus { + const factory MyDownloaderStatus_Finished() = + _$MyDownloaderStatus_FinishedImpl; +} diff --git a/lib/common/rust/bridge_generated.dart b/lib/common/rust/bridge_generated.dart index 4391c92..c83be4b 100644 --- a/lib/common/rust/bridge_generated.dart +++ b/lib/common/rust/bridge_generated.dart @@ -25,57 +25,66 @@ class RustImpl implements Rust { factory RustImpl.wasm(FutureOr module) => RustImpl(module as ExternalLibrary); RustImpl.raw(this._platform); - Future platform({dynamic hint}) { + Future ping({dynamic hint}) { return _platform.executeNormal(FlutterRustBridgeTask( - callFfi: (port_) => _platform.inner.wire_platform(port_), - parseSuccessData: _wire2api_platform, + callFfi: (port_) => _platform.inner.wire_ping(port_), + parseSuccessData: _wire2api_String, parseErrorData: null, - constMeta: kPlatformConstMeta, + constMeta: kPingConstMeta, argValues: [], hint: hint, )); } - FlutterRustBridgeTaskConstMeta get kPlatformConstMeta => + FlutterRustBridgeTaskConstMeta get kPingConstMeta => const FlutterRustBridgeTaskConstMeta( - debugName: "platform", + debugName: "ping", argNames: [], ); - Future add({required int left, required int right, dynamic hint}) { - var arg0 = api2wire_usize(left); - var arg1 = api2wire_usize(right); - return _platform.executeNormal(FlutterRustBridgeTask( - callFfi: (port_) => _platform.inner.wire_add(port_, arg0, arg1), - parseSuccessData: _wire2api_usize, + Stream startDownload( + {required String url, + required String savePath, + required String fileName, + required int connectionCount, + dynamic hint}) { + var arg0 = _platform.api2wire_String(url); + var arg1 = _platform.api2wire_String(savePath); + var arg2 = _platform.api2wire_String(fileName); + var arg3 = api2wire_u8(connectionCount); + return _platform.executeStream(FlutterRustBridgeTask( + callFfi: (port_) => + _platform.inner.wire_start_download(port_, arg0, arg1, arg2, arg3), + parseSuccessData: _wire2api_download_callback_data, parseErrorData: null, - constMeta: kAddConstMeta, - argValues: [left, right], + constMeta: kStartDownloadConstMeta, + argValues: [url, savePath, fileName, connectionCount], hint: hint, )); } - FlutterRustBridgeTaskConstMeta get kAddConstMeta => + FlutterRustBridgeTaskConstMeta get kStartDownloadConstMeta => const FlutterRustBridgeTaskConstMeta( - debugName: "add", - argNames: ["left", "right"], + debugName: "start_download", + argNames: ["url", "savePath", "fileName", "connectionCount"], ); - Future rustReleaseMode({dynamic hint}) { + Future cancelDownload({required String id, dynamic hint}) { + var arg0 = _platform.api2wire_String(id); return _platform.executeNormal(FlutterRustBridgeTask( - callFfi: (port_) => _platform.inner.wire_rust_release_mode(port_), - parseSuccessData: _wire2api_bool, + callFfi: (port_) => _platform.inner.wire_cancel_download(port_, arg0), + parseSuccessData: _wire2api_unit, parseErrorData: null, - constMeta: kRustReleaseModeConstMeta, - argValues: [], + constMeta: kCancelDownloadConstMeta, + argValues: [id], hint: hint, )); } - FlutterRustBridgeTaskConstMeta get kRustReleaseModeConstMeta => + FlutterRustBridgeTaskConstMeta get kCancelDownloadConstMeta => const FlutterRustBridgeTaskConstMeta( - debugName: "rust_release_mode", - argNames: [], + debugName: "cancel_download", + argNames: ["id"], ); void dispose() { @@ -83,29 +92,76 @@ class RustImpl implements Rust { } // Section: wire2api - bool _wire2api_bool(dynamic raw) { - return raw as bool; + String _wire2api_String(dynamic raw) { + return raw as String; + } + + DownloadCallbackData _wire2api_download_callback_data(dynamic raw) { + final arr = raw as List; + if (arr.length != 5) + throw Exception('unexpected arr length: expect 5 but see ${arr.length}'); + return DownloadCallbackData( + id: _wire2api_String(arr[0]), + total: _wire2api_u64(arr[1]), + progress: _wire2api_u64(arr[2]), + speed: _wire2api_u64(arr[3]), + status: _wire2api_my_downloader_status(arr[4]), + ); } int _wire2api_i32(dynamic raw) { return raw as int; } - Platform _wire2api_platform(dynamic raw) { - return Platform.values[raw as int]; + MyDownloaderStatus _wire2api_my_downloader_status(dynamic raw) { + switch (raw[0]) { + case 0: + return MyDownloaderStatus_NoStart(); + case 1: + return MyDownloaderStatus_Running(); + case 2: + return MyDownloaderStatus_Pending( + _wire2api_my_network_item_pending_type(raw[1]), + ); + case 3: + return MyDownloaderStatus_Error( + _wire2api_String(raw[1]), + ); + case 4: + return MyDownloaderStatus_Finished(); + default: + throw Exception("unreachable"); + } } - int _wire2api_usize(dynamic raw) { + MyNetworkItemPendingType _wire2api_my_network_item_pending_type(dynamic raw) { + return MyNetworkItemPendingType.values[raw as int]; + } + + int _wire2api_u64(dynamic raw) { return castInt(raw); } + + int _wire2api_u8(dynamic raw) { + return raw as int; + } + + Uint8List _wire2api_uint_8_list(dynamic raw) { + return raw as Uint8List; + } + + void _wire2api_unit(dynamic raw) { + return; + } } // Section: api2wire @protected -int api2wire_usize(int raw) { +int api2wire_u8(int raw) { return raw; } + // Section: finalizer class RustPlatform extends FlutterRustBridgeBase { @@ -113,6 +169,17 @@ class RustPlatform extends FlutterRustBridgeBase { // Section: api2wire + @protected + ffi.Pointer api2wire_String(String raw) { + return api2wire_uint_8_list(utf8.encoder.convert(raw)); + } + + @protected + ffi.Pointer api2wire_uint_8_list(Uint8List raw) { + final ans = inner.new_uint_8_list_0(raw.length); + ans.ref.ptr.asTypedList(raw.length).setAll(0, raw); + return ans; + } // Section: finalizer // Section: api_fill_to_wire @@ -213,51 +280,77 @@ class RustWire implements FlutterRustBridgeWireBase { late final _init_frb_dart_api_dl = _init_frb_dart_api_dlPtr .asFunction)>(); - void wire_platform( + void wire_ping( int port_, ) { - return _wire_platform( + return _wire_ping( port_, ); } - late final _wire_platformPtr = - _lookup>( - 'wire_platform'); - late final _wire_platform = - _wire_platformPtr.asFunction(); + late final _wire_pingPtr = + _lookup>('wire_ping'); + late final _wire_ping = _wire_pingPtr.asFunction(); - void wire_add( + void wire_start_download( int port_, - int left, - int right, + ffi.Pointer url, + ffi.Pointer save_path, + ffi.Pointer file_name, + int connection_count, ) { - return _wire_add( + return _wire_start_download( port_, - left, - right, + url, + save_path, + file_name, + connection_count, ); } - late final _wire_addPtr = _lookup< + late final _wire_start_downloadPtr = _lookup< ffi.NativeFunction< - ffi.Void Function(ffi.Int64, ffi.UintPtr, ffi.UintPtr)>>('wire_add'); - late final _wire_add = - _wire_addPtr.asFunction(); + ffi.Void Function( + ffi.Int64, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Uint8)>>('wire_start_download'); + late final _wire_start_download = _wire_start_downloadPtr.asFunction< + void Function(int, ffi.Pointer, + ffi.Pointer, ffi.Pointer, int)>(); - void wire_rust_release_mode( + void wire_cancel_download( int port_, + ffi.Pointer id, ) { - return _wire_rust_release_mode( + return _wire_cancel_download( port_, + id, ); } - late final _wire_rust_release_modePtr = - _lookup>( - 'wire_rust_release_mode'); - late final _wire_rust_release_mode = - _wire_rust_release_modePtr.asFunction(); + late final _wire_cancel_downloadPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function(ffi.Int64, + ffi.Pointer)>>('wire_cancel_download'); + late final _wire_cancel_download = _wire_cancel_downloadPtr + .asFunction)>(); + + ffi.Pointer new_uint_8_list_0( + int len, + ) { + return _new_uint_8_list_0( + len, + ); + } + + late final _new_uint_8_list_0Ptr = _lookup< + ffi + .NativeFunction Function(ffi.Int32)>>( + 'new_uint_8_list_0'); + late final _new_uint_8_list_0 = _new_uint_8_list_0Ptr + .asFunction Function(int)>(); void free_WireSyncReturn( WireSyncReturn ptr, @@ -276,6 +369,13 @@ class RustWire implements FlutterRustBridgeWireBase { final class _Dart_Handle extends ffi.Opaque {} +final class wire_uint_8_list extends ffi.Struct { + external ffi.Pointer ptr; + + @ffi.Int32() + external int len; +} + typedef DartPostCObjectFnType = ffi.Pointer< ffi.NativeFunction< ffi.Bool Function(DartPort port_id, ffi.Pointer message)>>; diff --git a/lib/main.dart b/lib/main.dart index 5fd6261..e5e9caa 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,7 +2,6 @@ import 'package:desktop_webview_window/desktop_webview_window.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:starcitizen_doctor/base/ui_model.dart'; import 'package:starcitizen_doctor/common/conf.dart'; -import 'package:starcitizen_doctor/common/rust/ffi.dart'; import 'package:starcitizen_doctor/ui/index_ui_model.dart'; import 'package:window_manager/window_manager.dart'; @@ -10,9 +9,6 @@ import 'global_ui_model.dart'; import 'ui/index_ui.dart'; void main(List args) async { - - dPrint("rust ffi ${await rustFii.platform()}"); - if (runWebViewTitleBarWidget(args, backgroundColor: const Color.fromRGBO(19, 36, 49, 1), builder: _defaultWebviewTitleBar)) { diff --git a/lib/ui/tools/downloader/dio_range_download.dart b/lib/ui/tools/downloader/dio_range_download.dart deleted file mode 100644 index 20f7674..0000000 --- a/lib/ui/tools/downloader/dio_range_download.dart +++ /dev/null @@ -1,163 +0,0 @@ -import 'dart:io'; -import 'package:dio/dio.dart'; -import 'package:starcitizen_doctor/base/ui.dart'; - -/// https://github.com/qiaoshouqing/dio_range_download/blob/master/lib/dio_range_download.dart - -class RangeDownload { - static Future downloadWithChunks( - url, - savePath, { - bool isRangeDownload = true, - ProgressCallback? onReceiveProgress, - int maxChunk = 6, - Dio? dio, - CancelToken? cancelToken, - }) async { - const firstChunkSize = 102; - - int total = 0; - if (dio == null) { - dio = Dio(); - dio.options.connectTimeout = const Duration(seconds: 10); - } - var progress = []; - var progressInit = []; - - Future mergeTempFiles(chunk) async { - File f = File(savePath + "temp0"); - IOSink ioSink = f.openWrite(mode: FileMode.writeOnlyAppend); - for (int i = 1; i < chunk; ++i) { - File f0 = File(savePath + "temp$i"); - await ioSink.addStream(f0.openRead()); - await f0.delete(); - } - await ioSink.close(); - await f.rename(savePath); - } - - Future mergeFiles(file1, file2, targetFile) async { - File f1 = File(file1); - File f2 = File(file2); - IOSink ioSink = f1.openWrite(mode: FileMode.writeOnlyAppend); - await ioSink.addStream(f2.openRead()); - await f2.delete(); - await ioSink.close(); - await f1.rename(targetFile); - } - - createCallback(no) { - return (int received, rangeTotal) async { - if (received >= rangeTotal) { - var path = savePath + "temp$no"; - var oldPath = savePath + "temp${no}_pre"; - File oldFile = File(oldPath); - if (oldFile.existsSync()) { - await mergeFiles(oldPath, path, path); - } - } - try { - progress[no] = progressInit[no] + received; - } catch (e) { - dPrint(e); - } - if (onReceiveProgress != null && total != 0) { - onReceiveProgress(progress.reduce((a, b) => a + b), total); - } - }; - } - - Future downloadChunk(url, start, end, no, - {isMerge = true}) async { - int initLength = 0; - --end; - var path = savePath + "temp$no"; - File targetFile = File(path); - if (await targetFile.exists() && isMerge) { - dPrint("good job start:$start length:${File(path).lengthSync()}"); - if (start + await targetFile.length() < end) { - initLength = await targetFile.length(); - start += initLength; - var preFile = File(path + "_pre"); - if (await preFile.exists()) { - initLength += await preFile.length(); - start += await preFile.length(); - await mergeFiles(preFile.path, targetFile.path, preFile.path); - } else { - await targetFile.rename(preFile.path); - } - } else { - await targetFile.delete(); - } - } - progress.add(initLength); - progressInit.add(initLength); - return dio!.download( - url, - path, - deleteOnError: false, - onReceiveProgress: createCallback(no), - options: Options( - headers: {"range": "bytes=$start-$end"}, - ), - cancelToken: cancelToken, - ); - } - - if (isRangeDownload) { - Response response = - await downloadChunk(url, 0, firstChunkSize, 0, isMerge: false); - if (response.statusCode == 206) { - dPrint("This http protocol support range download"); - total = int.parse(response.headers - .value(HttpHeaders.contentRangeHeader)! - .split("/") - .last); - int reserved = total - - int.parse(response.headers.value(HttpHeaders.contentLengthHeader)!); - int chunk = (reserved / firstChunkSize).ceil() + 1; - if (chunk > 1) { - int chunkSize = firstChunkSize; - if (chunk > maxChunk + 1) { - chunk = maxChunk + 1; - chunkSize = (reserved / maxChunk).ceil(); - } - var futures = []; - for (int i = 0; i < maxChunk; ++i) { - int start = firstChunkSize + i * chunkSize; - int end; - if (i == maxChunk - 1) { - end = total; - } else { - end = start + chunkSize; - } - futures.add(downloadChunk(url, start, end, i + 1)); - } - await Future.wait(futures); - } - await mergeTempFiles(chunk); - return Response( - statusCode: 200, - statusMessage: "Download success.", - data: "Download success.", - requestOptions: RequestOptions(), - ); - } else if (response.statusCode == 200) { - dPrint( - "The protocol does not support resumed downloads, and regular downloads will be used."); - return dio.download(url, savePath, - onReceiveProgress: onReceiveProgress, - cancelToken: cancelToken, - deleteOnError: false); - } else { - dPrint("The request encountered a problem, please handle it yourself"); - return response; - } - } else { - return dio.download(url, savePath, - onReceiveProgress: onReceiveProgress, - cancelToken: cancelToken, - deleteOnError: false); - } - } -} diff --git a/lib/ui/tools/downloader/downloader_dialog_ui.dart b/lib/ui/tools/downloader/downloader_dialog_ui.dart index 21c86d6..b70b766 100644 --- a/lib/ui/tools/downloader/downloader_dialog_ui.dart +++ b/lib/ui/tools/downloader/downloader_dialog_ui.dart @@ -54,7 +54,7 @@ class DownloaderDialogUI extends BaseUI { String getStatus(DownloaderDialogUIModel model) { if (model.progress == null && !model.isInMerging) return "准备中..."; - if (model.isInMerging) return "正在合并文件..."; + if (model.isInMerging) return "正在处理文件..."; return "${model.progress?.toStringAsFixed(2) ?? "0"}% "; } } diff --git a/lib/ui/tools/downloader/downloader_dialog_ui_model.dart b/lib/ui/tools/downloader/downloader_dialog_ui_model.dart index 6613dde..105c159 100644 --- a/lib/ui/tools/downloader/downloader_dialog_ui_model.dart +++ b/lib/ui/tools/downloader/downloader_dialog_ui_model.dart @@ -1,10 +1,8 @@ import 'dart:io'; -import 'package:dio/dio.dart'; import 'package:file_picker/file_picker.dart'; import 'package:starcitizen_doctor/base/ui_model.dart'; - -import 'dio_range_download.dart'; +import 'package:starcitizen_doctor/common/rust/ffi.dart'; class DownloaderDialogUIModel extends BaseUIModel { final String fileName; @@ -16,16 +14,12 @@ class DownloaderDialogUIModel extends BaseUIModel { DownloaderDialogUIModel(this.fileName, this.savePath, this.downloadUrl, {this.showChangeSavePathDialog = false, this.threadCount = 1}); - CancelToken? downloadCancelToken; - - int? downloadTaskId; - bool isInMerging = false; + String? downloadTaskId; + double? progress; int? speed; - DateTime? lastUpdateTime; - int? lastUpdateCount; int? count; int? total; @@ -52,48 +46,66 @@ class DownloaderDialogUIModel extends BaseUIModel { savePath = userSelect; dPrint(savePath); notifyListeners(); - } else { - savePath = "$savePath/$fileName"; } - // start download + + if (savePath.endsWith("\\$fileName")) { + savePath = savePath.substring(0, savePath.length - fileName.length - 1); + } + + final downloaderSavePath = "$savePath//$fileName.downloading"; + try { - downloadCancelToken = CancelToken(); - final r = await RangeDownload.downloadWithChunks(downloadUrl, savePath, - maxChunk: 10, - cancelToken: downloadCancelToken, - isRangeDownload: true, onReceiveProgress: (int count, int total) { - lastUpdateTime ??= DateTime.now(); - if ((DateTime.now().difference(lastUpdateTime ?? DateTime.now())) - .inSeconds >= - 1) { - lastUpdateTime = DateTime.now(); - speed = (count - (lastUpdateCount ?? 0)); - lastUpdateCount = count; - notifyListeners(); + rustFii + .startDownload( + url: downloadUrl, + savePath: savePath, + fileName: "$fileName.downloading", + connectionCount: 10) + .listen((event) async { + dPrint( + "id == ${event.id} p ==${event.progress} t==${event.total} s==${event.speed} st==${event.status}"); + + downloadTaskId = event.id; + count = event.progress; + if (event.total != 0) { + total = event.total; } - this.count = count; - this.total = total; - progress = count / total * 100; - if (count == total) { - isInMerging = true; + speed = event.speed; + if (total != null && total != 0 && event.progress != 0) { + progress = (event.progress / total!) * 100; } notifyListeners(); + + if (progress != null && + progress != 0 && + event.status == const MyDownloaderStatus.noStart()) { + Navigator.pop(context!, "cancel"); + return; + } + + if (event.status == const MyDownloaderStatus.finished()) { + count = total; + isInMerging = true; + notifyListeners(); + await File(downloaderSavePath) + .rename(downloaderSavePath.replaceAll(".downloading", "")); + final bsonFile = File("$downloaderSavePath.bson"); + if (await bsonFile.exists()) { + bsonFile.delete(); + } + Navigator.pop(context!, "$savePath\\$fileName"); + } }); - if (r.statusCode == 200) { - Navigator.pop(context!, savePath); - } } catch (e) { - if (e is DioException && e.type != DioExceptionType.cancel) { - Navigator.pop(context!, e); - } + Navigator.pop(context!, e); } } doCancel() { try { - downloadCancelToken?.cancel(); - downloadCancelToken = null; + if (downloadTaskId != null) { + rustFii.cancelDownload(id: downloadTaskId!); + } } catch (_) {} - Navigator.pop(context!, "cancel"); } } diff --git a/lib/ui/tools/tools_ui_model.dart b/lib/ui/tools/tools_ui_model.dart index 80123cc..6cceaf3 100644 --- a/lib/ui/tools/tools_ui_model.dart +++ b/lib/ui/tools/tools_ui_model.dart @@ -333,7 +333,7 @@ class ToolsUIModel extends BaseUIModel { } Future _downloadP4k() async { - final downloadUrl = AppConf.networkVersionData?.p4kDownloadUrl; + var downloadUrl = AppConf.networkVersionData?.p4kDownloadUrl; if (downloadUrl == null || downloadUrl.isEmpty) { showToast(context!, "该功能维护中,请稍后再试!"); return; diff --git a/rust/Cargo.toml b/rust/Cargo.toml index d44af96..051e52a 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -8,4 +8,9 @@ crate-type = ["staticlib", "cdylib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -flutter_rust_bridge = "1" \ No newline at end of file +flutter_rust_bridge = "1" +http-downloader = { version = "0.3.2", features = ["status-tracker", "speed-tracker", "breakpoint-resume", "bson-file-archiver"] } +tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } +url = "2.4.1" +uuid = { version = "1.5.0", features = ["v4", "fast-rng", "macro-diagnostics"] } +async-std = "1.12.0" diff --git a/rust/src/api.rs b/rust/src/api.rs index d39e2a9..e0a9802 100644 --- a/rust/src/api.rs +++ b/rust/src/api.rs @@ -1,63 +1,16 @@ -// This is the entry point of your Rust library. -// When adding new code to your project, note that only items used -// here will be transformed to their Dart equivalents. +use std::sync::Arc; +use async_std::task; +use flutter_rust_bridge::StreamSink; +use crate::downloader::{do_cancel_download, do_start_download, DownloadCallbackData}; -// A plain enum without any fields. This is similar to Dart- or C-style enums. -// flutter_rust_bridge is capable of generating code for enums with fields -// (@freezed classes in Dart and tagged unions in C). -pub enum Platform { - Unknown, - Android, - Ios, - Windows, - Unix, - MacIntel, - MacApple, - Wasm, +pub fn ping() -> String { + return String::from("PONG"); } -// A function definition in Rust. Similar to Dart, the return type must always be named -// and is never inferred. -pub fn platform() -> Platform { - // This is a macro, a special expression that expands into code. In Rust, all macros - // end with an exclamation mark and can be invoked with all kinds of brackets (parentheses, - // brackets and curly braces). However, certain conventions exist, for example the - // vector macro is almost always invoked as vec![..]. - // - // The cfg!() macro returns a boolean value based on the current compiler configuration. - // When attached to expressions (#[cfg(..)] form), they show or hide the expression at compile time. - // Here, however, they evaluate to runtime values, which may or may not be optimized out - // by the compiler. A variety of configurations are demonstrated here which cover most of - // the modern oeprating systems. Try running the Flutter application on different machines - // and see if it matches your expected OS. - // - // Furthermore, in Rust, the last expression in a function is the return value and does - // not have the trailing semicolon. This entire if-else chain forms a single expression. - if cfg!(windows) { - Platform::Windows - } else if cfg!(target_os = "android") { - Platform::Android - } else if cfg!(target_os = "ios") { - Platform::Ios - } else if cfg!(all(target_os = "macos", target_arch = "aarch64")) { - Platform::MacApple - } else if cfg!(target_os = "macos") { - Platform::MacIntel - } else if cfg!(target_family = "wasm") { - Platform::Wasm - } else if cfg!(unix) { - Platform::Unix - } else { - Platform::Unknown - } +pub fn start_download(url: String, save_path: String, file_name: String, connection_count: u8, sink: StreamSink) { + let _ = do_start_download(url, save_path, file_name, connection_count, Arc::new(sink)); } -pub fn add(left: usize, right: usize) -> usize { - left + right -} - -// The convention for Rust identifiers is the snake_case, -// and they are automatically converted to camelCase on the Dart side. -pub fn rust_release_mode() -> bool { - cfg!(not(debug_assertions)) +pub fn cancel_download(id: String) { + task::block_on(do_cancel_download(&id)) } \ No newline at end of file diff --git a/rust/src/bridge_generated.io.rs b/rust/src/bridge_generated.io.rs index f89e5e0..8317adb 100644 --- a/rust/src/bridge_generated.io.rs +++ b/rust/src/bridge_generated.io.rs @@ -2,28 +2,65 @@ use super::*; // Section: wire functions #[no_mangle] -pub extern "C" fn wire_platform(port_: i64) { - wire_platform_impl(port_) +pub extern "C" fn wire_ping(port_: i64) { + wire_ping_impl(port_) } #[no_mangle] -pub extern "C" fn wire_add(port_: i64, left: usize, right: usize) { - wire_add_impl(port_, left, right) +pub extern "C" fn wire_start_download( + port_: i64, + url: *mut wire_uint_8_list, + save_path: *mut wire_uint_8_list, + file_name: *mut wire_uint_8_list, + connection_count: u8, +) { + wire_start_download_impl(port_, url, save_path, file_name, connection_count) } #[no_mangle] -pub extern "C" fn wire_rust_release_mode(port_: i64) { - wire_rust_release_mode_impl(port_) +pub extern "C" fn wire_cancel_download(port_: i64, id: *mut wire_uint_8_list) { + wire_cancel_download_impl(port_, id) } // Section: allocate functions +#[no_mangle] +pub extern "C" fn new_uint_8_list_0(len: i32) -> *mut wire_uint_8_list { + let ans = wire_uint_8_list { + ptr: support::new_leak_vec_ptr(Default::default(), len), + len, + }; + support::new_leak_box_ptr(ans) +} + // Section: related functions // Section: impl Wire2Api +impl Wire2Api for *mut wire_uint_8_list { + fn wire2api(self) -> String { + let vec: Vec = self.wire2api(); + String::from_utf8_lossy(&vec).into_owned() + } +} + +impl Wire2Api> for *mut wire_uint_8_list { + fn wire2api(self) -> Vec { + unsafe { + let wrap = support::box_from_leak_ptr(self); + support::vec_from_leak_ptr(wrap.ptr, wrap.len) + } + } +} // Section: wire structs +#[repr(C)] +#[derive(Clone)] +pub struct wire_uint_8_list { + ptr: *mut u8, + len: i32, +} + // Section: impl NewWithNullPtr pub trait NewWithNullPtr { diff --git a/rust/src/bridge_generated.io.rs.bk b/rust/src/bridge_generated.io.rs.bk deleted file mode 100644 index d704f0e..0000000 --- a/rust/src/bridge_generated.io.rs.bk +++ /dev/null @@ -1,43 +0,0 @@ -use super::*; -// Section: wire functions - - - #[no_mangle] - pub extern "C" fn wire_add(port_: i64,left: usize,right: usize) { - wire_add_impl(port_,left,right) - } - -// Section: allocate functions - - -// Section: related functions - - -// Section: impl Wire2Api - - -// Section: wire structs - - - -// Section: impl NewWithNullPtr - -pub trait NewWithNullPtr { - fn new_with_null_ptr() -> Self; - } - - impl NewWithNullPtr for *mut T { - fn new_with_null_ptr() -> Self { - std::ptr::null_mut() - } - } - - -// Section: sync execution mode utility - - - #[no_mangle] - pub extern "C" fn free_WireSyncReturn(ptr: support::WireSyncReturn) { - unsafe { let _ = support::box_from_leak_ptr(ptr); }; - } - \ No newline at end of file diff --git a/rust/src/bridge_generated.rs b/rust/src/bridge_generated.rs index bd8921b..824fde1 100644 --- a/rust/src/bridge_generated.rs +++ b/rust/src/bridge_generated.rs @@ -20,46 +20,65 @@ use std::sync::Arc; // Section: imports +use crate::downloader::DownloadCallbackData; +use crate::downloader::MyDownloaderStatus; +use crate::downloader::MyNetworkItemPendingType; + // Section: wire functions -fn wire_platform_impl(port_: MessagePort) { - FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, Platform, _>( +fn wire_ping_impl(port_: MessagePort) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, String, _>( WrapInfo { - debug_name: "platform", + debug_name: "ping", port: Some(port_), mode: FfiCallMode::Normal, }, - move || move |task_callback| Result::<_, ()>::Ok(platform()), + move || move |task_callback| Result::<_, ()>::Ok(ping()), ) } -fn wire_add_impl( +fn wire_start_download_impl( port_: MessagePort, - left: impl Wire2Api + UnwindSafe, - right: impl Wire2Api + UnwindSafe, + url: impl Wire2Api + UnwindSafe, + save_path: impl Wire2Api + UnwindSafe, + file_name: impl Wire2Api + UnwindSafe, + connection_count: impl Wire2Api + UnwindSafe, ) { - FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, usize, _>( + FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, (), _>( WrapInfo { - debug_name: "add", + debug_name: "start_download", + port: Some(port_), + mode: FfiCallMode::Stream, + }, + move || { + let api_url = url.wire2api(); + let api_save_path = save_path.wire2api(); + let api_file_name = file_name.wire2api(); + let api_connection_count = connection_count.wire2api(); + move |task_callback| { + Result::<_, ()>::Ok(start_download( + api_url, + api_save_path, + api_file_name, + api_connection_count, + task_callback.stream_sink::<_, DownloadCallbackData>(), + )) + } + }, + ) +} +fn wire_cancel_download_impl(port_: MessagePort, id: impl Wire2Api + UnwindSafe) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, (), _>( + WrapInfo { + debug_name: "cancel_download", port: Some(port_), mode: FfiCallMode::Normal, }, move || { - let api_left = left.wire2api(); - let api_right = right.wire2api(); - move |task_callback| Result::<_, ()>::Ok(add(api_left, api_right)) + let api_id = id.wire2api(); + move |task_callback| Result::<_, ()>::Ok(cancel_download(api_id)) }, ) } -fn wire_rust_release_mode_impl(port_: MessagePort) { - FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, bool, _>( - WrapInfo { - debug_name: "rust_release_mode", - port: Some(port_), - mode: FfiCallMode::Normal, - }, - move || move |task_callback| Result::<_, ()>::Ok(rust_release_mode()), - ) -} // Section: wrapper structs // Section: static checks @@ -82,30 +101,66 @@ where (!self.is_null()).then(|| self.wire2api()) } } -impl Wire2Api for usize { - fn wire2api(self) -> usize { + +impl Wire2Api for u8 { + fn wire2api(self) -> u8 { self } } + // Section: impl IntoDart -impl support::IntoDart for Platform { +impl support::IntoDart for DownloadCallbackData { + fn into_dart(self) -> support::DartAbi { + vec![ + self.id.into_into_dart().into_dart(), + self.total.into_into_dart().into_dart(), + self.progress.into_into_dart().into_dart(), + self.speed.into_into_dart().into_dart(), + self.status.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl support::IntoDartExceptPrimitive for DownloadCallbackData {} +impl rust2dart::IntoIntoDart for DownloadCallbackData { + fn into_into_dart(self) -> Self { + self + } +} + +impl support::IntoDart for MyDownloaderStatus { fn into_dart(self) -> support::DartAbi { match self { - Self::Unknown => 0, - Self::Android => 1, - Self::Ios => 2, - Self::Windows => 3, - Self::Unix => 4, - Self::MacIntel => 5, - Self::MacApple => 6, - Self::Wasm => 7, + Self::NoStart => vec![0.into_dart()], + Self::Running => vec![1.into_dart()], + Self::Pending(field0) => vec![2.into_dart(), field0.into_into_dart().into_dart()], + Self::Error(field0) => vec![3.into_dart(), field0.into_into_dart().into_dart()], + Self::Finished => vec![4.into_dart()], } .into_dart() } } -impl support::IntoDartExceptPrimitive for Platform {} -impl rust2dart::IntoIntoDart for Platform { +impl support::IntoDartExceptPrimitive for MyDownloaderStatus {} +impl rust2dart::IntoIntoDart for MyDownloaderStatus { + fn into_into_dart(self) -> Self { + self + } +} + +impl support::IntoDart for MyNetworkItemPendingType { + fn into_dart(self) -> support::DartAbi { + match self { + Self::QueueUp => 0, + Self::Starting => 1, + Self::Stopping => 2, + Self::Initializing => 3, + } + .into_dart() + } +} +impl support::IntoDartExceptPrimitive for MyNetworkItemPendingType {} +impl rust2dart::IntoIntoDart for MyNetworkItemPendingType { fn into_into_dart(self) -> Self { self } diff --git a/rust/src/downloader/mod.rs b/rust/src/downloader/mod.rs new file mode 100644 index 0000000..fa20465 --- /dev/null +++ b/rust/src/downloader/mod.rs @@ -0,0 +1,183 @@ +use std::collections::HashMap; +use std::error::Error; +use std::num::{NonZeroU8, NonZeroUsize}; +use std::path::PathBuf; +use std::sync::Arc; +use std::time::Duration; +use async_std::sync::Mutex; +use http_downloader::{breakpoint_resume::DownloadBreakpointResumeExtension, ExtendedHttpFileDownloader, HttpDownloaderBuilder, speed_tracker::DownloadSpeedTrackerExtension, status_tracker::DownloadStatusTrackerExtension}; +use http_downloader::bson_file_archiver::{ArchiveFilePath, BsonFileArchiverBuilder}; +use url::Url; +use flutter_rust_bridge::StreamSink; +use flutter_rust_bridge::support::lazy_static; +use http_downloader::status_tracker::{DownloaderStatus, NetworkItemPendingType}; +use uuid::Uuid; + +pub enum MyNetworkItemPendingType { + QueueUp, + Starting, + Stopping, + Initializing, +} + +pub enum MyDownloaderStatus { + NoStart, + Running, + Pending(MyNetworkItemPendingType), + Error(String), + Finished, +} + +pub struct DownloadCallbackData { + pub id: String, + pub total: u64, + pub progress: u64, + pub speed: u64, + pub status: MyDownloaderStatus, +} + +impl DownloadCallbackData { + pub fn new(id: String, total: u64) -> Self { + DownloadCallbackData { + id, + total, + progress: 0, + speed: 0, + status: MyDownloaderStatus::NoStart, + } + } +} + +lazy_static! { + static ref DOWNLOADERS_MAP: Mutex>> = { + let map = HashMap::new(); + Mutex::new(map) + }; +} + +pub async fn do_cancel_download(id: &str) { + let d = get_downloader(id).await; + if d.is_none() { + return; + } + d.unwrap().cancel().await +} + +#[tokio::main] +pub async fn do_start_download(url: String, save_path: String, file_name: String, connection_count: u8, sink: Arc>) -> Result<(), Box> { + let save_dir = PathBuf::from(save_path); + let test_url = Url::parse(&*url)?; + + let (mut downloader, (status_state, speed_state, ..)) = + + HttpDownloaderBuilder::new(test_url, save_dir) + .chunk_size(NonZeroUsize::new(1024 * 1024 * 10).unwrap()) // 块大小 + .download_connection_count(NonZeroU8::new(connection_count).unwrap()) + .file_name(Option::from(file_name)) + .build(( + // 下载状态追踪扩展 + // by cargo feature "status-tracker" enable + DownloadStatusTrackerExtension { log: true }, + // 下载速度追踪扩展 + // by cargo feature "speed-tracker" enable + DownloadSpeedTrackerExtension { log: true }, + // 断点续传扩展, + // by cargo feature "breakpoint-resume" enable + DownloadBreakpointResumeExtension { + // BsonFileArchiver by cargo feature "bson-file-archiver" enable + download_archiver_builder: BsonFileArchiverBuilder::new(ArchiveFilePath::Suffix("bson".to_string())) + } + )); + + let status_state_arc = Arc::new(status_state); + let status_state_clone = Arc::clone(&status_state_arc); + + let id = Uuid::new_v4(); + // info!("Prepare download,准备下载"); + let download_future = downloader.prepare_download()?; + let sink_clone = sink.clone(); + add_downloader(&id.to_string(), Arc::new(downloader)).await; + + // 打印下载进度 + // Print download Progress + tokio::spawn({ + let mut downloaded_len_receiver = get_downloader(&id.to_string()).await.unwrap().downloaded_len_receiver().clone(); + let total_size_future = get_downloader(&id.to_string()).await.unwrap().total_size_future(); + async move { + let total_len = total_size_future.await; + if let Some(total_len) = total_len { + // info!("Total size: {:.2} Mb",total_len.get() as f64 / 1024_f64/ 1024_f64); + sink_clone.add(DownloadCallbackData::new(id.to_string(), total_len.get())); + } + + while downloaded_len_receiver.changed().await.is_ok() { + let p = *downloaded_len_receiver.borrow(); + let _status = status_state_clone.status(); // get download status, 获取状态 + let _byte_per_second = speed_state.download_speed(); // get download speed,Byte per second,获取速度,字节每秒 + + if let Some(total_len) = total_len { + sink_clone.add(DownloadCallbackData { + id: id.to_string(), + total: total_len.get(), + progress: p, + speed: _byte_per_second, + status: get_my_status(_status), + }); + } + tokio::time::sleep(Duration::from_millis(100)).await; + } + } + }); + + download_future.await?; + let _status = status_state_arc.status(); + sink.add(DownloadCallbackData { + id: id.to_string(), + total: 0, + progress: 0, + speed: 0, + status: get_my_status(_status), + }); + sink.close(); + remove_downloader(&id.to_string()).await; + println!("rust downloader download complete"); + Ok(()) +} + +async fn remove_downloader(id: &str) { + let mut downloader_map = DOWNLOADERS_MAP.lock().await; + downloader_map.remove(id); +} + +async fn get_downloader(id: &str) -> Option> { + let downloader_map = DOWNLOADERS_MAP.lock().await; + return if let Some(downloader) = downloader_map.get(id) { + Some(downloader.clone()) + } else { + None + }; +} + +async fn add_downloader(id: &str, d: Arc) { + let mut downloader_map = DOWNLOADERS_MAP.lock().await; + downloader_map.insert(id.to_string(), d); +} + +fn get_my_status(_status: DownloaderStatus) -> MyDownloaderStatus { + match _status { + DownloaderStatus::NoStart => { MyDownloaderStatus::NoStart } + DownloaderStatus::Running => { MyDownloaderStatus::Running } + DownloaderStatus::Pending(n) => { MyDownloaderStatus::Pending(get_my_network_type(n)) } + DownloaderStatus::Error(e) => { MyDownloaderStatus::Error(e) } + DownloaderStatus::Finished => { MyDownloaderStatus::Finished } + } +} + +fn get_my_network_type(n: NetworkItemPendingType) -> MyNetworkItemPendingType { + match n { + NetworkItemPendingType::QueueUp => { MyNetworkItemPendingType::QueueUp } + NetworkItemPendingType::Starting => { MyNetworkItemPendingType::Starting } + NetworkItemPendingType::Stopping => { MyNetworkItemPendingType::Stopping } + NetworkItemPendingType::Initializing => { MyNetworkItemPendingType::Initializing } + } +} \ No newline at end of file diff --git a/rust/src/lib.rs b/rust/src/lib.rs index bde602f..49c0daa 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1,2 +1,3 @@ mod api; -mod bridge_generated; /* AUTO INJECTED BY flutter_rust_bridge. This line may not be accurate, and you can change it according to your needs. */ +mod bridge_generated; +mod downloader;