Establishing a scope for each account loaded.

This commit is contained in:
Vitor Pamplona
2025-09-08 14:34:13 -04:00
parent 93a639fea5
commit 222615a116
5 changed files with 57 additions and 41 deletions

View File

@@ -26,8 +26,6 @@ import android.util.Log
import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.EncryptedSharedPreferences
import coil3.disk.DiskCache import coil3.disk.DiskCache
import coil3.memory.MemoryCache 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.LocalCache
import com.vitorpamplona.amethyst.model.accountsCache.AccountCacheState import com.vitorpamplona.amethyst.model.accountsCache.AccountCacheState
import com.vitorpamplona.amethyst.service.connectivity.ConnectivityManager 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.service.uploads.nip95.Nip95CacheFactory
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav.scope import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav.scope
import com.vitorpamplona.amethyst.ui.tor.TorManager 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.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
import com.vitorpamplona.quartz.nip03Timestamp.VerificationStateCache import com.vitorpamplona.quartz.nip03Timestamp.VerificationStateCache
import com.vitorpamplona.quartz.nip03Timestamp.ots.okhttp.OtsBlockHeightCache import com.vitorpamplona.quartz.nip03Timestamp.ots.okhttp.OtsBlockHeightCache
import com.vitorpamplona.quartz.nip55AndroidSigner.client.NostrSignerExternal
import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -161,13 +155,14 @@ class Amethyst : Application() {
// Coordinates all subscriptions for the Nostr Client // Coordinates all subscriptions for the Nostr Client
val sources: RelaySubscriptionsCoordinator = RelaySubscriptionsCoordinator(LocalCache, client, applicationIOScope) val sources: RelaySubscriptionsCoordinator = RelaySubscriptionsCoordinator(LocalCache, client, applicationIOScope)
// keeps all accounts live
val accountsCache = val accountsCache =
AccountCacheState( AccountCacheState(
geolocationFlow = locationManager.geohashStateFlow, geolocationFlow = locationManager.geohashStateFlow,
nwcFilterAssembler = sources.nwc, nwcFilterAssembler = sources.nwc,
contentResolver = contentResolver,
cache = cache, cache = cache,
client = client, client = client,
scope = applicationIOScope,
) )
// saves the .content of NIP-95 blobs in disk to save memory // 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 { companion object {
lateinit var instance: Amethyst lateinit var instance: Amethyst
private set private set

View File

@@ -20,6 +20,8 @@
*/ */
package com.vitorpamplona.amethyst.model.accountsCache package com.vitorpamplona.amethyst.model.accountsCache
import android.content.ContentResolver
import android.util.Log
import androidx.collection.LruCache import androidx.collection.LruCache
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AccountSettings 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.location.LocationState
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.nwc.NWCPaymentFilterAssembler import com.vitorpamplona.amethyst.service.relayClient.reqCommand.nwc.NWCPaymentFilterAssembler
import com.vitorpamplona.quartz.nip01Core.core.HexKey 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.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner 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.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
class AccountCacheState( class AccountCacheState(
val geolocationFlow: StateFlow<LocationState.LocationResult>, val geolocationFlow: StateFlow<LocationState.LocationResult>,
val nwcFilterAssembler: NWCPaymentFilterAssembler, val nwcFilterAssembler: NWCPaymentFilterAssembler,
val contentResolver: ContentResolver,
val cache: LocalCache, val cache: LocalCache,
val client: INostrClient, val client: INostrClient,
val scope: CoroutineScope,
) { ) {
val accounts = LruCache<HexKey, Account>(20) val accounts =
object : LruCache<HexKey, Account>(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 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( fun loadAccount(
signer: NostrSigner, signer: NostrSigner,
accountSettings: AccountSettings, accountSettings: AccountSettings,
@@ -57,7 +96,14 @@ class AccountCacheState(
nwcFilterAssembler = nwcFilterAssembler, nwcFilterAssembler = nwcFilterAssembler,
cache = cache, cache = cache,
client = client, client = client,
scope = scope, scope =
CoroutineScope(
Dispatchers.Default +
SupervisorJob() +
CoroutineExceptionHandler { _, throwable ->
Log.e("AccountCacheState", "Account ${signer.pubKey} caught exception: ${throwable.message}", throwable)
},
),
).also { ).also {
accounts.put(signer.pubKey, it) accounts.put(signer.pubKey, it)
} }

View File

@@ -71,7 +71,7 @@ class EventNotificationConsumer(
LocalPreferences.loadAccountConfigFromEncryptedStorage(it.npub)?.let { acc -> LocalPreferences.loadAccountConfigFromEncryptedStorage(it.npub)?.let { acc ->
Log.d(TAG, "New Notification Testing if for ${it.npub}") Log.d(TAG, "New Notification Testing if for ${it.npub}")
try { try {
val account = Amethyst.instance.loadAccount(acc) val account = Amethyst.instance.accountsCache.loadAccount(acc)
consumeIfMatchesAccount(event, account) consumeIfMatchesAccount(event, account)
matchAccount = true matchAccount = true
} catch (e: Exception) { } catch (e: Exception) {
@@ -128,7 +128,7 @@ class EventNotificationConsumer(
LocalPreferences.loadAccountConfigFromEncryptedStorage(it.npub)?.let { accountSettings -> LocalPreferences.loadAccountConfigFromEncryptedStorage(it.npub)?.let { accountSettings ->
Log.d(TAG, "New Notification Testing if for ${it.npub}") Log.d(TAG, "New Notification Testing if for ${it.npub}")
try { try {
val account = Amethyst.instance.loadAccount(accountSettings) val account = Amethyst.instance.accountsCache.loadAccount(accountSettings)
consumeNotificationEvent(event, account) consumeNotificationEvent(event, account)
matchAccount = true matchAccount = true
} catch (e: Exception) { } catch (e: Exception) {

View File

@@ -59,7 +59,7 @@ class RegisterAccounts(
} }
return mapNotNullAsync(remainingTos) { info -> 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) RelayAuthEvent.create(info.relays, notificationToken, account.signer)
} }
} }

View File

@@ -185,7 +185,7 @@ class AccountStateViewModel : ViewModel() {
route: Route? = null, route: Route? = null,
) = withContext(Dispatchers.Main) { ) = withContext(Dispatchers.Main) {
_accountContent.update { _accountContent.update {
AccountState.LoggedIn(Amethyst.instance.loadAccount(accountSettings), route) AccountState.LoggedIn(Amethyst.instance.accountsCache.loadAccount(accountSettings), route)
} }
collectorJob?.cancel() collectorJob?.cancel()
@@ -390,12 +390,12 @@ class AccountStateViewModel : ViewModel() {
// log off and relogin with the 0 account // log off and relogin with the 0 account
prepareLogoutOrSwitch() prepareLogoutOrSwitch()
LocalPreferences.deleteAccount(accountInfo) LocalPreferences.deleteAccount(accountInfo)
Amethyst.instance.removeAccount(accountInfo.npub.bechToBytes().toHexKey()) Amethyst.instance.accountsCache.removeAccount(accountInfo.npub.bechToBytes().toHexKey())
loginWithDefaultAccount() loginWithDefaultAccount()
} else { } else {
// delete without switching logins // delete without switching logins
LocalPreferences.deleteAccount(accountInfo) LocalPreferences.deleteAccount(accountInfo)
Amethyst.instance.removeAccount(accountInfo.npub.bechToBytes().toHexKey()) Amethyst.instance.accountsCache.removeAccount(accountInfo.npub.bechToBytes().toHexKey())
} }
} }
} }