From d9c26e01f49f0bca09b9dcd462ded1b49cdf8fe1 Mon Sep 17 00:00:00 2001 From: lollipopkit Date: Mon, 28 Aug 2023 19:26:17 +0800 Subject: [PATCH] #148 fix --- lib/data/model/app/shell_func.dart | 32 ++++----- lib/data/model/server/proc.dart | 112 ++++++++++++++++++++++------- lib/view/page/process.dart | 37 ++++++++-- 3 files changed, 130 insertions(+), 51 deletions(-) diff --git a/lib/data/model/app/shell_func.dart b/lib/data/model/app/shell_func.dart index cfabccb3..ee366784 100644 --- a/lib/data/model/app/shell_func.dart +++ b/lib/data/model/app/shell_func.dart @@ -43,8 +43,7 @@ enum AppShellFuncType { switch (this) { case AppShellFuncType.status: return ''' -result=\$(uname 2>&1 | grep "Linux") -if [ "\$result" != "" ]; then +if [ "\$isLinux" != "" ]; then \t${_statusCmds.join(_cmdDivider)} else \t${_bsdStatusCmd.join(_cmdDivider)} @@ -59,23 +58,16 @@ else fi'''; case AppShellFuncType.process: return ''' -# Try sequencially -# main Linux: `ps -aux` -# BSD: `ps -ax` -# alpine: `ps -o pid,user,time,args` -# -# If there is any error, try another one -result=\$(ps -aux 2>&1 | grep "ps: ") -if [ "\$result" = "" ]; then - ps -aux +if [ "\$isLinux" != "" ]; then +\tif [ "\$isBusybox" != "" ]; then +\t\tps w +\telse +\t\tps -aux +\tfi else - result=\$(ps -ax 2>&1 | grep "ps: ") - if [ "\$result" = "" ]; then - ps -ax - else - ps -o pid,user,time,args - fi -fi'''; +\tps -ax +fi +'''; } } @@ -201,6 +193,10 @@ final _shellCmd = """ export LANG=en_US.UTF-8 +isLinux=\$(uname 2>&1 | grep "Linux") +# Link /bin/sh to busybox? +isBusybox=\$(ls -l /bin/sh | grep "busybox") + ${AppShellFuncType.shellScript} """; diff --git a/lib/data/model/server/proc.dart b/lib/data/model/server/proc.dart index 1fedf397..bd0aa907 100644 --- a/lib/data/model/server/proc.dart +++ b/lib/data/model/server/proc.dart @@ -1,8 +1,40 @@ +import 'package:logging/logging.dart'; + import '../../../data/res/misc.dart'; +final _logger = Logger('Proc'); + +class _ProcValIdxMap { + final int pid; + final int? user; + final int? cpu; + final int? mem; + final int? vsz; + final int? rss; + final int? tty; + final int? stat; + final int? start; + final int? time; + final int command; + + _ProcValIdxMap({ + required this.pid, + this.user, + this.cpu, + this.mem, + this.vsz, + this.rss, + this.tty, + this.stat, + this.start, + this.time, + required this.command, + }); +} + /// Some field can be null due to incompatible format on `BSD` and `Alpine` class Proc { - final String user; + final String? user; final int pid; final double? cpu; final double? mem; @@ -11,37 +43,37 @@ class Proc { final String? tty; final String? stat; final String? start; - final String time; + final String? time; final String command; Proc({ - required this.user, + this.user, required this.pid, - required this.cpu, - required this.mem, - required this.vsz, - required this.rss, - required this.tty, - required this.stat, - required this.start, - required this.time, + this.cpu, + this.mem, + this.vsz, + this.rss, + this.tty, + this.stat, + this.start, + this.time, required this.command, }); - factory Proc.parse(String raw) { + factory Proc.parse(String raw, _ProcValIdxMap map) { final parts = raw.split(RegExp(r'\s+')); return Proc( - user: parts[0], - pid: int.parse(parts[1]), - cpu: double.parse(parts[2]), - mem: double.parse(parts[3]), - vsz: parts[4], - rss: parts[5], - tty: parts[6], - stat: parts[7], - start: parts[8], - time: parts[9], - command: parts.sublist(10).join(' '), + user: map.user == null ? null : parts[map.user!], + pid: int.parse(parts[map.pid]), + cpu: map.cpu == null ? null : double.parse(parts[map.cpu!]), + mem: map.mem == null ? null : double.parse(parts[map.mem!]), + vsz: map.vsz == null ? null : parts[map.vsz!], + rss: map.rss == null ? null : parts[map.rss!], + tty: map.tty == null ? null : parts[map.tty!], + stat: map.stat == null ? null : parts[map.stat!], + start: map.start == null ? null : parts[map.start!], + time: map.time == null ? null : parts[map.time!], + command: parts.sublist(map.command).join(' '), ); } @@ -84,17 +116,38 @@ class PsResult { factory PsResult.parse(String raw, {ProcSortMode sort = ProcSortMode.cpu}) { final lines = raw.split('\n'); + if (lines.isEmpty) return PsResult(procs: [], error: null); + + final header = lines[0]; + final parts = header.split(RegExp(r'\s+')); + parts.removeWhere((element) => element.isEmpty); + final map = _ProcValIdxMap( + pid: parts.indexOfOrNull('PID')!, + user: parts.indexOfOrNull('USER'), + cpu: parts.indexOfOrNull('%CPU'), + mem: parts.indexOfOrNull('%MEM'), + vsz: parts.indexOfOrNull('VSZ'), + rss: parts.indexOfOrNull('RSS'), + tty: parts.indexOfOrNull('TTY'), + stat: parts.indexOfOrNull('STAT'), + start: parts.indexOfOrNull('START'), + time: parts.indexOfOrNull('TIME'), + command: parts.indexOfOrNull('COMMAND') ?? parts.indexOfOrNull('CMD')!, + ); + final procs = []; var err = ''; for (var i = 1; i < lines.length; i++) { final line = lines[i]; if (line.isEmpty) continue; try { - procs.add(Proc.parse(line)); - } catch (e) { + procs.add(Proc.parse(line, map)); + } catch (e, trace) { err += '$line: $e\n'; + _logger.warning(trace); } } + switch (sort) { case ProcSortMode.cpu: procs.sort((a, b) => b.cpu?.compareTo(a.cpu ?? 0) ?? 0); @@ -106,7 +159,7 @@ class PsResult { procs.sort((a, b) => a.pid.compareTo(b.pid)); break; case ProcSortMode.user: - procs.sort((a, b) => a.user.compareTo(b.user)); + procs.sort((a, b) => a.user?.compareTo(b.user ?? '') ?? 0); break; case ProcSortMode.name: procs.sort((a, b) => a.binary.compareTo(b.binary)); @@ -124,3 +177,10 @@ enum ProcSortMode { name, ; } + +extension _StrIndex on List { + int? indexOfOrNull(String val) { + final idx = indexOf(val); + return idx == -1 ? null : idx; + } +} diff --git a/lib/view/page/process.dart b/lib/view/page/process.dart index ba00f997..3b08d90f 100644 --- a/lib/view/page/process.dart +++ b/lib/view/page/process.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:toolbox/core/extension/context.dart'; import 'package:toolbox/core/extension/uint8list.dart'; +import 'package:toolbox/core/utils/misc.dart'; import '../../core/utils/ui.dart'; import '../../data/model/app/shell_func.dart'; @@ -12,6 +13,7 @@ import '../../data/model/server/proc.dart'; import '../../data/model/server/server_private_info.dart'; import '../../data/provider/server.dart'; import '../../data/res/ui.dart'; +import '../../data/store/setting.dart'; import '../../locator.dart'; import '../widget/custom_appbar.dart'; import '../widget/round_rect_card.dart'; @@ -30,6 +32,8 @@ class _ProcessPageState extends State { late Timer _timer; late MediaQueryData _media; + final _setting = locator(); + SSHClient? _client; PsResult _result = PsResult(procs: []); @@ -39,7 +43,7 @@ class _ProcessPageState extends State { // In cpu mode, the process list will change in a high frequency. // So user will easily know that the list is refreshed. ProcSortMode _procSortMode = ProcSortMode.cpu; - List _sortModes = ProcSortMode.values; + List _sortModes = List.from(ProcSortMode.values); final _serverProvider = locator(); @@ -47,7 +51,9 @@ class _ProcessPageState extends State { void initState() { super.initState(); _client = _serverProvider.servers[widget.spi.id]?.client; - _timer = Timer.periodic(const Duration(seconds: 3), (_) => _refresh()); + final duration = + Duration(seconds: _setting.serverStatusUpdateInterval.fetch()); + _timer = Timer.periodic(duration, (_) => _refresh()); } @override @@ -110,7 +116,13 @@ class _ProcessPageState extends State { onPressed: () => showRoundDialog( context: context, title: Text(_s.error), - child: Text(_result.error!), + child: SingleChildScrollView(child: Text(_result.error!)), + actions: [ + TextButton( + onPressed: () => copy2Clipboard(_result.error!), + child: Text(_s.copy), + ), + ], ), )); } @@ -135,11 +147,14 @@ class _ProcessPageState extends State { } Widget _buildListItem(Proc proc) { + final leading = proc.user == null + ? Text(proc.pid.toString()) + : TwoLineText(up: proc.pid.toString(), down: proc.user!); return RoundRectCard( ListTile( leading: SizedBox( width: _media.size.width / 6, - child: TwoLineText(up: proc.pid.toString(), down: proc.user), + child: leading, ), title: Text(proc.binary), subtitle: Text( @@ -175,15 +190,23 @@ class _ProcessPageState extends State { } Widget? _buildItemTrail(Proc proc) { - if (proc.cpu == null || proc.mem == null) { + if (proc.cpu == null && proc.mem == null) { return null; } return Row( mainAxisSize: MainAxisSize.min, children: [ - TwoLineText(up: proc.cpu!.toStringAsFixed(1), down: 'cpu'), + if (proc.cpu != null) + TwoLineText( + up: proc.cpu!.toStringAsFixed(1), + down: 'cpu', + ), width13, - TwoLineText(up: proc.mem!.toStringAsFixed(1), down: 'mem'), + if (proc.mem != null) + TwoLineText( + up: proc.mem!.toStringAsFixed(1), + down: 'mem', + ), ], ); }