// Modules to control application life and create native browser window const {app, BrowserWindow, Tray, nativeImage, Menu} = require('electron'); const {ipcMain} = require('electron'); 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(); } } } ]); 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('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('check_installed_reply', { data: { Dependencies: dependencies, Exists: exists, Success: true, Version: data.Version, } }); }).catch((e) => { event.sender.send('check_installed_reply', { data: { Dependencies: [], Error: e, Success: false, Version: data.Version, } }); }); }); ipcMain.on('delete_file', (event, data) => { try { if (fs.existsSync(data.FilePath)) { fs.unlinkSync(data.FilePath); } } catch (e) { } }); ipcMain.on('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('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('detect_mounts_reply', { data: { DriveLetters: driveLetters, Error: err, Success: false, } }); }); }); ipcMain.on('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('download_file_progress', { data: { Destination: destination, Progress: progress, URL: data.URL, } }); }, (success, err) => { event.sender.send('download_file_complete', { data: { Destination: destination, Error: err, Success: success, URL: data.URL, } }); }); }); ipcMain.on('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('extract_release_complete', { data: { Error: e, Source: data.Source, Success: false, } }); }) .on('finish', () => { stream.close(); event.sender.send('extract_release_complete', { data: { Source: data.Source, Success: true, } }); }); }); ipcMain.on('get_platform', (event) => { event.sender.send('get_platform_reply', { data: os.platform() }); }); ipcMain.on('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('get_state_reply', { data: JSON.parse(fs.readFileSync(configFile, 'utf8')), }); } else { event.sender.send('get_state_reply', { data: null, }); } }); ipcMain.on('grab_releases', (event) => { event.sender.send('grab_releases_reply'); }); ipcMain.on('grab_ui_releases', (event) => { event.sender.send('grab_ui_releases_reply'); }); ipcMain.on('install_dependency', (event, data) => { helpers .executeAndWait(data.Source) .then(()=> { event.sender.send('install_dependency_reply', { data: { Source: data.Source, Success: true, } }); }) .catch((e)=> { event.sender.send('install_dependency_reply', { data: { Error: e, Source: data.Source, Success: false, } }); }); }); ipcMain.on('install_upgrade', (event, data) => { helpers .executeAsync(data.Source) .then(()=> { mainWindow.close(); }) .catch((e)=> { event.sender.send('install_upgrade_reply', { data: { Error: e, Source: data.Source, Success: false, } }); }); }); ipcMain.on('mount_drive', (event, data) => { const dataDirectory = helpers.resolvePath(data.Directory); const errorHandler = (pid) => { mountedPIDs.splice(mountedPIDs.indexOf(pid), 1); event.sender.send('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('mount_drive_reply', { data: { PID: pid, StorageType: data.StorageType, Success: true, } }); }) .catch((_, pid) => { errorHandler(pid); }); }); ipcMain.on('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('unmount_drive', (event, data) => { helpers.stopProcessByPID(data.PID) .then((pid)=> { if (mountedPIDs.indexOf(pid) === -1) { event.sender.send('unmount_drive_reply'); } }) .catch((e) => { console.log(e); }); });