import * as Types from '@meetalva/types' ; import * as Model from '@meetalva/model' ; import * as Path from 'path' ; import { Persistence } from '../persistence' ; import * as Fs from 'fs' ; import { sortBy } from 'lodash' ; import * as Mobx from 'mobx' ; import { Message } from '@meetalva/message' ; const pathIsInside = require ( 'path-is-inside' ) ; export class LocalDataHost implements Types . DataHost < Model . Project > { private host : Types . Host < Model . AlvaApp < Message > , Model . Project , Message > ; private cache : Map < string , Model . Project > = new Map ( ) ; @ Mobx . observable private projects : Types . ProjectRecord [ ] | undefined ; private constructor ( { host } ) { this . host = host ; } public static async fromHost ( host ) { return new LocalDataHost ( { host } ) ; } private async readMemory ( ) { const path = await this . host . resolveFrom ( Types . HostBase . UserData , 'projects.json' ) ; const file = await this . host . readFile ( path ) . catch ( ( ) => undefined ) ; return Promise . resolve ( ) . then ( ( ) => ( file ? JSON . parse ( file . contents ) : { projects : { } , connections : { } } ) ) . catch ( ( ) => ( { projects : { } , connections : { } } ) ) ; } private async writeMemory ( memory ) { const path = await this . host . resolveFrom ( Types . HostBase . UserData , 'projects.json' ) ; const parent = Path . dirname ( path ) ; if ( ! await this . host . exists ( parent ) ) { await this . host . mkdir ( parent ) ; } return this . host . writeFile ( path , JSON . stringify ( memory ) ) ; } public async setUpdate ( update ) { const memory = await this . readMemory ( ) ; memory . update = update ; await this . writeMemory ( memory ) ; } public async removeUpdate ( ) { const memory = await this . readMemory ( ) ; memory . update = undefined ; await this . writeMemory ( memory ) ; } public async getUpdate ( ) { const memory = await this . readMemory ( ) ; return memory . update ; } public async addProject ( project ) { const memory = await this . readMemory ( ) ; memory . projects [ project . getId ( ) ] = project . getPath ( ) ; await this . writeMemory ( memory ) ; this . cache . set ( project . getId ( ) , project ) ; } public async getProject ( id ) { const memory = await this . readMemory ( ) ; const projectPath = memory . projects [ id ] ; if ( ! projectPath ) { return ; } if ( this . cache . has ( id ) ) { return this . cache . get ( id ) ; } const file = await this . host . readFile ( projectPath ) ; const parsed = await Persistence . parse < Types . SerializedProject > ( file . contents ) ; if ( parsed . state === Types . PersistenceState . Error ) { return ; } const project = Model . Project . from ( parsed . contents ) ; this . cache . set ( project . getId ( ) , project ) ; return project ; } @ Mobx . action public async checkProjects ( ) { const memory = await this . readMemory ( ) ; const tempPath = await this . host . resolveFrom ( Types . HostBase . AppData , '.' ) ; const userHome = await this . host . resolveFrom ( Types . HostBase . UserHome , '.' ) ; const projects = await Promise . all ( Object . entries ( memory . projects ) . map ( async ( [ id , path ] ) => { const valid = await this . host . access ( path , Fs . constants . R_OK ) ; const editDate = valid ? ( await this . host . stat ( path ) ) . mtimeMs : undefined ; const draft = this . host . type === Types . HostType . Electron ? pathIsInside ( path , tempPath ) : true ; const displayPath = pathIsInside ( path , userHome ) ? `~${ Path . sep }${ Path . dirname ( Path . relative ( userHome , path ) ) }` : Path . dirname ( path ) ; return { draft , editDate , id , path , displayPath , name : draft ? 'Draft' : Path . basename ( path , Path . extname ( path ) ) , valid } ; } ) ) ; const sortedProjects = sortBy ( projects , [ 'editDate' , 'name' ] ) . reverse ( ) ; this . projects = sortedProjects ; return sortedProjects ; } public async getProjects ( ) { if ( ! this . projects ) { return this . checkProjects ( ) ; } return this . projects ; } public async addConnection ( project , item ) { const memory = await this . readMemory ( ) ; const previous = memory . connections [ ( project as any ) . id ] || [ ] ; memory . connections [ ( project as any ) . id ] = [ ... previous , item ] ; await this . writeMemory ( memory ) ; } public async getConnections ( project ) { const memory = await this . readMemory ( ) ; return memory . connections [ ( project as any ) . id ] || [ ] ; } }