Documented manager code

This commit is contained in:
Pax1601 2024-01-03 15:08:06 +01:00
parent 7bf6c1bb23
commit a0634a7f99
11 changed files with 263 additions and 82 deletions

View File

@ -86,7 +86,7 @@
</div>
<% if (!simplified) { %>
<div class="button cancel">
Cancel installation
<%= install? "Cancel installation": "Cancel editing" %>
</div>
<% } %>
</div>

View File

@ -43,7 +43,7 @@
</div>
<% if (!simplified) { %>
<div class="button cancel">
Cancel installation
<%= install? "Cancel installation": "Cancel editing" %>
</div>
<% } %>
</div>

View File

@ -1,6 +1,9 @@
const ManagerPage = require("./managerpage");
const ejs = require('ejs')
/** Connections page, allows the user to set the ports and address for each Olympus instance
*
*/
class ConnectionsPage extends ManagerPage {
onBackClicked;
onNextClicked;
@ -35,6 +38,8 @@ class ConnectionsPage extends ManagerPage {
ejs.renderFile("./ejs/connections.ejs", this.options, {}, (err, str) => {
if (!err) {
this.render(str);
/* Call the port setters to check if the ports are free */
this.setClientPort(this.instance.clientPort);
this.setBackendPort(this.instance.backendPort);
} else {
@ -45,6 +50,9 @@ class ConnectionsPage extends ManagerPage {
super.show();
}
/** Asynchronously check if the client port is free and if it is, set the new value
*
*/
async setClientPort(newPort) {
const success = await this.instance.setClientPort(newPort);
var successEls = this.element.querySelector(".client-port").querySelectorAll(".success");
@ -57,6 +65,9 @@ class ConnectionsPage extends ManagerPage {
}
}
/** Asynchronously check if the backend port is free and if it is, set the new value
*
*/
async setBackendPort(newPort) {
const success = await this.instance.setBackendPort(newPort);
var successEls = this.element.querySelector(".backend-port").querySelectorAll(".success");

View File

@ -8,12 +8,15 @@ const { checkPort, fetchWithTimeout } = require('./net')
const dircompare = require('dir-compare');
const { spawn } = require('child_process');
const find = require('find-process');
const { deleteMod, uninstallInstance } = require('./filesystem')
const { uninstallInstance } = require('./filesystem')
const { showErrorPopup, showConfirmPopup } = require('./popup')
class DCSInstance {
static instances = null;
/** Static asynchronous method to retrieve all DCS instances. Only runs at startup
*
*/
static async getInstances() {
if (this.instances === null) {
this.instances = await this.findInstances();
@ -21,18 +24,25 @@ class DCSInstance {
return this.instances;
}
/** Static asynchronous method to find all existing DCS instances
*
*/
static async findInstances() {
let promise = new Promise((res, rej) => {
/* Get the Saved Games folder from the registry */
regedit.list(shellFoldersKey, function (err, result) {
if (err) {
rej(err);
}
else {
/* Check that the registry read was successfull */
if (result[shellFoldersKey] !== undefined && result[shellFoldersKey]["exists"] && result[shellFoldersKey]['values'][saveGamesKey] !== undefined && result[shellFoldersKey]['values'][saveGamesKey]['value'] !== undefined) {
/* Read all the folders in Saved Games */
const searchpath = result[shellFoldersKey]['values'][saveGamesKey]['value'];
const folders = fs.readdirSync(searchpath);
var instances = [];
/* A DCS Instance is created if either the appsettings.lua or serversettings.lua file is detected */
folders.forEach((folder) => {
if (fs.existsSync(path.join(searchpath, folder, "Config", "appsettings.lua")) ||
fs.existsSync(path.join(searchpath, folder, "Config", "serversettings.lua"))) {
@ -73,8 +83,10 @@ class DCSInstance {
this.folder = folder;
this.name = path.basename(folder);
/* Check if the olympus.json file is detected. If true, Olympus is considered to be installed */
if (fs.existsSync(path.join(folder, "Config", "olympus.json"))) {
try {
/* Read the olympus.json */
var config = JSON.parse(fs.readFileSync(path.join(folder, "Config", "olympus.json")));
this.clientPort = config["client"]["port"];
this.backendPort = config["server"]["port"];
@ -84,6 +96,8 @@ class DCSInstance {
console.error(err)
}
/* Compare the contents of the installed Olympus instance and the one in the root folder. Exclude the databases folder, which users can edit.
If there is any difference, the instance is flagged as either corrupted or outdated */
this.installed = true;
const options = {
compareContent: true,
@ -107,6 +121,7 @@ class DCSInstance {
}
}
/* Periodically "ping" Olympus to check if either the client or the backend are active */
window.setInterval(async () => {
await this.getData();
@ -136,6 +151,9 @@ class DCSInstance {
}, 1000);
}
/** Asynchronously check if the client port is free and if it is, set the new value
*
*/
async setClientPort(newPort) {
if (await this.checkClientPort(newPort)) {
console.log(`Instance ${this.folder} client port set to ${newPort}`)
@ -145,6 +163,9 @@ class DCSInstance {
return false;
}
/** Asynchronously check if the client port is free and if it is, set the new value
*
*/
async setBackendPort(newPort) {
if (await this.checkBackendPort(newPort)) {
console.log(`Instance ${this.folder} client port set to ${newPort}`)
@ -154,22 +175,37 @@ class DCSInstance {
return false;
}
/** Set backend address
*
*/
setBackendAddress(newAddress) {
this.backendAddress = newAddress;
}
/** Set Game Master password
*
*/
setGameMasterPassword(newPassword) {
this.gameMasterPassword = newPassword;
}
/** Set Blue Commander password
*
*/
setBlueCommanderPassword(newPassword) {
this.blueCommanderPassword = newPassword;
}
/** Set Red Commander password
*
*/
setRedCommanderPassword(newPassword) {
this.redCommanderPassword = newPassword;
}
/** Check if the client port is free
*
*/
async checkClientPort(port) {
var promise = new Promise((res, rej) => {
checkPort(port, async (portFree) => {
@ -198,6 +234,9 @@ class DCSInstance {
return promise;
}
/** Check if the backend port is free
*
*/
async checkBackendPort(port) {
var promise = new Promise((res, rej) => {
checkPort(port, async (portFree) => {
@ -225,6 +264,9 @@ class DCSInstance {
return promise;
}
/** Asynchronously interrogate the webserver and the backend to check if they are active and to retrieve data.
*
*/
async getData() {
if (this.installed && !this.error) {
fetchWithTimeout(`http://localhost:${this.clientPort}`, { timeout: 250 })
@ -261,6 +303,9 @@ class DCSInstance {
}
}
/** Start the Olympus server associated with this instance
*
*/
startServer() {
console.log(`Starting server for instance at ${this.folder}`)
const out = fs.openSync(`./${this.name}.log`, 'a');
@ -274,6 +319,9 @@ class DCSInstance {
sub.unref();
}
/** Start the Olympus client associated with this instance
*
*/
startClient() {
console.log(`Starting client for instance at ${this.folder}`)
const out = fs.openSync(`./${this.name}.log`, 'a');
@ -287,6 +335,7 @@ class DCSInstance {
sub.unref();
}
/* Stop any node process running on the server port. This will stop either the server or the client depending on what is running */
stop() {
find('port', this.clientPort)
.then((list) => {
@ -312,6 +361,7 @@ class DCSInstance {
})
}
/* Uninstall this instance */
uninstall() {
showConfirmPopup("Are you sure you want to completely remove this Olympus installation?", () =>
uninstallInstance(this.folder, this.name).then(

View File

@ -5,6 +5,9 @@ const path = require('path');
const { showWaitPopup } = require('./popup');
const homeDir = require('os').homedir();
/** Conveniency function to asynchronously delete a single file, with error catching
*
*/
async function deleteFile(filePath) {
console.log(`Deleting ${filePath}`);
var promise = new Promise((res, rej) => {
@ -27,6 +30,9 @@ async function deleteFile(filePath) {
return promise;
}
/** Given a list of Olympus instances, it fixes/updates them by deleting the existing installation and the copying over the clean files
*
*/
async function fixInstances(instances) {
var promise = new Promise((res, rej) => {
var instancePromises = instances.map((instance) => {
@ -46,6 +52,9 @@ async function fixInstances(instances) {
return promise;
}
/** Uninstalls a specific instance given its folder
*
*/
async function uninstallInstance(folder, name) {
console.log(`Uninstalling Olympus from ${folder}`)
showWaitPopup("Please wait while the Olympus installation is being uninstalled.")
@ -59,6 +68,9 @@ async function uninstallInstance(folder, name) {
return promise;
}
/** Installs the Hooks script
*
*/
async function installHooks(folder) {
console.log(`Installing hooks in ${folder}`)
var promise = new Promise((res, rej) => {
@ -76,6 +88,10 @@ async function installHooks(folder) {
return promise;
}
/** Installs the Mod folder
*
*/
// TODO: add option to not copy over databases
async function installMod(folder) {
console.log(`Installing mod in ${folder}`)
var promise = new Promise((res, rej) => {
@ -93,6 +109,9 @@ async function installMod(folder) {
return promise;
}
/** Installs the olympus.json file
*
*/
async function installJSON(folder) {
console.log(`Installing config in ${folder}`)
var promise = new Promise((res, rej) => {
@ -110,38 +129,9 @@ async function installJSON(folder) {
return promise;
}
async function applyConfiguration(folder, instance) {
console.log(`Applying configuration to Olympus in ${folder}`);
var promise = new Promise((res, rej) => {
if (fs.existsSync(path.join(folder, "Config", "olympus.json"))) {
var config = JSON.parse(fs.readFileSync(path.join(folder, "Config", "olympus.json")));
config["client"]["port"] = instance.clientPort;
config["server"]["port"] = instance.backendPort;
config["server"]["address"] = instance.backendAddress;
config["authentication"]["gameMasterPassword"] = sha256(instance.gameMasterPassword);
config["authentication"]["blueCommanderPassword"] = sha256(instance.blueCommanderPassword);
config["authentication"]["redCommanderPassword"] = sha256(instance.redCommanderPassword);
fs.writeFile(path.join(folder, "Config", "olympus.json"), JSON.stringify(config, null, 4), (err) => {
if (err) {
console.log(`Error applying config in ${folder}: ${err}`)
rej(err);
}
else {
console.log(`Config succesfully applied in ${folder}`)
res(true);
}
});
} else {
rej("File does not exist")
}
res(true);
});
return promise;
}
/** Creates shortcuts both in the DCS Saved Games folder and on the desktop
*
*/
async function installShortCuts(folder, name) {
console.log(`Installing shortcuts for Olympus in ${folder}`);
var promise = new Promise((res, rej) => {
@ -196,11 +186,52 @@ async function installShortCuts(folder, name) {
return promise;
}
/** Writes the configuration of an instance to the olympus.json file
*
*/
async function applyConfiguration(folder, instance) {
console.log(`Applying configuration to Olympus in ${folder}`);
var promise = new Promise((res, rej) => {
if (fs.existsSync(path.join(folder, "Config", "olympus.json"))) {
var config = JSON.parse(fs.readFileSync(path.join(folder, "Config", "olympus.json")));
config["client"]["port"] = instance.clientPort;
config["server"]["port"] = instance.backendPort;
config["server"]["address"] = instance.backendAddress;
config["authentication"]["gameMasterPassword"] = sha256(instance.gameMasterPassword);
config["authentication"]["blueCommanderPassword"] = sha256(instance.blueCommanderPassword);
config["authentication"]["redCommanderPassword"] = sha256(instance.redCommanderPassword);
fs.writeFile(path.join(folder, "Config", "olympus.json"), JSON.stringify(config, null, 4), (err) => {
if (err) {
console.log(`Error applying config in ${folder}: ${err}`)
rej(err);
}
else {
console.log(`Config succesfully applied in ${folder}`)
res(true);
}
});
} else {
rej("File does not exist")
}
res(true);
});
return promise;
}
/** Deletes the Hooks script
*
*/
async function deleteHooks(folder) {
console.log(`Deleting hooks from ${folder}`);
return deleteFile(path.join(folder, "Scripts", "Hooks", "OlympusHook.lua"));
}
/** Deletes the Mod folder
*
*/
async function deleteMod(folder) {
console.log(`Deleting mod from ${folder}`);
var promise = new Promise((res, rej) => {
@ -222,11 +253,17 @@ async function deleteMod(folder) {
return promise;
}
/** Deletes the olympus.json configuration file
*
*/
async function deleteJSON(folder) {
console.log(`Deleting JSON from ${folder}`);
return deleteFile(path.join(folder, "Config", "olympus.json"));
}
/** Deletes the shortcuts
*
*/
async function deleteShortCuts(folder, name) {
console.log(`Deleting ShortCuts from ${folder}`);
var promise = new Promise((res, rej) => {

View File

@ -17,26 +17,31 @@ class Manager {
}
async start() {
/* Get the list of DCS instances */
var instances = await DCSInstance.getInstances();
/* If there is only 1 DCS Instance and Olympus is not installed in it, go straight to the installation page (since there is nothing else to do) */
this.simplified = instances.length === 1 && !instances[0].installed;
document.getElementById("loader").classList.add("hide");
/* Check if there are corrupted or outdate instances */
if (instances.some((instance) => {
return instance.installed && instance.error;
})) {
/* Ask the user for confirmation */
showErrorPopup("One or more Olympus instances are corrupted or need updating. Press Close to fix this.", async () => {
showWaitPopup("Please wait while your instances are being fixed.")
fixInstances(instances.filter((instance) => {
return instance.installed && instance.error;
})).then(
() => { location.reload() },
() => { showErrorPopup("An error occurred while trying to fix you installations. Please reinstall Olympus manually"); }
() => { showErrorPopup("An error occurred while trying to fix your installations. Please reinstall Olympus manually."); }
)
})
}
/* Check which buttons should be enabled */
const installEnabled = instances.some((instance) => { return !instance.installed; });
const updateEnabled = instances.some((instance) => { return instance.installed; });
const manageEnabled = instances.some((instance) => { return instance.installed; });
@ -49,10 +54,12 @@ class Manager {
updateEnabled: updateEnabled,
manageEnabled: manageEnabled
}
/* When the install button is clicked go the installation page */
menuPage.onInstallClicked = (e) => {
menuPage.hide();
installationsPage.show();
}
/* When the update button is clicked go to the instances page in "update mode" (i.e. manage = false) */
menuPage.onUpdateClicked = (e) => {
menuPage.hide();
instancesPage.options = {
@ -61,6 +68,7 @@ class Manager {
}
instancesPage.show();
}
/* When the manage button is clicked go to the instances page in "manage mode" (i.e. manage = true) */
menuPage.onManageClicked = (e) => {
menuPage.hide();
instancesPage.options = {
@ -77,33 +85,37 @@ class Manager {
instances: instances
}
installationsPage.setSelectedInstance = (activeInstance) => {
connectionsPage.options = {
...connectionsPage.options,
/* Set the active options for the pages */
const options = {
instance: activeInstance,
simplified: this.simplified,
install: true
}
connectionsPage.options = {
...connectionsPage.options,
...options
}
passwordsPage.options = {
...passwordsPage.options,
instance: activeInstance,
simplified: this.simplified,
install: true
...options
}
resultPage.options = {
...resultPage.options,
instance: activeInstance,
simplified: this.simplified,
install: true
...options
}
/* Show the connections page */
installationsPage.hide();
connectionsPage.show();
connectionsPage.onBackClicked = (e) => {
/* Show the installation page */
connectionsPage.hide();
installationsPage.show();
}
}
installationsPage.onCancelClicked = (e) => {
/* Go back to the main menu */
installationsPage.hide();
menuPage.show();
}
@ -115,33 +127,37 @@ class Manager {
instances: instances.filter((instance) => { return instance.installed; })
}
instancesPage.setSelectedInstance = (activeInstance) => {
connectionsPage.options = {
...connectionsPage.options,
/* Set the active options for the pages */
const options = {
instance: activeInstance,
simplified: this.simplified,
install: false
}
connectionsPage.options = {
...connectionsPage.options,
...options
}
passwordsPage.options = {
...passwordsPage.options,
instance: activeInstance,
simplified: this.simplified,
install: false
...options
}
resultPage.options = {
...resultPage.options,
instance: activeInstance,
simplified: this.simplified,
install: false
...options
}
/* Show the connections page */
instancesPage.hide();
connectionsPage.show();
connectionsPage.onBackClicked = (e) => {
/* Show the instances page */
connectionsPage.hide();
instancesPage.show();
}
}
instancesPage.onCancelClicked = (e) => {
/* Go back to the main menu */
instancesPage.hide();
menuPage.show();
}
@ -151,6 +167,7 @@ class Manager {
connectionsPage.onNextClicked = async (e) => {
let activeInstance = connectionsPage.options.instance;
if (activeInstance) {
/* Check that the selected ports are free before proceeding */
if (await activeInstance.checkClientPort(activeInstance.clientPort) && await activeInstance.checkBackendPort(activeInstance.backendPort)) {
connectionsPage.hide();
passwordsPage.show();
@ -161,8 +178,8 @@ class Manager {
showErrorPopup("An error has occurred, please restart the Olympus Manager.")
}
}
connectionsPage.onCancelClicked = (e) => {
/* Go back to the main menu */
connectionsPage.hide();
menuPage.show();
}
@ -170,6 +187,7 @@ class Manager {
/* Passwords */
var passwordsPage = new PasswordsPage();
passwordsPage.onBackClicked = (e) => {
/* Go back to the connections page */
let activeInstance = connectionsPage.options.instance;
if (activeInstance) {
passwordsPage.hide();
@ -181,6 +199,7 @@ class Manager {
passwordsPage.onNextClicked = (e) => {
let activeInstance = connectionsPage.options.instance;
if (activeInstance) {
/* Check that all the passwords have been set */
if (activeInstance.gameMasterPassword === "" || activeInstance.blueCommanderPassword === "" || activeInstance.redCommanderPassword === "") {
showErrorPopup("Please fill all the password inputs.")
}
@ -197,6 +216,7 @@ class Manager {
}
passwordsPage.onCancelClicked = (e) => {
/* Go back to the main menu */
passwordsPage.hide();
menuPage.show();
}
@ -204,14 +224,17 @@ class Manager {
/* Result */
var resultPage = new ResultPage();
resultPage.onBackClicked = (e) => {
/* Reload the page to apply changes */
resultPage.hide();
location.reload();
}
resultPage.onCancelClicked = (e) => {
/* Reload the page to apply changes */
resultPage.hide();
location.reload();
}
/* Create all the HTML pages */
document.body.appendChild(menuPage.getElement());
document.body.appendChild(installationsPage.getElement());
document.body.appendChild(instancesPage.getElement());
@ -219,28 +242,30 @@ class Manager {
document.body.appendChild(passwordsPage.getElement());
document.body.appendChild(resultPage.getElement());
/* In simplified mode we directly show the connections page */
if (this.simplified) {
connectionsPage.options = {
...connectionsPage.options,
const options = {
instance: instances[0],
simplified: this.simplified,
install: true
}
connectionsPage.options = {
...connectionsPage.options,
...options
}
passwordsPage.options = {
...passwordsPage.options,
instance: instances[0],
simplified: this.simplified,
install: true
...options
}
resultPage.options = {
...resultPage.options,
instance: instances[0],
simplified: this.simplified,
install: true
...options
}
/* Show the connections page directly */
instancesPage.hide();
connectionsPage.show();
} else {
/* Show the main menu */
menuPage.show();
}
}

View File

@ -1,5 +1,8 @@
const portfinder = require('portfinder')
/** Checks if a port is already in use
*
*/
function checkPort(port, callback) {
portfinder.getPort({ port: port, stopPort: port }, (err, res) => {
if (err !== null) {
@ -11,6 +14,9 @@ function checkPort(port, callback) {
});
}
/** Performs a fetch request, with a configurable timeout
*
*/
async function fetchWithTimeout(resource, options = {}) {
const { timeout = 8000 } = options;

View File

@ -1,3 +1,5 @@
// TODO: we can probably refactor this to be a bit cleaner
function showErrorPopup(message, onCloseCallback) {
document.getElementById("grayout").classList.remove("hide");
document.getElementById("popup").classList.remove("hide");

View File

@ -13,6 +13,7 @@ const { Octokit } = require('octokit');
const VERSION = "{{OLYMPUS_VERSION_NUMBER}}";
function checkVersion() {
/* Check if we are running the latest version */
const request = new Request("https://raw.githubusercontent.com/Pax1601/DCSOlympus/main/version.json");
fetch(request).then((response) => {
@ -22,12 +23,15 @@ function checkVersion() {
throw new Error("Error connecting to Github to retrieve latest version");
}
}).then((res) => {
/* If we are running a development version of the script (not from a compiled package), skip version checking */
if (VERSION.includes("OLYMPUS_VERSION_NUMBER")) {
console.log("Development build detected, skipping version checks...")
} else {
/* Check if there is a newer version available */
var reg1 = res["version"].match(/\d+/g).map((str) => { return Number(str) });
var reg2 = VERSION.match(/\d+/g).map((str) => { return Number(str) });
/* If a newer version is available update Olympus in Release mode */
if (reg1[0] > reg2[0] || (reg1[0] == reg2[0] && reg1[1] > reg2[1]) || (reg1[0] == reg2[0] && reg1[1] == reg2[1] && reg1[2] > reg2[2])) {
console.log(`New version available: ${res["version"]}`);
showConfirmPopup(`You are currently running DCS Olympus ${VERSION}, but ${res["version"]} is available. Do you want to update DCS Olympus automatically? <div style="max-width: 100%; color: orange">Note: DCS and Olympus MUST be stopped before proceeding.</div>`,
@ -37,6 +41,7 @@ function checkVersion() {
console.log("Update canceled");
})
}
/* If the current version is newer than the latest release, the user is probably a developer. Ask for a beta update */
else if (reg2[0] > reg1[0] || (reg2[0] == reg1[0] && reg2[1] > reg1[1]) || (reg2[0] == reg1[0] && reg2[1] == reg1[1] && reg2[2] > reg1[2])) {
console.log(`Beta version detected: ${res["version"]}`);
showConfirmPopup(`You are currently running DCS Olympus ${VERSION}, which is newer than the latest release version. Do you want to download the latest beta version? <div style="max-width: 100%; color: orange">Note: DCS and Olympus MUST be stopped before proceeding.</div>`,
@ -50,9 +55,12 @@ function checkVersion() {
})
}
/** Update Olympus in "beta" mode. The user will be provided with the option to update from a build artifact
*
*/
async function updateOlympusBeta() {
/* Get a list of build artifacts */
const octokit = new Octokit({});
const res = await octokit.request('GET /repos/{owner}/{repo}/actions/artifacts', {
owner: 'Pax1601',
repo: 'DCSOlympus',
@ -60,19 +68,22 @@ async function updateOlympusBeta() {
'X-GitHub-Api-Version': '2022-11-28'
}
});
const artifacts = res.data.artifacts;
/* Select the newest artifact */
var artifact = artifacts.find((artifact) => { return artifact.name = "development_build_not_a_release" });
showConfirmPopup(`Latest beta artifact has a timestamp of ${artifact.updated_at}. Do you want to continue?`, () => {
/* Run the browser and download the artifact */ //TODO: try and directly download the file from code rather than using the browser
exec(`start https://github.com/Pax1601/DCSOlympus/actions/runs/${artifact.workflow_run.id}/artifacts/${artifact.id}`)
showConfirmPopup('A browser window was opened to download the beta artifact. Please wait for the download to complete, then press "Accept" and select the downloaded beta artifact.',
() => {
/* Ask the user to select the downloaded file */
var input = document.createElement('input');
input.type = 'file';
input.click();
input.onchange = e => {
console.log(e.target.files[0]);
/* Run the update process */
updateOlympus(e.target.files[0])
}
},
@ -85,6 +96,9 @@ async function updateOlympusBeta() {
})
}
/** Update Olympus to the lastest release
*
*/
async function updateOlympusRelease() {
const octokit = new Octokit({})
@ -96,11 +110,14 @@ async function updateOlympusRelease() {
}
})
/* Run the update process */
updateOlympus(res.data.assets[0].browser_download_url)
}
function updateOlympus(location) {
showWaitPopup("Please wait while Olympus is being updated. The Manager will be closed and reopened automatically when updating is completed.")
/* If the location is a string, it is interpreted as a download url. Else, it is interpreted as a File (on disk)*/
if (typeof location === "string") {
console.log(`Updating Olympus with package from ${location}`)
} else {
@ -110,20 +127,24 @@ function updateOlympus(location) {
let tmpDir;
const appPrefix = 'dcs-olympus-';
try {
/* Create a temporary folder */
const folder = path.join(os.tmpdir(), appPrefix);
tmpDir = fs.mkdtempSync(folder);
if (typeof location === "string") {
/* Download the file */
const file = fs.createWriteStream(path.join(tmpDir, "temp.zip"));
console.log(`Downloading update package in ${path.join(tmpDir, "temp.zip")}`)
const request = https.get(location, (response) => {
if (response.statusCode === 200) {
response.pipe(file);
// after download completed close filestream
/* Either on success or on error close the file stream */
file.on("finish", () => {
file.close();
console.log("Download completed");
/* Extract and copy the files */
extractAndCopy(tmpDir);
});
file.on("error", (err) => {
@ -137,16 +158,47 @@ function updateOlympus(location) {
}
});
} else {
/* Copy the archive to the temporary folder */
fs.copyFileSync(location.path, path.join(tmpDir, "temp.zip"));
/* Extract and copy the files */
extractAndCopy(tmpDir);
}
}
catch (err) {
/* Show the failed update message */
failUpdate();
console.error(err)
}
}
/** Extract the contents of the zip and update Olympus by deleting all files from the current installation and copying the new files over.
*
*/
function extractAndCopy(folder) {
/* Extract all the files */
const zip = new AdmZip(path.join(folder, "temp.zip"));
zip.extractAllTo(path.join(folder, "temp"));
/* Create a batch file that:
1) waits for 5 seconds to allow the old process to close;
2) deletes the existing installation;
3) copies the new installation;
4) cds into the new installation;
5) runs the install.bat script */
fs.writeFileSync(path.join(folder, 'update.bat'),
`timeout /t 5 \nrmdir "${path.join(__dirname, "..", "..")}" /s /q \necho D|xcopy /Y /S /E "${path.join(folder, "temp")}" "${path.join(__dirname, "..", "..")}" \ncd "${path.join(__dirname, "..", "..")}" \ninstall.bat`
)
/* Launch the update script then close gracefully */
var proc = spawn('cmd.exe', ["/c", path.join(folder, 'update.bat')], { cwd: folder, shell: true, detached: true });
proc.unref();
ipcRenderer.send('window:close');
}
/** Something went wrong. Tell the user to update manually.
*
*/
function failUpdate() {
showErrorPopup("An error has occurred while updating Olympus. Please delete Olympus and update it manually. A browser window will open automatically on the download page.", () => {
exec(`start https://github.com/Pax1601/DCSOlympus/releases`, () => {
@ -155,19 +207,6 @@ function failUpdate() {
})
}
function extractAndCopy(folder) {
const zip = new AdmZip(path.join(folder, "temp.zip"));
zip.extractAllTo(path.join(folder, "temp"));
fs.writeFileSync(path.join(folder, 'update.bat'),
`timeout /t 5 \nrmdir "${path.join(__dirname, "..", "..")}" /s /q \necho D|xcopy /Y /S /E "${path.join(folder, "temp")}" "${path.join(__dirname, "..", "..")}" \ncd "${path.join(__dirname, "..", "..")}" \ninstall.bat`
)
var proc = spawn('cmd.exe', ["/c", path.join(folder, 'update.bat')], { cwd: folder, shell: true, detached: true });
proc.unref();
ipcRenderer.send('window:close');
}
/* White-listed channels. */
const ipc = {
'render': {
@ -216,15 +255,22 @@ contextBridge.exposeInMainWorld(
}
});
/* New instance of the manager app */
const manager = new Manager();
/* On content loaded */
window.addEventListener('DOMContentLoaded', async () => {
/* Check if a new version is available */
checkVersion();
/* Compute the height of the content page */
computePagesHeight();
document.getElementById("loader").classList.remove("hide");
await manager.start();
/* Compute the height of the content page to account for the pages created by the manager*/
computePagesHeight();
/* Create event listeners for the hyperlinks */
var links = document.querySelectorAll(".link");
for (let i = 0; i < links.length; i++) {
links[i].addEventListener("click", (e) => {
@ -233,11 +279,13 @@ window.addEventListener('DOMContentLoaded', async () => {
}
})
checkVersion();
window.addEventListener('resize', () => {
computePagesHeight();
})
/** Computes the height of the content area
*
*/
function computePagesHeight() {
var pages = document.querySelectorAll(".manager-page");
var titleBar = document.querySelector("#title-bar");

View File

@ -32,6 +32,9 @@ class ResultPage extends ManagerPage {
super.show();
}
/** Installation is performed by using an then chain of async functions. Installation is aborted on any error along the chain
*
*/
startInstallation() {
installHooks(this.instance.folder).then(
() => {

View File

@ -5,6 +5,7 @@ const path = require('path');
let window;
/* Add the System32 folder to the environment for the shortcuts creation to work properly */
process.env['PATH'] = process.env['PATH'] + "%WINDIR%\\System32;"
function createWindow() {
@ -17,7 +18,7 @@ function createWindow() {
webPreferences: {
contextIsolation: true,
preload: path.join(__dirname, "javascripts", 'preload.js'),
nodeIntegration: true, // like here
nodeIntegration: true,
},
icon: "./../img/olympus_configurator.ico"
});
@ -51,8 +52,6 @@ electronApp.on('activate', () => {
}
});
// ---
electronIpcMain.on('window:minimize', () => {
window.minimize();
})