Partial export processing

This commit is contained in:
2020-06-17 16:57:57 -05:00
parent e69dfa9565
commit 53a236000e
9 changed files with 436 additions and 137 deletions

View File

@@ -7,7 +7,7 @@
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.28", "@fortawesome/fontawesome-svg-core": "^1.2.28",
"@fortawesome/free-solid-svg-icons": "^5.13.0", "@fortawesome/free-solid-svg-icons": "^5.13.0",
"@fortawesome/react-fontawesome": "^0.1.9", "@fortawesome/react-fontawesome": "^0.1.11",
"@reduxjs/toolkit": "^1.3.4", "@reduxjs/toolkit": "^1.3.4",
"auto-launch": "^5.0.5", "auto-launch": "^5.0.5",
"axios": "^0.19.2", "axios": "^0.19.2",
@@ -19,6 +19,7 @@
"node-schedule": "^1.3.2", "node-schedule": "^1.3.2",
"randomstring": "^1.1.5", "randomstring": "^1.1.5",
"react": "^16.13.1", "react": "^16.13.1",
"react-checkbox-tree": "^1.6.0",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-loader-spinner": "^3.1.5", "react-loader-spinner": "^3.1.5",
"react-redux": "^7.2.0", "react-redux": "^7.2.0",

View File

@@ -36,6 +36,7 @@ import {createModalConditionally} from './utils';
import SkynetImport from './containers/SkynetImport/SkynetImport'; import SkynetImport from './containers/SkynetImport/SkynetImport';
import {displaySkynetImport} from './redux/actions/skynet_actions'; import {displaySkynetImport} from './redux/actions/skynet_actions';
import ApplicationBusy from './components/ApplicationBusy/ApplicationBusy'; import ApplicationBusy from './components/ApplicationBusy/ApplicationBusy';
import SkynetExport from './containers/SkynetExport/SkynetExport';
const Constants = require('./constants'); const Constants = require('./constants');
const Scheduler = require('node-schedule'); const Scheduler = require('node-schedule');
@@ -162,12 +163,23 @@ class App extends IPCContainer {
!showUpgrade && !showUpgrade &&
this.props.DisplayImport; this.props.DisplayImport;
const configDisplay = createModalConditionally(showConfig, <Configuration version={selectedVersion} const showSkynetExport = !showConfig &&
!showDependencies &&
!this.props.DownloadActive &&
!showNewReleases &&
!this.props.RebootRequired &&
!this.props.DisplaySelectAppPlatform &&
!showUpgrade &&
this.props.DisplayExport;
const configDisplay = createModalConditionally(showConfig, <Configuration
version={selectedVersion}
remoteSupported={remoteSupported}/>); remoteSupported={remoteSupported}/>);
const confirmDisplay = createModalConditionally(this.props.DisplayConfirmYesNo, <YesNo/>); const confirmDisplay = createModalConditionally(this.props.DisplayConfirmYesNo, <YesNo/>);
const dependencyDisplay = createModalConditionally(showDependencies, const dependencyDisplay = createModalConditionally(showDependencies,
<DependencyList/>, false, this.props.InstallActive); <DependencyList/>, false, this.props.InstallActive);
const downloadDisplay = createModalConditionally(this.props.DownloadActive, <DownloadProgress/>, false, true); const downloadDisplay = createModalConditionally(this.props.DownloadActive,
<DownloadProgress/>, false, true);
const errorDisplay = createModalConditionally(this.props.DisplayError, <ErrorDetails/>, true); const errorDisplay = createModalConditionally(this.props.DisplayError, <ErrorDetails/>, true);
const infoDisplay = createModalConditionally(this.props.DisplayInfo, <InfoDetails/>, true); const infoDisplay = createModalConditionally(this.props.DisplayInfo, <InfoDetails/>, true);
const newReleasesDisplay = createModalConditionally(showNewReleases, <NewReleases/>); const newReleasesDisplay = createModalConditionally(showNewReleases, <NewReleases/>);
@@ -175,7 +187,10 @@ class App extends IPCContainer {
const selectAppPlatformDisplay = createModalConditionally(this.props.DisplaySelectAppPlatform, const selectAppPlatformDisplay = createModalConditionally(this.props.DisplaySelectAppPlatform,
<SelectAppPlatform/>); <SelectAppPlatform/>);
const upgradeDisplay = createModalConditionally(showUpgrade, <UpgradeUI/>); const upgradeDisplay = createModalConditionally(showUpgrade, <UpgradeUI/>);
const importDisplay = createModalConditionally(showSkynetImport, <SkynetImport version={selectedVersion}/>); const importDisplay = createModalConditionally(showSkynetImport, <SkynetImport
version={selectedVersion}/>);
const exportDisplay = createModalConditionally(showSkynetExport, <SkynetExport
version={selectedVersion}/>)
const appBusyDisplay = createModalConditionally(this.props.AppBusy, const appBusyDisplay = createModalConditionally(this.props.AppBusy,
<ApplicationBusy/>, false, true, this.props.AppBusyTransparent); <ApplicationBusy/>, false, true, this.props.AppBusyTransparent);
@@ -246,6 +261,7 @@ class App extends IPCContainer {
</div> </div>
</div> </div>
{importDisplay} {importDisplay}
{exportDisplay}
{newReleasesDisplay} {newReleasesDisplay}
{selectAppPlatformDisplay} {selectAppPlatformDisplay}
{dependencyDisplay} {dependencyDisplay}
@@ -274,6 +290,7 @@ const mapStateToProps = state => {
DisplayConfiguration: state.mounts.DisplayConfiguration, DisplayConfiguration: state.mounts.DisplayConfiguration,
DisplayConfirmYesNo: state.common.DisplayConfirmYesNo, DisplayConfirmYesNo: state.common.DisplayConfirmYesNo,
DisplayError: state.error.DisplayError, DisplayError: state.error.DisplayError,
DisplayExport: state.skynet.DisplayExport,
DisplayImport: state.skynet.DisplayImport, DisplayImport: state.skynet.DisplayImport,
DisplayInfo: state.error.DisplayInfo, DisplayInfo: state.error.DisplayInfo,
DisplaySelectAppPlatform: state.common.DisplaySelectAppPlatform, DisplaySelectAppPlatform: state.common.DisplaySelectAppPlatform,

View File

@@ -99,6 +99,9 @@ exports.IPC_Download_File = 'download_file';
exports.IPC_Download_File_Complete = 'download_file_complete'; exports.IPC_Download_File_Complete = 'download_file_complete';
exports.IPC_Download_File_Progress = 'download_file_progress'; exports.IPC_Download_File_Progress = 'download_file_progress';
exports.IPC_Export_Skylinks = 'export_skylinks';
exports.IPC_Export_Skylinks_Reply = 'export_skylinks_reply';
exports.IPC_Extract_Release = 'extract_release'; exports.IPC_Extract_Release = 'extract_release';
exports.IPC_Extract_Release_Complete = 'extract_release_complete'; exports.IPC_Extract_Release_Complete = 'extract_release_complete';
@@ -114,6 +117,9 @@ exports.IPC_Get_Platform_Reply = 'get_platform_reply';
exports.IPC_Get_State = 'get_state'; exports.IPC_Get_State = 'get_state';
exports.IPC_Get_State_Reply = 'get_state_reply'; exports.IPC_Get_State_Reply = 'get_state_reply';
exports.IPC_Grab_Skynet_Tree = 'grab_skynet_tree';
exports.IPC_Grab_Skynet_Tree_Reply = 'grab_skynet_tree_reply';
exports.IPC_Import_Skylinks = 'import_skylinks'; exports.IPC_Import_Skylinks = 'import_skylinks';
exports.IPC_Import_Skylinks_Reply = 'import_skylinks_reply'; exports.IPC_Import_Skylinks_Reply = 'import_skylinks_reply';

View File

@@ -0,0 +1,4 @@
.SkynetExportHeading {
text-align: center;
padding-bottom: var(--default_spacing);
}

View File

@@ -0,0 +1,144 @@
import React from 'react';
import './SkynetExport.css';
import CheckboxTree from 'react-checkbox-tree';
import {connect} from 'react-redux';
import IPCContainer from '../IPCContainer/IPCContainer';
import {notifyApplicationBusy} from '../../redux/actions/common_actions';
import {notifyError} from '../../redux/actions/error_actions';
import Box from '../../components/UI/Box/Box';
import {displaySkynetExport} from '../../redux/actions/skynet_actions';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {
faCheckSquare, faChevronDown,
faChevronRight, faFile, faFolder, faFolderOpen,
faHSquare, faMinusSquare, faPlusSquare,
faSquare, faSquareFull, faSquareRootAlt
} from '@fortawesome/free-solid-svg-icons';
const Constants = require('../../constants');
const mapStateToProps = state => {
return {
AppBusy: state.common.AppBusy,
};
};
const mapDispatchToProps = dispatch => {
return {
displaySkynetExport: display => dispatch(displaySkynetExport(display)),
notifyApplicationBusy: busy => dispatch(notifyApplicationBusy(busy, true)),
notifyError: msg => dispatch(notifyError(msg)),
}
};
export default connect(mapStateToProps, mapDispatchToProps)(class extends IPCContainer {
state = {
checked: [],
expanded: [],
nodes: [],
}
componentDidMount() {
this.setRequestHandler(Constants.IPC_Grab_Skynet_Tree_Reply, this.onGrabSkynetTreeReply);
this.setRequestHandler(Constants.IPC_Export_Skylinks_Reply, this.onExportSkylinksReply);
this.sendRequest(Constants.IPC_Grab_Skynet_Tree, {Version: this.props.version});
}
componentWillUnmount() {
super.componentWillUnmount();
}
createNodes = items => {
/*
{
name: '',
path: '',
directory: true/false,
children: [],
}
*/
const ret = [];
for (const item of items) {
const treeItem = {
label: item.name,
path: item.path,
value: item.name === '/' ? 0 : JSON.stringify(item),
};
if (item.directory) {
treeItem.children = this.createNodes(item.children);
}
ret.push(treeItem);
}
return ret;
}
onGrabSkynetTreeReply = (_, arg) => {
if (arg.data.Success) {
this.setState({
checked: [],
expanded: [0],
nodes: this.createNodes(arg.data.Result),
});
} else {
this.setState({
checked: [],
expanded: [],
nodes: [],
}, () => {
this.props.notifyError(arg.data.Error);
});
}
}
onExportSkylinksReply = (_, arg) => {
}
render() {
console.log(this.state.expanded);
return this.props.AppBusy ? (<div/>) : (
<Box dxDark dxStyle={{
height: '90vh',
padding: 'var(--default_spacing)',
width: 'calc(100vw - (var(--default_spacing) * 4)'
}}>
<div
style={{
float: 'right',
margin: 0,
padding: 0,
marginTop: '-4px',
boxSizing: 'border-box',
display: 'block'
}}>
<a href={'#'}
onClick={() => this.props.displaySkynetExport(false)}
style={{cursor: 'pointer'}}>X</a>
</div>
<h1 className={'SkynetExportHeading'}>{'Export Files'}</h1>
<CheckboxTree checked={this.state.checked}
expanded={this.state.expanded}
icons={{
check: <FontAwesomeIcon icon={faCheckSquare}/>,
uncheck: <FontAwesomeIcon icon={faSquare}/>,
halfCheck: <FontAwesomeIcon icon={faHSquare}/>,
expandClose: <FontAwesomeIcon icon={faChevronRight}/>,
expandOpen: <FontAwesomeIcon icon={faChevronDown}/>,
expandAll: <FontAwesomeIcon icon={faPlusSquare}/>,
collapseAll: <FontAwesomeIcon icon={faMinusSquare}/>,
parentClose: <FontAwesomeIcon icon={faFolder}
color={'var(--heading_text_color)'}/>,
parentOpen: <FontAwesomeIcon icon={faFolderOpen}
color={'var(--heading_text_color)'}/>,
leaf: <FontAwesomeIcon icon={faFile} color={'var(--heading_text_color)'}/>
}}
nodes={this.state.nodes}
onCheck={checked => this.setState({checked})}
onExpand={expanded => this.setState({expanded})}/>
</Box>
);
}
});

View File

@@ -166,12 +166,20 @@ export default connect(mapStateToProps, mapDispatchToProps)(class extends IPCCon
width: 'calc(100vw - (var(--default_spacing) * 4)' width: 'calc(100vw - (var(--default_spacing) * 4)'
}}> }}>
<div <div
style={{float: 'right', margin: 0, padding: 0, marginTop: '-4px', boxSizing: 'border-box', display: 'block'}}> style={{
float: 'right',
margin: 0,
padding: 0,
marginTop: '-4px',
boxSizing: 'border-box',
display: 'block'
}}>
<a href={'#'} <a href={'#'}
onClick={() => this.props.displaySkynetImport(false)} onClick={() => this.props.displaySkynetImport(false)}
style={{cursor: 'pointer'}}>X</a> style={{cursor: 'pointer'}}>X</a>
</div> </div>
<h1 className={'SkynetImportHeading'}>{this.state.second_stage ? 'Verify Imports' : 'Import List'}</h1> <h1
className={'SkynetImportHeading'}>{this.state.second_stage ? 'Verify Imports' : 'Import List'}</h1>
{ {
this.state.second_stage ? ( this.state.second_stage ? (
<ImportList imports_array={this.state.imports_array}/> <ImportList imports_array={this.state.imports_array}/>
@@ -186,7 +194,8 @@ export default connect(mapStateToProps, mapDispatchToProps)(class extends IPCCon
) )
} }
<div className={'SkynetImportButtons'}> <div className={'SkynetImportButtons'}>
<Button buttonStyles={{height: 'auto', marginTop: 'var(--default_spacing)', width: 'auto'}} <Button
buttonStyles={{height: 'auto', marginTop: 'var(--default_spacing)', width: 'auto'}}
clicked={this.displaySyntax}>Import Syntax...</Button> clicked={this.displaySyntax}>Import Syntax...</Button>
<div className={'SkynetActionButtons'}> <div className={'SkynetActionButtons'}>
{ {
@@ -206,7 +215,8 @@ export default connect(mapStateToProps, mapDispatchToProps)(class extends IPCCon
marginLeft: 'var(--default_spacing)', marginLeft: 'var(--default_spacing)',
marginTop: 'var(--default_spacing)', marginTop: 'var(--default_spacing)',
width: 'auto' width: 'auto'
}} clicked={this.handleNavigation}>{this.state.second_stage ? 'Import' : 'Next'}</Button> }}
clicked={this.handleNavigation}>{this.state.second_stage ? 'Import' : 'Next'}</Button>
</div> </div>
</div> </div>
</Box> </Box>

View File

@@ -9,7 +9,6 @@ const Constants = require('./constants');
const RandomString = require('randomstring'); const RandomString = require('randomstring');
let vcRuntimeExists; let vcRuntimeExists;
const _vcRuntimeExists = () => { const _vcRuntimeExists = () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (os.platform() !== 'win32') { if (os.platform() !== 'win32') {
@@ -19,7 +18,7 @@ const _vcRuntimeExists = () => {
resolve(true); resolve(true);
} else { } else {
const cmd = path.join(process.env.windir, 'system32', 'reg.exe'); const cmd = path.join(process.env.windir, 'system32', 'reg.exe');
const args = ["QUERY", "HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"]; const args = ['QUERY', 'HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall'];
_execProcessGetOutput(cmd, null, args) _execProcessGetOutput(cmd, null, args)
.then(lines => { .then(lines => {
const parseLine = index => { const parseLine = index => {
@@ -62,6 +61,92 @@ const _vcRuntimeExists = () => {
}); });
}; };
// https://stackoverflow.com/questions/19531453/transform-file-directory-structure-into-tree-in-javascript
const _createTreeNodes = fileList => {
let tree = {}
const directorySort = (a, b) => {
return !!a.directory === !!b.directory ? a.name.localeCompare(b.name) : a.directory ? -1 : 1;
};
const addNode = obj => {
const fullPath = path.join(obj.directory, obj.filename).replace(/\\/g, '/');
const pathParts = fullPath.replace(/^\/|\/$/g, '').split('/');
let ptr = tree;
for (let i = 0; i < pathParts.length; i++) {
const node = {
directory: true,
name: pathParts[i],
};
if (i === pathParts.length - 1) {
node.directory = false;
node.path = fullPath;
}
ptr[pathParts[i]] = ptr[pathParts[i]] || node;
ptr[pathParts[i]].children = ptr[pathParts[i]].children || {};
ptr = ptr[pathParts[i]].children;
}
}
const objectToArray = node => {
Object.keys(node || {}).map((k) => {
if (node[k].children) {
objectToArray(node[k])
}
})
if (node.children) {
node.children = Object.values(node.children);
node.children.forEach(objectToArray)
node.children = node.children.sort(directorySort);
}
}
fileList.map(addNode);
objectToArray(tree);
return Object.values(tree).sort(directorySort);
};
const _exportAllSkylinks = version => {
return new Promise((resolve, reject) => {
const repertoryExec = _getRepertoryExec(version);
const processOptions = {
cwd: repertoryExec.working,
detached: true,
shell: false,
windowsHide: true,
};
const args = _getDefaultRepertoryArgs('Skynet');
args.push('-ea');
let result = '';
const process = new spawn(repertoryExec.cmd, args, processOptions);
process.on('error', (err) => {
reject(err);
});
process.stdout.on('data', (d) => {
result += d;
});
process.stderr.on('data', (d) => {
result += d;
});
process.on('exit', code => {
if (code === 0) {
result = result.substr(result.indexOf('{'));
resolve(JSON.parse(result));
} else {
reject(new Error('Failed to import: ' + code + ':' + result));
}
});
process.unref();
});
};
const _executeProcess = (command, working, args = []) => { const _executeProcess = (command, working, args = []) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let processOptions = { let processOptions = {
@@ -510,6 +595,8 @@ module.exports.executeMount = (version, provider, remote, location, exitCallback
}); });
}; };
module.exports.exportAllSkylinks = _exportAllSkylinks;
module.exports.getConfig = (version, provider, remote) => { module.exports.getConfig = (version, provider, remote) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const repertoryExec = _getRepertoryExec(version); const repertoryExec = _getRepertoryExec(version);
@@ -683,6 +770,22 @@ module.exports.getMissingDependencies = dependencies => {
}); });
}; };
module.exports.grabSkynetFileTree = version => {
return new Promise((resolve, reject) => {
_exportAllSkylinks(version)
.then(results => {
resolve([{
name: '/',
directory: true,
children: _createTreeNodes(results.success),
}]);
})
.catch(e => {
reject(e);
});
});
};
module.exports.importSkylinks = (version, jsonArray) => { module.exports.importSkylinks = (version, jsonArray) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const repertoryExec = _getRepertoryExec(version); const repertoryExec = _getRepertoryExec(version);
@@ -761,7 +864,7 @@ module.exports.performWindowsUninstall = names => {
reject('Windows OS is not being used'); reject('Windows OS is not being used');
} else { } else {
const cmd = path.join(process.env.windir, 'system32', 'reg.exe'); const cmd = path.join(process.env.windir, 'system32', 'reg.exe');
const args = ["QUERY", "HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"]; const args = ['QUERY', 'HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall'];
_execProcessGetOutput(cmd, null, args) _execProcessGetOutput(cmd, null, args)
.then(lines => { .then(lines => {
const parseLine = index => { const parseLine = index => {

View File

@@ -9,6 +9,7 @@ import {Provider} from 'react-redux';
import {setActiveRelease} from './redux/actions/release_version_actions'; import {setActiveRelease} from './redux/actions/release_version_actions';
import {setProviderState} from './redux/actions/mount_actions'; import {setProviderState} from './redux/actions/mount_actions';
import * as serviceWorker from './serviceWorker'; import * as serviceWorker from './serviceWorker';
import 'react-checkbox-tree/lib/react-checkbox-tree.css';
const Constants = require('./constants'); const Constants = require('./constants');

View File

@@ -14,6 +14,19 @@ const addListeners = (ipcMain, {standardIPCReply}) => {
standardIPCReply(event, Constants.IPC_Import_Skylinks_Reply, {}, error); standardIPCReply(event, Constants.IPC_Import_Skylinks_Reply, {}, error);
}); });
}); });
ipcMain.on(Constants.IPC_Grab_Skynet_Tree, (event, data) => {
helpers
.grabSkynetFileTree(data.Version)
.then(result => {
standardIPCReply(event, Constants.IPC_Grab_Skynet_Tree_Reply, {
Result: result,
});
})
.catch(error => {
standardIPCReply(event, Constants.IPC_Grab_Skynet_Tree_Reply, {}, error);
});
});
}; };
module.exports = { module.exports = {