const UPDATE_INTERVAL_S = 20 ; import { lstat } from "fs" ; import { execFile } from "child_process" ; import { callback , delay } from "awaiting" ; let monitor = undefined ; export default function init ( ) { if ( monitor !== undefined ) return ; monitor = new MonitorPublicPaths ( ) ; } interface Client { dbg : Function ; client_id : Function ; sync_table : Function ; query : Function ; } class MonitorPublicPaths { private client : Client ; private table : any ; constructor ( ) { this . client = require ( "./client" ) . client ; if ( this . client == null ) { throw Error ( "client must have been initialized first" ) ; } if ( process . env . COCALC_EPHEMERAL_STATE === "yes" ) { return ; } this . init ( ) ; } private dbg ( f ) { return this . client . dbg ( `MonitorPublicPaths.${ f }` ) ; } private init ( ) { const dbg = this . dbg ( "_init" ) ; dbg ( "initializing public_paths table" ) ; const pattern = { id : null , project_id : this . client . client_id ( ) , path : null , last_edited : null , disabled : null , } ; this . table = this . client . sync_table ( { public_paths : [ pattern ] } ) ; this . update_loop ( ) ; } private async update_loop ( ) { const dbg = this . dbg ( "update_loop" ) ; dbg ( `run update every ${ UPDATE_INTERVAL_S } seconds` ) ; while ( this . table != null ) { try { await this . update ( ) ; dbg ( "successful update" ) ; } catch ( err ) { dbg ( "error doing update" , err ) ; } await delay ( UPDATE_INTERVAL_S * 1000 ) ; } dbg ( "this.table is null, so stopping update loop" ) ; } public close ( ) { const d = this . dbg ( "close" ) ; if ( this . table == null ) { d ( "already closed" ) ; return ; } d ( "closing..." ) ; this . table . close ( ) ; delete this . table ; } private async update ( ) { if ( this . table == null || this . table . get_state ( ) !== "connected" ) { return ; } const work = [ ] ; this . table . get ( ) . forEach ( ( info , id ) => { if ( ! info . get ( "disabled" ) ) { let last_edited = info . get ( "last_edited" , 0 ) ; if ( last_edited ) { last_edited = last_edited . valueOf ( ) ; } work . push ( { id , path : info . get ( "path" ) , last_edited , } ) ; } } ) ; for ( const w of work ) { await this . update_path ( w ) ; } } private async update_path ( opts ) { const { id , path , last_edited } = opts ; const d = function ( ... _args ) { } ; let changed = false ; let stats ; d ( "lstat" ) ; stats = await callback ( lstat , path ) ; if ( stats . mtime . valueOf ( ) > last_edited ) { d ( "clearly modified, since path changed" ) ; changed = true ; } if ( ! changed && stats . isDirectory ( ) ) { const days = ( new Date ( ) . valueOf ( ) - last_edited ) / ( 1000 * 60 * 60 * 24 ) ; const args = [ process . env . HOME + "/" + path , "-type" , "f" , "-mtime" , `-${ days }` , "-exec" , "false" , "{}" , "+" , ] ; try { await callback ( execFile , "find" , args ) ; } catch ( err ) { if ( ( err as any ) . code ) { d ( "some files changed" ) ; changed = true ; } else { d ( "nothing changed" ) ; } } } if ( changed ) { d ( "change -- update database table" ) ; const last_edited = new Date ( ) ; this . table . set ( { id , last_edited } , "shallow" ) ; await this . table . save ( ) ; } } }