Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,11 @@ class _MyAppState extends State<MyApp> {
);
}

Widget _buildApp(BuildContext ctx, {required ThemeData light, required ThemeData dark}) {
Widget _buildApp(
BuildContext ctx, {
required ThemeData light,
required ThemeData dark,
}) {
final tMode = Stores.setting.themeMode.fetch();
// Issue #57
final themeMode = switch (tMode) {
Expand All @@ -124,7 +128,10 @@ class _MyAppState extends State<MyApp> {
navigatorKey: AppNavigator.key,
builder: ResponsivePoints.builder,
locale: locale,
localizationsDelegates: const [LibLocalizations.delegate, ...AppLocalizations.localizationsDelegates],
localizationsDelegates: const [
LibLocalizations.delegate,
...AppLocalizations.localizationsDelegates,
],
supportedLocales: AppLocalizations.supportedLocales,
localeListResolutionCallback: LocaleUtil.resolve,
navigatorObservers: [AppRouteObserver.instance],
Expand All @@ -142,7 +149,9 @@ class _MyAppState extends State<MyApp> {
Widget child;
var hasWindowFrame = false;
if (snapshot.connectionState == ConnectionState.waiting) {
child = const Scaffold(body: Center(child: CircularProgressIndicator()));
child = const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
} else {
final intros = snapshot.data ?? [];
if (intros.isNotEmpty) {
Expand Down
13 changes: 10 additions & 3 deletions lib/core/chan.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ abstract final class MethodChans {
final res = await _channel.invokeMethod('isServiceRunning');
return res == true;
} catch (e, s) {
Loggers.app.warning('Failed to check if Android service is running', e, s);
Loggers.app.warning(
'Failed to check if Android service is running',
e,
s,
);
return false;
}
}
Expand Down Expand Up @@ -84,10 +88,13 @@ abstract final class MethodChans {
}

/// Register a handler for native -> Flutter callbacks.
/// Currently handles:
/// Currently handles:
/// - `disconnectSession` with argument map {id: string}
/// - `stopAllConnections` with no arguments
static void registerHandler(Future<void> Function(String id) onDisconnect, [VoidCallback? onStopAll]) {
static void registerHandler(
Future<void> Function(String id) onDisconnect, [
VoidCallback? onStopAll,
]) {
_channel.setMethodCallHandler((call) async {
switch (call.method) {
case 'disconnectSession':
Expand Down
9 changes: 7 additions & 2 deletions lib/core/extension/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,20 @@ import 'package:server_box/data/res/store.dart';

extension LogoExt on ServerState {
String? getLogoUrl(BuildContext context) {
var logoUrl = spi.custom?.logoUrl ?? Stores.setting.serverLogoUrl.fetch().selfNotEmptyOrNull;
var logoUrl =
spi.custom?.logoUrl ??
Stores.setting.serverLogoUrl.fetch().selfNotEmptyOrNull;
if (logoUrl == null) {
return null;
}
final dist = status.more[StatusCmdType.sys]?.dist;
if (dist != null) {
logoUrl = logoUrl.replaceFirst('{DIST}', dist.name);
}
logoUrl = logoUrl.replaceFirst('{BRIGHT}', context.isDark ? 'dark' : 'light');
logoUrl = logoUrl.replaceFirst(
'{BRIGHT}',
context.isDark ? 'dark' : 'light',
);
return logoUrl;
}
}
3 changes: 2 additions & 1 deletion lib/core/extension/ssh_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ extension SSHClientX on SSHClient {
final session = await execute(
entry ??
switch (systemType) {
SystemType.windows => 'powershell -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass',
SystemType.windows =>
'powershell -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass',
_ => 'cat | sh',
},
pty: pty,
Expand Down
87 changes: 69 additions & 18 deletions lib/core/service/ssh_discovery.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import 'package:server_box/data/model/server/discovery_result.dart';
class SshDiscoveryService {
static const _sshPort = 22;

static Future<SshDiscoveryReport> discover([SshDiscoveryConfig config = const SshDiscoveryConfig()]) async {
static Future<SshDiscoveryReport> discover([
SshDiscoveryConfig config = const SshDiscoveryConfig(),
]) async {
final t0 = DateTime.now();
final candidates = <InternetAddress>{};

Expand All @@ -32,7 +34,11 @@ class SshDiscoveryService {

// Filter out unwanted addresses: loopback, link-local, 0.0.0.0, broadcast, multicast
candidates.removeWhere(
(a) => a.isLoopback || a.isLinkLocal || a.address == '0.0.0.0' || _isBroadcastOrMulticast(a),
(a) =>
a.isLoopback ||
a.isLinkLocal ||
a.address == '0.0.0.0' ||
_isBroadcastOrMulticast(a),
);

// 4) Concurrent SSH port scanning
Expand All @@ -45,7 +51,13 @@ class SshDiscoveryService {
results.sort((a, b) => a.addr.address.compareTo(b.addr.address));

final discoveryResults = results
.map((r) => SshDiscoveryResult(ip: r.addr.address, port: _sshPort, banner: r.banner?.trim()))
.map(
(r) => SshDiscoveryResult(
ip: r.addr.address,
port: _sshPort,
banner: r.banner?.trim(),
),
)
.toList();

return SshDiscoveryReport(
Expand All @@ -56,7 +68,11 @@ class SshDiscoveryService {
);
}

static Future<String?> _run(String exe, List<String> args, {Duration? timeout}) async {
static Future<String?> _run(
String exe,
List<String> args, {
Duration? timeout,
}) async {
try {
final p = await Process.start(exe, args, runInShell: false);
final out = await p.stdout
Expand All @@ -75,7 +91,11 @@ class SshDiscoveryService {
if (out.trim().isNotEmpty) return out;
return null;
} catch (e, s) {
Loggers.app.warning('Failed to run command: $exe ${args.join(' ')}', e, s);
Loggers.app.warning(
'Failed to run command: $exe ${args.join(' ')}',
e,
s,
);
return null;
}
}
Expand All @@ -92,7 +112,8 @@ class SshDiscoveryService {
final tok = line.split(RegExp(r'\s+'));
if (tok.isNotEmpty) {
final ip = tok[0];
if (InternetAddress.tryParse(ip)?.type == InternetAddressType.IPv4) {
if (InternetAddress.tryParse(ip)?.type ==
InternetAddressType.IPv4) {
set.add(InternetAddress(ip));
}
}
Expand Down Expand Up @@ -126,7 +147,8 @@ class SshDiscoveryService {
if (s != null) {
for (final line in const LineSplitter().convert(s)) {
final ip = line.split(RegExp(r'\s+')).firstOrNull;
if (ip != null && InternetAddress.tryParse(ip)?.type == InternetAddressType.IPv6) {
if (ip != null &&
InternetAddress.tryParse(ip)?.type == InternetAddressType.IPv6) {
set.add(InternetAddress(ip));
}
}
Expand All @@ -136,7 +158,8 @@ class SshDiscoveryService {
if (s != null) {
for (final line in const LineSplitter().convert(s)) {
final ip = line.trim().split(RegExp(r'\s+')).firstOrNull;
if (ip != null && InternetAddress.tryParse(ip)?.type == InternetAddressType.IPv6) {
if (ip != null &&
InternetAddress.tryParse(ip)?.type == InternetAddressType.IPv6) {
set.add(InternetAddress(ip));
}
}
Expand All @@ -148,10 +171,19 @@ class SshDiscoveryService {
static Future<List<_Cidr>> _localIPv4Cidrs() async {
final res = <_Cidr>[];
if (_isLinux) {
final s = await _run('ip', ['-o', '-4', 'addr', 'show', 'scope', 'global']);
final s = await _run('ip', [
'-o',
'-4',
'addr',
'show',
'scope',
'global',
]);
if (s != null) {
for (final line in const LineSplitter().convert(s)) {
final m = RegExp(r'inet\s+(\d+\.\d+\.\d+\.\d+)\/(\d+)').firstMatch(line);
final m = RegExp(
r'inet\s+(\d+\.\d+\.\d+\.\d+)\/(\d+)',
).firstMatch(line);
if (m != null) {
final ip = InternetAddress(m.group(1)!);
final prefix = int.parse(m.group(2)!);
Expand All @@ -177,7 +209,9 @@ class SshDiscoveryService {
r'inet\s+(\d+\.\d+\.\d+\.\d+)\s+netmask\s+0x([0-9a-fA-F]+)(?:\s+broadcast\s+(\d+\.\d+\.\d+\.\d+))?',
).firstMatch(line);
if (ipm == null) {
Loggers.app.warning('[ssh_discovery] Warning: Unexpected ifconfig line format: $line');
Loggers.app.warning(
'[ssh_discovery] Warning: Unexpected ifconfig line format: $line',
);
continue;
}
final ip = InternetAddress(ipm.group(1)!);
Expand All @@ -187,10 +221,14 @@ class SshDiscoveryService {
final mask = InternetAddress(dotted);
final prefix = _maskToPrefix(mask.address);
final net = _networkAddress(ip, mask);
final brd = InternetAddress(ipm.group(3) ?? _broadcastAddress(ip, mask).address);
final brd = InternetAddress(
ipm.group(3) ?? _broadcastAddress(ip, mask).address,
);
res.add(_Cidr(ip, prefix, mask, net, brd));
} catch (e) {
Loggers.app.warning('[ssh_discovery] Error parsing ifconfig output: $e, line: $line');
Loggers.app.warning(
'[ssh_discovery] Error parsing ifconfig output: $e, line: $line',
);
continue;
}
}
Expand Down Expand Up @@ -219,7 +257,10 @@ class SshDiscoveryService {
final set = <InternetAddress>{};
if (_isMac) {
try {
final proc = await Process.start('/usr/bin/dns-sd', ['-B', '_ssh._tcp']);
final proc = await Process.start('/usr/bin/dns-sd', [
'-B',
'_ssh._tcp',
]);
final lines = <String>[];
final subscription = proc.stdout
.transform(utf8.decoder)
Expand Down Expand Up @@ -250,7 +291,11 @@ class SshDiscoveryService {
}
}
} catch (e, s) {
Loggers.app.warning('Failed to discover mDNS SSH candidates on macOS', e, s);
Loggers.app.warning(
'Failed to discover mDNS SSH candidates on macOS',
e,
s,
);
}
} else if (_isLinux) {
final s = await _run('/usr/bin/avahi-browse', ['-rat', '_ssh._tcp']);
Expand Down Expand Up @@ -316,7 +361,11 @@ class _Scanner {
Socket? socket;
StreamSubscription? sub;
try {
socket = await Socket.connect(ip, SshDiscoveryService._sshPort, timeout: timeout);
socket = await Socket.connect(
ip,
SshDiscoveryService._sshPort,
timeout: timeout,
);
socket.timeout(timeout);
final c = Completer<String?>();
sub = socket.listen(
Expand Down Expand Up @@ -370,7 +419,8 @@ class _Semaphore {
}
}

Future<T> _guarded<T>(_Semaphore sem, Future<T> Function() fn) => sem.withPermit(fn);
Future<T> _guarded<T>(_Semaphore sem, Future<T> Function() fn) =>
sem.withPermit(fn);

// IPv4 utilities

Expand All @@ -379,7 +429,8 @@ int _ipv4ToInt(String ip) {
return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
}

String _intToIPv4(int v) => '${(v >> 24) & 0xff}.${(v >> 16) & 0xff}.${(v >> 8) & 0xff}.${v & 0xff}';
String _intToIPv4(int v) =>
'${(v >> 24) & 0xff}.${(v >> 16) & 0xff}.${(v >> 8) & 0xff}.${v & 0xff}';

InternetAddress _prefixToMask(int prefix) {
final mask = prefix == 0 ? 0 : 0xffffffff << (32 - prefix);
Expand Down
32 changes: 25 additions & 7 deletions lib/core/utils/comparator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ class ChainComparator<T> {
ChainComparator.empty() : this._create(null, (a, b) => 0);
ChainComparator.create() : this._create(null, (a, b) => 0);

static ChainComparator<T> comparing<T, F extends Comparable<F>>(F Function(T) extractor) {
return ChainComparator._create(null, (a, b) => extractor(a).compareTo(extractor(b)));
static ChainComparator<T> comparing<T, F extends Comparable<F>>(
F Function(T) extractor,
) {
return ChainComparator._create(
null,
(a, b) => extractor(a).compareTo(extractor(b)),
);
}

int compare(T a, T b) {
Expand Down Expand Up @@ -35,12 +40,23 @@ class ChainComparator<T> {
);
}

ChainComparator<T> thenWithComparator(Comparator<T> comparator, {bool reversed = false}) {
return ChainComparator._create(this, !reversed ? comparator : (a, b) => comparator(b, a));
ChainComparator<T> thenWithComparator(
Comparator<T> comparator, {
bool reversed = false,
}) {
return ChainComparator._create(
this,
!reversed ? comparator : (a, b) => comparator(b, a),
);
}

ChainComparator<T> thenCompareByReversed<F extends Comparable<F>>(F Function(T) extractor) {
return ChainComparator._create(this, (a, b) => -extractor(a).compareTo(extractor(b)));
ChainComparator<T> thenCompareByReversed<F extends Comparable<F>>(
F Function(T) extractor,
) {
return ChainComparator._create(
this,
(a, b) => -extractor(a).compareTo(extractor(b)),
);
}

ChainComparator<T> thenTrueFirst(bool Function(T) f) {
Expand All @@ -56,7 +72,9 @@ class ChainComparator<T> {
}

class Comparators {
static Comparator<String> compareStringCaseInsensitive({bool uppercaseFirst = false}) {
static Comparator<String> compareStringCaseInsensitive({
bool uppercaseFirst = false,
}) {
return (String a, String b) {
final r = a.toLowerCase().compareTo(b.toLowerCase());
if (r != 0) return r;
Expand Down
3 changes: 2 additions & 1 deletion lib/core/utils/host_key_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ Future<bool> ensureHostKeyAcceptedForSftp(BuildContext context, Spi spi) async {
fn: () async {
await ensureKnownHostKey(
spi,
onKeyboardInteractive: (_) => KeybordInteractive.defaultHandle(spi, ctx: context),
onKeyboardInteractive: (_) =>
KeybordInteractive.defaultHandle(spi, ctx: context),
);
return true;
},
Expand Down
Loading