Full end to end use case works; still ugly though
This commit is contained in:
parent
7dbbf12c99
commit
73f610afa8
7 changed files with 111 additions and 45 deletions
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue