mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Documented manager code
This commit is contained in:
parent
7bf6c1bb23
commit
a0634a7f99
@ -86,7 +86,7 @@
|
||||
</div>
|
||||
<% if (!simplified) { %>
|
||||
<div class="button cancel">
|
||||
Cancel installation
|
||||
<%= install? "Cancel installation": "Cancel editing" %>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
</div>
|
||||
<% if (!simplified) { %>
|
||||
<div class="button cancel">
|
||||
Cancel installation
|
||||
<%= install? "Cancel installation": "Cancel editing" %>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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(
|
||||
() => {
|
||||
|
||||
@ -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();
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user