CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/project/public-paths.ts
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
/*
7
Monitoring of public paths in a running project.
8
*/
9
10
const UPDATE_INTERVAL_S: number = 20;
11
//const UPDATE_INTERVAL_S: number = 3; // for testing
12
13
import { callback, delay } from "awaiting";
14
import { execFile } from "node:child_process";
15
import { lstat } from "node:fs";
16
17
import type { Client } from "@cocalc/project/client";
18
import { getClient } from "@cocalc/project/client";
19
20
let monitor: MonitorPublicPaths | undefined = undefined;
21
export default function init() {
22
if (monitor !== undefined) return;
23
monitor = new MonitorPublicPaths();
24
}
25
26
class MonitorPublicPaths {
27
private client: Client;
28
private table: any;
29
30
constructor() {
31
this.client = getClient();
32
if (process.env.COCALC_EPHEMERAL_STATE === "yes") {
33
// nothing to do -- can't do anything with public paths if can't write to db.
34
return;
35
}
36
this.init();
37
}
38
39
private dbg(f): Function {
40
return this.client.dbg(`MonitorPublicPaths.${f}`);
41
}
42
43
private init(): void {
44
const dbg = this.dbg("_init");
45
dbg("initializing public_paths table");
46
const pattern = {
47
id: null,
48
project_id: this.client.client_id(),
49
path: null,
50
last_edited: null,
51
disabled: null,
52
};
53
this.table = this.client.sync_table({ public_paths: [pattern] });
54
this.update_loop(); // do not await!
55
}
56
57
private async update_loop(): Promise<void> {
58
const dbg = this.dbg("update_loop");
59
dbg(`run update every ${UPDATE_INTERVAL_S} seconds`);
60
while (this.table != null) {
61
try {
62
await this.update();
63
dbg("successful update");
64
} catch (err) {
65
dbg("error doing update", err);
66
}
67
await delay(UPDATE_INTERVAL_S * 1000);
68
}
69
dbg("this.table is null, so stopping update loop");
70
}
71
72
public close(): void {
73
const d = this.dbg("close");
74
if (this.table == null) {
75
d("already closed");
76
return;
77
}
78
d("closing...");
79
this.table.close();
80
delete this.table;
81
}
82
83
private async update(): Promise<void> {
84
if (this.table == null || this.table.get_state() !== "connected") {
85
return;
86
}
87
// const d = this.dbg("update");
88
const work: { id: string; path: string; last_edited: number }[] = [];
89
this.table.get().forEach((info, id) => {
90
if (!info.get("disabled")) {
91
let last_edited = info.get("last_edited", 0);
92
if (last_edited) {
93
last_edited = last_edited.valueOf();
94
}
95
work.push({
96
id,
97
path: info.get("path"),
98
last_edited,
99
});
100
}
101
});
102
for (const w of work) {
103
await this.update_path(w);
104
}
105
}
106
107
private async update_path(opts: {
108
id: string;
109
path: string;
110
last_edited: number;
111
}): Promise<void> {
112
const { id, path, last_edited } = opts;
113
//const d = this.dbg(`update_path('${path}')`);
114
const d = function (..._args) {}; // too verbose...
115
// If any file in the given path was modified after last_edited,
116
// update last_edited to when the path was modified.
117
let changed: boolean = false; // don't know yet
118
let stats: any;
119
d("lstat");
120
try {
121
stats = await callback(lstat, path);
122
} catch (err) {
123
d(err);
124
return;
125
}
126
if (stats.mtime.valueOf() > last_edited) {
127
d("clearly modified, since path changed");
128
changed = true;
129
}
130
if (!changed && stats.isDirectory()) {
131
// Is a directory, and directory mtime hasn't changed; still possible
132
// that there is a file in some subdir has changed, so have to do
133
// a full scan.
134
const days = (Date.now() - last_edited) / (1000 * 60 * 60 * 24);
135
// This input to find will give return code 1 if and only if it finds a FILE
136
// modified since last_edited (since we know the path exists).
137
const args = [
138
process.env.HOME + "/" + path,
139
"-type",
140
"f",
141
"-mtime",
142
`-${days}`,
143
"-exec",
144
"false",
145
"{}",
146
"+",
147
];
148
try {
149
await callback(execFile, "find", args);
150
} catch (err) {
151
if ((err as any).code) {
152
d("some files changed");
153
changed = true;
154
} else {
155
d("nothing changed");
156
}
157
}
158
}
159
if (changed) {
160
d("change -- update database table");
161
const last_edited = new Date();
162
this.table.set({ id, last_edited }, "shallow");
163
await this.table.save(); // and also cause change to get saved to database.
164
}
165
}
166
}
167
168