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

241 lines
7.5 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<void> 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<int> insertMiningTask(MiningTaskInfo task) async {
await initialize();
try {
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
final record = <String, dynamic>{
'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<void> finishMiningTask(MiningTaskInfo task) async {
await initialize();
try {
if (!await _logFile.exists()) {
return;
}
final lines = await _logFile.readAsLines();
if (lines.isEmpty) return;
final List<String> keptLines = [];
for (final line in lines) {
if (line.trim().isEmpty) continue;
try {
final Map<String, dynamic> data = jsonDecode(line) as Map<String, dynamic>;
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<MiningTaskInfo?> 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<String, dynamic> data = jsonDecode(line) as Map<String, dynamic>;
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<List<Map<String, dynamic>>> getTaskHistory({int limit = 100}) async {
// 如有需要,可以在未来扩展为持久化历史记录
return [];
}
/// 关闭(对文件存储无实际操作,保留接口以兼容旧代码)
Future<void> close() async {
// no-op
}
// === 内部工具方法 ===
MiningTaskInfo _taskFromJson(Map<String, dynamic> 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<String, dynamic> _taskToJson(MiningTaskInfo task, {required int createdAt}) {
return <String, dynamic>{
'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});
}