// Modules to control application life and create native browser window const {app, BrowserWindow, Tray, nativeImage, Menu} = require('electron'); const {ipcMain} = require('electron'); const Constants = require('./src/constants'); const path = require('path'); const url = require('url'); require('electron-debug')(); const os = require('os'); const helpers = require('./helpers'); const fs = require('fs'); const unzip = require('unzipper'); const AutoLaunch = require('auto-launch'); // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. let mainContextWindow; let mainWindow; let mainWindowTray; let mountedPIDs = []; function createWindow() { // Create the browser window. const height = process.env.ELECTRON_START_URL ? 340 : 320; mainWindow = new BrowserWindow({ width: 425, height: height, resizable: false, webPreferences: { webSecurity: !process.env.ELECTRON_START_URL } }); // and load the index.html of the app. const startUrl = process.env.ELECTRON_START_URL || url.format({ pathname: path.join(__dirname, '/build/index.html'), protocol: 'file:', slashes: true }); mainWindow.loadURL(startUrl); // Emitted when the window is closed. mainWindow.on('closed', function () { // Dereference the window object, usually you would store windows // in an array if your app supports multi windows, this is the time // when you should delete the corresponding element. mainWindow = null }); if (os.platform() === 'win32') { const autoLauncher = new AutoLaunch({ name: 'Repertory UI', path: path.resolve(path.join(app.getAppPath(), '..\\..\\repertory-ui.exe')), }); const image = nativeImage.createFromPath(path.join(__dirname, '/build/icon.ico')); mainContextWindow = Menu.buildFromTemplate([ { label: 'Visible', type: 'checkbox', click(item) { if (item.checked) { mainWindow.show(); if (mainWindow.isMinimized()) { mainWindow.restore(); } mainWindow.focus() } else { mainWindow.hide(); } } }, { label: 'Auto-start', type: 'checkbox', click(item) { if (item.checked) { autoLauncher.enable(); } else { autoLauncher.disable(); } } }, { type: 'separator' }, { label: 'Exit', click(item) { app.quit(); } } ]); mainContextWindow.items[0].checked = true; autoLauncher.isEnabled() .then((enabled) => { mainContextWindow.items[1].checked = enabled; mainWindowTray = new Tray(image); mainWindowTray.setToolTip('Repertory UI'); mainWindowTray.setContextMenu(mainContextWindow) }) .catch(() => { app.quit(); }); } } const instanceLock = app.requestSingleInstanceLock(); if (!instanceLock) { app.quit() } else { app.on('second-instance', () => { if (mainWindow) { mainWindow.show(); if (mainContextWindow) { mainContextWindow.items[0].checked = true; } if (mainWindow.isMinimized()) { mainWindow.restore(); } mainWindow.focus() } }); app.on('ready', createWindow); app.on('window-all-closed', () => { // On OS X it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q if (process.platform !== 'darwin') { app.quit() } }); app.on('activate', () => { // On OS X it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (mainWindow === null) { createWindow() } }); } ipcMain.on(Constants.IPC_Check_Installed, (event, data) => { const dataDirectory = helpers.resolvePath(data.Directory); const destination = path.join(dataDirectory, data.Version); helpers .getMissingDependencies(data.Dependencies) .then((dependencies) => { let exists = false; try { exists = fs.existsSync(destination) && fs.lstatSync(destination).isDirectory(); } catch (e) { } event.sender.send(Constants.IPC_Check_Installed_Reply, { data: { Dependencies: dependencies, Exists: exists, Success: true, Version: data.Version, } }); }).catch((e) => { event.sender.send(Constants.IPC_Check_Installed_Reply, { data: { Dependencies: [], Error: e, Success: false, Version: data.Version, } }); }); }); ipcMain.on(Constants.IPC_Delete_File, (event, data) => { try { if (fs.existsSync(data.FilePath)) { fs.unlinkSync(data.FilePath); } } catch (e) { } }); ipcMain.on(Constants.IPC_Detect_Mounts, (event, data) => { let driveLetters = { Hyperspace: [], Sia: [], }; const grabDriveLetters = (hsLocation, siaLocation) => { for (let i = 'c'.charCodeAt(0); i <= 'z'.charCodeAt(0); i++) { const drive = (String.fromCharCode(i) + ':').toUpperCase(); if (!(hsLocation.startsWith(drive) || siaLocation.startsWith(drive))) { try { if (!fs.existsSync(drive)) { driveLetters.Hyperspace.push(drive); driveLetters.Sia.push(drive); } } catch (e) { } } } if (hsLocation.length > 0) { if (!driveLetters.Hyperspace.find((driveLetter) => { return driveLetter === hsLocation; })) { driveLetters.Hyperspace.push(hsLocation); } } if (siaLocation.length > 0) { if (!driveLetters.Sia.find((driveLetter) => { return driveLetter === siaLocation; })) { driveLetters.Sia.push(siaLocation); } } }; const dataDirectory = helpers.resolvePath(data.Directory); helpers .detectRepertoryMounts(dataDirectory, data.Version) .then((results) => { let hsLocation = results.Hyperspace.Location; let siaLocation = results.Sia.Location; if (os.platform() === 'win32') { hsLocation = hsLocation.toUpperCase(); siaLocation = siaLocation.toUpperCase(); grabDriveLetters(hsLocation, siaLocation); } event.sender.send(Constants.IPC_Detect_Mounts_Reply, { data: { DriveLetters: driveLetters, Locations: { Hyperspace: hsLocation, Sia: siaLocation, }, Success: true, PIDS: { Hyperspace: results.Hyperspace.PID, Sia: results.Sia.PID, } } }); }) .catch((err) => { grabDriveLetters('', ''); event.sender.send(Constants.IPC_Detect_Mounts_Reply, { data: { DriveLetters: driveLetters, Error: err, Success: false, } }); }); }); ipcMain.on(Constants.IPC_Download_File, (event, data) => { const dataDirectory = helpers.resolvePath(data.Directory); const destination = path.join(dataDirectory, data.Filename); helpers.downloadFile(data.URL, destination, (progress) => { event.sender.send(Constants.IPC_Download_File_Progress, { data: { Destination: destination, Progress: progress, URL: data.URL, } }); }, (success, err) => { event.sender.send(Constants.IPC_Download_File_Complete, { data: { Destination: destination, Error: err, Success: success, URL: data.URL, } }); }); }); ipcMain.on(Constants.IPC_Extract_Release, (event, data) => { const dataDirectory = helpers.resolvePath(data.Directory); const destination = path.join(dataDirectory, data.Version); helpers.mkDirByPathSync(destination); const stream = fs.createReadStream(data.Source); stream .pipe(unzip.Extract({ path: destination })) .on('error', (e) => { try { helpers.removeDirectoryRecursively(destination); } catch (e) { } stream.close(); event.sender.send(Constants.IPC_Extract_Release_Complete, { data: { Error: e, Source: data.Source, Success: false, } }); }) .on('finish', () => { stream.close(); event.sender.send(Constants.IPC_Extract_Release_Complete, { data: { Source: data.Source, Success: true, } }); }); }); ipcMain.on(Constants.IPC_Get_Config, (event, data) => { const dataDirectory = helpers.resolvePath(data.Directory); helpers .getConfig(dataDirectory, data.Version, data.StorageType) .then((data) => { if (data.Code === 0) { event.sender.send(Constants.IPC_Get_Config_Reply, { data: { Success: true, Config: data.Data, } }); } else { event.sender.send(Constants.IPC_Get_Config_Reply, { data: { Error: data.Code, Success: false, } }); } }) .catch((e)=> { event.sender.send(Constants.IPC_Get_Config_Reply, { data: { Error: e, Success: false, } }); }); }); ipcMain.on(Constants.IPC_Get_Config_Template, (event, data) => { const dataDirectory = helpers.resolvePath(data.Directory); helpers .getConfigTemplate(dataDirectory, data.Version, data.StorageType) .then((data) => { event.sender.send(Constants.IPC_Get_Config_Template_Reply, { data: { Success: true, Template: data, } }); }) .catch((e)=> { event.sender.send(Constants.IPC_Get_Config_Template_Reply, { data: { Error: e, Success: false, } }); }); }); ipcMain.on(Constants.IPC_Get_Platform, (event) => { event.sender.send(Constants.IPC_Get_Platform_Reply, { data: os.platform() }); }); ipcMain.on(Constants.IPC_Get_State, (event, data) => { const dataDirectory = helpers.resolvePath(data); helpers.mkDirByPathSync(dataDirectory); const configFile = path.join(dataDirectory, 'settings.json'); if (fs.existsSync(configFile)) { event.sender.send(Constants.IPC_Get_State_Reply, { data: JSON.parse(fs.readFileSync(configFile, 'utf8')), }); } else { event.sender.send(Constants.IPC_Get_State_Reply, { data: null, }); } }); ipcMain.on(Constants.IPC_Grab_Releases, (event) => { event.sender.send(Constants.IPC_Grab_Releases_Reply); }); ipcMain.on(Constants.IPC_Grab_UI_Releases, (event) => { event.sender.send(Constants.IPC_Grab_UI_Releases_Reply); }); ipcMain.on(Constants.IPC_Install_Dependency, (event, data) => { helpers .executeAndWait(data.Source) .then(()=> { event.sender.send(Constants.IPC_Install_Dependency_Reply, { data: { Source: data.Source, Success: true, } }); }) .catch((e)=> { event.sender.send(Constants.IPC_Install_Dependency_Reply, { data: { Error: e, Source: data.Source, Success: false, } }); }); }); ipcMain.on(Constants.IPC_Install_Upgrade, (event, data) => { helpers .executeAsync(data.Source) .then(()=> { mainWindow.close(); }) .catch((e)=> { event.sender.send(Constants.IPC_Install_Upgrade_Reply, { data: { Error: e, Source: data.Source, Success: false, } }); }); }); ipcMain.on(Constants.IPC_Mount_Drive, (event, data) => { const dataDirectory = helpers.resolvePath(data.Directory); const errorHandler = (pid) => { mountedPIDs.splice(mountedPIDs.indexOf(pid), 1); event.sender.send(Constants.IPC_Unmount_Drive_Reply, { data: { PID: -1, StorageType: data.StorageType, Success: false, } }); }; helpers.executeMount(dataDirectory, data.Version, data.StorageType, data.Location, (_, pid)=> { errorHandler(pid); }) .then(pid=> { if (pid !== -1) { mountedPIDs.push(pid); } event.sender.send(Constants.IPC_Mount_Drive_Reply, { data: { PID: pid, StorageType: data.StorageType, Success: true, } }); }) .catch((_, pid) => { errorHandler(pid); }); }); ipcMain.on(Constants.IPC_Save_State, (event, data) => { const dataDirectory = helpers.resolvePath(data.Directory); helpers.mkDirByPathSync(dataDirectory); const configFile = path.join(dataDirectory, 'settings.json'); fs.writeFileSync(configFile, JSON.stringify(data.State), 'utf8'); }); ipcMain.on(Constants.IPC_Set_Config_Values, (event, data) => { const dataDirectory = helpers.resolvePath(data.Directory); const setConfigValue = (i) => { if (i < data.Items.length) { helpers .setConfigValue(data.Items[i].Name, data.Items[i].Value, dataDirectory, data.StorageType, data.Version) .then(() => { setConfigValue(++i); }) .catch(() => { setConfigValue(++i); }); } else { event.sender.send(Constants.IPC_Set_Config_Values_Reply, {}); } }; setConfigValue(0); }); ipcMain.on(Constants.IPC_Unmount_Drive, (event, data) => { helpers .stopProcessByPID(data.PID) .then((pid)=> { if (mountedPIDs.indexOf(pid) === -1) { event.sender.send(Constants.IPC_Unmount_Drive_Reply); } }) .catch((e) => { console.log(e); }); });