diff --git a/src/itdelatrisu/opsu/Opsu.java b/src/itdelatrisu/opsu/Opsu.java index c01e411b..a6364016 100644 --- a/src/itdelatrisu/opsu/Opsu.java +++ b/src/itdelatrisu/opsu/Opsu.java @@ -40,9 +40,6 @@ import java.io.IOException; import java.io.PrintStream; import java.lang.reflect.Field; -import java.net.InetAddress; -import java.net.ServerSocket; -import java.net.UnknownHostException; import org.newdawn.slick.GameContainer; import org.newdawn.slick.Input; @@ -54,6 +51,8 @@ import org.newdawn.slick.util.FileSystemLocation; import org.newdawn.slick.util.Log; import org.newdawn.slick.util.ResourceLoader; +import org.sqlite.SQLiteErrorCode; +import org.sqlite.SQLiteException; /** * Main class. @@ -72,9 +71,6 @@ public class Opsu extends StateBasedGame { STATE_GAMERANKING = 6, STATE_DOWNLOADSMENU = 7; - /** Server socket for restricting the program to a single instance. */ - private static ServerSocket SERVER_SOCKET; - /** * Constructor. * @param name the program name @@ -119,26 +115,28 @@ public void uncaughtException(Thread t, Throwable e) { // parse configuration file Options.parseOptions(); - // only allow a single instance + // initialize databases try { - SERVER_SOCKET = new ServerSocket(Options.getPort(), 1, InetAddress.getLocalHost()); - } catch (UnknownHostException e) { - // shouldn't happen - } catch (IOException e) { - errorAndExit( - null, - String.format( - "%s could not be launched for one of these reasons:\n" + - "- An instance of %s is already running.\n" + - "- Another program is bound to port %d. " + - "You can change the port %s uses by editing the \"Port\" field in the configuration file.", - OpsuConstants.PROJECT_NAME, - OpsuConstants.PROJECT_NAME, - Options.getPort(), - OpsuConstants.PROJECT_NAME - ), - false - ); + DBController.init(); + } catch (SQLiteException e) { + // probably locked by another instance + if (e.getErrorCode() == SQLiteErrorCode.SQLITE_BUSY.code) { + Log.error(e); + errorAndExit( + null, + String.format( + "%s could not be launched for one of these reasons:\n" + + "- An instance of %s is already running.\n" + + "- A database is locked for another reason (unlikely). ", + OpsuConstants.PROJECT_NAME, + OpsuConstants.PROJECT_NAME + ), + false + ); + } else + errorAndExit(e, "The databases could not be initialized.", true); + } catch (Exception e) { + errorAndExit(e, "The databases could not be initialized.", true); } // load natives @@ -171,13 +169,6 @@ public void uncaughtException(Thread t, Throwable e) { // set the resource paths ResourceLoader.addResourceLocation(new FileSystemLocation(new File("./res/"))); - // initialize databases - try { - DBController.init(); - } catch (Exception e) { - errorAndExit(e, "The databases could not be initialized.", true); - } - // check if just updated if (args.length >= 2) Updater.get().setUpdateInfo(args[0], args[1]); @@ -277,15 +268,6 @@ public static void close() { // cancel all downloads DownloadList.get().cancelAllDownloads(); - - // close server socket - if (SERVER_SOCKET != null) { - try { - SERVER_SOCKET.close(); - } catch (IOException e) { - ErrorHandler.error("Failed to close server socket.", e, false); - } - } } /** diff --git a/src/itdelatrisu/opsu/db/BeatmapDB.java b/src/itdelatrisu/opsu/db/BeatmapDB.java index bfd5bb43..cb57d5c9 100644 --- a/src/itdelatrisu/opsu/db/BeatmapDB.java +++ b/src/itdelatrisu/opsu/db/BeatmapDB.java @@ -131,7 +131,7 @@ private BeatmapDB() {} /** * Initializes the database connection. */ - public static void init() { + public static void init() throws SQLException { // create a database connection connection = DBController.createConnection(Options.BEATMAP_DB.getPath()); if (connection == null) @@ -144,40 +144,32 @@ public static void init() { createDatabase(); // prepare sql statements (used below) - try { - updateSizeStmt = connection.prepareStatement("REPLACE INTO info (key, value) VALUES ('size', ?)"); - } catch (SQLException e) { - ErrorHandler.error("Failed to prepare beatmap statements.", e, true); - } + updateSizeStmt = connection.prepareStatement("REPLACE INTO info (key, value) VALUES ('size', ?)"); // retrieve the cache size getCacheSize(); // prepare sql statements (not used here) - try { - insertStmt = connection.prepareStatement( - "INSERT INTO beatmaps VALUES (" + - "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?," + - "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?," + - "?, ?, ?, ?, ?, ?, ?, ?, ?" + - ")" - ); - selectStmt = connection.prepareStatement("SELECT * FROM beatmaps WHERE dir = ? AND file = ?"); - deleteMapStmt = connection.prepareStatement("DELETE FROM beatmaps WHERE dir = ? AND file = ?"); - deleteGroupStmt = connection.prepareStatement("DELETE FROM beatmaps WHERE dir = ?"); - setStarsStmt = connection.prepareStatement("UPDATE beatmaps SET stars = ? WHERE dir = ? AND file = ?"); - updatePlayStatsStmt = connection.prepareStatement("UPDATE beatmaps SET playCount = ?, lastPlayed = ? WHERE dir = ? AND file = ?"); - setFavoriteStmt = connection.prepareStatement("UPDATE beatmaps SET favorite = ? WHERE dir = ? AND file = ?"); - setLocalOffsetStmt = connection.prepareStatement("UPDATE beatmaps SET localOffset = ? WHERE dir = ? AND file = ?"); - } catch (SQLException e) { - ErrorHandler.error("Failed to prepare beatmap statements.", e, true); - } + insertStmt = connection.prepareStatement( + "INSERT INTO beatmaps VALUES (" + + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?," + + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?," + + "?, ?, ?, ?, ?, ?, ?, ?, ?" + + ")" + ); + selectStmt = connection.prepareStatement("SELECT * FROM beatmaps WHERE dir = ? AND file = ?"); + deleteMapStmt = connection.prepareStatement("DELETE FROM beatmaps WHERE dir = ? AND file = ?"); + deleteGroupStmt = connection.prepareStatement("DELETE FROM beatmaps WHERE dir = ?"); + setStarsStmt = connection.prepareStatement("UPDATE beatmaps SET stars = ? WHERE dir = ? AND file = ?"); + updatePlayStatsStmt = connection.prepareStatement("UPDATE beatmaps SET playCount = ?, lastPlayed = ? WHERE dir = ? AND file = ?"); + setFavoriteStmt = connection.prepareStatement("UPDATE beatmaps SET favorite = ? WHERE dir = ? AND file = ?"); + setLocalOffsetStmt = connection.prepareStatement("UPDATE beatmaps SET localOffset = ? WHERE dir = ? AND file = ?"); } /** * Creates the database, if it does not exist. */ - private static void createDatabase() { + private static void createDatabase() throws SQLException { try (Statement stmt = connection.createStatement()) { String sql = "CREATE TABLE IF NOT EXISTS beatmaps (" + @@ -208,8 +200,6 @@ private static void createDatabase() { // set the version key, if empty sql = String.format("INSERT OR IGNORE INTO info(key, value) VALUES('version', '%s')", DATABASE_VERSION); stmt.executeUpdate(sql); - } catch (SQLException e) { - ErrorHandler.error("Could not create beatmap database.", e, true); } } @@ -217,7 +207,7 @@ private static void createDatabase() { * Applies any database updates by comparing the current version to the * stored version. Does nothing if tables have not been created. */ - private static void updateDatabase() { + private static void updateDatabase() throws SQLException { try (Statement stmt = connection.createStatement()) { int version = 0; @@ -260,8 +250,6 @@ private static void updateDatabase() { ps.executeUpdate(); ps.close(); } - } catch (SQLException e) { - ErrorHandler.error("Failed to update beatmap database.", e, true); } } @@ -305,7 +293,7 @@ public static void clearDatabase() { if (connection == null) return; - // drop the table, then recreate it + // drop the table try (Statement stmt = connection.createStatement()) { String sql = "DROP TABLE beatmaps"; stmt.executeUpdate(sql); @@ -314,7 +302,13 @@ public static void clearDatabase() { } catch (SQLException e) { ErrorHandler.error("Could not drop beatmap database.", e, true); } - createDatabase(); + + // recreate it + try { + createDatabase(); + } catch (SQLException e) { + ErrorHandler.error("Could not create beatmap database.", e, true); + } } /** diff --git a/src/itdelatrisu/opsu/db/DBController.java b/src/itdelatrisu/opsu/db/DBController.java index 1fa4a2bf..4f5a6bd2 100644 --- a/src/itdelatrisu/opsu/db/DBController.java +++ b/src/itdelatrisu/opsu/db/DBController.java @@ -34,7 +34,7 @@ private DBController() {} /** * Initializes all databases. */ - public static void init() { + public static void init() throws SQLException { // load the sqlite-JDBC driver using the current class loader try { Class.forName("org.sqlite.JDBC"); diff --git a/src/itdelatrisu/opsu/db/ScoreDB.java b/src/itdelatrisu/opsu/db/ScoreDB.java index 9fa2c1b4..f23f4d15 100644 --- a/src/itdelatrisu/opsu/db/ScoreDB.java +++ b/src/itdelatrisu/opsu/db/ScoreDB.java @@ -90,7 +90,7 @@ private ScoreDB() {} /** * Initializes the database connection. */ - public static void init() { + public static void init() throws SQLException { // create a database connection connection = DBController.createConnection(Options.SCORE_DB.getPath()); if (connection == null) @@ -103,45 +103,41 @@ public static void init() { createDatabase(); // prepare sql statements - try { - insertStmt = connection.prepareStatement( - // TODO: There will be problems if multiple replays have the same - // timestamp (e.g. when imported) due to timestamp being the primary key. - "INSERT OR IGNORE INTO scores VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" - ); - selectMapStmt = connection.prepareStatement( - "SELECT * FROM scores WHERE " + - "MID = ? AND title = ? AND artist = ? AND creator = ? AND version = ?" - ); - selectMapSetStmt = connection.prepareStatement( - "SELECT * FROM scores WHERE " + - "MSID = ? AND title = ? AND artist = ? AND creator = ? ORDER BY version DESC" - ); - deleteSongStmt = connection.prepareStatement( - "DELETE FROM scores WHERE " + - "MID = ? AND title = ? AND artist = ? AND creator = ? AND version = ?" - ); - deleteScoreStmt = connection.prepareStatement( - "DELETE FROM scores WHERE " + - "timestamp = ? AND MID = ? AND MSID = ? AND title = ? AND artist = ? AND " + - "creator = ? AND version = ? AND hit300 = ? AND hit100 = ? AND hit50 = ? AND " + - "geki = ? AND katu = ? AND miss = ? AND score = ? AND combo = ? AND perfect = ? AND mods = ? AND " + - "(replay = ? OR (replay IS NULL AND ? IS NULL)) AND " + - "(playerName = ? OR (playerName IS NULL AND ? IS NULL))" - // TODO: extra playerName checks not needed if name is guaranteed not null - ); - setCurrentUserStmt = connection.prepareStatement("INSERT OR REPLACE INTO info VALUES ('user', ?)"); - insertUserStmt = connection.prepareStatement("INSERT OR REPLACE INTO users VALUES (?, ?, ?, ?, ?, ?)"); - deleteUserStmt = connection.prepareStatement("DELETE FROM users WHERE name = ?"); - } catch (SQLException e) { - ErrorHandler.error("Failed to prepare score statements.", e, true); - } + insertStmt = connection.prepareStatement( + // TODO: There will be problems if multiple replays have the same + // timestamp (e.g. when imported) due to timestamp being the primary key. + "INSERT OR IGNORE INTO scores VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + ); + selectMapStmt = connection.prepareStatement( + "SELECT * FROM scores WHERE " + + "MID = ? AND title = ? AND artist = ? AND creator = ? AND version = ?" + ); + selectMapSetStmt = connection.prepareStatement( + "SELECT * FROM scores WHERE " + + "MSID = ? AND title = ? AND artist = ? AND creator = ? ORDER BY version DESC" + ); + deleteSongStmt = connection.prepareStatement( + "DELETE FROM scores WHERE " + + "MID = ? AND title = ? AND artist = ? AND creator = ? AND version = ?" + ); + deleteScoreStmt = connection.prepareStatement( + "DELETE FROM scores WHERE " + + "timestamp = ? AND MID = ? AND MSID = ? AND title = ? AND artist = ? AND " + + "creator = ? AND version = ? AND hit300 = ? AND hit100 = ? AND hit50 = ? AND " + + "geki = ? AND katu = ? AND miss = ? AND score = ? AND combo = ? AND perfect = ? AND mods = ? AND " + + "(replay = ? OR (replay IS NULL AND ? IS NULL)) AND " + + "(playerName = ? OR (playerName IS NULL AND ? IS NULL))" + // TODO: extra playerName checks not needed if name is guaranteed not null + ); + setCurrentUserStmt = connection.prepareStatement("INSERT OR REPLACE INTO info VALUES ('user', ?)"); + insertUserStmt = connection.prepareStatement("INSERT OR REPLACE INTO users VALUES (?, ?, ?, ?, ?, ?)"); + deleteUserStmt = connection.prepareStatement("DELETE FROM users WHERE name = ?"); } /** * Creates the database, if it does not exist. */ - private static void createDatabase() { + private static void createDatabase() throws SQLException { try (Statement stmt = connection.createStatement()) { String sql = "CREATE TABLE IF NOT EXISTS scores (" + @@ -172,8 +168,6 @@ private static void createDatabase() { // set the version key, if empty sql = String.format("INSERT OR IGNORE INTO info(key, value) VALUES('version', %d)", DATABASE_VERSION); stmt.executeUpdate(sql); - } catch (SQLException e) { - ErrorHandler.error("Could not create score database.", e, true); } } @@ -181,7 +175,7 @@ private static void createDatabase() { * Applies any database updates by comparing the current version to the * stored version. Does nothing if tables have not been created. */ - private static void updateDatabase() { + private static void updateDatabase() throws SQLException { try (Statement stmt = connection.createStatement()) { int version = 0; @@ -224,8 +218,6 @@ private static void updateDatabase() { ps.executeUpdate(); ps.close(); } - } catch (SQLException e) { - ErrorHandler.error("Failed to update score database.", e, true); } } diff --git a/src/itdelatrisu/opsu/options/Options.java b/src/itdelatrisu/opsu/options/Options.java index 9f644419..010d5709 100644 --- a/src/itdelatrisu/opsu/options/Options.java +++ b/src/itdelatrisu/opsu/options/Options.java @@ -138,9 +138,6 @@ public class Options { /** The custom FFmpeg location (or null for the default). */ private static File FFmpegPath; - /** Port binding. */ - private static int port = 49250; - /** The theme song string: {@code filename,title,artist,length(ms)} */ private static String themeString = "theme.mp3,Rainbows,Kevin MacLeod,219350"; @@ -303,17 +300,6 @@ public void read(String s) { } } }, - PORT ("Port") { - @Override - public String write() { return Integer.toString(port); } - - @Override - public void read(String s) { - int i = Integer.parseInt(s); - if (i > 0 && i <= 65535) - port = i; - } - }, // in-game options SCREEN_RESOLUTION ("Resolution", "ScreenResolution", "") { @@ -1114,12 +1100,6 @@ public static void setDisplayMode(Container app) { */ public static boolean isComboBurstEnabled() { return GameOption.SHOW_COMBO_BURSTS.getBooleanValue(); } - /** - * Returns the port number to bind to. - * @return the port - */ - public static int getPort() { return port; } - /** * Returns the cursor scale. * @return the scale [0.5, 2]