-
Notifications
You must be signed in to change notification settings - Fork 51
Expand file tree
/
Copy pathHub.ts
More file actions
78 lines (75 loc) · 3.15 KB
/
Copy pathHub.ts
File metadata and controls
78 lines (75 loc) · 3.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import { Config, FileData } from "./types.ts";
import { Peer, PeerHealth } from "./Peer.ts";
import { PeerStorage } from "./PeerStorage.ts";
import { PeerCouchDB } from "./PeerCouchDB.ts";
export class Hub {
conf: Config;
peers = [] as Peer[];
constructor(conf: Config) {
this.conf = conf;
}
// Aggregate peer health for the heartbeat. `ok` = every peer syncing (also
// false if no peers were constructed). `restartWorthy` = any peer judges itself
// restart-worthy (was healthy, now persistently failing while its backend is
// up) — see Peer.probeHealth.
async healthProbe(): Promise<{ ok: boolean; restartWorthy: boolean; peers: PeerHealth[] }> {
const peers = await Promise.all(this.peers.map((p) => p.probeHealth()));
const ok = peers.length > 0 && peers.every((p) => p.ok);
const restartWorthy = peers.some((p) => p.restartWorthy);
return { ok, restartWorthy, peers };
}
start() {
for (const p of this.peers) {
p.stop();
}
this.peers = [];
for (const peer of this.conf.peers) {
if (peer.type == "couchdb") {
const p = new PeerCouchDB(peer, this.dispatch.bind(this));
this.peers.push(p);
} else if (peer.type == "storage") {
const p = new PeerStorage(peer, this.dispatch.bind(this));
this.peers.push(p);
} else {
throw new Error(`Unexpected Peer type: ${(peer as any)?.name} - ${(peer as any)?.type}`);
}
}
// Initialize couchdb peers FIRST and await them, then start storage peers.
// Otherwise a storage peer's offline scan can push to a couchdb peer before its
// DB managers are initialized (initializeDatabase), causing
// "Cannot read properties of undefined (reading 'getDBEntryMeta')".
(async () => {
for (const p of this.peers) {
if (p.config.type === "couchdb") {
await p.start().catch((e) => {
console.error(`[Hub] peer "${p.config.name}" start() failed:`, e);
});
}
}
for (const p of this.peers) {
if (p.config.type !== "couchdb") {
p.start().catch((e) => {
console.error(`[Hub] peer "${p.config.name}" start() failed:`, e);
});
}
}
})();
}
async dispatch(source: Peer, path: string, data: FileData | false) {
for (const peer of this.peers) {
if (peer !== source && (source.config.group ?? "") === (peer.config.group ?? "")) {
let ret = false;
if (data === false) {
ret = await peer.delete(path);
} else {
ret = await peer.put(path, data);
}
if (ret) {
// Logger(` ${data === false ? "-x->" : "--->"} ${peer.config.name} ${path} `)
} else {
// Logger(` ${peer.config.name} ignored ${path} `)
}
}
}
}
}