mirror of
https://github.com/EasyTier/astral.git
synced 2025-05-19 10:30:24 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4209ea813 | ||
|
|
dbf4168e13 | ||
|
|
c4c7d93ca8 | ||
|
|
086c3a7818 | ||
|
|
afcf43bcf7 | ||
|
|
f19853a49e | ||
|
|
92e9d0fc15 | ||
|
|
0d567ce686 | ||
|
|
912dc263b0 | ||
|
|
38acea1bf5 | ||
|
|
16d31c01fc | ||
|
|
48c1894cc4 | ||
|
|
5f66d65fe4 | ||
|
|
ad875e0e8a | ||
|
|
1ff1067b98 | ||
|
|
fca3d3b17f | ||
|
|
5646d550cb | ||
|
|
fa8885bec3 | ||
|
|
27bd2b0c1b | ||
|
|
0fef772a01 | ||
|
|
87e6e6d70d | ||
|
|
e6049383bb | ||
|
|
d506555741 | ||
|
|
01a4d1a81e | ||
|
|
ca49264ad6 | ||
|
|
99ed31f992 | ||
|
|
f6de2f3849 | ||
|
|
3e8fae1c3e |
@@ -0,0 +1,69 @@
|
||||
name: Dart and Flutter Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Release version (e.g., v1.0.0)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
|
||||
# 调整 GITHUB_TOKEN 的权限
|
||||
permissions:
|
||||
contents: write # 允许写入仓库内容(上传文件)
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# 安装 Flutter
|
||||
- name: Install Flutter
|
||||
run: |
|
||||
echo "Cloning Flutter stable branch..."
|
||||
git clone https://github.com/flutter/flutter.git -b stable
|
||||
echo "Adding Flutter to PATH..."
|
||||
echo "$env:GITHUB_WORKSPACE\flutter\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
echo "Flutter installation completed."
|
||||
|
||||
# 安装 Flutter 依赖
|
||||
- name: Install Flutter dependencies
|
||||
run: |
|
||||
echo "Fetching Flutter dependencies..."
|
||||
flutter pub get
|
||||
echo "Flutter dependencies installed."
|
||||
|
||||
# 编译 Flutter Windows 应用
|
||||
- name: Build Flutter Windows Release
|
||||
run: |
|
||||
echo "Building Flutter Windows Release..."
|
||||
flutter build windows --release
|
||||
echo "Flutter Windows Release build completed."
|
||||
|
||||
# 压缩 build\windows\x64\runner\Release 目录
|
||||
- name: Compress Release Directory
|
||||
run: |
|
||||
echo "Compressing Release directory..."
|
||||
$releaseDir = "$env:GITHUB_WORKSPACE\build\windows\x64\runner\Release"
|
||||
$zipFile = "$env:GITHUB_WORKSPACE\release.zip"
|
||||
Compress-Archive -Path $releaseDir -DestinationPath $zipFile
|
||||
echo "Release directory compressed to $zipFile."
|
||||
|
||||
# 上传压缩包作为 Artifact
|
||||
- name: Upload Release Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release
|
||||
path: ${{ github.workspace }}/release.zip
|
||||
|
||||
# 创建 GitHub Release 并上传压缩包
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
name: Release ${{ inputs.version }}
|
||||
draft: true
|
||||
files: ${{ github.workspace }}/release.zip
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag_name: ${{ inputs.version }}
|
||||
@@ -15,6 +15,9 @@ migration:
|
||||
- platform: root
|
||||
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
||||
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
||||
- platform: android
|
||||
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
||||
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
||||
|
||||
# User provided section
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
# AstralET 游戏联机工具
|
||||
|
||||
|
||||
[临时发布位置](https://github.com/ldoubil/astral)
|
||||
|
||||
[](https://github.com/ldoubil/astral)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
.cxx/
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/to/reference-keystore
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
@@ -0,0 +1,56 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.astral"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
// ndkVersion = flutter.ndkVersion
|
||||
ndkVersion = "27.2.12479018"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
// 启用 core library desugaring
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||
}
|
||||
// 添加 buildFeatures 配置
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.example.astral"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加 dependencies 块
|
||||
dependencies {
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,45 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application
|
||||
android:label="astral"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.example.astral
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity : FlutterActivity()
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 544 B |
Binary file not shown.
|
After Width: | Height: | Size: 442 B |
Binary file not shown.
|
After Width: | Height: | Size: 721 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,21 @@
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
|
||||
rootProject.layout.buildDirectory.value(newBuildDir)
|
||||
|
||||
subprojects {
|
||||
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
||||
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register<Delete>("clean") {
|
||||
delete(rootProject.layout.buildDirectory)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
|
||||
@@ -0,0 +1,25 @@
|
||||
pluginManagement {
|
||||
val flutterSdkPath = run {
|
||||
val properties = java.util.Properties()
|
||||
file("local.properties").inputStream().use { properties.load(it) }
|
||||
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||
flutterSdkPath
|
||||
}
|
||||
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.7.0" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
|
||||
}
|
||||
|
||||
include(":app")
|
||||
+3
-3
@@ -4,7 +4,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:system_tray/system_tray.dart';
|
||||
import 'dart:io';
|
||||
import 'screens/Home.dart';
|
||||
import 'config/themeconfiguration.dart';
|
||||
import 'config/themeconfiguration.dart' as theme_config;
|
||||
import 'config/app_config.dart';
|
||||
|
||||
// 定义应用程序的主要StatefulWidget
|
||||
@@ -114,7 +114,7 @@ class _MyAppState extends State<MyApp> {
|
||||
],
|
||||
// Insert this line
|
||||
supportedLocales: const [Locale("zh", "CN"), Locale("en", "US")],
|
||||
theme: ThemeConfig.getLightTheme(
|
||||
theme: theme_config.ThemeConfig.getLightTheme(
|
||||
useMaterial3: useMaterial3,
|
||||
seedColor: _seedColor,
|
||||
).copyWith(
|
||||
@@ -124,7 +124,7 @@ class _MyAppState extends State<MyApp> {
|
||||
primaryTextTheme: Typography.material2021().black.apply(
|
||||
fontFamily: 'MiSans',
|
||||
)),
|
||||
darkTheme: ThemeConfig.getDarkTheme(
|
||||
darkTheme: theme_config.ThemeConfig.getDarkTheme(
|
||||
useMaterial3: useMaterial3,
|
||||
seedColor: _seedColor,
|
||||
).copyWith(
|
||||
|
||||
+824
-115
@@ -1,172 +1,881 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:io';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'cof.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
|
||||
// 配置模型基类
|
||||
abstract class ConfigModel {
|
||||
Map<String, dynamic> toJson();
|
||||
String get configKey; // 配置在存储中的键名
|
||||
}
|
||||
|
||||
// 添加高级配置类
|
||||
class AdvancedConfig implements ConfigModel {
|
||||
final String defaultProtocol;
|
||||
final String devName;
|
||||
final bool enableEncryption;
|
||||
final bool enableIpv6;
|
||||
final int mtu;
|
||||
final bool latencyFirst;
|
||||
final bool enableExitNode;
|
||||
final bool proxyForwardBySystem;
|
||||
final bool noTun;
|
||||
final bool useSmoltcp;
|
||||
final String relayNetworkWhitelist;
|
||||
final bool disableP2p;
|
||||
final bool relayAllPeerRpc;
|
||||
final bool disableUdpHolePunching;
|
||||
final bool multiThread;
|
||||
final String dataCompressAlgo;
|
||||
final bool bindDevice;
|
||||
final bool enableKcpProxy;
|
||||
final bool disableKcpInput;
|
||||
final bool disableRelayKcp;
|
||||
|
||||
@override
|
||||
String get configKey => 'advanced';
|
||||
|
||||
AdvancedConfig({
|
||||
this.defaultProtocol = "tcp",
|
||||
this.devName = "",
|
||||
this.enableEncryption = true,
|
||||
this.enableIpv6 = true,
|
||||
this.mtu = 1380,
|
||||
this.latencyFirst = false,
|
||||
this.enableExitNode = false,
|
||||
this.proxyForwardBySystem = false,
|
||||
this.noTun = false,
|
||||
this.useSmoltcp = false,
|
||||
this.relayNetworkWhitelist = "*",
|
||||
this.disableP2p = false,
|
||||
this.relayAllPeerRpc = false,
|
||||
this.disableUdpHolePunching = false,
|
||||
this.multiThread = true,
|
||||
this.dataCompressAlgo = "None",
|
||||
this.bindDevice = true,
|
||||
this.enableKcpProxy = false,
|
||||
this.disableKcpInput = false,
|
||||
this.disableRelayKcp = true,
|
||||
});
|
||||
|
||||
factory AdvancedConfig.fromJson(Map<String, dynamic> json) {
|
||||
return AdvancedConfig(
|
||||
defaultProtocol: json['defaultProtocol'] ?? "tcp",
|
||||
devName: json['devName'] ?? "",
|
||||
enableEncryption: json['enableEncryption'] ?? true,
|
||||
enableIpv6: json['enableIpv6'] ?? true,
|
||||
mtu: json['mtu'] ?? 1380,
|
||||
latencyFirst: json['latencyFirst'] ?? false,
|
||||
enableExitNode: json['enableExitNode'] ?? false,
|
||||
proxyForwardBySystem: json['proxyForwardBySystem'] ?? false,
|
||||
noTun: json['noTun'] ?? false,
|
||||
useSmoltcp: json['useSmoltcp'] ?? false,
|
||||
relayNetworkWhitelist: json['relayNetworkWhitelist'] ?? "*",
|
||||
disableP2p: json['disableP2p'] ?? false,
|
||||
relayAllPeerRpc: json['relayAllPeerRpc'] ?? false,
|
||||
disableUdpHolePunching: json['disableUdpHolePunching'] ?? false,
|
||||
multiThread: json['multiThread'] ?? true,
|
||||
dataCompressAlgo: json['dataCompressAlgo'] ?? "None",
|
||||
bindDevice: json['bindDevice'] ?? true,
|
||||
enableKcpProxy: json['enableKcpProxy'] ?? false,
|
||||
disableKcpInput: json['disableKcpInput'] ?? false,
|
||||
disableRelayKcp: json['disableRelayKcp'] ?? true,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() => {
|
||||
'defaultProtocol': defaultProtocol,
|
||||
'devName': devName,
|
||||
'enableEncryption': enableEncryption,
|
||||
'enableIpv6': enableIpv6,
|
||||
'mtu': mtu,
|
||||
'latencyFirst': latencyFirst,
|
||||
'enableExitNode': enableExitNode,
|
||||
'proxyForwardBySystem': proxyForwardBySystem,
|
||||
'noTun': noTun,
|
||||
'useSmoltcp': useSmoltcp,
|
||||
'relayNetworkWhitelist': relayNetworkWhitelist,
|
||||
'disableP2p': disableP2p,
|
||||
'relayAllPeerRpc': relayAllPeerRpc,
|
||||
'disableUdpHolePunching': disableUdpHolePunching,
|
||||
'multiThread': multiThread,
|
||||
'dataCompressAlgo': dataCompressAlgo,
|
||||
'bindDevice': bindDevice,
|
||||
'enableKcpProxy': enableKcpProxy,
|
||||
'disableKcpInput': disableKcpInput,
|
||||
'disableRelayKcp': disableRelayKcp,
|
||||
};
|
||||
}
|
||||
|
||||
// 配置模型类 - 保持原有结构
|
||||
class ThemeConfig implements ConfigModel {
|
||||
final String mode;
|
||||
final int seedColor;
|
||||
|
||||
@override
|
||||
String get configKey => 'theme';
|
||||
|
||||
ThemeConfig({
|
||||
this.mode = 'system',
|
||||
this.seedColor = 0xFF2196F3, // Colors.blue.value
|
||||
});
|
||||
|
||||
factory ThemeConfig.fromJson(Map<String, dynamic> json) {
|
||||
return ThemeConfig(
|
||||
mode: json['mode'] ?? 'system',
|
||||
seedColor: json['seedColor'] ?? 0xFF2196F3,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() => {
|
||||
'mode': mode,
|
||||
'seedColor': seedColor,
|
||||
};
|
||||
}
|
||||
|
||||
class ServerConfig implements ConfigModel {
|
||||
final String url;
|
||||
final String name;
|
||||
final bool selected;
|
||||
final bool tcp;
|
||||
final bool udp;
|
||||
final bool ws;
|
||||
final bool wss;
|
||||
final bool quic;
|
||||
|
||||
@override
|
||||
String get configKey => 'server';
|
||||
|
||||
ServerConfig({
|
||||
required this.url,
|
||||
required this.name,
|
||||
this.selected = false,
|
||||
this.tcp = true,
|
||||
this.udp = true,
|
||||
this.ws = false,
|
||||
this.wss = false,
|
||||
this.quic = false,
|
||||
});
|
||||
|
||||
factory ServerConfig.fromJson(Map<String, dynamic> json) {
|
||||
return ServerConfig(
|
||||
url: json['url'] ?? '',
|
||||
name: json['name'] ?? '',
|
||||
selected: json['selected'] ?? false,
|
||||
tcp: json['tcp'] ?? true,
|
||||
udp: json['udp'] ?? true,
|
||||
ws: json['ws'] ?? false,
|
||||
wss: json['wss'] ?? false,
|
||||
quic: json['quic'] ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'url': url,
|
||||
'name': name,
|
||||
'selected': selected,
|
||||
'tcp': tcp,
|
||||
'udp': udp,
|
||||
'ws': ws,
|
||||
'wss': wss,
|
||||
'quic': quic,
|
||||
};
|
||||
}
|
||||
|
||||
// 创建一个新的服务器列表配置类
|
||||
class ServerListConfig implements ConfigModel {
|
||||
final List<ServerConfig> servers;
|
||||
|
||||
@override
|
||||
String get configKey => 'server';
|
||||
|
||||
ServerListConfig({
|
||||
required this.servers,
|
||||
});
|
||||
|
||||
factory ServerListConfig.fromJson(Map<String, dynamic> json) {
|
||||
List<ServerConfig> serverList = [];
|
||||
|
||||
if (json['list'] is List) {
|
||||
try {
|
||||
serverList = (json['list'] as List).map((item) {
|
||||
// 安全地将 Map<dynamic, dynamic> 转换为 Map<String, dynamic>
|
||||
if (item is Map) {
|
||||
Map<String, dynamic> serverMap = {};
|
||||
item.forEach((key, value) {
|
||||
if (key is String) {
|
||||
serverMap[key] = value;
|
||||
}
|
||||
});
|
||||
return ServerConfig.fromJson(serverMap);
|
||||
}
|
||||
// 如果不是 Map,返回默认服务器配置
|
||||
return ServerConfig(
|
||||
url: 'public.easytier.cn:11010',
|
||||
name: '公共服务器',
|
||||
selected: true,
|
||||
tcp: true,
|
||||
udp: true,
|
||||
ws: false,
|
||||
wss: false,
|
||||
quic: false,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
// 确保至少有一个服务器被选中
|
||||
if (!serverList.any((server) => server.selected) &&
|
||||
serverList.isNotEmpty) {
|
||||
print('没有选中的服务器,将第一个服务器设为选中状态');
|
||||
serverList[0] = ServerConfig(
|
||||
url: serverList[0].url,
|
||||
name: serverList[0].name,
|
||||
selected: true,
|
||||
tcp: serverList[0].tcp,
|
||||
udp: serverList[0].udp,
|
||||
ws: serverList[0].ws,
|
||||
wss: serverList[0].wss,
|
||||
quic: serverList[0].quic,
|
||||
);
|
||||
} else {
|
||||
print('已有选中的服务器,保持原状');
|
||||
}
|
||||
} catch (e) {
|
||||
print('解析服务器列表失败: $e');
|
||||
// 解析失败时使用默认值
|
||||
}
|
||||
}
|
||||
|
||||
// 如果列表为空,添加默认服务器
|
||||
if (serverList.isEmpty) {
|
||||
print('服务器列表为空,添加默认服务器');
|
||||
serverList = [
|
||||
ServerConfig(
|
||||
url: 'public.easytier.cn:11010',
|
||||
name: '公共服务器',
|
||||
selected: true,
|
||||
tcp: true,
|
||||
udp: true,
|
||||
ws: false,
|
||||
wss: false,
|
||||
quic: false,
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
return ServerListConfig(servers: serverList);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() => {
|
||||
'list': servers.map((server) => server.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
class RoomConfig implements ConfigModel {
|
||||
final String name;
|
||||
final String password;
|
||||
|
||||
@override
|
||||
String get configKey => 'room';
|
||||
|
||||
RoomConfig({
|
||||
this.name = 'kevin',
|
||||
this.password = 'kevin',
|
||||
});
|
||||
|
||||
factory RoomConfig.fromJson(Map<String, dynamic> json) {
|
||||
return RoomConfig(
|
||||
name: json['name'] ?? 'kevin',
|
||||
password: json['password'] ?? 'kevin',
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'name': name,
|
||||
'password': password,
|
||||
};
|
||||
}
|
||||
|
||||
class UserConfig implements ConfigModel {
|
||||
final String name;
|
||||
|
||||
@override
|
||||
String get configKey => 'user';
|
||||
|
||||
UserConfig({
|
||||
required this.name,
|
||||
});
|
||||
|
||||
factory UserConfig.fromJson(Map<String, dynamic> json) {
|
||||
return UserConfig(
|
||||
name: json['name'] ?? Platform.localHostname,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'name': name,
|
||||
};
|
||||
}
|
||||
|
||||
class NetworkConfig implements ConfigModel {
|
||||
final String virtualIP;
|
||||
final bool dynamicIP;
|
||||
|
||||
@override
|
||||
String get configKey => 'network';
|
||||
|
||||
NetworkConfig({
|
||||
this.virtualIP = '',
|
||||
this.dynamicIP = true,
|
||||
});
|
||||
|
||||
factory NetworkConfig.fromJson(Map<String, dynamic> json) {
|
||||
return NetworkConfig(
|
||||
virtualIP: json['virtualIP'] ?? '',
|
||||
dynamicIP: json['dynamicIP'] ?? true,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'virtualIP': virtualIP,
|
||||
'dynamicIP': dynamicIP,
|
||||
};
|
||||
}
|
||||
|
||||
class SystemConfig implements ConfigModel {
|
||||
final bool closeToTray;
|
||||
final bool enablePing;
|
||||
|
||||
@override
|
||||
String get configKey => 'system';
|
||||
|
||||
SystemConfig({
|
||||
this.closeToTray = true,
|
||||
this.enablePing = true,
|
||||
});
|
||||
|
||||
factory SystemConfig.fromJson(Map<String, dynamic> json) {
|
||||
return SystemConfig(
|
||||
closeToTray: json['closeToTray'] ?? true,
|
||||
enablePing: json['enablePing'] ?? true,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'closeToTray': closeToTray,
|
||||
'enablePing': enablePing,
|
||||
};
|
||||
}
|
||||
|
||||
// 配置管理器
|
||||
class AppConfig {
|
||||
static final AppConfig _instance = AppConfig._internal();
|
||||
static late ConfigManager _configManager;
|
||||
static late Box _configBox;
|
||||
static late String _configDirectory;
|
||||
static bool _initialized = false;
|
||||
|
||||
// 配置模型映射表
|
||||
final Map<Type, ConfigModel> _configModels = {};
|
||||
|
||||
// 配置类型与工厂函数映射
|
||||
final Map<Type, Function> _configFactories = {};
|
||||
|
||||
factory AppConfig() {
|
||||
return _instance;
|
||||
}
|
||||
|
||||
AppConfig._internal();
|
||||
|
||||
// 初始化配置
|
||||
static Future<void> init() async {
|
||||
// 获取可执行文件所在目录而不是当前工作目录
|
||||
_configDirectory = File(Platform.resolvedExecutable).parent.path;
|
||||
final configPath = path.join(_configDirectory, 'config.yaml');
|
||||
|
||||
_configManager = ConfigManager(
|
||||
filePath: configPath,
|
||||
defaultConfig: {
|
||||
'theme': {
|
||||
'mode': 'system',
|
||||
'seedColor': Colors.blue.value,
|
||||
},
|
||||
'server': {
|
||||
'list': ['public.easytier.cn:11010'],
|
||||
'current': 'public.easytier.cn:11010',
|
||||
},
|
||||
'room': {
|
||||
'name': 'kevin',
|
||||
'password': 'kevin',
|
||||
},
|
||||
'user': {
|
||||
'name': Platform.localHostname,
|
||||
},
|
||||
'network': {
|
||||
'virtualIP': '',
|
||||
'dynamicIP': true,
|
||||
},
|
||||
'system': {
|
||||
'closeToTray': true,
|
||||
'enablePing': true, // 添加全局ping开关默认配置
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await _configManager.load();
|
||||
// 在AppConfig类中添加静态方法setConfigDir
|
||||
static void setConfigDir(String dirPath) {
|
||||
_configDirectory = dirPath;
|
||||
print('配置目录已设置为: $_configDirectory');
|
||||
}
|
||||
|
||||
AppConfig._internal() {
|
||||
// 注册所有配置类型
|
||||
_registerConfig<ThemeConfig>(
|
||||
(json) => ThemeConfig.fromJson(json),
|
||||
ThemeConfig(),
|
||||
);
|
||||
|
||||
_registerConfig<RoomConfig>(
|
||||
(json) => RoomConfig.fromJson(json),
|
||||
RoomConfig(),
|
||||
);
|
||||
|
||||
_registerConfig<UserConfig>(
|
||||
(json) => UserConfig.fromJson(json),
|
||||
UserConfig(name: Platform.localHostname),
|
||||
);
|
||||
|
||||
_registerConfig<NetworkConfig>(
|
||||
(json) => NetworkConfig.fromJson(json),
|
||||
NetworkConfig(),
|
||||
);
|
||||
|
||||
_registerConfig<SystemConfig>(
|
||||
(json) => SystemConfig.fromJson(json),
|
||||
SystemConfig(),
|
||||
);
|
||||
|
||||
// 注册服务器列表配置
|
||||
_registerConfig<ServerListConfig>(
|
||||
(json) => ServerListConfig.fromJson(json),
|
||||
ServerListConfig(servers: [
|
||||
ServerConfig(
|
||||
url: 'public.easytier.cn:11010',
|
||||
name: '公共服务器',
|
||||
selected: true,
|
||||
tcp: true,
|
||||
udp: true,
|
||||
ws: false,
|
||||
wss: false,
|
||||
quic: false,
|
||||
)
|
||||
]),
|
||||
);
|
||||
|
||||
// 注册高级配置
|
||||
_registerConfig<AdvancedConfig>(
|
||||
(json) => AdvancedConfig.fromJson(json),
|
||||
AdvancedConfig(),
|
||||
);
|
||||
}
|
||||
|
||||
// 注册配置类型
|
||||
void _registerConfig<T extends ConfigModel>(
|
||||
T Function(Map<String, dynamic>) fromJson,
|
||||
T defaultValue,
|
||||
) {
|
||||
_configFactories[T] = fromJson;
|
||||
_configModels[T] = defaultValue;
|
||||
}
|
||||
|
||||
static Future<void> init() async {
|
||||
if (_initialized) return;
|
||||
|
||||
try {
|
||||
// 如果没有预先设置配置目录,则尝试获取可执行文件所在目录
|
||||
if (!_configDirectory.isNotEmpty) {
|
||||
_configDirectory = File(Platform.resolvedExecutable).parent.path;
|
||||
}
|
||||
print('配置目录: $_configDirectory');
|
||||
|
||||
// 初始化Hive
|
||||
await Hive.initFlutter(_configDirectory);
|
||||
print('Hive初始化完成');
|
||||
|
||||
// 打开配置Box
|
||||
_configBox = await Hive.openBox('app_config');
|
||||
print('配置Box打开成功,包含 ${_configBox.length} 个条目');
|
||||
|
||||
// 打印所有键值,用于调试
|
||||
print('配置Box中的所有键: ${_configBox.keys.toList()}');
|
||||
|
||||
// 先创建实例并注册所有配置类型
|
||||
// 由于AppConfig是单例模式,这里不需要显式创建实例
|
||||
|
||||
// 加载并验证配置
|
||||
await _instance._loadAndValidateConfig();
|
||||
print('配置加载和验证完成');
|
||||
|
||||
_initialized = true;
|
||||
} catch (e) {
|
||||
print('AppConfig初始化失败: $e');
|
||||
// 尝试使用备用目录
|
||||
try {
|
||||
// 使用应用文档目录作为备用目录
|
||||
final appDocDir = Directory(path.join(
|
||||
Directory.current.path,
|
||||
'config',
|
||||
));
|
||||
|
||||
// 确保目录存在
|
||||
if (!appDocDir.existsSync()) {
|
||||
appDocDir.createSync(recursive: true);
|
||||
}
|
||||
|
||||
_configDirectory = appDocDir.path;
|
||||
print('尝试使用备用目录: $_configDirectory');
|
||||
|
||||
await Hive.initFlutter(_configDirectory);
|
||||
_configBox = await Hive.openBox('app_config');
|
||||
|
||||
await _instance._loadAndValidateConfig();
|
||||
_initialized = true;
|
||||
print('使用备用目录初始化成功');
|
||||
} catch (e2) {
|
||||
print('备用初始化也失败: $e2');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 加载并验证所有配置
|
||||
Future<void> _loadAndValidateConfig() async {
|
||||
print('开始加载配置...');
|
||||
// 加载所有注册的配置
|
||||
for (var entry in _configFactories.entries) {
|
||||
final type = entry.key;
|
||||
final fromJson = entry.value;
|
||||
final defaultValue = _configModels[type]!;
|
||||
final configKey = defaultValue.configKey;
|
||||
|
||||
print('加载配置: $configKey (${type.toString()})');
|
||||
_configModels[type] = _loadConfig(
|
||||
configKey,
|
||||
fromJson as ConfigModel Function(Map<String, dynamic>),
|
||||
defaultValue,
|
||||
);
|
||||
}
|
||||
|
||||
// 更新服务器列表缓存
|
||||
_serverConfigs = (getModel<ServerListConfig>()).servers;
|
||||
print('服务器列表缓存更新完成,共 ${_serverConfigs.length} 个服务器');
|
||||
|
||||
// 打印服务器列表,用于调试
|
||||
for (var server in _serverConfigs) {
|
||||
print(
|
||||
'服务器: ${server.name} (${server.url}), 选中: ${server.selected}, 协议: TCP=${server.tcp}, UDP=${server.udp}, WS=${server.ws}, WSS=${server.wss}, QUIC=${server.quic}');
|
||||
}
|
||||
}
|
||||
|
||||
// 通用配置加载方法
|
||||
T _loadConfig<T extends ConfigModel>(
|
||||
String key,
|
||||
T Function(Map<String, dynamic>) fromJson,
|
||||
T defaultValue,
|
||||
) {
|
||||
final dynamic config = _configBox.get(key);
|
||||
print('读取配置 $key: ${config != null ? '存在' : '不存在'}');
|
||||
|
||||
if (config != null) {
|
||||
try {
|
||||
print('配置内容: $config');
|
||||
final result = fromJson(Map<String, dynamic>.from(config));
|
||||
print('配置解析成功');
|
||||
return result;
|
||||
} catch (e) {
|
||||
print('配置解析失败: $e');
|
||||
// 如果解析失败,使用默认值
|
||||
}
|
||||
}
|
||||
|
||||
// 保存默认值
|
||||
final defaultMap = defaultValue.toJson();
|
||||
print('使用默认配置: $defaultMap');
|
||||
_configBox.put(key, defaultMap);
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// 获取配置
|
||||
T getModel<T extends ConfigModel>() {
|
||||
if (!_configModels.containsKey(T)) {
|
||||
throw Exception('未注册的配置类型: $T');
|
||||
}
|
||||
return _configModels[T] as T;
|
||||
}
|
||||
|
||||
// 更新配置
|
||||
Future<void> updateModel<T extends ConfigModel>(T newConfig) async {
|
||||
if (!_configModels.containsKey(T)) {
|
||||
throw Exception('未注册的配置类型: $T');
|
||||
}
|
||||
|
||||
_configModels[T] = newConfig;
|
||||
await _configBox.put(newConfig.configKey, newConfig.toJson());
|
||||
}
|
||||
|
||||
// 缓存的服务器配置列表
|
||||
late List<ServerConfig> _serverConfigs;
|
||||
|
||||
// 保存服务器列表
|
||||
Future<void> _saveServerList() async {
|
||||
try {
|
||||
// 打印调试信息
|
||||
print('保存服务器列表: ${_serverConfigs.length} 个服务器');
|
||||
|
||||
// 确保配置模型已更新
|
||||
await updateModel<ServerListConfig>(
|
||||
ServerListConfig(servers: _serverConfigs));
|
||||
|
||||
// 强制刷新 Hive 存储
|
||||
await _configBox.flush();
|
||||
|
||||
print('服务器列表保存完成');
|
||||
} catch (e) {
|
||||
print('保存服务器列表时出错: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// 以下是为了保持原有API的getter和setter
|
||||
|
||||
// 主题设置
|
||||
ThemeConfig get theme => getModel<ThemeConfig>();
|
||||
|
||||
ThemeMode get themeMode {
|
||||
final String? value = _configManager.get<String>('theme.mode');
|
||||
final String mode = theme.mode.toLowerCase();
|
||||
return ThemeMode.values.firstWhere(
|
||||
(mode) => mode.toString() == 'ThemeMode.$value',
|
||||
(m) => m.toString().split('.').last.toLowerCase() == mode,
|
||||
orElse: () => ThemeMode.system,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> setThemeMode(ThemeMode mode) async {
|
||||
final modeString = mode.toString().split('.').last.toLowerCase();
|
||||
_configManager.set('theme.mode', modeString);
|
||||
await _configManager.save();
|
||||
await updateModel<ThemeConfig>(ThemeConfig(
|
||||
mode: modeString,
|
||||
seedColor: theme.seedColor,
|
||||
));
|
||||
}
|
||||
|
||||
// 主题色设置
|
||||
Color get seedColor {
|
||||
final int? value = _configManager.get<int>('theme.seedColor');
|
||||
return value != null ? Color(value) : Colors.blue;
|
||||
}
|
||||
Color get seedColor => Color(theme.seedColor);
|
||||
|
||||
Future<void> setSeedColor(Color color) async {
|
||||
_configManager.set('theme.seedColor', color.value);
|
||||
await _configManager.save();
|
||||
await updateModel<ThemeConfig>(ThemeConfig(
|
||||
mode: theme.mode,
|
||||
seedColor: color.value,
|
||||
));
|
||||
}
|
||||
|
||||
// 服务器列表设置
|
||||
List<String> get serverList {
|
||||
final List? value = _configManager.get<List>('server.list');
|
||||
return value?.cast<String>() ?? ['public.easytier.cn:11010'];
|
||||
List<Map<String, dynamic>> get serverList {
|
||||
return _serverConfigs.map((server) => server.toJson()).toList();
|
||||
}
|
||||
|
||||
Future<void> setServerList(List<String> servers) async {
|
||||
_configManager.set('server.list', servers);
|
||||
await _configManager.save();
|
||||
Future<void> setServerList(List<Map<String, dynamic>> servers) async {
|
||||
try {
|
||||
print('设置服务器列表: ${servers.length} 个服务器');
|
||||
|
||||
_serverConfigs = servers.map((server) {
|
||||
// 确保所有必要的字段都存在
|
||||
return ServerConfig(
|
||||
url: server['url'] ?? '',
|
||||
name: server['name'] ?? '',
|
||||
selected: server['selected'] ?? false,
|
||||
tcp: server['tcp'] ?? true,
|
||||
udp: server['udp'] ?? true,
|
||||
ws: server['ws'] ?? false,
|
||||
wss: server['wss'] ?? false,
|
||||
quic: server['quic'] ?? false,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
await _saveServerList();
|
||||
|
||||
// 打印保存后的服务器列表,用于调试
|
||||
print(
|
||||
'服务器列表已更新: ${_serverConfigs.map((s) => '${s.name}(${s.url})').join(', ')}');
|
||||
} catch (e) {
|
||||
print('设置服务器列表时出错: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// 当前选中的服务器设置
|
||||
String get currentServer {
|
||||
return _configManager.get<String>('server.current') ??
|
||||
'public.easytier.cn:11010';
|
||||
}
|
||||
|
||||
Future<void> setCurrentServer(String server) async {
|
||||
_configManager.set('server.current', server);
|
||||
await _configManager.save();
|
||||
}
|
||||
|
||||
// 房间名设置
|
||||
String get roomName {
|
||||
return _configManager.get<String>('room.name') ?? 'kevin';
|
||||
}
|
||||
// 房间配置
|
||||
RoomConfig get room => getModel<RoomConfig>();
|
||||
String get roomName => room.name;
|
||||
String get roomPassword => room.password;
|
||||
|
||||
Future<void> setRoomName(String name) async {
|
||||
_configManager.set('room.name', name);
|
||||
await _configManager.save();
|
||||
}
|
||||
|
||||
// 房间密码设置
|
||||
String get roomPassword {
|
||||
return _configManager.get<String>('room.password') ?? 'kevin';
|
||||
await updateModel<RoomConfig>(RoomConfig(
|
||||
name: name,
|
||||
password: room.password,
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> setRoomPassword(String password) async {
|
||||
_configManager.set('room.password', password);
|
||||
await _configManager.save();
|
||||
await updateModel<RoomConfig>(RoomConfig(
|
||||
name: room.name,
|
||||
password: password,
|
||||
));
|
||||
}
|
||||
|
||||
// 用户名设置
|
||||
String get username {
|
||||
return _configManager.get<String>('user.name') ?? Platform.localHostname;
|
||||
}
|
||||
// 用户配置
|
||||
UserConfig get user => getModel<UserConfig>();
|
||||
String get username => user.name;
|
||||
|
||||
Future<void> setUsername(String name) async {
|
||||
_configManager.set('user.name', name);
|
||||
await _configManager.save();
|
||||
await updateModel<UserConfig>(UserConfig(name: name));
|
||||
}
|
||||
|
||||
// 虚拟IP设置
|
||||
String get virtualIP {
|
||||
return _configManager.get<String>('network.virtualIP') ?? '';
|
||||
}
|
||||
// 网络配置
|
||||
NetworkConfig get network => getModel<NetworkConfig>();
|
||||
String get virtualIP => network.virtualIP;
|
||||
bool get dynamicIP => network.dynamicIP;
|
||||
|
||||
Future<void> setVirtualIP(String ip) async {
|
||||
_configManager.set('network.virtualIP', ip);
|
||||
await _configManager.save();
|
||||
}
|
||||
|
||||
// 关闭按钮进入托盘
|
||||
bool get closeToTray {
|
||||
return _configManager.get<bool>('system.closeToTray') ?? true;
|
||||
}
|
||||
|
||||
Future<void> setCloseToTray(bool enabled) async {
|
||||
_configManager.set('system.closeToTray', enabled);
|
||||
await _configManager.save();
|
||||
}
|
||||
|
||||
// 动态获取IP设置
|
||||
bool get dynamicIP {
|
||||
return _configManager.get<bool>('network.dynamicIP') ?? true;
|
||||
await updateModel<NetworkConfig>(NetworkConfig(
|
||||
virtualIP: ip,
|
||||
dynamicIP: network.dynamicIP,
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> setDynamicIP(bool enabled) async {
|
||||
_configManager.set('network.dynamicIP', enabled);
|
||||
await _configManager.save();
|
||||
await updateModel<NetworkConfig>(NetworkConfig(
|
||||
virtualIP: network.virtualIP,
|
||||
dynamicIP: enabled,
|
||||
));
|
||||
}
|
||||
|
||||
// 全局ping开关设置
|
||||
bool get enablePing {
|
||||
return _configManager.get<bool>('system.enablePing') ?? true;
|
||||
|
||||
// 系统配置
|
||||
SystemConfig get system => getModel<SystemConfig>();
|
||||
bool get closeToTray => system.closeToTray;
|
||||
bool get enablePing => system.enablePing;
|
||||
|
||||
Future<void> setCloseToTray(bool enabled) async {
|
||||
await updateModel<SystemConfig>(SystemConfig(
|
||||
closeToTray: enabled,
|
||||
enablePing: system.enablePing,
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> setEnablePing(bool enabled) async {
|
||||
_configManager.set('system.enablePing', enabled);
|
||||
await _configManager.save();
|
||||
await updateModel<SystemConfig>(SystemConfig(
|
||||
closeToTray: system.closeToTray,
|
||||
enablePing: enabled,
|
||||
));
|
||||
}
|
||||
|
||||
// 通用配置获取方法
|
||||
T? getConfig<T>(String key) {
|
||||
return _configBox.get(key) as T?;
|
||||
}
|
||||
|
||||
// 通用配置设置方法
|
||||
Future<void> setConfig<T>(String key, T value) async {
|
||||
await _configBox.put(key, value);
|
||||
|
||||
// 更新缓存的配置对象
|
||||
await _loadAndValidateConfig();
|
||||
}
|
||||
|
||||
// 高级配置
|
||||
AdvancedConfig get advanced => getModel<AdvancedConfig>();
|
||||
|
||||
// 高级配置 getter
|
||||
String get defaultProtocol => advanced.defaultProtocol;
|
||||
String get devName => advanced.devName;
|
||||
bool get enableEncryption => advanced.enableEncryption;
|
||||
bool get enableIpv6 => advanced.enableIpv6;
|
||||
int get mtu => advanced.mtu;
|
||||
bool get latencyFirst => advanced.latencyFirst;
|
||||
bool get enableExitNode => advanced.enableExitNode;
|
||||
bool get proxyForwardBySystem => advanced.proxyForwardBySystem;
|
||||
bool get noTun => advanced.noTun;
|
||||
bool get useSmoltcp => advanced.useSmoltcp;
|
||||
String get relayNetworkWhitelist => advanced.relayNetworkWhitelist;
|
||||
bool get disableP2p => advanced.disableP2p;
|
||||
bool get relayAllPeerRpc => advanced.relayAllPeerRpc;
|
||||
bool get disableUdpHolePunching => advanced.disableUdpHolePunching;
|
||||
bool get multiThread => advanced.multiThread;
|
||||
String get dataCompressAlgo => advanced.dataCompressAlgo;
|
||||
bool get bindDevice => advanced.bindDevice;
|
||||
bool get enableKcpProxy => advanced.enableKcpProxy;
|
||||
bool get disableKcpInput => advanced.disableKcpInput;
|
||||
bool get disableRelayKcp => advanced.disableRelayKcp;
|
||||
|
||||
// 高级配置 setter 方法
|
||||
Future<void> setDefaultProtocol(String value) async {
|
||||
await updateModel<AdvancedConfig>(AdvancedConfig(
|
||||
defaultProtocol: value,
|
||||
devName: advanced.devName,
|
||||
enableEncryption: advanced.enableEncryption,
|
||||
enableIpv6: advanced.enableIpv6,
|
||||
mtu: advanced.mtu,
|
||||
latencyFirst: advanced.latencyFirst,
|
||||
enableExitNode: advanced.enableExitNode,
|
||||
proxyForwardBySystem: advanced.proxyForwardBySystem,
|
||||
noTun: advanced.noTun,
|
||||
useSmoltcp: advanced.useSmoltcp,
|
||||
relayNetworkWhitelist: advanced.relayNetworkWhitelist,
|
||||
disableP2p: advanced.disableP2p,
|
||||
relayAllPeerRpc: advanced.relayAllPeerRpc,
|
||||
disableUdpHolePunching: advanced.disableUdpHolePunching,
|
||||
multiThread: advanced.multiThread,
|
||||
dataCompressAlgo: advanced.dataCompressAlgo,
|
||||
bindDevice: advanced.bindDevice,
|
||||
enableKcpProxy: advanced.enableKcpProxy,
|
||||
disableKcpInput: advanced.disableKcpInput,
|
||||
disableRelayKcp: advanced.disableRelayKcp,
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> setDevName(String value) async {
|
||||
await updateModel<AdvancedConfig>(AdvancedConfig(
|
||||
defaultProtocol: advanced.defaultProtocol,
|
||||
devName: value,
|
||||
enableEncryption: advanced.enableEncryption,
|
||||
enableIpv6: advanced.enableIpv6,
|
||||
mtu: advanced.mtu,
|
||||
latencyFirst: advanced.latencyFirst,
|
||||
enableExitNode: advanced.enableExitNode,
|
||||
proxyForwardBySystem: advanced.proxyForwardBySystem,
|
||||
noTun: advanced.noTun,
|
||||
useSmoltcp: advanced.useSmoltcp,
|
||||
relayNetworkWhitelist: advanced.relayNetworkWhitelist,
|
||||
disableP2p: advanced.disableP2p,
|
||||
relayAllPeerRpc: advanced.relayAllPeerRpc,
|
||||
disableUdpHolePunching: advanced.disableUdpHolePunching,
|
||||
multiThread: advanced.multiThread,
|
||||
dataCompressAlgo: advanced.dataCompressAlgo,
|
||||
bindDevice: advanced.bindDevice,
|
||||
enableKcpProxy: advanced.enableKcpProxy,
|
||||
disableKcpInput: advanced.disableKcpInput,
|
||||
disableRelayKcp: advanced.disableRelayKcp,
|
||||
));
|
||||
}
|
||||
|
||||
// 更新高级配置的通用方法
|
||||
Future<void> updateAdvancedConfig({
|
||||
String? defaultProtocol,
|
||||
String? devName,
|
||||
bool? enableEncryption,
|
||||
bool? enableIpv6,
|
||||
int? mtu,
|
||||
bool? latencyFirst,
|
||||
bool? enableExitNode,
|
||||
bool? proxyForwardBySystem,
|
||||
bool? noTun,
|
||||
bool? useSmoltcp,
|
||||
String? relayNetworkWhitelist,
|
||||
bool? disableP2p,
|
||||
bool? relayAllPeerRpc,
|
||||
bool? disableUdpHolePunching,
|
||||
bool? multiThread,
|
||||
String? dataCompressAlgo,
|
||||
bool? bindDevice,
|
||||
bool? enableKcpProxy,
|
||||
bool? disableKcpInput,
|
||||
bool? disableRelayKcp,
|
||||
}) async {
|
||||
await updateModel<AdvancedConfig>(AdvancedConfig(
|
||||
defaultProtocol: defaultProtocol ?? advanced.defaultProtocol,
|
||||
devName: devName ?? advanced.devName,
|
||||
enableEncryption: enableEncryption ?? advanced.enableEncryption,
|
||||
enableIpv6: enableIpv6 ?? advanced.enableIpv6,
|
||||
mtu: mtu ?? advanced.mtu,
|
||||
latencyFirst: latencyFirst ?? advanced.latencyFirst,
|
||||
enableExitNode: enableExitNode ?? advanced.enableExitNode,
|
||||
proxyForwardBySystem:
|
||||
proxyForwardBySystem ?? advanced.proxyForwardBySystem,
|
||||
noTun: noTun ?? advanced.noTun,
|
||||
useSmoltcp: useSmoltcp ?? advanced.useSmoltcp,
|
||||
relayNetworkWhitelist:
|
||||
relayNetworkWhitelist ?? advanced.relayNetworkWhitelist,
|
||||
disableP2p: disableP2p ?? advanced.disableP2p,
|
||||
relayAllPeerRpc: relayAllPeerRpc ?? advanced.relayAllPeerRpc,
|
||||
disableUdpHolePunching:
|
||||
disableUdpHolePunching ?? advanced.disableUdpHolePunching,
|
||||
multiThread: multiThread ?? advanced.multiThread,
|
||||
dataCompressAlgo: dataCompressAlgo ?? advanced.dataCompressAlgo,
|
||||
bindDevice: bindDevice ?? advanced.bindDevice,
|
||||
enableKcpProxy: enableKcpProxy ?? advanced.enableKcpProxy,
|
||||
disableKcpInput: disableKcpInput ?? advanced.disableKcpInput,
|
||||
disableRelayKcp: disableRelayKcp ?? advanced.disableRelayKcp,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
+39
-3
@@ -25,7 +25,10 @@ class ConfigManager {
|
||||
final yamlMap = loadYaml(content);
|
||||
_config = _mergeConfigs(_convertYaml(yamlMap));
|
||||
} catch (e) {
|
||||
throw Exception('配置文件解析失败: $e');
|
||||
// 提供更详细的错误信息并使用默认配置
|
||||
print('配置文件解析失败: $e');
|
||||
print('将使用默认配置继续运行。请检查配置文件格式是否正确。');
|
||||
_config = Map.from(defaultConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +64,15 @@ class ConfigManager {
|
||||
current = current.putIfAbsent(key, () => <String, dynamic>{});
|
||||
}
|
||||
|
||||
current[keys.last] = value;
|
||||
// 类型转换确保值类型兼容性
|
||||
if (value is Map<String, dynamic>) {
|
||||
current[keys.last] = Map<String, Object>.from(value);
|
||||
} else if (value is List<Map<String, dynamic>>) {
|
||||
current[keys.last] =
|
||||
value.map((e) => Map<String, Object>.from(e)).toList();
|
||||
} else {
|
||||
current[keys.last] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// 合并用户配置与默认配置
|
||||
@@ -135,7 +146,13 @@ class ConfigManager {
|
||||
for (final item in list) {
|
||||
buffer.write('$prefix- ');
|
||||
if (item is Map) {
|
||||
_writeMap(item.cast<String, dynamic>(), buffer, indent + 1);
|
||||
if (item.isEmpty) {
|
||||
buffer.writeln('{}');
|
||||
} else {
|
||||
buffer.writeln();
|
||||
// 注意这里使用了正确的缩进级别
|
||||
_writeMapInList(item.cast<String, dynamic>(), buffer, indent + 1);
|
||||
}
|
||||
} else if (item is List) {
|
||||
_writeList(item, buffer, indent + 1);
|
||||
} else {
|
||||
@@ -144,6 +161,25 @@ class ConfigManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// 专门用于处理列表中的Map项,确保正确缩进
|
||||
void _writeMapInList(
|
||||
Map<String, dynamic> map, StringBuffer buffer, int indent) {
|
||||
final prefix = ' ' * indent;
|
||||
|
||||
map.forEach((key, value) {
|
||||
buffer.write('$prefix$key: ');
|
||||
|
||||
if (value is Map<String, dynamic>) {
|
||||
buffer.writeln();
|
||||
_writeMap(value, buffer, indent + 1);
|
||||
} else if (value is List) {
|
||||
_writeList(value, buffer, indent + 1);
|
||||
} else {
|
||||
buffer.writeln(_formatValue(value));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// 格式值处理
|
||||
String _formatValue(dynamic value) {
|
||||
if (value is String) return '"$value"';
|
||||
|
||||
@@ -40,12 +40,12 @@ class NavigationConfig {
|
||||
currentThemeMode: currentThemeMode,
|
||||
)),
|
||||
),
|
||||
// NavItem(
|
||||
// label: '房间',
|
||||
// icon: Icons.room_outlined,
|
||||
// selectedIcon: Icons.room,
|
||||
// pageBuilder: () => _getOrCreatePage(1, () => const RoomPage()),
|
||||
// ),
|
||||
NavItem(
|
||||
label: '房间',
|
||||
icon: Icons.room_outlined,
|
||||
selectedIcon: Icons.room,
|
||||
pageBuilder: () => _getOrCreatePage(1, () => const RoomPage()),
|
||||
),
|
||||
NavItem(
|
||||
label: '设置',
|
||||
icon: Icons.settings_outlined,
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
Future<void> setupWindow() async {
|
||||
await windowManager.ensureInitialized();
|
||||
WindowOptions windowOptions = const WindowOptions(
|
||||
size: Size(850, 520),
|
||||
minimumSize: Size(300, 300),
|
||||
@@ -9,6 +10,7 @@ Future<void> setupWindow() async {
|
||||
backgroundColor: Colors.transparent,
|
||||
skipTaskbar: false,
|
||||
titleBarStyle: TitleBarStyle.hidden, // 隐藏标题栏
|
||||
title: "Astral", // 添加窗口标题
|
||||
);
|
||||
|
||||
await windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||
|
||||
+17
-6
@@ -1,26 +1,37 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:astral/src/rust/frb_generated.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'dart:io' show Platform, Directory;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'app.dart';
|
||||
import 'config/windowconfiguration.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'config/app_config.dart';
|
||||
import 'utils/kv_state.dart';
|
||||
import 'package:provider/provider.dart'; // 添加这一行
|
||||
import 'package:astral/utils/app_info.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
// 初始化应用信息
|
||||
await AppInfoUtil.init();
|
||||
await windowManager.ensureInitialized();
|
||||
// 设置窗口属性
|
||||
await setupWindow();
|
||||
|
||||
// 为Android平台设置配置目录
|
||||
if (Platform.isAndroid) {
|
||||
final appDir = await getApplicationDocumentsDirectory();
|
||||
AppConfig.setConfigDir(appDir.path);
|
||||
}
|
||||
|
||||
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
|
||||
await windowManager.ensureInitialized();
|
||||
// 设置窗口属性
|
||||
await setupWindow();
|
||||
}
|
||||
|
||||
await AppConfig.init();
|
||||
// 初始化应用信息
|
||||
await RustLib.init();
|
||||
runApp(
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => KM(),
|
||||
ProviderScope(
|
||||
child: const MyApp(),
|
||||
),
|
||||
);
|
||||
|
||||
+188
-85
@@ -1,13 +1,17 @@
|
||||
// 导入必要的包
|
||||
import 'dart:convert';
|
||||
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'package:astral/config/app_config.dart';
|
||||
import 'package:astral/src/rust/api/simple.dart';
|
||||
import 'package:astral/src/rust/frb_generated.dart';
|
||||
import 'package:astral/utils/kv_state.dart';
|
||||
import 'package:astral/utils/app_info.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'dart:async';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart'; // 替换 provider 导入
|
||||
import '../widgets/card.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import '../utils/runin.dart';
|
||||
@@ -31,7 +35,7 @@ String _intToIpv4String(int addr) {
|
||||
|
||||
/// 首页组件
|
||||
/// 用于显示应用的主页面,包含主题切换和问候功能
|
||||
class HomePage extends StatefulWidget {
|
||||
class HomePage extends ConsumerStatefulWidget {
|
||||
// 主题模式切换回调函数
|
||||
final Function toggleThemeMode;
|
||||
// 主题色更改回调函数
|
||||
@@ -48,10 +52,10 @@ class HomePage extends StatefulWidget {
|
||||
});
|
||||
|
||||
@override
|
||||
State<HomePage> createState() => _HomePageState();
|
||||
ConsumerState<HomePage> createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> {
|
||||
class _HomePageState extends ConsumerState<HomePage> {
|
||||
int connectionTimeoutCounter = 0;
|
||||
// 当前连接状态
|
||||
ConnectionState _connectionState = ConnectionState.notStarted;
|
||||
@@ -73,7 +77,8 @@ class _HomePageState extends State<HomePage> {
|
||||
|
||||
bool _isAutoIP = true; // 只用于控制IP自动/手动模式
|
||||
|
||||
String Serverip = "";
|
||||
List<String> Serverip = [""];
|
||||
List<ServerConfig> Serveripz = [];
|
||||
int _uploadBytes = 0;
|
||||
int _downloadBytes = 0;
|
||||
int _lastUploadBytes = 0;
|
||||
@@ -115,45 +120,47 @@ class _HomePageState extends State<HomePage> {
|
||||
_roomPasswordControllerFocusNode = FocusNode();
|
||||
_roomPasswordControllerFocusNode.addListener(_onRoomPasswordFocusChange);
|
||||
|
||||
// 修改卡片构建器列表,添加版本信息卡片
|
||||
// 修改卡片构建器列表,添加服务器列表卡片
|
||||
_cardBuilders = [
|
||||
_buildNetworkStatusCard,
|
||||
_buildUserInfoCard,
|
||||
_buildRoomInfoCard,
|
||||
_buildServerListCard,
|
||||
_buildVersionInfoCard,
|
||||
];
|
||||
// 启动内存监控
|
||||
}
|
||||
|
||||
// 添加焦点变化监听方法
|
||||
void _onVirtualIPFocusChange() {
|
||||
if (!_virtualIPFocusNode.hasFocus && !_isAutoIP) {
|
||||
// 当失去焦点且不是自动IP模式时更新值
|
||||
Provider.of<KM>(context, listen: false).virtualIP =
|
||||
_virtualIPController.text;
|
||||
ref
|
||||
.read(virtualIPProvider.notifier)
|
||||
.setVirtualIP(_virtualIPController.text);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加用户名焦点变化监听方法
|
||||
void _onUsernameFocusChange() {
|
||||
if (!_usernameControllerFocusNode.hasFocus) {
|
||||
Provider.of<KM>(context, listen: false).username =
|
||||
_usernameController.text;
|
||||
ref.read(usernameProvider.notifier).setUsername(_usernameController.text);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加房间名焦点变化监听方法
|
||||
void _onRoomNameFocusChange() {
|
||||
if (!_roomNameControllerFocusNode.hasFocus) {
|
||||
Provider.of<KM>(context, listen: false).roomName =
|
||||
_roomNameController.text;
|
||||
ref.read(roomNameProvider.notifier).setRoomName(_roomNameController.text);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加房间密码焦点变化监听方法
|
||||
void _onRoomPasswordFocusChange() {
|
||||
if (!_roomPasswordControllerFocusNode.hasFocus) {
|
||||
Provider.of<KM>(context, listen: false).roomPassword =
|
||||
_roomPasswordController.text;
|
||||
ref
|
||||
.read(roomPasswordProvider.notifier)
|
||||
.setRoomPassword(_roomPasswordController.text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,19 +192,84 @@ class _HomePageState extends State<HomePage> {
|
||||
}
|
||||
|
||||
void toggleRunning() {
|
||||
final km = Provider.of<KM>(context, listen: false);
|
||||
setState(() {
|
||||
isRunning = !isRunning;
|
||||
if (isRunning) {
|
||||
// 切换到连接中状态
|
||||
_connectionState = ConnectionState.connecting;
|
||||
|
||||
//利用 Serveripz 重组
|
||||
List<String> ssServerip = [];
|
||||
print(ssServerip);
|
||||
for (var item in Serveripz) {
|
||||
if (item.tcp) {
|
||||
ssServerip.add("tcp://" + item.url);
|
||||
}
|
||||
if (item.udp) {
|
||||
ssServerip.add("udp://" + item.url);
|
||||
}
|
||||
if (item.ws) {
|
||||
ssServerip.add("ws://" + item.url);
|
||||
}
|
||||
if (item.wss) {
|
||||
ssServerip.add("wss://" + item.url);
|
||||
}
|
||||
if (item.quic) {
|
||||
ssServerip.add("quic://" + item.url);
|
||||
}
|
||||
}
|
||||
// 复制
|
||||
createServer(
|
||||
username: username,
|
||||
enableDhcp: _isAutoIP,
|
||||
specifiedIp: publicIP,
|
||||
roomName: roomName,
|
||||
roomPassword: roomPassword,
|
||||
severurl: Serverip);
|
||||
severurl: ssServerip,
|
||||
flag: FlagsC(
|
||||
defaultProtocol:
|
||||
ref.read(advancedConfigProvider)['defaultProtocol'] ??
|
||||
"tcp",
|
||||
devName: ref.read(advancedConfigProvider)['devName'] ?? "",
|
||||
enableEncryption:
|
||||
ref.read(advancedConfigProvider)['enableEncryption'] ??
|
||||
true,
|
||||
enableIpv6:
|
||||
ref.read(advancedConfigProvider)['enableIpv6'] ?? true,
|
||||
mtu: ref.read(advancedConfigProvider)['mtu'] ?? 1380,
|
||||
multiThread:
|
||||
ref.read(advancedConfigProvider)['multiThread'] ?? true,
|
||||
latencyFirst:
|
||||
ref.read(advancedConfigProvider)['latencyFirst'] ?? false,
|
||||
enableExitNode:
|
||||
ref.read(advancedConfigProvider)['enableExitNode'] ?? false,
|
||||
noTun: ref.read(advancedConfigProvider)['noTun'] ?? false,
|
||||
useSmoltcp:
|
||||
ref.read(advancedConfigProvider)['useSmoltcp'] ?? false,
|
||||
relayNetworkWhitelist:
|
||||
ref.read(advancedConfigProvider)['relayNetworkWhitelist'] ??
|
||||
"*",
|
||||
disableP2P:
|
||||
ref.read(advancedConfigProvider)['disableP2p'] ?? false,
|
||||
relayAllPeerRpc:
|
||||
ref.read(advancedConfigProvider)['relayAllPeerRpc'] ??
|
||||
false,
|
||||
disableUdpHolePunching:
|
||||
ref.read(advancedConfigProvider)['disableUdpHolePunching'] ??
|
||||
false,
|
||||
dataCompressAlgo: ref.read(advancedConfigProvider)['dataCompressAlgo'] ==
|
||||
"Invalid"
|
||||
? 0
|
||||
: ref.read(advancedConfigProvider)['dataCompressAlgo'] == "None"
|
||||
? 1
|
||||
: ref.read(advancedConfigProvider)['dataCompressAlgo'] == "Zstd"
|
||||
? 2
|
||||
: 1,
|
||||
bindDevice: ref.read(advancedConfigProvider)['bindDevice'] ?? true,
|
||||
enableKcpProxy: ref.read(advancedConfigProvider)['enableKcpProxy'] ?? false,
|
||||
disableKcpInput: ref.read(advancedConfigProvider)['disableKcpInput'] ?? false,
|
||||
disableRelayKcp: ref.read(advancedConfigProvider)['disableRelayKcp'] ?? true,
|
||||
proxyForwardBySystem: ref.read(advancedConfigProvider)['proxyForwardBySystem'] ?? false));
|
||||
|
||||
// 不再使用固定延迟模拟连接成功,而是通过定时检查IP来确定连接状态
|
||||
timer = Timer.periodic(const Duration(seconds: 1), (timer) async {
|
||||
@@ -212,8 +284,7 @@ class _HomePageState extends State<HomePage> {
|
||||
|
||||
// 获取网络状态
|
||||
final networkStatus = await getNetworkStatus();
|
||||
final km = Provider.of<KM>(context, listen: false);
|
||||
km.nodes = networkStatus.nodes;
|
||||
ref.read(nodesProvider.notifier).setNodes(networkStatus.nodes);
|
||||
|
||||
// 更新网络流量数据
|
||||
_updateNetworkStats(networkStatus.nodes);
|
||||
@@ -222,7 +293,7 @@ class _HomePageState extends State<HomePage> {
|
||||
if (version != null) {
|
||||
String ipStr = _intToIpv4String(version);
|
||||
if (publicIP != ipStr) {
|
||||
km.virtualIP = ipStr;
|
||||
ref.read(virtualIPProvider.notifier).setVirtualIP(ipStr);
|
||||
}
|
||||
|
||||
// 如果当前状态还是连接中,则更新为已连接
|
||||
@@ -244,8 +315,8 @@ class _HomePageState extends State<HomePage> {
|
||||
});
|
||||
|
||||
closeAllServer();
|
||||
// 清空玩家列表数据
|
||||
Provider.of<KM>(context, listen: false).nodes = [];
|
||||
// 清空玩家列表数据
|
||||
ref.read(nodesProvider.notifier).setNodes([]);
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
@@ -268,13 +339,12 @@ class _HomePageState extends State<HomePage> {
|
||||
runningTime += const Duration(seconds: 1);
|
||||
});
|
||||
});
|
||||
// 移除原来的Future.delayed模拟连接成功的代码
|
||||
} else {
|
||||
// 停止时重置状态
|
||||
_connectionState = ConnectionState.notStarted;
|
||||
closeAllServer();
|
||||
// 清空玩家列表数据
|
||||
Provider.of<KM>(context, listen: false).nodes = [];
|
||||
ref.read(nodesProvider.notifier).setNodes([]);
|
||||
timer?.cancel();
|
||||
runningTime = Duration.zero;
|
||||
// 重置网络统计数据
|
||||
@@ -284,7 +354,6 @@ class _HomePageState extends State<HomePage> {
|
||||
_lastDownloadBytes = 0;
|
||||
uploadSpeed = 0;
|
||||
downloadSpeed = 0;
|
||||
//connectionTimeoutCounter
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -295,7 +364,7 @@ class _HomePageState extends State<HomePage> {
|
||||
|
||||
int totalUploadBytes = 0;
|
||||
int totalDownloadBytes = 0;
|
||||
String myIP = Provider.of<KM>(context, listen: false).virtualIP;
|
||||
String myIP = ref.read(virtualIPProvider);
|
||||
|
||||
// 查找本机节点
|
||||
for (var node in nodes) {
|
||||
@@ -342,22 +411,22 @@ class _HomePageState extends State<HomePage> {
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
final km = Provider.of<KM>(context);
|
||||
// 使用 Riverpod 读取数据
|
||||
_roomNameController.value = TextEditingValue(
|
||||
text: km.roomName,
|
||||
text: ref.read(roomNameProvider),
|
||||
selection: _roomNameController.selection,
|
||||
);
|
||||
_roomPasswordController.value = TextEditingValue(
|
||||
text: km.roomPassword,
|
||||
text: ref.read(roomPasswordProvider),
|
||||
selection: _roomPasswordController.selection,
|
||||
);
|
||||
_usernameController.value = TextEditingValue(
|
||||
text: km.username,
|
||||
text: ref.read(usernameProvider),
|
||||
selection: _usernameController.selection,
|
||||
);
|
||||
// 添加虚拟IP控制器的值同步
|
||||
_virtualIPController.value = TextEditingValue(
|
||||
text: km.virtualIP,
|
||||
text: ref.read(virtualIPProvider),
|
||||
selection: _virtualIPController.selection,
|
||||
);
|
||||
}
|
||||
@@ -365,15 +434,17 @@ class _HomePageState extends State<HomePage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
publicIP = Provider.of<KM>(context).virtualIP;
|
||||
_isAutoIP = Provider.of<KM>(context).dynamicIP; // 更新自动IP状态
|
||||
//我的房间
|
||||
roomName = Provider.of<KM>(context).roomName;
|
||||
//我的密码
|
||||
roomPassword = Provider.of<KM>(context).roomPassword;
|
||||
//我的用户名
|
||||
username = Provider.of<KM>(context).username;
|
||||
Serverip = Provider.of<KM>(context).serverIP;
|
||||
// 使用 Riverpod 读取数据
|
||||
publicIP = ref.watch(virtualIPProvider);
|
||||
_isAutoIP = ref.watch(dynamicIPProvider);
|
||||
roomName = ref.watch(roomNameProvider);
|
||||
roomPassword = ref.watch(roomPasswordProvider);
|
||||
username = ref.watch(usernameProvider);
|
||||
Serverip = ref.watch(serverIPProvider);
|
||||
// 从 selectedServerProvider 获取服务器配置列表
|
||||
final serverConfigs =
|
||||
ref.watch(selectedServerProvider) as List<ServerConfig>;
|
||||
Serveripz = serverConfigs;
|
||||
|
||||
// 使用 LayoutBuilder 来处理布局变化,同时保留状态
|
||||
return Scaffold(
|
||||
@@ -395,10 +466,7 @@ class _HomePageState extends State<HomePage> {
|
||||
childCount: _cardBuilders.length,
|
||||
itemBuilder: (context, index) {
|
||||
// 直接从列表中获取构建函数并调用
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(minHeight: 100),
|
||||
child: _cardBuilders[index](colorScheme),
|
||||
);
|
||||
return _cardBuilders[index](colorScheme);
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -573,7 +641,64 @@ class _HomePageState extends State<HomePage> {
|
||||
);
|
||||
}
|
||||
|
||||
// 添加服务器列表卡片
|
||||
Widget _buildServerListCard(ColorScheme colorScheme) {
|
||||
return Consumer(
|
||||
builder: (context, ref, child) {
|
||||
// 直接使用 serverIP 列表
|
||||
final serverUrls = ref.watch(serverIPProvider);
|
||||
|
||||
return FloatingCard(
|
||||
colorScheme: colorScheme,
|
||||
maxWidth: 600,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 标题栏
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.dns, color: colorScheme.primary, size: 22),
|
||||
const SizedBox(width: 8),
|
||||
const Text('当前服务器',
|
||||
style:
|
||||
TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// 当前选中的服务器
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: serverUrls.isEmpty ||
|
||||
(serverUrls.length == 1 && serverUrls[0].isEmpty)
|
||||
? [
|
||||
Chip(
|
||||
avatar: Icon(Icons.info_outline,
|
||||
size: 16, color: colorScheme.error),
|
||||
label: const Text('未选择服务器'),
|
||||
backgroundColor:
|
||||
colorScheme.errorContainer.withOpacity(0.3),
|
||||
)
|
||||
]
|
||||
: serverUrls
|
||||
.map((url) => Chip(
|
||||
avatar: Icon(Icons.dns,
|
||||
size: 16, color: colorScheme.primary),
|
||||
label: Text(url),
|
||||
backgroundColor: colorScheme.surfaceVariant,
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// 新增合并后的网络状态卡片(合并了流量统计和IP信息)
|
||||
// 修改网络状态卡片,去除流量统计部分
|
||||
Widget _buildNetworkStatusCard(ColorScheme colorScheme) {
|
||||
return FloatingCard(
|
||||
colorScheme: colorScheme,
|
||||
@@ -611,33 +736,6 @@ class _HomePageState extends State<HomePage> {
|
||||
|
||||
// IP信息部分
|
||||
_buildIPInfo('虚拟 IP', publicIP, Icons.public, colorScheme),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// 流量统计部分
|
||||
const Divider(),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.data_usage, color: colorScheme.primary, size: 18),
|
||||
const SizedBox(width: 8),
|
||||
Text('流量统计',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: colorScheme.primary,
|
||||
)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
_buildTrafficInfo('上传速度', '$uploadSpeed MB/s', Icons.upload,
|
||||
colorScheme.primary),
|
||||
_buildTrafficInfo('下载速度', '$downloadSpeed MB/s', Icons.download,
|
||||
colorScheme.secondary),
|
||||
],
|
||||
),
|
||||
|
||||
// 添加运行时间显示
|
||||
if (_connectionState == ConnectionState.connected) ...[
|
||||
@@ -689,8 +787,7 @@ class _HomePageState extends State<HomePage> {
|
||||
|
||||
// 优化用户信息卡片
|
||||
Widget _buildUserInfoCard(ColorScheme colorScheme) {
|
||||
final km = Provider.of<KM>(context, listen: false);
|
||||
final isValidIP = _isAutoIP || _isValidIPv4(km.virtualIP);
|
||||
final isValidIP = _isAutoIP || _isValidIPv4(ref.watch(virtualIPProvider));
|
||||
|
||||
return FloatingCard(
|
||||
colorScheme: colorScheme,
|
||||
@@ -735,8 +832,9 @@ class _HomePageState extends State<HomePage> {
|
||||
enabled: _connectionState != ConnectionState.connected,
|
||||
onEditingComplete: () {
|
||||
// 改为完成编辑时更新
|
||||
Provider.of<KM>(context, listen: false).username =
|
||||
_usernameController.text;
|
||||
ref
|
||||
.read(usernameProvider.notifier)
|
||||
.setUsername(_usernameController.text);
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
labelText: '用户名',
|
||||
@@ -762,8 +860,9 @@ class _HomePageState extends State<HomePage> {
|
||||
onEditingComplete: () {
|
||||
// 添加完成编辑回调
|
||||
if (!_isAutoIP) {
|
||||
Provider.of<KM>(context, listen: false).virtualIP =
|
||||
_virtualIPController.text;
|
||||
ref
|
||||
.read(virtualIPProvider.notifier)
|
||||
.setVirtualIP(_virtualIPController.text);
|
||||
}
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
@@ -785,10 +884,14 @@ class _HomePageState extends State<HomePage> {
|
||||
setState(() {
|
||||
_isAutoIP = value;
|
||||
});
|
||||
km.dynamicIP = value;
|
||||
ref
|
||||
.read(dynamicIPProvider.notifier)
|
||||
.setDynamicIP(value);
|
||||
// 切换模式时同步最新值
|
||||
if (!value) {
|
||||
km.virtualIP = _virtualIPController.text;
|
||||
ref
|
||||
.read(virtualIPProvider.notifier)
|
||||
.setVirtualIP(_virtualIPController.text);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
@@ -819,7 +922,6 @@ class _HomePageState extends State<HomePage> {
|
||||
|
||||
// 优化房间信息卡片
|
||||
Widget _buildRoomInfoCard(ColorScheme colorScheme) {
|
||||
final km = Provider.of<KM>(context, listen: false);
|
||||
return FloatingCard(
|
||||
colorScheme: colorScheme,
|
||||
maxWidth: 600,
|
||||
@@ -863,8 +965,9 @@ class _HomePageState extends State<HomePage> {
|
||||
enabled: _connectionState != ConnectionState.connected,
|
||||
onEditingComplete: () {
|
||||
// 改为完成编辑时更新
|
||||
Provider.of<KM>(context, listen: false).roomName =
|
||||
_roomNameController.text;
|
||||
ref
|
||||
.read(roomNameProvider.notifier)
|
||||
.setRoomName(_roomNameController.text);
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
labelText: '房间名称',
|
||||
@@ -881,9 +984,9 @@ class _HomePageState extends State<HomePage> {
|
||||
focusNode: _roomPasswordControllerFocusNode, // 添加焦点节点
|
||||
enabled: _connectionState != ConnectionState.connected,
|
||||
onEditingComplete: () {
|
||||
// 改为完成编辑时更新
|
||||
Provider.of<KM>(context, listen: false).roomPassword =
|
||||
_roomPasswordController.text;
|
||||
ref
|
||||
.read(roomPasswordProvider.notifier)
|
||||
.setRoomPassword(_roomPasswordController.text);
|
||||
},
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
|
||||
+17
-14
@@ -3,7 +3,7 @@ import 'package:flutter/services.dart'; // 添加这一行导入剪贴板服务
|
||||
|
||||
import 'package:astral/utils/kv_state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart'; // 替换 provider 导入
|
||||
import '../widgets/card.dart';
|
||||
|
||||
/// 玩家信息模型类
|
||||
@@ -37,14 +37,17 @@ class PlayerInfo {
|
||||
|
||||
/// 房间页面组件
|
||||
/// 用于显示所有玩家的信息
|
||||
class RoomPage extends StatefulWidget {
|
||||
class RoomPage extends ConsumerStatefulWidget {
|
||||
// 修改为 ConsumerStatefulWidget
|
||||
const RoomPage({super.key});
|
||||
|
||||
@override
|
||||
State<RoomPage> createState() => _RoomPageState();
|
||||
ConsumerState<RoomPage> createState() =>
|
||||
_RoomPageState(); // 修改为 ConsumerState
|
||||
}
|
||||
|
||||
class _RoomPageState extends State<RoomPage> {
|
||||
class _RoomPageState extends ConsumerState<RoomPage> {
|
||||
// 修改为 ConsumerState
|
||||
List<PlayerInfo> players = [];
|
||||
List<PlayerInfo> filteredPlayers = []; // 添加过滤后的玩家列表
|
||||
bool isLoading = true;
|
||||
@@ -88,6 +91,7 @@ class _RoomPageState extends State<RoomPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
// 使用 Riverpod 监听节点数据
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
@@ -109,11 +113,10 @@ class _RoomPageState extends State<RoomPage> {
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Consumer<KM>(
|
||||
builder: (context, km, child) {
|
||||
// 当 KM 更新时,这个 builder 会被重新调用
|
||||
body: Builder(
|
||||
builder: (context) {
|
||||
// 异步处理数据
|
||||
_processNodeData(km);
|
||||
_processNodeData();
|
||||
|
||||
if (isLoading) {
|
||||
return Center(
|
||||
@@ -203,9 +206,9 @@ class _RoomPageState extends State<RoomPage> {
|
||||
}
|
||||
|
||||
// 处理节点数据 - 合并了原来的两个相似方法
|
||||
Future<void> _processNodeData(KM km) async {
|
||||
Future<void> _processNodeData() async {
|
||||
try {
|
||||
final nodes = await km.nodes; // 获取最新的节点信息
|
||||
final nodes = await ref.read(nodesProvider); // 获取最新的节点信息
|
||||
|
||||
// 将节点数据转换为PlayerInfo对象
|
||||
List<PlayerInfo> nodePlayerInfos = [];
|
||||
@@ -216,8 +219,8 @@ class _RoomPageState extends State<RoomPage> {
|
||||
int downloadSpeed = 0;
|
||||
int sentPackets = 0;
|
||||
int receivedPackets = 0;
|
||||
String connectionType =
|
||||
_mapConnectionType(node.cost, node.ipv4, km.virtualIP);
|
||||
String connectionType = _mapConnectionType(
|
||||
node.cost, node.ipv4, ref.read(virtualIPProvider));
|
||||
|
||||
// 如果有连接信息,计算网络统计数据
|
||||
if (node.connections.isNotEmpty) {
|
||||
@@ -382,7 +385,7 @@ class _RoomPageState extends State<RoomPage> {
|
||||
_buildInfoRow(
|
||||
Icons.error_outline,
|
||||
'丢包率',
|
||||
'${player.packetLossRate}%',
|
||||
'${player.packetLossRate.toStringAsFixed(2)}%', // 修改这里,保留2位小数
|
||||
colorScheme,
|
||||
valueColor: _getPacketLossColor(player.packetLossRate),
|
||||
),
|
||||
@@ -551,7 +554,7 @@ class _RoomPageState extends State<RoomPage> {
|
||||
_buildInfoRow(
|
||||
Icons.error_outline,
|
||||
'丢包率',
|
||||
'${player.packetLossRate}%',
|
||||
'${player.packetLossRate.toStringAsFixed(2)}%', // 修改这里,保留2位小数
|
||||
colorScheme,
|
||||
valueColor: _getPacketLossColor(player.packetLossRate),
|
||||
),
|
||||
|
||||
+1437
-180
File diff suppressed because it is too large
Load Diff
@@ -37,14 +37,16 @@ Future<void> createServer(
|
||||
required String specifiedIp,
|
||||
required String roomName,
|
||||
required String roomPassword,
|
||||
required String severurl}) =>
|
||||
required List<String> severurl,
|
||||
required FlagsC flag}) =>
|
||||
RustLib.instance.api.crateApiSimpleCreateServer(
|
||||
username: username,
|
||||
enableDhcp: enableDhcp,
|
||||
specifiedIp: specifiedIp,
|
||||
roomName: roomName,
|
||||
roomPassword: roomPassword,
|
||||
severurl: severurl);
|
||||
severurl: severurl,
|
||||
flag: flag);
|
||||
|
||||
Future<void> closeAllServer() =>
|
||||
RustLib.instance.api.crateApiSimpleCloseAllServer();
|
||||
@@ -63,6 +65,103 @@ abstract class PeerRoutePair implements RustOpaqueInterface {}
|
||||
// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<Route>>
|
||||
abstract class Route implements RustOpaqueInterface {}
|
||||
|
||||
class FlagsC {
|
||||
final String defaultProtocol;
|
||||
final String devName;
|
||||
final bool enableEncryption;
|
||||
final bool enableIpv6;
|
||||
final int mtu;
|
||||
final bool latencyFirst;
|
||||
final bool enableExitNode;
|
||||
final bool noTun;
|
||||
final bool useSmoltcp;
|
||||
final String relayNetworkWhitelist;
|
||||
final bool disableP2P;
|
||||
final bool relayAllPeerRpc;
|
||||
final bool disableUdpHolePunching;
|
||||
|
||||
/// string ipv6_listener = 14; \[deprecated = true\]; use -l udp://\[::\]:12345 instead
|
||||
final bool multiThread;
|
||||
final int dataCompressAlgo;
|
||||
final bool bindDevice;
|
||||
final bool enableKcpProxy;
|
||||
final bool disableKcpInput;
|
||||
final bool disableRelayKcp;
|
||||
final bool proxyForwardBySystem;
|
||||
|
||||
const FlagsC({
|
||||
required this.defaultProtocol,
|
||||
required this.devName,
|
||||
required this.enableEncryption,
|
||||
required this.enableIpv6,
|
||||
required this.mtu,
|
||||
required this.latencyFirst,
|
||||
required this.enableExitNode,
|
||||
required this.noTun,
|
||||
required this.useSmoltcp,
|
||||
required this.relayNetworkWhitelist,
|
||||
required this.disableP2P,
|
||||
required this.relayAllPeerRpc,
|
||||
required this.disableUdpHolePunching,
|
||||
required this.multiThread,
|
||||
required this.dataCompressAlgo,
|
||||
required this.bindDevice,
|
||||
required this.enableKcpProxy,
|
||||
required this.disableKcpInput,
|
||||
required this.disableRelayKcp,
|
||||
required this.proxyForwardBySystem,
|
||||
});
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
defaultProtocol.hashCode ^
|
||||
devName.hashCode ^
|
||||
enableEncryption.hashCode ^
|
||||
enableIpv6.hashCode ^
|
||||
mtu.hashCode ^
|
||||
latencyFirst.hashCode ^
|
||||
enableExitNode.hashCode ^
|
||||
noTun.hashCode ^
|
||||
useSmoltcp.hashCode ^
|
||||
relayNetworkWhitelist.hashCode ^
|
||||
disableP2P.hashCode ^
|
||||
relayAllPeerRpc.hashCode ^
|
||||
disableUdpHolePunching.hashCode ^
|
||||
multiThread.hashCode ^
|
||||
dataCompressAlgo.hashCode ^
|
||||
bindDevice.hashCode ^
|
||||
enableKcpProxy.hashCode ^
|
||||
disableKcpInput.hashCode ^
|
||||
disableRelayKcp.hashCode ^
|
||||
proxyForwardBySystem.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is FlagsC &&
|
||||
runtimeType == other.runtimeType &&
|
||||
defaultProtocol == other.defaultProtocol &&
|
||||
devName == other.devName &&
|
||||
enableEncryption == other.enableEncryption &&
|
||||
enableIpv6 == other.enableIpv6 &&
|
||||
mtu == other.mtu &&
|
||||
latencyFirst == other.latencyFirst &&
|
||||
enableExitNode == other.enableExitNode &&
|
||||
noTun == other.noTun &&
|
||||
useSmoltcp == other.useSmoltcp &&
|
||||
relayNetworkWhitelist == other.relayNetworkWhitelist &&
|
||||
disableP2P == other.disableP2P &&
|
||||
relayAllPeerRpc == other.relayAllPeerRpc &&
|
||||
disableUdpHolePunching == other.disableUdpHolePunching &&
|
||||
multiThread == other.multiThread &&
|
||||
dataCompressAlgo == other.dataCompressAlgo &&
|
||||
bindDevice == other.bindDevice &&
|
||||
enableKcpProxy == other.enableKcpProxy &&
|
||||
disableKcpInput == other.disableKcpInput &&
|
||||
disableRelayKcp == other.disableRelayKcp &&
|
||||
proxyForwardBySystem == other.proxyForwardBySystem;
|
||||
}
|
||||
|
||||
class KVNetworkStatus {
|
||||
final BigInt totalNodes;
|
||||
final List<KVNodeInfo> nodes;
|
||||
|
||||
@@ -85,7 +85,8 @@ abstract class RustLibApi extends BaseApi {
|
||||
required String specifiedIp,
|
||||
required String roomName,
|
||||
required String roomPassword,
|
||||
required String severurl});
|
||||
required List<String> severurl,
|
||||
required FlagsC flag});
|
||||
|
||||
Future<String> crateApiSimpleEasytierVersion();
|
||||
|
||||
@@ -174,7 +175,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
required String specifiedIp,
|
||||
required String roomName,
|
||||
required String roomPassword,
|
||||
required String severurl}) {
|
||||
required List<String> severurl,
|
||||
required FlagsC flag}) {
|
||||
return handler.executeNormal(NormalTask(
|
||||
callFfi: (port_) {
|
||||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||
@@ -183,7 +185,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
sse_encode_String(specifiedIp, serializer);
|
||||
sse_encode_String(roomName, serializer);
|
||||
sse_encode_String(roomPassword, serializer);
|
||||
sse_encode_String(severurl, serializer);
|
||||
sse_encode_list_String(severurl, serializer);
|
||||
sse_encode_box_autoadd_flags_c(flag, serializer);
|
||||
pdeCallFfi(generalizedFrbRustBinding, serializer,
|
||||
funcId: 2, port: port_);
|
||||
},
|
||||
@@ -198,7 +201,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
specifiedIp,
|
||||
roomName,
|
||||
roomPassword,
|
||||
severurl
|
||||
severurl,
|
||||
flag
|
||||
],
|
||||
apiImpl: this,
|
||||
));
|
||||
@@ -212,7 +216,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
"specifiedIp",
|
||||
"roomName",
|
||||
"roomPassword",
|
||||
"severurl"
|
||||
"severurl",
|
||||
"flag"
|
||||
],
|
||||
);
|
||||
|
||||
@@ -515,6 +520,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
return raw as bool;
|
||||
}
|
||||
|
||||
@protected
|
||||
FlagsC dco_decode_box_autoadd_flags_c(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return dco_decode_flags_c(raw);
|
||||
}
|
||||
|
||||
@protected
|
||||
double dco_decode_f_32(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
@@ -527,6 +538,36 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
return raw as double;
|
||||
}
|
||||
|
||||
@protected
|
||||
FlagsC dco_decode_flags_c(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
final arr = raw as List<dynamic>;
|
||||
if (arr.length != 20)
|
||||
throw Exception('unexpected arr length: expect 20 but see ${arr.length}');
|
||||
return FlagsC(
|
||||
defaultProtocol: dco_decode_String(arr[0]),
|
||||
devName: dco_decode_String(arr[1]),
|
||||
enableEncryption: dco_decode_bool(arr[2]),
|
||||
enableIpv6: dco_decode_bool(arr[3]),
|
||||
mtu: dco_decode_u_32(arr[4]),
|
||||
latencyFirst: dco_decode_bool(arr[5]),
|
||||
enableExitNode: dco_decode_bool(arr[6]),
|
||||
noTun: dco_decode_bool(arr[7]),
|
||||
useSmoltcp: dco_decode_bool(arr[8]),
|
||||
relayNetworkWhitelist: dco_decode_String(arr[9]),
|
||||
disableP2P: dco_decode_bool(arr[10]),
|
||||
relayAllPeerRpc: dco_decode_bool(arr[11]),
|
||||
disableUdpHolePunching: dco_decode_bool(arr[12]),
|
||||
multiThread: dco_decode_bool(arr[13]),
|
||||
dataCompressAlgo: dco_decode_i_32(arr[14]),
|
||||
bindDevice: dco_decode_bool(arr[15]),
|
||||
enableKcpProxy: dco_decode_bool(arr[16]),
|
||||
disableKcpInput: dco_decode_bool(arr[17]),
|
||||
disableRelayKcp: dco_decode_bool(arr[18]),
|
||||
proxyForwardBySystem: dco_decode_bool(arr[19]),
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
int dco_decode_i_32(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
@@ -611,6 +652,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
.toList();
|
||||
}
|
||||
|
||||
@protected
|
||||
List<String> dco_decode_list_String(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return (raw as List<dynamic>).map(dco_decode_String).toList();
|
||||
}
|
||||
|
||||
@protected
|
||||
List<KVNodeConnectionStats> dco_decode_list_kv_node_connection_stats(
|
||||
dynamic raw) {
|
||||
@@ -651,6 +698,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
int dco_decode_u_32(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return raw as int;
|
||||
}
|
||||
|
||||
@protected
|
||||
BigInt dco_decode_u_64(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
@@ -760,6 +813,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
return deserializer.buffer.getUint8() != 0;
|
||||
}
|
||||
|
||||
@protected
|
||||
FlagsC sse_decode_box_autoadd_flags_c(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
return (sse_decode_flags_c(deserializer));
|
||||
}
|
||||
|
||||
@protected
|
||||
double sse_decode_f_32(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
@@ -772,6 +831,52 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
return deserializer.buffer.getFloat64();
|
||||
}
|
||||
|
||||
@protected
|
||||
FlagsC sse_decode_flags_c(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
var var_defaultProtocol = sse_decode_String(deserializer);
|
||||
var var_devName = sse_decode_String(deserializer);
|
||||
var var_enableEncryption = sse_decode_bool(deserializer);
|
||||
var var_enableIpv6 = sse_decode_bool(deserializer);
|
||||
var var_mtu = sse_decode_u_32(deserializer);
|
||||
var var_latencyFirst = sse_decode_bool(deserializer);
|
||||
var var_enableExitNode = sse_decode_bool(deserializer);
|
||||
var var_noTun = sse_decode_bool(deserializer);
|
||||
var var_useSmoltcp = sse_decode_bool(deserializer);
|
||||
var var_relayNetworkWhitelist = sse_decode_String(deserializer);
|
||||
var var_disableP2P = sse_decode_bool(deserializer);
|
||||
var var_relayAllPeerRpc = sse_decode_bool(deserializer);
|
||||
var var_disableUdpHolePunching = sse_decode_bool(deserializer);
|
||||
var var_multiThread = sse_decode_bool(deserializer);
|
||||
var var_dataCompressAlgo = sse_decode_i_32(deserializer);
|
||||
var var_bindDevice = sse_decode_bool(deserializer);
|
||||
var var_enableKcpProxy = sse_decode_bool(deserializer);
|
||||
var var_disableKcpInput = sse_decode_bool(deserializer);
|
||||
var var_disableRelayKcp = sse_decode_bool(deserializer);
|
||||
var var_proxyForwardBySystem = sse_decode_bool(deserializer);
|
||||
return FlagsC(
|
||||
defaultProtocol: var_defaultProtocol,
|
||||
devName: var_devName,
|
||||
enableEncryption: var_enableEncryption,
|
||||
enableIpv6: var_enableIpv6,
|
||||
mtu: var_mtu,
|
||||
latencyFirst: var_latencyFirst,
|
||||
enableExitNode: var_enableExitNode,
|
||||
noTun: var_noTun,
|
||||
useSmoltcp: var_useSmoltcp,
|
||||
relayNetworkWhitelist: var_relayNetworkWhitelist,
|
||||
disableP2P: var_disableP2P,
|
||||
relayAllPeerRpc: var_relayAllPeerRpc,
|
||||
disableUdpHolePunching: var_disableUdpHolePunching,
|
||||
multiThread: var_multiThread,
|
||||
dataCompressAlgo: var_dataCompressAlgo,
|
||||
bindDevice: var_bindDevice,
|
||||
enableKcpProxy: var_enableKcpProxy,
|
||||
disableKcpInput: var_disableKcpInput,
|
||||
disableRelayKcp: var_disableRelayKcp,
|
||||
proxyForwardBySystem: var_proxyForwardBySystem);
|
||||
}
|
||||
|
||||
@protected
|
||||
int sse_decode_i_32(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
@@ -874,6 +979,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
return ans_;
|
||||
}
|
||||
|
||||
@protected
|
||||
List<String> sse_decode_list_String(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
||||
var len_ = sse_decode_i_32(deserializer);
|
||||
var ans_ = <String>[];
|
||||
for (var idx_ = 0; idx_ < len_; ++idx_) {
|
||||
ans_.add(sse_decode_String(deserializer));
|
||||
}
|
||||
return ans_;
|
||||
}
|
||||
|
||||
@protected
|
||||
List<KVNodeConnectionStats> sse_decode_list_kv_node_connection_stats(
|
||||
SseDeserializer deserializer) {
|
||||
@@ -922,6 +1039,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
return (var_field0, var_field1);
|
||||
}
|
||||
|
||||
@protected
|
||||
int sse_decode_u_32(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
return deserializer.buffer.getUint32();
|
||||
}
|
||||
|
||||
@protected
|
||||
BigInt sse_decode_u_64(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
@@ -1031,6 +1154,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
serializer.buffer.putUint8(self ? 1 : 0);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_flags_c(FlagsC self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_flags_c(self, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_f_32(double self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
@@ -1043,6 +1172,31 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
serializer.buffer.putFloat64(self);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_flags_c(FlagsC self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_String(self.defaultProtocol, serializer);
|
||||
sse_encode_String(self.devName, serializer);
|
||||
sse_encode_bool(self.enableEncryption, serializer);
|
||||
sse_encode_bool(self.enableIpv6, serializer);
|
||||
sse_encode_u_32(self.mtu, serializer);
|
||||
sse_encode_bool(self.latencyFirst, serializer);
|
||||
sse_encode_bool(self.enableExitNode, serializer);
|
||||
sse_encode_bool(self.noTun, serializer);
|
||||
sse_encode_bool(self.useSmoltcp, serializer);
|
||||
sse_encode_String(self.relayNetworkWhitelist, serializer);
|
||||
sse_encode_bool(self.disableP2P, serializer);
|
||||
sse_encode_bool(self.relayAllPeerRpc, serializer);
|
||||
sse_encode_bool(self.disableUdpHolePunching, serializer);
|
||||
sse_encode_bool(self.multiThread, serializer);
|
||||
sse_encode_i_32(self.dataCompressAlgo, serializer);
|
||||
sse_encode_bool(self.bindDevice, serializer);
|
||||
sse_encode_bool(self.enableKcpProxy, serializer);
|
||||
sse_encode_bool(self.disableKcpInput, serializer);
|
||||
sse_encode_bool(self.disableRelayKcp, serializer);
|
||||
sse_encode_bool(self.proxyForwardBySystem, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_i_32(int self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
@@ -1117,6 +1271,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_list_String(List<String> self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_i_32(self.length, serializer);
|
||||
for (final item in self) {
|
||||
sse_encode_String(item, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_list_kv_node_connection_stats(
|
||||
List<KVNodeConnectionStats> self, SseSerializer serializer) {
|
||||
@@ -1156,6 +1319,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
self.$2, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_u_32(int self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
serializer.buffer.putUint32(self);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_u_64(BigInt self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
||||
@@ -77,12 +77,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
bool dco_decode_bool(dynamic raw);
|
||||
|
||||
@protected
|
||||
FlagsC dco_decode_box_autoadd_flags_c(dynamic raw);
|
||||
|
||||
@protected
|
||||
double dco_decode_f_32(dynamic raw);
|
||||
|
||||
@protected
|
||||
double dco_decode_f_64(dynamic raw);
|
||||
|
||||
@protected
|
||||
FlagsC dco_decode_flags_c(dynamic raw);
|
||||
|
||||
@protected
|
||||
int dco_decode_i_32(dynamic raw);
|
||||
|
||||
@@ -110,6 +116,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
dco_decode_list_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoute(
|
||||
dynamic raw);
|
||||
|
||||
@protected
|
||||
List<String> dco_decode_list_String(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<KVNodeConnectionStats> dco_decode_list_kv_node_connection_stats(
|
||||
dynamic raw);
|
||||
@@ -127,6 +136,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
) dco_decode_record_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_peer_info_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_route(
|
||||
dynamic raw);
|
||||
|
||||
@protected
|
||||
int dco_decode_u_32(dynamic raw);
|
||||
|
||||
@protected
|
||||
BigInt dco_decode_u_64(dynamic raw);
|
||||
|
||||
@@ -185,12 +197,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
bool sse_decode_bool(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
FlagsC sse_decode_box_autoadd_flags_c(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
double sse_decode_f_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
double sse_decode_f_64(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
FlagsC sse_decode_flags_c(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
int sse_decode_i_32(SseDeserializer deserializer);
|
||||
|
||||
@@ -219,6 +237,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
sse_decode_list_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoute(
|
||||
SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<String> sse_decode_list_String(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<KVNodeConnectionStats> sse_decode_list_kv_node_connection_stats(
|
||||
SseDeserializer deserializer);
|
||||
@@ -236,6 +257,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
) sse_decode_record_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_peer_info_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_route(
|
||||
SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
int sse_decode_u_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
BigInt sse_decode_u_64(SseDeserializer deserializer);
|
||||
|
||||
@@ -294,12 +318,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_bool(bool self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_flags_c(FlagsC self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_f_32(double self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_f_64(double self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_flags_c(FlagsC self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_i_32(int self, SseSerializer serializer);
|
||||
|
||||
@@ -329,6 +359,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
sse_encode_list_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoute(
|
||||
List<Route> self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_String(List<String> self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_kv_node_connection_stats(
|
||||
List<KVNodeConnectionStats> self, SseSerializer serializer);
|
||||
@@ -346,6 +379,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
sse_encode_record_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_peer_info_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_route(
|
||||
(List<PeerInfo>, List<Route>) self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_u_32(int self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_u_64(BigInt self, SseSerializer serializer);
|
||||
|
||||
|
||||
@@ -79,12 +79,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
bool dco_decode_bool(dynamic raw);
|
||||
|
||||
@protected
|
||||
FlagsC dco_decode_box_autoadd_flags_c(dynamic raw);
|
||||
|
||||
@protected
|
||||
double dco_decode_f_32(dynamic raw);
|
||||
|
||||
@protected
|
||||
double dco_decode_f_64(dynamic raw);
|
||||
|
||||
@protected
|
||||
FlagsC dco_decode_flags_c(dynamic raw);
|
||||
|
||||
@protected
|
||||
int dco_decode_i_32(dynamic raw);
|
||||
|
||||
@@ -112,6 +118,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
dco_decode_list_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoute(
|
||||
dynamic raw);
|
||||
|
||||
@protected
|
||||
List<String> dco_decode_list_String(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<KVNodeConnectionStats> dco_decode_list_kv_node_connection_stats(
|
||||
dynamic raw);
|
||||
@@ -129,6 +138,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
) dco_decode_record_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_peer_info_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_route(
|
||||
dynamic raw);
|
||||
|
||||
@protected
|
||||
int dco_decode_u_32(dynamic raw);
|
||||
|
||||
@protected
|
||||
BigInt dco_decode_u_64(dynamic raw);
|
||||
|
||||
@@ -187,12 +199,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
bool sse_decode_bool(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
FlagsC sse_decode_box_autoadd_flags_c(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
double sse_decode_f_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
double sse_decode_f_64(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
FlagsC sse_decode_flags_c(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
int sse_decode_i_32(SseDeserializer deserializer);
|
||||
|
||||
@@ -221,6 +239,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
sse_decode_list_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoute(
|
||||
SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<String> sse_decode_list_String(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<KVNodeConnectionStats> sse_decode_list_kv_node_connection_stats(
|
||||
SseDeserializer deserializer);
|
||||
@@ -238,6 +259,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
) sse_decode_record_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_peer_info_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_route(
|
||||
SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
int sse_decode_u_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
BigInt sse_decode_u_64(SseDeserializer deserializer);
|
||||
|
||||
@@ -296,12 +320,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_bool(bool self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_flags_c(FlagsC self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_f_32(double self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_f_64(double self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_flags_c(FlagsC self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_i_32(int self, SseSerializer serializer);
|
||||
|
||||
@@ -331,6 +361,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
sse_encode_list_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoute(
|
||||
List<Route> self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_String(List<String> self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_kv_node_connection_stats(
|
||||
List<KVNodeConnectionStats> self, SseSerializer serializer);
|
||||
@@ -348,6 +381,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
sse_encode_record_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_peer_info_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_route(
|
||||
(List<PeerInfo>, List<Route>) self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_u_32(int self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_u_64(BigInt self, SseSerializer serializer);
|
||||
|
||||
|
||||
+427
-55
@@ -1,64 +1,436 @@
|
||||
import 'package:astral/src/rust/api/simple.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../config/app_config.dart';
|
||||
|
||||
class KM extends ChangeNotifier {
|
||||
final _config = AppConfig();
|
||||
int _count = 0;
|
||||
int get count => _count;
|
||||
// 全局配置实例
|
||||
final appConfigProvider = Provider<AppConfig>((ref) => AppConfig());
|
||||
|
||||
void increment() {
|
||||
_count++;
|
||||
notifyListeners(); // 通知监听器重建UI
|
||||
}
|
||||
// 计数器相关
|
||||
class CountNotifier extends StateNotifier<int> {
|
||||
CountNotifier() : super(0);
|
||||
|
||||
// 房间名
|
||||
String get roomName => _config.roomName;
|
||||
set roomName(String value) {
|
||||
void increment() => state++;
|
||||
}
|
||||
|
||||
final countProvider =
|
||||
StateNotifierProvider<CountNotifier, int>((ref) => CountNotifier());
|
||||
|
||||
// 房间名相关
|
||||
class RoomNameNotifier extends StateNotifier<String> {
|
||||
final AppConfig _config;
|
||||
|
||||
RoomNameNotifier(this._config) : super(_config.roomName);
|
||||
|
||||
void setRoomName(String value) {
|
||||
_config.setRoomName(value);
|
||||
notifyListeners(); // 通知监听器重建UI
|
||||
}
|
||||
|
||||
//房间密码设置
|
||||
String get roomPassword => _config.roomPassword;
|
||||
set roomPassword(String value) {
|
||||
_config.setRoomPassword(value);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
//用户名设置
|
||||
String get username => _config.username;
|
||||
set username(String value) {
|
||||
_config.setUsername(value);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
//虚拟IP设置
|
||||
String get virtualIP => _config.virtualIP;
|
||||
set virtualIP(String value) {
|
||||
_config.setVirtualIP(value);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
//动态获取IP设置
|
||||
bool get dynamicIP => _config.dynamicIP;
|
||||
set dynamicIP(bool value) {
|
||||
_config.setDynamicIP(value);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
//当前服务器IP
|
||||
String get serverIP => _config.currentServer;
|
||||
set serverIP(String value) {
|
||||
_config.setCurrentServer(value);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 节点列表
|
||||
List<KVNodeInfo> _nodes = [];
|
||||
List<KVNodeInfo> get nodes => _nodes;
|
||||
set nodes(List<KVNodeInfo> value) {
|
||||
_nodes = value;
|
||||
notifyListeners();
|
||||
state = value;
|
||||
}
|
||||
}
|
||||
|
||||
final roomNameProvider = StateNotifierProvider<RoomNameNotifier, String>((ref) {
|
||||
return RoomNameNotifier(ref.watch(appConfigProvider));
|
||||
});
|
||||
|
||||
// 房间密码相关
|
||||
class RoomPasswordNotifier extends StateNotifier<String> {
|
||||
final AppConfig _config;
|
||||
|
||||
RoomPasswordNotifier(this._config) : super(_config.roomPassword);
|
||||
|
||||
void setRoomPassword(String value) {
|
||||
_config.setRoomPassword(value);
|
||||
state = value;
|
||||
}
|
||||
}
|
||||
|
||||
final roomPasswordProvider =
|
||||
StateNotifierProvider<RoomPasswordNotifier, String>((ref) {
|
||||
return RoomPasswordNotifier(ref.watch(appConfigProvider));
|
||||
});
|
||||
|
||||
// 用户名相关
|
||||
class UsernameNotifier extends StateNotifier<String> {
|
||||
final AppConfig _config;
|
||||
|
||||
UsernameNotifier(this._config) : super(_config.username);
|
||||
|
||||
void setUsername(String value) {
|
||||
_config.setUsername(value);
|
||||
state = value;
|
||||
}
|
||||
}
|
||||
|
||||
final usernameProvider = StateNotifierProvider<UsernameNotifier, String>((ref) {
|
||||
return UsernameNotifier(ref.watch(appConfigProvider));
|
||||
});
|
||||
|
||||
// 虚拟IP相关
|
||||
class VirtualIPNotifier extends StateNotifier<String> {
|
||||
final AppConfig _config;
|
||||
|
||||
VirtualIPNotifier(this._config) : super(_config.virtualIP);
|
||||
|
||||
void setVirtualIP(String value) {
|
||||
_config.setVirtualIP(value);
|
||||
state = value;
|
||||
}
|
||||
}
|
||||
|
||||
final virtualIPProvider =
|
||||
StateNotifierProvider<VirtualIPNotifier, String>((ref) {
|
||||
return VirtualIPNotifier(ref.watch(appConfigProvider));
|
||||
});
|
||||
|
||||
// 动态获取IP设置相关
|
||||
class DynamicIPNotifier extends StateNotifier<bool> {
|
||||
final AppConfig _config;
|
||||
|
||||
DynamicIPNotifier(this._config) : super(_config.dynamicIP);
|
||||
|
||||
void setDynamicIP(bool value) {
|
||||
_config.setDynamicIP(value);
|
||||
state = value;
|
||||
}
|
||||
}
|
||||
|
||||
final dynamicIPProvider = StateNotifierProvider<DynamicIPNotifier, bool>((ref) {
|
||||
return DynamicIPNotifier(ref.watch(appConfigProvider));
|
||||
});
|
||||
|
||||
// 服务器列表相关
|
||||
class ServerListNotifier extends StateNotifier<List<Map<String, dynamic>>> {
|
||||
final AppConfig _config;
|
||||
|
||||
ServerListNotifier(this._config) : super(_config.serverList);
|
||||
|
||||
void setServerList(List<Map<String, dynamic>> value) {
|
||||
List<Map<String, dynamic>> convertedList = value.map((item) {
|
||||
return Map<String, dynamic>.from(item);
|
||||
}).toList();
|
||||
_config.setServerList(convertedList);
|
||||
state = convertedList;
|
||||
}
|
||||
|
||||
void setServerSelected(String url, bool selected) {
|
||||
final servers = [...state];
|
||||
for (var i = 0; i < servers.length; i++) {
|
||||
if (servers[i]['url'] == url) {
|
||||
servers[i]['selected'] = selected;
|
||||
}
|
||||
}
|
||||
setServerList(servers);
|
||||
}
|
||||
}
|
||||
|
||||
final serverListProvider =
|
||||
StateNotifierProvider<ServerListNotifier, List<Map<String, dynamic>>>(
|
||||
(ref) {
|
||||
return ServerListNotifier(ref.watch(appConfigProvider));
|
||||
});
|
||||
|
||||
// 选中的服务器IP
|
||||
final serverIPProvider = Provider<List<String>>((ref) {
|
||||
final serverList = ref.watch(serverListProvider);
|
||||
|
||||
try {
|
||||
final selected =
|
||||
serverList.where((server) => server['selected'] == true).toList();
|
||||
|
||||
if (selected.isEmpty && serverList.isNotEmpty) {
|
||||
final firstServer = serverList.first;
|
||||
if (firstServer['url'] is String) {
|
||||
return [firstServer['url'] as String];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
return selected
|
||||
.where((server) => server['url'] is String)
|
||||
.map((server) => server['url'] as String)
|
||||
.toList();
|
||||
} catch (e) {
|
||||
debugPrint('获取服务器IP时出错: $e');
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
//返回选中的服务器
|
||||
final selectedServerProvider = Provider<List<ServerConfig>>((ref) {
|
||||
final serverList = ref.watch(serverListProvider);
|
||||
final selected =
|
||||
serverList.where((server) => server['selected'] == true).toList();
|
||||
if (selected.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 将选中的服务器转换为ServerConfig对象列表
|
||||
return selected
|
||||
.map((server) => ServerConfig(
|
||||
url: server['url'] ?? '',
|
||||
name: server['name'] ?? '',
|
||||
selected: true,
|
||||
tcp: server['tcp'] ?? true,
|
||||
udp: server['udp'] ?? true,
|
||||
ws: server['ws'] ?? false,
|
||||
wss: server['wss'] ?? false,
|
||||
quic: server['quic'] ?? false,
|
||||
))
|
||||
.toList();
|
||||
});
|
||||
|
||||
// 节点列表相关
|
||||
class NodesNotifier extends StateNotifier<List<KVNodeInfo>> {
|
||||
NodesNotifier() : super([]);
|
||||
|
||||
void setNodes(List<KVNodeInfo> value) {
|
||||
state = value;
|
||||
}
|
||||
}
|
||||
|
||||
final nodesProvider = StateNotifierProvider<NodesNotifier, List<KVNodeInfo>>(
|
||||
(ref) => NodesNotifier());
|
||||
|
||||
// 高级配置相关
|
||||
class AdvancedConfigNotifier extends StateNotifier<Map<String, dynamic>> {
|
||||
final AppConfig _config;
|
||||
|
||||
AdvancedConfigNotifier(this._config)
|
||||
: super({
|
||||
'defaultProtocol': _config.advanced.defaultProtocol,
|
||||
'devName': _config.advanced.devName,
|
||||
'enableEncryption': _config.advanced.enableEncryption,
|
||||
'enableIpv6': _config.advanced.enableIpv6,
|
||||
'mtu': _config.advanced.mtu,
|
||||
'latencyFirst': _config.advanced.latencyFirst,
|
||||
'enableExitNode': _config.advanced.enableExitNode,
|
||||
'proxyForwardBySystem': _config.advanced.proxyForwardBySystem,
|
||||
'noTun': _config.advanced.noTun,
|
||||
'useSmoltcp': _config.advanced.useSmoltcp,
|
||||
'relayNetworkWhitelist': _config.advanced.relayNetworkWhitelist,
|
||||
'disableP2p': _config.advanced.disableP2p,
|
||||
'relayAllPeerRpc': _config.advanced.relayAllPeerRpc,
|
||||
'disableUdpHolePunching': _config.advanced.disableUdpHolePunching,
|
||||
'multiThread': _config.advanced.multiThread,
|
||||
'dataCompressAlgo': _config.advanced.dataCompressAlgo,
|
||||
'bindDevice': _config.advanced.bindDevice,
|
||||
'enableKcpProxy': _config.advanced.enableKcpProxy,
|
||||
'disableKcpInput': _config.advanced.disableKcpInput,
|
||||
'disableRelayKcp': _config.advanced.disableRelayKcp,
|
||||
});
|
||||
|
||||
// 更新单个配置项
|
||||
Future<void> updateConfig(String key, dynamic value) async {
|
||||
if (state.containsKey(key) && state[key] != value) {
|
||||
final newState = {...state, key: value};
|
||||
state = newState;
|
||||
|
||||
// 使用通用方法更新配置
|
||||
await _config.updateAdvancedConfig(
|
||||
defaultProtocol: key == 'defaultProtocol' ? value : null,
|
||||
devName: key == 'devName' ? value : null,
|
||||
enableEncryption: key == 'enableEncryption' ? value : null,
|
||||
enableIpv6: key == 'enableIpv6' ? value : null,
|
||||
mtu: key == 'mtu' ? value : null,
|
||||
latencyFirst: key == 'latencyFirst' ? value : null,
|
||||
enableExitNode: key == 'enableExitNode' ? value : null,
|
||||
proxyForwardBySystem: key == 'proxyForwardBySystem' ? value : null,
|
||||
noTun: key == 'noTun' ? value : null,
|
||||
useSmoltcp: key == 'useSmoltcp' ? value : null,
|
||||
relayNetworkWhitelist: key == 'relayNetworkWhitelist' ? value : null,
|
||||
disableP2p: key == 'disableP2p' ? value : null,
|
||||
relayAllPeerRpc: key == 'relayAllPeerRpc' ? value : null,
|
||||
disableUdpHolePunching: key == 'disableUdpHolePunching' ? value : null,
|
||||
multiThread: key == 'multiThread' ? value : null,
|
||||
dataCompressAlgo: key == 'dataCompressAlgo' ? value : null,
|
||||
bindDevice: key == 'bindDevice' ? value : null,
|
||||
enableKcpProxy: key == 'enableKcpProxy' ? value : null,
|
||||
disableKcpInput: key == 'disableKcpInput' ? value : null,
|
||||
disableRelayKcp: key == 'disableRelayKcp' ? value : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 批量更新配置
|
||||
Future<void> updateMultipleConfigs(Map<String, dynamic> updates) async {
|
||||
final newState = {...state, ...updates};
|
||||
state = newState;
|
||||
|
||||
// 使用通用方法更新配置
|
||||
await _config.updateAdvancedConfig(
|
||||
defaultProtocol: updates.containsKey('defaultProtocol')
|
||||
? updates['defaultProtocol']
|
||||
: null,
|
||||
devName: updates.containsKey('devName') ? updates['devName'] : null,
|
||||
enableEncryption: updates.containsKey('enableEncryption')
|
||||
? updates['enableEncryption']
|
||||
: null,
|
||||
enableIpv6:
|
||||
updates.containsKey('enableIpv6') ? updates['enableIpv6'] : null,
|
||||
mtu: updates.containsKey('mtu') ? updates['mtu'] : null,
|
||||
latencyFirst:
|
||||
updates.containsKey('latencyFirst') ? updates['latencyFirst'] : null,
|
||||
enableExitNode: updates.containsKey('enableExitNode')
|
||||
? updates['enableExitNode']
|
||||
: null,
|
||||
proxyForwardBySystem: updates.containsKey('proxyForwardBySystem')
|
||||
? updates['proxyForwardBySystem']
|
||||
: null,
|
||||
noTun: updates.containsKey('noTun') ? updates['noTun'] : null,
|
||||
useSmoltcp:
|
||||
updates.containsKey('useSmoltcp') ? updates['useSmoltcp'] : null,
|
||||
relayNetworkWhitelist: updates.containsKey('relayNetworkWhitelist')
|
||||
? updates['relayNetworkWhitelist']
|
||||
: null,
|
||||
disableP2p:
|
||||
updates.containsKey('disableP2p') ? updates['disableP2p'] : null,
|
||||
relayAllPeerRpc: updates.containsKey('relayAllPeerRpc')
|
||||
? updates['relayAllPeerRpc']
|
||||
: null,
|
||||
disableUdpHolePunching: updates.containsKey('disableUdpHolePunching')
|
||||
? updates['disableUdpHolePunching']
|
||||
: null,
|
||||
multiThread:
|
||||
updates.containsKey('multiThread') ? updates['multiThread'] : null,
|
||||
dataCompressAlgo: updates.containsKey('dataCompressAlgo')
|
||||
? updates['dataCompressAlgo']
|
||||
: null,
|
||||
bindDevice:
|
||||
updates.containsKey('bindDevice') ? updates['bindDevice'] : null,
|
||||
enableKcpProxy: updates.containsKey('enableKcpProxy')
|
||||
? updates['enableKcpProxy']
|
||||
: null,
|
||||
disableKcpInput: updates.containsKey('disableKcpInput')
|
||||
? updates['disableKcpInput']
|
||||
: null,
|
||||
disableRelayKcp: updates.containsKey('disableRelayKcp')
|
||||
? updates['disableRelayKcp']
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
// 为每个配置项提供单独的更新方法
|
||||
Future<void> setDefaultProtocol(String value) async =>
|
||||
updateConfig('defaultProtocol', value);
|
||||
Future<void> setDevName(String value) async => updateConfig('devName', value);
|
||||
Future<void> setEnableEncryption(bool value) async =>
|
||||
updateConfig('enableEncryption', value);
|
||||
Future<void> setEnableIpv6(bool value) async =>
|
||||
updateConfig('enableIpv6', value);
|
||||
Future<void> setMtu(int value) async => updateConfig('mtu', value);
|
||||
Future<void> setLatencyFirst(bool value) async =>
|
||||
updateConfig('latencyFirst', value);
|
||||
Future<void> setEnableExitNode(bool value) async =>
|
||||
updateConfig('enableExitNode', value);
|
||||
Future<void> setProxyForwardBySystem(bool value) async =>
|
||||
updateConfig('proxyForwardBySystem', value);
|
||||
Future<void> setNoTun(bool value) async => updateConfig('noTun', value);
|
||||
Future<void> setUseSmoltcp(bool value) async =>
|
||||
updateConfig('useSmoltcp', value);
|
||||
Future<void> setRelayNetworkWhitelist(String value) async =>
|
||||
updateConfig('relayNetworkWhitelist', value);
|
||||
Future<void> setDisableP2p(bool value) async =>
|
||||
updateConfig('disableP2p', value);
|
||||
Future<void> setRelayAllPeerRpc(bool value) async =>
|
||||
updateConfig('relayAllPeerRpc', value);
|
||||
Future<void> setDisableUdpHolePunching(bool value) async =>
|
||||
updateConfig('disableUdpHolePunching', value);
|
||||
Future<void> setMultiThread(bool value) async =>
|
||||
updateConfig('multiThread', value);
|
||||
Future<void> setDataCompressAlgo(String value) async =>
|
||||
updateConfig('dataCompressAlgo', value);
|
||||
Future<void> setBindDevice(bool value) async =>
|
||||
updateConfig('bindDevice', value);
|
||||
Future<void> setEnableKcpProxy(bool value) async =>
|
||||
updateConfig('enableKcpProxy', value);
|
||||
Future<void> setDisableKcpInput(bool value) async =>
|
||||
updateConfig('disableKcpInput', value);
|
||||
Future<void> setDisableRelayKcp(bool value) async =>
|
||||
updateConfig('disableRelayKcp', value);
|
||||
}
|
||||
|
||||
final advancedConfigProvider =
|
||||
StateNotifierProvider<AdvancedConfigNotifier, Map<String, dynamic>>((ref) {
|
||||
return AdvancedConfigNotifier(ref.watch(appConfigProvider));
|
||||
});
|
||||
|
||||
// 为每个高级配置项创建单独的Provider,方便在UI中使用
|
||||
final defaultProtocolProvider = Provider<String>((ref) {
|
||||
return ref.watch(advancedConfigProvider)['defaultProtocol'];
|
||||
});
|
||||
|
||||
final devNameProvider = Provider<String>((ref) {
|
||||
return ref.watch(advancedConfigProvider)['devName'];
|
||||
});
|
||||
|
||||
final enableEncryptionProvider = Provider<bool>((ref) {
|
||||
return ref.watch(advancedConfigProvider)['enableEncryption'];
|
||||
});
|
||||
|
||||
final enableIpv6Provider = Provider<bool>((ref) {
|
||||
return ref.watch(advancedConfigProvider)['enableIpv6'];
|
||||
});
|
||||
|
||||
final mtuProvider = Provider<int>((ref) {
|
||||
return ref.watch(advancedConfigProvider)['mtu'];
|
||||
});
|
||||
|
||||
final latencyFirstProvider = Provider<bool>((ref) {
|
||||
return ref.watch(advancedConfigProvider)['latencyFirst'];
|
||||
});
|
||||
|
||||
final enableExitNodeProvider = Provider<bool>((ref) {
|
||||
return ref.watch(advancedConfigProvider)['enableExitNode'];
|
||||
});
|
||||
|
||||
final proxyForwardBySystemProvider = Provider<bool>((ref) {
|
||||
return ref.watch(advancedConfigProvider)['proxyForwardBySystem'];
|
||||
});
|
||||
|
||||
final noTunProvider = Provider<bool>((ref) {
|
||||
return ref.watch(advancedConfigProvider)['noTun'];
|
||||
});
|
||||
|
||||
final useSmoltcpProvider = Provider<bool>((ref) {
|
||||
return ref.watch(advancedConfigProvider)['useSmoltcp'];
|
||||
});
|
||||
|
||||
final relayNetworkWhitelistProvider = Provider<String>((ref) {
|
||||
return ref.watch(advancedConfigProvider)['relayNetworkWhitelist'];
|
||||
});
|
||||
|
||||
final disableP2pProvider = Provider<bool>((ref) {
|
||||
return ref.watch(advancedConfigProvider)['disableP2p'];
|
||||
});
|
||||
|
||||
final relayAllPeerRpcProvider = Provider<bool>((ref) {
|
||||
return ref.watch(advancedConfigProvider)['relayAllPeerRpc'];
|
||||
});
|
||||
|
||||
final disableUdpHolePunchingProvider = Provider<bool>((ref) {
|
||||
return ref.watch(advancedConfigProvider)['disableUdpHolePunching'];
|
||||
});
|
||||
|
||||
final multiThreadProvider = Provider<bool>((ref) {
|
||||
return ref.watch(advancedConfigProvider)['multiThread'];
|
||||
});
|
||||
|
||||
final dataCompressAlgoProvider = Provider<String>((ref) {
|
||||
return ref.watch(advancedConfigProvider)['dataCompressAlgo'];
|
||||
});
|
||||
|
||||
final bindDeviceProvider = Provider<bool>((ref) {
|
||||
return ref.watch(advancedConfigProvider)['bindDevice'];
|
||||
});
|
||||
|
||||
final enableKcpProxyProvider = Provider<bool>((ref) {
|
||||
return ref.watch(advancedConfigProvider)['enableKcpProxy'];
|
||||
});
|
||||
|
||||
final disableKcpInputProvider = Provider<bool>((ref) {
|
||||
return ref.watch(advancedConfigProvider)['disableKcpInput'];
|
||||
});
|
||||
|
||||
final disableRelayKcpProvider = Provider<bool>((ref) {
|
||||
return ref.watch(advancedConfigProvider)['disableRelayKcp'];
|
||||
});
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
import 'dart:convert';
|
||||
|
||||
// 主入口类
|
||||
class StatusPageData {
|
||||
final Config config;
|
||||
final Incident incident;
|
||||
final List<PublicGroup> publicGroupList;
|
||||
|
||||
StatusPageData({
|
||||
required this.config,
|
||||
required this.incident,
|
||||
required this.publicGroupList,
|
||||
});
|
||||
|
||||
factory StatusPageData.fromJson(Map<String, dynamic> json) => StatusPageData(
|
||||
config: Config.fromJson(json['config']),
|
||||
incident: Incident.fromJson(json['incident']),
|
||||
publicGroupList: List<PublicGroup>.from(
|
||||
json['publicGroupList'].map((x) => PublicGroup.fromJson(x))),
|
||||
);
|
||||
}
|
||||
|
||||
// 配置信息
|
||||
class Config {
|
||||
final String slug;
|
||||
final String title;
|
||||
final String description;
|
||||
final String icon;
|
||||
final String theme;
|
||||
final bool published;
|
||||
final bool showTags;
|
||||
final String customCSS;
|
||||
final String footerText;
|
||||
final bool showPoweredBy;
|
||||
final dynamic googleAnalyticsId;
|
||||
final bool showCertificateExpiry;
|
||||
|
||||
Config({
|
||||
required this.slug,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.icon,
|
||||
required this.theme,
|
||||
required this.published,
|
||||
required this.showTags,
|
||||
required this.customCSS,
|
||||
required this.footerText,
|
||||
required this.showPoweredBy,
|
||||
this.googleAnalyticsId,
|
||||
required this.showCertificateExpiry,
|
||||
});
|
||||
|
||||
factory Config.fromJson(Map<String, dynamic> json) => Config(
|
||||
slug: json['slug'],
|
||||
title: json['title'],
|
||||
description: json['description'],
|
||||
icon: json['icon'],
|
||||
theme: json['theme'],
|
||||
published: json['published'],
|
||||
showTags: json['showTags'],
|
||||
customCSS: json['customCSS'],
|
||||
footerText: json['footerText'],
|
||||
showPoweredBy: json['showPoweredBy'],
|
||||
googleAnalyticsId: json['googleAnalyticsId'],
|
||||
showCertificateExpiry: json['showCertificateExpiry'],
|
||||
);
|
||||
}
|
||||
|
||||
// 事件信息
|
||||
class Incident {
|
||||
final int id;
|
||||
final String style;
|
||||
final String title;
|
||||
final String content;
|
||||
final int pin;
|
||||
final DateTime createdDate;
|
||||
final DateTime lastUpdatedDate;
|
||||
|
||||
Incident({
|
||||
required this.id,
|
||||
required this.style,
|
||||
required this.title,
|
||||
required this.content,
|
||||
required this.pin,
|
||||
required this.createdDate,
|
||||
required this.lastUpdatedDate,
|
||||
});
|
||||
|
||||
factory Incident.fromJson(Map<String, dynamic> json) => Incident(
|
||||
id: json['id'],
|
||||
style: json['style'],
|
||||
title: json['title'],
|
||||
content: json['content'],
|
||||
pin: json['pin'],
|
||||
createdDate: DateTime.parse(json['createdDate']),
|
||||
lastUpdatedDate: DateTime.parse(json['lastUpdatedDate']),
|
||||
);
|
||||
}
|
||||
|
||||
// 公共服务器组
|
||||
class PublicGroup {
|
||||
final int id;
|
||||
final String name;
|
||||
final int weight;
|
||||
final List<Monitor> monitorList;
|
||||
|
||||
PublicGroup({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.weight,
|
||||
required this.monitorList,
|
||||
});
|
||||
|
||||
factory PublicGroup.fromJson(Map<String, dynamic> json) => PublicGroup(
|
||||
id: json['id'],
|
||||
name: json['name'],
|
||||
weight: json['weight'],
|
||||
monitorList: List<Monitor>.from(
|
||||
json['monitorList'].map((x) => Monitor.fromJson(x))),
|
||||
);
|
||||
}
|
||||
|
||||
// 监控项
|
||||
class Monitor {
|
||||
final int id;
|
||||
final String name;
|
||||
final int sendUrl;
|
||||
final String type;
|
||||
final List<Tag> tags;
|
||||
final int? certExpiryDaysRemaining;
|
||||
final bool? validCert;
|
||||
|
||||
Monitor({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.sendUrl,
|
||||
required this.type,
|
||||
required this.tags,
|
||||
this.certExpiryDaysRemaining,
|
||||
this.validCert,
|
||||
});
|
||||
|
||||
factory Monitor.fromJson(Map<String, dynamic> json) => Monitor(
|
||||
id: json['id'],
|
||||
name: json['name'],
|
||||
sendUrl: json['sendUrl'],
|
||||
type: json['type'],
|
||||
tags: List<Tag>.from(json['tags'].map((x) => Tag.fromJson(x))),
|
||||
certExpiryDaysRemaining: json['certExpiryDaysRemaining'],
|
||||
validCert: json['validCert'],
|
||||
);
|
||||
}
|
||||
|
||||
// 标签
|
||||
class Tag {
|
||||
final int id;
|
||||
final int monitorId;
|
||||
final int tagId;
|
||||
final String value;
|
||||
final String name;
|
||||
final String color;
|
||||
|
||||
Tag({
|
||||
required this.id,
|
||||
required this.monitorId,
|
||||
required this.tagId,
|
||||
required this.value,
|
||||
required this.name,
|
||||
required this.color,
|
||||
});
|
||||
|
||||
factory Tag.fromJson(Map<String, dynamic> json) => Tag(
|
||||
id: json['id'],
|
||||
monitorId: json['monitor_id'],
|
||||
tagId: json['tag_id'],
|
||||
value: json['value'],
|
||||
name: json['name'],
|
||||
color: json['color'],
|
||||
);
|
||||
}
|
||||
|
||||
// // 使用示例
|
||||
// void main() {
|
||||
// final jsonString = '''{/* 你的原始JSON数据 */}''';
|
||||
|
||||
// final data = StatusPageData.fromJson(jsonDecode(jsonString));
|
||||
|
||||
// // 示例:获取第一个服务器组的名称
|
||||
// print('第一组名称: ${data.publicGroupList.first.name}');
|
||||
|
||||
// // 示例:列出所有可中转的服务器
|
||||
// final transferableServers = data.publicGroupList
|
||||
// .expand((group) => group.monitorList)
|
||||
// .where((monitor) => monitor.tags.any((tag) => tag.name == '可中转'))
|
||||
// .toList();
|
||||
|
||||
// print('可中转服务器数量: ${transferableServers.length}');
|
||||
// }
|
||||
@@ -3,6 +3,7 @@ import 'dart:math';
|
||||
import 'package:astral/utils/app_info.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ class _FloatingCardState extends State<FloatingCard> {
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: widget.maxWidth ?? double.infinity,
|
||||
minHeight: widget.height ?? 74, // 默认最小高度为74
|
||||
),
|
||||
child: AnimatedContainer(
|
||||
duration: widget.duration,
|
||||
|
||||
@@ -16,11 +16,63 @@ NotificationMessage message = NotificationMessage.fromPluginTemplate(
|
||||
// image: file_path
|
||||
);
|
||||
|
||||
class WindowControls extends StatelessWidget {
|
||||
class WindowControls extends StatefulWidget {
|
||||
const WindowControls({super.key});
|
||||
|
||||
@override
|
||||
State<WindowControls> createState() => _WindowControlsState();
|
||||
}
|
||||
|
||||
class _WindowControlsState extends State<WindowControls> with WindowListener {
|
||||
bool _isMaximized = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
windowManager.addListener(this);
|
||||
_init();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
windowManager.removeListener(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _init() async {
|
||||
final isMaximized = await windowManager.isMaximized();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isMaximized = isMaximized;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowMaximize() {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isMaximized = true;
|
||||
});
|
||||
print("窗口已最大化: $_isMaximized"); // 添加调试信息
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowUnmaximize() {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isMaximized = false;
|
||||
});
|
||||
print("窗口已还原: $_isMaximized"); // 添加调试信息
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 添加调试信息
|
||||
print("构建窗口控制按钮,当前最大化状态: $_isMaximized");
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
IconButton(
|
||||
@@ -29,12 +81,19 @@ class WindowControls extends StatelessWidget {
|
||||
tooltip: '最小化',
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.crop_square),
|
||||
icon: Icon(_isMaximized ? Icons.filter_none : Icons.crop_square),
|
||||
onPressed: () async {
|
||||
if (await windowManager.isMaximized()) {
|
||||
windowManager.unmaximize();
|
||||
if (_isMaximized) {
|
||||
await windowManager.unmaximize();
|
||||
} else {
|
||||
windowManager.maximize();
|
||||
await windowManager.maximize();
|
||||
}
|
||||
// 手动更新状态,以防事件监听不工作
|
||||
if (mounted) {
|
||||
final isMaximized = await windowManager.isMaximized();
|
||||
setState(() {
|
||||
_isMaximized = isMaximized;
|
||||
});
|
||||
}
|
||||
},
|
||||
tooltip: '最大化/还原',
|
||||
|
||||
+219
-43
@@ -22,6 +22,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.11.0"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: "0c64e928dcbefddecd234205422bcfc2b5e6d31be0b86fef0d0dd48d7b4c9742"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.4"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -50,10 +58,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
|
||||
sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
version: "2.4.2"
|
||||
build_cli_annotations:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -66,42 +74,42 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_config
|
||||
sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1
|
||||
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
version: "1.1.2"
|
||||
build_daemon:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9"
|
||||
sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
version: "4.0.4"
|
||||
build_resolvers:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_resolvers
|
||||
sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
|
||||
sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
version: "2.4.4"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d"
|
||||
sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.13"
|
||||
version: "2.4.15"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0
|
||||
sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.3.2"
|
||||
version: "8.0.0"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -186,10 +194,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab"
|
||||
sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.7"
|
||||
version: "2.3.8"
|
||||
dbus:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -218,10 +226,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
version: "2.1.4"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -246,6 +254,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.70.2"
|
||||
floor_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: floor_annotation
|
||||
sha256: a40949580a7ab0eee572686e2d3b1638fd6bd6a753e661d792ab4236b365b23b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
floor_generator:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: floor_generator
|
||||
sha256: "1499b3ab878a807e6fbe6f140dc014124845cd1df3090a113aae5fa7577a1e77"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@@ -309,6 +333,14 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_riverpod:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_riverpod
|
||||
sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.1"
|
||||
flutter_rust_bridge:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -364,8 +396,32 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
hive:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: hive
|
||||
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.3"
|
||||
hive_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: hive_flutter
|
||||
sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
hive_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: hive_generator
|
||||
sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
http:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f
|
||||
@@ -384,10 +440,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
version: "4.1.2"
|
||||
integration_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@@ -413,10 +469,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
|
||||
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.1"
|
||||
version: "0.7.2"
|
||||
json_annotation:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -461,10 +517,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413"
|
||||
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
version: "5.1.1"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -554,13 +610,37 @@ packages:
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.16"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -589,10 +669,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
|
||||
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.2"
|
||||
version: "6.1.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -617,6 +697,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
posix:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: posix
|
||||
sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.1"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -645,10 +733,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pubspec_parse
|
||||
sha256: "81876843eb50dc2e1e5b151792c9a985c5ed2536914115ed04e9c8528f6647b0"
|
||||
sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
version: "1.5.0"
|
||||
riverpod:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: riverpod
|
||||
sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.1"
|
||||
rust_lib_fltier:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -708,10 +804,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "9f9f3d372d4304723e6136663bb291c0b93f5e4c8a4a6314347f481a33bda2b1"
|
||||
sha256: "3ec7210872c4ba945e3244982918e502fa2bfb5230dff6832459ca0e1879b7ad"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.7"
|
||||
version: "2.4.8"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -756,18 +852,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf
|
||||
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
|
||||
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
version: "1.4.2"
|
||||
shelf_web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
|
||||
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "3.0.0"
|
||||
shortid:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -776,6 +872,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.2"
|
||||
simple_sparse_list:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: simple_sparse_list
|
||||
sha256: aa648fd240fa39b49dcd11c19c266990006006de6699a412de485695910fbc1f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.4"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@@ -805,6 +909,46 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
sqflite:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sqflite
|
||||
sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
sqflite_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_android
|
||||
sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
sqflite_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_common
|
||||
sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.5"
|
||||
sqflite_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_darwin
|
||||
sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
sqflite_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_platform_interface
|
||||
sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -813,6 +957,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
state_notifier:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: state_notifier
|
||||
sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -837,6 +989,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
strings:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: strings
|
||||
sha256: "482f1511d2cd8ab9f2c4cc148e6837be8a00c03b9a8eaedbc981a84d9c6305d8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
sync_http:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -845,6 +1005,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.1"
|
||||
synchronized:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: synchronized
|
||||
sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.1"
|
||||
system_tray:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -901,6 +1069,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
unicode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: unicode
|
||||
sha256: "0d99edbd2e74726bed2e4989713c8bec02e5581628e334d8c88c0271593fb402"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.8"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -913,10 +1089,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193"
|
||||
sha256: "1d0eae19bd7606ef60fe69ef3b312a437a16549476c42321d5dc1506c9ca3bf4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.14"
|
||||
version: "6.3.15"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -953,10 +1129,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e"
|
||||
sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.3"
|
||||
version: "2.4.0"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1033,10 +1209,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e
|
||||
sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.10.1"
|
||||
version: "5.12.0"
|
||||
window_manager:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1078,5 +1254,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.7.0-0 <4.0.0"
|
||||
flutter: ">=3.24.0"
|
||||
dart: ">=3.7.0 <4.0.0"
|
||||
flutter: ">=3.27.0"
|
||||
|
||||
+12
-2
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.3
|
||||
version: 1.0.6
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.4
|
||||
@@ -52,11 +52,19 @@ dependencies:
|
||||
flutter_local_notifications: ^19.0.0
|
||||
windows_notification: ^1.3.0
|
||||
fl_chart: ^0.70.2
|
||||
sqflite: ^2.4.2
|
||||
path: ^1.9.1
|
||||
flutter_riverpod: ^2.6.1
|
||||
floor_generator: ^1.5.0
|
||||
build_runner: ^2.4.15
|
||||
hive: ^2.2.3
|
||||
hive_flutter: ^1.1.0
|
||||
http: ^1.1.0
|
||||
path_provider: ^2.1.5
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
build_runner: ^2.4.6
|
||||
json_serializable: ^6.7.1
|
||||
# The "flutter_lints" package below contains a set of recommended lints to
|
||||
# encourage good coding practices. The lint set provided by the package is
|
||||
@@ -64,6 +72,8 @@ dev_dependencies:
|
||||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^5.0.0
|
||||
hive_generator: ^2.0.0
|
||||
build_runner: ^2.3.3
|
||||
integration_test:
|
||||
sdk: flutter
|
||||
|
||||
|
||||
Generated
+11
-1
@@ -389,6 +389,15 @@ dependencies = [
|
||||
"x25519-dalek",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bounded_join_set"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae18fd8f4a623bcf416b5bc8f1e0905534d9911597ed17cc57ab9b6eed65454d"
|
||||
dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.8.0"
|
||||
@@ -954,7 +963,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "easytier"
|
||||
version = "2.2.2"
|
||||
version = "2.2.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-compression",
|
||||
@@ -967,6 +976,7 @@ dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bitflags 2.9.0",
|
||||
"boringtun-easytier",
|
||||
"bounded_join_set",
|
||||
"bytecodec",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
|
||||
@@ -21,3 +21,4 @@ easytier = { path = "./easytier" }
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(frb_expand)'] }
|
||||
|
||||
|
||||
Generated
-5274
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ name = "easytier"
|
||||
description = "A full meshed p2p VPN, connecting all your devices in one network with one command."
|
||||
homepage = "https://github.com/EasyTier/EasyTier"
|
||||
repository = "https://github.com/EasyTier/EasyTier"
|
||||
version = "2.2.2"
|
||||
version = "2.2.3"
|
||||
edition = "2021"
|
||||
authors = ["kkrainbow"]
|
||||
keywords = ["vpn", "p2p", "network", "easytier"]
|
||||
@@ -192,6 +192,8 @@ http_req = { git = "https://github.com/EasyTier/http_req.git", default-features
|
||||
# for dns connector
|
||||
hickory-resolver = "0.24.4"
|
||||
|
||||
bounded_join_set = "0.3.0"
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "macos", target_os = "windows", target_os = "freebsd"))'.dependencies]
|
||||
machine-uid = "0.5.3"
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ pub fn gen_default_flags() -> Flags {
|
||||
disable_p2p: false,
|
||||
relay_all_peer_rpc: false,
|
||||
disable_udp_hole_punching: false,
|
||||
ipv6_listener: "udp://[::]:0".to_string(),
|
||||
multi_thread: true,
|
||||
data_compress_algo: CompressionAlgoPb::None.into(),
|
||||
bind_device: true,
|
||||
|
||||
@@ -29,7 +29,7 @@ pub const WIN_SERVICE_WORK_DIR_REG_KEY: &str = "SOFTWARE\\EasyTier\\Service\\Wor
|
||||
|
||||
pub const EASYTIER_VERSION: &str = git_version::git_version!(
|
||||
args = ["--abbrev=8", "--always", "--dirty=~"],
|
||||
prefix = concat!(env!("CARGO_PKG_VERSION"), "-@astral "),
|
||||
suffix = "",
|
||||
fallback = env!("CARGO_PKG_VERSION")
|
||||
prefix = concat!(env!("CARGO_PKG_VERSION"), "-"),
|
||||
suffix = "-astral",
|
||||
fallback = concat!(env!("CARGO_PKG_VERSION"), "-astral")
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::{Duration, Instant};
|
||||
@@ -8,6 +8,8 @@ use crate::proto::common::{NatType, StunInfo};
|
||||
use anyhow::Context;
|
||||
use chrono::Local;
|
||||
use crossbeam::atomic::AtomicCell;
|
||||
use hickory_resolver::config::{NameServerConfig, Protocol, ResolverConfig, ResolverOpts};
|
||||
use hickory_resolver::TokioAsyncResolver;
|
||||
use rand::seq::IteratorRandom;
|
||||
use tokio::net::{lookup_host, UdpSocket};
|
||||
use tokio::sync::{broadcast, Mutex};
|
||||
@@ -22,21 +24,68 @@ use crate::common::error::Error;
|
||||
|
||||
use super::stun_codec_ext::*;
|
||||
|
||||
pub fn get_default_resolver_config() -> ResolverConfig {
|
||||
let mut default_resolve_config = ResolverConfig::new();
|
||||
default_resolve_config.add_name_server(NameServerConfig::new(
|
||||
"223.5.5.5:53".parse().unwrap(),
|
||||
Protocol::Udp,
|
||||
));
|
||||
default_resolve_config.add_name_server(NameServerConfig::new(
|
||||
"180.184.1.1:53".parse().unwrap(),
|
||||
Protocol::Udp,
|
||||
));
|
||||
default_resolve_config
|
||||
}
|
||||
|
||||
pub async fn resolve_txt_record(
|
||||
domain_name: &str,
|
||||
resolver: &TokioAsyncResolver,
|
||||
) -> Result<String, Error> {
|
||||
let response = resolver.txt_lookup(domain_name).await.with_context(|| {
|
||||
format!(
|
||||
"txt_lookup failed, domain_name: {}",
|
||||
domain_name.to_string()
|
||||
)
|
||||
})?;
|
||||
|
||||
let txt_record = response.iter().next().with_context(|| {
|
||||
format!(
|
||||
"no txt record found, domain_name: {}",
|
||||
domain_name.to_string()
|
||||
)
|
||||
})?;
|
||||
|
||||
let txt_data = String::from_utf8_lossy(&txt_record.txt_data()[0]);
|
||||
tracing::info!(?txt_data, ?domain_name, "get txt record");
|
||||
|
||||
Ok(txt_data.to_string())
|
||||
}
|
||||
|
||||
struct HostResolverIter {
|
||||
hostnames: Vec<String>,
|
||||
ips: Vec<SocketAddr>,
|
||||
max_ip_per_domain: u32,
|
||||
use_ipv6: bool,
|
||||
}
|
||||
|
||||
impl HostResolverIter {
|
||||
fn new(hostnames: Vec<String>, max_ip_per_domain: u32) -> Self {
|
||||
fn new(hostnames: Vec<String>, max_ip_per_domain: u32, use_ipv6: bool) -> Self {
|
||||
Self {
|
||||
hostnames,
|
||||
ips: vec![],
|
||||
max_ip_per_domain,
|
||||
use_ipv6,
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_txt_record(domain_name: &str) -> Result<Vec<String>, Error> {
|
||||
let resolver = TokioAsyncResolver::tokio_from_system_conf().unwrap_or(
|
||||
TokioAsyncResolver::tokio(get_default_resolver_config(), ResolverOpts::default()),
|
||||
);
|
||||
let txt_data = resolve_txt_record(domain_name, &resolver).await?;
|
||||
Ok(txt_data.split(" ").map(|x| x.to_string()).collect())
|
||||
}
|
||||
|
||||
#[async_recursion::async_recursion]
|
||||
async fn next(&mut self) -> Option<SocketAddr> {
|
||||
if self.ips.is_empty() {
|
||||
@@ -51,10 +100,35 @@ impl HostResolverIter {
|
||||
format!("{}:3478", host)
|
||||
};
|
||||
|
||||
if host.starts_with("txt:") {
|
||||
let domain_name = host.trim_start_matches("txt:");
|
||||
match Self::get_txt_record(domain_name).await {
|
||||
Ok(hosts) => {
|
||||
tracing::info!(
|
||||
?domain_name,
|
||||
?hosts,
|
||||
"get txt record success when resolve stun server"
|
||||
);
|
||||
// insert hosts to the head of hostnames
|
||||
self.hostnames.splice(0..0, hosts.into_iter());
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
?domain_name,
|
||||
?e,
|
||||
"get txt record failed when resolve stun server"
|
||||
);
|
||||
}
|
||||
}
|
||||
return self.next().await;
|
||||
}
|
||||
|
||||
let use_ipv6 = self.use_ipv6;
|
||||
|
||||
match lookup_host(&host).await {
|
||||
Ok(ips) => {
|
||||
self.ips = ips
|
||||
.filter(|x| x.is_ipv4())
|
||||
.filter(|x| if use_ipv6 { x.is_ipv6() } else { x.is_ipv4() })
|
||||
.choose_multiple(&mut rand::thread_rng(), self.max_ip_per_domain as usize);
|
||||
|
||||
if self.ips.is_empty() {
|
||||
@@ -400,7 +474,7 @@ impl UdpNatTypeDetectResult {
|
||||
// find resp with distinct stun server
|
||||
self.stun_resps
|
||||
.iter()
|
||||
.map(|x| x.stun_server_addr)
|
||||
.map(|x| x.recv_from_addr)
|
||||
.collect::<BTreeSet<_>>()
|
||||
.len()
|
||||
}
|
||||
@@ -555,8 +629,11 @@ impl UdpNatTypeDetector {
|
||||
udp: Arc<UdpSocket>,
|
||||
) -> Result<UdpNatTypeDetectResult, Error> {
|
||||
let mut stun_servers = vec![];
|
||||
let mut host_resolver =
|
||||
HostResolverIter::new(self.stun_server_hosts.clone(), self.max_ip_per_domain);
|
||||
let mut host_resolver = HostResolverIter::new(
|
||||
self.stun_server_hosts.clone(),
|
||||
self.max_ip_per_domain,
|
||||
false,
|
||||
);
|
||||
while let Some(addr) = host_resolver.next().await {
|
||||
stun_servers.push(addr);
|
||||
}
|
||||
@@ -602,7 +679,9 @@ pub trait StunInfoCollectorTrait: Send + Sync {
|
||||
|
||||
pub struct StunInfoCollector {
|
||||
stun_servers: Arc<RwLock<Vec<String>>>,
|
||||
stun_servers_v6: Arc<RwLock<Vec<String>>>,
|
||||
udp_nat_test_result: Arc<RwLock<Option<UdpNatTypeDetectResult>>>,
|
||||
public_ipv6: Arc<AtomicCell<Option<Ipv6Addr>>>,
|
||||
nat_test_result_time: Arc<AtomicCell<chrono::DateTime<Local>>>,
|
||||
redetect_notify: Arc<tokio::sync::Notify>,
|
||||
tasks: std::sync::Mutex<JoinSet<()>>,
|
||||
@@ -621,7 +700,12 @@ impl StunInfoCollectorTrait for StunInfoCollector {
|
||||
udp_nat_type: result.nat_type() as i32,
|
||||
tcp_nat_type: 0,
|
||||
last_update_time: self.nat_test_result_time.load().timestamp(),
|
||||
public_ip: result.public_ips().iter().map(|x| x.to_string()).collect(),
|
||||
public_ip: result
|
||||
.public_ips()
|
||||
.iter()
|
||||
.map(|x| x.to_string())
|
||||
.chain(self.public_ipv6.load().map(|x| x.to_string()))
|
||||
.collect(),
|
||||
min_port: result.min_port() as u32,
|
||||
max_port: result.max_port() as u32,
|
||||
}
|
||||
@@ -640,7 +724,7 @@ impl StunInfoCollectorTrait for StunInfoCollector {
|
||||
|
||||
if stun_servers.is_empty() {
|
||||
let mut host_resolver =
|
||||
HostResolverIter::new(self.stun_servers.read().unwrap().clone(), 2);
|
||||
HostResolverIter::new(self.stun_servers.read().unwrap().clone(), 2, false);
|
||||
while let Some(addr) = host_resolver.next().await {
|
||||
stun_servers.push(addr);
|
||||
if stun_servers.len() >= 2 {
|
||||
@@ -680,7 +764,9 @@ impl StunInfoCollector {
|
||||
pub fn new(stun_servers: Vec<String>) -> Self {
|
||||
Self {
|
||||
stun_servers: Arc::new(RwLock::new(stun_servers)),
|
||||
stun_servers_v6: Arc::new(RwLock::new(Self::get_default_servers_v6())),
|
||||
udp_nat_test_result: Arc::new(RwLock::new(None)),
|
||||
public_ipv6: Arc::new(AtomicCell::new(None)),
|
||||
nat_test_result_time: Arc::new(AtomicCell::new(Local::now())),
|
||||
redetect_notify: Arc::new(tokio::sync::Notify::new()),
|
||||
tasks: std::sync::Mutex::new(JoinSet::new()),
|
||||
@@ -696,28 +782,42 @@ impl StunInfoCollector {
|
||||
// NOTICE: we may need to choose stun stun server based on geo location
|
||||
// stun server cross nation may return a external ip address with high latency and loss rate
|
||||
vec![
|
||||
"txt:stun.easytier.cn",
|
||||
"stun.miwifi.com",
|
||||
"stun.chat.bilibili.com",
|
||||
"stun.hitv.com",
|
||||
"stun.cdnbye.com",
|
||||
"stun.douyucdn.cn:18000",
|
||||
"fwa.lifesizecloud.com",
|
||||
"global.turn.twilio.com",
|
||||
"turn.cloudflare.com",
|
||||
"stun.isp.net.au",
|
||||
"stun.nextcloud.com",
|
||||
"stun.freeswitch.org",
|
||||
"stun.voip.blackberry.com",
|
||||
"stunserver.stunprotocol.org",
|
||||
"stun.sipnet.com",
|
||||
"stun.radiojar.com",
|
||||
"stun.sonetel.com",
|
||||
]
|
||||
.iter()
|
||||
.map(|x| x.to_string())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_default_servers_v6() -> Vec<String> {
|
||||
vec!["txt:stun-v6.easytier.cn"]
|
||||
.iter()
|
||||
.map(|x| x.to_string())
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn get_public_ipv6(servers: &Vec<String>) -> Option<Ipv6Addr> {
|
||||
let mut ips = HostResolverIter::new(servers.to_vec(), 10, true);
|
||||
while let Some(ip) = ips.next().await {
|
||||
let udp = Arc::new(UdpSocket::bind(format!("[::]:0")).await.unwrap());
|
||||
let ret = StunClientBuilder::new(udp.clone())
|
||||
.new_stun_client(ip)
|
||||
.bind_request(false, false)
|
||||
.await;
|
||||
tracing::debug!(?ret, "finish ipv6 udp nat type detect");
|
||||
match ret.map(|x| x.mapped_socket_addr.map(|x| x.ip())) {
|
||||
Ok(Some(IpAddr::V6(v6))) => {
|
||||
return Some(v6);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn start_stun_routine(&self) {
|
||||
if self.started.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
return;
|
||||
@@ -784,6 +884,30 @@ impl StunInfoCollector {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// for ipv6
|
||||
let stun_servers = self.stun_servers_v6.clone();
|
||||
let stored_ipv6 = self.public_ipv6.clone();
|
||||
let redetect_notify = self.redetect_notify.clone();
|
||||
self.tasks.lock().unwrap().spawn(async move {
|
||||
loop {
|
||||
let servers = stun_servers.read().unwrap().clone();
|
||||
Self::get_public_ipv6(&servers)
|
||||
.await
|
||||
.map(|x| stored_ipv6.store(Some(x)));
|
||||
|
||||
let sleep_sec = if stored_ipv6.load().is_none() {
|
||||
60
|
||||
} else {
|
||||
360
|
||||
};
|
||||
|
||||
tokio::select! {
|
||||
_ = redetect_notify.notified() => {}
|
||||
_ = tokio::time::sleep(Duration::from_secs(sleep_sec)) => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn update_stun_info(&self) {
|
||||
@@ -862,6 +986,48 @@ mod tests {
|
||||
let detector = UdpNatTypeDetector::new(stun_servers, 1);
|
||||
let ret = detector.detect_nat_type(0).await;
|
||||
println!("{:#?}, {:?}", ret, ret.as_ref().unwrap().nat_type());
|
||||
assert_eq!(ret.unwrap().nat_type(), NatType::PortRestricted);
|
||||
assert_eq!(ret.unwrap().nat_type(), NatType::Restricted);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_txt_public_stun_server() {
|
||||
let stun_servers = vec!["txt:stun.easytier.cn".to_string()];
|
||||
let detector = UdpNatTypeDetector::new(stun_servers, 1);
|
||||
let ret = detector.detect_nat_type(0).await;
|
||||
println!("{:#?}, {:?}", ret, ret.as_ref().unwrap().nat_type());
|
||||
assert!(!ret.unwrap().stun_resps.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_v4_stun() {
|
||||
let mut udp_server = UdpTunnelListener::new("udp://0.0.0.0:55355".parse().unwrap());
|
||||
let mut tasks = JoinSet::new();
|
||||
tasks.spawn(async move {
|
||||
udp_server.listen().await.unwrap();
|
||||
loop {
|
||||
udp_server.accept().await.unwrap();
|
||||
}
|
||||
});
|
||||
let stun_servers = vec!["127.0.0.1:55355".to_string()];
|
||||
|
||||
let detector = UdpNatTypeDetector::new(stun_servers, 1);
|
||||
let ret = detector.detect_nat_type(0).await;
|
||||
println!("{:#?}, {:?}", ret, ret.as_ref().unwrap().nat_type());
|
||||
assert_eq!(ret.unwrap().nat_type(), NatType::Restricted);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_v6_stun() {
|
||||
let mut udp_server = UdpTunnelListener::new("udp://[::]:55355".parse().unwrap());
|
||||
let mut tasks = JoinSet::new();
|
||||
tasks.spawn(async move {
|
||||
udp_server.listen().await.unwrap();
|
||||
loop {
|
||||
udp_server.accept().await.unwrap();
|
||||
}
|
||||
});
|
||||
let stun_servers = vec!["::1:55355".to_string()];
|
||||
let ret = StunInfoCollector::get_public_ipv6(&stun_servers).await;
|
||||
println!("{:#?}", ret);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// try connect peers directly, with either its public ip or lan ip
|
||||
|
||||
use std::{
|
||||
net::SocketAddr,
|
||||
collections::HashSet,
|
||||
net::{Ipv6Addr, SocketAddr},
|
||||
str::FromStr,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
@@ -79,7 +81,6 @@ struct DstListenerUrlBlackListItem(PeerId, url::Url);
|
||||
struct DirectConnectorManagerData {
|
||||
global_ctx: ArcGlobalCtx,
|
||||
peer_manager: Arc<PeerManager>,
|
||||
dst_blacklist: timedmap::TimedMap<DstBlackListItem, ()>,
|
||||
dst_listener_blacklist: timedmap::TimedMap<DstListenerUrlBlackListItem, ()>,
|
||||
}
|
||||
|
||||
@@ -88,7 +89,6 @@ impl DirectConnectorManagerData {
|
||||
Self {
|
||||
global_ctx,
|
||||
peer_manager,
|
||||
dst_blacklist: timedmap::TimedMap::new(),
|
||||
dst_listener_blacklist: timedmap::TimedMap::new(),
|
||||
}
|
||||
}
|
||||
@@ -150,7 +150,9 @@ impl DirectConnectorManager {
|
||||
let peers = data.peer_manager.list_peers().await;
|
||||
let mut tasks = JoinSet::new();
|
||||
for peer_id in peers {
|
||||
if peer_id == my_peer_id {
|
||||
if peer_id == my_peer_id
|
||||
|| data.peer_manager.has_directly_connected_conn(peer_id)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
tasks.spawn(Self::do_try_direct_connect(data.clone(), peer_id));
|
||||
@@ -173,24 +175,13 @@ impl DirectConnectorManager {
|
||||
dst_peer_id: PeerId,
|
||||
addr: String,
|
||||
) -> Result<(), Error> {
|
||||
data.dst_blacklist.cleanup();
|
||||
if data
|
||||
.dst_blacklist
|
||||
.contains(&DstBlackListItem(dst_peer_id.clone(), addr.clone()))
|
||||
{
|
||||
tracing::debug!("try_connect_to_ip failed, addr in blacklist: {}", addr);
|
||||
return Err(Error::UrlInBlacklist);
|
||||
}
|
||||
|
||||
let connector = create_connector_by_url(&addr, &data.global_ctx).await?;
|
||||
let (peer_id, conn_id) = timeout(
|
||||
std::time::Duration::from_secs(5),
|
||||
data.peer_manager.try_connect(connector),
|
||||
std::time::Duration::from_secs(3),
|
||||
data.peer_manager.try_direct_connect(connector),
|
||||
)
|
||||
.await??;
|
||||
|
||||
// let (peer_id, conn_id) = data.peer_manager.try_connect(connector).await?;
|
||||
|
||||
if peer_id != dst_peer_id && !TESTING.load(Ordering::Relaxed) {
|
||||
tracing::info!(
|
||||
"connect to ip succ: {}, but peer id mismatch, expect: {}, actual: {}",
|
||||
@@ -204,6 +195,7 @@ impl DirectConnectorManager {
|
||||
.await?;
|
||||
return Err(Error::InvalidUrl(addr));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -214,7 +206,7 @@ impl DirectConnectorManager {
|
||||
addr: String,
|
||||
) -> Result<(), Error> {
|
||||
let mut rand_gen = rand::rngs::OsRng::default();
|
||||
let backoff_ms = vec![1000, 2000, 4000];
|
||||
let backoff_ms = vec![1000, 2000];
|
||||
let mut backoff_idx = 0;
|
||||
|
||||
loop {
|
||||
@@ -237,12 +229,6 @@ impl DirectConnectorManager {
|
||||
backoff_idx += 1;
|
||||
continue;
|
||||
} else {
|
||||
data.dst_blacklist.insert(
|
||||
DstBlackListItem(dst_peer_id.clone(), addr.clone()),
|
||||
(),
|
||||
std::time::Duration::from_secs(DIRECT_CONNECTOR_BLACKLIST_TIMEOUT_SEC),
|
||||
);
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -273,61 +259,43 @@ impl DirectConnectorManager {
|
||||
|
||||
tracing::debug!(?available_listeners, "got available listeners");
|
||||
|
||||
let mut listener = available_listeners.get(0).ok_or(anyhow::anyhow!(
|
||||
"peer {} have no valid listener",
|
||||
dst_peer_id
|
||||
))?;
|
||||
if available_listeners.is_empty() {
|
||||
return Err(anyhow::anyhow!("peer {} have no valid listener", dst_peer_id).into());
|
||||
}
|
||||
|
||||
// if have default listener, use it first
|
||||
listener = available_listeners
|
||||
let listener = available_listeners
|
||||
.iter()
|
||||
.find(|l| l.scheme() == data.global_ctx.get_flags().default_protocol)
|
||||
.unwrap_or(listener);
|
||||
.unwrap_or(available_listeners.get(0).unwrap());
|
||||
|
||||
let mut tasks = JoinSet::new();
|
||||
let mut tasks = bounded_join_set::JoinSet::new(2);
|
||||
|
||||
let listener_host = listener.socket_addrs(|| None).unwrap().pop();
|
||||
match listener_host {
|
||||
Some(SocketAddr::V4(s_addr)) => {
|
||||
if s_addr.ip().is_unspecified() {
|
||||
ip_list.interface_ipv4s.iter().for_each(|ip| {
|
||||
let mut addr = (*listener).clone();
|
||||
if addr.set_host(Some(ip.to_string().as_str())).is_ok() {
|
||||
tasks.spawn(Self::try_connect_to_ip(
|
||||
data.clone(),
|
||||
dst_peer_id.clone(),
|
||||
addr.to_string(),
|
||||
));
|
||||
} else {
|
||||
tracing::error!(
|
||||
?ip,
|
||||
?listener,
|
||||
?dst_peer_id,
|
||||
"failed to set host for interface ipv4"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(public_ipv4) = ip_list.public_ipv4 {
|
||||
let mut addr = (*listener).clone();
|
||||
if addr
|
||||
.set_host(Some(public_ipv4.to_string().as_str()))
|
||||
.is_ok()
|
||||
{
|
||||
tasks.spawn(Self::try_connect_to_ip(
|
||||
data.clone(),
|
||||
dst_peer_id.clone(),
|
||||
addr.to_string(),
|
||||
));
|
||||
} else {
|
||||
tracing::error!(
|
||||
?public_ipv4,
|
||||
?listener,
|
||||
?dst_peer_id,
|
||||
"failed to set host for public ipv4"
|
||||
);
|
||||
}
|
||||
}
|
||||
ip_list
|
||||
.interface_ipv4s
|
||||
.iter()
|
||||
.chain(ip_list.public_ipv4.iter())
|
||||
.for_each(|ip| {
|
||||
let mut addr = (*listener).clone();
|
||||
if addr.set_host(Some(ip.to_string().as_str())).is_ok() {
|
||||
tasks.spawn(Self::try_connect_to_ip(
|
||||
data.clone(),
|
||||
dst_peer_id.clone(),
|
||||
addr.to_string(),
|
||||
));
|
||||
} else {
|
||||
tracing::error!(
|
||||
?ip,
|
||||
?listener,
|
||||
?dst_peer_id,
|
||||
"failed to set host for interface ipv4"
|
||||
);
|
||||
}
|
||||
});
|
||||
} else if !s_addr.ip().is_loopback() || TESTING.load(Ordering::Relaxed) {
|
||||
tasks.spawn(Self::try_connect_to_ip(
|
||||
data.clone(),
|
||||
@@ -338,47 +306,42 @@ impl DirectConnectorManager {
|
||||
}
|
||||
Some(SocketAddr::V6(s_addr)) => {
|
||||
if s_addr.ip().is_unspecified() {
|
||||
ip_list.interface_ipv6s.iter().for_each(|ip| {
|
||||
let mut addr = (*listener).clone();
|
||||
if addr
|
||||
.set_host(Some(format!("[{}]", ip.to_string()).as_str()))
|
||||
.is_ok()
|
||||
{
|
||||
tasks.spawn(Self::try_connect_to_ip(
|
||||
data.clone(),
|
||||
dst_peer_id.clone(),
|
||||
addr.to_string(),
|
||||
));
|
||||
} else {
|
||||
tracing::error!(
|
||||
?ip,
|
||||
?listener,
|
||||
?dst_peer_id,
|
||||
"failed to set host for interface ipv6"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(public_ipv6) = ip_list.public_ipv6 {
|
||||
let mut addr = (*listener).clone();
|
||||
if addr
|
||||
.set_host(Some(format!("[{}]", public_ipv6.to_string()).as_str()))
|
||||
.is_ok()
|
||||
{
|
||||
tasks.spawn(Self::try_connect_to_ip(
|
||||
data.clone(),
|
||||
dst_peer_id.clone(),
|
||||
addr.to_string(),
|
||||
));
|
||||
} else {
|
||||
tracing::error!(
|
||||
?public_ipv6,
|
||||
?listener,
|
||||
?dst_peer_id,
|
||||
"failed to set host for public ipv6"
|
||||
);
|
||||
}
|
||||
}
|
||||
// for ipv6, only try public ip
|
||||
ip_list
|
||||
.interface_ipv6s
|
||||
.iter()
|
||||
.chain(ip_list.public_ipv6.iter())
|
||||
.filter_map(|x| Ipv6Addr::from_str(&x.to_string()).ok())
|
||||
.filter(|x| {
|
||||
TESTING.load(Ordering::Relaxed)
|
||||
|| (!x.is_loopback()
|
||||
&& !x.is_unspecified()
|
||||
&& !x.is_unique_local()
|
||||
&& !x.is_unicast_link_local()
|
||||
&& !x.is_multicast())
|
||||
})
|
||||
.collect::<HashSet<_>>()
|
||||
.iter()
|
||||
.for_each(|ip| {
|
||||
let mut addr = (*listener).clone();
|
||||
if addr
|
||||
.set_host(Some(format!("[{}]", ip.to_string()).as_str()))
|
||||
.is_ok()
|
||||
{
|
||||
tasks.spawn(Self::try_connect_to_ip(
|
||||
data.clone(),
|
||||
dst_peer_id.clone(),
|
||||
addr.to_string(),
|
||||
));
|
||||
} else {
|
||||
tracing::error!(
|
||||
?ip,
|
||||
?listener,
|
||||
?dst_peer_id,
|
||||
"failed to set host for public ipv6"
|
||||
);
|
||||
}
|
||||
});
|
||||
} else if !s_addr.ip().is_loopback() || TESTING.load(Ordering::Relaxed) {
|
||||
tasks.spawn(Self::try_connect_to_ip(
|
||||
data.clone(),
|
||||
@@ -430,14 +393,6 @@ impl DirectConnectorManager {
|
||||
dst_peer_id: PeerId,
|
||||
) -> Result<(), Error> {
|
||||
let peer_manager = data.peer_manager.clone();
|
||||
// check if we have direct connection with dst_peer_id
|
||||
if let Some(c) = peer_manager.list_peer_conns(dst_peer_id).await {
|
||||
// currently if we have any type of direct connection (udp or tcp), we will not try to connect
|
||||
if !c.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
tracing::debug!("try direct connect to peer: {}", dst_peer_id);
|
||||
|
||||
let rpc_stub = peer_manager
|
||||
@@ -466,8 +421,7 @@ mod tests {
|
||||
|
||||
use crate::{
|
||||
connector::direct::{
|
||||
DirectConnectorManager, DirectConnectorManagerData, DstBlackListItem,
|
||||
DstListenerUrlBlackListItem,
|
||||
DirectConnectorManager, DirectConnectorManagerData, DstListenerUrlBlackListItem,
|
||||
},
|
||||
instance::listeners::ListenerManager,
|
||||
peers::tests::{
|
||||
@@ -526,9 +480,7 @@ mod tests {
|
||||
#[values("tcp", "udp", "wg")] proto: &str,
|
||||
#[values("true", "false")] ipv6: bool,
|
||||
) {
|
||||
if ipv6 && proto != "udp" {
|
||||
return;
|
||||
}
|
||||
TESTING.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
let p_a = create_mock_peer_manager().await;
|
||||
let p_b = create_mock_peer_manager().await;
|
||||
@@ -544,14 +496,18 @@ mod tests {
|
||||
dm_a.run_as_client();
|
||||
dm_c.run_as_server();
|
||||
|
||||
let port = if proto == "wg" { 11040 } else { 11041 };
|
||||
if !ipv6 {
|
||||
let port = if proto == "wg" { 11040 } else { 11041 };
|
||||
p_c.get_global_ctx().config.set_listeners(vec![format!(
|
||||
"{}://0.0.0.0:{}",
|
||||
proto, port
|
||||
)
|
||||
.parse()
|
||||
.unwrap()]);
|
||||
} else {
|
||||
p_c.get_global_ctx()
|
||||
.config
|
||||
.set_listeners(vec![format!("{}://[::]:{}", proto, port).parse().unwrap()]);
|
||||
}
|
||||
let mut f = p_c.get_global_ctx().config.get_flags();
|
||||
f.enable_ipv6 = ipv6;
|
||||
@@ -592,9 +548,5 @@ mod tests {
|
||||
1,
|
||||
"tcp://127.0.0.1:10222".parse().unwrap()
|
||||
)));
|
||||
|
||||
assert!(data
|
||||
.dst_blacklist
|
||||
.contains(&DstBlackListItem(1, ip_list.listeners[0].to_string())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
common::{error::Error, global_ctx::ArcGlobalCtx},
|
||||
tunnel::{Tunnel, TunnelConnector, TunnelError, PROTO_PORT_OFFSET},
|
||||
common::{
|
||||
error::Error,
|
||||
global_ctx::ArcGlobalCtx,
|
||||
stun::{get_default_resolver_config, resolve_txt_record},
|
||||
},
|
||||
tunnel::{IpVersion, Tunnel, TunnelConnector, TunnelError, PROTO_PORT_OFFSET},
|
||||
};
|
||||
use anyhow::Context;
|
||||
use dashmap::DashSet;
|
||||
use hickory_resolver::{
|
||||
config::{NameServerConfig, Protocol, ResolverConfig, ResolverOpts},
|
||||
config::{ResolverConfig, ResolverOpts},
|
||||
proto::rr::rdata::SRV,
|
||||
TokioAsyncResolver,
|
||||
};
|
||||
@@ -38,6 +42,7 @@ pub struct DNSTunnelConnector {
|
||||
addr: url::Url,
|
||||
bind_addrs: Vec<SocketAddr>,
|
||||
global_ctx: ArcGlobalCtx,
|
||||
ip_version: IpVersion,
|
||||
|
||||
default_resolve_config: ResolverConfig,
|
||||
default_resolve_opts: ResolverOpts,
|
||||
@@ -45,21 +50,13 @@ pub struct DNSTunnelConnector {
|
||||
|
||||
impl DNSTunnelConnector {
|
||||
pub fn new(addr: url::Url, global_ctx: ArcGlobalCtx) -> Self {
|
||||
let mut default_resolve_config = ResolverConfig::new();
|
||||
default_resolve_config.add_name_server(NameServerConfig::new(
|
||||
"223.5.5.5:53".parse().unwrap(),
|
||||
Protocol::Udp,
|
||||
));
|
||||
default_resolve_config.add_name_server(NameServerConfig::new(
|
||||
"180.184.1.1:53".parse().unwrap(),
|
||||
Protocol::Udp,
|
||||
));
|
||||
Self {
|
||||
addr,
|
||||
bind_addrs: Vec::new(),
|
||||
global_ctx,
|
||||
ip_version: IpVersion::Both,
|
||||
|
||||
default_resolve_config,
|
||||
default_resolve_config: get_default_resolver_config(),
|
||||
default_resolve_opts: ResolverOpts::default(),
|
||||
}
|
||||
}
|
||||
@@ -69,26 +66,14 @@ impl DNSTunnelConnector {
|
||||
&self,
|
||||
domain_name: &str,
|
||||
) -> Result<Box<dyn TunnelConnector>, Error> {
|
||||
let resolver = TokioAsyncResolver::tokio_from_system_conf().unwrap_or(
|
||||
TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default()),
|
||||
);
|
||||
|
||||
let response = resolver.txt_lookup(domain_name).await.with_context(|| {
|
||||
format!(
|
||||
"txt_lookup failed, domain_name: {}",
|
||||
domain_name.to_string()
|
||||
)
|
||||
})?;
|
||||
|
||||
let txt_record = response.iter().next().with_context(|| {
|
||||
format!(
|
||||
"no txt record found, domain_name: {}",
|
||||
domain_name.to_string()
|
||||
)
|
||||
})?;
|
||||
|
||||
let txt_data = String::from_utf8_lossy(&txt_record.txt_data()[0]);
|
||||
tracing::info!(?txt_data, ?domain_name, "get txt record");
|
||||
let resolver =
|
||||
TokioAsyncResolver::tokio_from_system_conf().unwrap_or(TokioAsyncResolver::tokio(
|
||||
self.default_resolve_config.clone(),
|
||||
self.default_resolve_opts.clone(),
|
||||
));
|
||||
let txt_data = resolve_txt_record(domain_name, &resolver)
|
||||
.await
|
||||
.with_context(|| format!("resolve txt record failed, domain_name: {}", domain_name))?;
|
||||
|
||||
let candidate_urls = txt_data
|
||||
.split(" ")
|
||||
@@ -106,9 +91,9 @@ impl DNSTunnelConnector {
|
||||
)
|
||||
})?;
|
||||
|
||||
let connector = create_connector_by_url(url.as_str(), &self.global_ctx).await;
|
||||
|
||||
connector
|
||||
let mut connector = create_connector_by_url(url.as_str(), &self.global_ctx).await?;
|
||||
connector.set_ip_version(self.ip_version);
|
||||
Ok(connector)
|
||||
}
|
||||
|
||||
fn handle_one_srv_record(record: &SRV, protocol: &str) -> Result<(url::Url, u64), Error> {
|
||||
@@ -141,9 +126,11 @@ impl DNSTunnelConnector {
|
||||
) -> Result<Box<dyn TunnelConnector>, Error> {
|
||||
tracing::info!("handle_srv_record: {}", domain_name);
|
||||
|
||||
let resolver = TokioAsyncResolver::tokio_from_system_conf().unwrap_or(
|
||||
TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default()),
|
||||
);
|
||||
let resolver =
|
||||
TokioAsyncResolver::tokio_from_system_conf().unwrap_or(TokioAsyncResolver::tokio(
|
||||
self.default_resolve_config.clone(),
|
||||
self.default_resolve_opts.clone(),
|
||||
));
|
||||
|
||||
let srv_domains = PROTO_PORT_OFFSET
|
||||
.iter()
|
||||
@@ -192,8 +179,9 @@ impl DNSTunnelConnector {
|
||||
)
|
||||
})?;
|
||||
|
||||
let connector = create_connector_by_url(url.as_str(), &self.global_ctx).await;
|
||||
connector
|
||||
let mut connector = create_connector_by_url(url.as_str(), &self.global_ctx).await?;
|
||||
connector.set_ip_version(self.ip_version);
|
||||
Ok(connector)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,6 +226,10 @@ impl super::TunnelConnector for DNSTunnelConnector {
|
||||
fn set_bind_addrs(&mut self, addrs: Vec<SocketAddr>) {
|
||||
self.bind_addrs = addrs;
|
||||
}
|
||||
|
||||
fn set_ip_version(&mut self, ip_version: IpVersion) {
|
||||
self.ip_version = ip_version;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -293,7 +293,6 @@ impl ManualConnectorManager {
|
||||
ip_version: IpVersion,
|
||||
) -> Result<ReconnResult, Error> {
|
||||
let ip_collector = data.global_ctx.get_ip_collector();
|
||||
let net_ns = data.net_ns.clone();
|
||||
|
||||
connector.lock().await.set_ip_version(ip_version);
|
||||
|
||||
@@ -309,18 +308,11 @@ impl ManualConnectorManager {
|
||||
data.global_ctx.issue_event(GlobalCtxEvent::Connecting(
|
||||
connector.lock().await.remote_url().clone(),
|
||||
));
|
||||
|
||||
let _g = net_ns.guard();
|
||||
tracing::info!("reconnect try connect... conn: {:?}", connector);
|
||||
let tunnel = connector.lock().await.connect().await?;
|
||||
tracing::info!("reconnect get tunnel succ: {:?}", tunnel);
|
||||
assert_eq!(
|
||||
dead_url,
|
||||
tunnel.info().unwrap().remote_addr.unwrap().to_string(),
|
||||
"info: {:?}",
|
||||
tunnel.info()
|
||||
);
|
||||
let (peer_id, conn_id) = data.peer_manager.add_client_tunnel(tunnel).await?;
|
||||
let (peer_id, conn_id) = data
|
||||
.peer_manager
|
||||
.try_direct_connect(connector.lock().await.as_mut())
|
||||
.await?;
|
||||
tracing::info!("reconnect succ: {} {} {}", peer_id, conn_id, dead_url);
|
||||
Ok(ReconnResult {
|
||||
dead_url,
|
||||
|
||||
@@ -388,7 +388,7 @@ impl UdpHolePunchListener {
|
||||
tracing::warn!(?conn, "udp hole punching listener got peer connection");
|
||||
let peer_mgr = peer_mgr.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = peer_mgr.add_tunnel_as_server(conn).await {
|
||||
if let Err(e) = peer_mgr.add_tunnel_as_server(conn, false).await {
|
||||
tracing::error!(
|
||||
?e,
|
||||
"failed to add tunnel as server in hole punch listener"
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
use std::{
|
||||
ffi::OsString, fmt::Write, net::SocketAddr, path::PathBuf, sync::Mutex, time::Duration, vec,
|
||||
ffi::OsString,
|
||||
fmt::Write,
|
||||
net::{IpAddr, SocketAddr},
|
||||
path::PathBuf,
|
||||
sync::Mutex,
|
||||
time::Duration,
|
||||
vec,
|
||||
};
|
||||
|
||||
use anyhow::{Context, Ok};
|
||||
use anyhow::Context;
|
||||
use clap::{command, Args, Parser, Subcommand};
|
||||
use humansize::format_size;
|
||||
use service_manager::*;
|
||||
@@ -311,7 +317,11 @@ impl CommandHandler {
|
||||
ipv4: route.ipv4_addr.map(|ip| ip.to_string()).unwrap_or_default(),
|
||||
hostname: route.hostname.clone(),
|
||||
cost: cost_to_str(route.cost),
|
||||
lat_ms: float_to_str(p.get_latency_ms().unwrap_or(0.0), 3),
|
||||
lat_ms: if route.cost == 1 {
|
||||
float_to_str(p.get_latency_ms().unwrap_or(0.0), 3)
|
||||
} else {
|
||||
route.path_latency_latency_first().to_string()
|
||||
},
|
||||
loss_rate: float_to_str(p.get_loss_rate().unwrap_or(0.0), 3),
|
||||
rx_bytes: format_size(p.get_rx_bytes().unwrap_or(0), humansize::DECIMAL),
|
||||
tx_bytes: format_size(p.get_tx_bytes().unwrap_or(0), humansize::DECIMAL),
|
||||
@@ -1036,6 +1046,7 @@ async fn main() -> Result<(), Error> {
|
||||
match sub_cmd.sub_command {
|
||||
Some(NodeSubCommand::Info) | None => {
|
||||
let stun_info = node_info.stun_info.clone().unwrap_or_default();
|
||||
let ip_list = node_info.ip_list.clone().unwrap_or_default();
|
||||
|
||||
let mut builder = tabled::builder::Builder::default();
|
||||
builder.push_record(vec!["Virtual IP", node_info.ipv4_addr.as_str()]);
|
||||
@@ -1045,11 +1056,32 @@ async fn main() -> Result<(), Error> {
|
||||
node_info.proxy_cidrs.join(", ").as_str(),
|
||||
]);
|
||||
builder.push_record(vec!["Peer ID", node_info.peer_id.to_string().as_str()]);
|
||||
builder.push_record(vec!["Public IP", stun_info.public_ip.join(", ").as_str()]);
|
||||
stun_info.public_ip.iter().for_each(|ip| {
|
||||
let Ok(ip) = ip.parse::<IpAddr>() else {
|
||||
return;
|
||||
};
|
||||
if ip.is_ipv4() {
|
||||
builder.push_record(vec!["Public IPv4", ip.to_string().as_str()]);
|
||||
} else {
|
||||
builder.push_record(vec!["Public IPv6", ip.to_string().as_str()]);
|
||||
}
|
||||
});
|
||||
builder.push_record(vec![
|
||||
"UDP Stun Type",
|
||||
format!("{:?}", stun_info.udp_nat_type()).as_str(),
|
||||
]);
|
||||
ip_list.interface_ipv4s.iter().for_each(|ip| {
|
||||
builder.push_record(vec![
|
||||
"Interface IPv4",
|
||||
format!("{}", ip.to_string()).as_str(),
|
||||
]);
|
||||
});
|
||||
ip_list.interface_ipv6s.iter().for_each(|ip| {
|
||||
builder.push_record(vec![
|
||||
"Interface IPv6",
|
||||
format!("{}", ip.to_string()).as_str(),
|
||||
]);
|
||||
});
|
||||
for (idx, l) in node_info.listeners.iter().enumerate() {
|
||||
if l.starts_with("ring") {
|
||||
continue;
|
||||
|
||||
@@ -308,12 +308,6 @@ struct Cli {
|
||||
)]
|
||||
socks5: Option<u16>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
help = t!("core_clap.ipv6_listener").to_string()
|
||||
)]
|
||||
ipv6_listener: Option<String>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
help = t!("core_clap.compression").to_string(),
|
||||
@@ -576,11 +570,6 @@ impl TryFrom<&Cli> for TomlConfigLoader {
|
||||
f.disable_p2p = cli.disable_p2p;
|
||||
f.disable_udp_hole_punching = cli.disable_udp_hole_punching;
|
||||
f.relay_all_peer_rpc = cli.relay_all_peer_rpc;
|
||||
if let Some(ipv6_listener) = cli.ipv6_listener.as_ref() {
|
||||
f.ipv6_listener = ipv6_listener
|
||||
.parse()
|
||||
.with_context(|| format!("failed to parse ipv6 listener: {}", ipv6_listener))?
|
||||
}
|
||||
f.multi_thread = cli.multi_thread;
|
||||
f.data_compress_algo = match cli.compression.as_str() {
|
||||
"none" => CompressionAlgoPb::None,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::{fmt::Debug, sync::Arc};
|
||||
|
||||
use anyhow::Context;
|
||||
use async_trait::async_trait;
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
@@ -49,6 +50,10 @@ pub fn get_listener_by_url(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_url_host_ipv6(l: &url::Url) -> bool {
|
||||
l.host_str().map_or(false, |h| h.contains(':'))
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait TunnelHandlerForListener {
|
||||
async fn handle_tunnel(&self, tunnel: Box<dyn Tunnel>) -> Result<(), Error>;
|
||||
@@ -58,7 +63,7 @@ pub trait TunnelHandlerForListener {
|
||||
impl TunnelHandlerForListener for PeerManager {
|
||||
#[tracing::instrument]
|
||||
async fn handle_tunnel(&self, tunnel: Box<dyn Tunnel>) -> Result<(), Error> {
|
||||
self.add_tunnel_as_server(tunnel).await
|
||||
self.add_tunnel_as_server(tunnel, true).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,22 +118,26 @@ impl<H: TunnelHandlerForListener + Send + Sync + 'static + Debug> ListenerManage
|
||||
continue;
|
||||
};
|
||||
let ctx = self.global_ctx.clone();
|
||||
self.add_listener(move || get_listener_by_url(&l, ctx.clone()).unwrap(), true)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if self.global_ctx.config.get_flags().enable_ipv6 {
|
||||
let ipv6_listener = self.global_ctx.config.get_flags().ipv6_listener.clone();
|
||||
let _ = self
|
||||
.add_listener(
|
||||
move || {
|
||||
Box::new(UdpTunnelListener::new(
|
||||
ipv6_listener.clone().parse().unwrap(),
|
||||
))
|
||||
},
|
||||
let listener = l.clone();
|
||||
self.add_listener(
|
||||
move || get_listener_by_url(&listener, ctx.clone()).unwrap(),
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if self.global_ctx.config.get_flags().enable_ipv6 && !is_url_host_ipv6(&l) {
|
||||
let mut ipv6_listener = l.clone();
|
||||
ipv6_listener
|
||||
.set_host(Some("[::]".to_string().as_str()))
|
||||
.with_context(|| format!("failed to set ipv6 host for listener: {}", l))?;
|
||||
let ctx = self.global_ctx.clone();
|
||||
self.add_listener(
|
||||
move || get_listener_by_url(&ipv6_listener, ctx.clone()).unwrap(),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -161,11 +170,11 @@ impl<H: TunnelHandlerForListener + Send + Sync + 'static + Debug> ListenerManage
|
||||
global_ctx.issue_event(GlobalCtxEvent::ListenerAdded(l.local_url()));
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(?e, ?l, "listener listen error");
|
||||
global_ctx.issue_event(GlobalCtxEvent::ListenerAddFailed(
|
||||
l.local_url(),
|
||||
format!("error: {:?}, retry listen later...", e),
|
||||
));
|
||||
tracing::error!(?e, ?l, "listener listen error");
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
continue;
|
||||
}
|
||||
@@ -217,6 +226,15 @@ impl<H: TunnelHandlerForListener + Send + Sync + 'static + Debug> ListenerManage
|
||||
|
||||
pub async fn run(&mut self) -> Result<(), Error> {
|
||||
for listener in &self.listeners {
|
||||
if listener.must_succ {
|
||||
// try listen once
|
||||
let mut l = (listener.creator_fn)();
|
||||
let _g = self.net_ns.guard();
|
||||
l.listen()
|
||||
.await
|
||||
.with_context(|| format!("failed to listen on {}", l.local_url()))?;
|
||||
}
|
||||
|
||||
self.tasks.spawn(Self::run_listener(
|
||||
listener.creator_fn.clone(),
|
||||
self.peer_manager.clone(),
|
||||
|
||||
@@ -695,7 +695,8 @@ mod tests {
|
||||
|
||||
let (a_ring, b_ring) = crate::tunnel::ring::create_ring_tunnel_pair();
|
||||
let b_mgr_copy = pm_center.clone();
|
||||
let s_ret = tokio::spawn(async move { b_mgr_copy.add_tunnel_as_server(b_ring).await });
|
||||
let s_ret =
|
||||
tokio::spawn(async move { b_mgr_copy.add_tunnel_as_server(b_ring, true).await });
|
||||
|
||||
pma_net1.add_client_tunnel(a_ring).await.unwrap();
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use super::{
|
||||
peer_conn::{PeerConn, PeerConnId},
|
||||
PacketRecvChan,
|
||||
};
|
||||
use crate::proto::cli::PeerConnInfo;
|
||||
use crate::{common::scoped_task::ScopedTask, proto::cli::PeerConnInfo};
|
||||
use crate::{
|
||||
common::{
|
||||
error::Error,
|
||||
@@ -36,7 +36,8 @@ pub struct Peer {
|
||||
|
||||
shutdown_notifier: Arc<tokio::sync::Notify>,
|
||||
|
||||
default_conn_id: AtomicCell<PeerConnId>,
|
||||
default_conn_id: Arc<AtomicCell<PeerConnId>>,
|
||||
default_conn_id_clear_task: ScopedTask<()>,
|
||||
}
|
||||
|
||||
impl Peer {
|
||||
@@ -88,6 +89,19 @@ impl Peer {
|
||||
)),
|
||||
);
|
||||
|
||||
let default_conn_id = Arc::new(AtomicCell::new(PeerConnId::default()));
|
||||
|
||||
let conns_copy = conns.clone();
|
||||
let default_conn_id_copy = default_conn_id.clone();
|
||||
let default_conn_id_clear_task = ScopedTask::from(tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
if conns_copy.len() > 1 {
|
||||
default_conn_id_copy.store(PeerConnId::default());
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
Peer {
|
||||
peer_node_id,
|
||||
conns: conns.clone(),
|
||||
@@ -98,7 +112,8 @@ impl Peer {
|
||||
close_event_listener,
|
||||
|
||||
shutdown_notifier,
|
||||
default_conn_id: AtomicCell::new(PeerConnId::default()),
|
||||
default_conn_id,
|
||||
default_conn_id_clear_task,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,14 +132,19 @@ impl Peer {
|
||||
return Some(conn.clone());
|
||||
}
|
||||
|
||||
let conn = self.conns.iter().next();
|
||||
if conn.is_none() {
|
||||
return None;
|
||||
// find a conn with the smallest latency
|
||||
let mut min_latency = std::u64::MAX;
|
||||
for conn in self.conns.iter() {
|
||||
let latency = conn.value().get_stats().latency_us;
|
||||
if latency < min_latency {
|
||||
min_latency = latency;
|
||||
self.default_conn_id.store(conn.get_conn_id());
|
||||
}
|
||||
}
|
||||
|
||||
let conn = conn.unwrap().clone();
|
||||
self.default_conn_id.store(conn.get_conn_id());
|
||||
Some(conn)
|
||||
self.conns
|
||||
.get(&self.default_conn_id.load())
|
||||
.map(|conn| conn.clone())
|
||||
}
|
||||
|
||||
pub async fn send_msg(&self, msg: ZCPacket) -> Result<(), Error> {
|
||||
@@ -158,6 +178,10 @@ impl Peer {
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn get_default_conn_id(&self) -> PeerConnId {
|
||||
self.default_conn_id.load()
|
||||
}
|
||||
}
|
||||
|
||||
// pritn on drop
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::{
|
||||
use anyhow::Context;
|
||||
use async_trait::async_trait;
|
||||
|
||||
use dashmap::DashMap;
|
||||
use dashmap::{DashMap, DashSet};
|
||||
|
||||
use tokio::{
|
||||
sync::{
|
||||
@@ -23,7 +23,7 @@ use crate::{
|
||||
compressor::{Compressor as _, DefaultCompressor},
|
||||
constants::EASYTIER_VERSION,
|
||||
error::Error,
|
||||
global_ctx::{ArcGlobalCtx, NetworkIdentity},
|
||||
global_ctx::{ArcGlobalCtx, GlobalCtxEvent, NetworkIdentity},
|
||||
stun::StunInfoCollectorTrait,
|
||||
PeerId,
|
||||
},
|
||||
@@ -141,6 +141,9 @@ pub struct PeerManager {
|
||||
data_compress_algo: CompressorAlgo,
|
||||
|
||||
exit_nodes: Vec<Ipv4Addr>,
|
||||
|
||||
// conns that are directly connected (which are not hole punched)
|
||||
directly_connected_conn_map: Arc<DashMap<PeerId, DashSet<uuid::Uuid>>>,
|
||||
}
|
||||
|
||||
impl Debug for PeerManager {
|
||||
@@ -267,6 +270,8 @@ impl PeerManager {
|
||||
data_compress_algo,
|
||||
|
||||
exit_nodes,
|
||||
|
||||
directly_connected_conn_map: Arc::new(DashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,8 +330,48 @@ impl PeerManager {
|
||||
Ok((peer_id, conn_id))
|
||||
}
|
||||
|
||||
fn add_directly_connected_conn(&self, peer_id: PeerId, conn_id: uuid::Uuid) {
|
||||
let _ = self
|
||||
.directly_connected_conn_map
|
||||
.entry(peer_id)
|
||||
.or_insert_with(DashSet::new)
|
||||
.insert(conn_id);
|
||||
}
|
||||
|
||||
pub fn has_directly_connected_conn(&self, peer_id: PeerId) -> bool {
|
||||
self.directly_connected_conn_map
|
||||
.get(&peer_id)
|
||||
.map_or(false, |x| !x.is_empty())
|
||||
}
|
||||
|
||||
async fn start_peer_conn_close_event_handler(&self) {
|
||||
let dmap = self.directly_connected_conn_map.clone();
|
||||
let mut event_recv = self.global_ctx.subscribe();
|
||||
self.tasks.lock().await.spawn(async move {
|
||||
while let Ok(event) = event_recv.recv().await {
|
||||
match event {
|
||||
GlobalCtxEvent::PeerConnRemoved(info) => {
|
||||
if let Some(set) = dmap.get_mut(&info.peer_id) {
|
||||
let conn_id = info.conn_id.parse().unwrap();
|
||||
let old = set.remove(&conn_id);
|
||||
tracing::info!(
|
||||
?old,
|
||||
?info,
|
||||
"try remove conn id from directly connected map"
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn try_connect<C>(&self, mut connector: C) -> Result<(PeerId, PeerConnId), Error>
|
||||
pub async fn try_direct_connect<C>(
|
||||
&self,
|
||||
mut connector: C,
|
||||
) -> Result<(PeerId, PeerConnId), Error>
|
||||
where
|
||||
C: TunnelConnector + Debug,
|
||||
{
|
||||
@@ -334,18 +379,28 @@ impl PeerManager {
|
||||
let t = ns
|
||||
.run_async(|| async move { connector.connect().await })
|
||||
.await?;
|
||||
self.add_client_tunnel(t).await
|
||||
let (peer_id, conn_id) = self.add_client_tunnel(t).await?;
|
||||
self.add_directly_connected_conn(peer_id, conn_id);
|
||||
Ok((peer_id, conn_id))
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn add_tunnel_as_server(&self, tunnel: Box<dyn Tunnel>) -> Result<(), Error> {
|
||||
pub async fn add_tunnel_as_server(
|
||||
&self,
|
||||
tunnel: Box<dyn Tunnel>,
|
||||
is_directly_connected: bool,
|
||||
) -> Result<(), Error> {
|
||||
tracing::info!("add tunnel as server start");
|
||||
let mut peer = PeerConn::new(self.my_peer_id, self.global_ctx.clone(), tunnel);
|
||||
peer.do_handshake_as_server().await?;
|
||||
if peer.get_network_identity().network_name
|
||||
== self.global_ctx.get_network_identity().network_name
|
||||
{
|
||||
let (peer_id, conn_id) = (peer.get_peer_id(), peer.get_conn_id());
|
||||
self.add_new_peer_conn(peer).await?;
|
||||
if is_directly_connected {
|
||||
self.add_directly_connected_conn(peer_id, conn_id);
|
||||
}
|
||||
} else {
|
||||
self.foreign_network_manager.add_peer_conn(peer).await?;
|
||||
}
|
||||
@@ -857,9 +912,11 @@ impl PeerManager {
|
||||
|
||||
async fn run_clean_peer_without_conn_routine(&self) {
|
||||
let peer_map = self.peers.clone();
|
||||
let dmap = self.directly_connected_conn_map.clone();
|
||||
self.tasks.lock().await.spawn(async move {
|
||||
loop {
|
||||
peer_map.clean_peer_without_conn().await;
|
||||
dmap.retain(|p, v| peer_map.has_peer(*p) && !v.is_empty());
|
||||
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
|
||||
}
|
||||
});
|
||||
@@ -876,6 +933,8 @@ impl PeerManager {
|
||||
}
|
||||
|
||||
pub async fn run(&self) -> Result<(), Error> {
|
||||
self.start_peer_conn_close_event_handler().await;
|
||||
|
||||
match &self.route_algo_inst {
|
||||
RouteAlgoInst::Ospf(route) => self.add_route(route.clone()).await,
|
||||
RouteAlgoInst::None => {}
|
||||
@@ -924,7 +983,7 @@ impl PeerManager {
|
||||
self.foreign_network_client.clone()
|
||||
}
|
||||
|
||||
pub fn get_my_info(&self) -> cli::NodeInfo {
|
||||
pub async fn get_my_info(&self) -> cli::NodeInfo {
|
||||
cli::NodeInfo {
|
||||
peer_id: self.my_peer_id,
|
||||
ipv4_addr: self
|
||||
@@ -950,6 +1009,7 @@ impl PeerManager {
|
||||
config: self.global_ctx.config.dump(),
|
||||
version: EASYTIER_VERSION.to_string(),
|
||||
feature_flag: Some(self.global_ctx.get_feature_flags()),
|
||||
ip_list: Some(self.global_ctx.get_ip_collector().collect_ip_addrs().await),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -958,6 +1018,13 @@ impl PeerManager {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_directly_connections(&self, peer_id: PeerId) -> DashSet<uuid::Uuid> {
|
||||
self.directly_connected_conn_map
|
||||
.get(&peer_id)
|
||||
.map(|x| x.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1026,7 +1093,7 @@ mod tests {
|
||||
|
||||
tokio::spawn(async move {
|
||||
client.set_bind_addrs(vec![]);
|
||||
client_mgr.try_connect(client).await.unwrap();
|
||||
client_mgr.try_direct_connect(client).await.unwrap();
|
||||
});
|
||||
|
||||
server_mgr
|
||||
|
||||
@@ -212,6 +212,11 @@ impl PeerMap {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_peer_default_conn_id(&self, peer_id: PeerId) -> Option<PeerConnId> {
|
||||
self.get_peer_by_id(peer_id)
|
||||
.map(|p| p.get_default_conn_id())
|
||||
}
|
||||
|
||||
pub async fn close_peer_conn(
|
||||
&self,
|
||||
peer_id: PeerId,
|
||||
|
||||
@@ -32,12 +32,23 @@ impl PeerManagerRpcService {
|
||||
.await
|
||||
.iter(),
|
||||
);
|
||||
let peer_map = self.peer_manager.get_peer_map();
|
||||
let mut peer_infos = Vec::new();
|
||||
for peer in peers {
|
||||
let mut peer_info = PeerInfo::default();
|
||||
peer_info.peer_id = peer;
|
||||
peer_info.default_conn_id = peer_map
|
||||
.get_peer_default_conn_id(peer)
|
||||
.await
|
||||
.map(Into::into);
|
||||
peer_info.directly_connected_conns = self
|
||||
.peer_manager
|
||||
.get_directly_connections(peer)
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
|
||||
if let Some(conns) = self.peer_manager.get_peer_map().list_peer_conns(peer).await {
|
||||
if let Some(conns) = peer_map.list_peer_conns(peer).await {
|
||||
peer_info.conns = conns;
|
||||
} else if let Some(conns) = self
|
||||
.peer_manager
|
||||
@@ -121,7 +132,7 @@ impl PeerManageRpc for PeerManagerRpcService {
|
||||
_request: ShowNodeInfoRequest, // Accept request of type HelloRequest
|
||||
) -> Result<ShowNodeInfoResponse, rpc_types::error::Error> {
|
||||
Ok(ShowNodeInfoResponse {
|
||||
node_info: Some(self.peer_manager.get_my_info()),
|
||||
node_info: Some(self.peer_manager.get_my_info().await),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ pub async fn connect_peer_manager(client: Arc<PeerManager>, server: Arc<PeerMana
|
||||
});
|
||||
let b_mgr_copy = server.clone();
|
||||
tokio::spawn(async move {
|
||||
b_mgr_copy.add_tunnel_as_server(b_ring).await.unwrap();
|
||||
b_mgr_copy.add_tunnel_as_server(b_ring, true).await.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "common.proto";
|
||||
import "peer_rpc.proto";
|
||||
|
||||
package cli;
|
||||
|
||||
@@ -34,6 +35,8 @@ message PeerConnInfo {
|
||||
message PeerInfo {
|
||||
uint32 peer_id = 1;
|
||||
repeated PeerConnInfo conns = 2;
|
||||
common.UUID default_conn_id = 3;
|
||||
repeated common.UUID directly_connected_conns = 4;
|
||||
}
|
||||
|
||||
message ListPeerRequest {}
|
||||
@@ -79,6 +82,7 @@ message NodeInfo {
|
||||
string config = 8;
|
||||
string version = 9;
|
||||
common.PeerFeatureFlag feature_flag = 10;
|
||||
peer_rpc.GetIpListResponse ip_list = 11;
|
||||
}
|
||||
|
||||
message ShowNodeInfoRequest {}
|
||||
|
||||
@@ -4,10 +4,14 @@ impl PeerRoutePair {
|
||||
pub fn get_latency_ms(&self) -> Option<f64> {
|
||||
let mut ret = u64::MAX;
|
||||
let p = self.peer.as_ref()?;
|
||||
let default_conn_id = p.default_conn_id.map(|id| id.to_string());
|
||||
for conn in p.conns.iter() {
|
||||
let Some(stats) = &conn.stats else {
|
||||
continue;
|
||||
};
|
||||
if default_conn_id == Some(conn.conn_id.to_string()) {
|
||||
return Some(f64::from(stats.latency_us as u32) / 1000.0);
|
||||
}
|
||||
ret = ret.min(stats.latency_us);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ message FlagsInConfig {
|
||||
bool disable_p2p = 11;
|
||||
bool relay_all_peer_rpc = 12;
|
||||
bool disable_udp_hole_punching = 13;
|
||||
string ipv6_listener = 14;
|
||||
// string ipv6_listener = 14; [deprecated = true]; use -l udp://[::]:12345 instead
|
||||
bool multi_thread = 15;
|
||||
CompressionAlgoPb data_compress_algo = 16;
|
||||
bool bind_device = 17;
|
||||
|
||||
@@ -360,7 +360,13 @@ pub(crate) fn setup_sokcet2_ext(
|
||||
|
||||
socket2_socket.set_nonblocking(true)?;
|
||||
socket2_socket.set_reuse_address(true)?;
|
||||
socket2_socket.bind(&socket2::SockAddr::from(*bind_addr))?;
|
||||
if let Err(e) = socket2_socket.bind(&socket2::SockAddr::from(*bind_addr)) {
|
||||
if bind_addr.is_ipv4() {
|
||||
return Err(e.into());
|
||||
} else {
|
||||
tracing::warn!(?e, "bind failed, do not return error for ipv6");
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(all(unix, not(target_os = "solaris"), not(target_os = "illumos")))]
|
||||
// socket2_socket.set_reuse_port(true)?;
|
||||
|
||||
@@ -126,7 +126,7 @@ pub trait TunnelListener: Send {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
#[auto_impl::auto_impl(Box)]
|
||||
#[auto_impl::auto_impl(Box, &mut)]
|
||||
pub trait TunnelConnector: Send {
|
||||
async fn connect(&mut self) -> Result<Box<dyn Tunnel>, TunnelError>;
|
||||
fn remote_url(&self) -> url::Url;
|
||||
|
||||
@@ -150,9 +150,9 @@ impl TcpTunnelConnector {
|
||||
&mut self,
|
||||
addr: SocketAddr,
|
||||
) -> Result<Box<dyn Tunnel>, super::TunnelError> {
|
||||
tracing::info!(addr = ?self.addr, "connect tcp start");
|
||||
tracing::info!(url = ?self.addr, ?addr, "connect tcp start, bind addrs: {:?}", self.bind_addrs);
|
||||
let stream = TcpStream::connect(addr).await?;
|
||||
tracing::info!(addr = ?self.addr, "connect tcp succ");
|
||||
tracing::info!(url = ?self.addr, ?addr, "connect tcp succ");
|
||||
return get_tunnel_with_tcp_stream(stream, self.addr.clone().into());
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ impl super::TunnelConnector for TcpTunnelConnector {
|
||||
async fn connect(&mut self) -> Result<Box<dyn Tunnel>, super::TunnelError> {
|
||||
let addr =
|
||||
check_scheme_and_get_socket_addr_ext::<SocketAddr>(&self.addr, "tcp", self.ip_version)?;
|
||||
if self.bind_addrs.is_empty() || addr.is_ipv6() {
|
||||
if self.bind_addrs.is_empty() {
|
||||
self.connect_with_default_bind(addr).await
|
||||
} else {
|
||||
self.connect_with_custom_bind(addr).await
|
||||
|
||||
@@ -141,12 +141,27 @@ async fn respond_stun_packet(
|
||||
.encode_into_bytes(resp_msg.clone())
|
||||
.map_err(|e| anyhow::anyhow!("stun encode error: {:?}", e))?;
|
||||
|
||||
socket
|
||||
.send_to(&rsp_buf, addr.clone())
|
||||
.await
|
||||
.with_context(|| "send stun response error")?;
|
||||
let change_req = req_msg
|
||||
.get_attribute::<ChangeRequest>()
|
||||
.map(|r| r.ip() || r.port())
|
||||
.unwrap_or(false);
|
||||
|
||||
tracing::debug!(?addr, ?req_msg, "udp respond stun packet done");
|
||||
if !change_req {
|
||||
socket
|
||||
.send_to(&rsp_buf, addr.clone())
|
||||
.await
|
||||
.with_context(|| "send stun response error")?;
|
||||
} else {
|
||||
// send from a new udp socket
|
||||
let socket = if addr.is_ipv4() {
|
||||
UdpSocket::bind("0.0.0.0:0").await?
|
||||
} else {
|
||||
UdpSocket::bind("[::]:0").await?
|
||||
};
|
||||
socket.send_to(&rsp_buf, addr.clone()).await?;
|
||||
}
|
||||
|
||||
tracing::debug!(?addr, ?req_msg, ?change_req, "udp respond stun packet done");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
+55
-10
@@ -2,7 +2,7 @@ pub use std::collections::BTreeMap;
|
||||
//use BTreeMap
|
||||
use dashmap::DashMap;
|
||||
pub use easytier::common::global_ctx::{EventBusSubscriber, GlobalCtxEvent};
|
||||
use easytier::common::scoped_task::ScopedTask;
|
||||
use easytier::common::{config::Flags, scoped_task::ScopedTask};
|
||||
pub use easytier::{
|
||||
common::config::{ConfigLoader, TomlConfigLoader},
|
||||
launcher::NetworkInstance,
|
||||
@@ -158,6 +158,7 @@ fn create_config() -> TomlConfigLoader {
|
||||
cfg.set_listeners(vec![
|
||||
"tcp://0.0.0.0:11010".to_string().parse().unwrap(),
|
||||
"udp://0.0.0.0:11010".to_string().parse().unwrap(),
|
||||
"tcp://[::]:11010".to_string().parse().unwrap(),
|
||||
]);
|
||||
// cfg.set_inst_name("default".to_string());
|
||||
// cfg.set_inst_name(name);
|
||||
@@ -566,6 +567,30 @@ pub fn get_running_info() -> String {
|
||||
.unwrap_or_else(|| "{}".to_string())
|
||||
}
|
||||
|
||||
pub struct FlagsC {
|
||||
pub default_protocol: String,
|
||||
pub dev_name: String,
|
||||
pub enable_encryption: bool,
|
||||
pub enable_ipv6: bool,
|
||||
pub mtu: u32,
|
||||
pub latency_first: bool,
|
||||
pub enable_exit_node: bool,
|
||||
pub no_tun: bool,
|
||||
pub use_smoltcp: bool,
|
||||
pub relay_network_whitelist: String,
|
||||
pub disable_p2p: bool,
|
||||
pub relay_all_peer_rpc: bool,
|
||||
pub disable_udp_hole_punching: bool,
|
||||
/// string ipv6_listener = 14; \[deprecated = true\]; use -l udp://\[::\]:12345 instead
|
||||
pub multi_thread: bool,
|
||||
pub data_compress_algo: i32,
|
||||
pub bind_device: bool,
|
||||
pub enable_kcp_proxy: bool,
|
||||
pub disable_kcp_input: bool,
|
||||
pub disable_relay_kcp: bool,
|
||||
pub proxy_forward_by_system: bool,
|
||||
}
|
||||
|
||||
// 创建服务器
|
||||
pub fn create_server(
|
||||
username: String,
|
||||
@@ -573,7 +598,8 @@ pub fn create_server(
|
||||
specified_ip: String,
|
||||
room_name: String,
|
||||
room_password: String,
|
||||
severurl: String,
|
||||
severurl: Vec<String>,
|
||||
flag:FlagsC,
|
||||
) {
|
||||
RT.spawn(async move {
|
||||
// 创建一个示例配置
|
||||
@@ -581,17 +607,36 @@ pub fn create_server(
|
||||
cfg.set_hostname(Option::from(username));
|
||||
cfg.set_dhcp(enable_dhcp);
|
||||
let mut flags = cfg.get_flags();
|
||||
flags.default_protocol = flag.default_protocol;
|
||||
flags.dev_name = flag.dev_name;
|
||||
flags.enable_encryption = flag.enable_encryption;
|
||||
flags.enable_ipv6 = flag.enable_ipv6;
|
||||
flags.mtu = flag.mtu;
|
||||
flags.latency_first = flag.latency_first;
|
||||
flags.enable_exit_node = flag.enable_exit_node;
|
||||
flags.no_tun = flag.no_tun;
|
||||
flags.use_smoltcp = flag.use_smoltcp;
|
||||
flags.relay_network_whitelist = flag.relay_network_whitelist;
|
||||
flags.disable_p2p = flag.disable_p2p;
|
||||
flags.relay_all_peer_rpc = flag.relay_all_peer_rpc;
|
||||
flags.disable_udp_hole_punching = flag.disable_udp_hole_punching;
|
||||
flags.multi_thread = flag.multi_thread;
|
||||
flags.data_compress_algo = flag.data_compress_algo;
|
||||
flags.bind_device = flag.bind_device;
|
||||
flags.enable_kcp_proxy = flag.enable_kcp_proxy;
|
||||
flags.disable_kcp_input = flag.disable_kcp_input;
|
||||
flags.disable_relay_kcp = flag.disable_relay_kcp;
|
||||
flags.proxy_forward_by_system = flag.proxy_forward_by_system;
|
||||
// flags.dev_name = "astral".to_string();
|
||||
cfg.set_flags(flags);
|
||||
// 创建TCP和UDP连接配置列表
|
||||
let peer_configs = vec![
|
||||
PeerConfig {
|
||||
uri: format!("tcp://{}", severurl).parse().unwrap(),
|
||||
},
|
||||
PeerConfig {
|
||||
uri: format!("udp://{}", severurl).parse().unwrap(),
|
||||
}
|
||||
];
|
||||
let mut peer_configs = Vec::new();
|
||||
// 为每个服务器URL创建TCP和UDP配置
|
||||
for url in severurl {
|
||||
peer_configs.push(PeerConfig {
|
||||
uri: format!("{}", url).parse().unwrap(),
|
||||
});
|
||||
}
|
||||
|
||||
cfg.set_peers(peer_configs);
|
||||
if enable_dhcp == false {
|
||||
|
||||
+147
-1
@@ -107,7 +107,8 @@ fn wire__crate__api__simple__create_server_impl(
|
||||
let api_specified_ip = <String>::sse_decode(&mut deserializer);
|
||||
let api_room_name = <String>::sse_decode(&mut deserializer);
|
||||
let api_room_password = <String>::sse_decode(&mut deserializer);
|
||||
let api_severurl = <String>::sse_decode(&mut deserializer);
|
||||
let api_severurl = <Vec<String>>::sse_decode(&mut deserializer);
|
||||
let api_flag = <crate::api::simple::FlagsC>::sse_decode(&mut deserializer);
|
||||
deserializer.end();
|
||||
move |context| {
|
||||
transform_result_sse::<_, ()>((move || {
|
||||
@@ -119,6 +120,7 @@ fn wire__crate__api__simple__create_server_impl(
|
||||
api_room_name,
|
||||
api_room_password,
|
||||
api_severurl,
|
||||
api_flag,
|
||||
);
|
||||
})?;
|
||||
Ok(output_ok)
|
||||
@@ -510,6 +512,54 @@ impl SseDecode for f64 {
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for crate::api::simple::FlagsC {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
let mut var_defaultProtocol = <String>::sse_decode(deserializer);
|
||||
let mut var_devName = <String>::sse_decode(deserializer);
|
||||
let mut var_enableEncryption = <bool>::sse_decode(deserializer);
|
||||
let mut var_enableIpv6 = <bool>::sse_decode(deserializer);
|
||||
let mut var_mtu = <u32>::sse_decode(deserializer);
|
||||
let mut var_latencyFirst = <bool>::sse_decode(deserializer);
|
||||
let mut var_enableExitNode = <bool>::sse_decode(deserializer);
|
||||
let mut var_noTun = <bool>::sse_decode(deserializer);
|
||||
let mut var_useSmoltcp = <bool>::sse_decode(deserializer);
|
||||
let mut var_relayNetworkWhitelist = <String>::sse_decode(deserializer);
|
||||
let mut var_disableP2P = <bool>::sse_decode(deserializer);
|
||||
let mut var_relayAllPeerRpc = <bool>::sse_decode(deserializer);
|
||||
let mut var_disableUdpHolePunching = <bool>::sse_decode(deserializer);
|
||||
let mut var_multiThread = <bool>::sse_decode(deserializer);
|
||||
let mut var_dataCompressAlgo = <i32>::sse_decode(deserializer);
|
||||
let mut var_bindDevice = <bool>::sse_decode(deserializer);
|
||||
let mut var_enableKcpProxy = <bool>::sse_decode(deserializer);
|
||||
let mut var_disableKcpInput = <bool>::sse_decode(deserializer);
|
||||
let mut var_disableRelayKcp = <bool>::sse_decode(deserializer);
|
||||
let mut var_proxyForwardBySystem = <bool>::sse_decode(deserializer);
|
||||
return crate::api::simple::FlagsC {
|
||||
default_protocol: var_defaultProtocol,
|
||||
dev_name: var_devName,
|
||||
enable_encryption: var_enableEncryption,
|
||||
enable_ipv6: var_enableIpv6,
|
||||
mtu: var_mtu,
|
||||
latency_first: var_latencyFirst,
|
||||
enable_exit_node: var_enableExitNode,
|
||||
no_tun: var_noTun,
|
||||
use_smoltcp: var_useSmoltcp,
|
||||
relay_network_whitelist: var_relayNetworkWhitelist,
|
||||
disable_p2p: var_disableP2P,
|
||||
relay_all_peer_rpc: var_relayAllPeerRpc,
|
||||
disable_udp_hole_punching: var_disableUdpHolePunching,
|
||||
multi_thread: var_multiThread,
|
||||
data_compress_algo: var_dataCompressAlgo,
|
||||
bind_device: var_bindDevice,
|
||||
enable_kcp_proxy: var_enableKcpProxy,
|
||||
disable_kcp_input: var_disableKcpInput,
|
||||
disable_relay_kcp: var_disableRelayKcp,
|
||||
proxy_forward_by_system: var_proxyForwardBySystem,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for i32 {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
@@ -608,6 +658,18 @@ impl SseDecode for Vec<Route> {
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for Vec<String> {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
let mut len_ = <i32>::sse_decode(deserializer);
|
||||
let mut ans_ = vec![];
|
||||
for idx_ in 0..len_ {
|
||||
ans_.push(<String>::sse_decode(deserializer));
|
||||
}
|
||||
return ans_;
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for Vec<crate::api::simple::KVNodeConnectionStats> {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
@@ -655,6 +717,13 @@ impl SseDecode for (Vec<PeerInfo>, Vec<Route>) {
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for u32 {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
deserializer.cursor.read_u32::<NativeEndian>().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for u64 {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
@@ -778,6 +847,40 @@ impl flutter_rust_bridge::IntoIntoDart<FrbWrapper<Route>> for Route {
|
||||
}
|
||||
}
|
||||
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
impl flutter_rust_bridge::IntoDart for crate::api::simple::FlagsC {
|
||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||
[
|
||||
self.default_protocol.into_into_dart().into_dart(),
|
||||
self.dev_name.into_into_dart().into_dart(),
|
||||
self.enable_encryption.into_into_dart().into_dart(),
|
||||
self.enable_ipv6.into_into_dart().into_dart(),
|
||||
self.mtu.into_into_dart().into_dart(),
|
||||
self.latency_first.into_into_dart().into_dart(),
|
||||
self.enable_exit_node.into_into_dart().into_dart(),
|
||||
self.no_tun.into_into_dart().into_dart(),
|
||||
self.use_smoltcp.into_into_dart().into_dart(),
|
||||
self.relay_network_whitelist.into_into_dart().into_dart(),
|
||||
self.disable_p2p.into_into_dart().into_dart(),
|
||||
self.relay_all_peer_rpc.into_into_dart().into_dart(),
|
||||
self.disable_udp_hole_punching.into_into_dart().into_dart(),
|
||||
self.multi_thread.into_into_dart().into_dart(),
|
||||
self.data_compress_algo.into_into_dart().into_dart(),
|
||||
self.bind_device.into_into_dart().into_dart(),
|
||||
self.enable_kcp_proxy.into_into_dart().into_dart(),
|
||||
self.disable_kcp_input.into_into_dart().into_dart(),
|
||||
self.disable_relay_kcp.into_into_dart().into_dart(),
|
||||
self.proxy_forward_by_system.into_into_dart().into_dart(),
|
||||
]
|
||||
.into_dart()
|
||||
}
|
||||
}
|
||||
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::api::simple::FlagsC {}
|
||||
impl flutter_rust_bridge::IntoIntoDart<crate::api::simple::FlagsC> for crate::api::simple::FlagsC {
|
||||
fn into_into_dart(self) -> crate::api::simple::FlagsC {
|
||||
self
|
||||
}
|
||||
}
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
impl flutter_rust_bridge::IntoDart for crate::api::simple::KVNetworkStatus {
|
||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||
@@ -952,6 +1055,32 @@ impl SseEncode for f64 {
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for crate::api::simple::FlagsC {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
<String>::sse_encode(self.default_protocol, serializer);
|
||||
<String>::sse_encode(self.dev_name, serializer);
|
||||
<bool>::sse_encode(self.enable_encryption, serializer);
|
||||
<bool>::sse_encode(self.enable_ipv6, serializer);
|
||||
<u32>::sse_encode(self.mtu, serializer);
|
||||
<bool>::sse_encode(self.latency_first, serializer);
|
||||
<bool>::sse_encode(self.enable_exit_node, serializer);
|
||||
<bool>::sse_encode(self.no_tun, serializer);
|
||||
<bool>::sse_encode(self.use_smoltcp, serializer);
|
||||
<String>::sse_encode(self.relay_network_whitelist, serializer);
|
||||
<bool>::sse_encode(self.disable_p2p, serializer);
|
||||
<bool>::sse_encode(self.relay_all_peer_rpc, serializer);
|
||||
<bool>::sse_encode(self.disable_udp_hole_punching, serializer);
|
||||
<bool>::sse_encode(self.multi_thread, serializer);
|
||||
<i32>::sse_encode(self.data_compress_algo, serializer);
|
||||
<bool>::sse_encode(self.bind_device, serializer);
|
||||
<bool>::sse_encode(self.enable_kcp_proxy, serializer);
|
||||
<bool>::sse_encode(self.disable_kcp_input, serializer);
|
||||
<bool>::sse_encode(self.disable_relay_kcp, serializer);
|
||||
<bool>::sse_encode(self.proxy_forward_by_system, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for i32 {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
@@ -1022,6 +1151,16 @@ impl SseEncode for Vec<Route> {
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for Vec<String> {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
<i32>::sse_encode(self.len() as _, serializer);
|
||||
for item in self {
|
||||
<String>::sse_encode(item, serializer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for Vec<crate::api::simple::KVNodeConnectionStats> {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
@@ -1060,6 +1199,13 @@ impl SseEncode for (Vec<PeerInfo>, Vec<Route>) {
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for u32 {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
serializer.cursor.write_u32::<NativeEndian>(self).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for u64 {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
|
||||
@@ -6,7 +6,7 @@ packages:
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "67.0.0"
|
||||
adaptive_number:
|
||||
@@ -14,7 +14,7 @@ packages:
|
||||
description:
|
||||
name: adaptive_number
|
||||
sha256: "3a567544e9b5c9c803006f51140ad544aedc79604fd4f3f2c1380003f97c1d77"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
analyzer:
|
||||
@@ -22,7 +22,7 @@ packages:
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.4.1"
|
||||
args:
|
||||
@@ -30,7 +30,7 @@ packages:
|
||||
description:
|
||||
name: args
|
||||
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
async:
|
||||
@@ -38,7 +38,7 @@ packages:
|
||||
description:
|
||||
name: async
|
||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
boolean_selector:
|
||||
@@ -46,7 +46,7 @@ packages:
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
collection:
|
||||
@@ -54,7 +54,7 @@ packages:
|
||||
description:
|
||||
name: collection
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
convert:
|
||||
@@ -62,7 +62,7 @@ packages:
|
||||
description:
|
||||
name: convert
|
||||
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
coverage:
|
||||
@@ -70,7 +70,7 @@ packages:
|
||||
description:
|
||||
name: coverage
|
||||
sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
crypto:
|
||||
@@ -78,7 +78,7 @@ packages:
|
||||
description:
|
||||
name: crypto
|
||||
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
ed25519_edwards:
|
||||
@@ -86,7 +86,7 @@ packages:
|
||||
description:
|
||||
name: ed25519_edwards
|
||||
sha256: "6ce0112d131327ec6d42beede1e5dfd526069b18ad45dcf654f15074ad9276cd"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.1"
|
||||
file:
|
||||
@@ -94,7 +94,7 @@ packages:
|
||||
description:
|
||||
name: file
|
||||
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.4"
|
||||
fixnum:
|
||||
@@ -102,7 +102,7 @@ packages:
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
frontend_server_client:
|
||||
@@ -110,7 +110,7 @@ packages:
|
||||
description:
|
||||
name: frontend_server_client
|
||||
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
github:
|
||||
@@ -118,7 +118,7 @@ packages:
|
||||
description:
|
||||
name: github
|
||||
sha256: "9966bc13bf612342e916b0a343e95e5f046c88f602a14476440e9b75d2295411"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.17.0"
|
||||
glob:
|
||||
@@ -126,7 +126,7 @@ packages:
|
||||
description:
|
||||
name: glob
|
||||
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
hex:
|
||||
@@ -134,7 +134,7 @@ packages:
|
||||
description:
|
||||
name: hex
|
||||
sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
http:
|
||||
@@ -142,7 +142,7 @@ packages:
|
||||
description:
|
||||
name: http
|
||||
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
http_multi_server:
|
||||
@@ -150,7 +150,7 @@ packages:
|
||||
description:
|
||||
name: http_multi_server
|
||||
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
http_parser:
|
||||
@@ -158,7 +158,7 @@ packages:
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
io:
|
||||
@@ -166,7 +166,7 @@ packages:
|
||||
description:
|
||||
name: io
|
||||
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
js:
|
||||
@@ -174,7 +174,7 @@ packages:
|
||||
description:
|
||||
name: js
|
||||
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
json_annotation:
|
||||
@@ -182,7 +182,7 @@ packages:
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.9.0"
|
||||
lints:
|
||||
@@ -190,7 +190,7 @@ packages:
|
||||
description:
|
||||
name: lints
|
||||
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
logging:
|
||||
@@ -198,7 +198,7 @@ packages:
|
||||
description:
|
||||
name: logging
|
||||
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
matcher:
|
||||
@@ -206,7 +206,7 @@ packages:
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
meta:
|
||||
@@ -214,7 +214,7 @@ packages:
|
||||
description:
|
||||
name: meta
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
mime:
|
||||
@@ -222,7 +222,7 @@ packages:
|
||||
description:
|
||||
name: mime
|
||||
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
node_preamble:
|
||||
@@ -230,7 +230,7 @@ packages:
|
||||
description:
|
||||
name: node_preamble
|
||||
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
package_config:
|
||||
@@ -238,7 +238,7 @@ packages:
|
||||
description:
|
||||
name: package_config
|
||||
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
path:
|
||||
@@ -246,7 +246,7 @@ packages:
|
||||
description:
|
||||
name: path
|
||||
sha256: "2ad4cddff7f5cc0e2d13069f2a3f7a73ca18f66abd6f5ecf215219cdb3638edb"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.8.0"
|
||||
petitparser:
|
||||
@@ -254,7 +254,7 @@ packages:
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.4.0"
|
||||
pool:
|
||||
@@ -262,7 +262,7 @@ packages:
|
||||
description:
|
||||
name: pool
|
||||
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
pub_semver:
|
||||
@@ -270,7 +270,7 @@ packages:
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
shelf:
|
||||
@@ -278,7 +278,7 @@ packages:
|
||||
description:
|
||||
name: shelf
|
||||
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
shelf_packages_handler:
|
||||
@@ -286,7 +286,7 @@ packages:
|
||||
description:
|
||||
name: shelf_packages_handler
|
||||
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
shelf_static:
|
||||
@@ -294,7 +294,7 @@ packages:
|
||||
description:
|
||||
name: shelf_static
|
||||
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.3"
|
||||
shelf_web_socket:
|
||||
@@ -302,7 +302,7 @@ packages:
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
source_map_stack_trace:
|
||||
@@ -310,7 +310,7 @@ packages:
|
||||
description:
|
||||
name: source_map_stack_trace
|
||||
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
source_maps:
|
||||
@@ -318,7 +318,7 @@ packages:
|
||||
description:
|
||||
name: source_maps
|
||||
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.13"
|
||||
source_span:
|
||||
@@ -326,7 +326,7 @@ packages:
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
stack_trace:
|
||||
@@ -334,7 +334,7 @@ packages:
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
@@ -342,7 +342,7 @@ packages:
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
@@ -350,7 +350,7 @@ packages:
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
term_glyph:
|
||||
@@ -358,7 +358,7 @@ packages:
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test:
|
||||
@@ -366,7 +366,7 @@ packages:
|
||||
description:
|
||||
name: test
|
||||
sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.25.15"
|
||||
test_api:
|
||||
@@ -374,7 +374,7 @@ packages:
|
||||
description:
|
||||
name: test_api
|
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
test_core:
|
||||
@@ -382,7 +382,7 @@ packages:
|
||||
description:
|
||||
name: test_core
|
||||
sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.8"
|
||||
toml:
|
||||
@@ -390,7 +390,7 @@ packages:
|
||||
description:
|
||||
name: toml
|
||||
sha256: "157c5dca5160fced243f3ce984117f729c788bb5e475504f3dbcda881accee44"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.14.0"
|
||||
typed_data:
|
||||
@@ -398,7 +398,7 @@ packages:
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
version:
|
||||
@@ -406,7 +406,7 @@ packages:
|
||||
description:
|
||||
name: version
|
||||
sha256: "2307e23a45b43f96469eeab946208ed63293e8afca9c28cd8b5241ff31c55f55"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
vm_service:
|
||||
@@ -414,7 +414,7 @@ packages:
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.0.0"
|
||||
watcher:
|
||||
@@ -422,7 +422,7 @@ packages:
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
web:
|
||||
@@ -430,7 +430,7 @@ packages:
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
web_socket:
|
||||
@@ -438,7 +438,7 @@ packages:
|
||||
description:
|
||||
name: web_socket
|
||||
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.6"
|
||||
web_socket_channel:
|
||||
@@ -446,7 +446,7 @@ packages:
|
||||
description:
|
||||
name: web_socket_channel
|
||||
sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
webkit_inspection_protocol:
|
||||
@@ -454,7 +454,7 @@ packages:
|
||||
description:
|
||||
name: webkit_inspection_protocol
|
||||
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
yaml:
|
||||
@@ -462,7 +462,7 @@ packages:
|
||||
description:
|
||||
name: yaml
|
||||
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
|
||||
@@ -90,12 +90,12 @@ BEGIN
|
||||
BLOCK "040904e4"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "com.example" "\0"
|
||||
VALUE "FileDescription", "fltier" "\0"
|
||||
VALUE "FileDescription", "Astral" "\0"
|
||||
VALUE "FileVersion", VERSION_AS_STRING "\0"
|
||||
VALUE "InternalName", "fltier" "\0"
|
||||
VALUE "InternalName", "Astral" "\0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2025 com.example. All rights reserved." "\0"
|
||||
VALUE "OriginalFilename", "fltier.exe" "\0"
|
||||
VALUE "ProductName", "fltier" "\0"
|
||||
VALUE "OriginalFilename", "Astral.exe" "\0"
|
||||
VALUE "ProductName", "Astral" "\0"
|
||||
VALUE "ProductVersion", VERSION_AS_STRING "\0"
|
||||
END
|
||||
END
|
||||
|
||||
+33
-3
@@ -1,12 +1,42 @@
|
||||
#include <flutter/dart_project.h>
|
||||
#include <flutter/dart_project.h>
|
||||
#include <flutter/flutter_view_controller.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "flutter_window.h"
|
||||
#include "utils.h"
|
||||
|
||||
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) {
|
||||
const int bufferSize = 256;
|
||||
wchar_t windowTitle[bufferSize];
|
||||
|
||||
if (GetWindowTextW(hwnd, windowTitle, bufferSize)) {
|
||||
if (_wcsicmp(windowTitle, L"Astral") == 0) {
|
||||
*((HWND*)lParam) = hwnd;
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||
_In_ wchar_t *command_line, _In_ int show_command) {
|
||||
HANDLE hMutex = CreateMutexW(NULL, TRUE, L"AstralAppSingleInstanceMutex");
|
||||
if (GetLastError() == ERROR_ALREADY_EXISTS) {
|
||||
HWND hWnd = NULL;
|
||||
EnumWindows(EnumWindowsProc, (LPARAM)&hWnd);
|
||||
|
||||
if (hWnd != NULL) {
|
||||
if (IsIconic(hWnd)) {
|
||||
ShowWindow(hWnd, SW_RESTORE);
|
||||
}
|
||||
SetForegroundWindow(hWnd);
|
||||
}
|
||||
CloseHandle(hMutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Attach to console when present (e.g., 'flutter run') or create a
|
||||
// new console when running with a debugger.
|
||||
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
|
||||
@@ -27,7 +57,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||
FlutterWindow window(project);
|
||||
Win32Window::Point origin(10, 10);
|
||||
Win32Window::Size size(1280, 720);
|
||||
if (!window.Create(L"fltier", origin, size)) {
|
||||
if (!window.Create(L"Astral", origin, size)) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
window.SetQuitOnClose(true);
|
||||
@@ -38,6 +68,6 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||
::DispatchMessage(&msg);
|
||||
}
|
||||
|
||||
::CoUninitialize();
|
||||
CloseHandle(hMutex);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
|
||||
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
|
||||
<heapType xmlns="http://schemas.microsoft.com/SMI/2020/WindowsSettings">SegmentHeap</heapType>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
|
||||
Reference in New Issue
Block a user