diff --git a/MCGalaxy/Commands/Maintenance/CmdServer.cs b/MCGalaxy/Commands/Maintenance/CmdServer.cs index 51e2e2a35..d8ec77a01 100644 --- a/MCGalaxy/Commands/Maintenance/CmdServer.cs +++ b/MCGalaxy/Commands/Maintenance/CmdServer.cs @@ -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."); + Server.LoadAllSettings(); + 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; @@ -140,20 +135,30 @@ namespace MCGalaxy.Commands.Maintenance { if (ServerConfig.OwnerName.CaselessEq("Notch")) return false; return p.name.CaselessEq(ServerConfig.OwnerName); } + + public override void Help(Player p, string message) { + if (message.CaselessEq("backup")) { + p.Message("%T/Server backup [mode] "); + 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 - 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); } } } diff --git a/MCGalaxy/Server/Backup.cs b/MCGalaxy/Server/Backup.cs index 94bbd66f3..8bc407b80 100644 --- a/MCGalaxy/Server/Backup.cs +++ b/MCGalaxy/Server/Backup.cs @@ -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); } @@ -46,13 +46,9 @@ 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); + ZipWriter writer = new ZipWriter(stream); + 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 paths) { - foreach (string path in paths) { + static void SaveFiles(ZipWriter writer, List 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."); diff --git a/MCGalaxy/Server/BackupDB.cs b/MCGalaxy/Server/BackupDB.cs index 6e0a8b132..e1a84f109 100644 --- a/MCGalaxy/Server/BackupDB.cs +++ b/MCGalaxy/Server/BackupDB.cs @@ -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 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 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) - + // Import data (we only have CREATE TABLE and INSERT INTO statements) using (StreamReader reader = new StreamReader(sql)) { ImportBulk(reader); } diff --git a/MCGalaxy/Server/ZipWriter.cs b/MCGalaxy/Server/ZipWriter.cs index 95e3c68ad..346e6581a 100644 --- a/MCGalaxy/Server/ZipWriter.cs +++ b/MCGalaxy/Server/ZipWriter.cs @@ -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; @@ -93,7 +104,7 @@ namespace MCGalaxy { } } } - + public sealed class ZipWriter { BinaryWriter w; Stream stream; @@ -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++; }