From 0282c950d276bcb74122151f7618c9aef645858e Mon Sep 17 00:00:00 2001 From: Dev_Bon_Bon Date: Wed, 17 Jul 2019 14:19:08 +0300 Subject: [PATCH] Dump --- .eslintrc.js | 32 ------- manager/Map.js | 199 +++++++++++++++++++++++++++++++++++++++++++ manager/Server.js | 7 ++ manager/Timestamp.js | 26 ++++++ minecraft-server.js | 40 +++++++++ package-lock.json | 71 ++++++++++++--- package.json | 7 +- 7 files changed, 335 insertions(+), 47 deletions(-) delete mode 100644 .eslintrc.js create mode 100644 manager/Map.js create mode 100644 manager/Server.js create mode 100644 manager/Timestamp.js diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index a354611..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,32 +0,0 @@ -module.exports = { - "env": { - "browser": true, - "es6": true, - "node": true - }, - "extends": [ - "eslint:recommended" - ], - "parser": "babel-eslint", - "parserOptions": { - "ecmaFeatures": { - "jsx": true - }, - "ecmaVersion": 6 - }, - "rules": { - "indent": [ - "error", - 4 - ], - "linebreak-style": [ - "error", - "unix" - ], - "no-console": "off", - "semi": [ - "error", - "always" - ] - } -}; \ No newline at end of file diff --git a/manager/Map.js b/manager/Map.js new file mode 100644 index 0000000..7b214ad --- /dev/null +++ b/manager/Map.js @@ -0,0 +1,199 @@ +const fs = require('fs-extra'); +const tar = require('tar'); +const path = require('path'); + +const timestamp = require(path.resolve('Timestamp.js')); + +/** + * Information about the Minecraft world and its properties + */ +class Map { + /** + * Takes an existing saved instance of Map, or if none uses a default one + * @type {Object} + */ + constructor (object = Map.default) { + this.static = object.static; + this.name = object.name; + this.server = object.server; + this.properties = object.properties; + this.modt = object.modt; + this.config = object.config; + } + /** + * Returns the data of this Map instance as an object + * @return {Object} + */ + export () { + return { + static: this.static, + name: this.name, + server: this.server, + properties: this.properties, + modt: this.modt, + connfig: this.config + }; + } + /** + * @return {String} The name of the world folder as it would be on disk + */ + get foldername () { + return `${this.name.replace(/[^\w]/g, '_')}`; + } + /** + * Updates the world archive on disk with the latest files + * @param {String} serverPath Path to Minecraft Server executables folder + * @return {Promise} Resolves to true or false depending on whether successful or not + */ + async save (serverPath) { + try { + await tar.update({ + file: `${this.foldername}.tgz`, + cwd: Map.paths.maps, + gzip: { level: 9 } + }, [path.join(serverPath, this.foldername)]); + return true; + } catch (error) { + console.log(`Failed to save map to disk! \n${error}`); + return false; + } + } + /** + * If the world exists, extracts it into the server folder + * If not, creates an empty archive ready for the world folder + * @param {String} serverPath Path to Minecraft Server executables folder + * @return {Promise} Resolves to true or false depending on whether successful or not + */ + async load (serverPath) { + try { + if (!this.static) { + await tar.create({ + gzip: { level: 9 }, + file: path.join(Map.paths.maps, `${this.foldername}.tgz`) + }, []); + this.static = true; + } else { + await tar.extract({ + cwd: serverPath, + file: path.join(Map.paths.maps, `${this.foldername}.tgz`) + }); + } + return true; + } catch (error) { + console.log(`Failed to load map! \n${error}`); + return false; + } + } + /** + * Saves and removes the map from the Minecraft Server executables folder + * @param {String} serverPath Path to Minecraft Server executables folder + * @return {Promise} Resolves to true or false depending on whether successful or not + */ + async unload (serverPath) { + try { + await this.save(); + await fs.remove(path.join(serverPath, this.foldername)); + return true; + } catch (error) { + console.log(`Failed to unload map! \n${error}`); + return false; + } + } + /** + * Saves and creates a backup of the map + * Has the timestamp, Map name and filename of created backup file + * @param {[type]} serverPath Path to Minecraft Server executables folder + * @return {Promise} Resolves to an object, that holds information about the backup + */ + async backup (serverPath) { + try { + const backup = { + timestamp: timestamp.getDateTime(), + map: this.name, + file: `${this.foldername}.tgz` + }; + await this.save(); + await fs.copy( + path.join(Map.paths.maps, `${this.foldername}.tgz`), + path.join(Map.paths.backups, `${backup.timestamp}_${backup.file}`) + ); + return backup; + } catch (error) { + console.log(`Failed to backup map! \n${error}`); + return false; + } + } +} +/** + * Absolute paths to folders and files used to store Map and Minecraft world data + * @type {Object} + */ +Map.paths = { + maps: path.resolve('maps'), + backups: path.resolve('backups'), + get mapList () { + return path.join(Map.paths.maps, 'maps.json'); + }, + get backupList () { + return path.join(Map.paths.backups, 'backups.json'); + } +}; +/** + * Values for the default Map instance + * @type {Object} + */ +Map.default = { + /** + * True if the world exists on disk + * Used to check if the map name, server etc. can still be edited freely + * @type {Boolean} + */ + static: false, + /** + * The world name / id + * @type {String} + */ + name: 'world', + /** + * The name of the server to be used + * @type {String} + */ + server: 'default', + /** + * The name of the properties to be used + * @type {String} + */ + properties: 'default', + /** + * MOTD string(s) and icon(s) shown in Minecrafts multiplayer page + * If multiple, one message / icon pair will be chosen randomly on map load + * @type {Array} + */ + modt: [ + { + message: 'A Minecraft Server', + /** Name of a '.png' file stored in '/icons' or false if none */ + icon: false + } + ], + /** + * Controls how map saves and backups are handled + * @type {Object} + */ + config: { + /** + * How often in minutes the world is backed up + * If negative or zero automatic backups are turned off + * @type {Number} + */ + backupInterval: 0, + /** + * How many backups are kept before old ones are deleted + * If negative or zero no all backups are saved + * @type {Number} + */ + backups: 3 + } +}; + +module.exports = Map; diff --git a/manager/Server.js b/manager/Server.js new file mode 100644 index 0000000..db2879b --- /dev/null +++ b/manager/Server.js @@ -0,0 +1,7 @@ +/** + * How often in minutes to save the world and flush it to the hard disk + * If an Integer, autosave is turned off and map is saved by the manager + * if negative, zero or 'auto' autosave is left on + * @type {String/Number} + */ +// saveInterval: 'auto', diff --git a/manager/Timestamp.js b/manager/Timestamp.js new file mode 100644 index 0000000..ce3ebd4 --- /dev/null +++ b/manager/Timestamp.js @@ -0,0 +1,26 @@ +module.exports = { + /** + * A wrapper for 'padStart()' + * @param {string} string String to pad + * @return {string} + */ + zp: (string) => string.padStart(2, '0'), + /** + * Create a Time only timestamp string with the format: + * [Hour]:[Minutes]:[Seconds] + * @param {Date} date The time to be used + * @return {string} + */ + getTime: function (date = new Date()) { + return `${this.zp(date.getHours())}:${this.zp(date.getMinutes() + 1)}:${this.zp(date.getSeconds())}`; + }, + /** + * Create a complete Date / Time timestamp string with the format: + * [Year]-[Month]-[Date]_[Hour]:[Minutes]:[Seconds] + * @param {Date} date The date and time to be used + * @return {string} + */ + getDateTime: function getDateTime (date = new Date()) { + return `${date.getFullYear()}-${this.zp(date.getMonth() + 1)}-${this.zp(date.getDate())}_${this.getTime(date)}`; + } +}; diff --git a/minecraft-server.js b/minecraft-server.js index 36edee2..7ca6e14 100644 --- a/minecraft-server.js +++ b/minecraft-server.js @@ -8,6 +8,8 @@ const os = require('os'); const path = require('path'); const spawn = require('child_process').spawn; +const Map = require(path.resolve(path.join('manager', 'Map.js'))); + const debugMinecraftServer = false; let defaultProperties = { @@ -190,9 +192,47 @@ class MinecraftServer { .value() .address; + /** + * Start asynchronous tasks + */ + if (global.debugMinecraftServer) { + console.log('Starting asynchronous tasks...'); + } + Promise.all([this.readMapList()]) + .then(results => { + this.maps = results[0]; + }); + this.checkForMinecraftInstallation(); this.getMinecraftVersions(); } + /** + * Reads an array of map objects from 'Map.paths.mapList' and returns them + * @return {Promise} Resolves to an array of Map instances + */ + async readMapList () { + try { + return (await fs.readJson(Map.paths.mapList)).map(map => new Map(map)); + } catch (error) { + console.log(`Failed to read map list! \n${error}`); + return []; + } + } + /** + * Writes an array of map objects to 'Map.paths.mapList' + * @return {Promise} Resolves to true or false depending on whether successful or not + */ + async writeMapList () { + try { + await fs.writeJson(Map.paths.mapList, this.maps.map(map => map.export()) || []); + return true; + } catch (error) { + console.log(`Failed to write map list! \n${error}`); + return false; + } + } + + async backup acceptEula () { if (debugMinecraftServer) { diff --git a/package-lock.json b/package-lock.json index eb8f697..81a95c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,9 +34,9 @@ } }, "acorn": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", - "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==" + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.2.0.tgz", + "integrity": "sha512-8oe72N3WPMjA+2zVG71Ia0nXZ8DpQH+QyyHO+p06jT8eg8FGG3FbcUIi8KziHlAfheJQZeoqbvq1mQSQHXKYLw==" }, "acorn-jsx": { "version": "5.0.1", @@ -45,9 +45,9 @@ "dev": true }, "ajv": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", - "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", @@ -56,9 +56,9 @@ } }, "ajv-keywords": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz", - "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", + "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==" }, "ansi-escapes": { "version": "3.2.0", @@ -267,6 +267,11 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "chownr": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", + "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==" + }, "cli-cursor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", @@ -806,6 +811,14 @@ } } }, + "fs-minipass": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.6.tgz", + "integrity": "sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==", + "requires": { + "minipass": "^2.2.1" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1136,14 +1149,29 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "minipass": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "requires": { + "minipass": "^2.2.1" + } }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, "requires": { "minimist": "0.0.8" } @@ -1635,6 +1663,20 @@ } } }, + "tar": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz", + "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==", + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.5", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + } + }, "tar-stream": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", @@ -1784,6 +1826,11 @@ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + }, "zip-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-2.0.1.tgz", diff --git a/package.json b/package.json index e51570c..cfe5136 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,9 @@ "url": "https://github.com/nickrnet/" }, "dependencies": { - "acorn": "^6.1.1", - "ajv": "^6.10.0", - "ajv-keywords": "^3.4.0", + "acorn": "^6.2.0", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", "archiver": "^3.0.0", "axios": "^0.19.0", "body-parser": "^1.19.0", @@ -35,6 +35,7 @@ "passport": "^0.4.0", "permission": "^1.1.0", "prop-types": "^15.7.2", + "tar": "^4.4.10", "underscore": "^1.9.1" }, "devDependencies": {