package io.heckel.ntfy.data import android.content.SharedPreferences import android.util.Log import androidx.annotation.WorkerThread import androidx.lifecycle.* import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicLong class Repository(private val sharedPrefs: SharedPreferences, private val subscriptionDao: SubscriptionDao, private val notificationDao: NotificationDao) { private val connectionStates = ConcurrentHashMap() private val connectionStatesLiveData = MutableLiveData(connectionStates) val detailViewSubscriptionId = AtomicLong(0L) // Omg, what a hack ... init { Log.d(TAG, "Created $this") } fun getSubscriptionsLiveData(): LiveData> { return subscriptionDao .listFlow() .asLiveData() .combineWith(connectionStatesLiveData) { subscriptionsWithMetadata, _ -> toSubscriptionList(subscriptionsWithMetadata.orEmpty()) } } fun getSubscriptionIdsWithInstantStatusLiveData(): LiveData>> { return subscriptionDao .listFlow() .asLiveData() .map { list -> list.map { Pair(it.id, it.instant) }.toSet() } } fun getSubscriptions(): List { return toSubscriptionList(subscriptionDao.list()) } @Suppress("RedundantSuspendModifier") @WorkerThread suspend fun getSubscription(subscriptionId: Long): Subscription? { return toSubscription(subscriptionDao.get(subscriptionId)) } @Suppress("RedundantSuspendModifier") @WorkerThread suspend fun getSubscription(baseUrl: String, topic: String): Subscription? { return toSubscription(subscriptionDao.get(baseUrl, topic)) } @Suppress("RedundantSuspendModifier") @WorkerThread suspend fun addSubscription(subscription: Subscription) { subscriptionDao.add(subscription) } @Suppress("RedundantSuspendModifier") @WorkerThread suspend fun updateSubscription(subscription: Subscription) { subscriptionDao.update(subscription) } @Suppress("RedundantSuspendModifier") @WorkerThread suspend fun removeSubscription(subscriptionId: Long) { subscriptionDao.remove(subscriptionId) } fun getNotificationsLiveData(subscriptionId: Long): LiveData> { return notificationDao.listFlow(subscriptionId).asLiveData() } fun clearAllNotificationIds(subscriptionId: Long) { return notificationDao.clearAllNotificationIds(subscriptionId) } fun getNotification(notificationId: String): Notification? { return notificationDao.get(notificationId) } fun onlyNewNotifications(subscriptionId: Long, notifications: List): List { val existingIds = notificationDao.listIds(subscriptionId) return notifications.filterNot { existingIds.contains(it.id) } } @Suppress("RedundantSuspendModifier") @WorkerThread suspend fun addNotification(notification: Notification): Boolean { val maybeExistingNotification = notificationDao.get(notification.id) if (maybeExistingNotification != null) { return false } notificationDao.add(notification) return true } @Suppress("RedundantSuspendModifier") @WorkerThread suspend fun markAsDeleted(notificationId: String) { notificationDao.markAsDeleted(notificationId) } fun markAllAsDeleted(subscriptionId: Long) { notificationDao.markAllAsDeleted(subscriptionId) } @Suppress("RedundantSuspendModifier") @WorkerThread fun removeAllNotifications(subscriptionId: Long) { notificationDao.removeAll(subscriptionId) } fun getPollWorkerVersion(): Int { return sharedPrefs.getInt(SHARED_PREFS_POLL_WORKER_VERSION, 0) } fun setPollWorkerVersion(version: Int) { sharedPrefs.edit() .putInt(SHARED_PREFS_POLL_WORKER_VERSION, version) .apply() } fun getAutoRestartWorkerVersion(): Int { return sharedPrefs.getInt(SHARED_PREFS_AUTO_RESTART_WORKER_VERSION, 0) } fun setAutoRestartWorkerVersion(version: Int) { sharedPrefs.edit() .putInt(SHARED_PREFS_AUTO_RESTART_WORKER_VERSION, version) .apply() } private suspend fun isMuted(subscriptionId: Long): Boolean { if (isGlobalMuted()) { return true } val s = getSubscription(subscriptionId) ?: return true return s.mutedUntil == 1L || (s.mutedUntil > 1L && s.mutedUntil > System.currentTimeMillis()/1000) } fun isGlobalMuted(): Boolean { val mutedUntil = getGlobalMutedUntil() return mutedUntil == 1L || (mutedUntil > 1L && mutedUntil > System.currentTimeMillis()/1000) } fun getGlobalMutedUntil(): Long { return sharedPrefs.getLong(SHARED_PREFS_MUTED_UNTIL_TIMESTAMP, 0L) } fun setGlobalMutedUntil(mutedUntilTimestamp: Long) { sharedPrefs.edit() .putLong(SHARED_PREFS_MUTED_UNTIL_TIMESTAMP, mutedUntilTimestamp) .apply() } fun checkGlobalMutedUntil(): Boolean { val mutedUntil = sharedPrefs.getLong(SHARED_PREFS_MUTED_UNTIL_TIMESTAMP, 0L) val expired = mutedUntil > 1L && System.currentTimeMillis()/1000 > mutedUntil if (expired) { sharedPrefs.edit() .putLong(SHARED_PREFS_MUTED_UNTIL_TIMESTAMP, 0L) .apply() return true } return false } private fun toSubscriptionList(list: List): List { return list.map { s -> val connectionState = connectionStates.getOrElse(s.id) { ConnectionState.NOT_APPLICABLE } Subscription( id = s.id, baseUrl = s.baseUrl, topic = s.topic, instant = s.instant, mutedUntil = s.mutedUntil, upAppId = s.upAppId, upConnectorToken = s.upConnectorToken, totalCount = s.totalCount, newCount = s.newCount, lastActive = s.lastActive, state = connectionState ) } } private fun toSubscription(s: SubscriptionWithMetadata?): Subscription? { if (s == null) { return null } return Subscription( id = s.id, baseUrl = s.baseUrl, topic = s.topic, instant = s.instant, mutedUntil = s.mutedUntil, upAppId = s.upAppId, upConnectorToken = s.upConnectorToken, totalCount = s.totalCount, newCount = s.newCount, lastActive = s.lastActive, state = getState(s.id) ) } fun updateState(subscriptionIds: Collection, newState: ConnectionState) { var changed = false subscriptionIds.forEach { subscriptionId -> val state = connectionStates.getOrElse(subscriptionId) { ConnectionState.NOT_APPLICABLE } if (state !== newState) { changed = true if (newState == ConnectionState.NOT_APPLICABLE) { connectionStates.remove(subscriptionId) } else { connectionStates[subscriptionId] = newState } } } if (changed) { connectionStatesLiveData.postValue(connectionStates) } } private fun getState(subscriptionId: Long): ConnectionState { return connectionStatesLiveData.value!!.getOrElse(subscriptionId) { ConnectionState.NOT_APPLICABLE } } companion object { const val SHARED_PREFS_ID = "MainPreferences" const val SHARED_PREFS_POLL_WORKER_VERSION = "PollWorkerVersion" const val SHARED_PREFS_AUTO_RESTART_WORKER_VERSION = "AutoRestartWorkerVersion" const val SHARED_PREFS_MUTED_UNTIL_TIMESTAMP = "MutedUntil" private const val TAG = "NtfyRepository" private var instance: Repository? = null fun getInstance(sharedPrefs: SharedPreferences, subscriptionDao: SubscriptionDao, notificationDao: NotificationDao): Repository { return synchronized(Repository::class) { val newInstance = instance ?: Repository(sharedPrefs, subscriptionDao, notificationDao) instance = newInstance newInstance } } } } /* https://stackoverflow.com/a/57079290/1440785 */ fun LiveData.combineWith( liveData: LiveData, block: (T?, K?) -> R ): LiveData { val result = MediatorLiveData() result.addSource(this) { result.value = block(this.value, liveData.value) } result.addSource(liveData) { result.value = block(this.value, liveData.value) } return result }