261 lines
7.2 KiB
Dart
261 lines
7.2 KiB
Dart
import 'dart:async';
|
||
import 'dart:io';
|
||
import 'package:logging/logging.dart';
|
||
// import 'package:ini/ini.dart';
|
||
import 'mining_manager.dart';
|
||
import 'mining_task_info.dart';
|
||
import '../utils/ini_utils.dart';
|
||
import '../utils/path_utils.dart';
|
||
|
||
/// 持续挖矿管理器
|
||
class SustainMiner {
|
||
static final SustainMiner _instance = SustainMiner._internal();
|
||
factory SustainMiner() => _instance;
|
||
SustainMiner._internal();
|
||
|
||
final Logger _logger = Logger('SustainMiner');
|
||
final MiningManager _miningManager = MiningManager();
|
||
|
||
SustainMiningConfig? _config;
|
||
MiningConfig? _miningConfig;
|
||
Timer? _monitorTimer;
|
||
StreamSubscription<void>? _processExitSubscription;
|
||
bool _isRunning = false;
|
||
bool _isPaused = false;
|
||
|
||
/// 加载配置
|
||
Future<bool> loadConfig(MiningConfig miningConfig) async {
|
||
try {
|
||
_miningConfig = miningConfig;
|
||
|
||
final configFile = File(PathUtils.binFile('mining.windows.conf'));
|
||
if (!await configFile.exists()) {
|
||
_logger.warning('配置文件不存在');
|
||
return false;
|
||
}
|
||
|
||
final content = await configFile.readAsString();
|
||
final config = parseIni(content);
|
||
|
||
// ini 2.x: 使用 get(section, option)
|
||
final enabledStr = (config.get('sustain', 'enabled') ?? '').toLowerCase();
|
||
final enabled = enabledStr == 'true';
|
||
|
||
if (!enabled) {
|
||
_logger.info('持续挖矿未启用');
|
||
return false;
|
||
}
|
||
|
||
_config = SustainMiningConfig(
|
||
enabled: enabled,
|
||
algo: _trimQuotes(config.get('sustain', 'algo') ?? ''),
|
||
coin: _trimQuotes(config.get('sustain', 'coin') ?? ''),
|
||
miner: _trimQuotes(config.get('sustain', 'miner') ?? ''),
|
||
poolUrl: _trimQuotes(config.get('sustain', 'pool_url') ?? ''),
|
||
wallet: _trimQuotes(config.get('sustain', 'wallet') ?? ''),
|
||
workerId: _trimQuotes(config.get('sustain', 'worker_id') ?? ''),
|
||
poolUser: _trimQuotes(config.get('sustain', 'pool_user') ?? ''),
|
||
walletMining: (config.get('sustain', 'wallet_mining') ?? '').toLowerCase() == 'true',
|
||
);
|
||
|
||
// 验证配置
|
||
if (_config!.algo.isEmpty ||
|
||
_config!.coin.isEmpty ||
|
||
_config!.miner.isEmpty ||
|
||
_config!.poolUrl.isEmpty ||
|
||
_config!.wallet.isEmpty ||
|
||
_config!.workerId.isEmpty) {
|
||
_logger.severe('持续挖矿配置不完整');
|
||
return false;
|
||
}
|
||
|
||
// 验证挖矿软件路径
|
||
final minerPath = _getMinerPath(_config!.miner);
|
||
if (minerPath == null || minerPath.isEmpty) {
|
||
_logger.severe('${_config!.miner} 路径未配置');
|
||
return false;
|
||
}
|
||
|
||
_logger.info('持续挖矿配置加载成功: 算法=${_config!.algo}, 币种=${_config!.coin}, 挖矿软件=${_config!.miner}');
|
||
return true;
|
||
} catch (e) {
|
||
_logger.severe('加载持续挖矿配置失败: $e');
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// 启动持续挖矿
|
||
Future<void> start() async {
|
||
if (_config == null || !_config!.enabled) {
|
||
return;
|
||
}
|
||
|
||
if (_isRunning) {
|
||
_logger.info('持续挖矿已在运行中');
|
||
return;
|
||
}
|
||
|
||
if (_miningManager.isMining) {
|
||
_logger.info('当前有挖矿任务,等待任务结束后启动持续挖矿');
|
||
return;
|
||
}
|
||
|
||
_isRunning = true;
|
||
_isPaused = false;
|
||
|
||
_logger.info('启动持续挖矿...');
|
||
await _startMining();
|
||
_startProcessMonitor();
|
||
}
|
||
|
||
/// 停止持续挖矿
|
||
Future<void> stop() async {
|
||
_isRunning = false;
|
||
_monitorTimer?.cancel();
|
||
_monitorTimer = null;
|
||
_processExitSubscription?.cancel();
|
||
_processExitSubscription = null;
|
||
|
||
if (_miningManager.isMining && _miningManager.currentTask == null) {
|
||
// 只有持续挖矿任务在运行时才停止
|
||
await _miningManager.stopMining();
|
||
}
|
||
|
||
_logger.info('持续挖矿已停止');
|
||
}
|
||
|
||
/// 暂停持续挖矿
|
||
Future<void> pause() async {
|
||
if (!_isRunning || _isPaused) {
|
||
return;
|
||
}
|
||
|
||
_logger.info('暂停持续挖矿(有新任务)...');
|
||
_isPaused = true;
|
||
|
||
if (_miningManager.isMining && _miningManager.currentTask == null) {
|
||
await _miningManager.stopMining();
|
||
}
|
||
}
|
||
|
||
/// 恢复持续挖矿
|
||
Future<void> resume() async {
|
||
if (!_isRunning || !_isPaused) {
|
||
return;
|
||
}
|
||
|
||
if (_miningManager.isMining) {
|
||
_logger.info('当前有挖矿任务,等待任务结束后恢复持续挖矿');
|
||
return;
|
||
}
|
||
|
||
_logger.info('恢复持续挖矿...');
|
||
_isPaused = false;
|
||
await _startMining();
|
||
// 确保进程监控已启动
|
||
if (_processExitSubscription == null) {
|
||
_startProcessMonitor();
|
||
}
|
||
}
|
||
|
||
/// 启动挖矿
|
||
Future<void> _startMining() async {
|
||
if (_config == null || _isPaused || _miningConfig == null) {
|
||
return;
|
||
}
|
||
|
||
if (_miningManager.isMining) {
|
||
return;
|
||
}
|
||
|
||
final task = MiningTaskInfo(
|
||
coin: _config!.coin,
|
||
algo: _config!.algo,
|
||
pool: '',
|
||
poolUrl: _config!.poolUrl,
|
||
walletAddress: _config!.wallet,
|
||
workerId: _config!.workerId,
|
||
poolUser: _config!.poolUser,
|
||
walletMining: _config!.walletMining,
|
||
endTimestamp: DateTime.now().add(const Duration(days: 365)).millisecondsSinceEpoch ~/ 1000, // 持续挖矿设置很长的结束时间
|
||
miner: _config!.miner,
|
||
);
|
||
|
||
await _miningManager.startMining(task, _miningConfig!);
|
||
}
|
||
|
||
/// 启动进程监控
|
||
void _startProcessMonitor() {
|
||
_processExitSubscription?.cancel();
|
||
|
||
// 监听挖矿进程退出事件
|
||
_processExitSubscription = _miningManager.processExitStream.listen((_) {
|
||
// 检查是否是持续挖矿进程退出
|
||
if (_isRunning && !_isPaused && !_miningManager.isMining && _miningManager.currentTask == null) {
|
||
_logger.warning('持续挖矿进程意外退出,将在5秒后自动重启...');
|
||
// 延迟5秒后重启,避免频繁重启
|
||
Future.delayed(const Duration(seconds: 5), () {
|
||
if (_isRunning && !_isPaused && !_miningManager.isMining) {
|
||
_logger.info('自动重启持续挖矿...');
|
||
_startMining();
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
String _trimQuotes(String value) {
|
||
var v = value.trim();
|
||
if (v.length >= 2) {
|
||
final first = v[0];
|
||
final last = v[v.length - 1];
|
||
if ((first == '"' && last == '"') || (first == "'" && last == "'")) {
|
||
v = v.substring(1, v.length - 1);
|
||
}
|
||
}
|
||
return v;
|
||
}
|
||
|
||
String? _getMinerPath(String miner) {
|
||
if (_miningConfig == null) return null;
|
||
|
||
switch (miner.toLowerCase()) {
|
||
case 'lolminer':
|
||
return _miningConfig!.lolMinerPath;
|
||
case 'rigel':
|
||
return _miningConfig!.rigelPath;
|
||
case 'bzminer':
|
||
return _miningConfig!.bzMinerPath;
|
||
default:
|
||
return null;
|
||
}
|
||
}
|
||
|
||
bool get isRunning => _isRunning;
|
||
bool get isPaused => _isPaused;
|
||
}
|
||
|
||
class SustainMiningConfig {
|
||
final bool enabled;
|
||
final String algo;
|
||
final String coin;
|
||
final String miner;
|
||
final String poolUrl;
|
||
final String wallet;
|
||
final String workerId;
|
||
final String poolUser;
|
||
final bool walletMining;
|
||
|
||
SustainMiningConfig({
|
||
required this.enabled,
|
||
required this.algo,
|
||
required this.coin,
|
||
required this.miner,
|
||
required this.poolUrl,
|
||
required this.wallet,
|
||
required this.workerId,
|
||
required this.poolUser,
|
||
required this.walletMining,
|
||
});
|
||
}
|