import { SyncTable } from "@cocalc/sync/table" ; import { SyncDB } from "@cocalc/sync/editor/db/sync" ; import { SyncString } from "@cocalc/sync/editor/string/sync" ; import { SyncDoc } from "@cocalc/sync/editor/generic/sync-doc" ; import { Client } from "@cocalc/sync/editor/generic/types" ; import { once } from "@cocalc/util/async-utils" ; import { filename_extension } from "@cocalc/util/misc" ; import { jupyter_backend } from "../jupyter/jupyter" ; const COCALC_EPHEMERAL_STATE = process . env . COCALC_EPHEMERAL_STATE === "yes" ; const syncdocs = { } ; export function init_syncdoc ( client , synctable , logger ) { if ( synctable . get_table ( ) !== "syncstrings" ) { throw Error ( "table must be 'syncstrings'" ) ; } if ( synctable . get_state ( ) == "closed" ) { throw Error ( "synctable must not be closed" ) ; } init_syncdoc_async ( client , synctable , logger ) ; } export function get_syncdoc ( path ) { return syncdocs [ path ] ; } async function close_syncdoc ( path ) { const doc = get_syncdoc ( path ) ; if ( doc == null ) return ; delete syncdocs [ path ] ; await doc . close ( ) ; } async function init_syncdoc_async ( client , synctable , logger ) { function log ( ... args ) { logger . debug ( "init_syncdoc -- " , ... args ) ; } log ( "waiting until synctable is ready" ) ; await wait_until_synctable_ready ( synctable , log ) ; log ( "synctable ready.  Now getting type and opts" ) ; const { type , opts } = get_type_and_opts ( synctable ) ; opts . project_id = client . client_id ( ) ; log ( "type = " , type ) ; log ( "opts = " , JSON . stringify ( opts ) ) ; opts . client = client ; log ( "now creating syncdoc..." ) ; let syncdoc ; try { syncdoc = create_syncdoc ( type , opts ) ; syncdocs [ opts . path ] = syncdoc ; } catch ( err ) { log ( `ERROR creating syncdoc -- ${ err . toString ( ) }` , err . stack ) ; return ; } synctable . on ( "closed" , function ( ) { log ( "syncstring table closed, so closing syncdoc" , opts . path ) ; close_syncdoc ( opts . path ) ; } ) ; syncdoc . on ( "error" , function ( err ) { log ( `syncdoc error -- ${ err }` ) ; close_syncdoc ( opts . path ) ; } ) ; const ext = filename_extension ( opts . path ) ; log ( "ext = " , ext ) ; switch ( ext ) { case "sage-jupyter2" : log ( "activating jupyter backend" ) ; jupyter_backend ( syncdoc , client ) ; break ; } } async function wait_until_synctable_ready ( synctable , log ) { if ( synctable . get_state ( ) == "disconnected" ) { log ( "wait for synctable be connected" ) ; await once ( synctable , "connected" ) ; } const t = synctable . get_one ( ) ; if ( t != null ) { log ( "currently" , t . toJS ( ) ) ; } log ( "wait for document info to get loaded into synctable..." ) ; function is_ready ( ) { const t = synctable . get_one ( ) ; if ( t == null ) { log ( "is_ready: table is null still" ) ; return false ; } else { log ( "is_ready" , JSON . stringify ( t ) ) ; return t . has ( "path" ) ; } } await synctable . wait ( is_ready , 0 ) ; log ( "document info is now in synctable" ) ; } function get_type_and_opts ( synctable ) { const s = synctable . get_one ( ) ; if ( s == null ) { throw Error ( "synctable must not be empty" ) ; } const path = s . get ( "path" ) ; if ( typeof path != "string" ) { throw Error ( "path must be a string" ) ; } const opts = { path , ephemeral : COCALC_EPHEMERAL_STATE } ; let type = "" ; let doctype = s . get ( "doctype" ) ; if ( doctype != null ) { try { doctype = JSON . parse ( doctype ) ; } catch { doctype = { } ; } if ( doctype . opts != null ) { for ( const k in doctype . opts ) { opts [ k ] = doctype . opts [ k ] ; } } type = doctype . type ; } if ( type !== "db" && type !== "string" ) { type = "string" ; } return { type , opts } ; } function create_syncdoc ( type , opts ) { switch ( type ) { case "string" : return new SyncString ( opts ) ; case "db" : return new SyncDB ( opts ) ; default : throw Error ( `unknown syncdoc type ${ type }` ) ; } } export async function syncdoc_call ( path , logger , mesg ) { logger . debug ( "syncdoc_call" , path , mesg ) ; const doc = get_syncdoc ( path ) ; if ( doc == null ) { logger . debug ( "syncdoc_call -- not open: " , path ) ; return "not open" ; } switch ( mesg . cmd ) { case "close" : logger . debug ( "syncdoc_call -- now closing: " , path ) ; await close_syncdoc ( path ) ; logger . debug ( "syncdoc_call -- closed: " , path ) ; return "successfully closed" ; default : throw Error ( `unknown command ${ mesg . cmd }` ) ; } } export async function close_all_syncdocs_in_tree ( filename ) { for ( const path in syncdocs ) { if ( path == filename || path . indexOf ( filename + "/" ) != - 1 ) { await close_syncdoc ( path ) ; } } }