From 05cb055b1d1d290ba90b3c096cf81953bd01699d Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 1 Apr 2025 13:58:57 -0400 Subject: [PATCH] Migrates FDroid's push system to match Play services --- .../notifications/PushMessageReceiver.kt | 5 +- .../notifications/PushNotificationUtils.kt | 57 +++++++++++++------ .../amethyst/service/CoroutinesExt.kt | 51 +++++++++++++++++ .../playback/playerPool/MediaSessionPool.kt | 2 +- .../notifications/PushNotificationUtils.kt | 30 +--------- 5 files changed, 98 insertions(+), 47 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/service/CoroutinesExt.kt diff --git a/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushMessageReceiver.kt b/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushMessageReceiver.kt index 20d697e8b..5190b15cb 100644 --- a/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushMessageReceiver.kt +++ b/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushMessageReceiver.kt @@ -29,6 +29,8 @@ import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.LocalPreferences import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.getOrCreateDMChannel import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.getOrCreateZapChannel +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager +import com.vitorpamplona.amethyst.ui.tor.TorManager import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent import kotlinx.coroutines.CancellationException @@ -89,7 +91,8 @@ class PushMessageReceiver : MessagingReceiver() { Log.d(TAG, "New endpoint provided:- $endpoint for Instance: $instance ${pushHandler.getSavedEndpoint()} $sanitizedEndpoint") pushHandler.setEndpoint(sanitizedEndpoint) scope.launch(Dispatchers.IO) { - RegisterAccounts(LocalPreferences.allSavedAccounts()).go(sanitizedEndpoint) + val okHttpClient = HttpClientManager.getHttpClient(TorManager.isSocksReady()) + PushNotificationUtils.checkAndInit(sanitizedEndpoint, LocalPreferences.allSavedAccounts(), okHttpClient) notificationManager().getOrCreateZapChannel(appContext) notificationManager().getOrCreateDMChannel(appContext) } diff --git a/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationUtils.kt b/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationUtils.kt index 21ca41b1f..8b61c4656 100644 --- a/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationUtils.kt +++ b/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationUtils.kt @@ -20,28 +20,53 @@ */ package com.vitorpamplona.amethyst.service.notifications -import android.util.Log import com.vitorpamplona.amethyst.AccountInfo -import kotlinx.coroutines.CancellationException +import com.vitorpamplona.amethyst.service.retryIfException +import kotlinx.coroutines.Dispatchers +import okhttp3.OkHttpClient object PushNotificationUtils { - var hasInit: Boolean = false + var lastToken: String? = null + var hasInit: List? = null + private val pushHandler = PushDistributorHandler - suspend fun init(accounts: List) { - if (hasInit) { - return - } - try { - if (pushHandler.savedDistributorExists()) { - val currentDistributor = PushDistributorHandler.getSavedDistributor() - PushDistributorHandler.saveDistributor(currentDistributor) + suspend fun checkAndInit( + accounts: List, + okHttpClient: OkHttpClient, + ) = with(Dispatchers.IO) { + if (!pushHandler.savedDistributorExists()) return - RegisterAccounts(accounts).go(pushHandler.getSavedEndpoint()) - } - } catch (e: Exception) { - if (e is CancellationException) throw e - Log.d("Amethyst-OSSPushUtils", "Failed to get endpoint.") + val currentDistributor = PushDistributorHandler.getSavedDistributor() + PushDistributorHandler.saveDistributor(currentDistributor) + val token = pushHandler.getSavedEndpoint() + + if (hasInit?.equals(accounts) == true && lastToken == token) { + return@with } + + registerToken(token, accounts, okHttpClient) + } + + suspend fun checkAndInit( + token: String, + accounts: List, + okHttpClient: OkHttpClient, + ) = with(Dispatchers.IO) { + // initializes if the accounts are different or if the token has changed + if (hasInit?.equals(accounts) == true && lastToken == token) { + return@with + } + registerToken(token, accounts, okHttpClient) + } + + private suspend fun registerToken( + token: String, + accounts: List, + okHttpClient: OkHttpClient, + ) = retryIfException("RegisterAccounts") { + RegisterAccounts(accounts, okHttpClient).go(token) + lastToken = token + hasInit = accounts.toList() } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CoroutinesExt.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CoroutinesExt.kt new file mode 100644 index 000000000..e19df5073 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CoroutinesExt.kt @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.service + +import android.util.Log +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.delay + +suspend fun retryIfException( + debugTag: String = "RetryIfException", + maxRetries: Int = 10, + delayMs: Long = 1000, + func: suspend () -> T, +) { + var tentative = 0 + var currentDelay = delayMs + while (tentative < maxRetries) { + try { + func() + + // if it works, finishes. + return + } catch (e: Exception) { + if (e is CancellationException) throw e + Log.e(debugTag, "Tentative $tentative failed", e) + + delay(currentDelay) + tentative++ + currentDelay = currentDelay * 2 + } + } + // gives up +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/playerPool/MediaSessionPool.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/playerPool/MediaSessionPool.kt index 8b14591f3..2a9164241 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/playerPool/MediaSessionPool.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/playerPool/MediaSessionPool.kt @@ -83,7 +83,7 @@ class MediaSessionPool( } } - @UnstableApi + @OptIn(UnstableApi::class) fun newSession( id: String, context: Context, diff --git a/amethyst/src/play/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationUtils.kt b/amethyst/src/play/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationUtils.kt index 97c0c9299..6b97f51e5 100644 --- a/amethyst/src/play/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationUtils.kt +++ b/amethyst/src/play/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationUtils.kt @@ -20,12 +20,10 @@ */ package com.vitorpamplona.amethyst.service.notifications -import android.util.Log import com.google.firebase.messaging.FirebaseMessaging import com.vitorpamplona.amethyst.AccountInfo -import kotlinx.coroutines.CancellationException +import com.vitorpamplona.amethyst.service.retryIfException import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.tasks.await import okhttp3.OkHttpClient @@ -67,29 +65,3 @@ object PushNotificationUtils { hasInit = accounts.toList() } } - -suspend fun retryIfException( - debugTag: String = "RetryIfException", - maxRetries: Int = 10, - delayMs: Long = 1000, - func: suspend () -> T, -) { - var tentative = 0 - var currentDelay = delayMs - while (tentative < maxRetries) { - try { - func() - - // if it works, finishes. - return - } catch (e: Exception) { - if (e is CancellationException) throw e - Log.e(debugTag, "Tentative $tentative failed", e) - - delay(currentDelay) - tentative++ - currentDelay = currentDelay * 2 - } - } - // gives up -}