diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt index 4aae1b70a..78b89fcbe 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt @@ -26,8 +26,6 @@ import android.util.Log import androidx.security.crypto.EncryptedSharedPreferences import coil3.disk.DiskCache import coil3.memory.MemoryCache -import com.vitorpamplona.amethyst.model.Account -import com.vitorpamplona.amethyst.model.AccountSettings import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.accountsCache.AccountCacheState import com.vitorpamplona.amethyst.service.connectivity.ConnectivityManager @@ -54,13 +52,9 @@ import com.vitorpamplona.amethyst.service.relayClient.speedLogger.RelaySpeedLogg import com.vitorpamplona.amethyst.service.uploads.nip95.Nip95CacheFactory import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav.scope import com.vitorpamplona.amethyst.ui.tor.TorManager -import com.vitorpamplona.quartz.nip01Core.core.HexKey -import com.vitorpamplona.quartz.nip01Core.core.toHexKey import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient -import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal import com.vitorpamplona.quartz.nip03Timestamp.VerificationStateCache import com.vitorpamplona.quartz.nip03Timestamp.ots.okhttp.OtsBlockHeightCache -import com.vitorpamplona.quartz.nip55AndroidSigner.client.NostrSignerExternal import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -161,13 +155,14 @@ class Amethyst : Application() { // Coordinates all subscriptions for the Nostr Client val sources: RelaySubscriptionsCoordinator = RelaySubscriptionsCoordinator(LocalCache, client, applicationIOScope) + // keeps all accounts live val accountsCache = AccountCacheState( geolocationFlow = locationManager.geohashStateFlow, nwcFilterAssembler = sources.nwc, + contentResolver = contentResolver, cache = cache, client = client, - scope = applicationIOScope, ) // saves the .content of NIP-95 blobs in disk to save memory @@ -242,31 +237,6 @@ class Amethyst : Application() { } } - fun loadAccount(accountSettings: AccountSettings): Account { - val keyPair = accountSettings.keyPair - return accountsCache.loadAccount( - signer = - if (keyPair.privKey != null) { - NostrSignerInternal(keyPair) - } else { - when (val packageName = accountSettings.externalSignerPackageName) { - null -> NostrSignerInternal(keyPair) - else -> - NostrSignerExternal( - pubKey = keyPair.pubKey.toHexKey(), - packageName = packageName, - contentResolver = contentResolver, - ) - } - }, - accountSettings = accountSettings, - ) - } - - fun removeAccount(pubkey: HexKey) { - accountsCache.removeAccount(pubkey) - } - companion object { lateinit var instance: Amethyst private set diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/accountsCache/AccountCacheState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/accountsCache/AccountCacheState.kt index a2ad9dcc2..206c4a57f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/accountsCache/AccountCacheState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/accountsCache/AccountCacheState.kt @@ -20,6 +20,8 @@ */ package com.vitorpamplona.amethyst.model.accountsCache +import android.content.ContentResolver +import android.util.Log import androidx.collection.LruCache import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.AccountSettings @@ -27,22 +29,59 @@ import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.service.location.LocationState import com.vitorpamplona.amethyst.service.relayClient.reqCommand.nwc.NWCPaymentFilterAssembler import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.toHexKey import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner +import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal +import com.vitorpamplona.quartz.nip55AndroidSigner.client.NostrSignerExternal +import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.StateFlow class AccountCacheState( val geolocationFlow: StateFlow, val nwcFilterAssembler: NWCPaymentFilterAssembler, + val contentResolver: ContentResolver, val cache: LocalCache, val client: INostrClient, - val scope: CoroutineScope, ) { - val accounts = LruCache(20) + val accounts = + object : LruCache(20) { + override fun entryRemoved( + evicted: Boolean, + key: HexKey, + oldValue: Account, + newValue: Account?, + ) { + super.entryRemoved(evicted, key, oldValue, newValue) + oldValue.scope.cancel() + } + } fun removeAccount(pubkey: HexKey) = accounts.remove(pubkey) + fun loadAccount(accountSettings: AccountSettings): Account = + loadAccount( + signer = + if (accountSettings.keyPair.privKey != null) { + NostrSignerInternal(accountSettings.keyPair) + } else { + when (val packageName = accountSettings.externalSignerPackageName) { + null -> NostrSignerInternal(accountSettings.keyPair) + else -> + NostrSignerExternal( + pubKey = accountSettings.keyPair.pubKey.toHexKey(), + packageName = packageName, + contentResolver = contentResolver, + ) + } + }, + accountSettings = accountSettings, + ) + fun loadAccount( signer: NostrSigner, accountSettings: AccountSettings, @@ -57,7 +96,14 @@ class AccountCacheState( nwcFilterAssembler = nwcFilterAssembler, cache = cache, client = client, - scope = scope, + scope = + CoroutineScope( + Dispatchers.Default + + SupervisorJob() + + CoroutineExceptionHandler { _, throwable -> + Log.e("AccountCacheState", "Account ${signer.pubKey} caught exception: ${throwable.message}", throwable) + }, + ), ).also { accounts.put(signer.pubKey, it) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt index 8cb296032..3b0727d02 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt @@ -71,7 +71,7 @@ class EventNotificationConsumer( LocalPreferences.loadAccountConfigFromEncryptedStorage(it.npub)?.let { acc -> Log.d(TAG, "New Notification Testing if for ${it.npub}") try { - val account = Amethyst.instance.loadAccount(acc) + val account = Amethyst.instance.accountsCache.loadAccount(acc) consumeIfMatchesAccount(event, account) matchAccount = true } catch (e: Exception) { @@ -128,7 +128,7 @@ class EventNotificationConsumer( LocalPreferences.loadAccountConfigFromEncryptedStorage(it.npub)?.let { accountSettings -> Log.d(TAG, "New Notification Testing if for ${it.npub}") try { - val account = Amethyst.instance.loadAccount(accountSettings) + val account = Amethyst.instance.accountsCache.loadAccount(accountSettings) consumeNotificationEvent(event, account) matchAccount = true } catch (e: Exception) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/RegisterAccounts.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/RegisterAccounts.kt index 127cda20f..15f051305 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/RegisterAccounts.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/RegisterAccounts.kt @@ -59,7 +59,7 @@ class RegisterAccounts( } return mapNotNullAsync(remainingTos) { info -> - val account = Amethyst.instance.loadAccount(info.accountSettings) + val account = Amethyst.instance.accountsCache.loadAccount(info.accountSettings) RelayAuthEvent.create(info.relays, notificationToken, account.signer) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt index aa613a6b0..836e6370c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt @@ -185,7 +185,7 @@ class AccountStateViewModel : ViewModel() { route: Route? = null, ) = withContext(Dispatchers.Main) { _accountContent.update { - AccountState.LoggedIn(Amethyst.instance.loadAccount(accountSettings), route) + AccountState.LoggedIn(Amethyst.instance.accountsCache.loadAccount(accountSettings), route) } collectorJob?.cancel() @@ -390,12 +390,12 @@ class AccountStateViewModel : ViewModel() { // log off and relogin with the 0 account prepareLogoutOrSwitch() LocalPreferences.deleteAccount(accountInfo) - Amethyst.instance.removeAccount(accountInfo.npub.bechToBytes().toHexKey()) + Amethyst.instance.accountsCache.removeAccount(accountInfo.npub.bechToBytes().toHexKey()) loginWithDefaultAccount() } else { // delete without switching logins LocalPreferences.deleteAccount(accountInfo) - Amethyst.instance.removeAccount(accountInfo.npub.bechToBytes().toHexKey()) + Amethyst.instance.accountsCache.removeAccount(accountInfo.npub.bechToBytes().toHexKey()) } } }