import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:logging/logging.dart'; import 'mining_task_info.dart'; /// 挖矿管理器 - 管理挖矿软件的启动、停止 class MiningManager { static final MiningManager _instance = MiningManager._internal(); factory MiningManager() => _instance; MiningManager._internal(); final Logger _logger = Logger('MiningManager'); Process? _currentProcess; MiningTaskInfo? _currentTask; Timer? _taskMonitor; final StreamController _minerLogController = StreamController.broadcast(); final StreamController _processExitController = StreamController.broadcast(); /// 进程退出事件流 Stream get processExitStream => _processExitController.stream; /// 启动挖矿 Future startMining(MiningTaskInfo task, MiningConfig config) async { if (_currentProcess != null) { _logger.warning('已有挖矿任务在运行'); return false; } try { final minerPath = _getMinerPath(task.miner, config); if (minerPath == null) { _logger.severe('挖矿软件路径未配置: ${task.miner}'); return false; } final args = _buildMinerArgs(task, config); String executable; switch (task.miner.toLowerCase()) { case 'lolminer': executable = Platform.isWindows ? '${minerPath}\\lolMiner.exe' : '$minerPath/lolMiner'; break; case 'rigel': executable = Platform.isWindows ? '${minerPath}\\rigel.exe' : '$minerPath/rigel'; break; case 'bzminer': executable = Platform.isWindows ? '${minerPath}\\bzminer.exe' : '$minerPath/bzminer'; break; default: throw Exception('不支持的挖矿软件: ${task.miner}'); } // 确保可执行文件存在 final executableFile = File(executable); if (!await executableFile.exists()) { throw Exception('挖矿软件不存在: $executable'); } _logger.info('启动挖矿: $executable ${args.join(' ')}'); _currentProcess = await Process.start( executable, args, workingDirectory: minerPath, mode: ProcessStartMode.normal, // 改为 normal 以捕获输出 ); _currentTask = task; _startTaskMonitor(task); _startLogCapture(); _monitorProcessExit(); _logger.info('挖矿已启动 (PID: ${_currentProcess!.pid})'); return true; } catch (e) { _logger.severe('启动挖矿失败: $e'); return false; } } /// 停止挖矿 Future stopMining() async { _taskMonitor?.cancel(); _taskMonitor = null; if (_currentProcess != null) { try { _currentProcess!.kill(); await _currentProcess!.exitCode; _logger.info('挖矿已停止'); } catch (e) { _logger.severe('停止挖矿失败: $e'); } finally { _currentProcess = null; _currentTask = null; } } } /// 获取挖矿软件路径 String? _getMinerPath(String miner, MiningConfig config) { switch (miner.toLowerCase()) { case 'lolminer': return config.lolMinerPath; case 'rigel': return config.rigelPath; case 'bzminer': return config.bzMinerPath; default: return null; } } /// 构建挖矿软件参数 List _buildMinerArgs(MiningTaskInfo task, MiningConfig config) { final address = task.walletMining ? task.walletAddress : (task.poolUser ?? task.walletAddress); switch (task.miner.toLowerCase()) { case 'lolminer': return [ '--algo', task.coin, '--pool', task.poolUrl, '--user', '$address.${task.workerId}', ]; case 'rigel': return [ '--no-watchdog', '-a', task.algo.toLowerCase(), '-o', task.poolUrl, '-u', address, '-w', task.workerId, '--log-file', 'logs/miner.log', ]; case 'bzminer': return [ '-a', task.coin, '-w', '$address.${task.workerId}', '-p', task.poolUrl, ]; default: return []; } } /// 启动任务监控 void _startTaskMonitor(MiningTaskInfo task) { _taskMonitor?.cancel(); final endTime = DateTime.fromMillisecondsSinceEpoch(task.endTimestamp * 1000); final now = DateTime.now(); if (endTime.isBefore(now)) { // 任务已过期,立即停止 stopMining(); return; } final duration = endTime.difference(now); _taskMonitor = Timer(duration, () { _logger.info('挖矿任务已到期,自动停止'); stopMining(); }); } /// 开始捕获挖矿进程输出 void _startLogCapture() { if (_currentProcess == null) return; _currentProcess!.stdout .transform(const SystemEncoding().decoder) .transform(const LineSplitter()) .listen((line) { _minerLogController.add('[${DateTime.now().toString().substring(11, 19)}] $line'); }); _currentProcess!.stderr .transform(const SystemEncoding().decoder) .transform(const LineSplitter()) .listen((line) { _minerLogController.add('[${DateTime.now().toString().substring(11, 19)}] [ERROR] $line'); }); } /// 监控进程退出 void _monitorProcessExit() { if (_currentProcess == null) return; _currentProcess!.exitCode.then((exitCode) { _logger.warning('挖矿进程已退出,退出码: $exitCode'); // 清理状态 _currentProcess = null; final wasMining = _currentTask != null; _currentTask = null; _taskMonitor?.cancel(); _taskMonitor = null; // 发送进程退出事件 if (wasMining) { _processExitController.add(null); } }).catchError((e) { _logger.severe('监控进程退出失败: $e'); }); } /// 获取挖矿日志流 Stream get minerLogStream => _minerLogController.stream; MiningTaskInfo? get currentTask => _currentTask; bool get isMining => _currentProcess != null; /// 清理资源 void dispose() { _minerLogController.close(); _processExitController.close(); } } class MiningConfig { final String? lolMinerPath; final String? rigelPath; final String? bzMinerPath; final bool proxyEnabled; final String? serverUrl; final String? updateUrl; MiningConfig({ this.lolMinerPath, this.rigelPath, this.bzMinerPath, this.proxyEnabled = false, this.serverUrl, this.updateUrl, }); }