Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/conat/files/sync.ts
1539 views
1
/*
2
File Sync Services
3
4
There's a bunch of these running with access to all project files.
5
This is somewhat similar to the persist servers in architecture,
6
except this is to support sync editing of files.
7
8
These listen on this sticky subject:
9
10
subject = sync.project-{project_id}.edit
11
12
User sends a message that contains the path to a file they want to edit.
13
The fact user can even send the message means they have read/write
14
privileges to the subject.
15
16
17
the path to a file in a project
18
19
OUTPUT: starts (or keeps running) the filesystem aware side of an editing session
20
21
*/
22
23
import type { Client, Message, Subscription } from "@cocalc/conat/core/client";
24
import { STICKY_QUEUE_GROUP } from "@cocalc/conat/core/client";
25
import { isValidUUID } from "@cocalc/util/misc";
26
27
interface SyncDoc {
28
close: () => void;
29
}
30
31
export type SyncDocCreator = (opts: {
32
project_id: string;
33
path: string;
34
doctype?: any;
35
}) => SyncDoc;
36
37
interface Options {
38
client: Client;
39
40
// projects = absolute path in filesystem to user projects, so join(projects, project_id)
41
// is the path to project_id's files.
42
projects: string;
43
44
createSyncDoc: SyncDocCreator;
45
}
46
47
export async function init(opts: Options) {
48
const syncServer = new SyncServer(
49
opts.client,
50
opts.projects,
51
opts.createSyncDoc,
52
);
53
await syncServer.init();
54
return syncServer;
55
}
56
57
interface Api {
58
open: (opts: { path: string; doctype?: any }) => Promise<void>;
59
}
60
61
class SyncServer {
62
private service?: Subscription;
63
private syncDocs: { [key: string]: SyncDoc } = {};
64
private interest: { [key: string]: number } = {};
65
66
constructor(
67
private client: Client,
68
private projects: string,
69
private createSyncDoc: SyncDocCreator,
70
) {}
71
72
init = async () => {
73
const self = this;
74
this.service = await this.client.service<Api>(
75
"sync.*.open",
76
{
77
async open({ path, doctype }) {
78
const mesg: Message = this as any;
79
self.open(mesg.subject, path, doctype);
80
},
81
},
82
{ queue: STICKY_QUEUE_GROUP },
83
);
84
};
85
86
private key = (project_id, path) => {
87
return `${project_id}/${path}`;
88
};
89
90
private open = (subject: string, path: string, doctype) => {
91
const project_id = subject.split(".")[1]?.slice("project-".length);
92
console.log("open", {
93
subject,
94
path,
95
doctype,
96
project_id,
97
projects: this.projects,
98
});
99
if (!isValidUUID(project_id)) {
100
throw Error("invalid subject");
101
}
102
const key = this.key(project_id, path);
103
if (this.syncDocs[key] === undefined) {
104
this.syncDocs[key] = this.createSyncDoc({ project_id, path, doctype });
105
}
106
this.interest[key] = Date.now();
107
};
108
109
close = () => {
110
this.service?.close();
111
delete this.service;
112
for (const key in this.syncDocs) {
113
this.syncDocs[key].close();
114
delete this.syncDocs[key];
115
}
116
};
117
}
118
119