1
0
mirror of https://github.com/AllanWang/Frost-for-Facebook.git synced 2024-11-12 14:03:00 +01:00

Migrate to dao and add filter to title

This commit is contained in:
Allan Wang 2019-03-07 00:12:24 -05:00
parent b417cc51b2
commit 7f1f2247de
No known key found for this signature in database
GPG Key ID: C93E3F9C679D7A56
4 changed files with 91 additions and 42 deletions

View File

@ -1,12 +1,12 @@
package com.pitchedapps.frost.db
import android.database.sqlite.SQLiteConstraintException
import com.pitchedapps.frost.services.NOTIF_CHANNEL_GENERAL
import com.pitchedapps.frost.services.NOTIF_CHANNEL_MESSAGES
import com.pitchedapps.frost.services.NotificationContent
import kotlinx.coroutines.runBlocking
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class NotificationDbTest : BaseDbTest() {
@ -38,6 +38,40 @@ class NotificationDbTest : BaseDbTest() {
}
}
@Test
fun selectConditions() {
runBlocking {
val cookie1 = cookie(12345L)
val cookie2 = cookie(12L)
val notifs1 = (0L..2L).map { notifContent(it, cookie1) }
val notifs2 = (5L..10L).map { notifContent(it, cookie2) }
db.cookieDao().insertCookie(cookie1)
db.cookieDao().insertCookie(cookie2)
dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs1)
dao.saveNotifications(NOTIF_CHANNEL_MESSAGES, notifs2)
assertEquals(
emptyList(),
dao.selectNotifications(cookie1.id, NOTIF_CHANNEL_MESSAGES),
"Filtering by type did not work for cookie1"
)
assertEquals(
notifs1.sortedByDescending { it.timestamp },
dao.selectNotifications(cookie1.id, NOTIF_CHANNEL_GENERAL),
"Selection for cookie1 failed"
)
assertEquals(
emptyList(),
dao.selectNotifications(cookie2.id, NOTIF_CHANNEL_GENERAL),
"Filtering by type did not work for cookie2"
)
assertEquals(
notifs2.sortedByDescending { it.timestamp },
dao.selectNotifications(cookie2.id, NOTIF_CHANNEL_MESSAGES),
"Selection for cookie2 failed"
)
}
}
/**
* Primary key is both id and userId, in the event that the same notification to multiple users has the same id
*/
@ -50,8 +84,8 @@ class NotificationDbTest : BaseDbTest() {
val notifs2 = notifs1.map { it.copy(data = cookie2) }
db.cookieDao().insertCookie(cookie1)
db.cookieDao().insertCookie(cookie2)
dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs1)
dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs2)
assertTrue(dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs1), "Notif1 save failed")
assertTrue(dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs2), "Notif2 save failed")
}
}
@ -84,10 +118,11 @@ class NotificationDbTest : BaseDbTest() {
@Test
fun insertionWithInvalidCookies() {
assertFailsWith(SQLiteConstraintException::class) {
runBlocking {
dao.saveNotifications(NOTIF_CHANNEL_GENERAL, listOf(notifContent(1L, cookie(2L))))
}
runBlocking {
assertFalse(
dao.saveNotifications(NOTIF_CHANNEL_GENERAL, listOf(notifContent(1L, cookie(2L)))),
"Notif save should not have passed without relevant cookie entries"
)
}
}
}

View File

@ -136,9 +136,19 @@ suspend fun NotificationDao.selectNotifications(userId: Long, type: String): Lis
_selectNotifications(userId, type).map { it.toNotifContent() }
}
suspend fun NotificationDao.saveNotifications(type: String, notifs: List<NotificationContent>) {
withContext(Dispatchers.IO) {
_saveNotifications(type, notifs)
/**
* Returns true if successful, given that there are constraints to the insertion
*/
suspend fun NotificationDao.saveNotifications(type: String, notifs: List<NotificationContent>): Boolean {
if (notifs.isEmpty()) return true
return withContext(Dispatchers.IO) {
try {
_saveNotifications(type, notifs)
true
} catch (e: Exception) {
L.e(e) { "Notif save failed" }
false
}
}
}
@ -175,12 +185,4 @@ data class NotificationModel(
fun lastNotificationTime(id: Long): NotificationModel =
(select from NotificationModel::class where (NotificationModel_Table.id eq id)).querySingle()
?: NotificationModel(id = id)
fun saveNotificationTime(notificationModel: NotificationModel, callback: (() -> Unit)? = null) {
notificationModel.async save {
L.d { "Fb notification model saved" }
L._d { notificationModel }
callback?.invoke()
}
}
?: NotificationModel(id = id)

View File

@ -32,9 +32,11 @@ import com.pitchedapps.frost.BuildConfig
import com.pitchedapps.frost.R
import com.pitchedapps.frost.activities.FrostWebActivity
import com.pitchedapps.frost.db.CookieEntity
import com.pitchedapps.frost.db.CookieModel
import com.pitchedapps.frost.db.FrostDatabase
import com.pitchedapps.frost.db.NotificationModel
import com.pitchedapps.frost.db.lastNotificationTime
import com.pitchedapps.frost.db.latestEpoch
import com.pitchedapps.frost.db.saveNotifications
import com.pitchedapps.frost.enums.OverlayContext
import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.facebook.parsers.FrostParser
@ -65,8 +67,8 @@ enum class NotificationType(
private val overlayContext: OverlayContext,
private val fbItem: FbItem,
private val parser: FrostParser<ParseNotification>,
// Legacy; remove with dbflow
private val getTime: (notif: NotificationModel) -> Long,
private val putTime: (notif: NotificationModel, time: Long) -> NotificationModel,
private val ringtone: () -> String
) {
@ -76,7 +78,6 @@ enum class NotificationType(
FbItem.NOTIFICATIONS,
NotifParser,
NotificationModel::epoch,
{ notif, time -> notif.copy(epoch = time) },
Prefs::notificationRingtone
) {
@ -90,7 +91,6 @@ enum class NotificationType(
FbItem.MESSAGES,
MessageParser,
NotificationModel::epochIm,
{ notif, time -> notif.copy(epochIm = time) },
Prefs::messageRingtone
);
@ -117,7 +117,8 @@ enum class NotificationType(
* Returns the number of notifications generated,
* or -1 if an error occurred
*/
fun fetch(context: Context, data: CookieEntity): Int {
suspend fun fetch(context: Context, data: CookieEntity): Int {
val notifDao = FrostDatabase.get().notifDao()
val response = try {
parser.parse(data.cookie)
} catch (ignored: Exception) {
@ -128,35 +129,44 @@ enum class NotificationType(
return -1
}
val notifContents = response.data.getUnreadNotifications(data).filter { notif ->
val text = notif.text
Prefs.notificationKeywords.none { text.contains(it, true) }
val inText = notif.text.let { text ->
Prefs.notificationKeywords.none { text.contains(it, true) }
}
val inTitle = notif.title?.let { title ->
Prefs.notificationKeywords.none { title.contains(it, true) }
} ?: false
inText || inTitle
}
if (notifContents.isEmpty()) return 0
val userId = data.id
val prevNotifTime = lastNotificationTime(userId)
val prevLatestEpoch = getTime(prevNotifTime)
// Legacy, remove with dbflow
val prevLatestEpoch =
notifDao.latestEpoch(userId, channelId).takeIf { it != -1L } ?: getTime(lastNotificationTime(userId))
L.v { "Notif $name prev epoch $prevLatestEpoch" }
var newLatestEpoch = prevLatestEpoch
val notifs = mutableListOf<FrostNotification>()
notifContents.forEach { notif ->
L.v { "Notif timestamp ${notif.timestamp}" }
if (notif.timestamp <= prevLatestEpoch) return@forEach
notifs.add(createNotification(context, notif))
if (notif.timestamp > newLatestEpoch)
newLatestEpoch = notif.timestamp
}
if (newLatestEpoch > prevLatestEpoch)
putTime(prevNotifTime, newLatestEpoch).save()
L.d { "Notif $name new epoch ${getTime(lastNotificationTime(userId))}" }
if (prevLatestEpoch == -1L && !BuildConfig.DEBUG) {
L.d { "Skipping first notification fetch" }
return 0 // do not notify the first time
}
val newNotifContents = notifContents.filter { it.timestamp > prevLatestEpoch }
if (newNotifContents.isEmpty()) {
L.d { "No new notifs found for $name" }
return 0
}
L.d { "Notif $name new epoch ${newNotifContents.map { it.timestamp }.max()}" }
val notifs = newNotifContents.map { createNotification(context, it) }
notifDao.saveNotifications(channelId, newNotifContents)
frostEvent("Notifications", "Type" to name, "Count" to notifs.size)
if (notifs.size > 1)
summaryNotification(context, userId, notifs.size).notify(context)
val ringtone = ringtone()
notifs.forEachIndexed { i, notif ->
// Ring at most twice
notif.withAlert(i < 2, ringtone).notify(context)
}
return notifs.size

View File

@ -23,6 +23,7 @@ import com.pitchedapps.frost.BuildConfig
import com.pitchedapps.frost.R
import com.pitchedapps.frost.db.CookieDao
import com.pitchedapps.frost.db.CookieEntity
import com.pitchedapps.frost.db.NotificationDao
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.frostEvent
@ -44,6 +45,7 @@ import org.koin.android.ext.android.inject
class NotificationService : BaseJobService() {
val cookieDao: CookieDao by inject()
val notifDao: NotificationDao by inject()
override fun onStopJob(params: JobParameters?): Boolean {
super.onStopJob(params)
@ -110,7 +112,7 @@ class NotificationService : BaseJobService() {
* Implemented fetch to also notify when an error occurs
* Also normalized the output to return the number of notifications received
*/
private fun fetch(jobId: Int, type: NotificationType, cookie: CookieEntity): Int {
private suspend fun fetch(jobId: Int, type: NotificationType, cookie: CookieEntity): Int {
val count = type.fetch(this, cookie)
if (count < 0) {
if (jobId == NOTIFICATION_JOB_NOW)