mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
add: store exes & presets
This commit is contained in:
@@ -1,9 +1,11 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:server_box/data/store/setting.dart';
|
||||||
|
|
||||||
/// Exception thrown when executable management fails
|
/// Exception thrown when executable management fails
|
||||||
class ExecutableException implements Exception {
|
class ExecutableException implements Exception {
|
||||||
@@ -28,6 +30,8 @@ class ExecutableInfo {
|
|||||||
abstract final class ExecutableManager {
|
abstract final class ExecutableManager {
|
||||||
static const String _executablesDirName = 'executables';
|
static const String _executablesDirName = 'executables';
|
||||||
static late final Directory _executablesDir;
|
static late final Directory _executablesDir;
|
||||||
|
static final Map<String, ExecutableInfo> _customExecutables = {};
|
||||||
|
static bool _customExecutablesLoaded = false;
|
||||||
|
|
||||||
static Future<void> initialize() async {
|
static Future<void> initialize() async {
|
||||||
final appDir = await getApplicationSupportDirectory();
|
final appDir = await getApplicationSupportDirectory();
|
||||||
@@ -35,6 +39,8 @@ abstract final class ExecutableManager {
|
|||||||
if (!await _executablesDir.exists()) {
|
if (!await _executablesDir.exists()) {
|
||||||
await _executablesDir.create(recursive: true);
|
await _executablesDir.create(recursive: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ensureCustomExecutablesLoaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Predefined executables
|
/// Predefined executables
|
||||||
@@ -45,6 +51,52 @@ abstract final class ExecutableManager {
|
|||||||
'socat': ExecutableInfo(name: 'socat'),
|
'socat': ExecutableInfo(name: 'socat'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void _ensureCustomExecutablesLoaded() {
|
||||||
|
if (_customExecutablesLoaded) return;
|
||||||
|
|
||||||
|
final List<dynamic> stored = SettingStore.instance.proxyCmdCustomExecs.get();
|
||||||
|
for (final raw in stored) {
|
||||||
|
final info = _parseExecutableInfo(raw);
|
||||||
|
if (info == null) continue;
|
||||||
|
_customExecutables[info.name] = info;
|
||||||
|
_predefinedExecutables[info.name] = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
_customExecutablesLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _persistCustomExecutables() {
|
||||||
|
final values = _customExecutables.values
|
||||||
|
.map((info) => {
|
||||||
|
'name': info.name,
|
||||||
|
if (info.spokenName != null) 'spokenName': info.spokenName,
|
||||||
|
if (info.version != null) 'version': info.version,
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
SettingStore.instance.proxyCmdCustomExecs.set(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ExecutableInfo? _parseExecutableInfo(dynamic raw) {
|
||||||
|
if (raw is String) {
|
||||||
|
try {
|
||||||
|
return _parseExecutableInfo(jsonDecode(raw));
|
||||||
|
} catch (e) {
|
||||||
|
Loggers.app.warning('Failed to decode custom executable entry: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (raw is! Map) return null;
|
||||||
|
|
||||||
|
final name = raw['name']?.toString();
|
||||||
|
if (name == null || name.isEmpty) return null;
|
||||||
|
|
||||||
|
return ExecutableInfo(
|
||||||
|
name: name,
|
||||||
|
spokenName: raw['spokenName']?.toString(),
|
||||||
|
version: raw['version']?.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if an executable exists in PATH or local directory
|
/// Check if an executable exists in PATH or local directory
|
||||||
static Future<bool> isExecutableAvailable(String name) async {
|
static Future<bool> isExecutableAvailable(String name) async {
|
||||||
// First check if it's in PATH
|
// First check if it's in PATH
|
||||||
@@ -224,18 +276,27 @@ abstract final class ExecutableManager {
|
|||||||
|
|
||||||
/// Get predefined executable info
|
/// Get predefined executable info
|
||||||
static ExecutableInfo? getExecutableInfo(String name) {
|
static ExecutableInfo? getExecutableInfo(String name) {
|
||||||
|
_ensureCustomExecutablesLoaded();
|
||||||
return _predefinedExecutables[name];
|
return _predefinedExecutables[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a custom executable definition
|
/// Add a custom executable definition
|
||||||
static void addCustomExecutable(String name, ExecutableInfo info) {
|
static void addCustomExecutable(String name, ExecutableInfo info) {
|
||||||
// TODO: Implement persistent storage for custom executables
|
_ensureCustomExecutablesLoaded();
|
||||||
|
_customExecutables[name] = info;
|
||||||
|
_predefinedExecutables[name] = info;
|
||||||
|
_persistCustomExecutables();
|
||||||
Loggers.app.info('Adding custom executable: $name');
|
Loggers.app.info('Adding custom executable: $name');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a custom executable definition
|
/// Remove a custom executable definition
|
||||||
static void removeCustomExecutable(String name) {
|
static void removeCustomExecutable(String name) {
|
||||||
// TODO: Implement persistent storage for custom executables
|
_ensureCustomExecutablesLoaded();
|
||||||
|
final removed = _customExecutables.remove(name);
|
||||||
|
if (removed != null) {
|
||||||
|
_predefinedExecutables.remove(name);
|
||||||
|
_persistCustomExecutables();
|
||||||
Loggers.app.info('Removing custom executable: $name');
|
Loggers.app.info('Removing custom executable: $name');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dartssh2/dartssh2.dart';
|
import 'package:dartssh2/dartssh2.dart';
|
||||||
@@ -6,6 +7,7 @@ import 'package:fl_lib/fl_lib.dart';
|
|||||||
import 'package:server_box/core/utils/executable_manager.dart';
|
import 'package:server_box/core/utils/executable_manager.dart';
|
||||||
import 'package:server_box/core/utils/proxy_socket.dart';
|
import 'package:server_box/core/utils/proxy_socket.dart';
|
||||||
import 'package:server_box/data/model/server/proxy_command_config.dart';
|
import 'package:server_box/data/model/server/proxy_command_config.dart';
|
||||||
|
import 'package:server_box/data/store/setting.dart';
|
||||||
|
|
||||||
/// Exception thrown when proxy command execution fails
|
/// Exception thrown when proxy command execution fails
|
||||||
class ProxyCommandException implements Exception {
|
class ProxyCommandException implements Exception {
|
||||||
@@ -26,6 +28,58 @@ class ProxyCommandException implements Exception {
|
|||||||
|
|
||||||
/// Generic proxy command executor that handles SSH ProxyCommand functionality
|
/// Generic proxy command executor that handles SSH ProxyCommand functionality
|
||||||
abstract final class ProxyCommandExecutor {
|
abstract final class ProxyCommandExecutor {
|
||||||
|
static final Map<String, ProxyCommandConfig> _customPresets = {};
|
||||||
|
static bool _customPresetsLoaded = false;
|
||||||
|
|
||||||
|
static void _ensureCustomPresetsLoaded() {
|
||||||
|
if (_customPresetsLoaded) return;
|
||||||
|
|
||||||
|
final List<dynamic> stored = SettingStore.instance.proxyCmdCustomPresets.get();
|
||||||
|
for (final raw in stored) {
|
||||||
|
final preset = _parsePreset(raw);
|
||||||
|
if (preset == null) continue;
|
||||||
|
_customPresets[preset.key] = preset.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
_customPresetsLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _persistCustomPresets() {
|
||||||
|
final list = _customPresets.entries
|
||||||
|
.map((entry) => {
|
||||||
|
'name': entry.key,
|
||||||
|
'config': entry.value.toJson(),
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
SettingStore.instance.proxyCmdCustomPresets.set(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
static MapEntry<String, ProxyCommandConfig>? _parsePreset(dynamic raw) {
|
||||||
|
dynamic payload = raw;
|
||||||
|
if (payload is String) {
|
||||||
|
try {
|
||||||
|
payload = jsonDecode(payload);
|
||||||
|
} catch (e) {
|
||||||
|
Loggers.app.warning('Failed to decode custom proxy preset entry: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload is! Map) return null;
|
||||||
|
|
||||||
|
final name = payload['name']?.toString();
|
||||||
|
final configRaw = payload['config'];
|
||||||
|
if (name == null || name.isEmpty || configRaw is! Map) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final config = ProxyCommandConfig.fromJson(Map<String, dynamic>.from(configRaw));
|
||||||
|
return MapEntry(name, config);
|
||||||
|
} catch (e) {
|
||||||
|
Loggers.app.warning('Failed to parse custom proxy preset "$name": $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Execute a proxy command and return a socket connected through the proxy
|
/// Execute a proxy command and return a socket connected through the proxy
|
||||||
static Future<SSHSocket> executeProxyCommand(
|
static Future<SSHSocket> executeProxyCommand(
|
||||||
ProxyCommandConfig config, {
|
ProxyCommandConfig config, {
|
||||||
@@ -124,10 +178,18 @@ abstract final class ProxyCommandExecutor {
|
|||||||
return 'Proxy command must contain %h (hostname) placeholder';
|
return 'Proxy command must contain %h (hostname) placeholder';
|
||||||
}
|
}
|
||||||
|
|
||||||
// If executable is required, check if it exists
|
String executablePath;
|
||||||
|
|
||||||
|
// If executable is required, check if it exists and reuse resolved path
|
||||||
if (config.requiresExecutable && config.executableName != null) {
|
if (config.requiresExecutable && config.executableName != null) {
|
||||||
try {
|
try {
|
||||||
await ExecutableManager.ensureExecutable(config.executableName!);
|
executablePath = await ExecutableManager.ensureExecutable(config.executableName!);
|
||||||
|
} catch (e) {
|
||||||
|
return e.toString();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
executablePath = await ExecutableManager.getExecutablePath(tokens.first);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return e.toString();
|
return e.toString();
|
||||||
}
|
}
|
||||||
@@ -135,7 +197,7 @@ abstract final class ProxyCommandExecutor {
|
|||||||
|
|
||||||
// Try to validate command syntax (dry run)
|
// Try to validate command syntax (dry run)
|
||||||
try {
|
try {
|
||||||
await Process.run(tokens.first, ['--help']);
|
await Process.run(executablePath, ['--help']);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return 'Command validation failed: $e';
|
return 'Command validation failed: $e';
|
||||||
}
|
}
|
||||||
@@ -145,21 +207,30 @@ abstract final class ProxyCommandExecutor {
|
|||||||
|
|
||||||
/// Get available proxy command presets
|
/// Get available proxy command presets
|
||||||
static Map<String, ProxyCommandConfig> getPresets() {
|
static Map<String, ProxyCommandConfig> getPresets() {
|
||||||
return proxyCommandPresets;
|
_ensureCustomPresetsLoaded();
|
||||||
|
return {
|
||||||
|
...proxyCommandPresets,
|
||||||
|
..._customPresets,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a custom preset
|
/// Add a custom preset
|
||||||
static Future<void> addCustomPreset(String name, ProxyCommandConfig config) async {
|
static Future<void> addCustomPreset(String name, ProxyCommandConfig config) async {
|
||||||
// TODO: Implement custom preset storage
|
_ensureCustomPresetsLoaded();
|
||||||
// This would involve storing custom presets in a persistent storage
|
_customPresets[name] = config;
|
||||||
|
_persistCustomPresets();
|
||||||
Loggers.app.info('Adding custom proxy preset: $name');
|
Loggers.app.info('Adding custom proxy preset: $name');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a custom preset
|
/// Remove a custom preset
|
||||||
static Future<void> removeCustomPreset(String name) async {
|
static Future<void> removeCustomPreset(String name) async {
|
||||||
// TODO: Implement custom preset removal
|
_ensureCustomPresetsLoaded();
|
||||||
|
final removed = _customPresets.remove(name);
|
||||||
|
if (removed != null) {
|
||||||
|
_persistCustomPresets();
|
||||||
Loggers.app.info('Removing custom proxy preset: $name');
|
Loggers.app.info('Removing custom proxy preset: $name');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static List<String> tokenizeCommand(String command) => _tokenizeCommand(command);
|
static List<String> tokenizeCommand(String command) => _tokenizeCommand(command);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:hive_ce_flutter/adapters.dart';
|
|
||||||
|
|
||||||
part 'proxy_command_config.freezed.dart';
|
part 'proxy_command_config.freezed.dart';
|
||||||
part 'proxy_command_config.g.dart';
|
part 'proxy_command_config.g.dart';
|
||||||
|
|||||||
@@ -228,6 +228,10 @@ class SettingStore extends HiveStore {
|
|||||||
|
|
||||||
late final betaTest = propertyDefault('betaTest', false);
|
late final betaTest = propertyDefault('betaTest', false);
|
||||||
|
|
||||||
|
late final proxyCmdCustomExecs = listProperty('proxyCmdCustomExecs');
|
||||||
|
|
||||||
|
late final proxyCmdCustomPresets = listProperty('proxyCmdCustomPresets');
|
||||||
|
|
||||||
/// For desktop only.
|
/// For desktop only.
|
||||||
/// Record the position and size of the window.
|
/// Record the position and size of the window.
|
||||||
late final windowState = property<WindowState>(
|
late final windowState = property<WindowState>(
|
||||||
|
|||||||
Reference in New Issue
Block a user