Save current progress
This commit is contained in:
parent
669cd9e789
commit
d4740d71e7
13 changed files with 631 additions and 31 deletions
|
@ -68,7 +68,12 @@ repositories {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
provided "dev.zontreck:EventsBus:${Bus_API}.${Bus_Patch}"
|
provided "dev.zontreck:EventsBus:${Bus_API}.${Bus_Patch}"
|
||||||
|
|
||||||
provided "dev.zontreck:LibAC:${LibAC_API}.${LibAC_Patch}"
|
provided "dev.zontreck:LibAC:${LibAC_API}.${LibAC_Patch}"
|
||||||
|
|
||||||
|
provided "org.mariadb.jdbc:mariadb-java-client:${MariaDB_JDBC_Version}"
|
||||||
|
provided "org.slf4j:log4j-over-slf4j:2.0.7"
|
||||||
|
provided "org.slf4j:slf4j-simple:2.0.7"
|
||||||
}
|
}
|
||||||
|
|
||||||
def MAVEN_PASSWORD_PROPERTY = "AriasCreationsMavenPassword"
|
def MAVEN_PASSWORD_PROPERTY = "AriasCreationsMavenPassword"
|
||||||
|
@ -117,7 +122,8 @@ task jarjar(type: Jar) {
|
||||||
manifest {
|
manifest {
|
||||||
attributes (
|
attributes (
|
||||||
'Main-Class': application.mainClass,
|
'Main-Class': application.mainClass,
|
||||||
'Multi-Release': 'true'
|
'Multi-Release': 'true',
|
||||||
|
'Implementation-Version': version
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
archiveClassifier = "AIO"
|
archiveClassifier = "AIO"
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
apiVer=1.0
|
apiVer=1.0
|
||||||
Bus_API=1.0
|
Bus_API=1.0
|
||||||
Bus_Patch=33
|
Bus_Patch=45
|
||||||
|
|
||||||
|
|
||||||
LibAC_API=1.4
|
LibAC_API=1.4
|
||||||
LibAC_Patch=35
|
LibAC_Patch=46
|
||||||
|
|
||||||
|
MariaDB_JDBC_Version=3.3.2
|
||||||
|
|
||||||
org.gradle.daemon=false
|
org.gradle.daemon=false
|
|
@ -11,9 +11,14 @@ import dev.zontreck.ariaslib.terminal.Banners;
|
||||||
import dev.zontreck.ariaslib.util.FileIO;
|
import dev.zontreck.ariaslib.util.FileIO;
|
||||||
import dev.zontreck.ariaslib.util.Hashing;
|
import dev.zontreck.ariaslib.util.Hashing;
|
||||||
import dev.zontreck.eventsbus.Bus;
|
import dev.zontreck.eventsbus.Bus;
|
||||||
|
import dev.zontreck.eventsbus.EventDispatcher;
|
||||||
import dev.zontreck.playsync.data.DataAccountant;
|
import dev.zontreck.playsync.data.DataAccountant;
|
||||||
import dev.zontreck.playsync.data.DataFragment;
|
import dev.zontreck.playsync.data.DataFragment;
|
||||||
import dev.zontreck.playsync.data.Manifest;
|
import dev.zontreck.playsync.data.Manifest;
|
||||||
|
import dev.zontreck.playsync.data.database.DatabaseConnection;
|
||||||
|
import dev.zontreck.playsync.data.database.Migrations;
|
||||||
|
import dev.zontreck.playsync.data.database.migrations.ChunksTable;
|
||||||
|
import dev.zontreck.playsync.data.database.migrations.ManifestsTable;
|
||||||
import dev.zontreck.playsync.events.EventHandlers;
|
import dev.zontreck.playsync.events.EventHandlers;
|
||||||
import dev.zontreck.playsync.exceptions.UnsupportedAlgorithmException;
|
import dev.zontreck.playsync.exceptions.UnsupportedAlgorithmException;
|
||||||
import dev.zontreck.playsync.exceptions.UnsupportedIDException;
|
import dev.zontreck.playsync.exceptions.UnsupportedIDException;
|
||||||
|
@ -22,6 +27,8 @@ import dev.zontreck.playsync.games.nintendo.ID6;
|
||||||
import dev.zontreck.playsync.server.HTTPServer;
|
import dev.zontreck.playsync.server.HTTPServer;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -36,12 +43,26 @@ public class PlaySyncServer {
|
||||||
DEFAULT_ARGS.setArg(new IntegerArgument("port", 1588));
|
DEFAULT_ARGS.setArg(new IntegerArgument("port", 1588));
|
||||||
DEFAULT_ARGS.setArg(new StringArgument("dataDir", "data"));
|
DEFAULT_ARGS.setArg(new StringArgument("dataDir", "data"));
|
||||||
DEFAULT_ARGS.setArg(new StringArgument("hash", "MD5"));
|
DEFAULT_ARGS.setArg(new StringArgument("hash", "MD5"));
|
||||||
|
DEFAULT_ARGS.setArg(new IntegerArgument("db_port", 3306));
|
||||||
|
DEFAULT_ARGS.setArg(new StringArgument("db_host", "localhost"));
|
||||||
|
DEFAULT_ARGS.setArg(new StringArgument("db_name", "PlaySync"));
|
||||||
|
DEFAULT_ARGS.setArg(new StringArgument("db_user", "NSET\r"));
|
||||||
|
DEFAULT_ARGS.setArg(new StringArgument("db_pass", "NSET\r"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Settings.PORT_NUMBER = (int) DEFAULT_ARGS.getArg("port").getValue();
|
public static void printUsage()
|
||||||
Settings.DATA_DIRECTORY = (String)DEFAULT_ARGS.getArg("dataDir").getValue();
|
{
|
||||||
Settings.HASH_ALGORITHM = (String)DEFAULT_ARGS.getArg("hash").getValue();
|
log("Usage: java -jar PlaySyncServer.jar --db_user=<value> --db_pass=<value> [options]");
|
||||||
|
log("");
|
||||||
|
log("--port=<value>\t\t\tDefault: 1588\n",
|
||||||
|
"--dataDir=<value>\t\tDefault: data\n",
|
||||||
|
"--hash=<value>\t\t\tDefault: MD5, (valid: MD5, SHA256)\n",
|
||||||
|
"--db_port=<value>\t\tDefault: 3306\n",
|
||||||
|
"--db_host=<value>\t\tDefault: localhost\n",
|
||||||
|
"--db_user=<value>\t\t(REQUIRED)\n",
|
||||||
|
"--db_name=<value>\t\tDefault: PlaySync\n",
|
||||||
|
"--db_pass=<value>\t\t(REQUIRED)\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void log(String... args)
|
private static void log(String... args)
|
||||||
|
@ -74,19 +95,26 @@ public class PlaySyncServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
log(Banners.generateBanner("PlaySync Server"));
|
log(Banners.generateBanner("Harbinger CDN Server"));
|
||||||
|
log("Version: "+PlaySyncServer.class.getPackage().getImplementationVersion() + "\n");
|
||||||
|
|
||||||
Arguments active = ArgumentsParser.parseArguments(args, DEFAULT_ARGS);
|
Arguments active = ArgumentsParser.parseArguments(args, DEFAULT_ARGS);
|
||||||
|
|
||||||
IntegerArgument port = (IntegerArgument) active.getArg("port");
|
IntegerArgument port = (IntegerArgument) active.getArg("port");
|
||||||
StringArgument dataDir = (StringArgument) active.getArg("dataDir");
|
StringArgument dataDir = (StringArgument) active.getArg("dataDir");
|
||||||
StringArgument hashing = (StringArgument) active.getArg("hash");
|
StringArgument hashing = (StringArgument) active.getArg("hash");
|
||||||
|
IntegerArgument dbPort = (IntegerArgument) active.getArg("db_port");
|
||||||
log("Registering EventHandlers");
|
StringArgument dbHost = (StringArgument) active.getArg("db_host");
|
||||||
Bus.Register(EventHandlers.class, new EventHandlers());
|
StringArgument dbName = (StringArgument) active.getArg("db_name");
|
||||||
log("EventHandlers have been registered");
|
StringArgument dbUser = (StringArgument) active.getArg("db_user");
|
||||||
|
StringArgument dbPass = (StringArgument) active.getArg("db_pass");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
log("Parsing command line arguments...");
|
||||||
|
|
||||||
if(active.hasArg("port"))
|
if(active.hasArg("port"))
|
||||||
Settings.PORT_NUMBER = port.getValue();
|
Settings.PORT_NUMBER = port.getValue();
|
||||||
|
|
||||||
|
@ -96,10 +124,72 @@ public class PlaySyncServer {
|
||||||
if(active.hasArg("hash"))
|
if(active.hasArg("hash"))
|
||||||
Settings.HASH_ALGORITHM = hashing.getValue();
|
Settings.HASH_ALGORITHM = hashing.getValue();
|
||||||
|
|
||||||
|
if(active.hasArg("db_port"))
|
||||||
|
Settings.DB_PORT = dbPort.getValue();
|
||||||
|
|
||||||
|
if(active.hasArg("db_host"))
|
||||||
|
Settings.DB_HOST = dbHost.getValue();
|
||||||
|
|
||||||
|
if(active.hasArg("db_name"))
|
||||||
|
Settings.DB_NAME = dbName.getValue();
|
||||||
|
|
||||||
|
if(active.hasArg("db_user"))
|
||||||
|
{
|
||||||
|
if(dbUser.getValue().equalsIgnoreCase("NSET\r"))
|
||||||
|
{
|
||||||
|
printUsage();
|
||||||
|
|
||||||
|
log("Argument: --db_user is required");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings.DB_USERNAME = dbUser.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(active.hasArg("db_pass"))
|
||||||
|
{
|
||||||
|
if(dbPass.getValue().equalsIgnoreCase("NSET\r"))
|
||||||
|
{
|
||||||
|
log("Argument: --db_pass is required if the password is not blank");
|
||||||
|
Settings.DB_PASSWORD = "";
|
||||||
|
}else
|
||||||
|
Settings.DB_PASSWORD = dbPass.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
log("Finished parsing command line options");
|
||||||
|
|
||||||
|
|
||||||
|
log("Starting the EventDispatcher");
|
||||||
|
EventDispatcher.Reset();
|
||||||
|
EventDispatcher.Register(EventHandlers.class);
|
||||||
|
log("EventDispatcher has been started.");
|
||||||
|
|
||||||
|
|
||||||
log("Port number set to: ", String.valueOf(Settings.PORT_NUMBER));
|
log("Port number set to: ", String.valueOf(Settings.PORT_NUMBER));
|
||||||
log("Data will be loaded/stored in: ", Settings.DATA_DIRECTORY);
|
|
||||||
log("Hashing algorithm set to: ", Settings.HASH_ALGORITHM);
|
log("Hashing algorithm set to: ", Settings.HASH_ALGORITHM);
|
||||||
|
|
||||||
|
log("Connecting to database...");
|
||||||
|
Connection conn = null;
|
||||||
|
try {
|
||||||
|
conn = DatabaseConnection.getConnection();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
log("\n\n");
|
||||||
|
log(Banners.generateBanner("Invalid Credentials or DB Settings"));
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
log("Connection established");
|
||||||
|
|
||||||
|
log("Performing database migrations...");
|
||||||
|
try {
|
||||||
|
Migrations.doMigration(conn);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
log(Banners.generateBanner("Critical Failure when creating migrations table"));
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
File data = Settings.getDataFolder();
|
File data = Settings.getDataFolder();
|
||||||
if(!data.exists())
|
if(!data.exists())
|
||||||
{
|
{
|
||||||
|
@ -125,6 +215,7 @@ public class PlaySyncServer {
|
||||||
*/
|
*/
|
||||||
mf.FileHash = Hashing.md5(fullFile);
|
mf.FileHash = Hashing.md5(fullFile);
|
||||||
mf.GamePlatform = Platform.Nintendo;
|
mf.GamePlatform = Platform.Nintendo;
|
||||||
|
mf.GameName = "Animal Crossing";
|
||||||
|
|
||||||
DataAccountant.DataTotal = fullFile.length;
|
DataAccountant.DataTotal = fullFile.length;
|
||||||
|
|
||||||
|
@ -145,7 +236,7 @@ public class PlaySyncServer {
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
DataFragment frag = DataFragment.Create(dis);
|
DataFragment frag = DataFragment.Create(dis);
|
||||||
if(frag.getSize()<1024)
|
if(frag.getSize()<512)
|
||||||
{
|
{
|
||||||
hasData=false;
|
hasData=false;
|
||||||
}
|
}
|
||||||
|
@ -182,7 +273,11 @@ public class PlaySyncServer {
|
||||||
|
|
||||||
for(DataFragment frag : fragments)
|
for(DataFragment frag : fragments)
|
||||||
{
|
{
|
||||||
frag.Save();
|
ChunksTable chunksTable = (ChunksTable)Migrations.Tables.get(ChunksTable.class);
|
||||||
|
|
||||||
|
chunksTable.setChunk(frag);
|
||||||
|
|
||||||
|
|
||||||
DataAccountant.ChunksWritten++;
|
DataAccountant.ChunksWritten++;
|
||||||
DataAccountant.ChunksRemain--;
|
DataAccountant.ChunksRemain--;
|
||||||
DataAccountant.updateCur(DataAccountant.ChunksWritten);
|
DataAccountant.updateCur(DataAccountant.ChunksWritten);
|
||||||
|
|
|
@ -9,6 +9,14 @@ public class Settings
|
||||||
public static String DATA_DIRECTORY;
|
public static String DATA_DIRECTORY;
|
||||||
public static String HASH_ALGORITHM;
|
public static String HASH_ALGORITHM;
|
||||||
|
|
||||||
|
public static int DB_PORT;
|
||||||
|
public static String DB_HOST;
|
||||||
|
public static String DB_USERNAME;
|
||||||
|
public static String DB_PASSWORD;
|
||||||
|
public static String DB_NAME;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static final boolean DEBUG_MODE = true;
|
public static final boolean DEBUG_MODE = true;
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,8 @@ package dev.zontreck.playsync.data;
|
||||||
|
|
||||||
import dev.zontreck.ariaslib.util.Hashing;
|
import dev.zontreck.ariaslib.util.Hashing;
|
||||||
import dev.zontreck.playsync.Settings;
|
import dev.zontreck.playsync.Settings;
|
||||||
|
import dev.zontreck.playsync.data.database.Migrations;
|
||||||
|
import dev.zontreck.playsync.data.database.migrations.ChunksTable;
|
||||||
import dev.zontreck.playsync.exceptions.UnsupportedAlgorithmException;
|
import dev.zontreck.playsync.exceptions.UnsupportedAlgorithmException;
|
||||||
|
|
||||||
import javax.xml.crypto.Data;
|
import javax.xml.crypto.Data;
|
||||||
|
@ -13,7 +15,7 @@ import java.nio.file.Path;
|
||||||
*/
|
*/
|
||||||
public class DataFragment
|
public class DataFragment
|
||||||
{
|
{
|
||||||
private byte[] data = new byte[1024];
|
private byte[] data = new byte[512];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes either 1024, or the remaining number of bytes, whichever is less
|
* Takes either 1024, or the remaining number of bytes, whichever is less
|
||||||
|
@ -22,7 +24,7 @@ public class DataFragment
|
||||||
*/
|
*/
|
||||||
public static DataFragment Create(DataInputStream dis) throws IOException {
|
public static DataFragment Create(DataInputStream dis) throws IOException {
|
||||||
DataFragment frag = new DataFragment();
|
DataFragment frag = new DataFragment();
|
||||||
frag.data = dis.readNBytes(1024);
|
frag.data = dis.readNBytes(512);
|
||||||
|
|
||||||
return frag;
|
return frag;
|
||||||
}
|
}
|
||||||
|
@ -30,32 +32,50 @@ public class DataFragment
|
||||||
/**
|
/**
|
||||||
* Save the data fragment to its hash ID
|
* Save the data fragment to its hash ID
|
||||||
*/
|
*/
|
||||||
public void Save()
|
public byte[] Save()
|
||||||
{
|
{
|
||||||
Path chunks = Settings.getDataChunksFolder();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dos = new DataOutputStream(baos);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Path chunk = chunks.resolve(getHash() + ".bin");
|
if(((ChunksTable)Migrations.Tables.get(ChunksTable.class)).hasChunk(getHash()))
|
||||||
if(!chunks.toFile().exists()) chunks.toFile().mkdirs();
|
{
|
||||||
|
|
||||||
//DataAccountant.DataWritten += data.length;
|
|
||||||
if(chunk.toFile().exists()) {
|
|
||||||
DataAccountant.DataSkipped += data.length;
|
DataAccountant.DataSkipped += data.length;
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DataOutputStream dos = new DataOutputStream(new FileOutputStream(chunk.toFile()));
|
|
||||||
|
|
||||||
dos.writeInt(getSize());
|
dos.writeInt(getSize());
|
||||||
dos.write(data);
|
dos.write(data);
|
||||||
dos.close();
|
dos.close();
|
||||||
} catch (UnsupportedAlgorithmException e) {
|
} catch (UnsupportedAlgorithmException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return baos.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read from a byte array
|
||||||
|
* @param array Array to load as a data fragment
|
||||||
|
* @return The fragment of data
|
||||||
|
*/
|
||||||
|
public static DataFragment Read(byte[] array)
|
||||||
|
{
|
||||||
|
ByteArrayInputStream bais = new ByteArrayInputStream(array);
|
||||||
|
DataInputStream dis = new DataInputStream(bais);
|
||||||
|
|
||||||
|
DataFragment fragment = new DataFragment();
|
||||||
|
|
||||||
|
try {
|
||||||
|
int count = dis.readInt();
|
||||||
|
fragment.data = dis.readNBytes(count);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -19,6 +19,7 @@ public class Manifest
|
||||||
public List<String> Fragments = new ArrayList<>();
|
public List<String> Fragments = new ArrayList<>();
|
||||||
public String FileHash = "";
|
public String FileHash = "";
|
||||||
public ID6 GameID;
|
public ID6 GameID;
|
||||||
|
public String GameName;
|
||||||
public Platform GamePlatform = Platform.Unknown;
|
public Platform GamePlatform = Platform.Unknown;
|
||||||
private static final ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock();
|
private static final ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock();
|
||||||
|
|
||||||
|
@ -56,6 +57,7 @@ public class Manifest
|
||||||
DataOutputStream dos = new DataOutputStream(new FileOutputStream(manifest.toFile()));
|
DataOutputStream dos = new DataOutputStream(new FileOutputStream(manifest.toFile()));
|
||||||
dos.write(GameID.asBytes());
|
dos.write(GameID.asBytes());
|
||||||
dos.writeByte(GamePlatform.ordinal());
|
dos.writeByte(GamePlatform.ordinal());
|
||||||
|
dos.writeUTF(GameName);
|
||||||
|
|
||||||
dos.writeInt(Fragments.size());
|
dos.writeInt(Fragments.size());
|
||||||
|
|
||||||
|
@ -91,6 +93,7 @@ public class Manifest
|
||||||
|
|
||||||
ret.GameID = ID6.fromBytes(dis.readNBytes(6));
|
ret.GameID = ID6.fromBytes(dis.readNBytes(6));
|
||||||
ret.GamePlatform = Platform.fromByte(dis.readByte());
|
ret.GamePlatform = Platform.fromByte(dis.readByte());
|
||||||
|
ret.GameName = dis.readUTF();
|
||||||
ret.FileHash = hash;
|
ret.FileHash = hash;
|
||||||
int count = dis.readInt();
|
int count = dis.readInt();
|
||||||
for(int i=0;i<count;i++)
|
for(int i=0;i<count;i++)
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
package dev.zontreck.playsync.data.database;
|
||||||
|
|
||||||
|
import dev.zontreck.playsync.Settings;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public class DatabaseConnection {
|
||||||
|
private static Connection connection = null;
|
||||||
|
|
||||||
|
public static Connection getConnection() throws SQLException {
|
||||||
|
if (connection == null || connection.isClosed()) {
|
||||||
|
try {
|
||||||
|
// Load MariaDB JDBC driver
|
||||||
|
Class.forName("org.mariadb.jdbc.Driver");
|
||||||
|
|
||||||
|
// Establish connection
|
||||||
|
connection = DriverManager.getConnection(
|
||||||
|
"jdbc:mariadb://" + Settings.DB_HOST + ":" + Settings.DB_PORT + "/" + Settings.DB_NAME,
|
||||||
|
Settings.DB_USERNAME, Settings.DB_PASSWORD
|
||||||
|
);
|
||||||
|
} catch (ClassNotFoundException | SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new SQLException("Failed to connect to database.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void closeConnection() throws SQLException {
|
||||||
|
if (connection != null && !connection.isClosed()) {
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,176 @@
|
||||||
|
package dev.zontreck.playsync.data.database;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import dev.zontreck.eventsbus.Bus;
|
||||||
|
import dev.zontreck.eventsbus.EventDispatcher;
|
||||||
|
import dev.zontreck.playsync.server.events.MigrationEvent;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class Migrations {
|
||||||
|
private static final String MIGRATIONS_TABLE_NAME = "migrations";
|
||||||
|
public static Map<Class<?>,Migration> Tables = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
public static void createMigrationsTable(Connection connection) throws SQLException {
|
||||||
|
String createTableQuery = "CREATE TABLE IF NOT EXISTS " + MIGRATIONS_TABLE_NAME + " ("
|
||||||
|
+ "table_name VARCHAR(255) PRIMARY KEY,"
|
||||||
|
+ "version INT NOT NULL"
|
||||||
|
+ ")";
|
||||||
|
|
||||||
|
try (PreparedStatement statement = connection.prepareStatement(createTableQuery)) {
|
||||||
|
statement.executeUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getTableVersion(Connection connection, String tableName) throws SQLException {
|
||||||
|
String selectQuery = "SELECT version FROM " + MIGRATIONS_TABLE_NAME + " WHERE table_name = ?";
|
||||||
|
|
||||||
|
try (PreparedStatement statement = connection.prepareStatement(selectQuery)) {
|
||||||
|
statement.setString(1, tableName);
|
||||||
|
try (ResultSet resultSet = statement.executeQuery()) {
|
||||||
|
if (resultSet.next()) {
|
||||||
|
return resultSet.getInt("version");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Default version if not found
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the table version in the migrations table
|
||||||
|
* @param connection The DB connection to use
|
||||||
|
* @param tableName The table name to update
|
||||||
|
* @param version The new version to store
|
||||||
|
* @throws SQLException
|
||||||
|
*/
|
||||||
|
public static void setTableVersion(Connection connection, String tableName, int version) throws SQLException {
|
||||||
|
String updateQuery = "INSERT INTO " + MIGRATIONS_TABLE_NAME + " (table_name, version) VALUES (?, ?)"
|
||||||
|
+ " ON DUPLICATE KEY UPDATE version = ?";
|
||||||
|
|
||||||
|
try (PreparedStatement statement = connection.prepareStatement(updateQuery)) {
|
||||||
|
statement.setString(1, tableName);
|
||||||
|
statement.setInt(2, version);
|
||||||
|
statement.setInt(3, version);
|
||||||
|
statement.executeUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void doMigration(Connection connection) throws SQLException {
|
||||||
|
int ver = 0;
|
||||||
|
try {
|
||||||
|
ver = getTableVersion(connection, MIGRATIONS_TABLE_NAME);
|
||||||
|
|
||||||
|
}catch(SQLException e)
|
||||||
|
{
|
||||||
|
createMigrationsTable(connection);
|
||||||
|
setTableVersion(connection, MIGRATIONS_TABLE_NAME, 1);
|
||||||
|
|
||||||
|
ver = getTableVersion(connection, MIGRATIONS_TABLE_NAME);
|
||||||
|
}
|
||||||
|
if(ver == 0)
|
||||||
|
{
|
||||||
|
createMigrationsTable(connection);
|
||||||
|
|
||||||
|
setTableVersion(connection, MIGRATIONS_TABLE_NAME, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(ver)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
break; // nothing to do here yet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EventDispatcher.Post(new MigrationEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class defining the functions that will need to be used by any table implementing migrations.
|
||||||
|
*/
|
||||||
|
public static abstract class Migration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the name of the table associated with this migration.
|
||||||
|
*
|
||||||
|
* @return The name of the table.
|
||||||
|
*/
|
||||||
|
public abstract String getTableName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the table associated with this migration if it doesn't exist.
|
||||||
|
*
|
||||||
|
* @param connection The database connection.
|
||||||
|
* @throws SQLException if a database access error occurs or this method is called on a closed connection.
|
||||||
|
*/
|
||||||
|
public abstract void createTable(Connection connection) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current version of the table from the migrations table.
|
||||||
|
*
|
||||||
|
* @return The current version of the table.
|
||||||
|
* @throws SQLException if a database access error occurs or this method is called on a closed connection.
|
||||||
|
*/
|
||||||
|
public int getVersion() throws SQLException {
|
||||||
|
return Migrations.getTableVersion(DatabaseConnection.getConnection(), getTableName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the version of the table in the migrations table.
|
||||||
|
*
|
||||||
|
* @param version The version to set.
|
||||||
|
* @throws SQLException if a database access error occurs or this method is called on a closed connection.
|
||||||
|
*/
|
||||||
|
public void setVersion(int version) throws SQLException {
|
||||||
|
Migrations.setTableVersion(DatabaseConnection.getConnection(), getTableName(), version);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is used for the internal doMigrations function. It should return the current highest migration version for the table
|
||||||
|
* @return Current highest version
|
||||||
|
*/
|
||||||
|
public abstract int getCurrentTableVersion();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used internally.
|
||||||
|
* <br/><br/>
|
||||||
|
* This function executes migrations to bring the table to the current version.
|
||||||
|
* @throws SQLException
|
||||||
|
*/
|
||||||
|
public void doMigration() throws SQLException
|
||||||
|
{
|
||||||
|
int ver = getVersion();
|
||||||
|
Connection conn = DatabaseConnection.getConnection();
|
||||||
|
|
||||||
|
if(ver==0) createTable(conn);
|
||||||
|
|
||||||
|
while(ver < getCurrentTableVersion())
|
||||||
|
{
|
||||||
|
migrate(ver+1);
|
||||||
|
|
||||||
|
System.out.println("Migrate Table [" + getTableName() +"] from version " + ver + " to " + (ver+1));
|
||||||
|
ver = getVersion();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a migration specific to the table itself
|
||||||
|
* @param version
|
||||||
|
* @throws SQLException
|
||||||
|
*/
|
||||||
|
public abstract void migrate(int version) throws SQLException;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
package dev.zontreck.playsync.data.database.migrations;
|
||||||
|
|
||||||
|
import dev.zontreck.playsync.data.DataFragment;
|
||||||
|
import dev.zontreck.playsync.data.database.DatabaseConnection;
|
||||||
|
import dev.zontreck.playsync.data.database.Migrations;
|
||||||
|
import dev.zontreck.playsync.exceptions.UnsupportedAlgorithmException;
|
||||||
|
|
||||||
|
import javax.sql.RowSet;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public class ChunksTable extends Migrations.Migration {
|
||||||
|
@Override
|
||||||
|
public String getTableName() {
|
||||||
|
return "chunks";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createTable(Connection connection) throws SQLException {
|
||||||
|
String sql = "CREATE TABLE `chunks` (" +
|
||||||
|
" `Hash` varchar(255) NOT NULL," +
|
||||||
|
" `Data` blob NOT NULL," +
|
||||||
|
" PRIMARY KEY (`Hash`)," +
|
||||||
|
" UNIQUE KEY `Hash` (`Hash`)" +
|
||||||
|
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci";
|
||||||
|
|
||||||
|
try (PreparedStatement pstat = connection.prepareStatement(sql))
|
||||||
|
{
|
||||||
|
pstat.executeUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
setVersion(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentTableVersion() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void migrate(int version) throws SQLException {
|
||||||
|
Connection conn = DatabaseConnection.getConnection();
|
||||||
|
|
||||||
|
switch(version)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query the database to check if the Chunk exists or not
|
||||||
|
* @param hash The chunk's hash value
|
||||||
|
* @return True if the chunk is present in the database
|
||||||
|
*/
|
||||||
|
public boolean hasChunk(String hash)
|
||||||
|
{
|
||||||
|
String sql = "SELECT * FROM `?`" +
|
||||||
|
" WHERE Hash='?';";
|
||||||
|
|
||||||
|
try(PreparedStatement pstat = DatabaseConnection.getConnection().prepareStatement(sql))
|
||||||
|
{
|
||||||
|
pstat.setString(1, getTableName());
|
||||||
|
pstat.setString(2, hash);
|
||||||
|
|
||||||
|
ResultSet result = pstat.executeQuery();
|
||||||
|
if(result.getRow() == 0 && !result.next())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the chunk if it exists.
|
||||||
|
* <br/>
|
||||||
|
* If the chunk does not exist, returns null, otherwise, never null.
|
||||||
|
* @param hash The hash to check for
|
||||||
|
* @see ChunksTable#hasChunk(String)
|
||||||
|
* @return The deserialied DataFragment
|
||||||
|
*/
|
||||||
|
public DataFragment getChunk(String hash)
|
||||||
|
{
|
||||||
|
if(!hasChunk(hash)) return null;
|
||||||
|
String sql = "SELECT Data from `?`" +
|
||||||
|
" WHERE Hash='?';";
|
||||||
|
|
||||||
|
try (PreparedStatement pstat = DatabaseConnection.getConnection().prepareStatement(sql))
|
||||||
|
{
|
||||||
|
pstat.setString(1, getTableName());
|
||||||
|
pstat.setString(2, hash);
|
||||||
|
|
||||||
|
ResultSet result = pstat.executeQuery();
|
||||||
|
if(result.getRow()==0) result.next();
|
||||||
|
byte[] blob = result.getBytes("Data");
|
||||||
|
|
||||||
|
return DataFragment.Read(blob);
|
||||||
|
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert the data
|
||||||
|
* @param fragment The fragment to insert
|
||||||
|
*/
|
||||||
|
public void setChunk(DataFragment fragment)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
String hash = fragment.getHash();
|
||||||
|
byte[] array = fragment.Save();
|
||||||
|
|
||||||
|
String sql = "INSERT INTO `?` (Hash, Data) VALUES (" +
|
||||||
|
"'?', ?" +
|
||||||
|
");";
|
||||||
|
|
||||||
|
try(PreparedStatement pstat = DatabaseConnection.getConnection().prepareStatement(sql))
|
||||||
|
{
|
||||||
|
pstat.setString(1, getTableName());
|
||||||
|
pstat.setString(2, hash);
|
||||||
|
pstat.setBytes(3, array);
|
||||||
|
|
||||||
|
pstat.executeUpdate();
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (UnsupportedAlgorithmException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package dev.zontreck.playsync.data.database.migrations;
|
||||||
|
|
||||||
|
import dev.zontreck.playsync.data.database.DatabaseConnection;
|
||||||
|
import dev.zontreck.playsync.data.database.Migrations;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public class ManifestsTable extends Migrations.Migration
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public String getTableName() {
|
||||||
|
return "manifests";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createTable(Connection connection) throws SQLException {
|
||||||
|
String sql = "CREATE TABLE `" + getTableName() + "` (" +
|
||||||
|
" `Name` varchar(255) NOT NULL," +
|
||||||
|
" `Data` blob NOT NULL," +
|
||||||
|
" PRIMARY KEY (`Name`)," +
|
||||||
|
" UNIQUE KEY `Name` (`Name`)" +
|
||||||
|
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;";
|
||||||
|
|
||||||
|
try (PreparedStatement pstat = connection.prepareStatement(sql))
|
||||||
|
{
|
||||||
|
|
||||||
|
pstat.executeUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
setVersion(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentTableVersion() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void migrate(int version) throws SQLException {
|
||||||
|
Connection conn = DatabaseConnection.getConnection();
|
||||||
|
switch(version)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
{
|
||||||
|
String sql = "ALTER TABLE `" + getTableName() + "` " +
|
||||||
|
"CHANGE `Name` `Hash` VARCHAR(255) " +
|
||||||
|
"CHARACTER SET utf8mb4 " +
|
||||||
|
"COLLATE utf8mb4_general_ci NOT NULL;";
|
||||||
|
|
||||||
|
try(PreparedStatement pstat = conn.prepareStatement(sql))
|
||||||
|
{
|
||||||
|
pstat.executeUpdate();
|
||||||
|
}
|
||||||
|
setVersion(2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
{
|
||||||
|
String sql = "ALTER TABLE `manifests` ADD `GameTitle` " +
|
||||||
|
"VARCHAR(255) NOT NULL AFTER `Hash`; ";
|
||||||
|
|
||||||
|
try(PreparedStatement pstat = conn.prepareStatement(sql))
|
||||||
|
{
|
||||||
|
pstat.executeUpdate();
|
||||||
|
}
|
||||||
|
setVersion(3);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,34 @@
|
||||||
package dev.zontreck.playsync.events;
|
package dev.zontreck.playsync.events;
|
||||||
|
|
||||||
|
import dev.zontreck.ariaslib.util.Maps;
|
||||||
|
import dev.zontreck.eventsbus.annotations.EventSubscriber;
|
||||||
|
import dev.zontreck.eventsbus.annotations.SingleshotEvent;
|
||||||
|
import dev.zontreck.eventsbus.annotations.Subscribe;
|
||||||
|
import dev.zontreck.playsync.data.database.Migrations;
|
||||||
|
import dev.zontreck.playsync.data.database.migrations.ChunksTable;
|
||||||
|
import dev.zontreck.playsync.data.database.migrations.ManifestsTable;
|
||||||
|
import dev.zontreck.playsync.server.events.MigrationEvent;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
@EventSubscriber
|
||||||
public class EventHandlers
|
public class EventHandlers
|
||||||
{
|
{
|
||||||
|
@Subscribe(allowCancelled = false)
|
||||||
|
@SingleshotEvent
|
||||||
|
public static void onMigrations(MigrationEvent event)
|
||||||
|
{
|
||||||
|
System.out.println("Performing table migrations...");
|
||||||
|
ManifestsTable manifests = new ManifestsTable();
|
||||||
|
ChunksTable chunks = new ChunksTable();
|
||||||
|
try {
|
||||||
|
manifests.doMigration();
|
||||||
|
chunks.doMigration();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Migrations.Tables = Maps.of(new Maps.Entry<>(ManifestsTable.class, manifests), new Maps.Entry<>(ChunksTable.class, chunks));
|
||||||
|
System.out.println("Table migrations completed...");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
package dev.zontreck.playsync.server.events;
|
package dev.zontreck.playsync.server.events;
|
||||||
|
|
||||||
import dev.zontreck.ariaslib.util.Lists;
|
|
||||||
import dev.zontreck.eventsbus.Cancellable;
|
|
||||||
import dev.zontreck.eventsbus.Event;
|
import dev.zontreck.eventsbus.Event;
|
||||||
|
import dev.zontreck.eventsbus.annotations.Cancellable;
|
||||||
import dev.zontreck.playsync.exceptions.HTTPResponseLockedException;
|
import dev.zontreck.playsync.exceptions.HTTPResponseLockedException;
|
||||||
import dev.zontreck.playsync.server.HTTPResponseData;
|
import dev.zontreck.playsync.server.HTTPResponseData;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package dev.zontreck.playsync.server.events;
|
||||||
|
|
||||||
|
import dev.zontreck.eventsbus.Event;
|
||||||
|
|
||||||
|
public class MigrationEvent extends Event
|
||||||
|
{
|
||||||
|
}
|
Loading…
Reference in a new issue