新增 Rust 实现的多线程下载器,优化下载可靠性

This commit is contained in:
2023-11-05 15:56:48 +08:00
parent 3409a8597f
commit 5bc7024fe7
17 changed files with 1379 additions and 421 deletions

View File

@ -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");

View File

@ -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 {

View File

@ -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> platform({dynamic hint});
Future<String> ping({dynamic hint});
FlutterRustBridgeTaskConstMeta get kPlatformConstMeta;
FlutterRustBridgeTaskConstMeta get kPingConstMeta;
Future<int> add({required int left, required int right, dynamic hint});
Stream<DownloadCallbackData> startDownload(
{required String url,
required String savePath,
required String fileName,
required int connectionCount,
dynamic hint});
FlutterRustBridgeTaskConstMeta get kAddConstMeta;
FlutterRustBridgeTaskConstMeta get kStartDownloadConstMeta;
Future<bool> rustReleaseMode({dynamic hint});
Future<void> 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,
}

View File

@ -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>(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<TResult extends Object?>({
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 extends Object?>({
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 extends Object?>({
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<TResult extends Object?>({
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 extends Object?>({
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 extends Object?>({
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<TResult extends Object?>({
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 extends Object?>({
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 extends Object?>({
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<TResult extends Object?>({
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 extends Object?>({
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 extends Object?>({
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<TResult extends Object?>({
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 extends Object?>({
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 extends Object?>({
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<TResult extends Object?>({
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 extends Object?>({
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 extends Object?>({
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<TResult extends Object?>({
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 extends Object?>({
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 extends Object?>({
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<TResult extends Object?>({
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 extends Object?>({
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 extends Object?>({
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<TResult extends Object?>({
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 extends Object?>({
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 extends Object?>({
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<TResult extends Object?>({
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 extends Object?>({
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 extends Object?>({
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<TResult extends Object?>({
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 extends Object?>({
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 extends Object?>({
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<TResult extends Object?>({
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 extends Object?>({
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 extends Object?>({
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;
}

View File

@ -25,57 +25,66 @@ class RustImpl implements Rust {
factory RustImpl.wasm(FutureOr<WasmModule> module) =>
RustImpl(module as ExternalLibrary);
RustImpl.raw(this._platform);
Future<Platform> platform({dynamic hint}) {
Future<String> 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<int> 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<DownloadCallbackData> 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<bool> rustReleaseMode({dynamic hint}) {
Future<void> 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<dynamic>;
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<RustWire> {
@ -113,6 +169,17 @@ class RustPlatform extends FlutterRustBridgeBase<RustWire> {
// Section: api2wire
@protected
ffi.Pointer<wire_uint_8_list> api2wire_String(String raw) {
return api2wire_uint_8_list(utf8.encoder.convert(raw));
}
@protected
ffi.Pointer<wire_uint_8_list> 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<int Function(ffi.Pointer<ffi.Void>)>();
void wire_platform(
void wire_ping(
int port_,
) {
return _wire_platform(
return _wire_ping(
port_,
);
}
late final _wire_platformPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Int64)>>(
'wire_platform');
late final _wire_platform =
_wire_platformPtr.asFunction<void Function(int)>();
late final _wire_pingPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Int64)>>('wire_ping');
late final _wire_ping = _wire_pingPtr.asFunction<void Function(int)>();
void wire_add(
void wire_start_download(
int port_,
int left,
int right,
ffi.Pointer<wire_uint_8_list> url,
ffi.Pointer<wire_uint_8_list> save_path,
ffi.Pointer<wire_uint_8_list> 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<void Function(int, int, int)>();
ffi.Void Function(
ffi.Int64,
ffi.Pointer<wire_uint_8_list>,
ffi.Pointer<wire_uint_8_list>,
ffi.Pointer<wire_uint_8_list>,
ffi.Uint8)>>('wire_start_download');
late final _wire_start_download = _wire_start_downloadPtr.asFunction<
void Function(int, ffi.Pointer<wire_uint_8_list>,
ffi.Pointer<wire_uint_8_list>, ffi.Pointer<wire_uint_8_list>, int)>();
void wire_rust_release_mode(
void wire_cancel_download(
int port_,
ffi.Pointer<wire_uint_8_list> id,
) {
return _wire_rust_release_mode(
return _wire_cancel_download(
port_,
id,
);
}
late final _wire_rust_release_modePtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Int64)>>(
'wire_rust_release_mode');
late final _wire_rust_release_mode =
_wire_rust_release_modePtr.asFunction<void Function(int)>();
late final _wire_cancel_downloadPtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(ffi.Int64,
ffi.Pointer<wire_uint_8_list>)>>('wire_cancel_download');
late final _wire_cancel_download = _wire_cancel_downloadPtr
.asFunction<void Function(int, ffi.Pointer<wire_uint_8_list>)>();
ffi.Pointer<wire_uint_8_list> new_uint_8_list_0(
int len,
) {
return _new_uint_8_list_0(
len,
);
}
late final _new_uint_8_list_0Ptr = _lookup<
ffi
.NativeFunction<ffi.Pointer<wire_uint_8_list> Function(ffi.Int32)>>(
'new_uint_8_list_0');
late final _new_uint_8_list_0 = _new_uint_8_list_0Ptr
.asFunction<ffi.Pointer<wire_uint_8_list> 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<ffi.Uint8> ptr;
@ffi.Int32()
external int len;
}
typedef DartPostCObjectFnType = ffi.Pointer<
ffi.NativeFunction<
ffi.Bool Function(DartPort port_id, ffi.Pointer<ffi.Void> message)>>;

View File

@ -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<String> args) async {
dPrint("rust ffi ${await rustFii.platform()}");
if (runWebViewTitleBarWidget(args,
backgroundColor: const Color.fromRGBO(19, 36, 49, 1),
builder: _defaultWebviewTitleBar)) {

View File

@ -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<Response> 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 = <int>[];
var progressInit = <int>[];
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<Response> 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 = <Future>[];
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);
}
}
}

View File

@ -54,7 +54,7 @@ class DownloaderDialogUI extends BaseUI<DownloaderDialogUIModel> {
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"}% ";
}
}

View File

@ -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");
}
}

View File

@ -333,7 +333,7 @@ class ToolsUIModel extends BaseUIModel {
}
Future<void> _downloadP4k() async {
final downloadUrl = AppConf.networkVersionData?.p4kDownloadUrl;
var downloadUrl = AppConf.networkVersionData?.p4kDownloadUrl;
if (downloadUrl == null || downloadUrl.isEmpty) {
showToast(context!, "该功能维护中,请稍后再试!");
return;