Files
Unify/framework/server/hotSwapping.js
2025-12-25 11:16:59 +01:00

829 lines
14 KiB
JavaScript

/*
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 );
}
}