aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorAllan Wang <me@allanwang.ca>2019-03-06 17:42:31 -0500
committerAllan Wang <me@allanwang.ca>2019-03-06 17:42:31 -0500
commit8b70d80070209eb19791eecf207a8fdefea17a4e (patch)
tree41ed8afcbe74d138fc6dbf601c5e6e33486e8969 /app
parent9a1d9719ad6559054ea1bc4f21f8559559eb9cda (diff)
downloadfrost-8b70d80070209eb19791eecf207a8fdefea17a4e.tar.gz
frost-8b70d80070209eb19791eecf207a8fdefea17a4e.tar.bz2
frost-8b70d80070209eb19791eecf207a8fdefea17a4e.zip
Make db entities immutable
Diffstat (limited to 'app')
-rw-r--r--app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt34
-rw-r--r--app/src/androidTest/kotlin/com/pitchedapps/frost/db/FbTabsDbTest.kt12
-rw-r--r--app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt20
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt3
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt6
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt7
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt28
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt64
-rw-r--r--app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json125
9 files changed, 259 insertions, 40 deletions
diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt
index 351490e2..20592347 100644
--- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt
+++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt
@@ -6,13 +6,15 @@ import kotlin.test.assertEquals
import kotlin.test.assertNull
class CookieDbTest : BaseDbTest() {
+
+ private val dao get() = db.cookieDao()
@Test
fun basicCookie() {
val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie")
runBlocking {
- db.cookieDao().insertCookie(cookie)
- val cookies = db.cookieDao().selectAll()
+ dao.insertCookie(cookie)
+ val cookies = dao.selectAll()
assertEquals(listOf(cookie), cookies, "Cookie mismatch")
}
}
@@ -22,15 +24,15 @@ class CookieDbTest : BaseDbTest() {
val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie")
runBlocking {
- db.cookieDao().insertCookie(cookie)
- db.cookieDao().deleteById(cookie.id + 1)
+ dao.insertCookie(cookie)
+ dao.deleteById(cookie.id + 1)
assertEquals(
listOf(cookie),
- db.cookieDao().selectAll(),
+ dao.selectAll(),
"Cookie list should be the same after inexistent deletion"
)
- db.cookieDao().deleteById(cookie.id)
- assertEquals(emptyList(), db.cookieDao().selectAll(), "Cookie list should be empty after deletion")
+ dao.deleteById(cookie.id)
+ assertEquals(emptyList(), dao.selectAll(), "Cookie list should be empty after deletion")
}
}
@@ -38,18 +40,18 @@ class CookieDbTest : BaseDbTest() {
fun insertReplaceCookie() {
val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie")
runBlocking {
- db.cookieDao().insertCookie(cookie)
- assertEquals(listOf(cookie), db.cookieDao().selectAll(), "Cookie insertion failed")
- db.cookieDao().insertCookie(cookie.copy(name = "testName2"))
+ dao.insertCookie(cookie)
+ assertEquals(listOf(cookie), dao.selectAll(), "Cookie insertion failed")
+ dao.insertCookie(cookie.copy(name = "testName2"))
assertEquals(
listOf(cookie.copy(name = "testName2")),
- db.cookieDao().selectAll(),
+ dao.selectAll(),
"Cookie replacement failed"
)
- db.cookieDao().insertCookie(cookie.copy(id = 123L))
+ dao.insertCookie(cookie.copy(id = 123L))
assertEquals(
setOf(cookie.copy(id = 123L), cookie.copy(name = "testName2")),
- db.cookieDao().selectAll().toSet(),
+ dao.selectAll().toSet(),
"New cookie insertion failed"
)
}
@@ -59,9 +61,9 @@ class CookieDbTest : BaseDbTest() {
fun selectCookie() {
val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie")
runBlocking {
- db.cookieDao().insertCookie(cookie)
- assertEquals(cookie, db.cookieDao().selectById(cookie.id), "Cookie selection failed")
- assertNull(db.cookieDao().selectById(cookie.id + 1), "Inexistent cookie selection failed")
+ dao.insertCookie(cookie)
+ assertEquals(cookie, dao.selectById(cookie.id), "Cookie selection failed")
+ assertNull(dao.selectById(cookie.id + 1), "Inexistent cookie selection failed")
}
}
} \ No newline at end of file
diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FbTabsDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FbTabsDbTest.kt
index a2dce692..91a0bf9a 100644
--- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FbTabsDbTest.kt
+++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FbTabsDbTest.kt
@@ -7,6 +7,8 @@ import kotlin.test.Test
import kotlin.test.assertEquals
class FbTabsDbTest : BaseDbTest() {
+
+ private val dao get() = db.tabDao()
/**
* Note that order is also preserved here
@@ -15,18 +17,18 @@ class FbTabsDbTest : BaseDbTest() {
fun save() {
val tabs = listOf(FbItem.ACTIVITY_LOG, FbItem.BIRTHDAYS, FbItem.EVENTS, FbItem.MARKETPLACE, FbItem.ACTIVITY_LOG)
runBlocking {
- db.tabDao().save(tabs)
- assertEquals(tabs, db.tabDao().selectAll(), "Tab saving failed")
+ dao.save(tabs)
+ assertEquals(tabs, dao.selectAll(), "Tab saving failed")
val newTabs = listOf(FbItem.PAGES, FbItem.MENU)
- db.tabDao().save(newTabs)
- assertEquals(newTabs, db.tabDao().selectAll(), "Tab saving does not delete preexisting items")
+ dao.save(newTabs)
+ assertEquals(newTabs, dao.selectAll(), "Tab saving does not delete preexisting items")
}
}
@Test
fun defaultRetrieve() {
runBlocking {
- assertEquals(defaultTabs(), db.tabDao().selectAll(), "Default retrieval failed")
+ assertEquals(defaultTabs(), dao.selectAll(), "Default retrieval failed")
}
}
} \ No newline at end of file
diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt
new file mode 100644
index 00000000..12092bf6
--- /dev/null
+++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt
@@ -0,0 +1,20 @@
+package com.pitchedapps.frost.db
+
+import com.pitchedapps.frost.facebook.FbItem
+import com.pitchedapps.frost.facebook.defaultTabs
+import kotlinx.coroutines.runBlocking
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class NotificationDbTest : BaseDbTest() {
+
+ private val dao get() = db.notifDao()
+
+ /**
+ * Note that order is also preserved here
+ */
+ @Test
+ fun save() {
+
+ }
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt
index 5649cc73..27dbc37a 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt
@@ -184,8 +184,7 @@ class LoginActivity : BaseActivity() {
}
if (cookie.name?.isNotBlank() == false && result != cookie.name) {
- cookie.name = result
- cookieDao.insertCookie(cookie)
+ cookieDao.insertCookie(cookie.copy(name = result))
}
cookie.name ?: ""
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt
index 128abae3..34a88011 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt
@@ -38,9 +38,9 @@ import kotlinx.android.parcel.Parcelize
@Parcelize
data class CookieEntity(
@androidx.room.PrimaryKey
- var id: Long,
- var name: String?,
- var cookie: String?
+ val id: Long,
+ val name: String?,
+ val cookie: String?
) : Parcelable {
override fun toString(): String = "CookieEntity(${hashCode()})"
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt
index 161ed93d..a37c2ee9 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt
@@ -10,9 +10,14 @@ import org.koin.standalone.StandAloneContext
interface FrostPrivateDao {
fun cookieDao(): CookieDao
+ fun notifDao(): NotificationDao
}
-@Database(entities = [CookieEntity::class], version = 1, exportSchema = true)
+@Database(
+ entities = [CookieEntity::class, NotificationInfoEntity::class, NotificationEntity::class],
+ version = 1,
+ exportSchema = true
+)
abstract class FrostPrivateDatabase : RoomDatabase(), FrostPrivateDao {
companion object {
const val DATABASE_NAME = "frost-priv-db"
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt
index 44e62938..582d57fb 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt
@@ -33,7 +33,7 @@ import com.raizlabs.android.dbflow.kotlinextensions.fastSave
import com.raizlabs.android.dbflow.kotlinextensions.from
import com.raizlabs.android.dbflow.kotlinextensions.select
import com.raizlabs.android.dbflow.structure.BaseModel
-import kotlinx.coroutines.NonCancellable
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
/**
@@ -41,42 +41,48 @@ import kotlinx.coroutines.withContext
*/
@Entity(tableName = "tabs")
-data class FbTabEntity(@androidx.room.PrimaryKey var position: Int, var tab: FbItem)
+data class FbTabEntity(@androidx.room.PrimaryKey val position: Int, val tab: FbItem)
@Dao
interface FbTabDao {
@Query("SELECT * FROM tabs ORDER BY position ASC")
- suspend fun _selectAll(): List<FbTabEntity>
+ fun _selectAll(): List<FbTabEntity>
@Query("DELETE FROM tabs")
- suspend fun _deleteAll()
+ fun _deleteAll()
@Insert(onConflict = OnConflictStrategy.REPLACE)
- suspend fun _insertAll(items: List<FbTabEntity>)
+ fun _insertAll(items: List<FbTabEntity>)
+
+ @Transaction
+ fun _save(items: List<FbTabEntity>) {
+ _deleteAll()
+ _insertAll(items)
+ }
}
/**
* Saving tabs operates by deleting all db items and saving the new list.
* Transactions can't be done with suspensions in room as switching threads during the process
* may result in a deadlock.
- * In this case, there may be a chance that the 'transaction' completes partially,
- * but we'll just fallback to the default anyways.
+ * That's why we disallow thread switching within the transaction, but wrap the entire thing in a coroutine
*/
suspend fun FbTabDao.save(items: List<FbItem>) {
- withContext(NonCancellable) {
- _deleteAll()
+ withContext(Dispatchers.IO) {
val entities = (items.takeIf { it.isNotEmpty() } ?: defaultTabs()).mapIndexed { index, fbItem ->
FbTabEntity(
index,
fbItem
)
}
- _insertAll(entities)
+ _save(entities)
}
}
-suspend fun FbTabDao.selectAll(): List<FbItem> = _selectAll().map { it.tab }.takeIf { it.isNotEmpty() } ?: defaultTabs()
+suspend fun FbTabDao.selectAll(): List<FbItem> = withContext(Dispatchers.IO) {
+ _selectAll().map { it.tab }.takeIf { it.isNotEmpty() } ?: defaultTabs()
+}
object FbItemConverter {
@androidx.room.TypeConverter
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt
index 5b501792..56c3c5ac 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt
@@ -16,6 +16,16 @@
*/
package com.pitchedapps.frost.db
+import androidx.room.Dao
+import androidx.room.Embedded
+import androidx.room.Entity
+import androidx.room.ForeignKey
+import androidx.room.Ignore
+import androidx.room.Index
+import androidx.room.Insert
+import androidx.room.Query
+import androidx.room.Relation
+import androidx.room.Transaction
import com.pitchedapps.frost.utils.L
import com.raizlabs.android.dbflow.annotation.ConflictAction
import com.raizlabs.android.dbflow.annotation.Database
@@ -32,6 +42,60 @@ import com.raizlabs.android.dbflow.sql.SQLiteType
import com.raizlabs.android.dbflow.sql.migration.AlterTableMigration
import com.raizlabs.android.dbflow.structure.BaseModel
+@Entity(
+ tableName = "notification_info",
+ foreignKeys = [ForeignKey(
+ entity = CookieEntity::class,
+ parentColumns = ["id"], childColumns = ["id"], onDelete = ForeignKey.CASCADE
+ )]
+)
+data class NotificationInfoEntity(
+ @androidx.room.PrimaryKey val id: Long,
+ val epoch: Long,
+ val epochIm: Long
+)
+
+@Entity(
+ tableName = "notifications",
+ foreignKeys = [ForeignKey(
+ entity = NotificationInfoEntity::class,
+ parentColumns = ["id"],
+ childColumns = ["userId"],
+ onDelete = ForeignKey.CASCADE
+ )],
+ indices = [Index("userId")]
+)
+data class NotificationEntity(
+ @androidx.room.PrimaryKey var id: Long,
+ val userId: Long,
+ val href: String,
+ val title: String?,
+ val text: String,
+ val timestamp: Long,
+ val profileUrl: String?
+) {
+ @Ignore
+ val notifId = Math.abs(id.toInt())
+}
+
+data class NotificationInfo(
+ @Embedded
+ val info: NotificationInfoEntity,
+ @Relation(parentColumn = "id", entityColumn = "userId")
+ val notifications: List<NotificationEntity> = emptyList()
+)
+
+@Dao
+interface NotificationDao {
+
+ @Query("SELECT * FROM notification_info WHERE id = :id")
+ fun selectById(id: Long): NotificationInfo?
+
+ @Transaction
+ @Insert
+ fun insertInfo(info: NotificationInfoEntity)
+}
+
/**
* Created by Allan Wang on 2017-05-30.
*/
diff --git a/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json b/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json
index 9816651c..f0909a17 100644
--- a/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json
+++ b/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json
@@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 1,
- "identityHash": "ba6f1d7e47823dac6ed1622fec043d5d",
+ "identityHash": "d0911840f629ef359aaf8ac8635dc571",
"entities": [
{
"tableName": "cookies",
@@ -35,12 +35,133 @@
},
"indices": [],
"foreignKeys": []
+ },
+ {
+ "tableName": "notification_info",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `epoch` INTEGER NOT NULL, `epochIm` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`id`) REFERENCES `cookies`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "epoch",
+ "columnName": "epoch",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "epochIm",
+ "columnName": "epochIm",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": [
+ {
+ "table": "cookies",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "id"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "notifications",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `userId` INTEGER NOT NULL, `href` TEXT NOT NULL, `title` TEXT, `text` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `profileUrl` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`userId`) REFERENCES `notification_info`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userId",
+ "columnName": "userId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "href",
+ "columnName": "href",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "text",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "timestamp",
+ "columnName": "timestamp",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "profileUrl",
+ "columnName": "profileUrl",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_notifications_userId",
+ "unique": false,
+ "columnNames": [
+ "userId"
+ ],
+ "createSql": "CREATE INDEX `index_notifications_userId` ON `${TABLE_NAME}` (`userId`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "notification_info",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "userId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
- "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"ba6f1d7e47823dac6ed1622fec043d5d\")"
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"d0911840f629ef359aaf8ac8635dc571\")"
]
}
} \ No newline at end of file