Files
windows-application/lib/core/mining_manager.dart

243 lines
6.6 KiB
Dart
Raw Normal View History

2026-01-22 15:14:27 +08:00
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<String> _minerLogController = StreamController<String>.broadcast();
2026-01-23 16:11:20 +08:00
final StreamController<void> _processExitController = StreamController<void>.broadcast();
/// 进程退出事件流
Stream<void> get processExitStream => _processExitController.stream;
2026-01-22 15:14:27 +08:00
/// 启动挖矿
Future<bool> 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();
2026-01-23 16:11:20 +08:00
_monitorProcessExit();
2026-01-22 15:14:27 +08:00
_logger.info('挖矿已启动 (PID: ${_currentProcess!.pid})');
return true;
} catch (e) {
_logger.severe('启动挖矿失败: $e');
return false;
}
}
/// 停止挖矿
Future<void> 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<String> _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');
});
}
2026-01-23 16:11:20 +08:00
/// 监控进程退出
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');
});
}
2026-01-22 15:14:27 +08:00
/// 获取挖矿日志流
Stream<String> get minerLogStream => _minerLogController.stream;
MiningTaskInfo? get currentTask => _currentTask;
bool get isMining => _currentProcess != null;
/// 清理资源
void dispose() {
_minerLogController.close();
2026-01-23 16:11:20 +08:00
_processExitController.close();
2026-01-22 15:14:27 +08:00
}
}
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,
});
}