Merged 1.0.2_branch into master
11
CHANGELOG.md
@@ -1,4 +1,15 @@
|
|||||||
# Changelog #
|
# 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 ##
|
## 1.0.1 ##
|
||||||
* Added configuration settings for Repertory 1.0.0-alpha.2 and above
|
* Added configuration settings for Repertory 1.0.0-alpha.2 and above
|
||||||
* Fixed memory leak on component unmount
|
* Fixed memory leak on component unmount
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
# Repertory UI
|
# Repertory UI
|
||||||

|

|
||||||
### GUI for [Repertory](https://bitbucket.org/blockstorage/repertory) ###
|
### 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 #
|
# 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 #
|
# Supported Platforms #
|
||||||
|
* OS X
|
||||||
* Windows 64-bit
|
* Windows 64-bit
|
||||||
# Future Platforms #
|
# Future Platforms #
|
||||||
* OS X
|
|
||||||
* Linux 64-bit
|
* Linux 64-bit
|
||||||
512
electron.js
@@ -1,6 +1,6 @@
|
|||||||
// Modules to control application life and create native browser window
|
// 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 {ipcMain} = require('electron');
|
||||||
const Constants = require('./src/constants');
|
const Constants = require('./src/constants');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
@@ -14,43 +14,106 @@ const AutoLaunch = require('auto-launch');
|
|||||||
|
|
||||||
// Keep a global reference of the window object, if you don't, the window will
|
// 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.
|
// be closed automatically when the JavaScript object is garbage collected.
|
||||||
let mainContextWindow;
|
let trayContextMenu;
|
||||||
let mainWindow;
|
let mainWindow;
|
||||||
let mainWindowTray;
|
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() {
|
function createWindow() {
|
||||||
|
loadUiSettings();
|
||||||
|
|
||||||
// Create the browser window.
|
// 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({
|
mainWindow = new BrowserWindow({
|
||||||
width: 425,
|
width: 428 + ((os.platform() === 'win32') ? 0 : (os.platform() === 'darwin') ? 150 : 160),
|
||||||
height: height,
|
height: height,
|
||||||
|
fullscreen: false,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
|
show: !launchHidden,
|
||||||
title: 'Repertory UI',
|
title: 'Repertory UI',
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
webSecurity: !process.env.ELECTRON_START_URL
|
webSecurity: !process.env.ELECTRON_START_URL
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if ((os.platform() === 'darwin') && launchHidden) {
|
||||||
|
app.dock.hide();
|
||||||
|
}
|
||||||
|
|
||||||
// and load the index.html of the app.
|
// and load the index.html of the app.
|
||||||
const startUrl = process.env.ELECTRON_START_URL || url.format({
|
const startUrl = process.env.ELECTRON_START_URL || url.format({
|
||||||
pathname: path.join(__dirname, '/build/index.html'),
|
pathname: path.join(__dirname, '/build/index.html'),
|
||||||
protocol: 'file:',
|
protocol: 'file:',
|
||||||
slashes: true
|
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 () {
|
mainWindow.on('closed', function () {
|
||||||
// Dereference the window object, usually you would store windows
|
// Dereference the window object, usually you would store windows
|
||||||
// in an array if your app supports multi windows, this is the time
|
// in an array if your app supports multi windows, this is the time
|
||||||
// when you should delete the corresponding element.
|
// 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')) :
|
||||||
const appPath = (os.platform() === 'win32') ?
|
(os.platform() === 'darwin') ? path.resolve(path.join(path.dirname(app.getAppPath()), '../MacOS/repertory-ui')) :
|
||||||
path.resolve(path.join(app.getAppPath(), '..\\..\\repertory-ui.exe')) :
|
|
||||||
process.env.APPIMAGE;
|
process.env.APPIMAGE;
|
||||||
|
|
||||||
const autoLauncher = new AutoLaunch({
|
const autoLauncher = new AutoLaunch({
|
||||||
@@ -58,20 +121,12 @@ function createWindow() {
|
|||||||
path: appPath,
|
path: appPath,
|
||||||
});
|
});
|
||||||
|
|
||||||
const image = nativeImage.createFromPath(path.join(__dirname, '/build/logo.png'));
|
trayContextMenu = Menu.buildFromTemplate([
|
||||||
mainContextWindow = Menu.buildFromTemplate([
|
|
||||||
{
|
{
|
||||||
label: 'Visible', type: 'checkbox', click(item) {
|
label: 'Visible', type: 'checkbox', click(item) {
|
||||||
if (item.checked) {
|
setWindowVisibility(item.checked);
|
||||||
mainWindow.show();
|
},
|
||||||
if (mainWindow.isMinimized()) {
|
checked: !launchHidden,
|
||||||
mainWindow.restore();
|
|
||||||
}
|
|
||||||
mainWindow.focus()
|
|
||||||
} else {
|
|
||||||
mainWindow.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Auto-start', type: 'checkbox', click(item) {
|
label: 'Auto-start', type: 'checkbox', click(item) {
|
||||||
@@ -86,74 +141,172 @@ function createWindow() {
|
|||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Exit', click(item) {
|
label: 'Launch Hidden', type: 'checkbox', click(item) {
|
||||||
app.quit();
|
launchHidden = !!item.checked;
|
||||||
|
saveUiSettings();
|
||||||
|
},
|
||||||
|
checked: launchHidden,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Exit and Unmount', click() {
|
||||||
|
closeApplication();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
mainContextWindow.items[0].checked = true;
|
const image = nativeImage.createFromPath(path.join(__dirname, (os.platform() === 'darwin') ? '/build/logo_mac.png' : '/build/logo.png'));
|
||||||
|
mainWindowTray = new Tray(image);
|
||||||
|
|
||||||
autoLauncher
|
autoLauncher
|
||||||
.isEnabled()
|
.isEnabled()
|
||||||
.then((enabled) => {
|
.then((enabled) => {
|
||||||
mainContextWindow.items[1].checked = enabled;
|
trayContextMenu.items[1].checked = enabled;
|
||||||
|
|
||||||
mainWindowTray = new Tray(image);
|
|
||||||
mainWindowTray.setToolTip('Repertory UI');
|
mainWindowTray.setToolTip('Repertory UI');
|
||||||
mainWindowTray.setContextMenu(mainContextWindow)
|
mainWindowTray.setContextMenu(trayContextMenu)
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
app.quit();
|
closeApplication();
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
mainWindow.loadURL(startUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
const instanceLock = app.requestSingleInstanceLock();
|
const instanceLock = app.requestSingleInstanceLock();
|
||||||
if (!instanceLock) {
|
if (!instanceLock) {
|
||||||
app.quit()
|
closeApplication();
|
||||||
} else {
|
} else {
|
||||||
app.on('second-instance', () => {
|
app.on('second-instance', () => {
|
||||||
if (mainWindow) {
|
if (mainWindow) {
|
||||||
mainWindow.show();
|
setWindowVisibility(true);
|
||||||
if (mainContextWindow) {
|
|
||||||
mainContextWindow.items[0].checked = true;
|
|
||||||
}
|
|
||||||
if (mainWindow.isMinimized()) {
|
|
||||||
mainWindow.restore();
|
|
||||||
}
|
|
||||||
mainWindow.focus()
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('ready', createWindow);
|
app.on('ready', createWindow);
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', () => {
|
||||||
// On OS X it is common for applications and their menu bar
|
closeApplication();
|
||||||
// to stay active until the user quits explicitly with Cmd + Q
|
|
||||||
if (process.platform !== 'darwin') {
|
|
||||||
app.quit()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
app.on('activate', () => {
|
|
||||||
// On OS X it's common to re-create a window in the app when the
|
|
||||||
// dock icon is clicked and there are no other windows open.
|
|
||||||
if (mainWindow === null) {
|
|
||||||
createWindow()
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const 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) => {
|
const standardIPCReply = (event, channel, data, error) => {
|
||||||
|
if (mainWindow) {
|
||||||
event.sender.send(channel, {
|
event.sender.send(channel, {
|
||||||
data: {
|
data: {
|
||||||
...data,
|
...data,
|
||||||
Error: error,
|
Error: error instanceof Error ? error.toString() : error,
|
||||||
Success: !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) => {
|
ipcMain.on(Constants.IPC_Check_Installed, (event, data) => {
|
||||||
const dataDirectory = helpers.resolvePath(data.Directory);
|
const dataDirectory = helpers.resolvePath(data.Directory);
|
||||||
const destination = path.join(dataDirectory, data.Version);
|
const destination = path.join(dataDirectory, data.Version);
|
||||||
@@ -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) => {
|
ipcMain.on(Constants.IPC_Delete_File, (event, data) => {
|
||||||
try {
|
try {
|
||||||
if (fs.existsSync(data.FilePath)) {
|
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) => {
|
ipcMain.on(Constants.IPC_Delete_File + '_sync', (event, data) => {
|
||||||
let driveLetters = {
|
try {
|
||||||
Hyperspace: [],
|
if (fs.existsSync(data.FilePath)) {
|
||||||
Sia: [],
|
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++) {
|
for (let i = 'c'.charCodeAt(0); i <= 'z'.charCodeAt(0); i++) {
|
||||||
const drive = (String.fromCharCode(i) + ':').toUpperCase();
|
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 {
|
try {
|
||||||
if (!fs.existsSync(drive)) {
|
if (!fs.existsSync(drive)) {
|
||||||
driveLetters.Hyperspace.push(drive);
|
for (const provider of Constants.PROVIDER_LIST) {
|
||||||
driveLetters.Sia.push(drive);
|
driveLetters[provider].push(drive);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hsLocation.length > 0) {
|
if (Object.keys(locations).length > 0) {
|
||||||
if (!driveLetters.Hyperspace.find((driveLetter) => {
|
for (const provider of Constants.PROVIDER_LIST) {
|
||||||
return driveLetter === hsLocation;
|
if (locations[provider].length > 0) {
|
||||||
|
if (!driveLetters[provider].find((driveLetter) => {
|
||||||
|
return driveLetter === locations[provider];
|
||||||
})) {
|
})) {
|
||||||
driveLetters.Hyperspace.push(hsLocation);
|
driveLetters[provider].push(locations[provider]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (siaLocation.length > 0) {
|
|
||||||
if (!driveLetters.Sia.find((driveLetter) => {
|
|
||||||
return driveLetter === siaLocation;
|
|
||||||
})) {
|
|
||||||
driveLetters.Sia.push(siaLocation);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const setImage = (hsLocation, siaLocation) => {
|
const setImage = (locations) => {
|
||||||
if (os.platform() === 'win32') {
|
let driveInUse;
|
||||||
|
if (Object.keys(locations).length > 0) {
|
||||||
|
for (const provider of Constants.PROVIDER_LIST) {
|
||||||
|
driveInUse = locations[provider].length > 0;
|
||||||
|
if (driveInUse)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let image;
|
let image;
|
||||||
if ((siaLocation.length > 0) && (hsLocation.length > 0)) {
|
if (driveInUse) {
|
||||||
image = nativeImage.createFromPath(path.join(__dirname, '/build/logo_both.png'));
|
image = nativeImage.createFromPath(path.join(__dirname, os.platform() === 'darwin' ? '/build/logo_both_mac.png' : '/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 {
|
} else {
|
||||||
image = nativeImage.createFromPath(path.join(__dirname, '/build/logo.png'));
|
image = nativeImage.createFromPath(path.join(__dirname, os.platform() === 'darwin' ? '/build/logo_mac.png' : '/build/logo.png'));
|
||||||
}
|
}
|
||||||
|
|
||||||
mainWindowTray.setImage(image);
|
mainWindowTray.setImage(image);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const dataDirectory = helpers.resolvePath(data.Directory);
|
const dataDirectory = helpers.resolvePath(data.Directory);
|
||||||
helpers
|
helpers
|
||||||
.detectRepertoryMounts(dataDirectory, data.Version)
|
.detectRepertoryMounts(dataDirectory, data.Version)
|
||||||
.then((results) => {
|
.then((results) => {
|
||||||
let hsLocation = results.Hyperspace.Location;
|
let storageData = {};
|
||||||
let siaLocation = results.Sia.Location;
|
let locations = {};
|
||||||
if (os.platform() === 'win32') {
|
for (const provider of Constants.PROVIDER_LIST) {
|
||||||
hsLocation = hsLocation.toUpperCase();
|
storageData[provider] = results[provider] ? results[provider] : {
|
||||||
siaLocation = siaLocation.toUpperCase();
|
Active: false,
|
||||||
grabDriveLetters(hsLocation, siaLocation);
|
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, {
|
standardIPCReply(event, Constants.IPC_Detect_Mounts_Reply, {
|
||||||
DriveLetters: driveLetters,
|
DriveLetters: driveLetters,
|
||||||
Locations: {
|
Locations: locations,
|
||||||
Hyperspace: hsLocation,
|
|
||||||
Sia: siaLocation,
|
|
||||||
},
|
|
||||||
PIDS: {
|
|
||||||
Hyperspace: results.Hyperspace.PID,
|
|
||||||
Sia: results.Sia.PID,
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
grabDriveLetters('', '');
|
if (os.platform() === 'win32') {
|
||||||
setImage('', '');
|
grabDriveLetters({});
|
||||||
|
}
|
||||||
|
setImage({});
|
||||||
standardIPCReply(event, Constants.IPC_Detect_Mounts_Reply, {
|
standardIPCReply(event, Constants.IPC_Detect_Mounts_Reply, {
|
||||||
DriveLetters: driveLetters,
|
DriveLetters: driveLetters,
|
||||||
}, error);
|
}, error);
|
||||||
@@ -379,57 +591,119 @@ ipcMain.on(Constants.IPC_Grab_UI_Releases, (event) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on(Constants.IPC_Install_Dependency, (event, data) => {
|
ipcMain.on(Constants.IPC_Install_Dependency, (event, data) => {
|
||||||
|
if (data.Source.toLowerCase().endsWith('.dmg')) {
|
||||||
helpers
|
helpers
|
||||||
.executeAndWait(data.Source)
|
.executeAsync('open', ['-a', 'Finder', '-W', data.Source])
|
||||||
.then(()=> {
|
.then(() => {
|
||||||
standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, {
|
standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, {
|
||||||
Source: data.Source,
|
Source: data.Source,
|
||||||
|
URL: data.URL,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error=> {
|
||||||
|
standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, {
|
||||||
|
Source: data.Source,
|
||||||
|
URL: data.URL,
|
||||||
|
}, error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
helpers
|
||||||
|
.executeAndWait(data.Source)
|
||||||
|
.then(() => {
|
||||||
|
standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, {
|
||||||
|
Source: data.Source,
|
||||||
|
URL: data.URL,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, {
|
standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, {
|
||||||
Source: data.Source,
|
Source: data.Source,
|
||||||
|
URL: data.URL,
|
||||||
}, error);
|
}, error);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on(Constants.IPC_Install_Upgrade, (event, data) => {
|
ipcMain.on(Constants.IPC_Install_Upgrade, (event, data) => {
|
||||||
|
if (os.platform() === 'win32') {
|
||||||
helpers
|
helpers
|
||||||
.executeAsync(data.Source)
|
.executeAsync(data.Source)
|
||||||
.then(()=> {
|
.then(() => {
|
||||||
mainWindow.close();
|
closeApplication();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
standardIPCReply(event, Constants.IPC_Install_Upgrade_Reply, {
|
standardIPCReply(event, Constants.IPC_Install_Upgrade_Reply, {
|
||||||
Source: data.Source,
|
Source: data.Source,
|
||||||
}, error);
|
}, 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) => {
|
ipcMain.on(Constants.IPC_Mount_Drive, (event, data) => {
|
||||||
|
expectedUnmount[data.StorageType] = false;
|
||||||
const dataDirectory = helpers.resolvePath(data.Directory);
|
const dataDirectory = helpers.resolvePath(data.Directory);
|
||||||
|
|
||||||
|
if (mountedLocations.indexOf(data.Location) !== -1) {
|
||||||
|
console.log(data.StorageType + ' already mounted: ' + data.Location);
|
||||||
|
} else {
|
||||||
|
mountedLocations.push(data.Location);
|
||||||
|
mountedData[data.Location] = {
|
||||||
|
DataDirectory: dataDirectory,
|
||||||
|
Version: data.Version,
|
||||||
|
StorageType: data.StorageType,
|
||||||
|
};
|
||||||
const errorHandler = (pid, error) => {
|
const errorHandler = (pid, error) => {
|
||||||
mountedPIDs.splice(mountedPIDs.indexOf(pid), 1);
|
if (mountedLocations.indexOf(data.Location) !== -1) {
|
||||||
|
mountedLocations.splice(mountedLocations.indexOf(data.Location), 1);
|
||||||
|
delete mountedData[data.Location];
|
||||||
|
}
|
||||||
|
|
||||||
standardIPCReply(event, Constants.IPC_Unmount_Drive_Reply, {
|
standardIPCReply(event, Constants.IPC_Unmount_Drive_Reply, {
|
||||||
PID: -1,
|
Expected: expectedUnmount[data.StorageType],
|
||||||
|
Location: data.Location,
|
||||||
StorageType: data.StorageType,
|
StorageType: data.StorageType,
|
||||||
}, error || Error(data.StorageType + ' Unmounted'));
|
}, error || Error(data.StorageType + ' Unmounted'));
|
||||||
};
|
};
|
||||||
helpers.executeMount(dataDirectory, data.Version, data.StorageType, data.Location, (error, pid) => {
|
helpers
|
||||||
|
.executeMount(dataDirectory, data.Version, data.StorageType, data.Location, data.NoConsoleSupported, (error, pid) => {
|
||||||
errorHandler(pid, error);
|
errorHandler(pid, error);
|
||||||
})
|
})
|
||||||
.then(pid => {
|
.then(() => {
|
||||||
if (pid !== -1) {
|
|
||||||
mountedPIDs.push(pid);
|
|
||||||
}
|
|
||||||
standardIPCReply(event, Constants.IPC_Mount_Drive_Reply, {
|
standardIPCReply(event, Constants.IPC_Mount_Drive_Reply, {
|
||||||
PID: pid,
|
|
||||||
StorageType: data.StorageType,
|
StorageType: data.StorageType,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
errorHandler(-1, error);
|
errorHandler(-1, error);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on(Constants.IPC_Save_State, (event, data) => {
|
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, () => {
|
ipcMain.on(Constants.IPC_Shutdown, () => {
|
||||||
app.quit();
|
closeApplication();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on(Constants.IPC_Unmount_Drive, (event, data) => {
|
ipcMain.on(Constants.IPC_Unmount_Drive, (event, data) => {
|
||||||
|
clearManualMountDetection(data.StorageType);
|
||||||
|
|
||||||
|
const dataDirectory = helpers.resolvePath(data.Directory);
|
||||||
|
expectedUnmount[data.StorageType] = true;
|
||||||
helpers
|
helpers
|
||||||
.stopProcessByPID(data.PID)
|
.stopMountProcess(dataDirectory, data.Version, data.StorageType)
|
||||||
.then((pid)=> {
|
.then((result)=> {
|
||||||
if (mountedPIDs.indexOf(pid) === -1) {
|
console.log(result);
|
||||||
event.sender.send(Constants.IPC_Unmount_Drive_Reply);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
|
|||||||
118
helpers.js
@@ -4,6 +4,7 @@ const os = require('os');
|
|||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const exec = require('child_process').exec;
|
const exec = require('child_process').exec;
|
||||||
const spawn = require('child_process').spawn;
|
const spawn = require('child_process').spawn;
|
||||||
|
const Constants = require('./src/constants');
|
||||||
|
|
||||||
const tryParse = (j, def) => {
|
const tryParse = (j, def) => {
|
||||||
try {
|
try {
|
||||||
@@ -37,18 +38,15 @@ module.exports.detectRepertoryMounts = (directory, version) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
process.on('exit', () => {
|
process.on('exit', () => {
|
||||||
resolve(tryParse(result, {
|
let defaultData = {};
|
||||||
Hyperspace: {
|
for (const provider of Constants.PROVIDER_LIST) {
|
||||||
|
defaultData[provider] = {
|
||||||
Active: false,
|
Active: false,
|
||||||
Location: '',
|
Location: '',
|
||||||
PID: -1,
|
PID: -1,
|
||||||
},
|
};
|
||||||
Sia: {
|
}
|
||||||
Active: false,
|
resolve(tryParse(result, defaultData));
|
||||||
Location: '',
|
|
||||||
PID: -1,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
process.unref();
|
process.unref();
|
||||||
});
|
});
|
||||||
@@ -120,15 +118,15 @@ module.exports.executeAndWait = command => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.executeAsync = (command) => {
|
module.exports.executeAsync = (command, args=[]) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const launchProcess = (count, timeout) => {
|
const launchProcess = (count, timeout) => {
|
||||||
const processOptions = {
|
const processOptions = {
|
||||||
detached: true,
|
detached: true,
|
||||||
shell: true,
|
shell: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const process = new spawn(command, [], processOptions);
|
const process = new spawn(command, args, processOptions);
|
||||||
const pid = process.pid;
|
const pid = process.pid;
|
||||||
|
|
||||||
process.on('error', (err) => {
|
process.on('error', (err) => {
|
||||||
@@ -136,15 +134,18 @@ module.exports.executeAsync = (command) => {
|
|||||||
reject(err, pid);
|
reject(err, pid);
|
||||||
} else {
|
} else {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
setTimeout(()=>launchProcess(count, setTimeout(() => resolve(), 3000)), 1000);
|
setTimeout(()=> launchProcess(count, setTimeout(() => resolve(), 3000)), 1000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on('exit', (code) => {
|
process.on('exit', (code) => {
|
||||||
|
if (code !== 0) {
|
||||||
if (++count === 5) {
|
if (++count === 5) {
|
||||||
reject(code, pid);
|
reject(code, pid);
|
||||||
} else {
|
} else {
|
||||||
setTimeout(()=>launchProcess(count, setTimeout(() => resolve(), 3000)), 1000);
|
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) => {
|
return new Promise((resolve) => {
|
||||||
const processOptions = {
|
const processOptions = {
|
||||||
detached: true,
|
detached: false,
|
||||||
shell: true,
|
shell: os.platform() !== 'darwin',
|
||||||
|
stdio: 'ignore',
|
||||||
};
|
};
|
||||||
|
|
||||||
const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory');
|
const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory');
|
||||||
const args = [];
|
const args = [];
|
||||||
if (storageType.toLowerCase() === 'hyperspace') {
|
if (Constants.PROVIDER_ARG[storageType.toLowerCase()].length > 0) {
|
||||||
args.push('-hs');
|
args.push(Constants.PROVIDER_ARG[storageType.toLowerCase()]);
|
||||||
}
|
}
|
||||||
if (os.platform() === 'linux') {
|
|
||||||
args.push("-o");
|
if ((os.platform() === 'linux') || (os.platform() === 'darwin')) {
|
||||||
args.push("big_writes");
|
args.push('-o');
|
||||||
|
args.push('big_writes');
|
||||||
|
args.push('-f');
|
||||||
|
if (noConsoleSupported) {
|
||||||
|
args.push('-nc');
|
||||||
}
|
}
|
||||||
if (os.platform() === 'win32') {
|
} else if (os.platform() === 'win32') {
|
||||||
args.push('-hidden');
|
args.push('-hidden');
|
||||||
}
|
}
|
||||||
args.push(location);
|
args.push(location);
|
||||||
|
|
||||||
const process = new spawn(command, args, processOptions);
|
let process = new spawn(command, args, processOptions);
|
||||||
const pid = process.pid;
|
const pid = process.pid;
|
||||||
|
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
@@ -187,11 +193,11 @@ module.exports.executeMount = (directory, version, storageType, location, exitCa
|
|||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
exitCallback(err, pid);
|
exitCallback(err, pid);
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on('exit', (code) => {
|
process.on('exit', (code) => {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
exitCallback(code, pid);
|
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 command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory');
|
||||||
const args = [];
|
const args = [];
|
||||||
args.push('-dc');
|
args.push('-dc');
|
||||||
if (storageType.toLowerCase() === 'hyperspace') {
|
if (Constants.PROVIDER_ARG[storageType.toLowerCase()].length > 0) {
|
||||||
args.push('-hs');
|
args.push(Constants.PROVIDER_ARG[storageType.toLowerCase()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const process = new spawn(command, args, processOptions);
|
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 command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory');
|
||||||
const args = [];
|
const args = [];
|
||||||
args.push('-gt');
|
args.push('-gt');
|
||||||
if (storageType.toLowerCase() === 'hyperspace') {
|
if (Constants.PROVIDER_ARG[storageType.toLowerCase()].length > 0) {
|
||||||
args.push('-hs');
|
args.push(Constants.PROVIDER_ARG[storageType.toLowerCase()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const process = new spawn(command, args, processOptions);
|
const process = new spawn(command, args, processOptions);
|
||||||
@@ -276,8 +282,8 @@ module.exports.getConfigTemplate = (directory, version, storageType) => {
|
|||||||
|
|
||||||
module.exports.getMissingDependencies = dependencies => {
|
module.exports.getMissingDependencies = dependencies => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!dependencies || (dependencies.length === 0)) {
|
if (!dependencies) {
|
||||||
reject(Error('Dependency list is empty'));
|
reject(Error('Dependency list is invalid'));
|
||||||
}
|
}
|
||||||
|
|
||||||
let missing = [];
|
let missing = [];
|
||||||
@@ -334,7 +340,7 @@ module.exports.getMissingDependencies = dependencies => {
|
|||||||
} else {
|
} else {
|
||||||
for (const dep of dependencies) {
|
for (const dep of dependencies) {
|
||||||
try {
|
try {
|
||||||
if (!fs.lstatSync(dep.file).isFile()) {
|
if (!(fs.lstatSync(dep.file).isFile() || fs.lstatSync(dep.file).isSymbolicLink())) {
|
||||||
missing.push(dep);
|
missing.push(dep);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -415,8 +421,8 @@ module.exports.setConfigValue = (name, value, directory, storageType, version) =
|
|||||||
args.push('-set');
|
args.push('-set');
|
||||||
args.push(name);
|
args.push(name);
|
||||||
args.push(value);
|
args.push(value);
|
||||||
if (storageType.toLowerCase() === 'hyperspace') {
|
if (Constants.PROVIDER_ARG[storageType.toLowerCase()].length > 0) {
|
||||||
args.push('-hs');
|
args.push(Constants.PROVIDER_ARG[storageType.toLowerCase()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const process = new spawn(command, args, processOptions);
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
const processOptions = {
|
const processOptions = {
|
||||||
detached: true,
|
detached: os.platform() === 'darwin',
|
||||||
shell: false,
|
shell: os.platform() !== 'darwin',
|
||||||
windowsHide: true,
|
windowsHide: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const command = (os.platform() === 'win32') ? 'taskkill.exe' : '';
|
const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory');
|
||||||
const args = [];
|
const args = ['-unmount'];
|
||||||
args.push('/PID');
|
if (Constants.PROVIDER_ARG[storageType.toLowerCase()].length > 0) {
|
||||||
args.push(pid);
|
args.push(Constants.PROVIDER_ARG[storageType.toLowerCase()]);
|
||||||
|
}
|
||||||
|
|
||||||
const process = new spawn(command, args, processOptions);
|
const process = new spawn(command, args, processOptions);
|
||||||
|
const pid = process.pid;
|
||||||
process.on('error', (err) => {
|
process.on('error', (err) => {
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
process.on('exit', () => {
|
process.on('exit', (code) => {
|
||||||
setTimeout(()=>resolve(pid), 3000);
|
resolve({
|
||||||
|
PID: pid,
|
||||||
|
Code: code,
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (os.platform() === 'darwin') {
|
||||||
process.unref();
|
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",
|
"name": "repertory-ui",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"private": true,
|
"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": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.1",
|
"@fortawesome/fontawesome-svg-core": "^1.2.10",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.1.1",
|
"@fortawesome/free-solid-svg-icons": "^5.6.1",
|
||||||
"@fortawesome/react-fontawesome": "^0.1.0",
|
"@fortawesome/react-fontawesome": "^0.1.0",
|
||||||
"auto-launch": "^5.0.5",
|
"auto-launch": "^5.0.5",
|
||||||
"autoprefixer": "7.1.6",
|
"autoprefixer": "7.1.6",
|
||||||
@@ -17,12 +20,12 @@
|
|||||||
"babel-runtime": "6.26.0",
|
"babel-runtime": "6.26.0",
|
||||||
"case-sensitive-paths-webpack-plugin": "2.1.1",
|
"case-sensitive-paths-webpack-plugin": "2.1.1",
|
||||||
"chalk": "1.1.3",
|
"chalk": "1.1.3",
|
||||||
"color": "^3.0.0",
|
"color": "^3.1.0",
|
||||||
"color-string": "^1.5.2",
|
"color-string": "^1.5.2",
|
||||||
"css-loader": "0.28.7",
|
"css-loader": "0.28.7",
|
||||||
"dotenv": "4.0.0",
|
"dotenv": "4.0.0",
|
||||||
"dotenv-expand": "4.2.0",
|
"dotenv-expand": "4.2.0",
|
||||||
"electron-debug": "^2.0.0",
|
"electron-debug": "^2.1.0",
|
||||||
"eslint": "4.10.0",
|
"eslint": "4.10.0",
|
||||||
"eslint-config-react-app": "^2.1.0",
|
"eslint-config-react-app": "^2.1.0",
|
||||||
"eslint-loader": "1.9.0",
|
"eslint-loader": "1.9.0",
|
||||||
@@ -35,23 +38,23 @@
|
|||||||
"fs-extra": "3.0.1",
|
"fs-extra": "3.0.1",
|
||||||
"html-webpack-plugin": "2.29.0",
|
"html-webpack-plugin": "2.29.0",
|
||||||
"jest": "20.0.4",
|
"jest": "20.0.4",
|
||||||
"node-schedule": "^1.3.0",
|
"node-schedule": "^1.3.1",
|
||||||
"npm": "^6.2.0",
|
"npm": "^6.6.0",
|
||||||
"object-assign": "4.1.1",
|
"object-assign": "4.1.1",
|
||||||
"postcss-flexbugs-fixes": "3.2.0",
|
"postcss-flexbugs-fixes": "3.2.0",
|
||||||
"postcss-loader": "2.0.8",
|
"postcss-loader": "2.0.8",
|
||||||
"promise": "8.0.1",
|
"promise": "8.0.1",
|
||||||
"raf": "3.4.0",
|
"raf": "3.4.0",
|
||||||
"react": "^16.4.1",
|
"react": "^16.6.1",
|
||||||
"react-css-modules": "^4.7.4",
|
"react-css-modules": "^4.7.8",
|
||||||
"react-dev-utils": "^5.0.1",
|
"react-dev-utils": "^5.0.3",
|
||||||
"react-dom": "^16.4.1",
|
"react-dom": "^16.6.1",
|
||||||
"react-loader-spinner": "^2.0.6",
|
"react-loader-spinner": "^2.3.0",
|
||||||
"react-tooltip": "^3.8.4",
|
"react-tooltip": "^3.9.0",
|
||||||
"resolve": "1.6.0",
|
"resolve": "1.6.0",
|
||||||
"style-loader": "0.19.0",
|
"style-loader": "0.19.0",
|
||||||
"sw-precache-webpack-plugin": "0.11.4",
|
"sw-precache-webpack-plugin": "0.11.4",
|
||||||
"unzipper": "^0.9.3",
|
"unzipper": "^0.9.6",
|
||||||
"url-loader": "0.6.2",
|
"url-loader": "0.6.2",
|
||||||
"webpack": "3.8.1",
|
"webpack": "3.8.1",
|
||||||
"webpack-dev-server": "2.9.4",
|
"webpack-dev-server": "2.9.4",
|
||||||
@@ -63,14 +66,20 @@
|
|||||||
"start": "node scripts/start.js",
|
"start": "node scripts/start.js",
|
||||||
"build": "node scripts/build.js",
|
"build": "node scripts/build.js",
|
||||||
"test": "node scripts/test.js --env=jsdom",
|
"test": "node scripts/test.js --env=jsdom",
|
||||||
"electron-dev": "cross-env ELECTRON_START_URL=http://localhost:3000 electron .",
|
"electron-dev": "cross-env ELECTRON_START_URL=http://localhost:3000 electron %NODE_DEBUG_OPTION% .",
|
||||||
"pack": "npm run build && electron-builder --dir",
|
"electron-dev-unix": "cross-env ELECTRON_START_URL=http://localhost:3000 electron $NODE_DEBUG_OPTION .",
|
||||||
"dist": "npm run build && electron-builder"
|
"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": {
|
"devDependencies": {
|
||||||
"cross-env": "^5.2.0",
|
"cross-env": "^5.2.0",
|
||||||
"electron": "^3.0.2",
|
"electron": "^4.1.0",
|
||||||
"electron-builder": "^20.28.4",
|
"electron-builder": "^20.38.5",
|
||||||
"extract-text-webpack-plugin": "^3.0.2",
|
"extract-text-webpack-plugin": "^3.0.2",
|
||||||
"webpack-browser-plugin": "^1.0.20"
|
"webpack-browser-plugin": "^1.0.20"
|
||||||
},
|
},
|
||||||
@@ -120,6 +129,7 @@
|
|||||||
"homepage": "./",
|
"homepage": "./",
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "repertory-ui",
|
"appId": "repertory-ui",
|
||||||
|
"artifactName": "${productName}_${version}_${os}_${arch}.${ext}",
|
||||||
"files": [
|
"files": [
|
||||||
"./electron.js",
|
"./electron.js",
|
||||||
"./src/constants.js",
|
"./src/constants.js",
|
||||||
@@ -128,10 +138,19 @@
|
|||||||
"./helpers.js"
|
"./helpers.js"
|
||||||
],
|
],
|
||||||
"linux": {
|
"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": {
|
"win": {
|
||||||
"icon": "./build/icon.ico"
|
"icon": "./build/icon.ico",
|
||||||
|
"target": "nsis"
|
||||||
},
|
},
|
||||||
"directories": {
|
"directories": {
|
||||||
"buildResources": "public"
|
"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": {
|
"Locations": {
|
||||||
"win32": {
|
"win32": {
|
||||||
"1.0.1": {
|
"1.0.2": {
|
||||||
"hash": "",
|
"hash": "",
|
||||||
"urls": [
|
"urls": ["https://pixeldrain.com/u/4oJeVntd"]
|
||||||
"https://sia.pixeldrain.com/api/file/Alo1IF1u"
|
}
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"1.0.0": {
|
"darwin": {
|
||||||
|
"1.0.2": {
|
||||||
"hash": "",
|
"hash": "",
|
||||||
"urls": [
|
"urls": ["https://pixeldrain.com/u/OtxPlbOI"]
|
||||||
"https://sia.pixeldrain.com/api/file/qXM6pVZZ"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Versions": {
|
"Versions": {
|
||||||
"win32": [
|
"win32": [
|
||||||
"1.0.1",
|
"1.0.2"
|
||||||
"1.0.0"
|
],
|
||||||
|
"darwin": [
|
||||||
|
"1.0.2"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
417
src/App.js
@@ -1,4 +1,4 @@
|
|||||||
import React, {Component} from 'react';
|
import React from 'react';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import styles from './App.css';
|
import styles from './App.css';
|
||||||
import Box from './components/UI/Box/Box';
|
import Box from './components/UI/Box/Box';
|
||||||
@@ -15,35 +15,37 @@ import ReleaseVersionDisplay from './components/ReleaseVersionDisplay/ReleaseVer
|
|||||||
import Text from './components/UI/Text/Text';
|
import Text from './components/UI/Text/Text';
|
||||||
import UpgradeIcon from './components/UpgradeIcon/UpgradeIcon';
|
import UpgradeIcon from './components/UpgradeIcon/UpgradeIcon';
|
||||||
import UpgradeUI from './components/UpgradeUI/UpgradeUI';
|
import UpgradeUI from './components/UpgradeUI/UpgradeUI';
|
||||||
|
import IPCContainer from './containers/IPCContainer/IPCContainer';
|
||||||
|
|
||||||
const Constants = require('./constants');
|
const Constants = require('./constants');
|
||||||
const Scheduler = require('node-schedule');
|
const Scheduler = require('node-schedule');
|
||||||
|
|
||||||
let ipcRenderer = null;
|
class App extends IPCContainer {
|
||||||
if (!process.versions.hasOwnProperty('electron')) {
|
|
||||||
ipcRenderer = ((window && window.require) ? window.require('electron').ipcRenderer : null);
|
|
||||||
}
|
|
||||||
|
|
||||||
class App extends Component {
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
if (ipcRenderer) {
|
for (const provider of Constants.PROVIDER_LIST) {
|
||||||
ipcRenderer.on(Constants.IPC_Check_Installed_Reply, this.onCheckInstalledReply);
|
this.state[provider] = {
|
||||||
ipcRenderer.on(Constants.IPC_Download_File_Complete, this.onDownloadFileComplete);
|
AutoMount: false,
|
||||||
ipcRenderer.on(Constants.IPC_Download_File_Progress, this.onDownloadFileProgress);
|
AutoRestart: false,
|
||||||
ipcRenderer.on(Constants.IPC_Extract_Release_Complete, this.onExtractReleaseComplete);
|
MountLocation: '',
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = {
|
state = {
|
||||||
AllowDownload: false,
|
AllowDownload: false,
|
||||||
AutoMountProcessed: false,
|
AutoMountProcessed: false,
|
||||||
@@ -60,14 +62,9 @@ class App extends Component {
|
|||||||
DownloadingRelease: false,
|
DownloadingRelease: false,
|
||||||
DownloadingUpgrade: false,
|
DownloadingUpgrade: false,
|
||||||
ExtractActive: false,
|
ExtractActive: false,
|
||||||
Hyperspace: {
|
|
||||||
AutoMount: false,
|
|
||||||
MountLocation: '',
|
|
||||||
},
|
|
||||||
LocationsLookup: {},
|
LocationsLookup: {},
|
||||||
MissingDependencies: [],
|
MissingDependencies: [],
|
||||||
MountsBusy: false,
|
MountsBusy: false,
|
||||||
Platform: 'unknown',
|
|
||||||
Release: 3,
|
Release: 3,
|
||||||
ReleaseTypes: [
|
ReleaseTypes: [
|
||||||
'Release',
|
'Release',
|
||||||
@@ -76,10 +73,6 @@ class App extends Component {
|
|||||||
'Alpha',
|
'Alpha',
|
||||||
],
|
],
|
||||||
InstalledVersion: 'none',
|
InstalledVersion: 'none',
|
||||||
Sia: {
|
|
||||||
AutoMount: false,
|
|
||||||
MountLocation: '',
|
|
||||||
},
|
|
||||||
UpgradeAvailable: false,
|
UpgradeAvailable: false,
|
||||||
UpgradeData: {},
|
UpgradeData: {},
|
||||||
UpgradeDismissed: false,
|
UpgradeDismissed: false,
|
||||||
@@ -101,30 +94,24 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
checkVersionInstalled = (release, version, versionLookup) => {
|
checkVersionInstalled = () => {
|
||||||
if (!versionLookup) {
|
|
||||||
versionLookup = this.state.VersionLookup;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedVersion = versionLookup[this.state.ReleaseTypes[release]][version];
|
|
||||||
this.setState({
|
this.setState({
|
||||||
AllowDownload: false,
|
AllowDownload: false,
|
||||||
});
|
}, ()=> {
|
||||||
|
const selectedVersion = this.getSelectedVersion();
|
||||||
if (selectedVersion !== 'unavailable') {
|
if (selectedVersion !== 'unavailable') {
|
||||||
if (ipcRenderer) {
|
|
||||||
let dependencies = [];
|
let dependencies = [];
|
||||||
if (this.state.LocationsLookup[selectedVersion] && this.state.LocationsLookup[selectedVersion].dependencies) {
|
if (this.state.LocationsLookup[selectedVersion] && this.state.LocationsLookup[selectedVersion].dependencies) {
|
||||||
dependencies = 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,
|
Dependencies: dependencies,
|
||||||
Directory: Constants.DATA_LOCATIONS[this.props.platform],
|
Directory: Constants.DATA_LOCATIONS[this.props.platform],
|
||||||
Version: selectedVersion,
|
Version: selectedVersion,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
closeErrorDisplay = () => {
|
closeErrorDisplay = () => {
|
||||||
@@ -133,9 +120,7 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.ErrorCritical) {
|
if (this.state.ErrorCritical) {
|
||||||
if (ipcRenderer) {
|
this.sendRequest(Constants.IPC_Shutdown);
|
||||||
ipcRenderer.send(Constants.IPC_Shutdown);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
DisplayError: false,
|
DisplayError: false,
|
||||||
@@ -144,51 +129,63 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillUnmount = () => {
|
extractFileNameFromURL = url => {
|
||||||
if (ipcRenderer) {
|
const parts = url.split('/');
|
||||||
ipcRenderer.removeListener(Constants.IPC_Check_Installed_Reply, this.onCheckInstalledReply);
|
return parts[parts.length - 1];
|
||||||
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);
|
extractRelease = (data) => {
|
||||||
ipcRenderer.removeListener(Constants.IPC_Get_State_Reply, this.onGetStateReply);
|
if (data.Success) {
|
||||||
ipcRenderer.removeListener(Constants.IPC_Grab_Releases_Reply, this.onGrabReleasesReply);
|
const selectedVersion = this.getSelectedVersion();
|
||||||
ipcRenderer.removeListener(Constants.IPC_Grab_UI_Releases_Reply, this.onGrabUiReleasesReply);
|
this.sendRequest(Constants.IPC_Extract_Release, {
|
||||||
ipcRenderer.removeListener(Constants.IPC_Install_Dependency_Reply, this.onInstallDependencyReply);
|
Directory: Constants.DATA_LOCATIONS[this.props.platform],
|
||||||
ipcRenderer.removeListener(Constants.IPC_Install_Upgrade_Reply, this.onInstallUpgradeReply);
|
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 = () => {
|
grabReleases = () => {
|
||||||
if (this.props.platform !== 'unknown') {
|
if (this.props.platform !== 'unknown') {
|
||||||
if (ipcRenderer) {
|
this.sendRequest(Constants.IPC_Grab_Releases);
|
||||||
ipcRenderer.send(Constants.IPC_Grab_Releases);
|
this.sendRequest(Constants.IPC_Grab_UI_Releases);
|
||||||
ipcRenderer.send(Constants.IPC_Grab_UI_Releases);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
handleAutoMountChanged = (storageType, e) => {
|
handleAutoMountChanged = (storageType, e) => {
|
||||||
let sia = {
|
const state = {
|
||||||
...this.state.Sia
|
...this.state[storageType],
|
||||||
|
AutoMount: e.target.checked,
|
||||||
|
};
|
||||||
|
this.setState({
|
||||||
|
[storageType]: state,
|
||||||
|
}, ()=> {
|
||||||
|
this.saveState();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let hyperspace = {
|
handleAutoRestartChanged = (storageType, e) => {
|
||||||
...this.state.Hyperspace
|
const state = {
|
||||||
|
...this.state[storageType],
|
||||||
|
AutoRestart: e.target.checked,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (storageType === 'Hyperspace') {
|
|
||||||
hyperspace.AutoMount = e.target.checked;
|
|
||||||
this.setState({
|
this.setState({
|
||||||
Hyperspace: hyperspace,
|
[storageType]: state,
|
||||||
|
}, ()=> {
|
||||||
|
this.saveState();
|
||||||
});
|
});
|
||||||
} else if (storageType === 'Sia') {
|
|
||||||
sia.AutoMount = e.target.checked;
|
|
||||||
this.setState({
|
|
||||||
Sia: sia,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.saveState(this.state.Release, this.state.Version, sia, hyperspace);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handleConfigClicked = (storageType) => {
|
handleConfigClicked = (storageType) => {
|
||||||
@@ -204,22 +201,17 @@ class App extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
handleDependencyDownload = (url) => {
|
handleDependencyDownload = (url) => {
|
||||||
if (ipcRenderer) {
|
|
||||||
const items = url.split('/');
|
|
||||||
const fileName = items[items.length - 1];
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
DownloadActive: true,
|
DownloadActive: true,
|
||||||
DownloadingDependency: true,
|
DownloadingDependency: true,
|
||||||
DownloadName: fileName,
|
DownloadName: this.extractFileNameFromURL(url),
|
||||||
});
|
}, ()=> {
|
||||||
|
this.sendRequest(Constants.IPC_Download_File, {
|
||||||
ipcRenderer.send(Constants.IPC_Download_File, {
|
|
||||||
Directory: Constants.DATA_LOCATIONS[this.props.platform],
|
Directory: Constants.DATA_LOCATIONS[this.props.platform],
|
||||||
Filename: fileName,
|
Filename: this.state.DownloadName,
|
||||||
URL: url,
|
URL: url,
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
handleMountLocationChanged = (storageType, location) => {
|
handleMountLocationChanged = (storageType, location) => {
|
||||||
@@ -229,15 +221,9 @@ class App extends Component {
|
|||||||
};
|
};
|
||||||
this.setState({
|
this.setState({
|
||||||
[storageType]: state,
|
[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) => {
|
handleReleaseChanged = (e) => {
|
||||||
@@ -246,54 +232,81 @@ class App extends Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
Release: val,
|
Release: val,
|
||||||
Version: versionIndex
|
Version: versionIndex
|
||||||
|
}, ()=> {
|
||||||
|
this.saveState();
|
||||||
|
this.checkVersionInstalled( );
|
||||||
});
|
});
|
||||||
this.saveState(val, versionIndex, this.state.Sia, this.state.Hyperspace);
|
|
||||||
this.checkVersionInstalled(val, versionIndex);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handleReleaseDownload = () => {
|
handleReleaseDownload = () => {
|
||||||
const selectedVersion = this.state.VersionLookup[this.state.ReleaseTypes[this.state.Release]][this.state.Version];
|
const selectedVersion = this.getSelectedVersion();
|
||||||
const fileName = selectedVersion + '.zip';
|
const fileName = selectedVersion + '.zip';
|
||||||
if (ipcRenderer) {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
DownloadActive: true,
|
DownloadActive: true,
|
||||||
DownloadingRelease: true,
|
DownloadingRelease: true,
|
||||||
DownloadName: fileName,
|
DownloadName: fileName,
|
||||||
});
|
}, () => {
|
||||||
|
this.sendRequest(Constants.IPC_Download_File, {
|
||||||
ipcRenderer.send(Constants.IPC_Download_File, {
|
|
||||||
Directory: Constants.DATA_LOCATIONS[this.props.platform],
|
Directory: Constants.DATA_LOCATIONS[this.props.platform],
|
||||||
Filename: fileName,
|
Filename: this.state.DownloadName,
|
||||||
URL: this.state.LocationsLookup[selectedVersion].urls[0],
|
URL: this.state.LocationsLookup[selectedVersion].urls[0],
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
handleUIDownload = () => {
|
handleUIDownload = () => {
|
||||||
if (ipcRenderer) {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
DownloadActive: true,
|
DownloadActive: true,
|
||||||
DownloadingUpgrade: true,
|
DownloadingUpgrade: true,
|
||||||
DownloadName: 'UI Upgrade',
|
DownloadName: 'UI Upgrade',
|
||||||
});
|
}, ()=> {
|
||||||
|
const url = this.state.UpgradeData.urls[0];
|
||||||
ipcRenderer.send(Constants.IPC_Download_File, {
|
this.sendRequest(Constants.IPC_Download_File, {
|
||||||
Directory: Constants.DATA_LOCATIONS[this.props.platform],
|
Directory: Constants.DATA_LOCATIONS[this.props.platform],
|
||||||
Filename: this.props.platform === 'win32' ? 'upgrade.exe' : 'upgrade',
|
Filename: this.props.platform === 'win32' ? 'upgrade.exe' : this.extractFileNameFromURL(url),
|
||||||
URL: this.state.UpgradeData.urls[0],
|
URL: url,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
this.setState({UpgradeDismissed: true});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handleVersionChanged = (e) => {
|
handleVersionChanged = (e) => {
|
||||||
const val = parseInt(e.target.value, 10);
|
|
||||||
this.setState({
|
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 = () => {
|
notifyAutoMountProcessed = () => {
|
||||||
@@ -336,48 +349,11 @@ class App extends Component {
|
|||||||
|
|
||||||
onDownloadFileComplete = (event, arg) => {
|
onDownloadFileComplete = (event, arg) => {
|
||||||
if (this.state.DownloadingRelease) {
|
if (this.state.DownloadingRelease) {
|
||||||
if (arg.data.Success) {
|
this.extractRelease(arg.data);
|
||||||
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: '',
|
|
||||||
});
|
|
||||||
} else if (this.state.DownloadingDependency) {
|
} else if (this.state.DownloadingDependency) {
|
||||||
if (arg.data.Success) {
|
this.installDependency(arg.data);
|
||||||
ipcRenderer.send(Constants.IPC_Install_Dependency, {
|
|
||||||
Source: arg.data.Destination,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
DownloadActive: false,
|
|
||||||
DownloadProgress: 0.0,
|
|
||||||
DownloadingDependency: arg.data.Success,
|
|
||||||
DownloadName: '',
|
|
||||||
});
|
|
||||||
} else if (this.state.DownloadingUpgrade) {
|
} else if (this.state.DownloadingUpgrade) {
|
||||||
if (arg.data.Success) {
|
this.installUpgrade(arg.data);
|
||||||
ipcRenderer.send(Constants.IPC_Install_Upgrade, {
|
|
||||||
Source: arg.data.Destination,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
DownloadActive: false,
|
|
||||||
DownloadProgress: 0.0,
|
|
||||||
DownloadingUpgrade: false,
|
|
||||||
DownloadName: '',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
DownloadActive: false,
|
DownloadActive: false,
|
||||||
@@ -394,32 +370,41 @@ class App extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onExtractReleaseComplete = (event, arg) => {
|
onExtractReleaseComplete = (event, arg) => {
|
||||||
ipcRenderer.send(Constants.IPC_Delete_File, {
|
this.sendRequest(Constants.IPC_Delete_File, {
|
||||||
FilePath: arg.data.Source,
|
FilePath: arg.data.Source,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
ExtractActive: false,
|
ExtractActive: false,
|
||||||
|
}, ()=> {
|
||||||
|
this.checkVersionInstalled( );
|
||||||
});
|
});
|
||||||
this.checkVersionInstalled(this.state.Release, this.state.Version);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onGetStateReply = (event, arg) => {
|
onGetStateReply = (event, arg) => {
|
||||||
if (arg.data) {
|
if (arg.data) {
|
||||||
if (arg.data.Hyperspace.AutoMount === undefined) {
|
let state = {
|
||||||
arg.data.Hyperspace['AutoMount'] = false;
|
|
||||||
}
|
|
||||||
if (arg.data.Sia.AutoMount === undefined) {
|
|
||||||
arg.data.Sia['AutoMount'] = false;
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
Hyperspace: arg.data.Hyperspace,
|
|
||||||
Release: arg.data.Release,
|
Release: arg.data.Release,
|
||||||
Sia: arg.data.Sia,
|
|
||||||
Version: arg.data.Version,
|
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();
|
this.grabReleases();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.grabReleases();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onGrabReleasesReply = ()=> {
|
onGrabReleasesReply = ()=> {
|
||||||
@@ -428,7 +413,7 @@ class App extends Component {
|
|||||||
let version = this.state.Version;
|
let version = this.state.Version;
|
||||||
if ((version === -1) || !versionLookup[this.state.ReleaseTypes[this.state.Release]][version]) {
|
if ((version === -1) || !versionLookup[this.state.ReleaseTypes[this.state.Release]][version]) {
|
||||||
version = latestVersion;
|
version = latestVersion;
|
||||||
this.saveState(this.state.Release, version, this.state.Sia, this.state.Hyperspace);
|
this.saveState(version);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -437,12 +422,13 @@ class App extends Component {
|
|||||||
Version: version,
|
Version: version,
|
||||||
VersionAvailable: version !== latestVersion,
|
VersionAvailable: version !== latestVersion,
|
||||||
VersionLookup: versionLookup,
|
VersionLookup: versionLookup,
|
||||||
|
}, () => {
|
||||||
|
this.checkVersionInstalled( );
|
||||||
});
|
});
|
||||||
|
|
||||||
this.checkVersionInstalled(this.state.Release, version, versionLookup);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
axios.get(Constants.RELEASES_URL)
|
axios
|
||||||
|
.get(Constants.RELEASES_URL)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
const versionLookup = {
|
const versionLookup = {
|
||||||
Alpha: response.data.Versions.Alpha[this.props.platform],
|
Alpha: response.data.Versions.Alpha[this.props.platform],
|
||||||
@@ -475,7 +461,8 @@ class App extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onGrabUiReleasesReply = ()=> {
|
onGrabUiReleasesReply = ()=> {
|
||||||
axios.get(Constants.UI_RELEASES_URL)
|
axios
|
||||||
|
.get(Constants.UI_RELEASES_URL)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
if (data.Versions &&
|
if (data.Versions &&
|
||||||
@@ -497,14 +484,18 @@ class App extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onInstallDependencyReply = (event, arg) => {
|
onInstallDependencyReply = (event, arg) => {
|
||||||
ipcRenderer.send(Constants.IPC_Delete_File, {
|
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,
|
FilePath: arg.data.Source,
|
||||||
});
|
});
|
||||||
this.checkVersionInstalled(this.state.Release, this.state.Version);
|
this.checkVersionInstalled();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onInstallUpgradeReply = (event, arg) => {
|
onInstallUpgradeReply = (event, arg) => {
|
||||||
ipcRenderer.sendSync(Constants.IPC_Delete_File, {
|
this.sendSyncRequest(Constants.IPC_Delete_File, {
|
||||||
FilePath: arg.data.Source,
|
FilePath: arg.data.Source,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -515,18 +506,19 @@ class App extends Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
saveState = (release, version, sia, hyperspace)=> {
|
saveState = version => {
|
||||||
if (ipcRenderer) {
|
let state = {
|
||||||
ipcRenderer.send(Constants.IPC_Save_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],
|
Directory: Constants.DATA_LOCATIONS[this.props.platform],
|
||||||
State: {
|
State: state
|
||||||
Hyperspace: hyperspace,
|
|
||||||
Release: release,
|
|
||||||
Sia: sia,
|
|
||||||
Version: version,
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
setErrorState = (error, action, critical) => {
|
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() {
|
render() {
|
||||||
const selectedVersion = (this.state.Version === -1) ?
|
const selectedVersion = (this.state.Version === -1) ?
|
||||||
'unavailable' :
|
'unavailable' :
|
||||||
this.state.VersionLookup[this.state.ReleaseTypes[this.state.Release]][this.state.Version];
|
this.getSelectedVersion();
|
||||||
|
|
||||||
const downloadEnabled = this.state.AllowDownload &&
|
const downloadEnabled = this.state.AllowDownload &&
|
||||||
!this.state.MountsBusy &&
|
!this.state.MountsBusy &&
|
||||||
@@ -555,11 +565,18 @@ class App extends Component {
|
|||||||
(selectedVersion !== 'unavailable') &&
|
(selectedVersion !== 'unavailable') &&
|
||||||
(selectedVersion !== this.state.InstalledVersion);
|
(selectedVersion !== this.state.InstalledVersion);
|
||||||
|
|
||||||
const allowMount = this.state.InstalledVersion !== 'none';
|
|
||||||
const missingDependencies = (this.state.MissingDependencies.length > 0);
|
const missingDependencies = (this.state.MissingDependencies.length > 0);
|
||||||
|
const allowMount = this.state.InstalledVersion !== 'none' && !missingDependencies;
|
||||||
|
|
||||||
const allowConfig = this.state.LocationsLookup[selectedVersion] &&
|
const allowConfig = this.state.LocationsLookup[selectedVersion] &&
|
||||||
this.state.LocationsLookup[selectedVersion].config_support;
|
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 &&
|
const showDependencies = missingDependencies &&
|
||||||
!this.state.DownloadActive;
|
!this.state.DownloadActive;
|
||||||
|
|
||||||
@@ -605,9 +622,8 @@ class App extends Component {
|
|||||||
<DependencyList allowDownload={!this.state.DownloadingDependency}
|
<DependencyList allowDownload={!this.state.DownloadingDependency}
|
||||||
dependencies={this.state.MissingDependencies}
|
dependencies={this.state.MissingDependencies}
|
||||||
onDownload={this.handleDependencyDownload}/>
|
onDownload={this.handleDependencyDownload}/>
|
||||||
}
|
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let downloadDisplay = null;
|
let downloadDisplay = null;
|
||||||
@@ -616,7 +632,8 @@ class App extends Component {
|
|||||||
<Modal>
|
<Modal>
|
||||||
<DownloadProgress display={this.state.DownloadName}
|
<DownloadProgress display={this.state.DownloadName}
|
||||||
progress={this.state.DownloadProgress}/>
|
progress={this.state.DownloadProgress}/>
|
||||||
</Modal>);
|
</Modal>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let upgradeDisplay = null;
|
let upgradeDisplay = null;
|
||||||
@@ -634,7 +651,7 @@ class App extends Component {
|
|||||||
let key = 0;
|
let key = 0;
|
||||||
mainContent.push((
|
mainContent.push((
|
||||||
<div key={'rvd_' + key++}
|
<div key={'rvd_' + key++}
|
||||||
style={{height: '44%'}}>
|
style={{height: '25%'}}>
|
||||||
<ReleaseVersionDisplay disabled={this.state.DownloadActive || this.state.ExtractActive || this.state.MountsBusy}
|
<ReleaseVersionDisplay disabled={this.state.DownloadActive || this.state.ExtractActive || this.state.MountsBusy}
|
||||||
downloadClicked={this.handleReleaseDownload}
|
downloadClicked={this.handleReleaseDownload}
|
||||||
downloadDisabled={!downloadEnabled}
|
downloadDisabled={!downloadEnabled}
|
||||||
@@ -651,21 +668,27 @@ class App extends Component {
|
|||||||
));
|
));
|
||||||
|
|
||||||
if (allowMount) {
|
if (allowMount) {
|
||||||
|
let providerProps = {};
|
||||||
|
for (const provider of Constants.PROVIDER_LIST) {
|
||||||
|
const providerLower = provider.toLowerCase();
|
||||||
|
providerProps[providerLower] = this.state[provider];
|
||||||
|
}
|
||||||
mainContent.push((
|
mainContent.push((
|
||||||
<div key={'md_' + key++}
|
<div key={'md_' + key++}>
|
||||||
style={{height: '56%'}}>
|
<MountItems {...providerProps}
|
||||||
<MountItems allowConfig={allowConfig}
|
allowConfig={allowConfig}
|
||||||
|
allowSiaPrime={allowSiaPrime}
|
||||||
|
noConsoleSupported={noConsoleSupported}
|
||||||
autoMountChanged={this.handleAutoMountChanged}
|
autoMountChanged={this.handleAutoMountChanged}
|
||||||
autoMountProcessed={this.notifyAutoMountProcessed}
|
autoMountProcessed={this.notifyAutoMountProcessed}
|
||||||
|
autoRestartChanged={this.handleAutoRestartChanged}
|
||||||
changed={this.handleMountLocationChanged}
|
changed={this.handleMountLocationChanged}
|
||||||
configClicked={this.handleConfigClicked}
|
configClicked={this.handleConfigClicked}
|
||||||
directory={Constants.DATA_LOCATIONS[this.props.platform]}
|
directory={Constants.DATA_LOCATIONS[this.props.platform]}
|
||||||
errorHandler={this.setErrorState}
|
errorHandler={this.setErrorState}
|
||||||
hyperspace={this.state.Hyperspace}
|
|
||||||
mountsBusy={this.notifyMountsBusy}
|
mountsBusy={this.notifyMountsBusy}
|
||||||
platform={this.props.platform}
|
platform={this.props.platform}
|
||||||
processAutoMount={!this.state.AutoMountProcessed}
|
processAutoMount={!this.state.AutoMountProcessed}
|
||||||
sia={this.state.Sia}
|
|
||||||
version={this.state.InstalledVersion}/>
|
version={this.state.InstalledVersion}/>
|
||||||
</div>
|
</div>
|
||||||
));
|
));
|
||||||
@@ -698,12 +721,12 @@ class App extends Component {
|
|||||||
col={dimensions => dimensions.columns - 6}
|
col={dimensions => dimensions.columns - 6}
|
||||||
colSpan={5}
|
colSpan={5}
|
||||||
row={1}
|
row={1}
|
||||||
rowSpan={remain=>remain - 2}/>
|
rowSpan={remain=>remain - 1}/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Box>
|
</Box>
|
||||||
</div>
|
</div>
|
||||||
<div styleName='Content'>
|
<div styleName='Content'>
|
||||||
<Box dxStyle={{padding: '8px'}}>
|
<Box dxStyle={{padding: '8px 8px 0px 8px'}}>
|
||||||
{mainContent}
|
{mainContent}
|
||||||
</Box>
|
</Box>
|
||||||
</div>
|
</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}>
|
rowSpan={6}>
|
||||||
<img alt=''
|
<img alt=''
|
||||||
height={'16px'}
|
height={'16px'}
|
||||||
onClick={props.configClicked}
|
onClick={props.disabled ? (e)=>{e.preventDefault();} : props.configClicked}
|
||||||
src={configureImage}
|
src={configureImage}
|
||||||
style={{padding: 0, border: 0, margin: 0, cursor: 'pointer'}}
|
style={{padding: 0, border: 0, margin: 0, cursor: 'pointer'}}
|
||||||
width={'16px'}/>
|
width={'16px'}/>
|
||||||
@@ -27,31 +27,47 @@ export default CSSModules((props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let inputColumnSpan;
|
let inputColumnSpan;
|
||||||
let inputControl = null;
|
let inputControls = null;
|
||||||
if (props.platform === 'win32') {
|
if (props.platform === 'win32') {
|
||||||
inputColumnSpan = 20;
|
inputColumnSpan = 20;
|
||||||
inputControl = <DropDown changed={props.changed}
|
inputControls = <DropDown changed={props.changed}
|
||||||
colSpan={inputColumnSpan}
|
colSpan={inputColumnSpan}
|
||||||
disabled={!props.allowMount || props.mounted}
|
disabled={!props.allowMount || props.mounted || props.disabled}
|
||||||
items={props.items}
|
items={props.items}
|
||||||
row={secondRow}
|
row={secondRow}
|
||||||
rowSpan={7}
|
rowSpan={7}
|
||||||
selected={props.items.indexOf(props.location)}/>;
|
selected={props.items.indexOf(props.location)}/>;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
inputColumnSpan = 60;
|
inputColumnSpan = 58;
|
||||||
inputControl = (
|
inputControls = [];
|
||||||
<RootElem colSpan={inputColumnSpan}
|
let key = 0;
|
||||||
|
inputControls.push((
|
||||||
|
<RootElem colSpan={inputColumnSpan - 8}
|
||||||
|
key={'i' + key++}
|
||||||
row={secondRow}
|
row={secondRow}
|
||||||
rowSpan={7}>
|
rowSpan={7}>
|
||||||
<input disabled={!props.allowMount || props.mounted}
|
<input disabled={!props.allowMount || props.mounted || props.disabled}
|
||||||
|
maxLength={4096}
|
||||||
onChange={props.changed}
|
onChange={props.changed}
|
||||||
|
size={4096}
|
||||||
|
styleName={'Input'}
|
||||||
type={'text'}
|
type={'text'}
|
||||||
value={props.location}/>
|
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') :
|
(props.mounted ? 'Unmount' : 'Mount') :
|
||||||
<Loader color={'var(--heading_text_color)'}
|
<Loader color={'var(--heading_text_color)'}
|
||||||
height='19px'
|
height='19px'
|
||||||
@@ -59,26 +75,39 @@ export default CSSModules((props) => {
|
|||||||
width='19px'/>;
|
width='19px'/>;
|
||||||
|
|
||||||
const actionsDisplay = (
|
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}
|
col={inputColumnSpan + 2}
|
||||||
colSpan={20}
|
colSpan={21}
|
||||||
disabled={!props.allowMount}
|
disabled={!props.allowMount || props.disabled}
|
||||||
row={secondRow}
|
row={secondRow}
|
||||||
rowSpan={7}>
|
rowSpan={7}>
|
||||||
{buttonDisplay}
|
{buttonDisplay}
|
||||||
</Button>);
|
</Button>);
|
||||||
|
|
||||||
const autoMountControl = (
|
const autoMountControl = (
|
||||||
<RootElem col={inputColumnSpan + 23}
|
<RootElem col={inputColumnSpan + 24}
|
||||||
colSpan={26}
|
colSpan={28}
|
||||||
row={secondRow}
|
row={secondRow}
|
||||||
rowSpan={7}>
|
rowSpan={7}>
|
||||||
<input checked={props.autoMount}
|
<input checked={props.autoMount}
|
||||||
|
disabled={props.disabled}
|
||||||
onChange={props.autoMountChanged}
|
onChange={props.autoMountChanged}
|
||||||
type='checkbox'/>Auto-mount
|
type='checkbox'/>Auto-mount
|
||||||
</RootElem>
|
</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 (
|
return (
|
||||||
<Grid>
|
<Grid>
|
||||||
{configButton}
|
{configButton}
|
||||||
@@ -87,9 +116,10 @@ export default CSSModules((props) => {
|
|||||||
rowSpan={5}
|
rowSpan={5}
|
||||||
text={props.title}
|
text={props.title}
|
||||||
type={'Heading1'}/>
|
type={'Heading1'}/>
|
||||||
{inputControl}
|
{inputControls}
|
||||||
{actionsDisplay}
|
{actionsDisplay}
|
||||||
{autoMountControl}
|
{autoMountControl}
|
||||||
|
{autoRestartControl}
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
}, styles, {allowMultiple: true});
|
}, styles, {allowMultiple: true});
|
||||||
@@ -8,19 +8,57 @@ import Button from '../UI/Button/Button';
|
|||||||
import UpgradeIcon from '../UpgradeIcon/UpgradeIcon';
|
import UpgradeIcon from '../UpgradeIcon/UpgradeIcon';
|
||||||
|
|
||||||
export default CSSModules((props) => {
|
export default CSSModules((props) => {
|
||||||
let optionsDisplay = null;
|
let optionsDisplay = [];
|
||||||
|
let key = 0;
|
||||||
if (props.releaseExtracting) {
|
if (props.releaseExtracting) {
|
||||||
optionsDisplay = <Text align='center'
|
optionsDisplay.push((
|
||||||
row={13}
|
<Text col={dimensions => (dimensions.columns / 3) * 2}
|
||||||
rowSpan={7}
|
|
||||||
colSpan={'remain'}
|
colSpan={'remain'}
|
||||||
text={'Activating <' + props.installedVersion + '>'}/>
|
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 {
|
} else {
|
||||||
optionsDisplay = <Button clicked={props.downloadClicked}
|
optionsDisplay.push((
|
||||||
|
<Button clicked={props.downloadClicked}
|
||||||
|
col={dimensions => (dimensions.columns / 3) * 2}
|
||||||
colSpan={20}
|
colSpan={20}
|
||||||
|
key={key++}
|
||||||
disabled={props.downloadDisabled}
|
disabled={props.downloadDisabled}
|
||||||
row={13}
|
row={5}
|
||||||
rowSpan={7}>Install</Button>;
|
rowSpan={7}>Install</Button>
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -56,18 +94,6 @@ export default CSSModules((props) => {
|
|||||||
row={5}
|
row={5}
|
||||||
rowSpan={7}
|
rowSpan={7}
|
||||||
selected={props.version}/>
|
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}
|
{optionsDisplay}
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,9 +9,28 @@ exports.DATA_LOCATIONS = {
|
|||||||
};
|
};
|
||||||
exports.UI_RELEASES_URL = 'https://bitbucket.org/blockstorage/repertory-ui/raw/master/releases.json';
|
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 = 'check_installed';
|
||||||
exports.IPC_Check_Installed_Reply = 'check_installed_reply';
|
exports.IPC_Check_Installed_Reply = 'check_installed_reply';
|
||||||
|
|
||||||
|
exports.IPC_Check_Mount_Location = 'check_mount_location';
|
||||||
|
|
||||||
exports.IPC_Delete_File = 'delete_file';
|
exports.IPC_Delete_File = 'delete_file';
|
||||||
|
|
||||||
exports.IPC_Detect_Mounts = 'detect_mounts';
|
exports.IPC_Detect_Mounts = 'detect_mounts';
|
||||||
|
|||||||
@@ -5,29 +5,23 @@ import Button from '../../components/UI/Button/Button';
|
|||||||
import ConfigurationItem from '../../components/ConfigurationItem/ConfigurationItem';
|
import ConfigurationItem from '../../components/ConfigurationItem/ConfigurationItem';
|
||||||
import CSSModules from 'react-css-modules';
|
import CSSModules from 'react-css-modules';
|
||||||
import Modal from '../../components/UI/Modal/Modal';
|
import Modal from '../../components/UI/Modal/Modal';
|
||||||
|
import IPCContainer from '../IPCContainer/IPCContainer';
|
||||||
|
|
||||||
const Constants = require('../../constants');
|
const Constants = require('../../constants');
|
||||||
|
|
||||||
let ipcRenderer = null;
|
class Configuration extends IPCContainer {
|
||||||
if (!process.versions.hasOwnProperty('electron')) {
|
|
||||||
ipcRenderer = ((window && window.require) ? window.require('electron').ipcRenderer : null);
|
|
||||||
}
|
|
||||||
|
|
||||||
class Configuration extends Component {
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
if (ipcRenderer) {
|
this.setRequestHandler(Constants.IPC_Get_Config_Template_Reply, this.onGetConfigTemplateReply);
|
||||||
ipcRenderer.on(Constants.IPC_Get_Config_Template_Reply, this.onGetConfigTemplateReply);
|
this.setRequestHandler(Constants.IPC_Get_Config_Reply, this.onGetConfigReply);
|
||||||
ipcRenderer.on(Constants.IPC_Get_Config_Reply, this.onGetConfigReply);
|
this.setRequestHandler(Constants.IPC_Set_Config_Values_Reply, this.onSetConfigValuesReply);
|
||||||
ipcRenderer.on(Constants.IPC_Set_Config_Values_Reply, this.onSetConfigValuesReply);
|
|
||||||
|
|
||||||
ipcRenderer.send(Constants.IPC_Get_Config_Template, {
|
this.sendRequest(Constants.IPC_Get_Config_Template, {
|
||||||
Directory: this.props.directory,
|
Directory: this.props.directory,
|
||||||
StorageType: this.props.storageType,
|
StorageType: this.props.storageType,
|
||||||
Version: this.props.version,
|
Version: this.props.version,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
ChangedItems: [],
|
ChangedItems: [],
|
||||||
@@ -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) => {
|
createItemList = (config, template) => {
|
||||||
const objectList = [];
|
const objectList = [];
|
||||||
const itemList = Object
|
const itemList = Object
|
||||||
@@ -164,12 +150,13 @@ class Configuration extends Component {
|
|||||||
if (arg.data.Success) {
|
if (arg.data.Success) {
|
||||||
this.setState({
|
this.setState({
|
||||||
Template: arg.data.Template,
|
Template: arg.data.Template,
|
||||||
});
|
}, ()=> {
|
||||||
ipcRenderer.send(Constants.IPC_Get_Config, {
|
this.sendRequest(Constants.IPC_Get_Config, {
|
||||||
Directory: this.props.directory,
|
Directory: this.props.directory,
|
||||||
StorageType: this.props.storageType,
|
StorageType: this.props.storageType,
|
||||||
Version: this.props.version,
|
Version: this.props.version,
|
||||||
});
|
});
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.props.errorHandler(arg.data.Error, () => {
|
this.props.errorHandler(arg.data.Error, () => {
|
||||||
this.props.closed();
|
this.props.closed();
|
||||||
@@ -182,11 +169,9 @@ class Configuration extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
saveAndClose = () => {
|
saveAndClose = () => {
|
||||||
if (ipcRenderer) {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
Saving: true,
|
Saving: true,
|
||||||
});
|
}, ()=> {
|
||||||
|
|
||||||
const changedItems = [];
|
const changedItems = [];
|
||||||
for (const item of this.state.ChangedItems) {
|
for (const item of this.state.ChangedItems) {
|
||||||
changedItems.push({
|
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,
|
Directory: this.props.directory,
|
||||||
Items: changedItems,
|
Items: changedItems,
|
||||||
StorageType: this.props.storageType,
|
StorageType: this.props.storageType,
|
||||||
Version: this.props.version,
|
Version: this.props.version,
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
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 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 CSSModules from 'react-css-modules';
|
||||||
import styles from './MountItems.css';
|
import styles from './MountItems.css';
|
||||||
|
import Modal from '../../components/UI/Modal/Modal';
|
||||||
import MountItem from '../../components/MountItem/MountItem';
|
import MountItem from '../../components/MountItem/MountItem';
|
||||||
|
import IPCContainer from '../IPCContainer/IPCContainer';
|
||||||
|
|
||||||
const Constants = require('../../constants');
|
const Constants = require('../../constants');
|
||||||
|
|
||||||
let ipcRenderer = null;
|
class MountItems extends IPCContainer {
|
||||||
if (!process.versions.hasOwnProperty('electron')) {
|
|
||||||
ipcRenderer = ((window && window.require) ? window.require('electron').ipcRenderer : null);
|
|
||||||
}
|
|
||||||
|
|
||||||
class MountItems extends Component {
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
if (ipcRenderer) {
|
|
||||||
ipcRenderer.on(Constants.IPC_Detect_Mounts_Reply, this.onDetectMountsReply);
|
for (const provider of Constants.PROVIDER_LIST) {
|
||||||
ipcRenderer.on(Constants.IPC_Mount_Drive_Reply, this.onMountDriveReply);
|
this.state[provider] = {
|
||||||
ipcRenderer.on(Constants.IPC_Unmount_Drive_Reply, this.onUnmountDriveReply);
|
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();
|
this.detectMounts();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
retryIntervals = {};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
Hyperspace: {
|
DisplayRetry: false,
|
||||||
AllowMount: false,
|
RetryItems: {},
|
||||||
DriveLetters: [],
|
|
||||||
Mounted: false,
|
|
||||||
PID: -1,
|
|
||||||
},
|
|
||||||
Sia: {
|
|
||||||
AllowMount: false,
|
|
||||||
DriveLetters: [],
|
|
||||||
Mounted: false,
|
|
||||||
PID: -1,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillUnmount = () => {
|
cancelRetryMount = (storageType, stateCallback) => {
|
||||||
if (ipcRenderer) {
|
clearInterval(this.retryIntervals[storageType]);
|
||||||
ipcRenderer.removeListener(Constants.IPC_Detect_Mounts_Reply, this.onDetectMountsReply);
|
delete this.retryIntervals[storageType];
|
||||||
ipcRenderer.removeListener(Constants.IPC_Mount_Drive_Reply, this.onMountDriveReply);
|
|
||||||
ipcRenderer.removeListener(Constants.IPC_Unmount_Drive_Reply, this.onUnmountDriveReply);
|
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 = ()=> {
|
detectMounts = ()=> {
|
||||||
|
if (!this.state.DisplayRetry) {
|
||||||
this.props.mountsBusy(true);
|
this.props.mountsBusy(true);
|
||||||
ipcRenderer.send(Constants.IPC_Detect_Mounts, {
|
this.sendRequest(Constants.IPC_Detect_Mounts, {
|
||||||
Directory: this.props.directory,
|
Directory: this.props.directory,
|
||||||
Version: this.props.version,
|
Version: this.props.version,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
handleMountLocationChanged = (systemType, value) => {
|
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') {
|
if (this.props.platform === 'win32') {
|
||||||
this.props.changed(systemType, this.state[systemType].DriveLetters[value]);
|
this.props.changed(storageType, this.state[storageType].DriveLetters[value]);
|
||||||
}
|
} else {
|
||||||
else {
|
this.props.changed(storageType, value);
|
||||||
this.props.changed(systemType, value);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
handleMountUnMount = (storageType, mount, location, pid) => {
|
handleMountUnMount = (storageType, mount, location) => {
|
||||||
if (ipcRenderer) {
|
if (!location || (location.trim().length === 0)) {
|
||||||
const state = {
|
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,
|
||||||
|
});
|
||||||
|
if (!result.Success) {
|
||||||
|
allowAction = false;
|
||||||
|
this.props.errorHandler(result.Error.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowAction) {
|
||||||
|
const storageState = {
|
||||||
...this.state[storageType],
|
...this.state[storageType],
|
||||||
AllowMount: false,
|
AllowMount: false,
|
||||||
};
|
};
|
||||||
this.setState({
|
|
||||||
[storageType]: state,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.props.mountsBusy(true);
|
this.props.mountsBusy(true);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
[storageType]: storageState,
|
||||||
|
}, () => {
|
||||||
if (mount) {
|
if (mount) {
|
||||||
ipcRenderer.send(Constants.IPC_Mount_Drive, {
|
this.sendRequest(Constants.IPC_Mount_Drive, {
|
||||||
Directory: this.props.directory,
|
Directory: this.props.directory,
|
||||||
Location: location,
|
Location: location,
|
||||||
|
NoConsoleSupported: this.props.noConsoleSupported,
|
||||||
StorageType: storageType,
|
StorageType: storageType,
|
||||||
Version: this.props.version,
|
Version: this.props.version,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
ipcRenderer.send(Constants.IPC_Unmount_Drive, {
|
this.sendRequest(Constants.IPC_Unmount_Drive, {
|
||||||
Directory: this.props.directory,
|
Directory: this.props.directory,
|
||||||
Location: location,
|
Location: location,
|
||||||
PID: pid,
|
|
||||||
StorageType: storageType,
|
StorageType: storageType,
|
||||||
Version: this.props.version,
|
Version: this.props.version,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onDetectMountsReply = (event, arg) => {
|
onDetectMountsReply = (event, arg) => {
|
||||||
if (arg.data.Success) {
|
if (!this.state.DisplayRetry && arg.data.Success) {
|
||||||
const sia = {
|
let state = {};
|
||||||
...this.state.Sia,
|
let mountsBusy = false;
|
||||||
|
for (const provider of Constants.PROVIDER_LIST) {
|
||||||
|
state[provider] = {
|
||||||
|
...this.state[provider],
|
||||||
AllowMount: true,
|
AllowMount: true,
|
||||||
DriveLetters: (arg.data.DriveLetters.Sia),
|
DriveLetters: (arg.data.DriveLetters[provider]),
|
||||||
Mounted: (arg.data.Locations.Sia.length > 0),
|
Mounted: (arg.data.Locations[provider].length > 0),
|
||||||
PID: arg.data.PIDS.Sia,
|
|
||||||
};
|
};
|
||||||
const hs = {
|
mountsBusy = mountsBusy || state[provider].Mounted;
|
||||||
...this.state.Hyperspace,
|
}
|
||||||
AllowMount: true,
|
this.props.mountsBusy(mountsBusy);
|
||||||
DriveLetters: (arg.data.DriveLetters.Hyperspace),
|
|
||||||
Mounted: (arg.data.Locations.Hyperspace.length > 0),
|
this.setState(state, () => {
|
||||||
PID: arg.data.PIDS.Hyperspace,
|
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);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setState({
|
for (const provider of Constants.PROVIDER_LIST) {
|
||||||
Hyperspace: hs,
|
updateMountLocation(arg.data, provider);
|
||||||
Sia: sia,
|
|
||||||
});
|
|
||||||
|
|
||||||
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();
|
this.performAutoMount();
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.props.errorHandler(arg.data.Error);
|
this.props.errorHandler(arg.data.Error);
|
||||||
}
|
}
|
||||||
@@ -143,66 +179,134 @@ class MountItems extends Component {
|
|||||||
onMountDriveReply = (event, arg) => {
|
onMountDriveReply = (event, arg) => {
|
||||||
const state = {
|
const state = {
|
||||||
...this.state[arg.data.StorageType],
|
...this.state[arg.data.StorageType],
|
||||||
PID: arg.data.PID,
|
|
||||||
Mounted: arg.data.Success,
|
Mounted: arg.data.Success,
|
||||||
};
|
};
|
||||||
this.setState({
|
this.setState({
|
||||||
[arg.data.StorageType]: state,
|
[arg.data.StorageType]: state,
|
||||||
});
|
}, ()=> {
|
||||||
|
|
||||||
this.detectMounts();
|
this.detectMounts();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onUnmountDriveReply = (event, arg) => {
|
onUnmountDriveReply = (event, arg) => {
|
||||||
|
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();
|
this.detectMounts();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
performAutoMount = ()=> {
|
performAutoMount = ()=> {
|
||||||
if (this.props.processAutoMount) {
|
if (this.props.processAutoMount) {
|
||||||
this.props.autoMountProcessed();
|
this.props.autoMountProcessed();
|
||||||
if (this.props.hyperspace.AutoMount &&
|
const processAutoMount = (provider) => {
|
||||||
!this.state.Hyperspace.Mounted &&
|
const providerLower = provider.toLowerCase();
|
||||||
(this.props.hyperspace.MountLocation.length > 0)) {
|
if (this.props[providerLower].AutoMount &&
|
||||||
this.handleMountUnMount('Hyperspace', true, this.props.hyperspace.MountLocation);
|
!this.state[provider].Mounted &&
|
||||||
|
(this.props[providerLower].MountLocation.length > 0)) {
|
||||||
|
this.handleMountUnMount(provider, true, this.props[providerLower].MountLocation);
|
||||||
}
|
}
|
||||||
if (this.props.sia.AutoMount &&
|
};
|
||||||
!this.state.Sia.Mounted &&
|
|
||||||
(this.props.sia.MountLocation.length > 0)) {
|
for (const provider of Constants.PROVIDER_LIST) {
|
||||||
this.handleMountUnMount('Sia', true, this.props.sia.MountLocation);
|
processAutoMount(provider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
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 (
|
return (
|
||||||
<div styleName='MountItems'>
|
<div styleName='MountItems'>
|
||||||
<MountItem allowConfig={this.props.allowConfig}
|
{retryDisplay}
|
||||||
allowMount={this.state.Hyperspace.AllowMount}
|
{items}
|
||||||
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'}/>
|
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,13 @@
|
|||||||
--heading_text_color: rgba(161, 190, 219, 0.7);
|
--heading_text_color: rgba(161, 190, 219, 0.7);
|
||||||
--heading_other_text_color: var(--heading_text_color);
|
--heading_other_text_color: var(--heading_text_color);
|
||||||
--text_color_transition: color 0.3s;
|
--text_color_transition: color 0.3s;
|
||||||
|
|
||||||
|
--default_font_size: 4vmin
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
font-family: 'Nunito', sans-serif;
|
font-family: 'Nunito', sans-serif;
|
||||||
font-size: 5vh;
|
font-size: var(--default_font_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
*::-moz-focus-inner {
|
*::-moz-focus-inner {
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ if (!process.versions.hasOwnProperty('electron')) {
|
|||||||
const ipcRenderer = ((window && window.require) ? window.require('electron').ipcRenderer : null);
|
const ipcRenderer = ((window && window.require) ? window.require('electron').ipcRenderer : null);
|
||||||
if (ipcRenderer) {
|
if (ipcRenderer) {
|
||||||
ipcRenderer.on(Constants.IPC_Get_Platform_Reply, (event, arg) => {
|
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'));
|
ReactDOM.render(<App platform={arg.data} version={packageJson.version}/>, document.getElementById('root'));
|
||||||
registerServiceWorker();
|
registerServiceWorker();
|
||||||
});
|
});
|
||||||
|
|||||||