From 5002792f2c79a7479c531736a4a9c40c0b1fe116 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Sun, 21 Apr 2019 21:02:55 -0400 Subject: Wrap all db calls using our own context --- .../kotlin/com/pitchedapps/frost/db/CacheDb.kt | 19 +++- .../kotlin/com/pitchedapps/frost/db/CookiesDb.kt | 15 ++- .../kotlin/com/pitchedapps/frost/db/DaoUtils.kt | 28 +++++ .../kotlin/com/pitchedapps/frost/db/GenericDb.kt | 14 +-- .../com/pitchedapps/frost/db/NotificationDb.kt | 17 ++- .../kotlin/com/pitchedapps/frost/db/ThreadDb.kt | 115 --------------------- 6 files changed, 67 insertions(+), 141 deletions(-) create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/db/DaoUtils.kt delete mode 100644 app/src/main/kotlin/com/pitchedapps/frost/db/ThreadDb.kt (limited to 'app/src/main/kotlin/com/pitchedapps/frost/db') diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt index 4906f60a..f0dacdc7 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt @@ -55,23 +55,32 @@ data class CacheEntity( interface CacheDao { @Query("SELECT * FROM frost_cache WHERE id = :id AND type = :type") - suspend fun select(id: Long, type: String): CacheEntity? + fun _select(id: Long, type: String): CacheEntity? @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertCache(cache: CacheEntity) + fun _insertCache(cache: CacheEntity) @Query("DELETE FROM frost_cache WHERE id = :id AND type = :type") - suspend fun delete(id: Long, type: String) + fun _delete(id: Long, type: String) +} + +suspend fun CacheDao.select(id: Long, type: String) = dao { + _select(id, type) +} + +suspend fun CacheDao.delete(id: Long, type: String) = dao { + _delete(id, type) } /** * Returns true if successful, given that there are constraints to the insertion */ -suspend fun CacheDao.save(id: Long, type: String, contents: String): Boolean = +suspend fun CacheDao.save(id: Long, type: String, contents: String): Boolean = dao { try { - insertCache(CacheEntity(id, type, System.currentTimeMillis(), contents)) + _insertCache(CacheEntity(id, type, System.currentTimeMillis(), contents)) true } catch (e: Exception) { L.e(e) { "Cache save failed for $type" } false } +} 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 5aadbb02..b81ce365 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt @@ -53,21 +53,26 @@ data class CookieEntity( interface CookieDao { @Query("SELECT * FROM cookies") - suspend fun selectAll(): List + fun _selectAll(): List @Query("SELECT * FROM cookies WHERE cookie_id = :id") - suspend fun selectById(id: Long): CookieEntity? + fun _selectById(id: Long): CookieEntity? @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun save(cookie: CookieEntity) + fun _save(cookie: CookieEntity) @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun save(cookies: List) + fun _save(cookies: List) @Query("DELETE FROM cookies WHERE cookie_id = :id") - suspend fun deleteById(id: Long) + fun _deleteById(id: Long) } +suspend fun CookieDao.selectAll() = dao { _selectAll() } +suspend fun CookieDao.selectById(id: Long) = dao { _selectById(id) } +suspend fun CookieDao.save(cookie: CookieEntity) = dao { _save(cookie) } +suspend fun CookieDao.save(cookies: List) = dao { _save(cookies) } +suspend fun CookieDao.deleteById(id: Long) = dao { _deleteById(id) } suspend fun CookieDao.currentCookie() = selectById(Prefs.userId) @Database(version = CookiesDb.VERSION) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/DaoUtils.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/DaoUtils.kt new file mode 100644 index 00000000..c31aa9b7 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/DaoUtils.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2019 Allan Wang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.pitchedapps.frost.db + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +/** + * Wraps dao calls to work with coroutines + * Non transactional queries were supposed to be fixed in https://issuetracker.google.com/issues/69474692, + * but it still requires dispatch from a non ui thread. + * This avoids that constraint + */ +suspend inline fun dao(crossinline block: () -> T) = withContext(Dispatchers.IO) { block() } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/GenericDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/GenericDb.kt index 4177ae86..f36c8af9 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/GenericDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/GenericDb.kt @@ -43,27 +43,27 @@ data class GenericEntity( interface GenericDao { @Query("SELECT contents FROM frost_generic WHERE type = :type") - suspend fun select(type: String): String? + fun _select(type: String): String? @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun save(entity: GenericEntity) + fun _save(entity: GenericEntity) @Query("DELETE FROM frost_generic WHERE type = :type") - suspend fun delete(type: String) + fun _delete(type: String) companion object { const val TYPE_TABS = "generic_tabs" } } -suspend fun GenericDao.saveTabs(tabs: List) { +suspend fun GenericDao.saveTabs(tabs: List) = dao { val content = tabs.joinToString(",") { it.name } - save(GenericEntity(GenericDao.TYPE_TABS, content)) + _save(GenericEntity(GenericDao.TYPE_TABS, content)) } -suspend fun GenericDao.getTabs(): List { +suspend fun GenericDao.getTabs(): List = dao { val allTabs = FbItem.values.map { it.name to it }.toMap() - return select(GenericDao.TYPE_TABS) + _select(GenericDao.TYPE_TABS) ?.split(",") ?.mapNotNull { allTabs[it] } ?.takeIf { it.isNotEmpty() } 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 e1f7fc76..8936d682 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt @@ -42,8 +42,6 @@ import com.raizlabs.android.dbflow.kotlinextensions.where import com.raizlabs.android.dbflow.sql.SQLiteType import com.raizlabs.android.dbflow.sql.migration.AlterTableMigration import com.raizlabs.android.dbflow.structure.BaseModel -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext @Entity( tableName = "notifications", @@ -120,7 +118,7 @@ interface NotificationDao { fun _deleteNotifications(userId: Long, type: String) @Query("DELETE FROM notifications") - suspend fun deleteAll() + fun _deleteAll() /** * It is assumed that the notification batch comes from the same user @@ -134,17 +132,18 @@ interface NotificationDao { } } -suspend fun NotificationDao.selectNotifications(userId: Long, type: String): List = - withContext(Dispatchers.IO) { - _selectNotifications(userId, type).map { it.toNotifContent() } - } +suspend fun NotificationDao.deleteAll() = dao { _deleteAll() } + +suspend fun NotificationDao.selectNotifications(userId: Long, type: String): List = dao { + _selectNotifications(userId, type).map { it.toNotifContent() } +} /** * Returns true if successful, given that there are constraints to the insertion */ suspend fun NotificationDao.saveNotifications(type: String, notifs: List): Boolean { if (notifs.isEmpty()) return true - return withContext(Dispatchers.IO) { + return dao { try { _saveNotifications(type, notifs) true @@ -155,7 +154,7 @@ suspend fun NotificationDao.saveNotifications(type: String, notifs: List it.epoch diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/ThreadDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/ThreadDb.kt deleted file mode 100644 index d7c91211..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/ThreadDb.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2018 Allan Wang - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.pitchedapps.frost.db - -import androidx.room.Dao -import androidx.room.Embedded -import androidx.room.Entity -import androidx.room.ForeignKey -import androidx.room.Index -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import androidx.room.Transaction -import com.pitchedapps.frost.db.CookieModel_Table.cookie -import com.pitchedapps.frost.facebook.parsers.FrostThread -import com.pitchedapps.frost.utils.L -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - -@Entity( - tableName = "threads", - primaryKeys = ["thread_id", "userId"], - foreignKeys = [ForeignKey( - entity = CookieEntity::class, - parentColumns = ["cookie_id"], - childColumns = ["userId"], - onDelete = ForeignKey.CASCADE - )], - indices = [Index("thread_id"), Index("userId")] -) -data class ThreadEntity( - @Embedded(prefix = "thread_") - val thread: FrostThread, - val userId: Long -) - -data class ThreadContentEntity( - @Embedded - val cookie: CookieEntity, - @Embedded(prefix = "thread_") - val thread: FrostThread -) - -@Dao -interface ThreadDao { - - /** - * Note that notifications are guaranteed to be ordered by descending timestamp - */ - @Transaction - @Query("SELECT * FROM cookies INNER JOIN threads ON cookie_id = userId WHERE userId = :userId ORDER BY thread_time DESC") - fun _selectThreads(userId: Long): List - - @Query("SELECT thread_time FROM threads WHERE userId = :userId ORDER BY thread_time DESC LIMIT 1") - fun _selectEpoch(userId: Long): Long? - - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun _insertThreads(notifs: List) - - @Query("DELETE FROM threads WHERE userId = :userId ") - fun _deleteThreads(userId: Long) - - @Query("DELETE FROM threads") - suspend fun deleteAll() - - /** - * It is assumed that the notification batch comes from the same user - */ - @Transaction - fun _saveThreads(userId: Long, notifs: List) { - val entities = notifs.map { ThreadEntity(it, userId) } - _deleteThreads(userId) - _insertThreads(entities) - } -} - -suspend fun ThreadDao.selectThreads(userId: Long): List = - withContext(Dispatchers.IO) { - _selectThreads(userId) - } - -/** - * Returns true if successful, given that there are constraints to the insertion - */ -suspend fun ThreadDao.saveThreads(userId: Long, threads: List): Boolean { - if (threads.isEmpty()) return true - return withContext(Dispatchers.IO) { - try { - _saveThreads(userId, threads) - true - } catch (e: Exception) { - L.e(e) { "Thread save failed" } - false - } - } -} - -suspend fun ThreadDao.latestEpoch(userId: Long, type: String): Long = - withContext(Dispatchers.IO) { - _selectEpoch(userId) ?: lastNotificationTime(userId).epochIm - } -- cgit v1.2.3