Browse Source

Convert AppDatabase to kotlin

renovate/org.robolectric-robolectric-4.x
Ammar Githam 4 years ago
parent
commit
54ff196bb1
  1. 3
      app/build.gradle
  2. 292
      app/src/main/java/awais/instagrabber/db/AppDatabase.kt
  3. 6
      app/src/main/java/awais/instagrabber/db/Converters.kt

3
app/build.gradle

@ -1,6 +1,7 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: "androidx.navigation.safeargs" apply plugin: "androidx.navigation.safeargs"
apply plugin: 'kotlin-kapt'
apply from: 'sentry.gradle' apply from: 'sentry.gradle'
def getGitHash = { -> def getGitHash = { ->
@ -146,6 +147,7 @@ android {
// Error: Duplicate files during packaging of APK // Error: Duplicate files during packaging of APK
exclude 'META-INF/LICENSE.md' exclude 'META-INF/LICENSE.md'
exclude 'META-INF/LICENSE-notice.md' exclude 'META-INF/LICENSE-notice.md'
exclude 'META-INF/atomicfu.kotlin_module'
} }
testOptions.unitTests { testOptions.unitTests {
@ -196,6 +198,7 @@ dependencies {
implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-guava:$room_version" implementation "androidx.room:room-guava:$room_version"
implementation "androidx.room:room-ktx:$room_version" implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version" annotationProcessor "androidx.room:room-compiler:$room_version"
// CameraX // CameraX

292
app/src/main/java/awais/instagrabber/db/AppDatabase.kt

@ -1,97 +1,80 @@
package awais.instagrabber.db; package awais.instagrabber.db
import android.content.ContentValues
import android.content.ContentValues; import android.content.Context
import android.content.Context; import android.database.sqlite.SQLiteDatabase
import android.database.Cursor; import android.util.Log
import android.database.sqlite.SQLiteDatabase; import androidx.room.Database
import android.util.Log; import androidx.room.Room
import android.util.Pair; import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import androidx.annotation.NonNull; import androidx.room.migration.Migration
import androidx.room.Database; import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.room.Room; import awais.instagrabber.db.dao.AccountDao
import androidx.room.RoomDatabase; import awais.instagrabber.db.dao.DMLastNotifiedDao
import androidx.room.TypeConverters; import awais.instagrabber.db.dao.FavoriteDao
import androidx.room.migration.Migration; import awais.instagrabber.db.dao.RecentSearchDao
import androidx.sqlite.db.SupportSQLiteDatabase; import awais.instagrabber.db.entities.Account
import awais.instagrabber.db.entities.DMLastNotified
import java.time.Instant; import awais.instagrabber.db.entities.Favorite
import java.time.LocalDateTime; import awais.instagrabber.db.entities.RecentSearch
import java.time.ZoneId; import awais.instagrabber.utils.Utils
import java.util.ArrayList; import awais.instagrabber.utils.extensions.TAG
import java.util.List; import java.time.Instant
import java.time.LocalDateTime
import awais.instagrabber.db.dao.AccountDao; import java.time.ZoneId
import awais.instagrabber.db.dao.DMLastNotifiedDao; import java.util.*
import awais.instagrabber.db.dao.FavoriteDao; @Database(entities = [Account::class, Favorite::class, DMLastNotified::class, RecentSearch::class], version = 6)
import awais.instagrabber.db.dao.RecentSearchDao; @TypeConverters(Converters::class)
import awais.instagrabber.db.entities.Account; abstract class AppDatabase : RoomDatabase() {
import awais.instagrabber.db.entities.DMLastNotified; abstract fun accountDao(): AccountDao
import awais.instagrabber.db.entities.Favorite; abstract fun favoriteDao(): FavoriteDao
import awais.instagrabber.db.entities.RecentSearch; abstract fun dmLastNotifiedDao(): DMLastNotifiedDao
import awais.instagrabber.models.enums.FavoriteType; abstract fun recentSearchDao(): RecentSearchDao
import awais.instagrabber.utils.Utils; companion object {
private lateinit var INSTANCE: AppDatabase
@Database(entities = {Account.class, Favorite.class, DMLastNotified.class, RecentSearch.class}, fun getDatabase(context: Context): AppDatabase {
version = 6) if (!this::INSTANCE.isInitialized) {
@TypeConverters({Converters.class}) synchronized(AppDatabase::class.java) {
public abstract class AppDatabase extends RoomDatabase { if (!this::INSTANCE.isInitialized) {
private static final String TAG = AppDatabase.class.getSimpleName(); INSTANCE = Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "cookiebox.db")
private static AppDatabase INSTANCE;
public abstract AccountDao accountDao();
public abstract FavoriteDao favoriteDao();
public abstract DMLastNotifiedDao dmLastNotifiedDao();
public abstract RecentSearchDao recentSearchDao();
public static AppDatabase getDatabase(final Context context) {
if (INSTANCE == null) {
synchronized (AppDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "cookiebox.db")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6) .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6)
.build(); .build()
} }
} }
} }
return INSTANCE; return INSTANCE
} }
static final Migration MIGRATION_1_2 = new Migration(1, 2) { private val MIGRATION_1_2: Migration = object : Migration(1, 2) {
@Override override fun migrate(db: SupportSQLiteDatabase) {
public void migrate(@NonNull SupportSQLiteDatabase db) { db.execSQL("ALTER TABLE cookies ADD " + Account.COL_FULL_NAME + " TEXT")
db.execSQL("ALTER TABLE cookies ADD " + Account.COL_FULL_NAME + " TEXT"); db.execSQL("ALTER TABLE cookies ADD " + Account.COL_PROFILE_PIC + " TEXT")
db.execSQL("ALTER TABLE cookies ADD " + Account.COL_PROFILE_PIC + " TEXT");
} }
}; }
private val MIGRATION_2_3: Migration = object : Migration(2, 3) {
static final Migration MIGRATION_2_3 = new Migration(2, 3) { override fun migrate(db: SupportSQLiteDatabase) {
@Override val oldFavorites = backupOldFavorites(db)
public void migrate(@NonNull SupportSQLiteDatabase db) {
final List<Favorite> oldFavorites = backupOldFavorites(db);
// recreate with new columns (as there will be no doubt about the `query_display` column being present or not in the future versions) // recreate with new columns (as there will be no doubt about the `query_display` column being present or not in the future versions)
db.execSQL("DROP TABLE " + Favorite.TABLE_NAME); db.execSQL("DROP TABLE " + Favorite.TABLE_NAME)
db.execSQL("CREATE TABLE " + Favorite.TABLE_NAME + " (" db.execSQL("CREATE TABLE " + Favorite.TABLE_NAME + " ("
+ Favorite.COL_ID + " INTEGER PRIMARY KEY," + Favorite.COL_ID + " INTEGER PRIMARY KEY,"
+ Favorite.COL_QUERY + " TEXT," + Favorite.COL_QUERY + " TEXT,"
+ Favorite.COL_TYPE + " TEXT," + Favorite.COL_TYPE + " TEXT,"
+ Favorite.COL_DISPLAY_NAME + " TEXT," + Favorite.COL_DISPLAY_NAME + " TEXT,"
+ Favorite.COL_PIC_URL + " TEXT," + Favorite.COL_PIC_URL + " TEXT,"
+ Favorite.COL_DATE_ADDED + " INTEGER)"); + Favorite.COL_DATE_ADDED + " INTEGER)")
// add the old favorites back // add the old favorites back
for (final Favorite oldFavorite : oldFavorites) { for (oldFavorite in oldFavorites) {
insertOrUpdateFavorite(db, oldFavorite); insertOrUpdateFavorite(db, oldFavorite)
} }
} }
}; }
private val MIGRATION_3_4: Migration = object : Migration(3, 4) {
static final Migration MIGRATION_3_4 = new Migration(3, 4) { override fun migrate(db: SupportSQLiteDatabase) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase db) {
// Required when migrating to Room. // Required when migrating to Room.
// The original table primary keys were not 'NOT NULL', so the migration to Room were failing without the below migration. // The original table primary keys were not 'NOT NULL', so the migration to Room were failing without the below migration.
// Taking this opportunity to rename cookies table to accounts // Taking this opportunity to rename cookies table to accounts
@ -103,7 +86,7 @@ public abstract class AppDatabase extends RoomDatabase {
+ Account.COL_USERNAME + " TEXT," + Account.COL_USERNAME + " TEXT,"
+ Account.COL_COOKIE + " TEXT," + Account.COL_COOKIE + " TEXT,"
+ Account.COL_FULL_NAME + " TEXT," + Account.COL_FULL_NAME + " TEXT,"
+ Account.COL_PROFILE_PIC + " TEXT)"); + Account.COL_PROFILE_PIC + " TEXT)")
// Insert all data from table 'cookies' to 'accounts' // Insert all data from table 'cookies' to 'accounts'
db.execSQL("INSERT INTO " + Account.TABLE_NAME + " (" db.execSQL("INSERT INTO " + Account.TABLE_NAME + " ("
+ Account.COL_UID + "," + Account.COL_UID + ","
@ -117,9 +100,9 @@ public abstract class AppDatabase extends RoomDatabase {
+ Account.COL_COOKIE + "," + Account.COL_COOKIE + ","
+ Account.COL_FULL_NAME + "," + Account.COL_FULL_NAME + ","
+ Account.COL_PROFILE_PIC + Account.COL_PROFILE_PIC
+ " FROM cookies"); + " FROM cookies")
// Drop old cookies table // Drop old cookies table
db.execSQL("DROP TABLE cookies"); db.execSQL("DROP TABLE cookies")
// Create favorite backup table // Create favorite backup table
db.execSQL("CREATE TABLE " + Favorite.TABLE_NAME + "_backup (" db.execSQL("CREATE TABLE " + Favorite.TABLE_NAME + "_backup ("
@ -128,7 +111,7 @@ public abstract class AppDatabase extends RoomDatabase {
+ Favorite.COL_TYPE + " TEXT," + Favorite.COL_TYPE + " TEXT,"
+ Favorite.COL_DISPLAY_NAME + " TEXT," + Favorite.COL_DISPLAY_NAME + " TEXT,"
+ Favorite.COL_PIC_URL + " TEXT," + Favorite.COL_PIC_URL + " TEXT,"
+ Favorite.COL_DATE_ADDED + " INTEGER)"); + Favorite.COL_DATE_ADDED + " INTEGER)")
// Insert all data from table 'favorite' to 'favorite_backup' // Insert all data from table 'favorite' to 'favorite_backup'
db.execSQL("INSERT INTO " + Favorite.TABLE_NAME + "_backup (" db.execSQL("INSERT INTO " + Favorite.TABLE_NAME + "_backup ("
+ Favorite.COL_QUERY + "," + Favorite.COL_QUERY + ","
@ -142,29 +125,25 @@ public abstract class AppDatabase extends RoomDatabase {
+ Favorite.COL_DISPLAY_NAME + "," + Favorite.COL_DISPLAY_NAME + ","
+ Favorite.COL_PIC_URL + "," + Favorite.COL_PIC_URL + ","
+ Favorite.COL_DATE_ADDED + Favorite.COL_DATE_ADDED
+ " FROM " + Favorite.TABLE_NAME); + " FROM " + Favorite.TABLE_NAME)
// Drop favorites // Drop favorites
db.execSQL("DROP TABLE " + Favorite.TABLE_NAME); db.execSQL("DROP TABLE " + Favorite.TABLE_NAME)
// Rename favorite_backup to favorites // Rename favorite_backup to favorites
db.execSQL("ALTER TABLE " + Favorite.TABLE_NAME + "_backup RENAME TO " + Favorite.TABLE_NAME); db.execSQL("ALTER TABLE " + Favorite.TABLE_NAME + "_backup RENAME TO " + Favorite.TABLE_NAME)
} }
}; }
private val MIGRATION_4_5: Migration = object : Migration(4, 5) {
static final Migration MIGRATION_4_5 = new Migration(4, 5) { override fun migrate(database: SupportSQLiteDatabase) {
@Override
public void migrate(@NonNull final SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE IF NOT EXISTS `dm_last_notified` (" + database.execSQL("CREATE TABLE IF NOT EXISTS `dm_last_notified` (" +
"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " + "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
"`thread_id` TEXT, " + "`thread_id` TEXT, " +
"`last_notified_msg_ts` INTEGER, " + "`last_notified_msg_ts` INTEGER, " +
"`last_notified_at` INTEGER)"); "`last_notified_at` INTEGER)")
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_dm_last_notified_thread_id` ON `dm_last_notified` (`thread_id`)"); database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_dm_last_notified_thread_id` ON `dm_last_notified` (`thread_id`)")
} }
}; }
private val MIGRATION_5_6: Migration = object : Migration(5, 6) {
static final Migration MIGRATION_5_6 = new Migration(5, 6) { override fun migrate(database: SupportSQLiteDatabase) {
@Override
public void migrate(@NonNull final SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE IF NOT EXISTS `recent_searches` (" + database.execSQL("CREATE TABLE IF NOT EXISTS `recent_searches` (" +
"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " + "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
"`ig_id` TEXT NOT NULL, " + "`ig_id` TEXT NOT NULL, " +
@ -172,99 +151,102 @@ public abstract class AppDatabase extends RoomDatabase {
"`username` TEXT, " + "`username` TEXT, " +
"`pic_url` TEXT, " + "`pic_url` TEXT, " +
"`type` TEXT NOT NULL, " + "`type` TEXT NOT NULL, " +
"`last_searched_on` INTEGER NOT NULL)"); "`last_searched_on` INTEGER NOT NULL)")
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_recent_searches_ig_id_type` ON `recent_searches` (`ig_id`, `type`)"); database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_recent_searches_ig_id_type` ON `recent_searches` (`ig_id`, `type`)")
}
} }
};
@NonNull private fun backupOldFavorites(db: SupportSQLiteDatabase): List<Favorite> {
private static List<Favorite> backupOldFavorites(@NonNull final SupportSQLiteDatabase db) {
// check if old favorites table had the column query_display // check if old favorites table had the column query_display
final boolean queryDisplayExists = checkColumnExists(db, Favorite.TABLE_NAME, "query_display"); val queryDisplayExists = checkColumnExists(db, Favorite.TABLE_NAME, "query_display")
Log.d(TAG, "backupOldFavorites: queryDisplayExists: " + queryDisplayExists); Log.d(TAG, "backupOldFavorites: queryDisplayExists: $queryDisplayExists")
final List<Favorite> oldModels = new ArrayList<>(); val oldModels: MutableList<Favorite> = ArrayList()
final String sql = "SELECT " val sql = ("SELECT "
+ "query_text," + "query_text,"
+ "date_added" + "date_added"
+ (queryDisplayExists ? ",query_display" : "") + (if (queryDisplayExists) ",query_display" else "")
+ " FROM " + Favorite.TABLE_NAME; + " FROM " + Favorite.TABLE_NAME)
try (final Cursor cursor = db.query(sql)) { try {
db.query(sql).use { cursor ->
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
do { do {
try { try {
final String queryText = cursor.getString(cursor.getColumnIndex("query_text")); val queryText = cursor.getString(cursor.getColumnIndex("query_text"))
final Pair<FavoriteType, String> favoriteTypeQueryPair = Utils.migrateOldFavQuery(queryText); val favoriteTypeQueryPair = Utils.migrateOldFavQuery(queryText) ?: continue
if (favoriteTypeQueryPair == null) continue; val type = favoriteTypeQueryPair.first
final FavoriteType type = favoriteTypeQueryPair.first; val query = favoriteTypeQueryPair.second
final String query = favoriteTypeQueryPair.second; val epochMillis = cursor.getLong(cursor.getColumnIndex("date_added"))
final long epochMillis = cursor.getLong(cursor.getColumnIndex("date_added")); val localDateTime = LocalDateTime.ofInstant(
final LocalDateTime localDateTime = LocalDateTime.ofInstant(
Instant.ofEpochMilli(epochMillis), Instant.ofEpochMilli(epochMillis),
ZoneId.systemDefault() ZoneId.systemDefault()
); )
oldModels.add(new Favorite( oldModels.add(Favorite(
0, 0,
query, query,
type, type,
queryDisplayExists ? cursor.getString(cursor.getColumnIndex("query_display")) : null, if (queryDisplayExists) cursor.getString(cursor.getColumnIndex("query_display")) else null,
null, null,
localDateTime localDateTime
)); ))
} catch (Exception e) { } catch (e: Exception) {
Log.e(TAG, "onUpgrade", e); Log.e(TAG, "onUpgrade", e)
}
} while (cursor.moveToNext())
} }
} while (cursor.moveToNext());
} }
} catch (Exception e) { } catch (e: Exception) {
Log.e(TAG, "onUpgrade", e); Log.e(TAG, "onUpgrade", e)
} }
Log.d(TAG, "backupOldFavorites: oldModels:" + oldModels); Log.d(TAG, "backupOldFavorites: oldModels:$oldModels")
return oldModels; return oldModels
} }
private static synchronized void insertOrUpdateFavorite(@NonNull final SupportSQLiteDatabase db, @NonNull final Favorite model) { @Synchronized
final ContentValues values = new ContentValues(); private fun insertOrUpdateFavorite(db: SupportSQLiteDatabase, model: Favorite) {
values.put(Favorite.COL_QUERY, model.getQuery()); val values = ContentValues()
values.put(Favorite.COL_TYPE, model.getType().toString()); values.put(Favorite.COL_QUERY, model.query)
values.put(Favorite.COL_DISPLAY_NAME, model.getDisplayName()); values.put(Favorite.COL_TYPE, model.type.toString())
values.put(Favorite.COL_PIC_URL, model.getPicUrl()); values.put(Favorite.COL_DISPLAY_NAME, model.displayName)
values.put(Favorite.COL_DATE_ADDED, model.getDateAdded().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); values.put(Favorite.COL_PIC_URL, model.picUrl)
int rows; values.put(Favorite.COL_DATE_ADDED, model.dateAdded!!.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli())
if (model.getId() >= 1) { val rows: Int = if (model.id >= 1) {
rows = db.update(Favorite.TABLE_NAME, db.update(Favorite.TABLE_NAME,
SQLiteDatabase.CONFLICT_IGNORE, SQLiteDatabase.CONFLICT_IGNORE,
values, values,
Favorite.COL_ID + "=?", Favorite.COL_ID + "=?", arrayOf(model.id.toString()))
new String[]{String.valueOf(model.getId())});
} else { } else {
rows = db.update(Favorite.TABLE_NAME, db.update(Favorite.TABLE_NAME,
SQLiteDatabase.CONFLICT_IGNORE, SQLiteDatabase.CONFLICT_IGNORE,
values, values,
Favorite.COL_QUERY + "=?" + " AND " + Favorite.COL_TYPE + "=?", Favorite.COL_QUERY + "=?" + " AND " + Favorite.COL_TYPE + "=?", arrayOf(model.query, model.type.toString()))
new String[]{model.getQuery(), model.getType().toString()});
} }
if (rows != 1) { if (rows != 1) {
db.insert(Favorite.TABLE_NAME, SQLiteDatabase.CONFLICT_IGNORE, values); db.insert(Favorite.TABLE_NAME, SQLiteDatabase.CONFLICT_IGNORE, values)
} }
} }
private static boolean checkColumnExists(@NonNull final SupportSQLiteDatabase db, @Suppress("SameParameterValue")
@NonNull final String tableName, private fun checkColumnExists(
@NonNull final String columnName) { db: SupportSQLiteDatabase,
boolean exists = false; tableName: String,
try (Cursor cursor = db.query("PRAGMA table_info(" + tableName + ")")) { columnName: String,
): Boolean {
var exists = false
try {
db.query("PRAGMA table_info($tableName)").use { cursor ->
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
do { do {
final String currentColumn = cursor.getString(cursor.getColumnIndex("name")); val currentColumn = cursor.getString(cursor.getColumnIndex("name"))
if (currentColumn.equals(columnName)) { if (currentColumn == columnName) {
exists = true; exists = true
} }
} while (cursor.moveToNext()); } while (cursor.moveToNext())
}
}
} catch (ex: Exception) {
Log.e(TAG, "checkColumnExists", ex)
} }
} catch (Exception ex) { return exists
Log.e(TAG, "checkColumnExists", ex);
} }
return exists;
} }
} }

6
app/src/main/java/awais/instagrabber/db/Converters.kt

@ -7,8 +7,7 @@ import java.time.LocalDateTime
import java.time.ZoneId import java.time.ZoneId
import java.time.ZoneOffset import java.time.ZoneOffset
object Converters { class Converters {
@JvmStatic
@TypeConverter @TypeConverter
fun fromFavoriteTypeString(value: String?): FavoriteType? = fun fromFavoriteTypeString(value: String?): FavoriteType? =
if (value == null) null if (value == null) null
@ -18,16 +17,13 @@ object Converters {
null null
} }
@JvmStatic
@TypeConverter @TypeConverter
fun favoriteTypeToString(favoriteType: FavoriteType?): String? = favoriteType?.toString() fun favoriteTypeToString(favoriteType: FavoriteType?): String? = favoriteType?.toString()
@JvmStatic
@TypeConverter @TypeConverter
fun fromTimestampToLocalDateTime(value: Long?): LocalDateTime? = fun fromTimestampToLocalDateTime(value: Long?): LocalDateTime? =
if (value == null) null else LocalDateTime.ofInstant(Instant.ofEpochMilli(value), ZoneOffset.systemDefault()) if (value == null) null else LocalDateTime.ofInstant(Instant.ofEpochMilli(value), ZoneOffset.systemDefault())
@JvmStatic
@TypeConverter @TypeConverter
fun localDateTimeToTimestamp(localDateTime: LocalDateTime?): Long? = localDateTime?.atZone(ZoneId.systemDefault())?.toInstant()?.toEpochMilli() fun localDateTimeToTimestamp(localDateTime: LocalDateTime?): Long? = localDateTime?.atZone(ZoneId.systemDefault())?.toInstant()?.toEpochMilli()
} }
|||||||
100:0
Loading…
Cancel
Save