Merged 1.0.2_branch into master
11
CHANGELOG.md
@@ -1,4 +1,15 @@
|
||||
# Changelog #
|
||||
## 1.0.2 ##
|
||||
* Option to launch application hidden (notification icon only)
|
||||
* Close window to notification area
|
||||
* Unmount on application exit
|
||||
* Ability to cancel mount retry on unexpected failure
|
||||
* OS X support
|
||||
* SiaPrime support
|
||||
* Partial Linux support
|
||||
* Electron to v4
|
||||
* Prevent mount if dependencies are missing
|
||||
|
||||
## 1.0.1 ##
|
||||
* Added configuration settings for Repertory 1.0.0-alpha.2 and above
|
||||
* Fixed memory leak on component unmount
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
# Repertory UI
|
||||

|
||||

|
||||
### GUI for [Repertory](https://bitbucket.org/blockstorage/repertory) ###
|
||||
Repertory allows you to mount Hyperspace or Sia blockchain storage solutions via FUSE on Linux/OS X or via WinFSP on Windows.
|
||||
Repertory allows you to mount Sia, SiaPrime and/or Hyperspace blockchain storage solutions via FUSE on Linux/OS X or via WinFSP on Windows.
|
||||
# Downloads #
|
||||
* [Repertory UI v1.0.1 Windows 64-bit](https://sia.pixeldrain.com/api/file/Alo1IF1u)
|
||||
* [Repertory UI v1.0.2 OS X](https://pixeldrain.com/u/OtxPlbOI)
|
||||
* [Repertory UI v1.0.2 Windows 64-bit](https://pixeldrain.com/u/4oJeVntd)
|
||||
# Supported Platforms #
|
||||
* OS X
|
||||
* Windows 64-bit
|
||||
# Future Platforms #
|
||||
* OS X
|
||||
* Linux 64-bit
|
||||
632
electron.js
@@ -1,6 +1,6 @@
|
||||
// Modules to control application life and create native browser window
|
||||
|
||||
const {app, BrowserWindow, Tray, nativeImage, Menu} = require('electron');
|
||||
const {app, BrowserWindow, Tray, nativeImage, Menu, dialog} = require('electron');
|
||||
const {ipcMain} = require('electron');
|
||||
const Constants = require('./src/constants');
|
||||
const path = require('path');
|
||||
@@ -14,145 +14,298 @@ 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 trayContextMenu;
|
||||
let mainWindow;
|
||||
let mainWindowTray;
|
||||
let mountedPIDs = [];
|
||||
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 setWindowVisibility(show) {
|
||||
if (show) {
|
||||
mainWindow.show();
|
||||
if (os.platform() === 'darwin') {
|
||||
app.dock.show();
|
||||
}
|
||||
|
||||
if (mainWindow.isMinimized()) {
|
||||
mainWindow.restore();
|
||||
}
|
||||
|
||||
mainWindow.focus();
|
||||
} else {
|
||||
mainWindow.hide();
|
||||
if (os.platform() === 'darwin') {
|
||||
app.dock.hide();
|
||||
}
|
||||
}
|
||||
|
||||
if (trayContextMenu && mainWindowTray) {
|
||||
trayContextMenu.items[0].checked = show;
|
||||
mainWindowTray.setContextMenu(trayContextMenu)
|
||||
}
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
loadUiSettings();
|
||||
|
||||
// Create the browser window.
|
||||
const height = process.env.ELECTRON_START_URL ? 324 : 304;
|
||||
const height = (process.env.ELECTRON_START_URL || (os.platform() === 'darwin') ? 364 : 344) - ((os.platform() === 'win32') ? 0 : 20);
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 425,
|
||||
width: 428 + ((os.platform() === 'win32') ? 0 : (os.platform() === 'darwin') ? 150 : 160),
|
||||
height: height,
|
||||
fullscreen: false,
|
||||
resizable: false,
|
||||
show: !launchHidden,
|
||||
title: 'Repertory UI',
|
||||
webPreferences: {
|
||||
webSecurity: !process.env.ELECTRON_START_URL
|
||||
}
|
||||
});
|
||||
|
||||
if ((os.platform() === 'darwin') && launchHidden) {
|
||||
app.dock.hide();
|
||||
}
|
||||
|
||||
// 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('close', function (event) {
|
||||
if (!isQuiting) {
|
||||
event.preventDefault();
|
||||
if (mainWindow.isVisible()) {
|
||||
setWindowVisibility(false);
|
||||
}
|
||||
event.returnValue = false;
|
||||
}
|
||||
});
|
||||
|
||||
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
|
||||
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 appPath = (os.platform() === 'win32') ? path.resolve(path.join(app.getAppPath(), '..\\..\\repertory-ui.exe')) :
|
||||
(os.platform() === 'darwin') ? path.resolve(path.join(path.dirname(app.getAppPath()), '../MacOS/repertory-ui')) :
|
||||
process.env.APPIMAGE;
|
||||
|
||||
const autoLauncher = new AutoLaunch({
|
||||
name: 'Repertory UI',
|
||||
path: appPath,
|
||||
});
|
||||
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();
|
||||
}
|
||||
}
|
||||
trayContextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'Visible', type: 'checkbox', click(item) {
|
||||
setWindowVisibility(item.checked);
|
||||
},
|
||||
{
|
||||
label: 'Auto-start', type: 'checkbox', click(item) {
|
||||
if (item.checked) {
|
||||
autoLauncher.enable();
|
||||
} else {
|
||||
autoLauncher.disable();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Exit', click(item) {
|
||||
app.quit();
|
||||
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() {
|
||||
closeApplication();
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
mainContextWindow.items[0].checked = true;
|
||||
autoLauncher
|
||||
.isEnabled()
|
||||
.then((enabled) => {
|
||||
mainContextWindow.items[1].checked = enabled;
|
||||
const image = nativeImage.createFromPath(path.join(__dirname, (os.platform() === 'darwin') ? '/build/logo_mac.png' : '/build/logo.png'));
|
||||
mainWindowTray = new Tray(image);
|
||||
|
||||
mainWindowTray = new Tray(image);
|
||||
mainWindowTray.setToolTip('Repertory UI');
|
||||
mainWindowTray.setContextMenu(mainContextWindow)
|
||||
})
|
||||
.catch(() => {
|
||||
app.quit();
|
||||
});
|
||||
}
|
||||
autoLauncher
|
||||
.isEnabled()
|
||||
.then((enabled) => {
|
||||
trayContextMenu.items[1].checked = enabled;
|
||||
mainWindowTray.setToolTip('Repertory UI');
|
||||
mainWindowTray.setContextMenu(trayContextMenu)
|
||||
})
|
||||
.catch(() => {
|
||||
closeApplication();
|
||||
});
|
||||
|
||||
mainWindow.loadURL(startUrl);
|
||||
}
|
||||
|
||||
const instanceLock = app.requestSingleInstanceLock();
|
||||
if (!instanceLock) {
|
||||
app.quit()
|
||||
closeApplication();
|
||||
} else {
|
||||
app.on('second-instance', () => {
|
||||
if (mainWindow) {
|
||||
mainWindow.show();
|
||||
if (mainContextWindow) {
|
||||
mainContextWindow.items[0].checked = true;
|
||||
}
|
||||
if (mainWindow.isMinimized()) {
|
||||
mainWindow.restore();
|
||||
}
|
||||
mainWindow.focus()
|
||||
setWindowVisibility(true);
|
||||
}
|
||||
});
|
||||
|
||||
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()
|
||||
}
|
||||
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) => {
|
||||
event.sender.send(channel, {
|
||||
data: {
|
||||
...data,
|
||||
Error: error,
|
||||
Success: !error,
|
||||
if (mainWindow) {
|
||||
event.sender.send(channel, {
|
||||
data: {
|
||||
...data,
|
||||
Error: error instanceof Error ? error.toString() : error,
|
||||
Success: !error,
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
ipcMain.on(Constants.IPC_Browse_Directory + '_sync', (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, {
|
||||
data: {
|
||||
Exists: exists,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
standardIPCReply(event, Constants.IPC_Check_Dependency_Installed_Reply, {
|
||||
data : {
|
||||
Exists: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on(Constants.IPC_Check_Dependency_Installed + '_sync', (event, data) => {
|
||||
try {
|
||||
const ls = fs.lstatSync(data.File);
|
||||
event.returnValue = {
|
||||
data: {
|
||||
Exists: ls.isFile() || ls.isSymbolicLink(),
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
event.returnValue = {
|
||||
data: {
|
||||
Exists: false
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on(Constants.IPC_Check_Installed, (event, data) => {
|
||||
const dataDirectory = helpers.resolvePath(data.Directory);
|
||||
@@ -178,6 +331,29 @@ ipcMain.on(Constants.IPC_Check_Installed, (event, data) => {
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.on(Constants.IPC_Check_Mount_Location + '_sync', (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)) {
|
||||
@@ -187,87 +363,123 @@ ipcMain.on(Constants.IPC_Delete_File, (event, data) => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on(Constants.IPC_Detect_Mounts, (event, data) => {
|
||||
let driveLetters = {
|
||||
Hyperspace: [],
|
||||
Sia: [],
|
||||
};
|
||||
ipcMain.on(Constants.IPC_Delete_File + '_sync', (event, data) => {
|
||||
try {
|
||||
if (fs.existsSync(data.FilePath)) {
|
||||
fs.unlinkSync(data.FilePath);
|
||||
}
|
||||
event.returnValue = {
|
||||
data: true,
|
||||
};
|
||||
} catch (e) {
|
||||
event.returnValue = {
|
||||
data: false,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const grabDriveLetters = (hsLocation, siaLocation) => {
|
||||
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();
|
||||
if (!(hsLocation.startsWith(drive) || siaLocation.startsWith(drive))) {
|
||||
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)) {
|
||||
driveLetters.Hyperspace.push(drive);
|
||||
driveLetters.Sia.push(drive);
|
||||
for (const provider of Constants.PROVIDER_LIST) {
|
||||
driveLetters[provider].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 (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 = (hsLocation, siaLocation) => {
|
||||
if (os.platform() === 'win32') {
|
||||
let image;
|
||||
if ((siaLocation.length > 0) && (hsLocation.length > 0)) {
|
||||
image = nativeImage.createFromPath(path.join(__dirname, '/build/logo_both.png'));
|
||||
} else if (hsLocation.length > 0) {
|
||||
image = nativeImage.createFromPath(path.join(__dirname, '/build/logo_hs.png'));
|
||||
} else if (siaLocation.length > 0) {
|
||||
image = nativeImage.createFromPath(path.join(__dirname, '/build/logo_sia.png'));
|
||||
} else {
|
||||
image = nativeImage.createFromPath(path.join(__dirname, '/build/logo.png'));
|
||||
const setImage = (locations) => {
|
||||
let driveInUse;
|
||||
if (Object.keys(locations).length > 0) {
|
||||
for (const provider of Constants.PROVIDER_LIST) {
|
||||
driveInUse = locations[provider].length > 0;
|
||||
if (driveInUse)
|
||||
break;
|
||||
}
|
||||
|
||||
mainWindowTray.setImage(image);
|
||||
}
|
||||
|
||||
let image;
|
||||
if (driveInUse) {
|
||||
image = nativeImage.createFromPath(path.join(__dirname, os.platform() === 'darwin' ? '/build/logo_both_mac.png' : '/build/logo_both.png'));
|
||||
} else {
|
||||
image = nativeImage.createFromPath(path.join(__dirname, os.platform() === 'darwin' ? '/build/logo_mac.png' : '/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 (os.platform() === 'win32') {
|
||||
hsLocation = hsLocation.toUpperCase();
|
||||
siaLocation = siaLocation.toUpperCase();
|
||||
grabDriveLetters(hsLocation, siaLocation);
|
||||
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;
|
||||
}
|
||||
setImage(hsLocation, siaLocation);
|
||||
standardIPCReply(event, Constants.IPC_Detect_Mounts_Reply, {
|
||||
DriveLetters: driveLetters,
|
||||
Locations: {
|
||||
Hyperspace: hsLocation,
|
||||
Sia: siaLocation,
|
||||
},
|
||||
PIDS: {
|
||||
Hyperspace: results.Hyperspace.PID,
|
||||
Sia: results.Sia.PID,
|
||||
}
|
||||
Locations: locations,
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
grabDriveLetters('', '');
|
||||
setImage('', '');
|
||||
if (os.platform() === 'win32') {
|
||||
grabDriveLetters({});
|
||||
}
|
||||
setImage({});
|
||||
standardIPCReply(event, Constants.IPC_Detect_Mounts_Reply, {
|
||||
DriveLetters: driveLetters,
|
||||
}, error);
|
||||
@@ -379,57 +591,119 @@ ipcMain.on(Constants.IPC_Grab_UI_Releases, (event) => {
|
||||
});
|
||||
|
||||
ipcMain.on(Constants.IPC_Install_Dependency, (event, data) => {
|
||||
helpers
|
||||
if (data.Source.toLowerCase().endsWith('.dmg')) {
|
||||
helpers
|
||||
.executeAsync('open', ['-a', 'Finder', '-W', 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(()=> {
|
||||
.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);
|
||||
});
|
||||
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('open', ['-a', 'Finder', 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);
|
||||
const errorHandler = (pid, error) => {
|
||||
mountedPIDs.splice(mountedPIDs.indexOf(pid), 1);
|
||||
standardIPCReply(event, Constants.IPC_Unmount_Drive_Reply, {
|
||||
PID: -1,
|
||||
|
||||
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,
|
||||
}, 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);
|
||||
});
|
||||
};
|
||||
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) => {
|
||||
@@ -459,16 +733,18 @@ ipcMain.on(Constants.IPC_Set_Config_Values, (event, data) => {
|
||||
});
|
||||
|
||||
ipcMain.on(Constants.IPC_Shutdown, () => {
|
||||
app.quit();
|
||||
closeApplication();
|
||||
});
|
||||
|
||||
ipcMain.on(Constants.IPC_Unmount_Drive, (event, data) => {
|
||||
clearManualMountDetection(data.StorageType);
|
||||
|
||||
const dataDirectory = helpers.resolvePath(data.Directory);
|
||||
expectedUnmount[data.StorageType] = true;
|
||||
helpers
|
||||
.stopProcessByPID(data.PID)
|
||||
.then((pid)=> {
|
||||
if (mountedPIDs.indexOf(pid) === -1) {
|
||||
event.sender.send(Constants.IPC_Unmount_Drive_Reply);
|
||||
}
|
||||
.stopMountProcess(dataDirectory, data.Version, data.StorageType)
|
||||
.then((result)=> {
|
||||
console.log(result);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
|
||||
128
helpers.js
@@ -4,6 +4,7 @@ const os = require('os');
|
||||
const axios = require('axios');
|
||||
const exec = require('child_process').exec;
|
||||
const spawn = require('child_process').spawn;
|
||||
const Constants = require('./src/constants');
|
||||
|
||||
const tryParse = (j, def) => {
|
||||
try {
|
||||
@@ -37,18 +38,15 @@ module.exports.detectRepertoryMounts = (directory, version) => {
|
||||
});
|
||||
|
||||
process.on('exit', () => {
|
||||
resolve(tryParse(result, {
|
||||
Hyperspace: {
|
||||
let defaultData = {};
|
||||
for (const provider of Constants.PROVIDER_LIST) {
|
||||
defaultData[provider] = {
|
||||
Active: false,
|
||||
Location: '',
|
||||
PID: -1,
|
||||
},
|
||||
Sia: {
|
||||
Active: false,
|
||||
Location: '',
|
||||
PID: -1,
|
||||
},
|
||||
}));
|
||||
};
|
||||
}
|
||||
resolve(tryParse(result, defaultData));
|
||||
});
|
||||
process.unref();
|
||||
});
|
||||
@@ -120,15 +118,15 @@ module.exports.executeAndWait = command => {
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.executeAsync = (command) => {
|
||||
module.exports.executeAsync = (command, args=[]) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const launchProcess = (count, timeout) => {
|
||||
const processOptions = {
|
||||
detached: true,
|
||||
shell: true,
|
||||
shell: false,
|
||||
};
|
||||
|
||||
const process = new spawn(command, [], processOptions);
|
||||
const process = new spawn(command, args, processOptions);
|
||||
const pid = process.pid;
|
||||
|
||||
process.on('error', (err) => {
|
||||
@@ -136,15 +134,18 @@ module.exports.executeAsync = (command) => {
|
||||
reject(err, pid);
|
||||
} else {
|
||||
clearTimeout(timeout);
|
||||
setTimeout(()=>launchProcess(count, setTimeout(() => resolve(), 3000)), 1000);
|
||||
setTimeout(()=> launchProcess(count, setTimeout(() => resolve(), 3000)), 1000);
|
||||
}
|
||||
});
|
||||
|
||||
process.on('exit', (code) => {
|
||||
if (++count === 5) {
|
||||
reject(code, pid);
|
||||
} else {
|
||||
setTimeout(()=>launchProcess(count, setTimeout(() => resolve(), 3000)), 1000);
|
||||
if (code !== 0) {
|
||||
if (++count === 5) {
|
||||
reject(code, pid);
|
||||
} else {
|
||||
clearTimeout(timeout);
|
||||
setTimeout(() => launchProcess(count, setTimeout(() => resolve(), 3000)), 1000);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -155,28 +156,33 @@ module.exports.executeAsync = (command) => {
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.executeMount = (directory, version, storageType, location, exitCallback) => {
|
||||
module.exports.executeMount = (directory, version, storageType, location, noConsoleSupported, exitCallback) => {
|
||||
return new Promise((resolve) => {
|
||||
const processOptions = {
|
||||
detached: true,
|
||||
shell: true,
|
||||
detached: false,
|
||||
shell: os.platform() !== 'darwin',
|
||||
stdio: 'ignore',
|
||||
};
|
||||
|
||||
const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory');
|
||||
const args = [];
|
||||
if (storageType.toLowerCase() === 'hyperspace') {
|
||||
args.push('-hs');
|
||||
if (Constants.PROVIDER_ARG[storageType.toLowerCase()].length > 0) {
|
||||
args.push(Constants.PROVIDER_ARG[storageType.toLowerCase()]);
|
||||
}
|
||||
if (os.platform() === 'linux') {
|
||||
args.push("-o");
|
||||
args.push("big_writes");
|
||||
}
|
||||
if (os.platform() === 'win32') {
|
||||
|
||||
if ((os.platform() === 'linux') || (os.platform() === 'darwin')) {
|
||||
args.push('-o');
|
||||
args.push('big_writes');
|
||||
args.push('-f');
|
||||
if (noConsoleSupported) {
|
||||
args.push('-nc');
|
||||
}
|
||||
} else if (os.platform() === 'win32') {
|
||||
args.push('-hidden');
|
||||
}
|
||||
args.push(location);
|
||||
|
||||
const process = new spawn(command, args, processOptions);
|
||||
let process = new spawn(command, args, processOptions);
|
||||
const pid = process.pid;
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
@@ -187,11 +193,11 @@ module.exports.executeMount = (directory, version, storageType, location, exitCa
|
||||
clearTimeout(timeout);
|
||||
exitCallback(err, pid);
|
||||
});
|
||||
|
||||
process.on('exit', (code) => {
|
||||
clearTimeout(timeout);
|
||||
exitCallback(code, pid);
|
||||
});
|
||||
process.unref();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -206,8 +212,8 @@ module.exports.getConfig = (directory, version, storageType) => {
|
||||
const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory');
|
||||
const args = [];
|
||||
args.push('-dc');
|
||||
if (storageType.toLowerCase() === 'hyperspace') {
|
||||
args.push('-hs');
|
||||
if (Constants.PROVIDER_ARG[storageType.toLowerCase()].length > 0) {
|
||||
args.push(Constants.PROVIDER_ARG[storageType.toLowerCase()]);
|
||||
}
|
||||
|
||||
const process = new spawn(command, args, processOptions);
|
||||
@@ -252,8 +258,8 @@ module.exports.getConfigTemplate = (directory, version, storageType) => {
|
||||
const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory');
|
||||
const args = [];
|
||||
args.push('-gt');
|
||||
if (storageType.toLowerCase() === 'hyperspace') {
|
||||
args.push('-hs');
|
||||
if (Constants.PROVIDER_ARG[storageType.toLowerCase()].length > 0) {
|
||||
args.push(Constants.PROVIDER_ARG[storageType.toLowerCase()]);
|
||||
}
|
||||
|
||||
const process = new spawn(command, args, processOptions);
|
||||
@@ -276,8 +282,8 @@ module.exports.getConfigTemplate = (directory, version, storageType) => {
|
||||
|
||||
module.exports.getMissingDependencies = dependencies => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!dependencies || (dependencies.length === 0)) {
|
||||
reject(Error('Dependency list is empty'));
|
||||
if (!dependencies) {
|
||||
reject(Error('Dependency list is invalid'));
|
||||
}
|
||||
|
||||
let missing = [];
|
||||
@@ -334,7 +340,7 @@ module.exports.getMissingDependencies = dependencies => {
|
||||
} else {
|
||||
for (const dep of dependencies) {
|
||||
try {
|
||||
if (!fs.lstatSync(dep.file).isFile()) {
|
||||
if (!(fs.lstatSync(dep.file).isFile() || fs.lstatSync(dep.file).isSymbolicLink())) {
|
||||
missing.push(dep);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -415,8 +421,8 @@ module.exports.setConfigValue = (name, value, directory, storageType, version) =
|
||||
args.push('-set');
|
||||
args.push(name);
|
||||
args.push(value);
|
||||
if (storageType.toLowerCase() === 'hyperspace') {
|
||||
args.push('-hs');
|
||||
if (Constants.PROVIDER_ARG[storageType.toLowerCase()].length > 0) {
|
||||
args.push(Constants.PROVIDER_ARG[storageType.toLowerCase()]);
|
||||
}
|
||||
|
||||
const process = new spawn(command, args, processOptions);
|
||||
@@ -433,27 +439,51 @@ module.exports.setConfigValue = (name, value, directory, storageType, version) =
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.stopProcessByPID = pid => {
|
||||
module.exports.stopMountProcess = (directory, version, storageType) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const processOptions = {
|
||||
detached: true,
|
||||
shell: false,
|
||||
detached: os.platform() === 'darwin',
|
||||
shell: os.platform() !== 'darwin',
|
||||
windowsHide: true,
|
||||
};
|
||||
|
||||
const command = (os.platform() === 'win32') ? 'taskkill.exe' : '';
|
||||
const args = [];
|
||||
args.push('/PID');
|
||||
args.push(pid);
|
||||
const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory');
|
||||
const args = ['-unmount'];
|
||||
if (Constants.PROVIDER_ARG[storageType.toLowerCase()].length > 0) {
|
||||
args.push(Constants.PROVIDER_ARG[storageType.toLowerCase()]);
|
||||
}
|
||||
|
||||
const process = new spawn(command, args, processOptions);
|
||||
|
||||
const pid = process.pid;
|
||||
process.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
process.on('exit', () => {
|
||||
setTimeout(()=>resolve(pid), 3000);
|
||||
process.on('exit', (code) => {
|
||||
resolve({
|
||||
PID: pid,
|
||||
Code: code,
|
||||
});
|
||||
});
|
||||
process.unref();
|
||||
|
||||
if (os.platform() === 'darwin') {
|
||||
process.unref();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.stopMountProcessSync = (directory, version, storageType) => {
|
||||
const processOptions = {
|
||||
detached: true,
|
||||
shell: os.platform() !== 'darwin',
|
||||
windowsHide: true,
|
||||
};
|
||||
|
||||
const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory');
|
||||
const args = ['-unmount'];
|
||||
if (Constants.PROVIDER_ARG[storageType.toLowerCase()].length > 0) {
|
||||
args.push(Constants.PROVIDER_ARG[storageType.toLowerCase()]);
|
||||
}
|
||||
|
||||
const process = new spawn(command, args, processOptions);
|
||||
process.unref();
|
||||
};
|
||||
61
package.json
@@ -1,10 +1,13 @@
|
||||
{
|
||||
"name": "repertory-ui",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"private": true,
|
||||
"author": "scott.e.graves@gmail.com",
|
||||
"description": "GUI for Repertory - Repertory allows you to mount Hyperspace, Sia and/or SiaPrime blockchain storage solutions via FUSE on Linux/OS X or via WinFSP on Windows.",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.1.1",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.10",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.6.1",
|
||||
"@fortawesome/react-fontawesome": "^0.1.0",
|
||||
"auto-launch": "^5.0.5",
|
||||
"autoprefixer": "7.1.6",
|
||||
@@ -17,12 +20,12 @@
|
||||
"babel-runtime": "6.26.0",
|
||||
"case-sensitive-paths-webpack-plugin": "2.1.1",
|
||||
"chalk": "1.1.3",
|
||||
"color": "^3.0.0",
|
||||
"color": "^3.1.0",
|
||||
"color-string": "^1.5.2",
|
||||
"css-loader": "0.28.7",
|
||||
"dotenv": "4.0.0",
|
||||
"dotenv-expand": "4.2.0",
|
||||
"electron-debug": "^2.0.0",
|
||||
"electron-debug": "^2.1.0",
|
||||
"eslint": "4.10.0",
|
||||
"eslint-config-react-app": "^2.1.0",
|
||||
"eslint-loader": "1.9.0",
|
||||
@@ -35,23 +38,23 @@
|
||||
"fs-extra": "3.0.1",
|
||||
"html-webpack-plugin": "2.29.0",
|
||||
"jest": "20.0.4",
|
||||
"node-schedule": "^1.3.0",
|
||||
"npm": "^6.2.0",
|
||||
"node-schedule": "^1.3.1",
|
||||
"npm": "^6.6.0",
|
||||
"object-assign": "4.1.1",
|
||||
"postcss-flexbugs-fixes": "3.2.0",
|
||||
"postcss-loader": "2.0.8",
|
||||
"promise": "8.0.1",
|
||||
"raf": "3.4.0",
|
||||
"react": "^16.4.1",
|
||||
"react-css-modules": "^4.7.4",
|
||||
"react-dev-utils": "^5.0.1",
|
||||
"react-dom": "^16.4.1",
|
||||
"react-loader-spinner": "^2.0.6",
|
||||
"react-tooltip": "^3.8.4",
|
||||
"react": "^16.6.1",
|
||||
"react-css-modules": "^4.7.8",
|
||||
"react-dev-utils": "^5.0.3",
|
||||
"react-dom": "^16.6.1",
|
||||
"react-loader-spinner": "^2.3.0",
|
||||
"react-tooltip": "^3.9.0",
|
||||
"resolve": "1.6.0",
|
||||
"style-loader": "0.19.0",
|
||||
"sw-precache-webpack-plugin": "0.11.4",
|
||||
"unzipper": "^0.9.3",
|
||||
"unzipper": "^0.9.6",
|
||||
"url-loader": "0.6.2",
|
||||
"webpack": "3.8.1",
|
||||
"webpack-dev-server": "2.9.4",
|
||||
@@ -63,14 +66,20 @@
|
||||
"start": "node scripts/start.js",
|
||||
"build": "node scripts/build.js",
|
||||
"test": "node scripts/test.js --env=jsdom",
|
||||
"electron-dev": "cross-env ELECTRON_START_URL=http://localhost:3000 electron .",
|
||||
"pack": "npm run build && electron-builder --dir",
|
||||
"dist": "npm run build && electron-builder"
|
||||
"electron-dev": "cross-env ELECTRON_START_URL=http://localhost:3000 electron %NODE_DEBUG_OPTION% .",
|
||||
"electron-dev-unix": "cross-env ELECTRON_START_URL=http://localhost:3000 electron $NODE_DEBUG_OPTION .",
|
||||
"pack": "npm run build && electron-builder --dir --x64",
|
||||
"dist": "npm run build && electron-builder --x64",
|
||||
"dist-all": "npm run build && electron-builder --x64 --win --linux --mac",
|
||||
"dist-mac": "npm run build && electron-builder --x64 --mac",
|
||||
"dist-linux": "npm run build && electron-builder --x64 --linux",
|
||||
"dist-win": "npm run build && electron-builder --x64 --win",
|
||||
"postinstall": "electron-builder install-app-deps"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^5.2.0",
|
||||
"electron": "^3.0.2",
|
||||
"electron-builder": "^20.28.4",
|
||||
"electron": "^4.1.0",
|
||||
"electron-builder": "^20.38.5",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"webpack-browser-plugin": "^1.0.20"
|
||||
},
|
||||
@@ -120,6 +129,7 @@
|
||||
"homepage": "./",
|
||||
"build": {
|
||||
"appId": "repertory-ui",
|
||||
"artifactName": "${productName}_${version}_${os}_${arch}.${ext}",
|
||||
"files": [
|
||||
"./electron.js",
|
||||
"./src/constants.js",
|
||||
@@ -128,10 +138,19 @@
|
||||
"./helpers.js"
|
||||
],
|
||||
"linux": {
|
||||
"icon": "./build/icon.icns"
|
||||
"category": "Utility",
|
||||
"icon": "./build/logo.png",
|
||||
"target": "AppImage"
|
||||
},
|
||||
"mac": {
|
||||
"category": "public.app-category.utilities",
|
||||
"icon": "./build/icon_color.icns",
|
||||
"target": "dmg",
|
||||
"darkModeSupport": true
|
||||
},
|
||||
"win": {
|
||||
"icon": "./build/icon.ico"
|
||||
"icon": "./build/icon.ico",
|
||||
"target": "nsis"
|
||||
},
|
||||
"directories": {
|
||||
"buildResources": "public"
|
||||
|
||||
|
Before Width: | Height: | Size: 361 KiB |
BIN
public/icon_color.icns
Normal file
|
Before Width: | Height: | Size: 361 KiB |
BIN
public/logo.xcf
Normal file
BIN
public/logo_both_mac.png
Normal file
|
After Width: | Height: | Size: 269 B |
|
Before Width: | Height: | Size: 3.4 KiB |
BIN
public/logo_mac.png
Normal file
|
After Width: | Height: | Size: 265 B |
|
Before Width: | Height: | Size: 3.4 KiB |
@@ -1,24 +1,24 @@
|
||||
{
|
||||
"Locations": {
|
||||
"win32": {
|
||||
"1.0.1": {
|
||||
"1.0.2": {
|
||||
"hash": "",
|
||||
"urls": [
|
||||
"https://sia.pixeldrain.com/api/file/Alo1IF1u"
|
||||
]
|
||||
},
|
||||
"1.0.0": {
|
||||
"urls": ["https://pixeldrain.com/u/4oJeVntd"]
|
||||
}
|
||||
},
|
||||
"darwin": {
|
||||
"1.0.2": {
|
||||
"hash": "",
|
||||
"urls": [
|
||||
"https://sia.pixeldrain.com/api/file/qXM6pVZZ"
|
||||
]
|
||||
"urls": ["https://pixeldrain.com/u/OtxPlbOI"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Versions": {
|
||||
"win32": [
|
||||
"1.0.1",
|
||||
"1.0.0"
|
||||
"1.0.2"
|
||||
],
|
||||
"darwin": [
|
||||
"1.0.2"
|
||||
]
|
||||
}
|
||||
}
|
||||
535
src/App.js
@@ -1,4 +1,4 @@
|
||||
import React, {Component} from 'react';
|
||||
import React from 'react';
|
||||
import axios from 'axios';
|
||||
import styles from './App.css';
|
||||
import Box from './components/UI/Box/Box';
|
||||
@@ -15,33 +15,35 @@ import ReleaseVersionDisplay from './components/ReleaseVersionDisplay/ReleaseVer
|
||||
import Text from './components/UI/Text/Text';
|
||||
import UpgradeIcon from './components/UpgradeIcon/UpgradeIcon';
|
||||
import UpgradeUI from './components/UpgradeUI/UpgradeUI';
|
||||
import IPCContainer from './containers/IPCContainer/IPCContainer';
|
||||
|
||||
const Constants = require('./constants');
|
||||
const Scheduler = require('node-schedule');
|
||||
|
||||
let ipcRenderer = null;
|
||||
if (!process.versions.hasOwnProperty('electron')) {
|
||||
ipcRenderer = ((window && window.require) ? window.require('electron').ipcRenderer : null);
|
||||
}
|
||||
|
||||
class App extends Component {
|
||||
class App extends IPCContainer {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
if (ipcRenderer) {
|
||||
ipcRenderer.on(Constants.IPC_Check_Installed_Reply, this.onCheckInstalledReply);
|
||||
ipcRenderer.on(Constants.IPC_Download_File_Complete, this.onDownloadFileComplete);
|
||||
ipcRenderer.on(Constants.IPC_Download_File_Progress, this.onDownloadFileProgress);
|
||||
ipcRenderer.on(Constants.IPC_Extract_Release_Complete, this.onExtractReleaseComplete);
|
||||
ipcRenderer.on(Constants.IPC_Get_State_Reply, this.onGetStateReply);
|
||||
ipcRenderer.on(Constants.IPC_Grab_Releases_Reply, this.onGrabReleasesReply);
|
||||
ipcRenderer.on(Constants.IPC_Grab_UI_Releases_Reply, this.onGrabUiReleasesReply);
|
||||
ipcRenderer.on(Constants.IPC_Install_Dependency_Reply, this.onInstallDependencyReply);
|
||||
ipcRenderer.on(Constants.IPC_Install_Upgrade_Reply, this.onInstallUpgradeReply);
|
||||
|
||||
ipcRenderer.send(Constants.IPC_Get_State, Constants.DATA_LOCATIONS[this.props.platform]);
|
||||
Scheduler.scheduleJob('23 11 * * *', this.updateCheckScheduledJob);
|
||||
for (const provider of Constants.PROVIDER_LIST) {
|
||||
this.state[provider] = {
|
||||
AutoMount: false,
|
||||
AutoRestart: false,
|
||||
MountLocation: '',
|
||||
}
|
||||
}
|
||||
|
||||
this.setRequestHandler(Constants.IPC_Check_Installed_Reply, this.onCheckInstalledReply);
|
||||
this.setRequestHandler(Constants.IPC_Download_File_Complete, this.onDownloadFileComplete);
|
||||
this.setRequestHandler(Constants.IPC_Download_File_Progress, this.onDownloadFileProgress);
|
||||
this.setRequestHandler(Constants.IPC_Extract_Release_Complete, this.onExtractReleaseComplete);
|
||||
this.setRequestHandler(Constants.IPC_Get_State_Reply, this.onGetStateReply);
|
||||
this.setRequestHandler(Constants.IPC_Grab_Releases_Reply, this.onGrabReleasesReply);
|
||||
this.setRequestHandler(Constants.IPC_Grab_UI_Releases_Reply, this.onGrabUiReleasesReply);
|
||||
this.setRequestHandler(Constants.IPC_Install_Dependency_Reply, this.onInstallDependencyReply);
|
||||
this.setRequestHandler(Constants.IPC_Install_Upgrade_Reply, this.onInstallUpgradeReply);
|
||||
|
||||
this.sendRequest(Constants.IPC_Get_State, Constants.DATA_LOCATIONS[this.props.platform]);
|
||||
Scheduler.scheduleJob('23 11 * * *', this.updateCheckScheduledJob);
|
||||
}
|
||||
|
||||
state = {
|
||||
@@ -60,14 +62,9 @@ class App extends Component {
|
||||
DownloadingRelease: false,
|
||||
DownloadingUpgrade: false,
|
||||
ExtractActive: false,
|
||||
Hyperspace: {
|
||||
AutoMount: false,
|
||||
MountLocation: '',
|
||||
},
|
||||
LocationsLookup: {},
|
||||
MissingDependencies: [],
|
||||
MountsBusy: false,
|
||||
Platform: 'unknown',
|
||||
Release: 3,
|
||||
ReleaseTypes: [
|
||||
'Release',
|
||||
@@ -76,10 +73,6 @@ class App extends Component {
|
||||
'Alpha',
|
||||
],
|
||||
InstalledVersion: 'none',
|
||||
Sia: {
|
||||
AutoMount: false,
|
||||
MountLocation: '',
|
||||
},
|
||||
UpgradeAvailable: false,
|
||||
UpgradeData: {},
|
||||
UpgradeDismissed: false,
|
||||
@@ -101,30 +94,24 @@ class App extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
checkVersionInstalled = (release, version, versionLookup) => {
|
||||
if (!versionLookup) {
|
||||
versionLookup = this.state.VersionLookup;
|
||||
}
|
||||
|
||||
const selectedVersion = versionLookup[this.state.ReleaseTypes[release]][version];
|
||||
checkVersionInstalled = () => {
|
||||
this.setState({
|
||||
AllowDownload: false,
|
||||
});
|
||||
|
||||
if (selectedVersion !== 'unavailable') {
|
||||
if (ipcRenderer) {
|
||||
}, ()=> {
|
||||
const selectedVersion = this.getSelectedVersion();
|
||||
if (selectedVersion !== 'unavailable') {
|
||||
let dependencies = [];
|
||||
if (this.state.LocationsLookup[selectedVersion] && this.state.LocationsLookup[selectedVersion].dependencies) {
|
||||
dependencies = this.state.LocationsLookup[selectedVersion].dependencies;
|
||||
}
|
||||
|
||||
ipcRenderer.send(Constants.IPC_Check_Installed, {
|
||||
this.sendRequest(Constants.IPC_Check_Installed, {
|
||||
Dependencies: dependencies,
|
||||
Directory: Constants.DATA_LOCATIONS[this.props.platform],
|
||||
Version: selectedVersion,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
closeErrorDisplay = () => {
|
||||
@@ -133,9 +120,7 @@ class App extends Component {
|
||||
}
|
||||
|
||||
if (this.state.ErrorCritical) {
|
||||
if (ipcRenderer) {
|
||||
ipcRenderer.send(Constants.IPC_Shutdown);
|
||||
}
|
||||
this.sendRequest(Constants.IPC_Shutdown);
|
||||
} else {
|
||||
this.setState({
|
||||
DisplayError: false,
|
||||
@@ -144,51 +129,63 @@ class App extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
componentWillUnmount = () => {
|
||||
if (ipcRenderer) {
|
||||
ipcRenderer.removeListener(Constants.IPC_Check_Installed_Reply, this.onCheckInstalledReply);
|
||||
ipcRenderer.removeListener(Constants.IPC_Download_File_Complete, this.onDownloadFileComplete);
|
||||
ipcRenderer.removeListener(Constants.IPC_Download_File_Progress, this.onDownloadFileProgress);
|
||||
ipcRenderer.removeListener(Constants.IPC_Extract_Release_Complete, this.onExtractReleaseComplete);
|
||||
ipcRenderer.removeListener(Constants.IPC_Get_State_Reply, this.onGetStateReply);
|
||||
ipcRenderer.removeListener(Constants.IPC_Grab_Releases_Reply, this.onGrabReleasesReply);
|
||||
ipcRenderer.removeListener(Constants.IPC_Grab_UI_Releases_Reply, this.onGrabUiReleasesReply);
|
||||
ipcRenderer.removeListener(Constants.IPC_Install_Dependency_Reply, this.onInstallDependencyReply);
|
||||
ipcRenderer.removeListener(Constants.IPC_Install_Upgrade_Reply, this.onInstallUpgradeReply);
|
||||
}
|
||||
extractFileNameFromURL = url => {
|
||||
const parts = url.split('/');
|
||||
return parts[parts.length - 1];
|
||||
};
|
||||
|
||||
extractRelease = (data) => {
|
||||
if (data.Success) {
|
||||
const selectedVersion = this.getSelectedVersion();
|
||||
this.sendRequest(Constants.IPC_Extract_Release, {
|
||||
Directory: Constants.DATA_LOCATIONS[this.props.platform],
|
||||
Source: data.Destination,
|
||||
Version: selectedVersion,
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({
|
||||
DownloadActive: false,
|
||||
DownloadProgress: 0.0,
|
||||
DownloadingRelease: false,
|
||||
ExtractActive: data.Success,
|
||||
DownloadName: '',
|
||||
});
|
||||
};
|
||||
|
||||
getSelectedVersion = () => {
|
||||
return this.state.VersionLookup[this.state.ReleaseTypes[this.state.Release]][this.state.Version];
|
||||
};
|
||||
|
||||
grabReleases = () => {
|
||||
if (this.props.platform !== 'unknown') {
|
||||
if (ipcRenderer) {
|
||||
ipcRenderer.send(Constants.IPC_Grab_Releases);
|
||||
ipcRenderer.send(Constants.IPC_Grab_UI_Releases);
|
||||
}
|
||||
this.sendRequest(Constants.IPC_Grab_Releases);
|
||||
this.sendRequest(Constants.IPC_Grab_UI_Releases);
|
||||
}
|
||||
};
|
||||
|
||||
handleAutoMountChanged = (storageType, e) => {
|
||||
let sia = {
|
||||
...this.state.Sia
|
||||
const state = {
|
||||
...this.state[storageType],
|
||||
AutoMount: e.target.checked,
|
||||
};
|
||||
this.setState({
|
||||
[storageType]: state,
|
||||
}, ()=> {
|
||||
this.saveState();
|
||||
});
|
||||
};
|
||||
|
||||
let hyperspace = {
|
||||
...this.state.Hyperspace
|
||||
handleAutoRestartChanged = (storageType, e) => {
|
||||
const state = {
|
||||
...this.state[storageType],
|
||||
AutoRestart: e.target.checked,
|
||||
};
|
||||
|
||||
if (storageType === 'Hyperspace') {
|
||||
hyperspace.AutoMount = e.target.checked;
|
||||
this.setState({
|
||||
Hyperspace: hyperspace,
|
||||
});
|
||||
} else if (storageType === 'Sia') {
|
||||
sia.AutoMount = e.target.checked;
|
||||
this.setState({
|
||||
Sia: sia,
|
||||
});
|
||||
}
|
||||
|
||||
this.saveState(this.state.Release, this.state.Version, sia, hyperspace);
|
||||
this.setState({
|
||||
[storageType]: state,
|
||||
}, ()=> {
|
||||
this.saveState();
|
||||
});
|
||||
};
|
||||
|
||||
handleConfigClicked = (storageType) => {
|
||||
@@ -204,22 +201,17 @@ class App extends Component {
|
||||
};
|
||||
|
||||
handleDependencyDownload = (url) => {
|
||||
if (ipcRenderer) {
|
||||
const items = url.split('/');
|
||||
const fileName = items[items.length - 1];
|
||||
|
||||
this.setState({
|
||||
DownloadActive: true,
|
||||
DownloadingDependency: true,
|
||||
DownloadName: fileName,
|
||||
});
|
||||
|
||||
ipcRenderer.send(Constants.IPC_Download_File, {
|
||||
this.setState({
|
||||
DownloadActive: true,
|
||||
DownloadingDependency: true,
|
||||
DownloadName: this.extractFileNameFromURL(url),
|
||||
}, ()=> {
|
||||
this.sendRequest(Constants.IPC_Download_File, {
|
||||
Directory: Constants.DATA_LOCATIONS[this.props.platform],
|
||||
Filename: fileName,
|
||||
Filename: this.state.DownloadName,
|
||||
URL: url,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleMountLocationChanged = (storageType, location) => {
|
||||
@@ -229,15 +221,9 @@ class App extends Component {
|
||||
};
|
||||
this.setState({
|
||||
[storageType]: state,
|
||||
}, ()=> {
|
||||
this.saveState();
|
||||
});
|
||||
const hyperspace = (storageType === 'Hyperspace') ? state : {
|
||||
...this.state.Hyperspace,
|
||||
};
|
||||
const sia = storageType === 'Sia' ? state : {
|
||||
...this.state.Sia,
|
||||
};
|
||||
|
||||
this.saveState(this.state.Release, this.state.Version, sia, hyperspace);
|
||||
};
|
||||
|
||||
handleReleaseChanged = (e) => {
|
||||
@@ -246,54 +232,81 @@ class App extends Component {
|
||||
this.setState({
|
||||
Release: val,
|
||||
Version: versionIndex
|
||||
}, ()=> {
|
||||
this.saveState();
|
||||
this.checkVersionInstalled( );
|
||||
});
|
||||
this.saveState(val, versionIndex, this.state.Sia, this.state.Hyperspace);
|
||||
this.checkVersionInstalled(val, versionIndex);
|
||||
};
|
||||
|
||||
handleReleaseDownload = () => {
|
||||
const selectedVersion = this.state.VersionLookup[this.state.ReleaseTypes[this.state.Release]][this.state.Version];
|
||||
const selectedVersion = this.getSelectedVersion();
|
||||
const fileName = selectedVersion + '.zip';
|
||||
if (ipcRenderer) {
|
||||
this.setState({
|
||||
DownloadActive: true,
|
||||
DownloadingRelease: true,
|
||||
DownloadName: fileName,
|
||||
});
|
||||
|
||||
ipcRenderer.send(Constants.IPC_Download_File, {
|
||||
this.setState({
|
||||
DownloadActive: true,
|
||||
DownloadingRelease: true,
|
||||
DownloadName: fileName,
|
||||
}, () => {
|
||||
this.sendRequest(Constants.IPC_Download_File, {
|
||||
Directory: Constants.DATA_LOCATIONS[this.props.platform],
|
||||
Filename: fileName,
|
||||
Filename: this.state.DownloadName,
|
||||
URL: this.state.LocationsLookup[selectedVersion].urls[0],
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleUIDownload = () => {
|
||||
if (ipcRenderer) {
|
||||
this.setState({
|
||||
DownloadActive: true,
|
||||
DownloadingUpgrade: true,
|
||||
DownloadName: 'UI Upgrade',
|
||||
});
|
||||
|
||||
ipcRenderer.send(Constants.IPC_Download_File, {
|
||||
this.setState({
|
||||
DownloadActive: true,
|
||||
DownloadingUpgrade: true,
|
||||
DownloadName: 'UI Upgrade',
|
||||
}, ()=> {
|
||||
const url = this.state.UpgradeData.urls[0];
|
||||
this.sendRequest(Constants.IPC_Download_File, {
|
||||
Directory: Constants.DATA_LOCATIONS[this.props.platform],
|
||||
Filename: this.props.platform === 'win32' ? 'upgrade.exe' : 'upgrade',
|
||||
URL: this.state.UpgradeData.urls[0],
|
||||
Filename: this.props.platform === 'win32' ? 'upgrade.exe' : this.extractFileNameFromURL(url),
|
||||
URL: url,
|
||||
});
|
||||
} else {
|
||||
this.setState({UpgradeDismissed: true});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleVersionChanged = (e) => {
|
||||
const val = parseInt(e.target.value, 10);
|
||||
this.setState({
|
||||
Version: val
|
||||
Version: parseInt(e.target.value, 10),
|
||||
}, ()=> {
|
||||
this.saveState();
|
||||
this.checkVersionInstalled( );
|
||||
});
|
||||
this.saveState(this.state.Release, val, this.state.Sia, this.state.Hyperspace);
|
||||
this.checkVersionInstalled(this.state.Release, val);
|
||||
};
|
||||
|
||||
installDependency = data => {
|
||||
if (data.Success) {
|
||||
this.sendRequest(Constants.IPC_Install_Dependency, {
|
||||
Source: data.Destination,
|
||||
URL: data.URL,
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({
|
||||
DownloadActive: false,
|
||||
DownloadProgress: 0.0,
|
||||
DownloadingDependency: data.Success,
|
||||
DownloadName: '',
|
||||
});
|
||||
};
|
||||
|
||||
installUpgrade = data => {
|
||||
if (data.Success) {
|
||||
this.sendRequest(Constants.IPC_Install_Upgrade, {
|
||||
Source: data.Destination,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
DownloadActive: false,
|
||||
DownloadProgress: 0.0,
|
||||
DownloadingUpgrade: false,
|
||||
DownloadName: '',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
notifyAutoMountProcessed = () => {
|
||||
@@ -336,48 +349,11 @@ class App extends Component {
|
||||
|
||||
onDownloadFileComplete = (event, arg) => {
|
||||
if (this.state.DownloadingRelease) {
|
||||
if (arg.data.Success) {
|
||||
const selectedVersion = this.state.VersionLookup[this.state.ReleaseTypes[this.state.Release]][this.state.Version];
|
||||
ipcRenderer.send(Constants.IPC_Extract_Release, {
|
||||
Directory: Constants.DATA_LOCATIONS[this.props.platform],
|
||||
Source: arg.data.Destination,
|
||||
Version: selectedVersion,
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({
|
||||
DownloadActive: false,
|
||||
DownloadProgress: 0.0,
|
||||
DownloadingRelease: false,
|
||||
ExtractActive: arg.data.Success,
|
||||
DownloadName: '',
|
||||
});
|
||||
this.extractRelease(arg.data);
|
||||
} else if (this.state.DownloadingDependency) {
|
||||
if (arg.data.Success) {
|
||||
ipcRenderer.send(Constants.IPC_Install_Dependency, {
|
||||
Source: arg.data.Destination,
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({
|
||||
DownloadActive: false,
|
||||
DownloadProgress: 0.0,
|
||||
DownloadingDependency: arg.data.Success,
|
||||
DownloadName: '',
|
||||
});
|
||||
this.installDependency(arg.data);
|
||||
} else if (this.state.DownloadingUpgrade) {
|
||||
if (arg.data.Success) {
|
||||
ipcRenderer.send(Constants.IPC_Install_Upgrade, {
|
||||
Source: arg.data.Destination,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
DownloadActive: false,
|
||||
DownloadProgress: 0.0,
|
||||
DownloadingUpgrade: false,
|
||||
DownloadName: '',
|
||||
});
|
||||
}
|
||||
this.installUpgrade(arg.data);
|
||||
} else {
|
||||
this.setState({
|
||||
DownloadActive: false,
|
||||
@@ -394,32 +370,41 @@ class App extends Component {
|
||||
};
|
||||
|
||||
onExtractReleaseComplete = (event, arg) => {
|
||||
ipcRenderer.send(Constants.IPC_Delete_File, {
|
||||
this.sendRequest(Constants.IPC_Delete_File, {
|
||||
FilePath: arg.data.Source,
|
||||
});
|
||||
|
||||
this.setState({
|
||||
ExtractActive: false,
|
||||
}, ()=> {
|
||||
this.checkVersionInstalled( );
|
||||
});
|
||||
this.checkVersionInstalled(this.state.Release, this.state.Version);
|
||||
};
|
||||
|
||||
onGetStateReply = (event, arg) => {
|
||||
if (arg.data) {
|
||||
if (arg.data.Hyperspace.AutoMount === undefined) {
|
||||
arg.data.Hyperspace['AutoMount'] = false;
|
||||
}
|
||||
if (arg.data.Sia.AutoMount === undefined) {
|
||||
arg.data.Sia['AutoMount'] = false;
|
||||
}
|
||||
this.setState({
|
||||
Hyperspace: arg.data.Hyperspace,
|
||||
let state = {
|
||||
Release: arg.data.Release,
|
||||
Sia: arg.data.Sia,
|
||||
Version: arg.data.Version,
|
||||
};
|
||||
|
||||
for (const provider of Constants.PROVIDER_LIST) {
|
||||
let data = arg.data[provider] || this.state[provider];
|
||||
if (data.AutoMount === undefined) {
|
||||
data['AutoMount'] = false;
|
||||
}
|
||||
if (data.AutoRestart === undefined) {
|
||||
data['AutoRestart'] = false;
|
||||
}
|
||||
state[provider] = data;
|
||||
}
|
||||
|
||||
this.setState(state, ()=> {
|
||||
this.grabReleases();
|
||||
});
|
||||
} else {
|
||||
this.grabReleases();
|
||||
}
|
||||
this.grabReleases();
|
||||
};
|
||||
|
||||
onGrabReleasesReply = ()=> {
|
||||
@@ -428,7 +413,7 @@ class App extends Component {
|
||||
let version = this.state.Version;
|
||||
if ((version === -1) || !versionLookup[this.state.ReleaseTypes[this.state.Release]][version]) {
|
||||
version = latestVersion;
|
||||
this.saveState(this.state.Release, version, this.state.Sia, this.state.Hyperspace);
|
||||
this.saveState(version);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
@@ -437,74 +422,80 @@ class App extends Component {
|
||||
Version: version,
|
||||
VersionAvailable: version !== latestVersion,
|
||||
VersionLookup: versionLookup,
|
||||
}, () => {
|
||||
this.checkVersionInstalled( );
|
||||
});
|
||||
|
||||
this.checkVersionInstalled(this.state.Release, version, versionLookup);
|
||||
};
|
||||
|
||||
axios.get(Constants.RELEASES_URL)
|
||||
.then(response => {
|
||||
const versionLookup = {
|
||||
Alpha: response.data.Versions.Alpha[this.props.platform],
|
||||
Beta: response.data.Versions.Beta[this.props.platform],
|
||||
RC: response.data.Versions.RC[this.props.platform],
|
||||
Release: response.data.Versions.Release[this.props.platform],
|
||||
};
|
||||
const locationsLookup = {
|
||||
...response.data.Locations[this.props.platform],
|
||||
};
|
||||
axios
|
||||
.get(Constants.RELEASES_URL)
|
||||
.then(response => {
|
||||
const versionLookup = {
|
||||
Alpha: response.data.Versions.Alpha[this.props.platform],
|
||||
Beta: response.data.Versions.Beta[this.props.platform],
|
||||
RC: response.data.Versions.RC[this.props.platform],
|
||||
Release: response.data.Versions.Release[this.props.platform],
|
||||
};
|
||||
const locationsLookup = {
|
||||
...response.data.Locations[this.props.platform],
|
||||
};
|
||||
|
||||
window.localStorage.setItem('releases', JSON.stringify({
|
||||
LocationsLookup: locationsLookup,
|
||||
VersionLookup: versionLookup
|
||||
}));
|
||||
|
||||
doUpdate(locationsLookup, versionLookup);
|
||||
}).catch(error => {
|
||||
const releases = window.localStorage.getItem('releases');
|
||||
if (releases && (releases.length > 0)) {
|
||||
const obj = JSON.parse(releases);
|
||||
const locationsLookup = obj.LocationsLookup;
|
||||
const versionLookup = obj.VersionLookup;
|
||||
window.localStorage.setItem('releases', JSON.stringify({
|
||||
LocationsLookup: locationsLookup,
|
||||
VersionLookup: versionLookup
|
||||
}));
|
||||
|
||||
doUpdate(locationsLookup, versionLookup);
|
||||
} else {
|
||||
this.setErrorState(error, null, true);
|
||||
}
|
||||
});
|
||||
}).catch(error => {
|
||||
const releases = window.localStorage.getItem('releases');
|
||||
if (releases && (releases.length > 0)) {
|
||||
const obj = JSON.parse(releases);
|
||||
const locationsLookup = obj.LocationsLookup;
|
||||
const versionLookup = obj.VersionLookup;
|
||||
|
||||
doUpdate(locationsLookup, versionLookup);
|
||||
} else {
|
||||
this.setErrorState(error, null, true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onGrabUiReleasesReply = ()=> {
|
||||
axios.get(Constants.UI_RELEASES_URL)
|
||||
.then(response => {
|
||||
const data = response.data;
|
||||
if (data.Versions &&
|
||||
data.Versions[this.props.platform] &&
|
||||
(data.Versions[this.props.platform].length > 0) &&
|
||||
(data.Versions[this.props.platform][0] !== this.props.version)) {
|
||||
axios
|
||||
.get(Constants.UI_RELEASES_URL)
|
||||
.then(response => {
|
||||
const data = response.data;
|
||||
if (data.Versions &&
|
||||
data.Versions[this.props.platform] &&
|
||||
(data.Versions[this.props.platform].length > 0) &&
|
||||
(data.Versions[this.props.platform][0] !== this.props.version)) {
|
||||
this.setState({
|
||||
UpgradeAvailable: true,
|
||||
UpgradeDismissed: false,
|
||||
UpgradeData: data.Locations[this.props.platform][data.Versions[this.props.platform][0]],
|
||||
});
|
||||
}
|
||||
}).catch(() => {
|
||||
this.setState({
|
||||
UpgradeAvailable: true,
|
||||
UpgradeDismissed: false,
|
||||
UpgradeData: data.Locations[this.props.platform][data.Versions[this.props.platform][0]],
|
||||
UpgradeAvailable: false,
|
||||
UpgradeData: {},
|
||||
});
|
||||
}
|
||||
}).catch(() => {
|
||||
this.setState({
|
||||
UpgradeAvailable: false,
|
||||
UpgradeData: {},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
onInstallDependencyReply = (event, arg) => {
|
||||
ipcRenderer.send(Constants.IPC_Delete_File, {
|
||||
FilePath: arg.data.Source,
|
||||
});
|
||||
this.checkVersionInstalled(this.state.Release, this.state.Version);
|
||||
if (arg.data.Success && arg.data.Source.toLowerCase().endsWith('.dmg')) {
|
||||
this.waitForDependencyInstall(arg.data.URL);
|
||||
} else {
|
||||
this.sendRequest(Constants.IPC_Delete_File, {
|
||||
FilePath: arg.data.Source,
|
||||
});
|
||||
this.checkVersionInstalled();
|
||||
}
|
||||
};
|
||||
|
||||
onInstallUpgradeReply = (event, arg) => {
|
||||
ipcRenderer.sendSync(Constants.IPC_Delete_File, {
|
||||
this.sendSyncRequest(Constants.IPC_Delete_File, {
|
||||
FilePath: arg.data.Source,
|
||||
});
|
||||
|
||||
@@ -515,18 +506,19 @@ class App extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
saveState = (release, version, sia, hyperspace)=> {
|
||||
if (ipcRenderer) {
|
||||
ipcRenderer.send(Constants.IPC_Save_State, {
|
||||
Directory: Constants.DATA_LOCATIONS[this.props.platform],
|
||||
State: {
|
||||
Hyperspace: hyperspace,
|
||||
Release: release,
|
||||
Sia: sia,
|
||||
Version: version,
|
||||
}
|
||||
});
|
||||
saveState = version => {
|
||||
let state = {
|
||||
Release: this.state.Release,
|
||||
Version: version || this.state.Version,
|
||||
};
|
||||
for (const provider of Constants.PROVIDER_LIST) {
|
||||
state[provider] = this.state[provider];
|
||||
}
|
||||
|
||||
this.sendRequest(Constants.IPC_Save_State, {
|
||||
Directory: Constants.DATA_LOCATIONS[this.props.platform],
|
||||
State: state
|
||||
});
|
||||
};
|
||||
|
||||
setErrorState = (error, action, critical) => {
|
||||
@@ -544,10 +536,28 @@ class App extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
waitForDependencyInstall = (url) => {
|
||||
const dep = this.state.MissingDependencies.find(d => {
|
||||
return d.download === url;
|
||||
});
|
||||
|
||||
const i = setInterval(()=> {
|
||||
const ret = this.sendSyncRequest(Constants.IPC_Check_Dependency_Installed, {
|
||||
File: dep.file,
|
||||
});
|
||||
if (ret.data.Exists) {
|
||||
clearInterval(i);
|
||||
setTimeout(() => {
|
||||
this.checkVersionInstalled();
|
||||
}, 10000);
|
||||
}
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
render() {
|
||||
const selectedVersion = (this.state.Version === -1) ?
|
||||
'unavailable' :
|
||||
this.state.VersionLookup[this.state.ReleaseTypes[this.state.Release]][this.state.Version];
|
||||
this.getSelectedVersion();
|
||||
|
||||
const downloadEnabled = this.state.AllowDownload &&
|
||||
!this.state.MountsBusy &&
|
||||
@@ -555,11 +565,18 @@ class App extends Component {
|
||||
(selectedVersion !== 'unavailable') &&
|
||||
(selectedVersion !== this.state.InstalledVersion);
|
||||
|
||||
const allowMount = this.state.InstalledVersion !== 'none';
|
||||
const missingDependencies = (this.state.MissingDependencies.length > 0);
|
||||
const allowMount = this.state.InstalledVersion !== 'none' && !missingDependencies;
|
||||
|
||||
const allowConfig = this.state.LocationsLookup[selectedVersion] &&
|
||||
this.state.LocationsLookup[selectedVersion].config_support;
|
||||
|
||||
const allowSiaPrime = this.state.LocationsLookup[selectedVersion] &&
|
||||
this.state.LocationsLookup[selectedVersion].siaprime_support;
|
||||
|
||||
const noConsoleSupported = this.state.LocationsLookup[selectedVersion] &&
|
||||
this.state.LocationsLookup[selectedVersion].no_console_supported;
|
||||
|
||||
const showDependencies = missingDependencies &&
|
||||
!this.state.DownloadActive;
|
||||
|
||||
@@ -605,9 +622,8 @@ class App extends Component {
|
||||
<DependencyList allowDownload={!this.state.DownloadingDependency}
|
||||
dependencies={this.state.MissingDependencies}
|
||||
onDownload={this.handleDependencyDownload}/>
|
||||
}
|
||||
</Modal>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
let downloadDisplay = null;
|
||||
@@ -616,7 +632,8 @@ class App extends Component {
|
||||
<Modal>
|
||||
<DownloadProgress display={this.state.DownloadName}
|
||||
progress={this.state.DownloadProgress}/>
|
||||
</Modal>);
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
let upgradeDisplay = null;
|
||||
@@ -634,7 +651,7 @@ class App extends Component {
|
||||
let key = 0;
|
||||
mainContent.push((
|
||||
<div key={'rvd_' + key++}
|
||||
style={{height: '44%'}}>
|
||||
style={{height: '25%'}}>
|
||||
<ReleaseVersionDisplay disabled={this.state.DownloadActive || this.state.ExtractActive || this.state.MountsBusy}
|
||||
downloadClicked={this.handleReleaseDownload}
|
||||
downloadDisabled={!downloadEnabled}
|
||||
@@ -651,21 +668,27 @@ class App extends Component {
|
||||
));
|
||||
|
||||
if (allowMount) {
|
||||
let providerProps = {};
|
||||
for (const provider of Constants.PROVIDER_LIST) {
|
||||
const providerLower = provider.toLowerCase();
|
||||
providerProps[providerLower] = this.state[provider];
|
||||
}
|
||||
mainContent.push((
|
||||
<div key={'md_' + key++}
|
||||
style={{height: '56%'}}>
|
||||
<MountItems allowConfig={allowConfig}
|
||||
<div key={'md_' + key++}>
|
||||
<MountItems {...providerProps}
|
||||
allowConfig={allowConfig}
|
||||
allowSiaPrime={allowSiaPrime}
|
||||
noConsoleSupported={noConsoleSupported}
|
||||
autoMountChanged={this.handleAutoMountChanged}
|
||||
autoMountProcessed={this.notifyAutoMountProcessed}
|
||||
autoRestartChanged={this.handleAutoRestartChanged}
|
||||
changed={this.handleMountLocationChanged}
|
||||
configClicked={this.handleConfigClicked}
|
||||
directory={Constants.DATA_LOCATIONS[this.props.platform]}
|
||||
errorHandler={this.setErrorState}
|
||||
hyperspace={this.state.Hyperspace}
|
||||
mountsBusy={this.notifyMountsBusy}
|
||||
platform={this.props.platform}
|
||||
processAutoMount={!this.state.AutoMountProcessed}
|
||||
sia={this.state.Sia}
|
||||
version={this.state.InstalledVersion}/>
|
||||
</div>
|
||||
));
|
||||
@@ -698,12 +721,12 @@ class App extends Component {
|
||||
col={dimensions => dimensions.columns - 6}
|
||||
colSpan={5}
|
||||
row={1}
|
||||
rowSpan={remain=>remain - 2}/>
|
||||
rowSpan={remain=>remain - 1}/>
|
||||
</Grid>
|
||||
</Box>
|
||||
</div>
|
||||
<div styleName='Content'>
|
||||
<Box dxStyle={{padding: '8px'}}>
|
||||
<Box dxStyle={{padding: '8px 8px 0px 8px'}}>
|
||||
{mainContent}
|
||||
</Box>
|
||||
</div>
|
||||
|
||||
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 70 KiB |
@@ -0,0 +1,12 @@
|
||||
input.Input {
|
||||
margin: 0;
|
||||
padding: 3px;
|
||||
border-radius: var(--border_radius);
|
||||
background: rgba(160, 160, 160, 0.1);
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
color: var(--text_color);
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -18,7 +18,7 @@ export default CSSModules((props) => {
|
||||
rowSpan={6}>
|
||||
<img alt=''
|
||||
height={'16px'}
|
||||
onClick={props.configClicked}
|
||||
onClick={props.disabled ? (e)=>{e.preventDefault();} : props.configClicked}
|
||||
src={configureImage}
|
||||
style={{padding: 0, border: 0, margin: 0, cursor: 'pointer'}}
|
||||
width={'16px'}/>
|
||||
@@ -27,31 +27,47 @@ export default CSSModules((props) => {
|
||||
}
|
||||
|
||||
let inputColumnSpan;
|
||||
let inputControl = null;
|
||||
let inputControls = null;
|
||||
if (props.platform === 'win32') {
|
||||
inputColumnSpan = 20;
|
||||
inputControl = <DropDown changed={props.changed}
|
||||
colSpan={inputColumnSpan}
|
||||
disabled={!props.allowMount || props.mounted}
|
||||
items={props.items}
|
||||
row={secondRow}
|
||||
rowSpan={7}
|
||||
selected={props.items.indexOf(props.location)}/>;
|
||||
inputControls = <DropDown changed={props.changed}
|
||||
colSpan={inputColumnSpan}
|
||||
disabled={!props.allowMount || props.mounted || props.disabled}
|
||||
items={props.items}
|
||||
row={secondRow}
|
||||
rowSpan={7}
|
||||
selected={props.items.indexOf(props.location)}/>;
|
||||
|
||||
} else {
|
||||
inputColumnSpan = 60;
|
||||
inputControl = (
|
||||
<RootElem colSpan={inputColumnSpan}
|
||||
inputColumnSpan = 58;
|
||||
inputControls = [];
|
||||
let key = 0;
|
||||
inputControls.push((
|
||||
<RootElem colSpan={inputColumnSpan - 8}
|
||||
key={'i' + key++}
|
||||
row={secondRow}
|
||||
rowSpan={7}>
|
||||
<input disabled={!props.allowMount || props.mounted}
|
||||
<input disabled={!props.allowMount || props.mounted || props.disabled}
|
||||
maxLength={4096}
|
||||
onChange={props.changed}
|
||||
size={4096}
|
||||
styleName={'Input'}
|
||||
type={'text'}
|
||||
value={props.location}/>
|
||||
</RootElem>);
|
||||
</RootElem>
|
||||
));
|
||||
inputControls.push((
|
||||
<Button clicked={()=>props.browseClicked(props.title, props.location)}
|
||||
col={inputColumnSpan - 7}
|
||||
colSpan={7}
|
||||
disabled={props.mounted || props.disabled || !props.allowMount}
|
||||
key={'b' + key++}
|
||||
row={secondRow}
|
||||
rowSpan={7}>...</Button>
|
||||
));
|
||||
}
|
||||
|
||||
const buttonDisplay = props.allowMount ?
|
||||
const buttonDisplay = props.allowMount || props.disabled ?
|
||||
(props.mounted ? 'Unmount' : 'Mount') :
|
||||
<Loader color={'var(--heading_text_color)'}
|
||||
height='19px'
|
||||
@@ -59,26 +75,39 @@ export default CSSModules((props) => {
|
||||
width='19px'/>;
|
||||
|
||||
const actionsDisplay = (
|
||||
<Button clicked={()=>props.clicked(props.title, !props.mounted, props.location, props.pid)}
|
||||
<Button clicked={()=>props.clicked(props.title, !props.mounted, props.location)}
|
||||
col={inputColumnSpan + 2}
|
||||
colSpan={20}
|
||||
disabled={!props.allowMount}
|
||||
colSpan={21}
|
||||
disabled={!props.allowMount || props.disabled}
|
||||
row={secondRow}
|
||||
rowSpan={7}>
|
||||
{buttonDisplay}
|
||||
</Button>);
|
||||
|
||||
const autoMountControl = (
|
||||
<RootElem col={inputColumnSpan + 23}
|
||||
colSpan={26}
|
||||
<RootElem col={inputColumnSpan + 24}
|
||||
colSpan={28}
|
||||
row={secondRow}
|
||||
rowSpan={7}>
|
||||
<input checked={props.autoMount}
|
||||
disabled={props.disabled}
|
||||
onChange={props.autoMountChanged}
|
||||
type='checkbox'/>Auto-mount
|
||||
</RootElem>
|
||||
);
|
||||
|
||||
const autoRestartControl = (
|
||||
<RootElem col={inputColumnSpan + 24 + 28}
|
||||
colSpan={24}
|
||||
row={secondRow}
|
||||
rowSpan={7}>
|
||||
<input checked={props.autoRestart}
|
||||
disabled={props.disabled}
|
||||
onChange={props.autoRestartChanged}
|
||||
type='checkbox'/>Restart
|
||||
</RootElem>
|
||||
);
|
||||
|
||||
return (
|
||||
<Grid>
|
||||
{configButton}
|
||||
@@ -87,9 +116,10 @@ export default CSSModules((props) => {
|
||||
rowSpan={5}
|
||||
text={props.title}
|
||||
type={'Heading1'}/>
|
||||
{inputControl}
|
||||
{inputControls}
|
||||
{actionsDisplay}
|
||||
{autoMountControl}
|
||||
{autoRestartControl}
|
||||
</Grid>
|
||||
);
|
||||
}, styles, {allowMultiple: true});
|
||||
@@ -8,19 +8,57 @@ import Button from '../UI/Button/Button';
|
||||
import UpgradeIcon from '../UpgradeIcon/UpgradeIcon';
|
||||
|
||||
export default CSSModules((props) => {
|
||||
let optionsDisplay = null;
|
||||
let optionsDisplay = [];
|
||||
let key = 0;
|
||||
if (props.releaseExtracting) {
|
||||
optionsDisplay = <Text align='center'
|
||||
row={13}
|
||||
rowSpan={7}
|
||||
colSpan={'remain'}
|
||||
text={'Activating <' + props.installedVersion + '>'}/>
|
||||
optionsDisplay.push((
|
||||
<Text col={dimensions => (dimensions.columns / 3) * 2}
|
||||
colSpan={'remain'}
|
||||
key={key++}
|
||||
rowSpan={4}
|
||||
text={'Activating'}
|
||||
textAlign={'left'}
|
||||
type={'Heading2'}/>
|
||||
));
|
||||
optionsDisplay.push((
|
||||
<Text col={dimensions => (dimensions.columns / 3) * 2}
|
||||
colSpan={'remain'}
|
||||
key={key++}
|
||||
row={5}
|
||||
rowSpan={7}
|
||||
text={props.installedVersion}
|
||||
textAlign={'left'}/>
|
||||
));
|
||||
} else if (props.downloadDisabled) {
|
||||
optionsDisplay.push((
|
||||
<Text col={dimensions => (dimensions.columns / 3) * 2}
|
||||
colSpan={'remain'}
|
||||
key={key++}
|
||||
rowSpan={4}
|
||||
text={'Installed'}
|
||||
textAlign={'left'}
|
||||
type={'Heading2'}/>
|
||||
));
|
||||
|
||||
optionsDisplay.push((
|
||||
<Text col={dimensions => (dimensions.columns / 3) * 2}
|
||||
colSpan={'remain'}
|
||||
key={key++}
|
||||
row={5}
|
||||
rowSpan={7}
|
||||
text={props.installedVersion}
|
||||
textAlign={'left'}/>
|
||||
));
|
||||
} else {
|
||||
optionsDisplay = <Button clicked={props.downloadClicked}
|
||||
colSpan={20}
|
||||
disabled={props.downloadDisabled}
|
||||
row={13}
|
||||
rowSpan={7}>Install</Button>;
|
||||
optionsDisplay.push((
|
||||
<Button clicked={props.downloadClicked}
|
||||
col={dimensions => (dimensions.columns / 3) * 2}
|
||||
colSpan={20}
|
||||
key={key++}
|
||||
disabled={props.downloadDisabled}
|
||||
row={5}
|
||||
rowSpan={7}>Install</Button>
|
||||
));
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -56,18 +94,6 @@ export default CSSModules((props) => {
|
||||
row={5}
|
||||
rowSpan={7}
|
||||
selected={props.version}/>
|
||||
<Text col={dimensions => (dimensions.columns / 3) * 2}
|
||||
colSpan={'remain'}
|
||||
rowSpan={4}
|
||||
text={'Installed'}
|
||||
textAlign={'left'}
|
||||
type={'Heading2'}/>
|
||||
<Text col={dimensions => (dimensions.columns / 3) * 2}
|
||||
colSpan={'remain'}
|
||||
row={5}
|
||||
rowSpan={7}
|
||||
text={props.installedVersion}
|
||||
textAlign={'left'}/>
|
||||
{optionsDisplay}
|
||||
</Grid>
|
||||
);
|
||||
|
||||
@@ -9,9 +9,28 @@ exports.DATA_LOCATIONS = {
|
||||
};
|
||||
exports.UI_RELEASES_URL = 'https://bitbucket.org/blockstorage/repertory-ui/raw/master/releases.json';
|
||||
|
||||
exports.PROVIDER_LIST = [
|
||||
'Hyperspace',
|
||||
'Sia',
|
||||
'SiaPrime'
|
||||
];
|
||||
|
||||
exports.PROVIDER_ARG = {
|
||||
hyperspace: '-hs',
|
||||
sia: '',
|
||||
siaprime: '-sp'
|
||||
};
|
||||
|
||||
exports.IPC_Browse_Directory = 'browse_directory';
|
||||
|
||||
exports.IPC_Check_Dependency_Installed = 'check_dependency_installed';
|
||||
exports.IPC_Check_Dependency_Installed_Reply = 'check_dependency_installed';
|
||||
|
||||
exports.IPC_Check_Installed = 'check_installed';
|
||||
exports.IPC_Check_Installed_Reply = 'check_installed_reply';
|
||||
|
||||
exports.IPC_Check_Mount_Location = 'check_mount_location';
|
||||
|
||||
exports.IPC_Delete_File = 'delete_file';
|
||||
|
||||
exports.IPC_Detect_Mounts = 'detect_mounts';
|
||||
|
||||
@@ -5,28 +5,22 @@ import Button from '../../components/UI/Button/Button';
|
||||
import ConfigurationItem from '../../components/ConfigurationItem/ConfigurationItem';
|
||||
import CSSModules from 'react-css-modules';
|
||||
import Modal from '../../components/UI/Modal/Modal';
|
||||
import IPCContainer from '../IPCContainer/IPCContainer';
|
||||
|
||||
const Constants = require('../../constants');
|
||||
|
||||
let ipcRenderer = null;
|
||||
if (!process.versions.hasOwnProperty('electron')) {
|
||||
ipcRenderer = ((window && window.require) ? window.require('electron').ipcRenderer : null);
|
||||
}
|
||||
|
||||
class Configuration extends Component {
|
||||
class Configuration extends IPCContainer {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
if (ipcRenderer) {
|
||||
ipcRenderer.on(Constants.IPC_Get_Config_Template_Reply, this.onGetConfigTemplateReply);
|
||||
ipcRenderer.on(Constants.IPC_Get_Config_Reply, this.onGetConfigReply);
|
||||
ipcRenderer.on(Constants.IPC_Set_Config_Values_Reply, this.onSetConfigValuesReply);
|
||||
this.setRequestHandler(Constants.IPC_Get_Config_Template_Reply, this.onGetConfigTemplateReply);
|
||||
this.setRequestHandler(Constants.IPC_Get_Config_Reply, this.onGetConfigReply);
|
||||
this.setRequestHandler(Constants.IPC_Set_Config_Values_Reply, this.onSetConfigValuesReply);
|
||||
|
||||
ipcRenderer.send(Constants.IPC_Get_Config_Template, {
|
||||
Directory: this.props.directory,
|
||||
StorageType: this.props.storageType,
|
||||
Version: this.props.version,
|
||||
});
|
||||
}
|
||||
this.sendRequest(Constants.IPC_Get_Config_Template, {
|
||||
Directory: this.props.directory,
|
||||
StorageType: this.props.storageType,
|
||||
Version: this.props.version,
|
||||
});
|
||||
}
|
||||
|
||||
state = {
|
||||
@@ -78,14 +72,6 @@ class Configuration extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
componentWillUnmount = () => {
|
||||
if (ipcRenderer) {
|
||||
ipcRenderer.removeListener(Constants.IPC_Get_Config_Reply, this.onGetConfigReply);
|
||||
ipcRenderer.removeListener(Constants.IPC_Get_Config_Template_Reply, this.onGetConfigTemplateReply);
|
||||
ipcRenderer.removeListener(Constants.IPC_Set_Config_Values_Reply, this.onSetConfigValuesReply);
|
||||
}
|
||||
};
|
||||
|
||||
createItemList = (config, template) => {
|
||||
const objectList = [];
|
||||
const itemList = Object
|
||||
@@ -164,11 +150,12 @@ class Configuration extends Component {
|
||||
if (arg.data.Success) {
|
||||
this.setState({
|
||||
Template: arg.data.Template,
|
||||
});
|
||||
ipcRenderer.send(Constants.IPC_Get_Config, {
|
||||
Directory: this.props.directory,
|
||||
StorageType: this.props.storageType,
|
||||
Version: this.props.version,
|
||||
}, ()=> {
|
||||
this.sendRequest(Constants.IPC_Get_Config, {
|
||||
Directory: this.props.directory,
|
||||
StorageType: this.props.storageType,
|
||||
Version: this.props.version,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.props.errorHandler(arg.data.Error, () => {
|
||||
@@ -182,11 +169,9 @@ class Configuration extends Component {
|
||||
};
|
||||
|
||||
saveAndClose = () => {
|
||||
if (ipcRenderer) {
|
||||
this.setState({
|
||||
Saving: true,
|
||||
});
|
||||
|
||||
this.setState({
|
||||
Saving: true,
|
||||
}, ()=> {
|
||||
const changedItems = [];
|
||||
for (const item of this.state.ChangedItems) {
|
||||
changedItems.push({
|
||||
@@ -206,13 +191,13 @@ class Configuration extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
ipcRenderer.send(Constants.IPC_Set_Config_Values, {
|
||||
this.sendRequest(Constants.IPC_Set_Config_Values, {
|
||||
Directory: this.props.directory,
|
||||
Items: changedItems,
|
||||
StorageType: this.props.storageType,
|
||||
Version: this.props.version,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
51
src/containers/IPCContainer/IPCContainer.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import {Component} from 'react';
|
||||
|
||||
export default class extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
if (!process.versions.hasOwnProperty('electron')) {
|
||||
this.ipcRenderer = ((window && window.require) ? window.require('electron').ipcRenderer : null);
|
||||
}
|
||||
}
|
||||
|
||||
handlerList = {};
|
||||
ipcRenderer;
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.ipcRenderer) {
|
||||
for (let name in this.handlerList) {
|
||||
if (this.handlerList.hasOwnProperty(name)) {
|
||||
this.ipcRenderer.removeListener(name, this.handlerList[name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.handlerList = {};
|
||||
};
|
||||
|
||||
sendRequest = (name, data) => {
|
||||
if (this.ipcRenderer) {
|
||||
this.ipcRenderer.send(name, data);
|
||||
}
|
||||
};
|
||||
|
||||
sendSyncRequest = (name, data) => {
|
||||
if (this.ipcRenderer) {
|
||||
return this.ipcRenderer.sendSync(name + '_sync', data);
|
||||
} else {
|
||||
return {
|
||||
Success: false,
|
||||
Error: 'IPC not available. Running in browser?',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
setRequestHandler = (name, callback) => {
|
||||
if (this.ipcRenderer) {
|
||||
this.handlerList[name] = callback;
|
||||
this.ipcRenderer.on(name, callback);
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
@@ -1,140 +1,176 @@
|
||||
import React from 'react';
|
||||
import {Component} from 'react';
|
||||
import Box from '../../components/UI/Box/Box';
|
||||
import Button from '../../components/UI/Button/Button';
|
||||
import CSSModules from 'react-css-modules';
|
||||
import styles from './MountItems.css';
|
||||
import Modal from '../../components/UI/Modal/Modal';
|
||||
import MountItem from '../../components/MountItem/MountItem';
|
||||
import IPCContainer from '../IPCContainer/IPCContainer';
|
||||
|
||||
const Constants = require('../../constants');
|
||||
|
||||
let ipcRenderer = null;
|
||||
if (!process.versions.hasOwnProperty('electron')) {
|
||||
ipcRenderer = ((window && window.require) ? window.require('electron').ipcRenderer : null);
|
||||
}
|
||||
|
||||
class MountItems extends Component {
|
||||
class MountItems extends IPCContainer {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
if (ipcRenderer) {
|
||||
ipcRenderer.on(Constants.IPC_Detect_Mounts_Reply, this.onDetectMountsReply);
|
||||
ipcRenderer.on(Constants.IPC_Mount_Drive_Reply, this.onMountDriveReply);
|
||||
ipcRenderer.on(Constants.IPC_Unmount_Drive_Reply, this.onUnmountDriveReply);
|
||||
|
||||
this.detectMounts();
|
||||
for (const provider of Constants.PROVIDER_LIST) {
|
||||
this.state[provider] = {
|
||||
AllowMount: false,
|
||||
DriveLetters: [],
|
||||
Mounted: false,
|
||||
};
|
||||
}
|
||||
|
||||
this.setRequestHandler(Constants.IPC_Detect_Mounts_Reply, this.onDetectMountsReply);
|
||||
this.setRequestHandler(Constants.IPC_Mount_Drive_Reply, this.onMountDriveReply);
|
||||
this.setRequestHandler(Constants.IPC_Unmount_Drive_Reply, this.onUnmountDriveReply);
|
||||
|
||||
this.detectMounts();
|
||||
}
|
||||
|
||||
retryIntervals = {};
|
||||
|
||||
state = {
|
||||
Hyperspace: {
|
||||
AllowMount: false,
|
||||
DriveLetters: [],
|
||||
Mounted: false,
|
||||
PID: -1,
|
||||
},
|
||||
Sia: {
|
||||
AllowMount: false,
|
||||
DriveLetters: [],
|
||||
Mounted: false,
|
||||
PID: -1,
|
||||
},
|
||||
DisplayRetry: false,
|
||||
RetryItems: {},
|
||||
};
|
||||
|
||||
componentWillUnmount = () => {
|
||||
if (ipcRenderer) {
|
||||
ipcRenderer.removeListener(Constants.IPC_Detect_Mounts_Reply, this.onDetectMountsReply);
|
||||
ipcRenderer.removeListener(Constants.IPC_Mount_Drive_Reply, this.onMountDriveReply);
|
||||
ipcRenderer.removeListener(Constants.IPC_Unmount_Drive_Reply, this.onUnmountDriveReply);
|
||||
cancelRetryMount = (storageType, stateCallback) => {
|
||||
clearInterval(this.retryIntervals[storageType]);
|
||||
delete this.retryIntervals[storageType];
|
||||
|
||||
if (stateCallback) {
|
||||
let retryItems = {
|
||||
...this.state.RetryItems,
|
||||
};
|
||||
delete retryItems[storageType];
|
||||
this.setState({
|
||||
DisplayRetry: Object.keys(retryItems).length > 0,
|
||||
RetryItems: retryItems,
|
||||
}, stateCallback);
|
||||
}
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
for (const storageType in this.state.RetryItems) {
|
||||
if (this.state.RetryItems.hasOwnProperty(storageType)) {
|
||||
this.cancelRetryMount(storageType);
|
||||
}
|
||||
}
|
||||
|
||||
super.componentWillUnmount();
|
||||
};
|
||||
|
||||
detectMounts = ()=> {
|
||||
this.props.mountsBusy(true);
|
||||
ipcRenderer.send(Constants.IPC_Detect_Mounts, {
|
||||
Directory: this.props.directory,
|
||||
Version: this.props.version,
|
||||
});
|
||||
};
|
||||
|
||||
handleMountLocationChanged = (systemType, value) => {
|
||||
if (this.props.platform === 'win32') {
|
||||
this.props.changed(systemType, this.state[systemType].DriveLetters[value]);
|
||||
}
|
||||
else {
|
||||
this.props.changed(systemType, value);
|
||||
}
|
||||
};
|
||||
|
||||
handleMountUnMount = (storageType, mount, location, pid) => {
|
||||
if (ipcRenderer) {
|
||||
const state = {
|
||||
...this.state[storageType],
|
||||
AllowMount: false,
|
||||
};
|
||||
this.setState({
|
||||
[storageType]: state,
|
||||
});
|
||||
|
||||
if (!this.state.DisplayRetry) {
|
||||
this.props.mountsBusy(true);
|
||||
this.sendRequest(Constants.IPC_Detect_Mounts, {
|
||||
Directory: this.props.directory,
|
||||
Version: this.props.version,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (mount) {
|
||||
ipcRenderer.send(Constants.IPC_Mount_Drive, {
|
||||
Directory: this.props.directory,
|
||||
handleBrowseLocation = (storageType, location) => {
|
||||
location = this.sendSyncRequest(Constants.IPC_Browse_Directory, {
|
||||
Title: storageType + ' Mount Location',
|
||||
Location: location,
|
||||
});
|
||||
if (location && (location.length > 0)) {
|
||||
this.handleMountLocationChanged(storageType, location);
|
||||
}
|
||||
};
|
||||
|
||||
handleMountLocationChanged = (storageType, value) => {
|
||||
if (this.props.platform === 'win32') {
|
||||
this.props.changed(storageType, this.state[storageType].DriveLetters[value]);
|
||||
} else {
|
||||
this.props.changed(storageType, value);
|
||||
}
|
||||
};
|
||||
|
||||
handleMountUnMount = (storageType, mount, location) => {
|
||||
if (!location || (location.trim().length === 0)) {
|
||||
this.props.errorHandler('Mount location is not set');
|
||||
} else {
|
||||
let allowAction = true;
|
||||
if (mount && (this.props.platform !== 'win32')) {
|
||||
const result = this.sendSyncRequest(Constants.IPC_Check_Mount_Location, {
|
||||
Location: location,
|
||||
StorageType: storageType,
|
||||
Version: this.props.version,
|
||||
});
|
||||
} else {
|
||||
ipcRenderer.send(Constants.IPC_Unmount_Drive, {
|
||||
Directory: this.props.directory,
|
||||
Location: location,
|
||||
PID: pid,
|
||||
StorageType: storageType,
|
||||
Version: this.props.version,
|
||||
if (!result.Success) {
|
||||
allowAction = false;
|
||||
this.props.errorHandler(result.Error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (allowAction) {
|
||||
const storageState = {
|
||||
...this.state[storageType],
|
||||
AllowMount: false,
|
||||
};
|
||||
|
||||
this.props.mountsBusy(true);
|
||||
|
||||
this.setState({
|
||||
[storageType]: storageState,
|
||||
}, () => {
|
||||
if (mount) {
|
||||
this.sendRequest(Constants.IPC_Mount_Drive, {
|
||||
Directory: this.props.directory,
|
||||
Location: location,
|
||||
NoConsoleSupported: this.props.noConsoleSupported,
|
||||
StorageType: storageType,
|
||||
Version: this.props.version,
|
||||
});
|
||||
} else {
|
||||
this.sendRequest(Constants.IPC_Unmount_Drive, {
|
||||
Directory: this.props.directory,
|
||||
Location: location,
|
||||
StorageType: storageType,
|
||||
Version: this.props.version,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onDetectMountsReply = (event, arg) => {
|
||||
if (arg.data.Success) {
|
||||
const sia = {
|
||||
...this.state.Sia,
|
||||
AllowMount: true,
|
||||
DriveLetters: (arg.data.DriveLetters.Sia),
|
||||
Mounted: (arg.data.Locations.Sia.length > 0),
|
||||
PID: arg.data.PIDS.Sia,
|
||||
};
|
||||
const hs = {
|
||||
...this.state.Hyperspace,
|
||||
AllowMount: true,
|
||||
DriveLetters: (arg.data.DriveLetters.Hyperspace),
|
||||
Mounted: (arg.data.Locations.Hyperspace.length > 0),
|
||||
PID: arg.data.PIDS.Hyperspace,
|
||||
};
|
||||
if (!this.state.DisplayRetry && arg.data.Success) {
|
||||
let state = {};
|
||||
let mountsBusy = false;
|
||||
for (const provider of Constants.PROVIDER_LIST) {
|
||||
state[provider] = {
|
||||
...this.state[provider],
|
||||
AllowMount: true,
|
||||
DriveLetters: (arg.data.DriveLetters[provider]),
|
||||
Mounted: (arg.data.Locations[provider].length > 0),
|
||||
};
|
||||
mountsBusy = mountsBusy || state[provider].Mounted;
|
||||
}
|
||||
this.props.mountsBusy(mountsBusy);
|
||||
|
||||
this.setState({
|
||||
Hyperspace: hs,
|
||||
Sia: sia,
|
||||
this.setState(state, () => {
|
||||
const updateMountLocation = (data, provider) => {
|
||||
const providerLower = provider.toLowerCase();
|
||||
let location = data.Locations[provider];
|
||||
if (location.length === 0) {
|
||||
location = (this.props.platform === 'win32') ?
|
||||
this.props[providerLower].MountLocation || data.DriveLetters[provider][0] :
|
||||
this.props[providerLower].MountLocation;
|
||||
}
|
||||
if (location !== this.props[providerLower].MountLocation) {
|
||||
this.props.changed(provider, location);
|
||||
}
|
||||
};
|
||||
|
||||
for (const provider of Constants.PROVIDER_LIST) {
|
||||
updateMountLocation(arg.data, provider);
|
||||
}
|
||||
|
||||
this.performAutoMount();
|
||||
});
|
||||
|
||||
this.props.mountsBusy(hs.Mounted || sia.Mounted);
|
||||
|
||||
let hsLocation = arg.data.Locations.Hyperspace;
|
||||
if ((hsLocation.length === 0) && (this.props.platform === 'win32')) {
|
||||
hsLocation = this.props.hyperspace.MountLocation || arg.data.DriveLetters.Hyperspace[0];
|
||||
}
|
||||
if (hsLocation !== this.props.hyperspace.MountLocation) {
|
||||
this.props.changed('Hyperspace', hsLocation);
|
||||
}
|
||||
|
||||
let siaLocation = arg.data.Locations.Sia;
|
||||
if ((siaLocation.length === 0) && (this.props.platform === 'win32')) {
|
||||
siaLocation = this.props.sia.MountLocation || arg.data.DriveLetters.Sia[0];
|
||||
}
|
||||
if (siaLocation !== this.props.sia.MountLocation) {
|
||||
this.props.changed('Sia', siaLocation);
|
||||
}
|
||||
|
||||
this.performAutoMount();
|
||||
} else {
|
||||
this.props.errorHandler(arg.data.Error);
|
||||
}
|
||||
@@ -143,66 +179,134 @@ class MountItems extends Component {
|
||||
onMountDriveReply = (event, arg) => {
|
||||
const state = {
|
||||
...this.state[arg.data.StorageType],
|
||||
PID: arg.data.PID,
|
||||
Mounted: arg.data.Success,
|
||||
};
|
||||
this.setState({
|
||||
[arg.data.StorageType]: state,
|
||||
}, ()=> {
|
||||
this.detectMounts();
|
||||
});
|
||||
|
||||
this.detectMounts();
|
||||
};
|
||||
|
||||
onUnmountDriveReply = (event, arg) => {
|
||||
this.detectMounts();
|
||||
if (arg && arg.data && !arg.data.Expected && arg.data.Location && this.props[arg.data.StorageType.toLowerCase()].AutoRestart) {
|
||||
const storageType = arg.data.StorageType;
|
||||
if (!this.state.RetryItems[storageType]) {
|
||||
let retryItems = {
|
||||
...this.state.RetryItems
|
||||
};
|
||||
retryItems[storageType] = {
|
||||
RetrySeconds: 10,
|
||||
};
|
||||
const storageState = {
|
||||
...this.state[arg.data.StorageType],
|
||||
AllowMount: false,
|
||||
Mounted: false,
|
||||
};
|
||||
this.setState({
|
||||
[storageType]: storageState,
|
||||
DisplayRetry: true,
|
||||
RetryItems: retryItems,
|
||||
}, () => {
|
||||
this.retryIntervals[storageType] = setInterval(() => {
|
||||
let retryItems = {
|
||||
...this.state.RetryItems,
|
||||
};
|
||||
const retrySeconds = retryItems[storageType].RetrySeconds - 1;
|
||||
if (retrySeconds === 0) {
|
||||
this.cancelRetryMount(storageType, () => {
|
||||
this.handleMountUnMount(storageType, true, arg.data.Location);
|
||||
});
|
||||
} else {
|
||||
retryItems[storageType].RetrySeconds = retrySeconds;
|
||||
this.setState({
|
||||
RetryItems: retryItems,
|
||||
});
|
||||
}
|
||||
},1000);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.detectMounts();
|
||||
}
|
||||
};
|
||||
|
||||
performAutoMount = ()=> {
|
||||
if (this.props.processAutoMount) {
|
||||
this.props.autoMountProcessed();
|
||||
if (this.props.hyperspace.AutoMount &&
|
||||
!this.state.Hyperspace.Mounted &&
|
||||
(this.props.hyperspace.MountLocation.length > 0)) {
|
||||
this.handleMountUnMount('Hyperspace', true, this.props.hyperspace.MountLocation);
|
||||
}
|
||||
if (this.props.sia.AutoMount &&
|
||||
!this.state.Sia.Mounted &&
|
||||
(this.props.sia.MountLocation.length > 0)) {
|
||||
this.handleMountUnMount('Sia', true, this.props.sia.MountLocation);
|
||||
const processAutoMount = (provider) => {
|
||||
const providerLower = provider.toLowerCase();
|
||||
if (this.props[providerLower].AutoMount &&
|
||||
!this.state[provider].Mounted &&
|
||||
(this.props[providerLower].MountLocation.length > 0)) {
|
||||
this.handleMountUnMount(provider, true, this.props[providerLower].MountLocation);
|
||||
}
|
||||
};
|
||||
|
||||
for (const provider of Constants.PROVIDER_LIST) {
|
||||
processAutoMount(provider);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
let retryDisplay;
|
||||
if (this.state.DisplayRetry) {
|
||||
let retryList = [];
|
||||
let retryListCount = 0;
|
||||
for (const storageType in this.state.RetryItems) {
|
||||
if (this.state.RetryItems.hasOwnProperty(storageType)) {
|
||||
retryListCount++;
|
||||
retryList.push(<Button
|
||||
clicked={()=>this.cancelRetryMount(storageType, ()=> this.detectMounts())}
|
||||
key={'b' + retryListCount}>Cancel {storageType} Remount ({this.state.RetryItems[storageType].RetrySeconds}s)</Button>);
|
||||
if (retryListCount < Object.keys(this.state.RetryItems).length) {
|
||||
retryList.push(<div style={{paddingTop: '8px'}} key={'d' + retryListCount}/>);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
retryDisplay = (
|
||||
<Modal>
|
||||
<Box dxStyle={{padding: '8px', minWidth: '70vw'}}>
|
||||
<h1 style={{textAlign: 'center', paddingBottom: '8px', color: 'var(--text_color_error)'}}>Mount Failed</h1>
|
||||
{retryList}
|
||||
</Box>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
let items = [];
|
||||
for (const provider of Constants.PROVIDER_LIST) {
|
||||
const providerLower = provider.toLowerCase();
|
||||
items.push((
|
||||
<MountItem allowConfig={this.props.allowConfig}
|
||||
allowMount={this.state[provider].AllowMount}
|
||||
autoMount={this.props[providerLower].AutoMount}
|
||||
autoMountChanged={(e)=>this.props.autoMountChanged(provider, e)}
|
||||
autoRestart={this.props[providerLower].AutoRestart}
|
||||
autoRestartChanged={(e)=>this.props.autoRestartChanged(provider, e)}
|
||||
browseClicked={this.handleBrowseLocation}
|
||||
changed={(e) => this.handleMountLocationChanged(provider, e.target.value)}
|
||||
clicked={this.handleMountUnMount}
|
||||
configClicked={()=>this.props.configClicked(provider)}
|
||||
items={this.state[provider].DriveLetters}
|
||||
key={'mi_' + items.length}
|
||||
location={this.props[providerLower].MountLocation}
|
||||
mounted={this.state[provider].Mounted}
|
||||
platform={this.props.platform}
|
||||
title={provider} />
|
||||
));
|
||||
if (items.length !== this.state.length) {
|
||||
items.push(<div key={'di_' + items.length}
|
||||
style={{paddingTop: '12px'}} />)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div styleName='MountItems'>
|
||||
<MountItem allowConfig={this.props.allowConfig}
|
||||
allowMount={this.state.Hyperspace.AllowMount}
|
||||
autoMount={this.props.hyperspace.AutoMount}
|
||||
autoMountChanged={(e)=>this.props.autoMountChanged('Hyperspace', e)}
|
||||
changed={(e) => this.handleMountLocationChanged('Hyperspace', e.target.value)}
|
||||
clicked={this.handleMountUnMount}
|
||||
configClicked={()=>this.props.configClicked('Hyperspace')}
|
||||
items={this.state.Hyperspace.DriveLetters}
|
||||
location={this.props.hyperspace.MountLocation}
|
||||
mounted={this.state.Hyperspace.Mounted}
|
||||
pid={this.state.Hyperspace.PID}
|
||||
platform={this.props.platform}
|
||||
title={'Hyperspace'}/>
|
||||
<div style={{paddingTop: '8px'}}/>
|
||||
<MountItem allowConfig={this.props.allowConfig}
|
||||
allowMount={this.state.Sia.AllowMount}
|
||||
autoMount={this.props.sia.AutoMount}
|
||||
autoMountChanged={(e)=>this.props.autoMountChanged('Sia', e)}
|
||||
changed={(e) => this.handleMountLocationChanged('Sia', e.target.value)}
|
||||
clicked={this.handleMountUnMount}
|
||||
configClicked={()=>this.props.configClicked('Sia')}
|
||||
items={this.state.Sia.DriveLetters}
|
||||
location={this.props.sia.MountLocation}
|
||||
mounted={this.state.Sia.Mounted}
|
||||
pid={this.state.Sia.PID}
|
||||
platform={this.props.platform}
|
||||
title={'Sia'}/>
|
||||
{retryDisplay}
|
||||
{items}
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,13 @@
|
||||
--heading_text_color: rgba(161, 190, 219, 0.7);
|
||||
--heading_other_text_color: var(--heading_text_color);
|
||||
--text_color_transition: color 0.3s;
|
||||
|
||||
--default_font_size: 4vmin
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: 'Nunito', sans-serif;
|
||||
font-size: 5vh;
|
||||
font-size: var(--default_font_size);
|
||||
}
|
||||
|
||||
*::-moz-focus-inner {
|
||||
|
||||
@@ -11,6 +11,11 @@ if (!process.versions.hasOwnProperty('electron')) {
|
||||
const ipcRenderer = ((window && window.require) ? window.require('electron').ipcRenderer : null);
|
||||
if (ipcRenderer) {
|
||||
ipcRenderer.on(Constants.IPC_Get_Platform_Reply, (event, arg) => {
|
||||
if (arg.data === 'linux') {
|
||||
let root = document.documentElement;
|
||||
root.style.setProperty('--default_font_size', '4.8vmin');
|
||||
}
|
||||
|
||||
ReactDOM.render(<App platform={arg.data} version={packageJson.version}/>, document.getElementById('root'));
|
||||
registerServiceWorker();
|
||||
});
|
||||
|
||||