/* Copyright (c) 2020, 2023, The Unified Company. This code is part of Unify. This program is free software; you can redistribute it and/or modify it under the terms of the ESA Software Community License - Strong Copyleft LICENSE, as published by the ESA. See the ESA Software Community License - Strong Copyleft LICENSE, for more details. https://unifyjs.org */ import fs from 'fs'; import path from 'path'; import { spawn } from 'child_process'; import filemanager from './filemanager.js'; import unify from '../unify/unify.js'; import tools from '../unify/tools.js'; import colors from '../unify/consoleColors.js'; import groupsFlat from './themeGroups.js'; import cacheManager from "./scripts/cacheManager.js"; import { gpp } from '../node_modules/node-gpp/child.js'; import simplePath from '../unify/simplePath.js'; export default class hotSwap{ allPlatformFiles = new Array(); cache = new cacheManager(); gpp = new gpp(); constructor( clients ) { this.clients = clients; this.create(); this.createPlatformCachePath(); this.createGlobals(); } createGlobals() { if( !global.visibleElements ) { global.visibleElements = new Array(); } } createPlatformCachePath() { this.pathToPlatforms = new simplePath(); this.pathToPlatforms.beginWithSlash = false; this.pathToPlatforms.endWithSlash = true; this.pathToPlatforms.add( "framework" ) this.pathToPlatforms.add( "cache" ) this.pathToPlatforms.add( "platforms" ) } setSocketManager( socketManager ) { this.socketManager = socketManager; } createInheritenceCache() { } async spawnNodemonInstance() { var files = new filemanager( "./application/" ); await files.findApplicationFiles( "./application"); var directories = files.directories; var that = this; for (var i = 0; i < directories.length; i++) { var directory = directories[ i ]; var pathToFile = new simplePath(); pathToFile.beginWithSlash = false; pathToFile.endWithSlash = true; pathToFile.add( "application" ) pathToFile.add( directory ) let directoryPath = path.resolve( pathToFile.resolve() ); //console.log("Watch directory:", i, colors.green( directoryPath ) ); var callback = function( eventType, filename ) { var filePath = directoryPath + "/" + filename; console.log("file changed ", eventType, filePath ); that.message( eventType, filePath ); }; if ( fs.existsSync( directoryPath ) ) { fs.watch( directoryPath, callback ) } } } spawnNodemon() { this.createInheritenceCache(); try { this.spawnNodemonInstance(); } catch ( error ) { //console.error("Nodemon Crashed, Instantiating a new instance of nodemon..", error); this.spawnNodemonInstance(); } } create() { //if( global.clusterID == 1 ) { var app = this.spawnNodemon(); //} } async message( event, file ) { if( event == "change" ) { //var files = event.data; this.updateFiles( file ); } } getPlatformCachePath() { var cachePath = new simplePath(); cachePath.beginWithSlash = false; cachePath.add( "framework" ); cachePath.add( "cache" ); cachePath.add( "platforms" ); cachePath.add( global.os ); cachePath.add( global.device ); cachePath.add( global.tint ); return cachePath.resolve(); } getPathParts( file ) { var rootDir = tools.slash( path.resolve("./") ).replace("C:/", "c:/"); var fullPath = file.replace( rootDir, "" ); var pathParts = fullPath.split("/"); return pathParts; } async processSource( fileObject, filePath ) { var clientFiles = new filemanager(); clientFiles.cache_directory = this.getPlatformCachePath(); clientFiles.addCustomReplacements(); await clientFiles.processSource( fileObject, filePath, fileObject.depth - 2 ) } getRelativePath( pathParts ) { var relativeApplicationPath = pathParts.join("/").replace("/application/", ""); return tools.slash( relativeApplicationPath ); } async createFileObject( filePath ) { var slashedFilePath = tools.slash( filePath ); var pathParts = this.getPathParts( slashedFilePath ); var fileObject = new Object(); fileObject.name = pathParts.pop(); fileObject.depth = pathParts.length; fileObject.path = this.getRelativePath( pathParts ); fileObject.type = "file"; fileObject.files = new Array(); await this.processSource( fileObject, slashedFilePath ); return fileObject; } updateClientObjects( newInstance, clientObject ) { //console.log( "nodeMethods", clientObject ); var nodeMethods = newInstance.__nodeMethods.split(","); for ( var j = 0; j < nodeMethods.length; j++ ) { var nodeMethodName = nodeMethods[j]; clientObject[ nodeMethodName ] = newInstance[nodeMethodName]; } } updateMethodByClient( newInstance, client, className ) { var classObjects = client.classObjects; var clientObject = classObjects[ className ]; if( clientObject ) { this.updateClientObjects( newInstance, clientObject ); } } updateClients( newInstance, clients, className ) { //for ( var i = 0; i < clients.length; i++ ) { for ( const [ key, client ] of Object.entries( clients ) ) { //var client = clients[ i ]; this.updateMethodByClient( newInstance, client, className ); } } async updateMethods( newInstance, clients ) { var className = newInstance.getClassName(); if( global.mode == "development") { this.updateClients( newInstance, clients, className ); } } getAllExtendedObjects( extendObjects, pathsArray ) { //console.log("extendObjects", extendObjects, pathsArray); for ( var c = 0; c < extendObjects.length - 1; c++ ) { var objectName = extendObjects[c]; var extendedObject = global.classObjects[ objectName ] if( extendedObject ) { pathsArray.push( extendedObject.__sourcePath ); } else { console.error( "Class not found in core.classObjects.", objectName ); } } } getExtendedDependencyPathsOfObject( object, pathsArray ) { //var visible = global.visibleElements.includes( object.getClassName() ); //if( visible ) { pathsArray.push( object.__sourcePath ); var extendObjects = object.getExtends(); extendObjects = tools.removeDuplicates( extendObjects ); this.getAllExtendedObjects( extendObjects, pathsArray ); //console.log("extendObjects", extendObjects); // } } getExtendedDependencyPaths( objects ) { var pathsArray = new Array(); for ( var i = 0; i < objects.length; i++ ) { var object = objects[i]; //console.log("getExtendedDependencyPaths", object.__sourcePath ); this.getExtendedDependencyPathsOfObject( object, pathsArray ); } pathsArray = tools.removeDuplicates( pathsArray ); return pathsArray; } reloadPathCache( paths, group ) { for ( var c = 0; c < paths.length; c++ ) { var cachePath = this.pathToPlatforms.clone(); cachePath.beginWithSlash = true; cachePath.add( "Original" ); cachePath.add( group.path ); cachePath.add( paths[ c ] ); this.allPlatformFiles.push( cachePath.resolve() ); } } async reloadAllThemeCache( groups, paths ) { for ( var i = 0; i < groups.length; i++ ) { var group = groups[i]; this.reloadPathCache( paths, group ); await this.renewPlatform( group.name, group.path, paths ); } } createApplicationPath( path, filename ) { var fullPath = this.pathToPlatforms.clone(); fullPath.absolute = true; fullPath.add( global.os ) fullPath.add( global.device ) fullPath.add( global.tint ) fullPath.add( path ) fullPath.add( filename + "?disableCache=" + Math.random() ) return fullPath.resolve(); } async processDependencies( fileObject, groupsFlatSelection ) { var path = this.createApplicationPath( fileObject.path, fileObject.name ); var importPromise = import( "file://" + path ); var that = this; importPromise.catch( error => { console.log("Error loading dynamic import. Your code contains an error.", error) } ).then( async function( module ) { if( !module ) { return false; } var newInstance = new module.default(); await that.updateInstance( newInstance, groupsFlatSelection ); }); } async prepareInstance( newInstance ) { if( !newInstance ) { return false; } unify.extend( newInstance ); if( !newInstance.getClassName ) { return false; } await this.updateMethods( newInstance, this.clients ); return newInstance; } getDependencieObjects( newInstance ) { var className = newInstance.getClassName(); //console.log("dependencieMap", className, Object.keys( global.dependencieMap ) ) ; var dependencyObjects = global.dependencieMap[ className ]; if( dependencyObjects ) { return dependencyObjects; } else { return new Array(); } } async updateInstance( newInstance, groupsFlatSelection ) { var newInstance = await this.prepareInstance( newInstance ); if( !newInstance ) { return false; } var dependencyObjects = this.getDependencieObjects( newInstance ) var pathsArray = this.getExtendedDependencyPaths( dependencyObjects ); await this.reloadAllThemeCache( groupsFlatSelection, pathsArray ) } getAbsolutePathByFile( file, customPath ) { var absolutePath = this.pathToPlatforms.clone(); absolutePath.beginWithSlash = true; absolutePath.add( "Original" ); if( customPath ) { absolutePath.add( customPath ); } else { absolutePath.add( "Server" ); } absolutePath.add( file.path ); absolutePath.add( file.name ); return absolutePath.resolve(); } addPlatformPaths( files, customPath = false ) { for ( var i = 0; i < files.length; i++ ) { var file = files[i]; var absolutePath = this.getAbsolutePathByFile( file, customPath ); if( file.type == "file" ) { this.allPlatformFiles.push( absolutePath ); } } } async processServerFiles( filesArray ) { var cachePath = tools.slash( "framework/cache/platforms/Original/Server/" ); var server_files = new filemanager( cachePath ); server_files.definePragma("SERVER"); server_files.definePragma("server_cache"); server_files.addCustomReplacements(); server_files.files = filesArray; this.addPlatformPaths( filesArray ); await server_files.writeFiles(); return true; } async updateConfig() { var cachePath = tools.slash( "framework/server_cache/" ); var server_files = new filemanager( cachePath ); await server_files.findApplicationFiles( "./application"); var applications = server_files.applications; var application = await this.cache.importApplication( applications[0].path ); this.cache.getApplicationProperties( application ) await this.cache.writeApplicationPropertiesToFile(); } createGroupPath() { var groupPath = new simplePath(); groupPath.endWithSlash = false; groupPath.add( global.os ) groupPath.add( global.device ) groupPath.add( global.tint ) return groupPath.resolve(); } createGroup() { var groups = new Array( groupsFlat[5] ); //group.path = this.createGroupPath(); groups[0].path = this.createGroupPath(); return groups; } async processThemeGroups( groups, filesArray ) { for ( var i = 0; i < groups.length; i++ ) { var group = groups[i]; await this.processPlatform( group.name, group.path, filesArray );//filesArray ); } } async writeCachePathsToJSON() { var jsonFiles = JSON.stringify( this.allPlatformFiles ); var path = tools.slash("framework/cache/platforms/files.json"); await fs.writeFileSync( path, jsonFiles, "utf8" ); } async updateFile( file ) { var fileObject = await this.createFileObject( file ) ; var themeGroups = this.createGroup(); var filesArray = new Array( fileObject ); await this.processThemeGroups( themeGroups, filesArray ); await this.processServerFiles( filesArray ); await this.updateConfig(); await this.writeCachePathsToJSON(); await this.gpp.convert_files(); //console.log("themeGroups", themeGroups); await this.processDependencies( fileObject, themeGroups ); console.log( colors.green( "Updated file:" ), colors.green( file ) ); this.sendFileToClient( fileObject ); } async updateFiles( file ) { //for (var i = 0; i < files.length; i++) { // var file = files[i]; await this.updateFile( file ); //} } sendFileToClient( fileObject ) { var clients = this.clients; var parameters = new Object(); parameters.file = fileObject; parameters.action = "updateFile"; console.log( "send updated file to client", clients.length ); for ( const [ key, client ] of Object.entries( clients ) ) { client.debugMessage( parameters ) } } reloadPathCache( paths, group ) { for ( var c = 0; c < paths.length; c++ ) { var cachePath = this.pathToPlatforms.clone(); cachePath.beginWithSlash = true; cachePath.add( "Original" ); cachePath.add( group.path ); cachePath.add( paths[ c ] ); this.allPlatformFiles.push( cachePath.resolve() ); } } definePragmas( files, path ) { var terms = path.split("/"); terms.shift(); for ( var i = 0; i < terms.length; i++ ) { files.definePragma( terms[i] ); } files.definePragma("CLIENT"); } getThemePath( path, isOriginal = false ) { var fullPath = this.pathToPlatforms.clone(); if( isOriginal ) { fullPath.add( "Original" ); } fullPath.add( path ); return fullPath.resolve(); } async processPlatform( name, path, filesArray ) { var themePath = this.getThemePath( path ); var files = new filemanager( themePath ); files.addCustomReplacements(); this.definePragmas( files, path ); files.files = filesArray; files.temporary_cache_directory = this.getThemePath( path, true ); await files.writeFiles("client"); this.addPlatformPaths( files.files, path ) } async renewPlatform( name, path, filesArray ) { var themePath = this.getThemePath( path, true ); //console.log("getThemePath", path); var files = new filemanager( themePath ); await files.renewCache( path, filesArray ); } }