2026-01-22 15:14:27 +08:00
|
|
|
|
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;
|
2026-01-23 16:11:20 +08:00
|
|
|
|
StreamSubscription<void>? _processExitSubscription;
|
2026-01-22 15:14:27 +08:00
|
|
|
|
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();
|
2026-01-23 16:11:20 +08:00
|
|
|
|
_startProcessMonitor();
|
2026-01-22 15:14:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 停止持续挖矿
|
|
|
|
|
|
Future<void> stop() async {
|
|
|
|
|
|
_isRunning = false;
|
|
|
|
|
|
_monitorTimer?.cancel();
|
|
|
|
|
|
_monitorTimer = null;
|
2026-01-23 16:11:20 +08:00
|
|
|
|
_processExitSubscription?.cancel();
|
|
|
|
|
|
_processExitSubscription = null;
|
2026-01-22 15:14:27 +08:00
|
|
|
|
|
|
|
|
|
|
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();
|
2026-01-23 16:11:20 +08:00
|
|
|
|
// 确保进程监控已启动
|
|
|
|
|
|
if (_processExitSubscription == null) {
|
|
|
|
|
|
_startProcessMonitor();
|
|
|
|
|
|
}
|
2026-01-22 15:14:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 启动挖矿
|
|
|
|
|
|
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!);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 16:11:20 +08:00
|
|
|
|
/// 启动进程监控
|
|
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 15:14:27 +08:00
|
|
|
|
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,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|