import 'dart:async'; import 'dart:typed_data'; import 'package:dartssh2/dartssh2.dart'; import 'package:logging/logging.dart'; import 'package:toolbox/core/extension/ssh_client.dart'; import 'package:toolbox/core/extension/stringx.dart'; import 'package:toolbox/core/extension/uint8list.dart'; import 'package:toolbox/core/provider_base.dart'; import 'package:toolbox/data/model/pkg/manager.dart'; import 'package:toolbox/data/model/pkg/upgrade_info.dart'; import 'package:toolbox/data/model/server/dist.dart'; class PkgProvider extends BusyProvider { final logger = Logger('PKG'); SSHClient? client; Dist? dist; PkgManager? type; Function()? onUpgrade; Function()? onUpdate; PwdRequestFunc? onPasswordRequest; String? whoami; List? upgradeable; String? error; String? upgradeLog; String? updateLog; String lastLog = ''; bool isRequestingPwd = false; Future init( SSHClient client, Dist? dist, Function() onUpgrade, Function() onUpdate, PwdRequestFunc onPasswordRequest, String user, ) async { this.client = client; this.dist = dist; this.onUpgrade = onUpgrade; this.onPasswordRequest = onPasswordRequest; whoami = user; type = fromDist(dist); if (type == null) { error = 'Unsupported dist: $dist'; } } bool get isSU => whoami == 'root'; void clear() { client = dist = updateLog = upgradeLog = upgradeable = error = whoami = onUpdate = onUpgrade = onPasswordRequest = null; isRequestingPwd = false; } Future refresh() async { final result = await _update(); _parse(result); try {} catch (e) { error = '[Server Raw]:\n$result\n[App Error]:\n$e'; } finally { notifyListeners(); } } void _parse(String? raw) { if (raw == null) return; final list = type?.updateListRemoveUnused(raw.split('\n')); upgradeable = list?.map((e) => UpgradePkgInfo(e, type)).toList(); } Future _update() async { final updateCmd = type?.update; if (updateCmd != null) { await client!.exec( _wrap(updateCmd), onStderr: _onPwd, onStdout: (data, sink) { updateLog = (updateLog ?? '') + data; if (onUpdate != null) onUpdate!(); notifyListeners(); }, ); } final listCmd = type?.listUpdate; if (listCmd == null) { error = 'Unsupported dist: $dist'; return null; } return await client?.run(_wrap(listCmd)).string; } Future upgrade() async { final upgradeCmd = type?.upgrade(upgradeable?.map((e) => e.package).join(" ") ?? ''); if (upgradeCmd == null) { error = 'Unsupported dist: $dist'; return; } await client!.exec( _wrap(upgradeCmd), onStderr: _onPwd, onStdout: (log, sink) { if (lastLog == log.trim()) return; upgradeLog = (upgradeLog ?? '') + log; lastLog = log.trim(); notifyListeners(); if (onUpgrade != null) onUpgrade!(); }, ); upgradeLog = null; refresh(); } Future _onPwd(String event, StreamSink stdin) async { if (isRequestingPwd) return; isRequestingPwd = true; if (event.contains('[sudo] password for ')) { final user = pwdRequestWithUserReg.firstMatch(event)?.group(1); logger.info('sudo password request for $user'); final pwd = await onPasswordRequest!(); if (pwd.isEmpty) { logger.info('sudo password request cancelled'); return; } stdin.add('$pwd\n'.uint8List); } isRequestingPwd = false; } String _wrap(String cmd) => 'export LANG=en_US.utf-8 && ${isSU ? "" : "sudo -S "}$cmd'; }