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

455 lines
8.2 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,
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<clients.length;c++ ) {
var client = clients[c];
if( client.userID == userID ) {
clients.splice( c, 1 );
}
}
}
connection( socket ) {
const userID = this.userID++;
this.addClient( userID, socket );
console.log( "client connected : ", userID );
console.log( "number connected clients ", this.clients.length );
}
}
var socketManagerServer = new socketManager();
socketManagerServer.create();
// just in case some user like using "kill"
process.on("SIGTERM", (signal) => {
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);
});