From 104b6ebde8a7b83312669c7ff69fa220a9fb3749 Mon Sep 17 00:00:00 2001 From: Steven Robert Berdak Date: Mon, 4 Dec 2017 16:29:20 -0800 Subject: [PATCH] A different implementation of a zip based file management system. Changes to the way save.json files are handled. They are now wrapped in a zip and deleted at certain intervals based on config.properties values. --- .../java/net/simon987/server/FileUtils.java | 192 ++++++++++++++++++ .../java/net/simon987/server/GameServer.java | 38 ++-- .../main/java/net/simon987/server/Main.java | 12 -- .../java/net/simon987/server/ZipUtils.java | 91 --------- config.properties | 6 +- 5 files changed, 214 insertions(+), 125 deletions(-) create mode 100644 Server/src/main/java/net/simon987/server/FileUtils.java delete mode 100644 Server/src/main/java/net/simon987/server/ZipUtils.java diff --git a/Server/src/main/java/net/simon987/server/FileUtils.java b/Server/src/main/java/net/simon987/server/FileUtils.java new file mode 100644 index 0000000..f00684a --- /dev/null +++ b/Server/src/main/java/net/simon987/server/FileUtils.java @@ -0,0 +1,192 @@ +package net.simon987.server; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +public class FileUtils { + + private static final int BUFFER_SIZE = 1024; + private static final String STR_ENCODING = "UTF-8"; + private static final String DATE_FORMAT = "yyyyMMddHHmmss"; + private static final String FILE_TYPE = ".zip"; + private static final Path ROOT_DIR; + private static final String DIR_NAME = "history"; + public static final Path DIR_PATH; + + static { + ROOT_DIR = Paths.get(".").normalize(); + DIR_PATH = ROOT_DIR.resolve(DIR_NAME); + } + + //Private constructor + private FileUtils() { + + } + + /** + * Creates a new stamp containing the current date and time + * + * @return date and time stamp + */ + private static String getDateTimeStamp() { + Date millisToDate = new Date(System.currentTimeMillis()); + SimpleDateFormat f = new SimpleDateFormat(DATE_FORMAT); + return f.format(millisToDate); + } + + /** + * Created a directory if none exists with the specified name + * + * @param name folder to create + * @return true is the file exists or create operation is successful + */ + public static boolean prepDirectory(Path directory) { + File file = directory.toFile(); + + //If the directory exists or the directory created successfully return true + if(file.exists() || file.mkdir()) { + return true; + + } else { + System.out.println("Error creating directory: " + file.toString()); + return false; + } + } + + /** + * Converts a file into an array of bytes + * + * @param fileName the file to be converted into bytes + * @return the byte array of the given file + */ + public static byte[] bytifyFile(Path path) { + byte[] bytes = null; + + try { + bytes = Files.readAllBytes(path); + + } catch (IOException e) { + System.out.println("Failed to extract bytes from: " + path); + e.printStackTrace(); + } + + return bytes; + } + + /** + * Takes in a file that had been converted to a byte[] to be written to a new + * zip file + * + * @param payload + * contains data in byte array form to be written, typically a file + * that has been converted with bytifyFile() + * @throws IOException + * if an error occurs during the write process + */ + public static void writeSaveToZip(String name, byte[] data) throws IOException { + + String newFile = DIR_PATH.resolve(getDateTimeStamp() + FILE_TYPE).toString(); + FileOutputStream output = new FileOutputStream(newFile); + ZipOutputStream stream = new ZipOutputStream(output); + byte[] buffer = new byte[BUFFER_SIZE]; + ByteArrayInputStream bais = new ByteArrayInputStream(buffer); + + while ((bais.read(buffer)) > -1) { + // File name + ZipEntry entry = new ZipEntry(name); + // Set to start of next entry in the stream. + stream.putNextEntry(entry); + // Data to write. + stream.write(data); + // Close the current entry. + stream.closeEntry(); + } + + stream.close(); + output.close(); + } + + public static void cleanHistory(int size) { + + + File[] files = new File(DIR_PATH.toString()).listFiles(); + File[] sorted = new File[size]; + + File nextSortedFile = null; + File currentFile = null; + boolean changed = false; + + for(int i = 0; i < files.length / 2; i++) { + currentFile = files[i]; + files[i] = files[files.length - i - 1]; + files[files.length - i - 1] = currentFile; + } + + currentFile = null; + + for(int f = 0; f < files.length; f++) { + changed = false; + long dirFile = Long.parseLong(files[f].getName().substring(0, (files[f].getName().length() -4))); + + if(f < size && sorted[f] == null) { + sorted[f] = files[f]; + + } else { + + for(int s = 0; s < sorted.length; s++) { + + long sortedFile = Long.parseLong(sorted[s].getName().substring(0, (sorted[s].getName().length() -4))); + + if(dirFile > sortedFile) { + + if(s == sorted.length - 1) { + sorted[s] = files[f]; + + } else if(nextSortedFile == null) { + nextSortedFile = sorted[s]; + sorted[s] = files[f]; + + } else { + currentFile = sorted[s]; + sorted[s] = nextSortedFile; + nextSortedFile = currentFile; + } + + nextSortedFile = null; + currentFile = null; + changed = true; + } + } + + if(changed == false) { + files[f].delete(); + } + + } + } + + } + + /** + * Converts a byte array into human readable format using the provided encoding + * + * @param bytes + * data to be encoded to String + * @return a String containing the encoded bytes + */ + public static String byteArrAsString(byte[] bytes) throws UnsupportedEncodingException { + return new String(bytes, STR_ENCODING); + } +} diff --git a/Server/src/main/java/net/simon987/server/GameServer.java b/Server/src/main/java/net/simon987/server/GameServer.java index 698346f..6fd31bd 100644 --- a/Server/src/main/java/net/simon987/server/GameServer.java +++ b/Server/src/main/java/net/simon987/server/GameServer.java @@ -16,11 +16,13 @@ import org.json.simple.JSONObject; import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.nio.file.Paths; import java.util.ArrayList; public class GameServer implements Runnable { public final static GameServer INSTANCE = new GameServer(); + private static final String SAVE_JSON = "save.json"; private GameUniverse gameUniverse; private GameEventDispatcher eventDispatcher; @@ -32,10 +34,6 @@ public class GameServer implements Runnable { private int maxExecutionTime; - public ArrayList saveArchive; - - public int maxArchiveSize; - public GameServer() { this.config = new ServerConfiguration(new File("config.properties")); @@ -64,10 +62,6 @@ public class GameServer implements Runnable { } eventDispatcher = new GameEventDispatcher(pluginManager); - - saveArchive = new ArrayList(); - - maxArchiveSize = config.getInt("max_archive_size"); } public GameUniverse getGameUniverse() { @@ -149,9 +143,14 @@ public class GameServer implements Runnable { // Save if (gameUniverse.getTime() % config.getInt("save_interval") == 0) { - save(new File("save.json")); + save(new File(SAVE_JSON)); } - + + // Clean up history files + if(gameUniverse.getTime() % config.getInt("clean_interval") == 0) { + FileUtils.cleanHistory(config.getInt("history_size")); + } + socketServer.tick(); LogManager.LOGGER.info("Processed " + gameUniverse.getWorlds().size() + " worlds"); @@ -164,11 +163,16 @@ public class GameServer implements Runnable { * JSON file to save */ public void save(File file) { - - if (new File(new File("save.json").getAbsolutePath()).exists()) { - saveArchive.add(ZipUtils.bytifyFile("save.json")); - while(saveArchive.size() > maxArchiveSize) { - saveArchive.remove(0); + + boolean dirExists = FileUtils.prepDirectory(FileUtils.DIR_PATH); + + if (new File(new File(SAVE_JSON).getAbsolutePath()).exists() && dirExists) { + byte[] data = FileUtils.bytifyFile(new File(SAVE_JSON).toPath()); + try { + FileUtils.writeSaveToZip(SAVE_JSON, data); + } catch (IOException e) { + System.out.println("Failed to write " + SAVE_JSON + " to zip file"); + e.printStackTrace(); } } @@ -207,8 +211,4 @@ public class GameServer implements Runnable { public void setSocketServer(SocketServer socketServer) { this.socketServer = socketServer; } - - public ArrayList getSaveArchive() { - return this.saveArchive; - } } diff --git a/Server/src/main/java/net/simon987/server/Main.java b/Server/src/main/java/net/simon987/server/Main.java index 6821f32..834e478 100644 --- a/Server/src/main/java/net/simon987/server/Main.java +++ b/Server/src/main/java/net/simon987/server/Main.java @@ -11,18 +11,6 @@ import java.net.InetSocketAddress; public class Main { public static void main(String[] args) { - //Writes all of the files stored in GameServer.saveArray to a zip file. - Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { - public void run() { - try { - ZipUtils.writeSavesToZip(GameServer.INSTANCE.getSaveArchive()); - } catch (IOException e) { - System.out.println("Error writing saves to zip"); - e.printStackTrace(); - } - } - }, "Shutdown-thread")); - LogManager.initialize(); ServerConfiguration config = new ServerConfiguration(new File("config.properties")); diff --git a/Server/src/main/java/net/simon987/server/ZipUtils.java b/Server/src/main/java/net/simon987/server/ZipUtils.java deleted file mode 100644 index 906219e..0000000 --- a/Server/src/main/java/net/simon987/server/ZipUtils.java +++ /dev/null @@ -1,91 +0,0 @@ -package net.simon987.server; - -import java.io.ByteArrayInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -import net.simon987.server.logging.LogManager; - -public class ZipUtils { - - private static final int BUFFER_SIZE = 1024; - - public static byte[] bytifyFile(String fileName) { - - Path path = Paths.get(fileName); - byte[] bytes = null; - - try { - bytes = Files.readAllBytes(path); - - } catch (IOException e) { - System.out.println("Failed to extract bytes from: " + fileName); - e.printStackTrace(); - } - - return bytes; - } - - public static String getByteArrAsString(byte[] bytes) throws UnsupportedEncodingException { - return new String(bytes, "UTF-8"); - } - - public static void writeSavesToZip(ArrayList array) throws IOException { - - int writeCount = 0; - FileOutputStream output = new FileOutputStream("archive_" + getDateTimeStamp() + ".zip"); - ZipOutputStream stream = new ZipOutputStream(output); - byte[] buffer = new byte[BUFFER_SIZE]; - ByteArrayInputStream bais = new ByteArrayInputStream(buffer); - - while ((bais.read(buffer)) > -1) { - for (int i = 0; i < array.size(); i++) { - - ZipEntry entry = new ZipEntry("save_" + getTickTime(array.get(i)) + ".json"); - stream.putNextEntry(entry); - stream.write(array.get(i)); - stream.closeEntry(); - writeCount++; - } - } - - stream.close(); - output.close(); - - LogManager.LOGGER.info(writeCount + " saves moved to zip file archive"); - } - - private static String getTickTime(byte[] bytes) throws UnsupportedEncodingException { - - Pattern pattern = Pattern.compile("\"time\""); - String stringedBytes = getByteArrAsString(bytes); - Matcher matcher = pattern.matcher(stringedBytes); - int startIndex = 0; - - while (matcher.find()) { - startIndex = matcher.end() + 1; - } - - int endIndex = stringedBytes.indexOf(",", startIndex); - - return stringedBytes.substring(startIndex, endIndex); - } - - private static String getDateTimeStamp() { - Date millisToDate = new Date(System.currentTimeMillis()); - SimpleDateFormat f = new SimpleDateFormat("yyyyMMddHHmmss"); - return f.format(millisToDate); - } - -} diff --git a/config.properties b/config.properties index 0764880..7d6ef31 100644 --- a/config.properties +++ b/config.properties @@ -5,6 +5,9 @@ mysql_pass=mar # MySQL address mysql_url=jdbc:mysql://localhost:3306/mar?useSSL=false save_interval=10 +# File management +clean_interval=10 +history_size=10 # Web server port webSocket_port=8887 webSocket_host=0.0.0.0 @@ -63,6 +66,3 @@ wg_maxCopperCount=2 user_timeout=500 # Free CPU execution time in ms user_free_execution_time=2 -# ---------------------------------------------- -# Max saves to archive when the server is shutdown -max_archive_size=10 \ No newline at end of file