This repository has been archived on 2025-09-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
repertory-ui/electron.js
Scott E. Graves acfeb4a9a2 OS X changes
2018-11-13 13:27:37 -06:00

540 lines
15 KiB
JavaScript

// 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 = [];
let expectedUnmount = {};
function createWindow() {
// Create the browser window.
const height = process.env.ELECTRON_START_URL ? 394 : 374;
mainWindow = new BrowserWindow({
width: 425,
height: height,
resizable: false,
title: 'Repertory UI',
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') || (os.platform() === 'linux')) {
const appPath = (os.platform() === 'win32') ?
path.resolve(path.join(app.getAppPath(), '..\\..\\repertory-ui.exe')) :
process.env.APPIMAGE;
const autoLauncher = new AutoLaunch({
name: 'Repertory UI',
path: appPath,
});
const image = nativeImage.createFromPath(path.join(__dirname, '/build/logo.png'));
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()
}
});
}
const standardIPCReply = (event, channel, data, error) => {
event.sender.send(channel, {
data: {
...data,
Error: error,
Success: !error,
}
});
};
ipcMain.on(Constants.IPC_Check_Dependency_Installed, (event, data) => {
try {
const exists = fs.lstatSync(data.File).isFile();
standardIPCReply(event, Constants.IPC_Check_Dependency_Installed_Reply, {
Exists: exists,
});
} catch (e) {
standardIPCReply(event, Constants.IPC_Check_Dependency_Installed_Reply, {
Exists: false,
}, e);
}
});
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) {
}
standardIPCReply(event, Constants.IPC_Check_Installed_Reply, {
Dependencies: dependencies,
Exists: exists,
Version: data.Version,
});
}).catch(error => {
standardIPCReply(event, Constants.IPC_Check_Installed_Reply, {
Dependencies: [],
Version: data.Version,
}, error);
});
});
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: [],
SiaPrime: [],
};
const grabDriveLetters = (hsLocation, siaLocation, siaPrimeLocation) => {
for (let i = 'c'.charCodeAt(0); i <= 'z'.charCodeAt(0); i++) {
const drive = (String.fromCharCode(i) + ':').toUpperCase();
if (!(hsLocation.startsWith(drive) || siaLocation.startsWith(drive) || siaPrimeLocation.startsWith(drive))) {
try {
if (!fs.existsSync(drive)) {
driveLetters.Hyperspace.push(drive);
driveLetters.Sia.push(drive);
driveLetters.SiaPrime.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);
}
}
if (siaPrimeLocation.length > 0) {
if (!driveLetters.SiaPrime.find((driveLetter) => {
return driveLetter === siaPrimeLocation;
})) {
driveLetters.SiaPrime.push(siaPrimeLocation);
}
}
};
const setImage = (hsLocation, siaLocation, siaPrimeLocation) => {
if (os.platform() === 'win32') {
let image;
if ((siaLocation.length > 0) || (hsLocation.length > 0) || (siaPrimeLocation.length > 0)) {
image = nativeImage.createFromPath(path.join(__dirname, '/build/logo_both.png'));
} else {
image = nativeImage.createFromPath(path.join(__dirname, '/build/logo.png'));
}
mainWindowTray.setImage(image);
}
};
const dataDirectory = helpers.resolvePath(data.Directory);
helpers
.detectRepertoryMounts(dataDirectory, data.Version)
.then((results) => {
let hsLocation = results.Hyperspace.Location;
let siaLocation = results.Sia.Location;
if (!results.SiaPrime) {
results.SiaPrime = {
Active: false,
Location: '',
PID: -1,
};
}
let siaPrimeLocation = results.SiaPrime.Location;
if (os.platform() === 'win32') {
hsLocation = hsLocation.toUpperCase();
siaLocation = siaLocation.toUpperCase();
siaPrimeLocation = siaPrimeLocation.toUpperCase();
grabDriveLetters(hsLocation, siaLocation, siaPrimeLocation);
}
if (results.Hyperspace.PID !== -1) {
expectedUnmount['Hyperspace'] = false;
}
if (results.Sia.PID !== -1) {
expectedUnmount['Sia'] = false;
}
if (results.SiaPrime.PID !== -1) {
expectedUnmount['SiaPrime'] = false;
}
setImage(hsLocation, siaLocation, siaPrimeLocation);
standardIPCReply(event, Constants.IPC_Detect_Mounts_Reply, {
DriveLetters: driveLetters,
Locations: {
Hyperspace: hsLocation,
Sia: siaLocation,
SiaPrime: siaPrimeLocation,
},
PIDS: {
Hyperspace: results.Hyperspace.PID,
Sia: results.Sia.PID,
SiaPrime: results.SiaPrime.PID,
}
});
})
.catch(error => {
grabDriveLetters('', '');
setImage('', '');
standardIPCReply(event, Constants.IPC_Detect_Mounts_Reply, {
DriveLetters: driveLetters,
}, error);
});
});
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) => {
standardIPCReply(event, Constants.IPC_Download_File_Progress, {
Destination: destination,
Progress: progress,
URL: data.URL,
});
}, error => {
standardIPCReply(event, Constants.IPC_Download_File_Complete, {
Destination: destination,
URL: data.URL,
}, error);
});
});
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', error => {
try {
helpers.removeDirectoryRecursively(destination);
} catch (e) {
}
stream.close();
standardIPCReply(event, Constants.IPC_Extract_Release_Complete, {
Source: data.Source,
}, error);
})
.on('finish', () => {
stream.close();
standardIPCReply(event, Constants.IPC_Extract_Release_Complete, {
Source: data.Source,
});
});
});
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) {
standardIPCReply(event, Constants.IPC_Get_Config_Reply, {
Config: data.Data,
});
} else {
standardIPCReply(event, Constants.IPC_Get_Config_Reply, {}, data.Code);
}
})
.catch(error => {
standardIPCReply(event, Constants.IPC_Get_Config_Reply, {}, error);
});
});
ipcMain.on(Constants.IPC_Get_Config_Template, (event, data) => {
const dataDirectory = helpers.resolvePath(data.Directory);
helpers
.getConfigTemplate(dataDirectory, data.Version, data.StorageType)
.then((data) => {
standardIPCReply(event, Constants.IPC_Get_Config_Template_Reply, {
Template: data,
});
})
.catch(error => {
standardIPCReply(event, Constants.IPC_Get_Config_Template_Reply, {}, error);
});
});
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) => {
standardIPCReply(event, Constants.IPC_Grab_Releases_Reply);
});
ipcMain.on(Constants.IPC_Grab_UI_Releases, (event) => {
standardIPCReply(event, Constants.IPC_Grab_UI_Releases_Reply);
});
ipcMain.on(Constants.IPC_Install_Dependency, (event, data) => {
if (data.Source.toLowerCase().endsWith('.dmg')) {
helpers
.executeAsync('hdiutil', ['attach', data.Source])
.then(() => {
standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, {
Source: data.Source,
URL: data.URL,
});
})
.catch(error=> {
standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, {
Source: data.Source,
URL: data.URL,
}, error);
});
} else {
helpers
.executeAndWait(data.Source)
.then(() => {
standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, {
Source: data.Source,
URL: data.URL,
});
})
.catch(error => {
standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, {
Source: data.Source,
URL: data.URL,
}, error);
});
}
});
ipcMain.on(Constants.IPC_Install_Upgrade, (event, data) => {
helpers
.executeAsync(data.Source)
.then(()=> {
mainWindow.close();
})
.catch(error => {
standardIPCReply(event, Constants.IPC_Install_Upgrade_Reply, {
Source: data.Source,
}, error);
});
});
ipcMain.on(Constants.IPC_Mount_Drive, (event, data) => {
expectedUnmount[data.StorageType] = false;
const dataDirectory = helpers.resolvePath(data.Directory);
const errorHandler = (pid, error) => {
mountedPIDs.splice(mountedPIDs.indexOf(pid), 1);
standardIPCReply(event, Constants.IPC_Unmount_Drive_Reply, {
Expected: expectedUnmount[data.StorageType],
Location: data.Location,
PID: -1,
StorageType: data.StorageType,
}, error || Error(data.StorageType + ' Unmounted'));
};
helpers.executeMount(dataDirectory, data.Version, data.StorageType, data.Location, (error, pid) => {
errorHandler(pid, error);
})
.then(pid => {
if (pid !== -1) {
mountedPIDs.push(pid);
}
standardIPCReply(event, Constants.IPC_Mount_Drive_Reply, {
PID: pid,
StorageType: data.StorageType,
});
})
.catch(error => {
errorHandler(-1, error);
});
});
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 {
standardIPCReply(event, Constants.IPC_Set_Config_Values_Reply, {});
}
};
setConfigValue(0);
});
ipcMain.on(Constants.IPC_Shutdown, () => {
app.quit();
});
ipcMain.on(Constants.IPC_Unmount_Drive, (event, data) => {
expectedUnmount[data.StorageType] = true;
helpers
.stopProcessByPID(data.PID)
.then((pid)=> {
if (mountedPIDs.indexOf(pid) === -1) {
event.sender.send(Constants.IPC_Unmount_Drive_Reply);
}
})
.catch((e) => {
console.log(e);
});
});