126 lines
4.7 KiB
Dart
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,
|
|
});
|
|
} |