import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:logging/logging.dart'; import 'mining_task_info.dart'; import '../utils/path_utils.dart'; /// 挖矿任务持久化服务(基于 .log 文件,而非 SQLite) /// /// 设计约定: /// - 使用 `bin/mining_tasks.log` 记录当前(或最近)挖矿任务,一行一条 JSON 记录。 /// - 每次收到新的挖矿任务时,追加一条记录。 /// - 挖矿任务完成后,从 .log 中删除对应记录。 /// - 客户端启动时读取 .log: /// - 如果存在任务且 `endTimestamp` 尚未过期,则恢复该任务; /// - 如果已过期,则删除该记录,并不恢复。 class DatabaseService { static final DatabaseService _instance = DatabaseService._internal(); factory DatabaseService() => _instance; DatabaseService._internal(); final Logger _logger = Logger('DatabaseService'); /// 日志文件路径:bin/mining_tasks.log File get _logFile => File(PathUtils.binFile('mining_tasks.log')); /// 初始化(确保 bin 目录存在) Future initialize() async { try { // 确保 bin 目录存在 final binDir = Directory(PathUtils.binDir); if (!await binDir.exists()) { await binDir.create(recursive: true); } // 日志文件可懒创建,不强制在这里创建 _logger.info('任务日志初始化完成(使用文件存储,不再使用 SQLite)'); } catch (e) { _logger.severe('任务日志初始化失败: $e'); rethrow; } } /// 插入挖矿任务(在 .log 文件中追加一条记录) Future insertMiningTask(MiningTaskInfo task) async { await initialize(); try { final now = DateTime.now().millisecondsSinceEpoch ~/ 1000; final record = { 'coin': task.coin, 'algo': task.algo, 'pool': task.pool, 'pool_url': task.poolUrl, 'wallet_address': task.walletAddress, 'worker_id': task.workerId, 'pool_user': task.poolUser, 'wallet_mining': task.walletMining, 'end_timestamp': task.endTimestamp, 'miner': task.miner, 'created_at': now, }; final jsonLine = jsonEncode(record); await _logFile.writeAsString('$jsonLine\n', mode: FileMode.append, flush: true); return 1; // 返回值目前未被使用,保持兼容即可 } catch (e) { _logger.severe('插入挖矿任务失败: $e'); rethrow; } } /// 挖矿任务完成后,从 .log 文件中删除该任务 Future finishMiningTask(MiningTaskInfo task) async { await initialize(); try { if (!await _logFile.exists()) { return; } final lines = await _logFile.readAsLines(); if (lines.isEmpty) return; final List keptLines = []; for (final line in lines) { if (line.trim().isEmpty) continue; try { final Map data = jsonDecode(line) as Map; final existing = _taskFromJson(data); // 如果与当前任务匹配,则跳过(即删除) if (_isSameTask(existing, task)) { continue; } keptLines.add(line); } catch (_) { // 解析失败的行保留,避免误删 keptLines.add(line); } } await _logFile.writeAsString(keptLines.join('\n') + (keptLines.isEmpty ? '' : '\n')); } catch (e) { _logger.severe('完成挖矿任务(从日志中删除)失败: $e'); } } /// 加载未完成的挖矿任务 /// /// - 读取 .log 中所有任务; /// - 删除已过期(endTimestamp <= now)的任务; /// - 返回最新的、尚未过期的任务(如果有)。 Future loadUnfinishedTask() async { await initialize(); try { if (!await _logFile.exists()) { return null; } final lines = await _logFile.readAsLines(); if (lines.isEmpty) { return null; } final now = DateTime.now().millisecondsSinceEpoch ~/ 1000; final List<_StoredTask> validTasks = []; for (final line in lines) { if (line.trim().isEmpty) continue; try { final Map data = jsonDecode(line) as Map; final task = _taskFromJson(data); final createdAt = (data['created_at'] as int?) ?? task.endTimestamp; // 过滤掉已过期任务 if (now >= task.endTimestamp) { continue; } validTasks.add(_StoredTask(task: task, createdAt: createdAt)); } catch (e) { _logger.warning('解析任务日志行失败,已跳过: $e, 原始行: $line'); } } // 重新写回仅包含未过期的任务 if (validTasks.isEmpty) { await _logFile.writeAsString(''); return null; } else { // 按创建时间排序,取最新一条作为恢复任务 validTasks.sort((a, b) => b.createdAt.compareTo(a.createdAt)); final toKeep = validTasks; final buffer = StringBuffer(); for (final t in toKeep) { final record = _taskToJson(t.task, createdAt: t.createdAt); buffer.writeln(jsonEncode(record)); } await _logFile.writeAsString(buffer.toString()); return validTasks.first.task; } } catch (e) { _logger.severe('加载挖矿任务失败: $e'); return null; } } /// 获取任务历史(目前基于 .log 仅保存“当前/最近”任务,这里返回空列表以保持接口兼容) Future>> getTaskHistory({int limit = 100}) async { // 如有需要,可以在未来扩展为持久化历史记录 return []; } /// 关闭(对文件存储无实际操作,保留接口以兼容旧代码) Future close() async { // no-op } // === 内部工具方法 === MiningTaskInfo _taskFromJson(Map data) { return MiningTaskInfo( coin: data['coin'] as String, algo: data['algo'] as String, pool: (data['pool'] as String?) ?? '', poolUrl: data['pool_url'] as String, walletAddress: data['wallet_address'] as String, workerId: data['worker_id'] as String, poolUser: data['pool_user'] as String?, walletMining: (data['wallet_mining'] as bool?) ?? ((data['wallet_mining'] is int) ? (data['wallet_mining'] as int) == 1 : false), endTimestamp: data['end_timestamp'] as int, miner: data['miner'] as String, ); } Map _taskToJson(MiningTaskInfo task, {required int createdAt}) { return { 'coin': task.coin, 'algo': task.algo, 'pool': task.pool, 'pool_url': task.poolUrl, 'wallet_address': task.walletAddress, 'worker_id': task.workerId, 'pool_user': task.poolUser, 'wallet_mining': task.walletMining, 'end_timestamp': task.endTimestamp, 'miner': task.miner, 'created_at': createdAt, }; } bool _isSameTask(MiningTaskInfo a, MiningTaskInfo b) { return a.coin == b.coin && a.algo == b.algo && a.pool == b.pool && a.poolUrl == b.poolUrl && a.walletAddress == b.walletAddress && a.workerId == b.workerId && (a.poolUser ?? '') == (b.poolUser ?? '') && a.walletMining == b.walletMining && a.endTimestamp == b.endTimestamp && a.miner == b.miner; } } class _StoredTask { final MiningTaskInfo task; final int createdAt; _StoredTask({required this.task, required this.createdAt}); }