mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-16 23:04:22 +01:00
* feat: win compatibility * fix * fix: uptime parse * opt.: linux uptime accuracy * fix: windows temperature fetching * opt. * opt.: powershell exec * refactor: address PR review feedback and improve code quality ### Major Improvements: - **Refactored Windows status parsing**: Broke down large `_getWindowsStatus` method into 13 smaller, focused helper methods for better maintainability and readability - **Extracted system detection logic**: Created dedicated `SystemDetector` helper class to separate OS detection concerns from ServerProvider - **Improved concurrency handling**: Implemented proper synchronization for server updates using Future-based locks to prevent race conditions ### Bug Fixes: - **Fixed CPU percentage parsing**: Removed incorrect '*100' multiplication in BSD CPU parsing (values were already percentages) - **Enhanced memory parsing**: Added validation and error handling to BSD memory fallback parsing with proper logging - **Improved uptime parsing**: Added support for multiple Windows date formats and robust error handling with validation - **Fixed division by zero**: Added safety checks in Swap.usedPercent getter ### Code Quality Enhancements: - **Added comprehensive documentation**: Documented Windows CPU counter limitations and approach - **Strengthened error handling**: Added detailed logging and validation throughout parsing methods - **Improved robustness**: Enhanced BSD CPU parsing with percentage validation and warnings - **Better separation of concerns**: Each parsing method now has single responsibility ### Files Changed: - `lib/data/helper/system_detector.dart` (new): System detection helper - `lib/data/model/server/cpu.dart`: Fixed percentage parsing and added validation - `lib/data/model/server/memory.dart`: Enhanced fallback parsing and division-by-zero protection - `lib/data/model/server/server_status_update_req.dart`: Refactored into 13 focused parsing methods - `lib/data/provider/server.dart`: Improved synchronization and extracted system detection 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: parse & shell fn struct --------- Co-authored-by: Claude <noreply@anthropic.com>
175 lines
4.3 KiB
Dart
175 lines
4.3 KiB
Dart
import 'package:fl_lib/fl_lib.dart';
|
|
|
|
import 'package:server_box/data/res/misc.dart';
|
|
|
|
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;
|
|
|
|
const _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 int pid;
|
|
final double? cpu;
|
|
final double? mem;
|
|
final String? vsz;
|
|
final String? rss;
|
|
final String? tty;
|
|
final String? stat;
|
|
final String? start;
|
|
final String? time;
|
|
final String command;
|
|
|
|
const Proc({
|
|
this.user,
|
|
required this.pid,
|
|
this.cpu,
|
|
this.mem,
|
|
this.vsz,
|
|
this.rss,
|
|
this.tty,
|
|
this.stat,
|
|
this.start,
|
|
this.time,
|
|
required this.command,
|
|
});
|
|
|
|
factory Proc._parse(String raw, _ProcValIdxMap map) {
|
|
final parts = raw.split(RegExp(r'\s+'));
|
|
return Proc(
|
|
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(' '),
|
|
);
|
|
}
|
|
|
|
Map toJson() {
|
|
return {
|
|
'user': user,
|
|
'pid': pid,
|
|
'cpu': cpu,
|
|
'mem': mem,
|
|
'vsz': vsz,
|
|
'rss': rss,
|
|
'tty': tty,
|
|
'stat': stat,
|
|
'start': start,
|
|
'time': time,
|
|
'command': command,
|
|
};
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return Miscs.jsonEncoder.convert(toJson());
|
|
}
|
|
|
|
String get binary {
|
|
final parts = command.split(' ');
|
|
return parts[0];
|
|
}
|
|
}
|
|
|
|
// `ps -aux` result
|
|
class PsResult {
|
|
final List<Proc> procs;
|
|
final String? error;
|
|
|
|
const PsResult({required this.procs, this.error});
|
|
|
|
factory PsResult.parse(String raw, {ProcSortMode sort = ProcSortMode.cpu}) {
|
|
final lines = raw.split('\n').map((e) => e.trim()).toList();
|
|
if (lines.isEmpty) return const 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 = <Proc>[];
|
|
final errs = <String>[];
|
|
for (var i = 1; i < lines.length; i++) {
|
|
final line = lines[i];
|
|
if (line.isEmpty) continue;
|
|
try {
|
|
procs.add(Proc._parse(line, map));
|
|
} catch (e, trace) {
|
|
errs.add('$line: $e');
|
|
Loggers.app.warning('Process failed', e, trace);
|
|
}
|
|
}
|
|
|
|
switch (sort) {
|
|
case ProcSortMode.cpu:
|
|
procs.sort((a, b) => b.cpu?.compareTo(a.cpu ?? 0) ?? 0);
|
|
break;
|
|
case ProcSortMode.mem:
|
|
procs.sort((a, b) => b.mem?.compareTo(a.mem ?? 0) ?? 0);
|
|
break;
|
|
case ProcSortMode.pid:
|
|
procs.sort((a, b) => a.pid.compareTo(b.pid));
|
|
break;
|
|
case ProcSortMode.user:
|
|
procs.sort((a, b) => a.user?.compareTo(b.user ?? '') ?? 0);
|
|
break;
|
|
case ProcSortMode.name:
|
|
procs.sort((a, b) => a.binary.compareTo(b.binary));
|
|
break;
|
|
}
|
|
return PsResult(procs: procs, error: errs.isEmpty ? null : errs.join('\n'));
|
|
}
|
|
}
|
|
|
|
enum ProcSortMode { cpu, mem, pid, user, name }
|
|
|
|
extension _StrIndex on List<String> {
|
|
int? indexOfOrNull(String val) {
|
|
final idx = indexOf(val);
|
|
return idx == -1 ? null : idx;
|
|
}
|
|
}
|