codekingpro/portable-devtools
114k
1/////////////////////////////////////////////////////////////2//3// pgAdmin 4 - PostgreSQL Tools4//5// Copyright (C) 2013 - 2024, The pgAdmin Development Team6// This software is released under the PostgreSQL Licence7//8//////////////////////////////////////////////////////////////9 10const fs = require('fs');11const path = require('path');12const net = require('net');13const {platform, homedir} = require('os');14let pgadminServerProcess = null;15let pgAdminWindowObject = null;16let zoomInShortcut = null;17let zoomOutShortcut = null;18let actualSizeShortcut = null;19let toggleFullScreenShortcut = null;20 21// This function is used to check whether directory is present or not22// if not present then create it recursively23const createDir = (dirName) => {24 if (!fs.existsSync(dirName)) {25 fs.mkdirSync(dirName, {recursive: true});26 }27};28 29// This function is used to get the python executable path30// based on the platform. Use this for deployment.31const getPythonPath = () => {32 let pythonPath;33 switch (platform()) {34 case 'win32':35 pythonPath = '../python/python.exe';36 break;37 case 'darwin':38 pythonPath = '../../Frameworks/Python.framework/Versions/Current/bin/python3';39 break;40 case 'linux':41 pythonPath = '../venv/bin/python3';42 break;43 default:44 if (platform().startsWith('win')) {45 pythonPath = '../python/python.exe';46 } else {47 pythonPath = '../venv/bin/python3';48 }49 }50 51 return pythonPath;52};53 54// This function is used to get the [roaming] app data path55// based on the platform. Use this for config etc.56const getAppDataPath = () => {57 let appDataPath;58 switch (platform()) {59 case 'win32':60 appDataPath = path.join(process.env.APPDATA, 'pgadmin');61 break;62 case 'darwin':63 appDataPath = path.join(homedir(), 'Library', 'Preferences', 'pgadmin');64 break;65 case 'linux':66 if ('XDG_CONFIG_HOME' in process.env) {67 appDataPath = path.join(process.env.XDG_CONFIG_HOME, 'pgadmin');68 } else {69 appDataPath = path.join(homedir(), '.config', 'pgadmin');70 }71 break;72 default:73 if (platform().startsWith('win')) {74 appDataPath = path.join(process.env.APPDATA, 'pgadmin');75 } else if ('XDG_CONFIG_HOME' in process.env) {76 appDataPath = path.join(process.env.XDG_CONFIG_HOME, 'pgadmin');77 } else {78 appDataPath = path.join(homedir(), '.config', 'pgadmin');79 }80 }81 82 // Create directory if not exists83 createDir(appDataPath);84 85 return appDataPath;86};87 88// This function is used to get the [local] app data path89// based on the platform. Use this for logs etc.90const getLocalAppDataPath = () => {91 let localAppDataPath;92 switch (platform()) {93 case 'win32':94 localAppDataPath = path.join(process.env.LOCALAPPDATA, 'pgadmin');95 break;96 case 'darwin':97 localAppDataPath = path.join(homedir(), 'Library', 'Application Support', 'pgadmin');98 break;99 case 'linux':100 if ('XDG_DATA_HOME' in process.env) {101 localAppDataPath = path.join(process.env.XDG_DATA_HOME, 'pgadmin');102 } else {103 localAppDataPath = path.join(homedir(), '.local', 'share', 'pgadmin');104 }105 break;106 default:107 if (platform().startsWith('win')) {108 localAppDataPath = path.join(process.env.LOCALAPPDATA, 'pgadmin');109 } else if ('XDG_DATA_HOME' in process.env) {110 localAppDataPath = path.join(process.env.XDG_DATA_HOME, 'pgadmin');111 } else {112 localAppDataPath = path.join(homedir(), '.local', 'share', 'pgadmin');113 }114 }115 116 // Create directory if not exists117 createDir(localAppDataPath);118 119 return localAppDataPath;120};121 122// This function is used to get the random available TCP port123// if fixedPort is set to 0. Else check whether port is in used or not.124const getAvailablePort = (fixedPort) => {125 return new Promise(function(resolve, reject) {126 const server = net.createServer();127 128 server.listen(fixedPort, '127.0.0.1');129 130 server.on('error', (e) => {131 reject(e.code);132 });133 134 server.on('listening', () => {135 let serverPort = server.address().port;136 server.close();137 resolve(serverPort);138 });139 });140};141 142// Get the app data folder path143const currentTime = (new Date()).getTime();144const serverLogFile = path.join(getLocalAppDataPath(), 'pgadmin4.' + currentTime.toString() + '.log');145const configFileName = path.join(getAppDataPath(), 'runtime_config.json');146const DEFAULT_CONFIG_DATA = {'fixedPort': false, 'portNo': 5050, 'connectionTimeout': 90, 'zoomLevel': 0, 'openDocsInBrowser': true};147 148// This function is used to read the file and return the content149const readServerLog = () => {150 let data = null;151 152 if (fs.existsSync(serverLogFile)) {153 data = fs.readFileSync(serverLogFile, 'utf8');154 } else {155 let errMsg = 'Unable to read file ' + serverLogFile + '.';156 console.warn(errMsg);157 return errMsg;158 }159 160 return data;161};162 163// This function is used to write the data into the log file164const writeServerLog = (data) => {165 data = data + '\n';166 if (fs.existsSync(serverLogFile)) {167 fs.writeFileSync(serverLogFile, data, {flag: 'a+'});168 } else {169 fs.writeFileSync(serverLogFile, data, {flag: 'w'});170 }171};172 173// This function is used to remove the log file174const removeLogFile = () => {175 if (fs.existsSync(serverLogFile)) {176 fs.rmSync(serverLogFile);177 }178};179 180// This function used to set the object of pgAdmin server process.181const setProcessObject = (processObject) => {182 pgadminServerProcess = processObject;183};184 185// This function used to set the object of pgAdmin window.186const setPgAdminWindowObject = (windowObject) => {187 pgAdminWindowObject = windowObject;188};189 190// This function is used to get the server log file.191const getServerLogFile = () => {192 return serverLogFile;193};194 195// This function is used to get the runtime config file.196const getRunTimeConfigFile = () => {197 return configFileName;198};199 200// This function is used to kill the server process, remove the log files201// and quit the application.202const cleanupAndQuitApp = () => {203 // Remove the server log file on exit204 removeLogFile();205 206 // Killing pgAdmin4 server process if application quits207 if (pgadminServerProcess != null) {208 try {209 process.kill(pgadminServerProcess.pid);210 }211 catch (e) {212 console.warn('Failed to kill server process.');213 }214 }215 216 if (pgAdminWindowObject != null) {217 // Remove all the cookies.218 pgAdminWindowObject.cookies.getAll({}, function(cookies) {219 try {220 cookies.forEach(function(cookie) {221 pgAdminWindowObject.cookies.remove({url: 'http://' + cookie.domain, name: cookie.name});222 });223 } catch (error) {224 console.warn('Failed to remove cookies.');225 } finally {226 pgAdminWindowObject = null;227 // Quit Application228 nw.App.quit();229 }230 });231 } else {232 // Quit Application233 nw.App.quit();234 }235};236 237// This function is used to create zoom events based on platform238const setZoomEvents = () => {239 if (platform() == 'darwin') {240 zoomInShortcut = new nw.Shortcut({key: 'Command+Equal'});241 zoomOutShortcut = new nw.Shortcut({key: 'Command+Minus'});242 actualSizeShortcut = new nw.Shortcut({key: 'Command+0'});243 toggleFullScreenShortcut = new nw.Shortcut({key: 'Command+Ctrl+F'});244 } else {245 zoomInShortcut = new nw.Shortcut({key: 'Ctrl+Equal'});246 zoomOutShortcut = new nw.Shortcut({key: 'Ctrl+Minus'});247 actualSizeShortcut = new nw.Shortcut({key: 'Ctrl+0'});248 // Use F10 instead of F11. F11 does not work possibly due to Chromium reserving it for their function.249 toggleFullScreenShortcut = new nw.Shortcut({key: 'F10'});250 }251 252 zoomInShortcut.on('active', function() {253 zoomIn();254 });255 256 zoomOutShortcut.on('active', function() {257 zoomOut();258 });259 260 actualSizeShortcut.on('active', function() {261 actualSize();262 });263 264 toggleFullScreenShortcut.on('active', function() {265 toggleFullScreen();266 });267 268 zoomInShortcut.on('failed', function(msg) {269 let errMsg = 'Failed to register zoom in shortcut with error: ' + msg;270 console.warn(errMsg);271 });272 273 zoomOutShortcut.on('failed', function(msg) {274 let errMsg = 'Failed to register zoom out shortcut with error: ' + msg;275 console.warn(errMsg);276 });277 278 actualSizeShortcut.on('failed', function(msg) {279 let errMsg = 'Failed to register actual size shortcut with error: ' + msg;280 console.warn(errMsg);281 });282 283 toggleFullScreenShortcut.on('failed', function(msg) {284 let errMsg = 'Failed to register toggle full screen shortcut with error: ' + msg;285 console.warn(errMsg);286 });287};288 289// This function is used to iterate all open windows and set the zoom level.290const setZoomLevelForAllWindows = () => {291 nw.Window.getAll(function(winArray) {292 for (let arr_val of winArray) {293 arr_val.zoomLevel = pgAdminWindowObject.zoomLevel;294 }295 });296};297 298// This function used to zoom in the pgAdmin window.299const zoomIn = () => {300 if (pgAdminWindowObject != null) {301 pgAdminWindowObject.zoomLevel += 0.5;302 setZoomLevelForAllWindows();303 ConfigureStore.set('zoomLevel', pgAdminWindowObject.zoomLevel);304 ConfigureStore.saveConfig();305 }306};307 308// This function used to zoom out the pgAdmin window.309const zoomOut = () => {310 if (pgAdminWindowObject != null) {311 pgAdminWindowObject.zoomLevel -= 0.5;312 setZoomLevelForAllWindows();313 ConfigureStore.set('zoomLevel', pgAdminWindowObject.zoomLevel);314 ConfigureStore.saveConfig();315 }316};317 318// This function used to reset the zoom level of pgAdmin window.319const actualSize = () => {320 if (pgAdminWindowObject != null) {321 pgAdminWindowObject.zoomLevel = 0;322 setZoomLevelForAllWindows();323 ConfigureStore.set('zoomLevel', pgAdminWindowObject.zoomLevel);324 ConfigureStore.saveConfig();325 }326};327 328const toggleFullScreen = () => {329 if (pgAdminWindowObject != null) {330 // Toggle full screen331 pgAdminWindowObject.toggleFullscreen();332 333 // Change the menu label.334 let menu_label = pgAdminWindowObject.window.document.querySelector('#mnu_toggle_fullscreen_runtime span').innerHTML;335 if (menu_label.indexOf('Enter Full Screen') >= 0) {336 pgAdminWindowObject.window.document.querySelector('#mnu_toggle_fullscreen_runtime span').innerHTML = menu_label.replace('Enter', 'Exit');337 } else if (menu_label.indexOf('Exit Full Screen') >= 0) {338 pgAdminWindowObject.window.document.querySelector('#mnu_toggle_fullscreen_runtime span').innerHTML = menu_label.replace('Exit', 'Enter');339 }340 }341};342 343// This function is used to register zoom events.344const registerZoomEvents = () => {345 nw.App.registerGlobalHotKey(zoomInShortcut);346 nw.App.registerGlobalHotKey(zoomOutShortcut);347 nw.App.registerGlobalHotKey(actualSizeShortcut);348 nw.App.registerGlobalHotKey(toggleFullScreenShortcut);349};350 351// This function is used to unregister zoom events.352const unregisterZoomEvents = () => {353 nw.App.unregisterGlobalHotKey(zoomInShortcut);354 nw.App.unregisterGlobalHotKey(zoomOutShortcut);355 nw.App.unregisterGlobalHotKey(actualSizeShortcut);356 nw.App.unregisterGlobalHotKey(toggleFullScreenShortcut);357};358 359let ConfigureStore = {360 fileName: configFileName,361 jsonData: {},362 363 init: function() {364 if (!this.readConfig()){365 this.jsonData = DEFAULT_CONFIG_DATA;366 this.saveConfig();367 }368 },369 370 // This function is used to write configuration data371 saveConfig: function() {372 fs.writeFileSync(this.fileName, JSON.stringify(this.jsonData, null, 4), {flag: 'w'});373 },374 375 // This function is used to read the configuration data376 readConfig: function() {377 if (fs.existsSync(this.fileName)) {378 try {379 this.jsonData = JSON.parse(fs.readFileSync(this.fileName));380 } catch (error) {381 /* If the file is not present or invalid JSON data in file */382 this.jsonData = {};383 }384 } else {385 let errMsg = 'Unable to read file ' + this.fileName + ' not found.';386 console.warn(errMsg);387 return false;388 }389 390 return true;391 },392 393 getConfigData: function() {394 return this.jsonData;395 },396 397 get: function(key, if_not_value) {398 if(this.jsonData[key] !== undefined) {399 return this.jsonData[key];400 } else {401 return if_not_value;402 }403 },404 405 set: function(key, value) {406 if(typeof key === 'object'){407 this.jsonData = {408 ...this.jsonData,409 ...key,410 };411 } else if(value === '' || value == null || typeof(value) == 'undefined') {412 if(this.jsonData[key] !== undefined) {413 delete this.jsonData[key];414 }415 } else {416 this.jsonData[key] = value;417 }418 },419};420 421function parseConsoleArgs(_method, args) {422 const retData = Array.from(args).map(arg => {423 try {424 if(arg.stack) return arg.stack;425 return JSON.stringify(arg);426 } catch (e) {427 return arg428 }429 });430 return retData?.join(' ');431}432 433 434module.exports = {435 readServerLog: readServerLog,436 writeServerLog: writeServerLog,437 getAvailablePort: getAvailablePort,438 getPythonPath: getPythonPath,439 setProcessObject: setProcessObject,440 cleanupAndQuitApp: cleanupAndQuitApp,441 getServerLogFile: getServerLogFile,442 getRunTimeConfigFile: getRunTimeConfigFile,443 setPgAdminWindowObject: setPgAdminWindowObject,444 zoomIn: zoomIn,445 zoomOut: zoomOut,446 actualSize: actualSize,447 toggleFullScreen: toggleFullScreen,448 setZoomEvents: setZoomEvents,449 registerZoomEvents: registerZoomEvents,450 unregisterZoomEvents: unregisterZoomEvents,451 setZoomLevelForAllWindows: setZoomLevelForAllWindows,452 ConfigureStore: ConfigureStore,453 parseConsoleArgs: parseConsoleArgs,454};455 