import { callback2 } from "@cocalc/util/async-utils" ; import { db } from "@cocalc/database" ; import { EventEmitter } from "events" ; import { isEqual } from "lodash" ; import { ProjectState , ProjectStatus } from "@cocalc/util/db-schema/projects" ; import { quota } from "@cocalc/util/upgrades/quota" ; import { delay } from "awaiting" ; import getLogger from "@cocalc/backend/logger" ; import { site_license_hook } from "@cocalc/database/postgres/site-license/hook" ; export type { ProjectState , ProjectStatus } ; const winston = getLogger ( "project-control" ) ; export type Action = "open" | "start" | "stop" | "restart" ; const projectCache = { } ; export function getProject ( project_id ) { return projectCache [ project_id ] ?. deref ( ) ; } export abstract class BaseProject extends EventEmitter { public readonly project_id : string ; public is_ready : boolean = false ; public is_freed : boolean = false ; protected stateChanging : ProjectState | undefined = undefined ; constructor ( project_id ) { super ( ) ; projectCache [ project_id ] = new WeakRef ( this ) ; this . project_id = project_id ; const dbg = this . dbg ( "constructor" ) ; dbg ( "initializing" ) ; } protected async siteLicenseHook ( ) { await site_license_hook ( db ( ) , this . project_id ) ; } protected async saveStateToDatabase ( state ) { await callback2 ( db ( ) . set_project_state , { ... state , project_id : this . project_id , } ) ; } protected async saveStatusToDatabase ( status ) { await callback2 ( db ( ) . set_project_status , { project_id : this . project_id , status , } ) ; } dbg ( f ) { return ( msg ? ) => { winston . debug ( `(project_id=${ this . project_id }).${ f }: ${ msg }` ) ; } ; } abstract state ( ) ; abstract status ( ) ; abstract start ( ) ; abstract stop ( ) ; async restart ( ) { this . dbg ( "restart" ) ( ) ; await this . stop ( ) ; await this . start ( ) ; } protected async wait ( opts ) { const { until , maxTime } = opts ; const t0 = new Date ( ) . valueOf ( ) ; let d = 250 ; while ( new Date ( ) . valueOf ( ) - t0 <= maxTime ) { if ( await until ( ) ) { winston . debug ( `wait ${ this . project_id } -- satisfied` ) ; return ; } await delay ( d ) ; d *= 1.2 ; } const err = `wait ${ this . project_id } -- FAILED` ; winston . debug ( err ) ; throw Error ( err ) ; } async address ( ) { const dbg = this . dbg ( "address" ) ; dbg ( "first ensure is running" ) ; await this . start ( ) ; dbg ( "it is running" ) ; const status = await this . status ( ) ; if ( ! status [ "hub-server.port" ] ) { throw Error ( "unable to determine project port" ) ; } if ( ! status [ "secret_token" ] ) { throw Error ( "unable to determine secret_token" ) ; } const state = await this . state ( ) ; const host = state . ip ; if ( ! host ) { throw Error ( "unable to determine host" ) ; } return { host , port : status [ "hub-server.port" ] , secret_token : status . secret_token , } ; } abstract copyPath ( opts ) ; async setAllQuotas ( ) { const dbg = this . dbg ( "set_all_quotas" ) ; dbg ( ) ; const x = await callback2 ( db ( ) . get_project , { project_id : this . project_id , columns : [ "state" , "users" , "settings" , "run_quota" ] , } ) ; if ( ! [ "running" , "starting" , "pending" ] . includes ( x . state ?. state ) ) { dbg ( "project not active so nothing to do" ) ; return ; } const cur = quota ( x . settings , x . users ) ; if ( isEqual ( x . run_quota , cur ) ) { dbg ( "running, but no quotas changed" ) ; return ; } else { dbg ( "running and a quota changed; restart" ) ; ( async ( ) => { try { await this . restart ( ) ; dbg ( "restart worked" ) ; } catch ( err ) { dbg ( `restart failed -- ${ err }` ) ; } } ) ( ) ; } } } export interface CopyOptions { path : string ; target_project_id ? : string ; target_path ? : string ; overwrite_newer ? : boolean ; delete_missing ? : boolean ; backup ? : boolean ; timeout ? : number ; bwlimit ? : number ; wait_until_done ? : boolean ; scheduled ? : string | Date ; public ? : boolean ; exclude ? : string [ ] ; }