/* 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, https://unifyjs.org */ import { WebSocketServer } from 'ws'; import http from 'http'; import https from 'https'; import fs from 'fs'; import objectManager from '../server/objectManager.js'; import Console from '../server/console.js'; import webConsole from '../unify/console.js'; import socketMessage from '../unify/socketMessage.js'; import querySQL from '../unify/querySQL.js'; import database from '../server/database.js'; import sqlite from '../server/sqlite.js'; import path from 'path'; import sqlite3 from 'better-sqlite3'; database.createTable( "sync_server" ); database.addColumn( "original_id", "INTEGER", "sync_server" ); database.addColumn( "queryString", "TEXT", "sync_server" ); database.addColumn( "columnValues", "TEXT", "sync_server" ); database.addColumn( "operation", "TEXT", "sync_server" ); database.addColumn( "clusterClientID", "INTEGER", "sync_server" ); database.createTable( "sync_client" ); database.addColumn( "original_id", "INTEGER", "sync_client" ); database.addColumn( "server_id", "INTEGER", "sync_client" ); database.addColumn( "queryString", "TEXT", "sync_client" ); database.addColumn( "columnValues", "TEXT", "sync_client" ); database.addColumn( "operation", "TEXT", "sync_client" ); database.addColumn( "clusterClientID", "INTEGER", "sync_client" ); database.addColumn( "synced", "BOOLEAN", "sync_client" ); class socketClient{ socket; userID = 0; async createSocket( ) { this.createEventListeners(); var messageID = 0; var message = new socketMessage(); message.id = messageID; message.name = "connected"; message.type = "promise"; message.clientID = this.userID; this.send( message ); } destroy() { console.log( "client disconnected ", this.userID ); this.socketManager.removeClient( this.userID ); } createEventListeners() { var client = this; this.socket.on('message', async function( json ){ await client.message( json, client ) }); this.socket.on('close', function( json ){ client.destroy(); }); } send( socketMessage ) { this.socket.send( JSON.stringify( socketMessage ) ); } getRowsAfterDatetime( datetime, tablename ) { var query = new querySQL( ); query.type = "select"; query.table = "sync_server"; query.order = ["datetime"]; query.find( "table_name", tablename ); query.find( "datetime", datetime, ">" ); var result = database.query( query ); } getRowsFromIDTilNow( fromID, excludeClusterClientID ) { //console.log( "Get rows id > ", fromID, " and ClusterClientID != ", excludeClusterClientID ); var query = new querySQL( ); query.type = "select"; query.table = "sync_server"; //query.order = ["id"]; query.find( "id", fromID, ">" ); query.find( "clusterClientID", excludeClusterClientID, "!=" ); return database.query( query ); } async message( json ) { var object = JSON.parse( json ); switch( object.type ) { case "add_sqlite_operation": var unsynced = object.unsynced; if(unsynced.length > 0) { console.log("recieved unsynced", unsynced.length); console.log("add row with object.clusterClientID: ", object.clusterClientID); } // todo : feature - implement insertMany - better-sqlite3 make sql query static // So database doesnt have to generate query for every row. // Better is to make an database.js cache that does this for all query's in unify // that are alike so everything in unify will get an Significant speedup for (var i = 0; i < unsynced.length; i++) { var newRow = unsynced[i]; var query = new querySQL( ); query.type = "insert"; query.table = "sync_server"; query.setValue( "original_id", newRow.original_id ); query.setValue( "queryString", newRow.queryString ); query.setValue( "columnValues", newRow.columnValues ); query.setValue( "operation", newRow.operation ); query.setValue( "clusterClientID", newRow.clusterClientID ); var result = database.query( query ); /* // perform real query here var columnArray = JSON.parse( newRow.columnValues ); var query = new querySQL( ); query.type = newRow.operation; for (var a = 0; a < columnArray.length; a++) { var column = new Array(); column.name = false; column.value = columnArray[a]; query.values.push( column ); } query.sync = false; database.executeQuery( newRow.queryString, query ); // --- var query = new querySQL( ); query.type = "insert"; query.table = "sync_client"; query.setValue( "original_id", newRow.original_id ); query.setValue( "queryString", newRow.queryString ); query.setValue( "columnValues", newRow.columnValues ); query.setValue( "operation", newRow.operation ); query.setValue( "clusterClientID", 555 ); // prevent loading when new db is apperent query.setValue( "server_id", newRow.id ); query.setValue( "synced", true ); query.sync = false; var result = database.query( query ); */ } var message = new socketMessage(); message.name = "updateSyncClient"; message.clientID = this.userID; message.rows = this.getRowsFromIDTilNow( object.lastID, object.clusterClientID ); //console.log("get all rows not of clusterID: ", object.clusterClientID); //message.rows = []; message.synced = unsynced; //console.log("rows found", message.rows.length); this.send( message ); break; case "download_database": console.log("download_database"); var message = new socketMessage(); message.name = "upload_database"; message.binary = fs.readFileSync("./framework/db/syncServer.sqlite") this.send( message ); //console.log(databaseBinary); break; } } } class socketManager{ socket; core; userID = 0; port = 4422; host = "localhost"; clients = new Array(); constructor( port ){ if( port ) { this.port = port; //Console.log("start server at port:", port ); } global.socketManager = this; } emptyTable() { database.database.prepare( "DELETE from sync_server" ).run() } create() { console.log("create socket server after this"); if( !global.ssl ) { this.socket = new WebSocketServer({ port: this.port }); console.log("socket server started on port: " , this.port ); } else { this.socket = new WebSocketServer({ server: this.httpsServer }); console.log("started Secure socket server ", this.port); } this.createEventListeners(); } createEventListeners() { var socketManager = this; this.socket.on('connection', function( socket ){ socketManager.connection( socket ) }); } addClient( id, socket ) { var newClient = new socketClient( this.port ); newClient.userID = id; newClient.socket = socket; newClient.socketManager = this; newClient.createSocket(); this.clients.push( newClient ); } removeClient( userID ) { var clients = this.clients; //this.objectManager.removeObjectsByClientID( userID ); for(var c = 0; c { console.log(`Process ${process.pid} received a SIGTERM signal`); socketManagerServer.emptyTable(); process.exit(0); }); // catch ctrl-c, so that event 'exit' always works process.on("SIGINT", (signal) => { console.log(`Process ${process.pid} has been interrupted`); socketManagerServer.emptyTable(); process.exit(0); });