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
2018-12-13 15:00:07 -06:00

709 lines
20 KiB
JavaScript

// Modules to control application life and create native browser window
const {app, BrowserWindow, Tray, nativeImage, Menu, dialog} = 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 trayContextMenu;
let mainWindow;
let mainWindowTray;
let mountedData = {};
let mountedLocations = [];
let expectedUnmount = {};
let launchHidden = false;
let firstMountCheck = true;
let manualMountDetection = {};
let isQuiting = false;
app.on('before-quit', function () {
isQuiting = true;
});
function closeApplication() {
app.quit();
}
function createWindow() {
loadUiSettings();
// Create the browser window.
const height = (process.env.ELECTRON_START_URL ? 364 : 344) - ((os.platform() === 'win32') || (os.platform() === 'darwin') ? 0 : 24);
mainWindow = new BrowserWindow({
width: 428 + (os.platform() === 'win32' ? 0 : 120),
height: height,
resizable: false,
show: !launchHidden,
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;
// Unmount all items
for (const i in mountedLocations) {
const data = mountedData[mountedLocations[i]];
helpers.stopMountProcessSync(data.DataDirectory, data.Version, data.StorageType);
}
mountedLocations = [];
mountedData = {};
});
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'));
trayContextMenu = Menu.buildFromTemplate([
{
label: 'Visible', type: 'checkbox', click(item) {
if (item.checked) {
mainWindow.show();
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
mainWindow.focus()
} else {
mainWindow.hide();
}
},
checked: !launchHidden,
},
{
label: 'Auto-start', type: 'checkbox', click(item) {
if (item.checked) {
autoLauncher.enable();
} else {
autoLauncher.disable();
}
}
},
{
type: 'separator'
},
{
label: 'Launch Hidden', type: 'checkbox', click(item) {
launchHidden = !!item.checked;
saveUiSettings();
},
checked: launchHidden,
},
{
type: 'separator'
},
{
label: 'Exit and Unmount', click(item) {
closeApplication();
}
}
]);
mainWindow.on('close', function (event) {
if (!isQuiting) {
event.preventDefault();
if (mainWindow.isVisible()) {
mainWindow.hide();
trayContextMenu.items[0].checked = false;
mainWindowTray.setContextMenu(trayContextMenu);
}
event.returnValue = false;
}
});
autoLauncher
.isEnabled()
.then((enabled) => {
trayContextMenu.items[1].checked = enabled;
mainWindowTray = new Tray(image);
mainWindowTray.setToolTip('Repertory UI');
mainWindowTray.setContextMenu(trayContextMenu)
})
.catch(() => {
closeApplication();
});
}
}
const instanceLock = app.requestSingleInstanceLock();
if (!instanceLock) {
closeApplication();
} else {
app.on('second-instance', () => {
if (mainWindow) {
mainWindow.show();
if (trayContextMenu && mainWindowTray) {
trayContextMenu.items[0].checked = true;
mainWindowTray.setContextMenu(trayContextMenu)
}
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
mainWindow.focus()
}
});
app.on('ready', createWindow);
app.on('window-all-closed', () => {
closeApplication();
});
}
const clearManualMountDetection = (storageType) => {
if (manualMountDetection[storageType]) {
clearInterval(manualMountDetection[storageType]);
delete manualMountDetection[storageType];
}
};
const loadUiSettings = () => {
const settingFile = path.join(helpers.resolvePath(Constants.DATA_LOCATIONS[os.platform()]), 'ui.json');
try {
if (fs.statSync(settingFile).isFile()) {
const settings = JSON.parse(fs.readFileSync(settingFile, 'utf8'));
launchHidden = settings.launch_hidden;
}
} catch (e) {
}
};
const monitorMount = (sender, storageType, dataDirectory, version, pid, location) => {
manualMountDetection[storageType] = setInterval(() => {
helpers
.detectRepertoryMounts(dataDirectory, version)
.then(result => {
if (result[storageType].PID !== pid) {
if (result[storageType].PID === -1) {
clearManualMountDetection(storageType);
sender.send(Constants.IPC_Unmount_Drive_Reply, {
data: {
Expected: expectedUnmount[storageType],
Location: location,
StorageType: storageType,
Error: Error(storageType + ' Unmounted').toString(),
Success: false,
}
});
} else {
pid = result[storageType].PID;
}
}
})
.catch(e => {
console.log(e);
});
},6000);
};
const saveUiSettings = () => {
const settingFile = path.join(helpers.resolvePath(Constants.DATA_LOCATIONS[os.platform()]), 'ui.json');
try {
fs.writeFileSync(settingFile, JSON.stringify({
launch_hidden: launchHidden,
}), 'utf-8');
} catch (e) {
}
};
const standardIPCReply = (event, channel, data, error) => {
if (mainWindow) {
event.sender.send(channel, {
data: {
...data,
Error: error instanceof Error ? error.toString() : error,
Success: !error,
}
});
}
};
ipcMain.on(Constants.IPC_Browse_Directory, (event, data) => {
dialog.showOpenDialog(mainWindow, {
defaultPath: data.Location,
properties: ['openDirectory'],
title: data.Title,
}, (filePaths) => {
if (filePaths && (filePaths.length > 0)) {
event.returnValue = filePaths[0];
} else {
event.returnValue = '';
}
});
});
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_Check_Mount_Location, (event, data) => {
let response = {
Success: true,
Error: ''
};
try {
if (fs.existsSync(data.Location) && fs.statSync(data.Location).isDirectory()) {
if (fs.readdirSync(data.Location).length !== 0) {
response.Success = false;
response.Error = 'Directory not empty: ' + data.Location;
}
} else {
response.Success = false;
response.Error = 'Directory not found: ' + data.Location;
}
} catch (e) {
response.Success = false;
response.Error = e.toString();
}
event.returnValue = response;
});
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 = {};
for (const provider of Constants.PROVIDER_LIST) {
driveLetters[provider] = [];
}
const grabDriveLetters = (locations) => {
for (let i = 'c'.charCodeAt(0); i <= 'z'.charCodeAt(0); i++) {
const drive = (String.fromCharCode(i) + ':').toUpperCase();
let driveInUse;
if (Object.keys(locations).length > 0) {
for (const provider of Constants.PROVIDER_LIST) {
driveInUse = locations[provider].startsWith(drive);
if (driveInUse)
break;
}
}
if (!driveInUse) {
try {
if (!fs.existsSync(drive)) {
for (const provider of Constants.PROVIDER_LIST) {
driveLetters[provider].push(drive);
}
}
} catch (e) {
}
}
}
if (Object.keys(locations).length > 0) {
for (const provider of Constants.PROVIDER_LIST) {
if (locations[provider].length > 0) {
if (!driveLetters[provider].find((driveLetter) => {
return driveLetter === locations[provider];
})) {
driveLetters[provider].push(locations[provider]);
}
}
}
}
};
const setImage = (locations) => {
if (os.platform() === 'win32' || os.platform() === 'linux') {
let driveInUse;
if (Object.keys(locations).length > 0) {
for (const provider of Constants.PROVIDER_LIST) {
driveInUse = locations[provider].length > 0;
if (driveInUse)
break;
}
}
let image;
if (driveInUse) {
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 storageData = {};
let locations = {};
for (const provider of Constants.PROVIDER_LIST) {
storageData[provider] = results[provider] ? results[provider] : {
Active: false,
Location: '',
PID: -1,
};
locations[provider] = storageData[provider].Location;
if (storageData[provider].PID !== -1) {
expectedUnmount[provider] = false;
if (firstMountCheck) {
monitorMount(event.sender, provider, dataDirectory, data.Version, storageData[provider].PID, storageData[provider].Location);
}
}
}
if (os.platform() === 'win32') {
grabDriveLetters(locations);
}
setImage(locations);
if (firstMountCheck) {
firstMountCheck = false;
}
standardIPCReply(event, Constants.IPC_Detect_Mounts_Reply, {
DriveLetters: driveLetters,
Locations: locations,
});
})
.catch(error => {
if (os.platform() === 'win32') {
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) => {
if (os.platform() === 'win32') {
helpers
.executeAsync(data.Source)
.then(() => {
closeApplication();
})
.catch(error => {
standardIPCReply(event, Constants.IPC_Install_Upgrade_Reply, {
Source: data.Source,
}, error);
});
} else if (data.Source.toLocaleLowerCase().endsWith('.dmg')) {
helpers
.executeAsync('hdiutil', ['attach', data.Source])
.then(() => {
closeApplication();
})
.catch(error => {
standardIPCReply(event, Constants.IPC_Install_Upgrade_Reply, {
Source: data.Source,
}, error);
});
} else if (data.Source.toLocaleLowerCase().endsWith('.appimage')) {
// TODO Generate and execute script with delay
/*helpers
.executeAsync(data.Source)
.then(() => {
closeApplication();
})
.catch(error => {
standardIPCReply(event, Constants.IPC_Install_Upgrade_Reply, {
Source: data.Source,
}, error);
});*/
} else {
standardIPCReply(event, Constants.IPC_Install_Upgrade_Reply, {
Source: data.Source,
}, Error('Unsupported upgrade: ' + data.Source));
}
});
ipcMain.on(Constants.IPC_Mount_Drive, (event, data) => {
expectedUnmount[data.StorageType] = false;
const dataDirectory = helpers.resolvePath(data.Directory);
if (mountedLocations.indexOf(data.Location) !== -1) {
console.log(data.StorageType + ' already mounted: ' + data.Location);
} else {
mountedLocations.push(data.Location);
mountedData[data.Location] = {
DataDirectory: dataDirectory,
Version: data.Version,
StorageType: data.StorageType,
};
const errorHandler = (pid, error) => {
if (mountedLocations.indexOf(data.Location) !== -1) {
mountedLocations.splice(mountedLocations.indexOf(data.Location), 1);
delete mountedData[data.Location];
}
standardIPCReply(event, Constants.IPC_Unmount_Drive_Reply, {
Expected: expectedUnmount[data.StorageType],
Location: data.Location,
StorageType: data.StorageType,
}, error || Error(data.StorageType + ' Unmounted'));
};
helpers
.executeMount(dataDirectory, data.Version, data.StorageType, data.Location, data.NoConsoleSupported, (error, pid) => {
errorHandler(pid, error);
})
.then(() => {
standardIPCReply(event, Constants.IPC_Mount_Drive_Reply, {
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, () => {
closeApplication();
});
ipcMain.on(Constants.IPC_Unmount_Drive, (event, data) => {
clearManualMountDetection(data.StorageType);
const dataDirectory = helpers.resolvePath(data.Directory);
expectedUnmount[data.StorageType] = true;
helpers
.stopMountProcess(dataDirectory, data.Version, data.StorageType)
.then((result)=> {
console.log(result);
})
.catch((e) => {
console.log(e);
});
});