feat(sync): the folder loop, the hooks and the panel — it can be switched on now
The three layers underneath were already there and unreachable: the change-log format (7d664bd), the identity column (724e68b) and the no-backfill decision (c48294b). This is the wiring that gives the operator a switch. Settings, all profile-scoped, because each profile can point at its own logbook: the folder, this PC's name, the machine id minted once from it, the per-peer read offsets and the tie-break counter. Scoping the machine id is what keeps two profiles sharing one folder from writing two logbooks into one file. Three hooks. Add and update publish asynchronously, down with the rest of the after-the-fact work — a folder on a network share can block for seconds and a contact belongs on screen long before another machine hears about it. Deletion publishes SYNCHRONOUSLY and BEFORE the row goes, for the same reason deleteRemoteCopies does: once it is gone its identity is gone with it and the tombstone names nothing. The apply path uses the repository directly and never AddQSO/UpdateQSO/DeleteQSO — those publish, and a change applied here would be written straight back out, two machines echoing each other for ever. Saving writes a probe file to the chosen folder rather than asking whether it exists. A read-only cloud folder, or a share whose credentials expired, exists perfectly well and would swallow every contact in silence; if the probe fails the switch goes back off instead of sitting on while nothing is written. The panel is mostly status, and deliberately: every part of this runs on another machine and on a sync client OpsLog cannot see, so "it is not working" has to be answerable from the settings page — which PCs are in the folder, when each last logged, what is waiting unread. Four tests on the apply path, the middle two being the ones that matter: an edit made on the other PC lands on the copy already here, matched on the contact itself, instead of becoming a second row — that is what makes the no-backfill decision safe — and a contact with the same station on another band stays a separate contact.
This commit is contained in:
@@ -2465,6 +2465,82 @@ export namespace main {
|
||||
this.body = source["body"];
|
||||
}
|
||||
}
|
||||
export class FolderSyncConfig {
|
||||
enabled: boolean;
|
||||
folder: string;
|
||||
machine: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new FolderSyncConfig(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.enabled = source["enabled"];
|
||||
this.folder = source["folder"];
|
||||
this.machine = source["machine"];
|
||||
}
|
||||
}
|
||||
export class FolderSyncPeer {
|
||||
machine: string;
|
||||
last_change: string;
|
||||
behind: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new FolderSyncPeer(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.machine = source["machine"];
|
||||
this.last_change = source["last_change"];
|
||||
this.behind = source["behind"];
|
||||
}
|
||||
}
|
||||
export class FolderSyncStatus {
|
||||
enabled: boolean;
|
||||
folder: string;
|
||||
machine_id: string;
|
||||
peers: FolderSyncPeer[];
|
||||
last_sync: string;
|
||||
sent: number;
|
||||
received: number;
|
||||
error: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new FolderSyncStatus(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.enabled = source["enabled"];
|
||||
this.folder = source["folder"];
|
||||
this.machine_id = source["machine_id"];
|
||||
this.peers = this.convertValues(source["peers"], FolderSyncPeer);
|
||||
this.last_sync = source["last_sync"];
|
||||
this.sent = source["sent"];
|
||||
this.received = source["received"];
|
||||
this.error = source["error"];
|
||||
}
|
||||
|
||||
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 GridCacheStatus {
|
||||
enabled: boolean;
|
||||
known: number;
|
||||
|
||||
Reference in New Issue
Block a user