Files
TelemetryMonitor/lib/export/csv_exporter_base.dart
2026-04-21 14:40:09 -03:00

126 lines
4.7 KiB
Dart

import 'dart:async';
import '../layout/layout_controller.dart';
import '../session/log_buffer.dart';
import '../session/packet_buffer.dart';
typedef ProgressCallback = void Function(double progress);
/// Returned by export operations so the UI can report what happened.
class ExportResult {
ExportResult({required this.path, required this.bytesWritten});
/// On native: filesystem path. On Web: filename.
final String path;
final int bytesWritten;
}
/// Builds CSV row strings. Shared by both platform implementations so the
/// row layout is defined exactly once.
class CsvRowBuilder {
CsvRowBuilder(this.layout);
final LayoutController layout;
/// Header row for data CSV. Disabled channels are omitted; pause and the
/// 8 status fields always appear.
String dataHeader() {
final cols = <String>['timestamp_us'];
for (var ch = 1; ch <= 16; ch++) {
if (layout.configFor(ch).enabled) {
cols.add('ch${ch}_${_safe(layout.configFor(ch).name)}');
}
}
cols.add('pause');
for (var s = 0; s < 8; s++) {
cols.add('status${s + 1}_${_safe(layout.statusNames[s])}');
}
return cols.join(',');
}
/// Row for one packet. Missing values are blank cells.
String dataRow(packet) {
final cells = <String>[];
cells.add(packet.hasTimestampUs() ? packet.timestampUs.toString() : '');
for (var ch = 1; ch <= 16; ch++) {
if (!layout.configFor(ch).enabled) continue;
cells.add(_channelCell(packet, ch));
}
cells.add(packet.hasPause() ? (packet.pause ? '1' : '0') : '');
for (var s = 1; s <= 8; s++) {
cells.add(_statusCell(packet, s));
}
return cells.join(',');
}
String logHeader() => 'timestamp_us,severity,error_number,description';
String logRow(entry) {
final ts = entry.hasTimestampUs() ? entry.timestampUs.toString() : '';
final sev = entry.hasSeverity() ? entry.severity.name : '';
final err = entry.hasErrorNumber() ? entry.errorNumber.toString() : '';
final desc = entry.hasDescription() ? _escape(entry.description) : '';
return '$ts,$sev,$err,$desc';
}
static String _channelCell(packet, int ch) {
switch (ch) {
case 1: return packet.hasCh1() ? packet.ch1.toString() : '';
case 2: return packet.hasCh2() ? packet.ch2.toString() : '';
case 3: return packet.hasCh3() ? packet.ch3.toString() : '';
case 4: return packet.hasCh4() ? packet.ch4.toString() : '';
case 5: return packet.hasCh5() ? packet.ch5.toString() : '';
case 6: return packet.hasCh6() ? packet.ch6.toString() : '';
case 7: return packet.hasCh7() ? packet.ch7.toString() : '';
case 8: return packet.hasCh8() ? packet.ch8.toString() : '';
case 9: return packet.hasCh9() ? packet.ch9.toString() : '';
case 10: return packet.hasCh10() ? packet.ch10.toString() : '';
case 11: return packet.hasCh11() ? packet.ch11.toString() : '';
case 12: return packet.hasCh12() ? packet.ch12.toString() : '';
case 13: return packet.hasCh13() ? packet.ch13.toString() : '';
case 14: return packet.hasCh14() ? packet.ch14.toString() : '';
case 15: return packet.hasCh15() ? packet.ch15.toString() : '';
case 16: return packet.hasCh16() ? packet.ch16.toString() : '';
default: return '';
}
}
static String _statusCell(packet, int s) {
switch (s) {
case 1: return packet.hasStatus1() ? packet.status1.toString() : '';
case 2: return packet.hasStatus2() ? packet.status2.toString() : '';
case 3: return packet.hasStatus3() ? packet.status3.toString() : '';
case 4: return packet.hasStatus4() ? packet.status4.toString() : '';
case 5: return packet.hasStatus5() ? packet.status5.toString() : '';
case 6: return packet.hasStatus6() ? packet.status6.toString() : '';
case 7: return packet.hasStatus7() ? packet.status7.toString() : '';
case 8: return packet.hasStatus8() ? packet.status8.toString() : '';
default: return '';
}
}
/// Strip characters that would break CSV column names.
static String _safe(String s) => s.replaceAll(RegExp(r'[,\s]+'), '_');
/// Escape a free-form field for CSV. Wraps in quotes if needed.
static String _escape(String s) {
if (s.contains(',') || s.contains('"') || s.contains('\n')) {
return '"${s.replaceAll('"', '""')}"';
}
return s;
}
}
/// Common interface implemented by both platform exporters.
abstract class CsvExporter {
/// Export the entire packet buffer as data CSV.
Future<ExportResult> exportData({
required PacketBuffer buffer,
required LayoutController layout,
ProgressCallback? onProgress,
});
/// Export the entire log buffer as log CSV.
Future<ExportResult> exportLog({
required LogBuffer buffer,
ProgressCallback? onProgress,
});
}