feat: ADIF monitor — auto-import QSOs from watched external ADIF files
Watches a configurable list of external ADIF files (fldigi RTTY logbook, N1MM, VarAC…) and imports newly appended QSOs automatically. Each record goes through the existing UDP-log path, so it gets full enrichment, ±2-minute dedup (shared udpLogMu, can't race the UDP auto-log) and automatic upload to the configured external services — no per-file toggles like Log4OM. A newly added file starts at its current size (offset -1 sentinel → size on first scan), so the QSOs already in it are NOT bulk-imported; only contacts logged after it was added come in. Reads only up to the last complete <eor> so a half-written record waits. Handles truncation/rotation (re-reads from 0, dedup protects) and persists per-file offsets without clobbering a concurrent UI edit of the file list. New Settings → ADIF monitor section: master enable + add/remove/toggle files. Backend emits adifmon:imported → the grid refreshes and a toast reports the count. i18n EN + FR.
This commit is contained in:
@@ -1296,6 +1296,55 @@ export namespace lotwusers {
|
||||
|
||||
export namespace main {
|
||||
|
||||
export class ADIFWatchFile {
|
||||
path: string;
|
||||
enabled: boolean;
|
||||
offset: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new ADIFWatchFile(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.path = source["path"];
|
||||
this.enabled = source["enabled"];
|
||||
this.offset = source["offset"];
|
||||
}
|
||||
}
|
||||
export class ADIFMonitorConfig {
|
||||
enabled: boolean;
|
||||
files: ADIFWatchFile[];
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new ADIFMonitorConfig(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.enabled = source["enabled"];
|
||||
this.files = this.convertValues(source["files"], ADIFWatchFile);
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
export class AntGeniusSettings {
|
||||
enabled: boolean;
|
||||
host: string;
|
||||
|
||||
Reference in New Issue
Block a user