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