Full end to end use case works; still ugly though

This commit is contained in:
Philipp Heckel 2021-12-30 01:05:32 +01:00
parent 7dbbf12c99
commit 73f610afa8
7 changed files with 111 additions and 45 deletions

View file

@ -2,11 +2,11 @@
"formatVersion": 1,
"database": {
"version": 5,
"identityHash": "d72d045ad4ad20db887b4c6aed3da27b",
"identityHash": "306578182c2ad0f9803956beda094d28",
"entities": [
{
"tableName": "Subscription",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `topic` TEXT NOT NULL, `instant` INTEGER NOT NULL, `mutedUntil` INTEGER NOT NULL, `upAppId` TEXT NOT NULL, `upConnectorToken` TEXT NOT NULL, PRIMARY KEY(`id`))",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `topic` TEXT NOT NULL, `instant` INTEGER NOT NULL, `mutedUntil` INTEGER NOT NULL, `upAppId` TEXT, `upConnectorToken` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
@ -42,13 +42,13 @@
"fieldPath": "upAppId",
"columnName": "upAppId",
"affinity": "TEXT",
"notNull": true
"notNull": false
},
{
"fieldPath": "upConnectorToken",
"columnName": "upConnectorToken",
"affinity": "TEXT",
"notNull": true
"notNull": false
}
],
"primaryKey": {
@ -66,6 +66,14 @@
"topic"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Subscription_baseUrl_topic` ON `${TABLE_NAME}` (`baseUrl`, `topic`)"
},
{
"name": "index_Subscription_upConnectorToken",
"unique": true,
"columnNames": [
"upConnectorToken"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Subscription_upConnectorToken` ON `${TABLE_NAME}` (`upConnectorToken`)"
}
],
"foreignKeys": []
@ -144,7 +152,7 @@
"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, 'd72d045ad4ad20db887b4c6aed3da27b')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '306578182c2ad0f9803956beda094d28')"
]
}
}

View file

@ -6,15 +6,15 @@ import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import kotlinx.coroutines.flow.Flow
@Entity(indices = [Index(value = ["baseUrl", "topic"], unique = true)])
@Entity(indices = [Index(value = ["baseUrl", "topic"], unique = true), Index(value = ["upConnectorToken"], unique = true)])
data class Subscription(
@PrimaryKey val id: Long, // Internal ID, only used in Repository and activities
@ColumnInfo(name = "baseUrl") val baseUrl: String,
@ColumnInfo(name = "topic") val topic: String,
@ColumnInfo(name = "instant") val instant: Boolean,
@ColumnInfo(name = "mutedUntil") val mutedUntil: Long, // TODO notificationSound, notificationSchedule
@ColumnInfo(name = "upAppId") val upAppId: String,
@ColumnInfo(name = "upConnectorToken") val upConnectorToken: String,
@ColumnInfo(name = "upAppId") val upAppId: String?,
@ColumnInfo(name = "upConnectorToken") val upConnectorToken: String?,
@Ignore val totalCount: Int = 0, // Total notifications
@Ignore val newCount: Int = 0, // New notifications
@Ignore val lastActive: Long = 0, // Unix timestamp
@ -110,8 +110,8 @@ abstract class Database : RoomDatabase() {
private val MIGRATION_4_5 = object : Migration(3, 4) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE Subscription ADD COLUMN upAppId TEXT NOT NULL DEFAULT('')")
db.execSQL("ALTER TABLE Subscription ADD COLUMN upConnectorToken TEXT NOT NULL DEFAULT('')")
db.execSQL("ALTER TABLE Subscription ADD COLUMN upAppId TEXT")
db.execSQL("ALTER TABLE Subscription ADD COLUMN upConnectorToken TEXT")
}
}
}
@ -171,6 +171,19 @@ interface SubscriptionDao {
""")
fun get(subscriptionId: Long): SubscriptionWithMetadata?
@Query("""
SELECT
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.upAppId, s.upConnectorToken,
COUNT(n.id) totalCount,
COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount,
IFNULL(MAX(n.timestamp),0) AS lastActive
FROM Subscription AS s
LEFT JOIN Notification AS n ON s.id=n.subscriptionId AND n.deleted != 1
WHERE s.upConnectorToken = :connectorToken
GROUP BY s.id
""")
fun getByConnectorToken(connectorToken: String): SubscriptionWithMetadata?
@Insert
fun add(subscription: Subscription)

View file

@ -54,6 +54,12 @@ class Repository(private val sharedPrefs: SharedPreferences, private val subscri
return toSubscription(subscriptionDao.get(baseUrl, topic))
}
@Suppress("RedundantSuspendModifier")
@WorkerThread
suspend fun getSubscriptionByConnectorToken(connectorToken: String): Subscription? {
return toSubscription(subscriptionDao.getByConnectorToken(connectorToken))
}
@Suppress("RedundantSuspendModifier")
@WorkerThread
suspend fun addSubscription(subscription: Subscription) {

View file

@ -5,6 +5,7 @@ import io.heckel.ntfy.data.Notification
import io.heckel.ntfy.data.Repository
import io.heckel.ntfy.data.Subscription
import io.heckel.ntfy.up.Distributor
import io.heckel.ntfy.util.safeLet
class NotificationDispatcher(val context: Context, val repository: Repository) {
private val notifier = NotificationService(context)
@ -18,8 +19,8 @@ class NotificationDispatcher(val context: Context, val repository: Repository) {
fun dispatch(subscription: Subscription, notification: Notification) {
val muted = checkMuted(subscription)
val notify = checkNotify(subscription, notification, muted)
val broadcast = subscription.upAppId == ""
val distribute = subscription.upAppId != ""
val broadcast = subscription.upAppId == null
val distribute = subscription.upAppId != null
if (notify) {
notifier.send(subscription, notification)
}
@ -27,7 +28,9 @@ class NotificationDispatcher(val context: Context, val repository: Repository) {
broadcaster.send(subscription, notification, muted)
}
if (distribute) {
distributor.sendMessage(subscription.upAppId, subscription.upConnectorToken, notification.message)
safeLet(subscription.upAppId, subscription.upConnectorToken) { appId, connectorToken ->
distributor.sendMessage(appId, connectorToken, notification.message)
}
}
}

View file

@ -7,6 +7,8 @@ import io.heckel.ntfy.R
import io.heckel.ntfy.app.Application
import io.heckel.ntfy.data.Subscription
import io.heckel.ntfy.ui.SubscriberManager
import io.heckel.ntfy.util.randomString
import io.heckel.ntfy.util.topicUrlUp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@ -24,17 +26,23 @@ class BroadcastReceiver : android.content.BroadcastReceiver() {
Log.w(TAG, "Trying to register an app without packageName")
return
}
val baseUrl = context!!.getString(R.string.app_base_url) // FIXME
val topic = connectorToken // FIXME
val topic = "up" + randomString(TOPIC_LENGTH)
val endpoint = topicUrlUp(baseUrl, topic)
val app = context!!.applicationContext as Application
val repository = app.repository
val distributor = Distributor(app)
GlobalScope.launch(Dispatchers.IO) {
val existingSubscription = repository.getSubscriptionByConnectorToken(connectorToken)
if (existingSubscription != null) {
distributor.sendRegistrationRefused(appId, connectorToken)
return@launch
}
val subscription = Subscription(
id = Random.nextLong(),
baseUrl = baseUrl,
topic = topic,
instant = true,
instant = true, // No Firebase, always instant!
mutedUntil = 0,
upAppId = appId,
upConnectorToken = connectorToken,
@ -42,26 +50,38 @@ class BroadcastReceiver : android.content.BroadcastReceiver() {
newCount = 0,
lastActive = Date().time/1000
)
GlobalScope.launch(Dispatchers.IO) {
repository.addSubscription(subscription)
val subscriptionIdsWithInstantStatus = repository.getSubscriptionIdsWithInstantStatus()
val subscriberManager = SubscriberManager(context!!)
val subscriberManager = SubscriberManager(app)
subscriberManager.refreshService(subscriptionIdsWithInstantStatus)
distributor.sendEndpoint(appId, connectorToken, endpoint)
}
distributor.sendEndpoint(appId, connectorToken)
// XXXXXXXXX
}
ACTION_UNREGISTER -> {
val connectorToken = intent.getStringExtra(EXTRA_TOKEN) ?: ""
Log.d(TAG, "Unregister: connectorToken=$connectorToken")
// XXXXXXX
val distributor = Distributor(context!!)
distributor.sendUnregistered("org.unifiedpush.example", connectorToken)
val app = context!!.applicationContext as Application
val repository = app.repository
val distributor = Distributor(app)
GlobalScope.launch(Dispatchers.IO) {
val existingSubscription = repository.getSubscriptionByConnectorToken(connectorToken)
if (existingSubscription == null) {
return@launch
}
repository.removeSubscription(existingSubscription.id)
val subscriptionIdsWithInstantStatus = repository.getSubscriptionIdsWithInstantStatus()
val subscriberManager = SubscriberManager(app)
subscriberManager.refreshService(subscriptionIdsWithInstantStatus)
existingSubscription.upAppId?.let { appId ->
distributor.sendUnregistered(appId, connectorToken)
}
}
}
}
}
companion object {
private const val TAG = "NtfyUpBroadcastRecv"
private const val TOPIC_LENGTH = 16
}
}

View file

@ -2,35 +2,39 @@ package io.heckel.ntfy.up
import android.content.Context
import android.content.Intent
import io.heckel.ntfy.R
import io.heckel.ntfy.data.Repository
import io.heckel.ntfy.util.topicUrlUp
class Distributor(val context: Context) {
fun sendMessage(app: String, token: String, message: String) {
fun sendMessage(app: String, connectorToken: String, message: String) {
val broadcastIntent = Intent()
broadcastIntent.`package` = app
broadcastIntent.action = ACTION_MESSAGE
broadcastIntent.putExtra(EXTRA_TOKEN, token)
broadcastIntent.putExtra(EXTRA_TOKEN, connectorToken)
broadcastIntent.putExtra(EXTRA_MESSAGE, message)
context.sendBroadcast(broadcastIntent)
}
fun sendEndpoint(app: String, token: String) {
val appBaseUrl = context.getString(R.string.app_base_url) // FIXME
fun sendEndpoint(app: String, connectorToken: String, endpoint: String) {
val broadcastIntent = Intent()
broadcastIntent.`package` = app
broadcastIntent.action = ACTION_NEW_ENDPOINT
broadcastIntent.putExtra(EXTRA_TOKEN, token)
broadcastIntent.putExtra(EXTRA_ENDPOINT, topicUrlUp(appBaseUrl, token))
broadcastIntent.putExtra(EXTRA_TOKEN, connectorToken)
broadcastIntent.putExtra(EXTRA_ENDPOINT, endpoint)
context.sendBroadcast(broadcastIntent)
}
fun sendUnregistered(app: String, token: String) {
fun sendUnregistered(app: String, connectorToken: String) {
val broadcastIntent = Intent()
broadcastIntent.`package` = app
broadcastIntent.action = ACTION_UNREGISTERED
broadcastIntent.putExtra(EXTRA_TOKEN, token)
broadcastIntent.putExtra(EXTRA_TOKEN, connectorToken)
context.sendBroadcast(broadcastIntent)
}
fun sendRegistrationRefused(app: String, connectorToken: String) {
val broadcastIntent = Intent()
broadcastIntent.`package` = app
broadcastIntent.action = ACTION_REGISTRATION_REFUSED
broadcastIntent.putExtra(EXTRA_TOKEN, connectorToken)
context.sendBroadcast(broadcastIntent)
}
}

View file

@ -5,6 +5,7 @@ import android.animation.ValueAnimator
import android.view.Window
import io.heckel.ntfy.data.Notification
import io.heckel.ntfy.data.Subscription
import java.security.SecureRandom
import java.text.DateFormat
import java.util.*
@ -102,3 +103,14 @@ fun fadeStatusBarColor(window: Window, fromColor: Int, toColor: Int) {
}
statusBarColorAnimation.start()
}
fun randomString(len: Int): String {
val random = SecureRandom()
val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".toCharArray()
return (1..len).map { chars[random.nextInt(chars.size)] }.joinToString("")
}
// Allows letting multiple variables at once, see https://stackoverflow.com/a/35522422/1440785
inline fun <T1: Any, T2: Any, R: Any> safeLet(p1: T1?, p2: T2?, block: (T1, T2)->R?): R? {
return if (p1 != null && p2 != null) block(p1, p2) else null
}