diff options
| author | 2023-03-08 15:38:16 -0500 | |
|---|---|---|
| committer | 2023-06-03 00:05:38 -0700 | |
| commit | 4ce86a526c038ddea0694b299338ec1e2699d38b (patch) | |
| tree | b2d95f1dd9ed301423e71b51d8d87446c6c8ae78 /src/android | |
| parent | android: Convert Game to Kotlin (diff) | |
| download | yuzu-4ce86a526c038ddea0694b299338ec1e2699d38b.tar.gz yuzu-4ce86a526c038ddea0694b299338ec1e2699d38b.tar.xz yuzu-4ce86a526c038ddea0694b299338ec1e2699d38b.zip | |
android: Convert GameDatabase to Kotlin
Diffstat (limited to 'src/android')
| -rw-r--r-- | src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.java | 275 | ||||
| -rw-r--r-- | src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.kt | 260 |
2 files changed, 260 insertions, 275 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.java deleted file mode 100644 index a10ac6ff2..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.java +++ /dev/null | |||
| @@ -1,275 +0,0 @@ | |||
| 1 | package org.yuzu.yuzu_emu.model; | ||
| 2 | |||
| 3 | import android.content.ContentValues; | ||
| 4 | import android.content.Context; | ||
| 5 | import android.database.Cursor; | ||
| 6 | import android.database.sqlite.SQLiteDatabase; | ||
| 7 | import android.database.sqlite.SQLiteOpenHelper; | ||
| 8 | import android.net.Uri; | ||
| 9 | |||
| 10 | import org.yuzu.yuzu_emu.NativeLibrary; | ||
| 11 | import org.yuzu.yuzu_emu.utils.FileUtil; | ||
| 12 | import org.yuzu.yuzu_emu.utils.Log; | ||
| 13 | |||
| 14 | import java.io.File; | ||
| 15 | import java.util.Arrays; | ||
| 16 | import java.util.HashSet; | ||
| 17 | import java.util.Set; | ||
| 18 | |||
| 19 | import rx.Observable; | ||
| 20 | |||
| 21 | /** | ||
| 22 | * A helper class that provides several utilities simplifying interaction with | ||
| 23 | * the SQLite database. | ||
| 24 | */ | ||
| 25 | public final class GameDatabase extends SQLiteOpenHelper { | ||
| 26 | public static final int COLUMN_DB_ID = 0; | ||
| 27 | public static final int GAME_COLUMN_PATH = 1; | ||
| 28 | public static final int GAME_COLUMN_TITLE = 2; | ||
| 29 | public static final int GAME_COLUMN_DESCRIPTION = 3; | ||
| 30 | public static final int GAME_COLUMN_REGIONS = 4; | ||
| 31 | public static final int GAME_COLUMN_GAME_ID = 5; | ||
| 32 | public static final int GAME_COLUMN_CAPTION = 6; | ||
| 33 | public static final int FOLDER_COLUMN_PATH = 1; | ||
| 34 | public static final String KEY_DB_ID = "_id"; | ||
| 35 | public static final String KEY_GAME_PATH = "path"; | ||
| 36 | public static final String KEY_GAME_TITLE = "title"; | ||
| 37 | public static final String KEY_GAME_DESCRIPTION = "description"; | ||
| 38 | public static final String KEY_GAME_REGIONS = "regions"; | ||
| 39 | public static final String KEY_GAME_ID = "game_id"; | ||
| 40 | public static final String KEY_GAME_COMPANY = "company"; | ||
| 41 | public static final String KEY_FOLDER_PATH = "path"; | ||
| 42 | public static final String TABLE_NAME_FOLDERS = "folders"; | ||
| 43 | public static final String TABLE_NAME_GAMES = "games"; | ||
| 44 | private static final int DB_VERSION = 2; | ||
| 45 | private static final String TYPE_PRIMARY = " INTEGER PRIMARY KEY"; | ||
| 46 | private static final String TYPE_INTEGER = " INTEGER"; | ||
| 47 | private static final String TYPE_STRING = " TEXT"; | ||
| 48 | |||
| 49 | private static final String CONSTRAINT_UNIQUE = " UNIQUE"; | ||
| 50 | |||
| 51 | private static final String SEPARATOR = ", "; | ||
| 52 | |||
| 53 | private static final String SQL_CREATE_GAMES = "CREATE TABLE " + TABLE_NAME_GAMES + "(" | ||
| 54 | + KEY_DB_ID + TYPE_PRIMARY + SEPARATOR | ||
| 55 | + KEY_GAME_PATH + TYPE_STRING + SEPARATOR | ||
| 56 | + KEY_GAME_TITLE + TYPE_STRING + SEPARATOR | ||
| 57 | + KEY_GAME_DESCRIPTION + TYPE_STRING + SEPARATOR | ||
| 58 | + KEY_GAME_REGIONS + TYPE_STRING + SEPARATOR | ||
| 59 | + KEY_GAME_ID + TYPE_STRING + SEPARATOR | ||
| 60 | + KEY_GAME_COMPANY + TYPE_STRING + ")"; | ||
| 61 | |||
| 62 | private static final String SQL_CREATE_FOLDERS = "CREATE TABLE " + TABLE_NAME_FOLDERS + "(" | ||
| 63 | + KEY_DB_ID + TYPE_PRIMARY + SEPARATOR | ||
| 64 | + KEY_FOLDER_PATH + TYPE_STRING + CONSTRAINT_UNIQUE + ")"; | ||
| 65 | |||
| 66 | private static final String SQL_DELETE_FOLDERS = "DROP TABLE IF EXISTS " + TABLE_NAME_FOLDERS; | ||
| 67 | private static final String SQL_DELETE_GAMES = "DROP TABLE IF EXISTS " + TABLE_NAME_GAMES; | ||
| 68 | private final Context context; | ||
| 69 | |||
| 70 | public GameDatabase(Context context) { | ||
| 71 | // Superclass constructor builds a database or uses an existing one. | ||
| 72 | super(context, "games.db", null, DB_VERSION); | ||
| 73 | this.context = context; | ||
| 74 | } | ||
| 75 | |||
| 76 | @Override | ||
| 77 | public void onCreate(SQLiteDatabase database) { | ||
| 78 | Log.debug("[GameDatabase] GameDatabase - Creating database..."); | ||
| 79 | |||
| 80 | execSqlAndLog(database, SQL_CREATE_GAMES); | ||
| 81 | execSqlAndLog(database, SQL_CREATE_FOLDERS); | ||
| 82 | } | ||
| 83 | |||
| 84 | @Override | ||
| 85 | public void onDowngrade(SQLiteDatabase database, int oldVersion, int newVersion) { | ||
| 86 | Log.verbose("[GameDatabase] Downgrades not supporting, clearing databases.."); | ||
| 87 | execSqlAndLog(database, SQL_DELETE_FOLDERS); | ||
| 88 | execSqlAndLog(database, SQL_CREATE_FOLDERS); | ||
| 89 | |||
| 90 | execSqlAndLog(database, SQL_DELETE_GAMES); | ||
| 91 | execSqlAndLog(database, SQL_CREATE_GAMES); | ||
| 92 | } | ||
| 93 | |||
| 94 | @Override | ||
| 95 | public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) { | ||
| 96 | Log.info("[GameDatabase] Upgrading database from schema version " + oldVersion + " to " + | ||
| 97 | newVersion); | ||
| 98 | |||
| 99 | // Delete all the games | ||
| 100 | execSqlAndLog(database, SQL_DELETE_GAMES); | ||
| 101 | execSqlAndLog(database, SQL_CREATE_GAMES); | ||
| 102 | } | ||
| 103 | |||
| 104 | public void resetDatabase(SQLiteDatabase database) { | ||
| 105 | execSqlAndLog(database, SQL_DELETE_FOLDERS); | ||
| 106 | execSqlAndLog(database, SQL_CREATE_FOLDERS); | ||
| 107 | |||
| 108 | execSqlAndLog(database, SQL_DELETE_GAMES); | ||
| 109 | execSqlAndLog(database, SQL_CREATE_GAMES); | ||
| 110 | } | ||
| 111 | |||
| 112 | public void scanLibrary(SQLiteDatabase database) { | ||
| 113 | // Before scanning known folders, go through the game table and remove any entries for which the file itself is missing. | ||
| 114 | Cursor fileCursor = database.query(TABLE_NAME_GAMES, | ||
| 115 | null, // Get all columns. | ||
| 116 | null, // Get all rows. | ||
| 117 | null, | ||
| 118 | null, // No grouping. | ||
| 119 | null, | ||
| 120 | null); // Order of games is irrelevant. | ||
| 121 | |||
| 122 | // Possibly overly defensive, but ensures that moveToNext() does not skip a row. | ||
| 123 | fileCursor.moveToPosition(-1); | ||
| 124 | |||
| 125 | while (fileCursor.moveToNext()) { | ||
| 126 | String gamePath = fileCursor.getString(GAME_COLUMN_PATH); | ||
| 127 | File game = new File(gamePath); | ||
| 128 | |||
| 129 | if (!game.exists()) { | ||
| 130 | database.delete(TABLE_NAME_GAMES, | ||
| 131 | KEY_DB_ID + " = ?", | ||
| 132 | new String[]{Long.toString(fileCursor.getLong(COLUMN_DB_ID))}); | ||
| 133 | } | ||
| 134 | } | ||
| 135 | |||
| 136 | // Get a cursor listing all the folders the user has added to the library. | ||
| 137 | Cursor folderCursor = database.query(TABLE_NAME_FOLDERS, | ||
| 138 | null, // Get all columns. | ||
| 139 | null, // Get all rows. | ||
| 140 | null, | ||
| 141 | null, // No grouping. | ||
| 142 | null, | ||
| 143 | null); // Order of folders is irrelevant. | ||
| 144 | |||
| 145 | Set<String> allowedExtensions = new HashSet<String>(Arrays.asList( | ||
| 146 | ".xci", ".nsp", ".nca", ".nro")); | ||
| 147 | |||
| 148 | // Possibly overly defensive, but ensures that moveToNext() does not skip a row. | ||
| 149 | folderCursor.moveToPosition(-1); | ||
| 150 | |||
| 151 | // Iterate through all results of the DB query (i.e. all folders in the library.) | ||
| 152 | while (folderCursor.moveToNext()) { | ||
| 153 | String folderPath = folderCursor.getString(FOLDER_COLUMN_PATH); | ||
| 154 | |||
| 155 | Uri folderUri = Uri.parse(folderPath); | ||
| 156 | // If the folder is empty because it no longer exists, remove it from the library. | ||
| 157 | if (FileUtil.listFiles(context, folderUri).length == 0) { | ||
| 158 | Log.error( | ||
| 159 | "[GameDatabase] Folder no longer exists. Removing from the library: " + folderPath); | ||
| 160 | database.delete(TABLE_NAME_FOLDERS, | ||
| 161 | KEY_DB_ID + " = ?", | ||
| 162 | new String[]{Long.toString(folderCursor.getLong(COLUMN_DB_ID))}); | ||
| 163 | } | ||
| 164 | |||
| 165 | this.addGamesRecursive(database, folderUri, allowedExtensions, 3); | ||
| 166 | } | ||
| 167 | |||
| 168 | fileCursor.close(); | ||
| 169 | folderCursor.close(); | ||
| 170 | |||
| 171 | database.close(); | ||
| 172 | } | ||
| 173 | |||
| 174 | private void addGamesRecursive(SQLiteDatabase database, Uri parent, Set<String> allowedExtensions, int depth) { | ||
| 175 | if (depth <= 0) { | ||
| 176 | return; | ||
| 177 | } | ||
| 178 | |||
| 179 | // Ensure keys are loaded so that ROM metadata can be decrypted. | ||
| 180 | NativeLibrary.ReloadKeys(); | ||
| 181 | |||
| 182 | MinimalDocumentFile[] children = FileUtil.listFiles(context, parent); | ||
| 183 | for (MinimalDocumentFile file : children) { | ||
| 184 | if (file.isDirectory()) { | ||
| 185 | Set<String> newExtensions = new HashSet<>(Arrays.asList( | ||
| 186 | ".xci", ".nsp", ".nca", ".nro")); | ||
| 187 | this.addGamesRecursive(database, file.getUri(), newExtensions, depth - 1); | ||
| 188 | } else { | ||
| 189 | String filename = file.getUri().toString(); | ||
| 190 | |||
| 191 | int extensionStart = filename.lastIndexOf('.'); | ||
| 192 | if (extensionStart > 0) { | ||
| 193 | String fileExtension = filename.substring(extensionStart); | ||
| 194 | |||
| 195 | // Check that the file has an extension we care about before trying to read out of it. | ||
| 196 | if (allowedExtensions.contains(fileExtension.toLowerCase())) { | ||
| 197 | attemptToAddGame(database, filename); | ||
| 198 | } | ||
| 199 | } | ||
| 200 | } | ||
| 201 | } | ||
| 202 | } | ||
| 203 | |||
| 204 | private static void attemptToAddGame(SQLiteDatabase database, String filePath) { | ||
| 205 | String name = NativeLibrary.GetTitle(filePath); | ||
| 206 | |||
| 207 | // If the game's title field is empty, use the filename. | ||
| 208 | if (name.isEmpty()) { | ||
| 209 | name = filePath.substring(filePath.lastIndexOf("/") + 1); | ||
| 210 | } | ||
| 211 | |||
| 212 | String gameId = NativeLibrary.GetGameId(filePath); | ||
| 213 | |||
| 214 | // If the game's ID field is empty, use the filename without extension. | ||
| 215 | if (gameId.isEmpty()) { | ||
| 216 | gameId = filePath.substring(filePath.lastIndexOf("/") + 1, | ||
| 217 | filePath.lastIndexOf(".")); | ||
| 218 | } | ||
| 219 | |||
| 220 | ContentValues game = Game.asContentValues(name, | ||
| 221 | NativeLibrary.GetDescription(filePath).replace("\n", " "), | ||
| 222 | NativeLibrary.GetRegions(filePath), | ||
| 223 | filePath, | ||
| 224 | gameId, | ||
| 225 | NativeLibrary.GetCompany(filePath)); | ||
| 226 | |||
| 227 | // Try to update an existing game first. | ||
| 228 | int rowsMatched = database.update(TABLE_NAME_GAMES, // Which table to update. | ||
| 229 | game, | ||
| 230 | // The values to fill the row with. | ||
| 231 | KEY_GAME_ID + " = ?", | ||
| 232 | // The WHERE clause used to find the right row. | ||
| 233 | new String[]{game.getAsString( | ||
| 234 | KEY_GAME_ID)}); // The ? in WHERE clause is replaced with this, | ||
| 235 | // which is provided as an array because there | ||
| 236 | // could potentially be more than one argument. | ||
| 237 | |||
| 238 | // If update fails, insert a new game instead. | ||
| 239 | if (rowsMatched == 0) { | ||
| 240 | Log.verbose("[GameDatabase] Adding game: " + game.getAsString(KEY_GAME_TITLE)); | ||
| 241 | database.insert(TABLE_NAME_GAMES, null, game); | ||
| 242 | } else { | ||
| 243 | Log.verbose("[GameDatabase] Updated game: " + game.getAsString(KEY_GAME_TITLE)); | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | public Observable<Cursor> getGames() { | ||
| 248 | return Observable.create(subscriber -> | ||
| 249 | { | ||
| 250 | Log.info("[GameDatabase] Reading games list..."); | ||
| 251 | |||
| 252 | SQLiteDatabase database = getReadableDatabase(); | ||
| 253 | Cursor resultCursor = database.query( | ||
| 254 | TABLE_NAME_GAMES, | ||
| 255 | null, | ||
| 256 | null, | ||
| 257 | null, | ||
| 258 | null, | ||
| 259 | null, | ||
| 260 | KEY_GAME_TITLE + " ASC" | ||
| 261 | ); | ||
| 262 | |||
| 263 | // Pass the result cursor to the consumer. | ||
| 264 | subscriber.onNext(resultCursor); | ||
| 265 | |||
| 266 | // Tell the consumer we're done; it will unsubscribe implicitly. | ||
| 267 | subscriber.onCompleted(); | ||
| 268 | }); | ||
| 269 | } | ||
| 270 | |||
| 271 | private void execSqlAndLog(SQLiteDatabase database, String sql) { | ||
| 272 | Log.verbose("[GameDatabase] Executing SQL: " + sql); | ||
| 273 | database.execSQL(sql); | ||
| 274 | } | ||
| 275 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.kt new file mode 100644 index 000000000..52326ed0a --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.kt | |||
| @@ -0,0 +1,260 @@ | |||
| 1 | package org.yuzu.yuzu_emu.model | ||
| 2 | |||
| 3 | import android.content.Context | ||
| 4 | import android.database.Cursor | ||
| 5 | import android.database.sqlite.SQLiteDatabase | ||
| 6 | import android.database.sqlite.SQLiteOpenHelper | ||
| 7 | import android.net.Uri | ||
| 8 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 9 | import org.yuzu.yuzu_emu.utils.FileUtil | ||
| 10 | import org.yuzu.yuzu_emu.utils.Log | ||
| 11 | import rx.Observable | ||
| 12 | import rx.Subscriber | ||
| 13 | import java.io.File | ||
| 14 | import java.util.* | ||
| 15 | |||
| 16 | /** | ||
| 17 | * A helper class that provides several utilities simplifying interaction with | ||
| 18 | * the SQLite database. | ||
| 19 | */ | ||
| 20 | class GameDatabase(private val context: Context) : | ||
| 21 | SQLiteOpenHelper(context, "games.db", null, DB_VERSION) { | ||
| 22 | override fun onCreate(database: SQLiteDatabase) { | ||
| 23 | Log.debug("[GameDatabase] GameDatabase - Creating database...") | ||
| 24 | execSqlAndLog(database, SQL_CREATE_GAMES) | ||
| 25 | execSqlAndLog(database, SQL_CREATE_FOLDERS) | ||
| 26 | } | ||
| 27 | |||
| 28 | override fun onDowngrade(database: SQLiteDatabase, oldVersion: Int, newVersion: Int) { | ||
| 29 | Log.verbose("[GameDatabase] Downgrades not supporting, clearing databases..") | ||
| 30 | execSqlAndLog(database, SQL_DELETE_FOLDERS) | ||
| 31 | execSqlAndLog(database, SQL_CREATE_FOLDERS) | ||
| 32 | execSqlAndLog(database, SQL_DELETE_GAMES) | ||
| 33 | execSqlAndLog(database, SQL_CREATE_GAMES) | ||
| 34 | } | ||
| 35 | |||
| 36 | override fun onUpgrade(database: SQLiteDatabase, oldVersion: Int, newVersion: Int) { | ||
| 37 | Log.info( | ||
| 38 | "[GameDatabase] Upgrading database from schema version $oldVersion to $newVersion" | ||
| 39 | ) | ||
| 40 | |||
| 41 | // Delete all the games | ||
| 42 | execSqlAndLog(database, SQL_DELETE_GAMES) | ||
| 43 | execSqlAndLog(database, SQL_CREATE_GAMES) | ||
| 44 | } | ||
| 45 | |||
| 46 | fun resetDatabase(database: SQLiteDatabase) { | ||
| 47 | execSqlAndLog(database, SQL_DELETE_FOLDERS) | ||
| 48 | execSqlAndLog(database, SQL_CREATE_FOLDERS) | ||
| 49 | execSqlAndLog(database, SQL_DELETE_GAMES) | ||
| 50 | execSqlAndLog(database, SQL_CREATE_GAMES) | ||
| 51 | } | ||
| 52 | |||
| 53 | fun scanLibrary(database: SQLiteDatabase) { | ||
| 54 | // Before scanning known folders, go through the game table and remove any entries for which the file itself is missing. | ||
| 55 | val fileCursor = database.query( | ||
| 56 | TABLE_NAME_GAMES, | ||
| 57 | null, // Get all columns. | ||
| 58 | null, // Get all rows. | ||
| 59 | null, | ||
| 60 | null, // No grouping. | ||
| 61 | null, | ||
| 62 | null | ||
| 63 | ) // Order of games is irrelevant. | ||
| 64 | |||
| 65 | // Possibly overly defensive, but ensures that moveToNext() does not skip a row. | ||
| 66 | fileCursor.moveToPosition(-1) | ||
| 67 | while (fileCursor.moveToNext()) { | ||
| 68 | val gamePath = fileCursor.getString(GAME_COLUMN_PATH) | ||
| 69 | val game = File(gamePath) | ||
| 70 | if (!game.exists()) { | ||
| 71 | database.delete( | ||
| 72 | TABLE_NAME_GAMES, | ||
| 73 | "$KEY_DB_ID = ?", | ||
| 74 | arrayOf(fileCursor.getLong(COLUMN_DB_ID).toString()) | ||
| 75 | ) | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | // Get a cursor listing all the folders the user has added to the library. | ||
| 80 | val folderCursor = database.query( | ||
| 81 | TABLE_NAME_FOLDERS, | ||
| 82 | null, // Get all columns. | ||
| 83 | null, // Get all rows. | ||
| 84 | null, | ||
| 85 | null, // No grouping. | ||
| 86 | null, | ||
| 87 | null | ||
| 88 | ) // Order of folders is irrelevant. | ||
| 89 | |||
| 90 | |||
| 91 | // Possibly overly defensive, but ensures that moveToNext() does not skip a row. | ||
| 92 | folderCursor.moveToPosition(-1) | ||
| 93 | |||
| 94 | // Iterate through all results of the DB query (i.e. all folders in the library.) | ||
| 95 | while (folderCursor.moveToNext()) { | ||
| 96 | val folderPath = folderCursor.getString(FOLDER_COLUMN_PATH) | ||
| 97 | val folderUri = Uri.parse(folderPath) | ||
| 98 | // If the folder is empty because it no longer exists, remove it from the library. | ||
| 99 | if (FileUtil.listFiles(context, folderUri).isEmpty()) { | ||
| 100 | Log.error( | ||
| 101 | "[GameDatabase] Folder no longer exists. Removing from the library: $folderPath" | ||
| 102 | ) | ||
| 103 | database.delete( | ||
| 104 | TABLE_NAME_FOLDERS, | ||
| 105 | "$KEY_DB_ID = ?", | ||
| 106 | arrayOf(folderCursor.getLong(COLUMN_DB_ID).toString()) | ||
| 107 | ) | ||
| 108 | } | ||
| 109 | addGamesRecursive(database, folderUri, Game.extensions, 3) | ||
| 110 | } | ||
| 111 | fileCursor.close() | ||
| 112 | folderCursor.close() | ||
| 113 | database.close() | ||
| 114 | } | ||
| 115 | |||
| 116 | private fun addGamesRecursive( | ||
| 117 | database: SQLiteDatabase, | ||
| 118 | parent: Uri, | ||
| 119 | allowedExtensions: Set<String>, | ||
| 120 | depth: Int | ||
| 121 | ) { | ||
| 122 | if (depth <= 0) | ||
| 123 | return | ||
| 124 | |||
| 125 | // Ensure keys are loaded so that ROM metadata can be decrypted. | ||
| 126 | NativeLibrary.ReloadKeys() | ||
| 127 | val children = FileUtil.listFiles(context, parent) | ||
| 128 | for (file in children) { | ||
| 129 | if (file.isDirectory) { | ||
| 130 | addGamesRecursive(database, file.uri, Game.extensions, depth - 1) | ||
| 131 | } else { | ||
| 132 | val filename = file.uri.toString() | ||
| 133 | val extensionStart = filename.lastIndexOf('.') | ||
| 134 | if (extensionStart > 0) { | ||
| 135 | val fileExtension = filename.substring(extensionStart) | ||
| 136 | |||
| 137 | // Check that the file has an extension we care about before trying to read out of it. | ||
| 138 | if (allowedExtensions.contains(fileExtension.lowercase(Locale.getDefault()))) { | ||
| 139 | attemptToAddGame(database, filename) | ||
| 140 | } | ||
| 141 | } | ||
| 142 | } | ||
| 143 | } | ||
| 144 | } | ||
| 145 | // Pass the result cursor to the consumer. | ||
| 146 | |||
| 147 | // Tell the consumer we're done; it will unsubscribe implicitly. | ||
| 148 | val games: Observable<Cursor?> | ||
| 149 | get() = Observable.create { subscriber: Subscriber<in Cursor?> -> | ||
| 150 | Log.info("[GameDatabase] Reading games list...") | ||
| 151 | val database = readableDatabase | ||
| 152 | val resultCursor = database.query( | ||
| 153 | TABLE_NAME_GAMES, | ||
| 154 | null, | ||
| 155 | null, | ||
| 156 | null, | ||
| 157 | null, | ||
| 158 | null, | ||
| 159 | "$KEY_GAME_TITLE ASC" | ||
| 160 | ) | ||
| 161 | |||
| 162 | // Pass the result cursor to the consumer. | ||
| 163 | subscriber.onNext(resultCursor) | ||
| 164 | |||
| 165 | // Tell the consumer we're done; it will unsubscribe implicitly. | ||
| 166 | subscriber.onCompleted() | ||
| 167 | } | ||
| 168 | |||
| 169 | private fun execSqlAndLog(database: SQLiteDatabase, sql: String) { | ||
| 170 | Log.verbose("[GameDatabase] Executing SQL: $sql") | ||
| 171 | database.execSQL(sql) | ||
| 172 | } | ||
| 173 | |||
| 174 | companion object { | ||
| 175 | const val COLUMN_DB_ID = 0 | ||
| 176 | const val GAME_COLUMN_PATH = 1 | ||
| 177 | const val GAME_COLUMN_TITLE = 2 | ||
| 178 | const val GAME_COLUMN_DESCRIPTION = 3 | ||
| 179 | const val GAME_COLUMN_REGIONS = 4 | ||
| 180 | const val GAME_COLUMN_GAME_ID = 5 | ||
| 181 | const val GAME_COLUMN_CAPTION = 6 | ||
| 182 | const val FOLDER_COLUMN_PATH = 1 | ||
| 183 | const val KEY_DB_ID = "_id" | ||
| 184 | const val KEY_GAME_PATH = "path" | ||
| 185 | const val KEY_GAME_TITLE = "title" | ||
| 186 | const val KEY_GAME_DESCRIPTION = "description" | ||
| 187 | const val KEY_GAME_REGIONS = "regions" | ||
| 188 | const val KEY_GAME_ID = "game_id" | ||
| 189 | const val KEY_GAME_COMPANY = "company" | ||
| 190 | const val KEY_FOLDER_PATH = "path" | ||
| 191 | const val TABLE_NAME_FOLDERS = "folders" | ||
| 192 | const val TABLE_NAME_GAMES = "games" | ||
| 193 | private const val DB_VERSION = 2 | ||
| 194 | private const val TYPE_PRIMARY = " INTEGER PRIMARY KEY" | ||
| 195 | private const val TYPE_INTEGER = " INTEGER" | ||
| 196 | private const val TYPE_STRING = " TEXT" | ||
| 197 | private const val CONSTRAINT_UNIQUE = " UNIQUE" | ||
| 198 | private const val SEPARATOR = ", " | ||
| 199 | private const val SQL_CREATE_GAMES = ("CREATE TABLE " + TABLE_NAME_GAMES + "(" | ||
| 200 | + KEY_DB_ID + TYPE_PRIMARY + SEPARATOR | ||
| 201 | + KEY_GAME_PATH + TYPE_STRING + SEPARATOR | ||
| 202 | + KEY_GAME_TITLE + TYPE_STRING + SEPARATOR | ||
| 203 | + KEY_GAME_DESCRIPTION + TYPE_STRING + SEPARATOR | ||
| 204 | + KEY_GAME_REGIONS + TYPE_STRING + SEPARATOR | ||
| 205 | + KEY_GAME_ID + TYPE_STRING + SEPARATOR | ||
| 206 | + KEY_GAME_COMPANY + TYPE_STRING + ")") | ||
| 207 | private const val SQL_CREATE_FOLDERS = ("CREATE TABLE " + TABLE_NAME_FOLDERS + "(" | ||
| 208 | + KEY_DB_ID + TYPE_PRIMARY + SEPARATOR | ||
| 209 | + KEY_FOLDER_PATH + TYPE_STRING + CONSTRAINT_UNIQUE + ")") | ||
| 210 | private const val SQL_DELETE_FOLDERS = "DROP TABLE IF EXISTS $TABLE_NAME_FOLDERS" | ||
| 211 | private const val SQL_DELETE_GAMES = "DROP TABLE IF EXISTS $TABLE_NAME_GAMES" | ||
| 212 | private fun attemptToAddGame(database: SQLiteDatabase, filePath: String) { | ||
| 213 | var name = NativeLibrary.GetTitle(filePath) | ||
| 214 | |||
| 215 | // If the game's title field is empty, use the filename. | ||
| 216 | if (name.isEmpty()) { | ||
| 217 | name = filePath.substring(filePath.lastIndexOf("/") + 1) | ||
| 218 | } | ||
| 219 | var gameId = NativeLibrary.GetGameId(filePath) | ||
| 220 | |||
| 221 | // If the game's ID field is empty, use the filename without extension. | ||
| 222 | if (gameId.isEmpty()) { | ||
| 223 | gameId = filePath.substring( | ||
| 224 | filePath.lastIndexOf("/") + 1, | ||
| 225 | filePath.lastIndexOf(".") | ||
| 226 | ) | ||
| 227 | } | ||
| 228 | val game = Game.asContentValues( | ||
| 229 | name, | ||
| 230 | NativeLibrary.GetDescription(filePath).replace("\n", " "), | ||
| 231 | NativeLibrary.GetRegions(filePath), | ||
| 232 | filePath, | ||
| 233 | gameId, | ||
| 234 | NativeLibrary.GetCompany(filePath) | ||
| 235 | ) | ||
| 236 | |||
| 237 | // Try to update an existing game first. | ||
| 238 | val rowsMatched = database.update( | ||
| 239 | TABLE_NAME_GAMES, // Which table to update. | ||
| 240 | game, // The values to fill the row with. | ||
| 241 | "$KEY_GAME_ID = ?", arrayOf( | ||
| 242 | game.getAsString( | ||
| 243 | KEY_GAME_ID | ||
| 244 | ) | ||
| 245 | ) | ||
| 246 | ) | ||
| 247 | // The ? in WHERE clause is replaced with this, | ||
| 248 | // which is provided as an array because there | ||
| 249 | // could potentially be more than one argument. | ||
| 250 | |||
| 251 | // If update fails, insert a new game instead. | ||
| 252 | if (rowsMatched == 0) { | ||
| 253 | Log.verbose("[GameDatabase] Adding game: " + game.getAsString(KEY_GAME_TITLE)) | ||
| 254 | database.insert(TABLE_NAME_GAMES, null, game) | ||
| 255 | } else { | ||
| 256 | Log.verbose("[GameDatabase] Updated game: " + game.getAsString(KEY_GAME_TITLE)) | ||
| 257 | } | ||
| 258 | } | ||
| 259 | } | ||
| 260 | } | ||