Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 0 additions & 32 deletions .eslintrc.js

This file was deleted.

199 changes: 199 additions & 0 deletions manager/Map.js
Original file line number Diff line number Diff line change
@@ -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;
7 changes: 7 additions & 0 deletions manager/Server.js
Original file line number Diff line number Diff line change
@@ -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',
26 changes: 26 additions & 0 deletions manager/Timestamp.js
Original file line number Diff line number Diff line change
@@ -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)}`;
}
};
40 changes: 40 additions & 0 deletions minecraft-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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) {
Expand Down
Loading