Improve /server backup, make compress by default

This commit is contained in:
UnknownShadow200 2018-07-26 05:07:41 +10:00
parent 481bd1b9fd
commit 455e76ad73
4 changed files with 97 additions and 91 deletions

View File

@ -32,14 +32,14 @@ namespace MCGalaxy.Commands.Maintenance {
public override void Use(Player p, string message, CommandData data) {
string[] args = message.SplitSpaces();
switch (args[0].ToLower()) {
case "public": SetPublic(p, args); break;
case "private": SetPrivate(p, args); break;
case "reload": DoReload(p, args); break;
case "backup": DoBackup(p, args); break;
case "restore": DoRestore(p, args); break;
case "import": DoImport(p, args); break;
case "upgradeblockdb": DoBlockDBUpgrade(p, args); break;
default: Help(p); break;
case "public": SetPublic(p, args); break;
case "private": SetPrivate(p, args); break;
case "reload": DoReload(p, args); break;
case "backup": DoBackup(p, args); break;
case "restore": DoRestore(p, args); break;
case "import": DoImport(p, args); break;
case "upgradeblockdb": DoBlockDBUpgrade(p, args); break;
default: Help(p); break;
}
}
@ -56,33 +56,28 @@ namespace MCGalaxy.Commands.Maintenance {
}
void DoReload(Player p, string[] args) {
if (!CheckPerms(p)) {
p.Message("Only Console or the Server Owner can reload the server settings."); return;
}
p.Message("Reloading settings...");
Server.LoadAllSettings();
p.Message("Settings reloaded! You may need to restart the server, however.");
p.Message("Settings reloaded! You may need to restart the server, however.");
}
void DoBackup(Player p, string[] args) {
string type = args.Length == 1 ? "" : args[1].ToLower();
bool compress = true;
if (args.Length > 2 && !CommandParser.GetBool(p, args[2], ref compress)) return;
if (type.Length == 0 || type == "all") {
p.Message("Server backup started. Please wait while backup finishes.");
Backup.CreatePackage(p, true, true, false);
} else if (type == "database" || type == "sql" || type == "db") {
// Creates CREATE TABLE and INSERT statements for all tables and rows in the database
Backup.Perform(p, true, true, false, compress);
} else if (type == "database" || type == "db") {
p.Message("Database backup started. Please wait while backup finishes.");
Backup.CreatePackage(p, false, true, false);
} else if (type == "allbutdb" || type == "files" || type == "file") {
// Saves all files and folders to a .zip
Backup.Perform(p, false, true, false, compress);
} else if (type == "files" || type == "file") {
p.Message("All files backup started. Please wait while backup finishes.");
Backup.CreatePackage(p, true, false, false);
Backup.Perform(p, true, false, false, compress);
} else if (type == "lite") {
p.Message("Server backup (except BlockDB and undo data) started. Please wait while backup finishes.");
Backup.CreatePackage(p, true, true, true);
} else if (type == "litedb") {
p.Message("Database backup (except BlockDB tables) started. Please wait while backup finishes.");
Backup.CreatePackage(p, false, true, true);
p.Message("Server backup (except BlockDB) started. Please wait while backup finishes.");
Backup.Perform(p, true, true, true, compress);
} else if (type == "table") {
if (args.Length == 2) { p.Message("You need to provide the table name to backup."); return; }
if (!Formatter.ValidName(p, args[2], "table")) return;
@ -141,19 +136,29 @@ namespace MCGalaxy.Commands.Maintenance {
return p.name.CaselessEq(ServerConfig.OwnerName);
}
public override void Help(Player p, string message) {
if (message.CaselessEq("backup")) {
p.Message("%T/Server backup [mode] <compress>");
p.Message("%HMode can be one of the following:");
p.Message(" &fall %H- Backups everything (default)");
p.Message(" &fdb %H- Only backups the database");
p.Message(" &ffiles %H- Backups everything, except the database");
p.Message(" &flite %H- Backups everything, except BlockDB files");
p.Message("%H<compress> - Whether to compress the backup (default yes)");
} else {
base.Help(p, message);
}
}
public override void Help(Player p) {
p.Message("%T/Server reload %H- Reload the server files. (May require restart) (Owner only)");
p.Message("%T/Server public/private %H- Make the server public or private.");
p.Message("%T/Server restore %H- Restore the server from a backup.");
p.Message("%T/Server backup all/db/files/lite/litedb %H- Make a backup.");
p.Message(" %Hall - Backups everything (default)");
p.Message(" %Hdb - Only backups the database.");
p.Message(" %Hfiles - Backups everything, except the database.");
p.Message(" %Hlite - Backups everything, except BlockDB and undo files.");
p.Message(" %Hlitedb - Backups database, except BlockDB tables.");
p.Message("%T/Server reload %H- Reloads the server files");
p.Message("%T/Server public/private %H- Makes the server public or private");
p.Message("%T/Server restore %H- Restores the server from a backup");
p.Message("%T/Server backup %H- Make a backup. See %T/help server backup");
p.Message("%T/Server backup table [name] %H- Backups that database table");
p.Message("%T/Server import [name] %H- Imports a backed up database table");
p.Message("%T/Server upgradeblockdb %H- Dumps BlockDB tables from database");
p.Message("%HOnly useful when upgrading from a very old {0} version", Server.SoftwareName);
}
}
}

View File

@ -29,11 +29,11 @@ namespace MCGalaxy {
public bool Files, Database, Lite;
}
public static void CreatePackage(Player p, bool files, bool db, bool lite) {
public static void Perform(Player p, bool files, bool db, bool lite, bool compress) {
if (db) {
Logger.Log(LogType.SystemActivity, "Backing up the database...");
using (StreamWriter sql = new StreamWriter(sqlPath))
BackupDatabase(sql,lite);
BackupDatabase(sql);
Logger.Log(LogType.SystemActivity, "Backed up the database to " + sqlPath);
}
@ -47,12 +47,8 @@ namespace MCGalaxy {
Logger.Log(LogType.SystemActivity, "Creating compressed backup...");
using (Stream stream = File.Create(zipPath)) {
ZipWriter writer = new ZipWriter(stream);
if (files) {
Logger.Log(LogType.SystemActivity, "Compressing files...");
SaveFiles(writer, filesList);
}
if (db) SaveDatabase(writer);
if (files) SaveFiles(writer, filesList, compress);
if (db) SaveDatabase(writer, compress);
writer.FinishEntries();
writer.WriteFooter();
@ -87,23 +83,29 @@ namespace MCGalaxy {
return paths;
}
static void SaveFiles(ZipWriter writer, List<string> paths) {
foreach (string path in paths) {
static void SaveFiles(ZipWriter writer, List<string> paths, bool compress) {
Logger.Log(LogType.SystemActivity, "Compressing {0} files...", paths.Count);
for (int i = 0; i < paths.Count; i++) {
string path = paths[i];
bool compressThis = compress && !path.CaselessContains(".lvl");
try {
using (Stream src = File.OpenRead(path)) {
writer.WriteEntry(src, path);
writer.WriteEntry(src, path, compressThis);
}
} catch (Exception ex) {
Logger.LogError("Failed to backup file: " + path, ex);
}
if (i == 0 || (i % 100) != 0) continue;
Logger.Log(LogType.SystemActivity, "Backed up {0}/{1} files", i, paths.Count);
}
}
static void SaveDatabase(ZipWriter writer) {
static void SaveDatabase(ZipWriter writer, bool compress) {
Logger.Log(LogType.SystemActivity, "Compressing Database...");
// TODO: gzip compress
using (FileStream fs = File.OpenRead(sqlPath)) {
writer.WriteEntry(fs, sqlPath);
writer.WriteEntry(fs, sqlPath, compress);
}
Logger.Log(LogType.SystemActivity, "Database compressed");
}
@ -123,7 +125,7 @@ namespace MCGalaxy {
}
}
// To make life easier, we reload settings now, to maker it less likely to need restart
// To make life easier, we reload settings now, to make it less likely to need restart
Server.LoadAllSettings();
p.Message("Server restored" + (errors > 0 ? " with errors. May be a partial restore" : ""));
p.Message("It is recommended that you restart the server, although this is not required.");

View File

@ -24,13 +24,8 @@ using MCGalaxy.SQL;
namespace MCGalaxy {
public static partial class Backup {
public static void BackupDatabase(StreamWriter sql, bool lite) {
//We technically know all tables in the DB... But since this is MySQL, we can also get them all with a MySQL command
//So we show the tables, and store the result.
//Also output information data (Same format as phpMyAdmin's dump)
//Important note: This does NOT account for foreign keys, BLOB's etc. It only works for what we actually put in the db.
public static void BackupDatabase(StreamWriter sql) {
// NOTE: This does NOT account for foreign keys, BLOBs etc. It only works for what we actually put in the DB.
sql.WriteLine("-- {0} SQL Database Dump", Server.SoftwareName);
sql.WriteLine("-- Host: {0}", ServerConfig.MySQLHost);
sql.WriteLine("-- Generation Time: {0:d} at {0:HH:mm:ss}", DateTime.Now);
@ -40,7 +35,6 @@ namespace MCGalaxy {
List<string> tables = Database.Backend.AllTables();
foreach (string name in tables) {
if (lite && name.CaselessStarts("Block")) continue;
BackupTable(name, sql);
}
}
@ -60,18 +54,17 @@ namespace MCGalaxy {
internal static void ReplaceDatabase(Stream sql) {
using (FileStream backup = File.Create("backup.sql"))
BackupDatabase(new StreamWriter(backup), false); // backup
BackupDatabase(new StreamWriter(backup));
List<string> tables = Database.Backend.AllTables();
foreach (string table in tables)
Database.Backend.DeleteTable(table); // drop all tables
Database.Backend.DeleteTable(table);
ImportSql(sql);
}
internal static void ImportSql(Stream sql) {
// Import data (we only have CREATE TABLE and INSERT INTO statements)
using (StreamReader reader = new StreamReader(sql)) {
ImportBulk(reader);
}

View File

@ -18,12 +18,13 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Text;
namespace MCGalaxy {
struct ZipEntry {
public byte[] Filename;
public byte[] Filename;
public long CompressedSize, UncompressedSize, LocalHeaderOffset;
public uint Crc32;
public ushort BitFlags, CompressionMethod;
@ -37,15 +38,11 @@ namespace MCGalaxy {
}
sealed class ZipEntryStream : Stream {
public uint Crc32;
public uint Crc32 = uint.MaxValue;
public long CompressedLen;
Stream stream;
public ZipEntryStream(Stream stream) {
this.stream = stream;
Crc32 = uint.MaxValue;
}
public Stream stream;
public ZipEntryStream(Stream stream) { this.stream = stream; }
public override bool CanRead { get { return false; } }
public override bool CanSeek { get { return false; } }
public override bool CanWrite { get { return true; } }
@ -61,21 +58,35 @@ namespace MCGalaxy {
public override void Write(byte[] buffer, int offset, int count) {
stream.Write(buffer, offset, count);
CompressedLen += count;
for (int i = offset; i < offset + count; i++) {
Crc32 = crc32Table[(Crc32 ^ buffer[i]) & 0xFF] ^ (Crc32 >> 8);
}
}
public override void WriteByte(byte value) {
stream.WriteByte(value);
CompressedLen++;
Crc32 = crc32Table[(Crc32 ^ value) & 0xFF] ^ (Crc32 >> 8);
}
public override void Close() {
stream = null;
Crc32 ^= uint.MaxValue;
public override void Close() { stream = null; }
public long WriteStream(Stream src, byte[] buffer, bool compress) {
if (compress) {
using (DeflateStream ds = new DeflateStream(this, CompressionMode.Compress))
return WriteData(ds, src, buffer);
}
return WriteData(this, src, buffer);
}
long WriteData(Stream dst, Stream src, byte[] buffer) {
int count = 0;
long totalLen = 0;
while ((count = src.Read(buffer, 0, buffer.Length)) > 0) {
dst.Write(buffer, 0, count);
totalLen += count;
for (int i = 0; i < count; i++) {
Crc32 = crc32Table[(Crc32 ^ buffer[i]) & 0xFF] ^ (Crc32 >> 8);
}
}
return totalLen;
}
static uint[] crc32Table;
@ -112,7 +123,7 @@ namespace MCGalaxy {
w = new BinaryWriter(stream);
}
public void WriteEntry(Stream src, string file) {
public void WriteEntry(Stream src, string file, bool compress) {
ZipEntry entry = default(ZipEntry);
entry.Filename = Encoding.UTF8.GetBytes(file);
entry.LocalHeaderOffset = stream.Position;
@ -121,23 +132,18 @@ namespace MCGalaxy {
int headerSize = 30 + entry.Filename.Length + zip64Extra;
stream.Write(buffer, 0, headerSize);
// bit flag for non-ascii filename
// set bit flag for non-ascii filename
foreach (char c in file) {
if (c < ' ' || c > '~') entry.BitFlags |= (1 << 11);
}
ZipEntryStream dst;
using (dst = new ZipEntryStream(stream)) {
int read = 0;
while ((read = src.Read(buffer, 0, buffer.Length)) > 0) {
dst.Write(buffer, 0, read);
entry.UncompressedSize += read;
}
}
if (compress) entry.CompressionMethod = 8;
ZipEntryStream dst = new ZipEntryStream(stream);
entry.UncompressedSize = dst.WriteStream(src, buffer, compress);
dst.stream = null;
entry.CompressedSize = dst.CompressedLen;
entry.Crc32 = dst.Crc32;
entry.Crc32 = dst.Crc32 ^ uint.MaxValue;
entries.Add(entry); numEntries++;
}