Compare commits
5 Commits
a748c07a8d
...
main
Author | SHA1 | Date | |
---|---|---|---|
30f05ab934 | |||
e8568d8590 | |||
3bc9b9bd17 | |||
7b02311f54 | |||
cfde9a9119 |
15
README.md
15
README.md
@@ -3,3 +3,18 @@
|
|||||||
A Plutonium launcher that aims to make old CoD games more accessible
|
A Plutonium launcher that aims to make old CoD games more accessible
|
||||||
-----
|
-----
|
||||||
Currently being written from scratch, please be patient.
|
Currently being written from scratch, please be patient.
|
||||||
|
|
||||||
|
## Current features
|
||||||
|
- Plutonium login (maybe the first open-source launcher to implement such a thing)
|
||||||
|
- Launching games (currently not implemented in the UI)
|
||||||
|
- Choosing between online mode and LAN mode
|
||||||
|
- Automatic file checking and updating
|
||||||
|
|
||||||
|
## To do
|
||||||
|
- Make each HTTPS request its own function instead of binding them to an event
|
||||||
|
- De-duplicate code (HTTPS requests in src/main.js for example)
|
||||||
|
- Make a better UI/UX
|
||||||
|
|
||||||
|
## Planned features
|
||||||
|
- Native Mac and Linux support (and launching the game with WINE using binfmt)
|
||||||
|
- Custom manifest support (useful for modding or optimizing)
|
||||||
|
280
src/main.js
280
src/main.js
@@ -1,21 +1,28 @@
|
|||||||
const path = require("node:path");
|
const path = require("node:path");
|
||||||
const fs = require("node:fs");
|
const fs = require("node:fs");
|
||||||
|
const fsPromises = require("node:fs/promises");
|
||||||
const https = require("node:https");
|
const https = require("node:https");
|
||||||
const process = require("node:process");
|
const process = require("node:process");
|
||||||
const os = require("node:os");
|
const os = require("node:os");
|
||||||
const child_process = require("node:child_process");
|
const child_process = require("node:child_process");
|
||||||
const electron = require("electron");
|
const electron = require("electron");
|
||||||
|
|
||||||
|
const update = require(path.join(__dirname, "update.js"));
|
||||||
|
|
||||||
let configFilePath;
|
let configFilePath;
|
||||||
|
let plutoniumInstallDirectory;
|
||||||
switch (os.platform()) {
|
switch (os.platform()) {
|
||||||
case "win32":
|
case "win32":
|
||||||
configFilePath = path.join(process.env["LOCALAPPDATA"], "Plutonium", "open-plutonium-launcher.json");
|
configFilePath = path.join(process.env["LOCALAPPDATA"], "Plutonium", "open-plutonium-launcher.json");
|
||||||
|
plutoniumInstallDirectory = path.join(process.env["LOCALAPPDATA"], "Plutonium");
|
||||||
break;
|
break;
|
||||||
case "linux":
|
case "linux":
|
||||||
configFilePath = path.join(os.userInfo().homedir, ".config", "open-plutonium-launcher.json");
|
configFilePath = path.join(os.userInfo().homedir, ".config", "open-plutonium-launcher.json");
|
||||||
|
plutoniumInstallDirectory = path.join(os.userInfo().homedir, ".local", "share", "Plutonium");
|
||||||
break;
|
break;
|
||||||
case "darwin":
|
case "darwin":
|
||||||
configFilePath = path.join(os.userInfo().homedir, "Library", "Application Support", "open-plutonium-launcher.json");
|
configFilePath = path.join(os.userInfo().homedir, "Library", "Application Support", "open-plutonium-launcher.json");
|
||||||
|
plutoniumInstallDirectory = path.join(os.userInfo().homedir, "Library", "Application Support", "Plutonium");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
electron.dialog.showErrorBox("Incompatible system", "Sorry, your operating system doesn't seem to be supported...");
|
electron.dialog.showErrorBox("Incompatible system", "Sorry, your operating system doesn't seem to be supported...");
|
||||||
@@ -37,6 +44,15 @@ let userInfo = {
|
|||||||
"emailVerified": false,
|
"emailVerified": false,
|
||||||
"avatar": "https://forum.plutonium.pw/assets/uploads/system/avatar-default.png"
|
"avatar": "https://forum.plutonium.pw/assets/uploads/system/avatar-default.png"
|
||||||
};
|
};
|
||||||
|
let config = {
|
||||||
|
"user-info": userInfo,
|
||||||
|
"game-directories": {
|
||||||
|
"iw5": "",
|
||||||
|
"t4": "",
|
||||||
|
"t5": "",
|
||||||
|
"t6": ""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function createMainWindow() {
|
function createMainWindow() {
|
||||||
mainWindow = new electron.BrowserWindow({
|
mainWindow = new electron.BrowserWindow({
|
||||||
@@ -59,6 +75,34 @@ function createMainWindow() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readConfig(filePath) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) return reject(new Error("Config file doesn't exist or is not a file."));
|
||||||
|
|
||||||
|
fsPromises.readFile(filePath, "utf-8").then(function (contents) {
|
||||||
|
try {
|
||||||
|
let data = JSON.parse(contents);
|
||||||
|
|
||||||
|
resolve(data);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
}).catch(function (error) {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeConfig(filePath, data = {}) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
fsPromises.writeFile(filePath, JSON.stringify(data, null, "\t")).then(function () {
|
||||||
|
resolve();
|
||||||
|
}).catch(function (error) {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function fetchPlutoniumManifest() {
|
function fetchPlutoniumManifest() {
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
let request = https.request("https://cdn.plutonium.pw/updater/prod/info.json", {
|
let request = https.request("https://cdn.plutonium.pw/updater/prod/info.json", {
|
||||||
@@ -93,6 +137,162 @@ function fetchPlutoniumManifest() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validatePlutoniumLogin(token) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
let request = https.request("https://nix.plutonium.pw/api/auth/validate", {
|
||||||
|
"method": "GET",
|
||||||
|
"headers": {
|
||||||
|
"Content-Length": 0,
|
||||||
|
"Authorization": "UserToken " + token,
|
||||||
|
"X-Plutonium-Revision": String(plutoniumManifest.revision),
|
||||||
|
"User-Agent": "Nix/3.0"
|
||||||
|
}
|
||||||
|
}, function (response) {
|
||||||
|
if (response.statusCode !== 200) {
|
||||||
|
return resolve({
|
||||||
|
"status": "unauthenticated",
|
||||||
|
"successful": false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
response.setEncoding("utf-8");
|
||||||
|
|
||||||
|
let body = "";
|
||||||
|
response.on("data", function (chunk) {
|
||||||
|
body += chunk;
|
||||||
|
});
|
||||||
|
response.on("end", function () {
|
||||||
|
try {
|
||||||
|
let data = JSON.parse(body);
|
||||||
|
|
||||||
|
if (!("userId" in data)) {
|
||||||
|
reject(new Error("Authentication seems to be successful but no user identifier was returned."));
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
"status": "authenticated",
|
||||||
|
"successful": true,
|
||||||
|
...data
|
||||||
|
});
|
||||||
|
|
||||||
|
userInfo = {
|
||||||
|
"token": token,
|
||||||
|
...data
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
request.on("error", function (error) {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
request.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPlutoniumSession(game, token) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
let payload = JSON.stringify({
|
||||||
|
"game": game
|
||||||
|
});
|
||||||
|
|
||||||
|
let request = https.request("https://nix.plutonium.pw/api/auth/session", {
|
||||||
|
"method": "POST",
|
||||||
|
"headers": {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Content-Length": payload.length,
|
||||||
|
"Authorization": "UserToken " + token,
|
||||||
|
"X-Plutonium-Revision": String(plutoniumManifest.revision),
|
||||||
|
"User-Agent": "Nix/3.0"
|
||||||
|
}
|
||||||
|
}, function (response) {
|
||||||
|
if (response.statusCode !== 200) {
|
||||||
|
return resolve({
|
||||||
|
"status": "unauthenticated",
|
||||||
|
"successful": false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
response.setEncoding("utf-8");
|
||||||
|
|
||||||
|
let body = "";
|
||||||
|
response.on("data", function (chunk) {
|
||||||
|
body += chunk;
|
||||||
|
});
|
||||||
|
response.on("end", function () {
|
||||||
|
try {
|
||||||
|
let data = JSON.parse(body);
|
||||||
|
|
||||||
|
if (!("token" in data)) {
|
||||||
|
reject(new Error("Authentication seems to be successful but no token was returned."));
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
"status": "authenticated",
|
||||||
|
"successful": true,
|
||||||
|
...data
|
||||||
|
});
|
||||||
|
|
||||||
|
userInfo = data;
|
||||||
|
|
||||||
|
mainWindow.loadFile(path.join(__dirname, "views", "games.html"));
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
request.on("error", function (error) {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
request.write(payload);
|
||||||
|
request.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function launch(plutoniumInstallDir, game, gameInstallDirectory, online) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
getPlutoniumSession(game, userInfo.token).then(function (data) {
|
||||||
|
if (!data.successful) {
|
||||||
|
return reject(new Error("Authentication has failed."));
|
||||||
|
}
|
||||||
|
|
||||||
|
let bootstrapperBinary = path.join(plutoniumInstallDir, "bin", "plutonium-bootstrapper-win32.exe");
|
||||||
|
let bootstrapperArguments = [game, gameInstallDirectory];
|
||||||
|
|
||||||
|
if (online) {
|
||||||
|
bootstrapperArguments.push("-token");
|
||||||
|
bootstrapperArguments.push(data.token);
|
||||||
|
} else {
|
||||||
|
bootstrapperArguments.push("+name");
|
||||||
|
bootstrapperArguments.push(userInfo.username);
|
||||||
|
bootstrapperArguments.push("-lan");
|
||||||
|
}
|
||||||
|
|
||||||
|
fsPromises.chmod(bootstrapperBinary, 0o755).then(function () {
|
||||||
|
let gameProcess = child_process.spawn(bootstrapperBinary, bootstrapperArguments, {
|
||||||
|
"cwd": plutoniumInstallDir,
|
||||||
|
"detached": true,
|
||||||
|
"stdio": "ignore"
|
||||||
|
});
|
||||||
|
|
||||||
|
gameProcess.on("spawn", function () {
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
gameProcess.on("error", function (error) {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
}).catch(function (error) {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
electron.app.once("ready", function () {
|
electron.app.once("ready", function () {
|
||||||
createMainWindow();
|
createMainWindow();
|
||||||
fetchPlutoniumManifest().then(function (manifest) {
|
fetchPlutoniumManifest().then(function (manifest) {
|
||||||
@@ -100,6 +300,33 @@ electron.app.once("ready", function () {
|
|||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
electron.dialog.showErrorBox("Error", "The Plutonium launcher manifest could not be fetched, auto-updating might not be possible.\n" + error.message);
|
electron.dialog.showErrorBox("Error", "The Plutonium launcher manifest could not be fetched, auto-updating might not be possible.\n" + error.message);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (fs.existsSync(configFilePath)) {
|
||||||
|
readConfig(configFilePath).then(function (data) {
|
||||||
|
config = data;
|
||||||
|
|
||||||
|
if (config["user-info"].token === "V3ryS3cr3t4uth3nt1c4t10nT0k3n") {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
validatePlutoniumLogin(config["user-info"].token).then(function (data) {
|
||||||
|
if (!data.successful) {
|
||||||
|
config["user-info"].token = "V3ryS3cr3t4uth3nt1c4t10nT0k3n";
|
||||||
|
return writeConfig(configFilePath, config).catch(function (error) {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mainWindow.loadFile(path.join(__dirname, "views", "games.html"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch(function (error) {
|
||||||
|
electron.dialog.showErrorBox("Error", "Configuration file could not be read.\n" + error.message);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
writeConfig(configFilePath, config).catch(function (error) {
|
||||||
|
electron.dialog.showErrorBox("Error", "Configuration file doesn't exist and could not be created.\n" + error.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
electron.app.on("window-all-closed", function () {
|
electron.app.on("window-all-closed", function () {
|
||||||
@@ -151,8 +378,12 @@ electron.ipcMain.handle("login", function (event, username, password) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
userInfo = data;
|
userInfo = data;
|
||||||
|
config["user-info"] = userInfo;
|
||||||
|
writeConfig(configFilePath, config).catch(function (error) {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
|
||||||
mainWindow.loadFile(path.join(__dirname, "src", "views", "games.html"));
|
mainWindow.loadFile(path.join(__dirname, "views", "games.html"));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
@@ -167,3 +398,50 @@ electron.ipcMain.handle("login", function (event, username, password) {
|
|||||||
request.end();
|
request.end();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.handle("launch", function (event, game, online = true) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
let gameConfig;
|
||||||
|
switch (game) {
|
||||||
|
case "iw5mp":
|
||||||
|
case "iw5sp":
|
||||||
|
gameConfig = "iw5";
|
||||||
|
break;
|
||||||
|
case "t4mp":
|
||||||
|
case "t4sp":
|
||||||
|
gameConfig = "t4";
|
||||||
|
break;
|
||||||
|
case "t5mp":
|
||||||
|
case "t5sp":
|
||||||
|
gameConfig = "t5";
|
||||||
|
break;
|
||||||
|
case "t6mp":
|
||||||
|
case "t6zm":
|
||||||
|
gameConfig = "t6";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return reject(new Error("Unknown game : " + game));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(gameConfig in config["game-directories"])) {
|
||||||
|
return resolve(false);
|
||||||
|
}
|
||||||
|
let gameInstallDirectory = config["game-directories"][gameConfig];
|
||||||
|
|
||||||
|
update.checkFiles(plutoniumManifest, plutoniumInstallDirectory).then(function (fileList) {
|
||||||
|
let filesToUpdate = fileList.filter(function (file) {
|
||||||
|
return !file.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
update.downloadFiles(plutoniumInstallDirectory, plutoniumManifest.baseUrl, filesToUpdate).then(function () {
|
||||||
|
launch(plutoniumInstallDirectory, game, gameInstallDirectory, online).then(function (result) {
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
|
}).catch(function (error) {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
}).catch(function (error) {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@@ -3,3 +3,7 @@ const electron = require("electron");
|
|||||||
electron.contextBridge.exposeInMainWorld("login", function (username, password) {
|
electron.contextBridge.exposeInMainWorld("login", function (username, password) {
|
||||||
return electron.ipcRenderer.invoke("login", username, password);
|
return electron.ipcRenderer.invoke("login", username, password);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
electron.contextBridge.exposeInMainWorld("launch", function (game, online) {
|
||||||
|
return electron.ipcRenderer.invoke("launch", game, online);
|
||||||
|
});
|
||||||
|
@@ -3,6 +3,9 @@ const fs = require("node:fs");
|
|||||||
const crypto = require("node:crypto");
|
const crypto = require("node:crypto");
|
||||||
const https = require("node:https");
|
const https = require("node:https");
|
||||||
|
|
||||||
|
let runningDownloads = 0;
|
||||||
|
let downloadQueue = [];
|
||||||
|
|
||||||
function checkFileAgainstSHA1(baseDirectory, fileEntry) {
|
function checkFileAgainstSHA1(baseDirectory, fileEntry) {
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
let filePath = path.join(baseDirectory, fileEntry.name);
|
let filePath = path.join(baseDirectory, fileEntry.name);
|
||||||
@@ -19,6 +22,11 @@ function checkFileAgainstSHA1(baseDirectory, fileEntry) {
|
|||||||
|
|
||||||
let fileInputStream = fs.createReadStream(filePath);
|
let fileInputStream = fs.createReadStream(filePath);
|
||||||
|
|
||||||
|
fileInputStream.on("error", function (error) {
|
||||||
|
fileInputStream.close();
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
hash.on("readable", function () {
|
hash.on("readable", function () {
|
||||||
let data = hash.read();
|
let data = hash.read();
|
||||||
if (data) {
|
if (data) {
|
||||||
@@ -35,6 +43,12 @@ function checkFileAgainstSHA1(baseDirectory, fileEntry) {
|
|||||||
|
|
||||||
function downloadFile(url, filePath) {
|
function downloadFile(url, filePath) {
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
|
let dirPath = path.dirname(filePath);
|
||||||
|
if (!fs.existsSync(dirPath)) {
|
||||||
|
fs.mkdirSync(dirPath, {
|
||||||
|
"recursive": true
|
||||||
|
});
|
||||||
|
}
|
||||||
let fileOutputStream = fs.createWriteStream(filePath);
|
let fileOutputStream = fs.createWriteStream(filePath);
|
||||||
|
|
||||||
let request = https.request(url, {
|
let request = https.request(url, {
|
||||||
@@ -42,7 +56,7 @@ function downloadFile(url, filePath) {
|
|||||||
}, function (response) {
|
}, function (response) {
|
||||||
if (response.statusCode !== 200) return reject(new Error("Got a non-OK response while trying to download a file."));
|
if (response.statusCode !== 200) return reject(new Error("Got a non-OK response while trying to download a file."));
|
||||||
|
|
||||||
fileOutputStream.on("end", function () {
|
fileOutputStream.on("close", function () {
|
||||||
resolve(true);
|
resolve(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -59,29 +73,49 @@ function downloadFile(url, filePath) {
|
|||||||
|
|
||||||
function processDownloadQueue(baseDirectory, baseURL, fileEntries) {
|
function processDownloadQueue(baseDirectory, baseURL, fileEntries) {
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
let runningDownloads = 0;
|
queue = [...fileEntries];
|
||||||
let queue = [...fileEntries];
|
|
||||||
|
|
||||||
let finishCallback = function () {
|
let finishCallback = function () {
|
||||||
if (queue.length > 0) {
|
runningDownloads--;
|
||||||
|
|
||||||
|
if (queue.length > 0 && runningDownloads < module.exports.concurrentDownloads) {
|
||||||
|
let currentEntry = queue.shift().entry;
|
||||||
|
|
||||||
|
downloadFile(baseURL + currentEntry.hash, path.join(baseDirectory, currentEntry.name)).then(finishCallback).catch((errorCallback).bind(currentEntry));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queue.length < 1 && runningDownloads < 1) {
|
||||||
|
resolve();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let errorCallback = function (error) {
|
||||||
|
runningDownloads--;
|
||||||
|
|
||||||
|
if (!this.retries) {
|
||||||
|
this.retries = 0;
|
||||||
|
}
|
||||||
|
if (this.retries > 3) return reject(error);
|
||||||
|
|
||||||
|
this.retries++;
|
||||||
|
queue.push({
|
||||||
|
"entry": this,
|
||||||
|
"ok": false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (fileEntries.length < 1) {
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
|
||||||
for (let d = 0; d < module.exports.concurrentDownloads; d++) {
|
for (let d = 0; d < module.exports.concurrentDownloads; d++) {
|
||||||
if (queue.length < 1) break;
|
if (queue.length < 1) break;
|
||||||
|
|
||||||
let currentEntry = queue.shift();
|
let currentEntry = queue.shift().entry;
|
||||||
|
|
||||||
downloadFile(baseURL + currentEntry.hash, path.join(baseDirectory, currentEntry.name)).then(finishCallback).catch((function (error) {
|
runningDownloads++;
|
||||||
if (!this.retries) {
|
|
||||||
this.retries = 0;
|
|
||||||
}
|
|
||||||
if (this.retries > 3) return reject(error);
|
|
||||||
|
|
||||||
this.retries++;
|
downloadFile(baseURL + currentEntry.hash, path.join(baseDirectory, currentEntry.name)).then(finishCallback).catch((errorCallback).bind(currentEntry));
|
||||||
queue.push(this);
|
|
||||||
}).bind(currentEntry));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -107,9 +141,15 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
"downloadFiles": function (baseDirectory, baseURL, fileEntries) {
|
"downloadFiles": function (baseDirectory, baseURL, fileEntries) {
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
processDownloadQueue(baseDirectory, baseURL, fileEntries.filter(function (entry) {
|
let filesToProcess = fileEntries.filter(function (entry) {
|
||||||
return !entry.ok;
|
return !entry.ok;
|
||||||
})).then(function () {
|
});
|
||||||
|
|
||||||
|
if (filesToProcess.length < 1) {
|
||||||
|
return resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
processDownloadQueue(baseDirectory, baseURL, filesToProcess).then(function () {
|
||||||
resolve(true);
|
resolve(true);
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
reject(error);
|
reject(error);
|
||||||
|
@@ -9,7 +9,11 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
|
<div id="card-container">
|
||||||
|
<article class="game-card">
|
||||||
|
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
Reference in New Issue
Block a user