import { dialog , MessageBoxReturnValue , app , Event } from 'electron' ; import { CommonInterfaces } from './interfaces/interface.common' ; import { IApplication , EExitCodes } from './interfaces/interface.app' ; import * as FS from './tools/fs' ; import Logger from './tools/env.logger' ; import LogsService from './tools/env.logger.service' ; import ServiceElectron from './services/service.electron' ; import ServiceNotifications from './services/service.notifications' ; import ServicePackage from './services/service.package' ; import ServiceEnv from './services/service.env' ; import ServiceHotkeys from './services/service.hotkeys' ; import ServicePaths , { getHomeFolder } from './services/service.paths' ; import ServiceUserPaths from './services/service.paths.user' ; import ServiceNetwork from './services/service.network' ; import ServicePlugins from './services/service.plugins' ; import ServiceStreams from './services/service.streams' ; import ServiceSettings from './services/service.settings' ; import ServiceConfigDefault from './services/service.settings.default' ; import ServiceStorage from './services/service.storage' ; import ServiceWindowState from './services/service.window.state' ; import ServiceElectronState from './services/service.electron.state' ; import ServiceProduction from './services/service.production' ; import ServiceFileInfo from './services/files/service.file.info' ; import ServiceMergeFiles from './services/features/service.merge.files' ; import ServiceConcatFiles from './services/features/service.concat.files' ; import ServiceTimestamp from './services/features/service.timestamp' ; import ServiceFileReader from './services/files/service.file.reader' ; import ServiceFileSearch from './services/files/service.file.search' ; import ServiceFileOpener from './services/files/service.file.opener' ; import ServiceFilePicker from './services/files/service.file.picker' ; import ServiceFileRecent from './services/files/service.file.recent' ; import ServiceFileWriter from './services/files/service.file.writer' ; import ServiceStreamSources from './services/service.stream.sources' ; import ServiceFilters from './services/service.filters' ; import ServiceAppState from './services/service.app.state' ; import ServiceUpdate from './services/service.update' ; import ServiceDLTFiles from './services/parsers/service.dlt.files' ; import ServicePatchesBefore from './services/service.patches.before' ; import ServiceDLTDeamonConnector from './services/connectors/service.dlt.deamon' ; import ServiceOutputExport from './services/output/service.output.export' ; import ServiceRenderState from './services/service.render.state' ; import ServiceLogs from './services/service.logs' ; import ServiceLogsExtractor from './services/service.logs.extractor' ; import ServiceReleaseNotes from './services/service.release.notes' ; import ServiceCLI from './services/service.cli' ; import ServiceTimestampFormatRecent from './services/features/service.timestamp.recent' ; import ServiceImporter from './services/service.importer' ; import { IService } from './interfaces/interface.service' ; type THook = ( ) => Promise < void > ; interface IHook { name : string ; fn : THook ; } enum EAppState { initing = 'initing' , working = 'working' , destroying = 'destroying' } export class CloseProcessRunning extends Error { constructor ( ) { super ( ) ; } } const InitializeStages = [ [ ServicePatchesBefore ] , [ ServiceEnv ] , [ ServiceProduction ] , [ ServicePaths ] , [ ServicePackage ] , [ ServiceSettings , ServiceWindowState , ServiceStorage ] , [ ServiceConfigDefault ] , [ ServiceUserPaths , ServiceNetwork , ServiceElectron ] , [ ServiceLogs ] , [ ServiceElectronState , ServiceNotifications , ServiceRenderState ] , [ ServiceStreamSources , ServiceStreams ] , [ ServiceFileInfo , ServiceMergeFiles , ServiceConcatFiles , ServiceFileSearch , ServiceFilters , ServiceFileReader , ServiceFileOpener , ServiceAppState , ServiceDLTFiles , ServiceHotkeys , ServiceFilePicker , ServiceDLTDeamonConnector , ServiceOutputExport , ServiceLogsExtractor , ServiceFileRecent , ServiceTimestamp , ServiceFileWriter , ServiceTimestampFormatRecent , ServiceImporter , ] , [ ServicePlugins , ServiceReleaseNotes , ServiceCLI ] , [ ServiceUpdate ] , ] ; class Application implements IApplication { private _logger : Logger = new Logger ( 'Application' ) ; private _state : EAppState = EAppState . initing ; private _code : EExitCodes = EExitCodes . normal ; returns public init ( ) { return new Promise ( ( resolve , reject ) => { this . _initGlobalNamespace ( ) ; this . _bindProcessEvents ( ) ; this . _init ( 0 , ( error ? ) => { if ( error instanceof Error ) { const dialogOpts = { type : 'info' , buttons : [ 'Drop settings and close' , 'Close' ] , title : 'Error' , message : `Sorry, it looks like we have a problems with starting. You can try to drop settings and start it again.` , detail : `Error: ${ error . message }` , } ; dialog . showMessageBox ( dialogOpts ) . then ( ( response ) => { this . _logger . debug ( `Selected option: ${ response }` ) ; switch ( response . response ) { case 0 : FS . rmdir ( getHomeFolder ( ) ) . then ( ( ) => { app . quit ( ) ; } ) . catch ( ( errorRmdir ) => { this . _logger . error ( `Fail to drop settings due error: ${ errorRmdir . message }` ) ; app . quit ( ) ; } ) ; break ; case 1 : app . quit ( ) ; break ; } } ) ; return reject ( error ) ; } const hooks = [ ] ; InitializeStages . forEach ( ( services ) => { services . forEach ( ( service ) => { if ( typeof service . afterAppInit === 'function' ) { hooks . push ( { name : service . getName ( ) , fn : service . afterAppInit } ) ; } } ) ; } ) ; Promise . all ( hooks . map ( ( info ) => { return info . fn . bind ( this ) ; } ) ) . then ( ( ) => { resolve ( this ) ; } ) . catch ( ( err ) => { this . _logger . error ( `Services failed to be initialized on afterAppInit due to error: ${ err . message }` ) ; } ) ; resolve ( this ) ; } ) ; } ) ; } public destroy ( code = EExitCodes . normal ) { return new Promise ( ( resolve , reject ) => { if ( code !== EExitCodes . normal ) { this . _code = code ; } if ( this . _state === EAppState . destroying ) { return reject ( new CloseProcessRunning ( ) ) ; } this . _state = EAppState . destroying ; ServiceElectron . lock ( ) ; ServiceElectron . closeWindow ( ) . then ( ( ) => { this . _logger . debug ( `Browser window is closed` ) ; } ) . catch ( ( closeWinErr ) => { this . _logger . warn ( `Fail to close browser window before close due error: ${ closeWinErr . message }` ) ; } ) . finally ( ( ) => { ServiceStreams . closeAll ( ) . then ( ( ) => { this . _logger . debug ( `All streams are closed` ) ; } ) . catch ( ( closeErr ) => { this . _logger . warn ( `Fail to close all session before close due error: ${ closeErr . message }` ) ; } ) . finally ( ( ) => { ServicePlugins . accomplish ( ) . then ( ( ) => { this . _logger . debug ( `All plugins actions are accomplish` ) ; } ) . catch ( ( shutdownErr ) => { this . _logger . warn ( `Fail to accomplish plugins actions due error: ${ shutdownErr . message }` ) ; } ) . finally ( ( ) => { this . _destroy ( InitializeStages . length - 1 , ( destroyErr ? ) => { if ( destroyErr instanceof Error ) { console . log ( `Fail destroy due error: ${ destroyErr . message }` ) ; } this . _quit ( ) . catch ( ( quitErr ) => { console . log ( `Fail quit due error: ${ quitErr . message }` ) ; } ) . finally ( ( ) => { resolve ( ) ; } ) ; } ) ; } ) ; } ) ; } ) ; } ) ; } public getLogger ( ) { return this . _logger ; } private _init ( stage = 0 , callback ) { if ( InitializeStages . length <= stage ) { this . _logger . debug ( `Application is initialized` ) ; this . _state = EAppState . working ; if ( typeof callback === 'function' ) { callback ( ) ; } return ; } this . _logger . debug ( `Application initialization: stage #${ stage + 1 }: starting...` ) ; const services = InitializeStages [ stage ] ; const tasks = services . map ( ( ref ) => { this . _logger . debug ( `Init: ${ ref . getName ( ) }` ) ; return ref . init ( this ) . catch ( ( err ) => { this . _logger . error ( `${ ref . getName ( ) }: ${ err . message }` ) ; return Promise . reject ( err ) ; } ) ; } ) ; if ( tasks . length === 0 ) { return this . _init ( stage + 1 , callback ) ; } Promise . all ( tasks ) . then ( ( ) => { this . _logger . debug ( `Application initialization: stage #${ stage + 1 }: OK` ) ; this . _init ( stage + 1 , callback ) ; } ) . catch ( ( error ) => { this . _logger . debug ( `Fail to initialize application dure error: ${ error . message }` ) ; callback ( error ) ; } ) ; } private _destroy ( stage = 0 , callback ) { if ( stage < 0 ) { this . _logger . debug ( `Application is destroyed` ) ; if ( typeof callback === 'function' ) { callback ( ) ; } return ; } this . _logger . debug ( `Application destroy: stage #${ stage + 1 }: starting...` ) ; const services = InitializeStages [ stage ] ; const tasks = services . map ( ( ref ) => { this . _logger . debug ( `Destroy: ${ ref . getName ( ) }: started...` ) ; return ref . destroy ( ) . then ( ( ) => { this . _logger . debug ( `Destroy: ${ ref . getName ( ) }: DONE` ) ; } ) . catch ( ( err ) => { this . _logger . error ( `Destroy: ${ ref . getName ( ) }: FAILED due: ${ err . message }` ) ; } ) ; } ) ; if ( tasks . length === 0 ) { return this . _destroy ( stage - 1 , callback ) ; } Promise . all ( tasks ) . then ( ( ) => { this . _logger . debug ( `Application destroyed: stage #${ stage + 1 }: OK` ) ; this . _destroy ( stage - 1 , callback ) ; } ) . catch ( ( error ) => { this . _logger . debug ( `Fail to destroy application dure error: ${ error . message }` ) ; callback ( error ) ; } ) ; } private _bindProcessEvents ( ) { process . once ( 'exit' , this . _onClose . bind ( this ) ) ; process . once ( 'SIGINT' , this . _onClose . bind ( this ) ) ; process . once ( 'SIGTERM' , this . _onClose . bind ( this ) ) ; app . once ( 'will-quit' , ( event ) => { event . preventDefault ( ) ; this . _onClose ( ) ; } ) ; process . on ( 'uncaughtException' , this . _onUncaughtException . bind ( this ) ) ; process . on ( 'unhandledRejection' , this . _onUnhandledRejection . bind ( this ) ) ; } private _onUnhandledRejection ( reason , promise ) { if ( reason instanceof Error ) { this . _logger . error ( `[BAD] UnhandledRejection: ${ reason . message }` ) ; } else { this . _logger . error ( `[BAD] UnhandledRejection happened. No reason as error was provided.` ) ; } } private _onUncaughtException ( error ) { this . _logger . error ( `[BAD] UncaughtException: ${ error . message }` ) ; } private _onClose ( ) { this . _logger . debug ( `Application would be closed.` ) ; process . stdin . resume ( ) ; this . destroy ( ) . catch ( ( error ) => { if ( error instanceof CloseProcessRunning ) { return ; } this . _logger . warn ( `Fail correctly close app due error: ${ error . message }` ) ; } ) ; } private _quit ( ) { return new Promise ( ( resolve , reject ) => { this . _logger . debug ( `Application are ready to be closed with code "${ this . _code }".` ) ; this . _logger . debug ( `LogsService will be shutdown.` ) ; LogsService . shutdown ( ) . then ( ( ) => { resolve ( ) ; process . exit ( this . _code ) ; } ) . catch ( ( error ) => { console . log ( `Fail shutdown logservice due error: ${ error . message }` ) ; reject ( error ) ; app . exit ( this . _code ) ; } ) ; } ) ; } private _initGlobalNamespace ( ) { const gLogger = new Logger ( 'Global' ) ; const cGlobal = { logger : { warn : gLogger . warn . bind ( gLogger ) , debug : gLogger . debug . bind ( gLogger ) , env : gLogger . env . bind ( gLogger ) , error : gLogger . error . bind ( gLogger ) , info : gLogger . info . bind ( gLogger ) , verbose : gLogger . verbose . bind ( gLogger ) , wtf : gLogger . wtf . bind ( gLogger ) , } , } ; ( global as any ) . chipmunk = cGlobal ; } } ( new Application ( ) ) . init ( ) . then ( ( application ) => { application . getLogger ( ) . debug ( `Application is ready.` ) ; ServiceSettings . subscribe ( ) . then ( ( ) => { ServiceElectronState . setStateAsReady ( ) ; ServicePlugins . revision ( ) ; } ) . catch ( ( settingsErr ) => { application . getLogger ( ) . error ( `Fail to subscribe settings service due error: ${ settingsErr . message }` ) ; } ) ; } ) . catch ( ( error ) => { console . log ( error ) ; } ) ;