243 lines
6.6 KiB
Dart
243 lines
6.6 KiB
Dart
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();
|
|
final StreamController<void> _processExitController = StreamController<void>.broadcast();
|
|
|
|
/// 进程退出事件流
|
|
Stream<void> get processExitStream => _processExitController.stream;
|
|
|
|
/// 启动挖矿
|
|
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();
|
|
_monitorProcessExit();
|
|
|
|
_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');
|
|
});
|
|
}
|
|
|
|
/// 监控进程退出
|
|
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<String> 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,
|
|
});
|
|
}
|