- 协议与 C# 版 card-printer-agent 逐字段对齐(38200-38202 顺延、CORS *、串行任务队列、30 分钟保留) - macOS CUPS 后端:lpoptions 队列发现 + lp 参数列表提交(LC_ALL=C 防本地化粘连坑) - --mock 模拟模式 + bin/agent.dart headless 入口 + 测试卡生成工具 - 真机验收:HiTi CS-200e status 就绪 + 真实出卡;macOS 通用二进制构建通过 - App Sandbox 已关闭(服务需绑回环端口并调用 lp 子进程);zaxprint URL scheme 已注册
94 lines
2.9 KiB
Dart
94 lines
2.9 KiB
Dart
import 'dart:async';
|
||
|
||
import '../print/backend.dart';
|
||
import '../print/cups_backend.dart' show noopLog;
|
||
import '../utils/command_runner.dart';
|
||
import 'protocol.dart';
|
||
|
||
/// 打印队列中的单个任务运行时状态。
|
||
class PrintQueueTask {
|
||
PrintQueueTask({required this.taskId, required this.request});
|
||
|
||
final String taskId;
|
||
final PrintRequest request;
|
||
|
||
/// printing / done / error
|
||
String state = 'printing';
|
||
int status = 0;
|
||
String error = '';
|
||
DateTime? finishedAt;
|
||
}
|
||
|
||
/// 串行打印队列:打印机物理上一次只能打一张卡,任务逐个执行。
|
||
///
|
||
/// 任务状态存内存字典供前端轮询;完成后保留 30 分钟(对齐 C# 版 PrintQueue)。
|
||
class PrintQueue {
|
||
PrintQueue(this._backend, {void Function(String line)? onLog}) : _log = onLog ?? noopLog {
|
||
_cleanupTimer = Timer.periodic(const Duration(minutes: 5), (_) => _cleanupOld());
|
||
}
|
||
|
||
final PrintBackend _backend;
|
||
final void Function(String line) _log;
|
||
|
||
final _tasks = <String, PrintQueueTask>{};
|
||
Future<void> _tail = Future.value();
|
||
Timer? _cleanupTimer;
|
||
int _seq = 0;
|
||
|
||
/// 提交打印任务,立即返回 taskId。
|
||
String submit(PrintRequest request) {
|
||
if (request.front == null || request.front!.color.isEmpty) {
|
||
throw ArgumentError('缺少正面卡面图(front.color)');
|
||
}
|
||
final task = PrintQueueTask(taskId: 't${++_seq}', request: request);
|
||
_tasks[task.taskId] = task;
|
||
// 串行链:每个任务等前一个完成后执行,异常在 _run 内消化,链条不中断
|
||
_tail = _tail.then((_) => _run(task));
|
||
return task.taskId;
|
||
}
|
||
|
||
PrintTaskDto? get(String taskId) {
|
||
final task = _tasks[taskId];
|
||
if (task == null) return null;
|
||
return PrintTaskDto(taskId: task.taskId, state: task.state, status: task.status, statusText: task.error);
|
||
}
|
||
|
||
Future<void> _run(PrintQueueTask task) async {
|
||
_log('[${task.taskId}] 开始打印(份数 ${task.request.copies})');
|
||
try {
|
||
await _backend.printOneCard(task.request);
|
||
task.state = 'done';
|
||
_log('[${task.taskId}] 打印完成');
|
||
} catch (e) {
|
||
task.state = 'error';
|
||
task.error = errorMessageOf(e);
|
||
_log('[${task.taskId}] 打印失败:${task.error}');
|
||
} finally {
|
||
task.finishedAt = DateTime.now();
|
||
}
|
||
}
|
||
|
||
/// 清掉已完成/失败超 30 分钟的任务,避免内存常驻增长。
|
||
void _cleanupOld() {
|
||
final now = DateTime.now();
|
||
_tasks.removeWhere((_, task) =>
|
||
task.finishedAt != null && now.difference(task.finishedAt!).inMinutes > 30);
|
||
}
|
||
|
||
void dispose() {
|
||
_cleanupTimer?.cancel();
|
||
_cleanupTimer = null;
|
||
}
|
||
|
||
/// 从任意异常里提取可读文案(前端 statusText 直接展示)。
|
||
static String errorMessageOf(Object e) {
|
||
if (e is CommandException) return e.message;
|
||
try {
|
||
final dynamic err = e;
|
||
final msg = err.message;
|
||
if (msg is String && msg.isNotEmpty) return msg;
|
||
} catch (_) {}
|
||
return e.toString();
|
||
}
|
||
}
|