Parallelizes the JSON parsing from stored account.

This commit is contained in:
Vitor Pamplona
2026-03-26 11:11:21 -04:00
parent 958a460ea9
commit 7952f558eb
@@ -25,9 +25,31 @@ import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.core.content.edit import androidx.core.content.edit
import coil3.util.CoilUtils.result
import com.vitorpamplona.amethyst.model.AccountSettings import com.vitorpamplona.amethyst.model.AccountSettings
import com.vitorpamplona.amethyst.model.TopFilter import com.vitorpamplona.amethyst.model.TopFilter
import com.vitorpamplona.amethyst.model.UiSettings import com.vitorpamplona.amethyst.model.UiSettings
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.defaultDiscoveryFollowList
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.defaultFileServer
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.defaultHomeFollowList
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.defaultNotificationFollowList
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.defaultStoriesFollowList
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.hasDonatedInVersion
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.hideBlockAlertDialog
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.hideDeleteRequestDialog
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.latestAppSpecificData
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.latestBlockedRelayList
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.latestChannelList
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.latestCommunityList
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.latestContactList
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.latestGeohashList
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.latestHashtagList
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.latestMuteList
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.latestPrivateHomeRelayList
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.latestSearchRelayList
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.latestTrustedRelayList
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.latestUserMetadata
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.localRelayServers
import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.ui.actions.mediaServers.DEFAULT_MEDIA_SERVERS import com.vitorpamplona.amethyst.ui.actions.mediaServers.DEFAULT_MEDIA_SERVERS
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerName import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerName
@@ -62,6 +84,7 @@ import com.vitorpamplona.quartz.nip78AppData.AppSpecificDataEvent
import com.vitorpamplona.quartz.utils.Log import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
@@ -448,96 +471,128 @@ object LocalPreferences {
Log.d("LocalPreferences", "Load account from file $npub") Log.d("LocalPreferences", "Load account from file $npub")
val result = val result =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
checkNotInMainThread()
return@withContext with(encryptedPreferences(npub)) { return@withContext with(encryptedPreferences(npub)) {
Log.d("LocalPreferences", "Load account from file $npub - opened file")
val privKey = getString(PrefKeys.NOSTR_PRIVKEY, null) val privKey = getString(PrefKeys.NOSTR_PRIVKEY, null)
val pubKey = getString(PrefKeys.NOSTR_PUBKEY, null) ?: return@with null val pubKey = getString(PrefKeys.NOSTR_PUBKEY, null) ?: return@with null
val externalSignerPackageName = val externalSignerPackageName = getString(PrefKeys.SIGNER_PACKAGE_NAME, null) ?: if (getBoolean(PrefKeys.LOGIN_WITH_EXTERNAL_SIGNER, false)) "com.greenart7c3.nostrsigner" else null
getString(PrefKeys.SIGNER_PACKAGE_NAME, null)
?: if (getBoolean(PrefKeys.LOGIN_WITH_EXTERNAL_SIGNER, false)) "com.greenart7c3.nostrsigner" else null
val defaultHomeFollowList = parseOrNull<TopFilter>(PrefKeys.DEFAULT_HOME_FOLLOW_LIST) ?: TopFilter.AllFollows val keyPair = KeyPair(privKey = privKey?.hexToByteArray(), pubKey = pubKey.hexToByteArray())
val defaultStoriesFollowList = parseOrNull<TopFilter>(PrefKeys.DEFAULT_STORIES_FOLLOW_LIST) ?: TopFilter.Global
val defaultNotificationFollowList = parseOrNull<TopFilter>(PrefKeys.DEFAULT_NOTIFICATION_FOLLOW_LIST) ?: TopFilter.Global Log.d("LocalPreferences", "Load account from file $npub - keys ready")
val defaultDiscoveryFollowList = parseOrNull<TopFilter>(PrefKeys.DEFAULT_DISCOVERY_FOLLOW_LIST) ?: TopFilter.Global
val zapPaymentRequestServer = parseOrNull<Nip47WalletConnect.Nip47URI>(PrefKeys.ZAP_PAYMENT_REQUEST_SERVER)
val defaultFileServer = parseOrNull<ServerName>(PrefKeys.DEFAULT_FILE_SERVER) ?: DEFAULT_MEDIA_SERVERS[0]
val stripLocationOnUpload = getBoolean(PrefKeys.STRIP_LOCATION_ON_UPLOAD, true) val stripLocationOnUpload = getBoolean(PrefKeys.STRIP_LOCATION_ON_UPLOAD, true)
val pendingAttestations = parseOrNull<Map<HexKey, String>>(PrefKeys.PENDING_ATTESTATIONS) ?: mapOf()
val localRelayServers = getStringSet(PrefKeys.LOCAL_RELAY_SERVERS, null) ?: setOf()
val latestUserMetadata = parseEventOrNull<MetadataEvent>(PrefKeys.LATEST_USER_METADATA)
val latestContactList = parseEventOrNull<ContactListEvent>(PrefKeys.LATEST_CONTACT_LIST)
val latestDmRelayList = parseEventOrNull<ChatMessageRelayListEvent>(PrefKeys.LATEST_DM_RELAY_LIST)
val latestNip65RelayList = parseEventOrNull<AdvertisedRelayListEvent>(PrefKeys.LATEST_NIP65_RELAY_LIST)
val latestSearchRelayList = parseEventOrNull<SearchRelayListEvent>(PrefKeys.LATEST_SEARCH_RELAY_LIST)
val latestIndexRelayList = parseEventOrNull<IndexerRelayListEvent>(PrefKeys.LATEST_INDEX_RELAY_LIST)
val latestRelayFeedsList = parseEventOrNull<RelayFeedsListEvent>(PrefKeys.LATEST_RELAY_FEEDS_LIST)
val latestBlockedRelayList = parseEventOrNull<BlockedRelayListEvent>(PrefKeys.LATEST_BLOCKED_RELAY_LIST)
val latestTrustedRelayList = parseEventOrNull<TrustedRelayListEvent>(PrefKeys.LATEST_TRUSTED_RELAY_LIST)
val latestMuteList = parseEventOrNull<MuteListEvent>(PrefKeys.LATEST_MUTE_LIST)
val latestPrivateHomeRelayList = parseEventOrNull<PrivateOutboxRelayListEvent>(PrefKeys.LATEST_PRIVATE_HOME_RELAY_LIST)
val latestAppSpecificData = parseEventOrNull<AppSpecificDataEvent>(PrefKeys.LATEST_APP_SPECIFIC_DATA)
val latestChannelList = parseEventOrNull<ChannelListEvent>(PrefKeys.LATEST_CHANNEL_LIST)
val latestCommunityList = parseEventOrNull<CommunityListEvent>(PrefKeys.LATEST_COMMUNITY_LIST)
val latestHashtagList = parseEventOrNull<HashtagListEvent>(PrefKeys.LATEST_HASHTAG_LIST)
val latestGeohashList = parseEventOrNull<GeohashListEvent>(PrefKeys.LATEST_GEOHASH_LIST)
val latestEphemeralList = parseEventOrNull<EphemeralChatListEvent>(PrefKeys.LATEST_EPHEMERAL_LIST)
val latestTrustProviderList = parseEventOrNull<TrustProviderListEvent>(PrefKeys.LATEST_TRUST_PROVIDER_LIST)
val latestPaymentTargets = parseEventOrNull<PaymentTargetsEvent>(PrefKeys.LATEST_PAYMENT_TARGETS)
val hideDeleteRequestDialog = getBoolean(PrefKeys.HIDE_DELETE_REQUEST_DIALOG, false) val hideDeleteRequestDialog = getBoolean(PrefKeys.HIDE_DELETE_REQUEST_DIALOG, false)
val hideBlockAlertDialog = getBoolean(PrefKeys.HIDE_BLOCK_ALERT_DIALOG, false) val hideBlockAlertDialog = getBoolean(PrefKeys.HIDE_BLOCK_ALERT_DIALOG, false)
val hideNIP17WarningDialog = getBoolean(PrefKeys.HIDE_NIP_17_WARNING_DIALOG, false) val hideNIP17WarningDialog = getBoolean(PrefKeys.HIDE_NIP_17_WARNING_DIALOG, false)
val hasDonatedInVersion = getStringSet(PrefKeys.HAS_DONATED_IN_VERSION, null) ?: setOf()
val localRelayServers = getStringSet(PrefKeys.LOCAL_RELAY_SERVERS, null) ?: setOf()
val defaultHomeFollowListStr = getString(PrefKeys.DEFAULT_HOME_FOLLOW_LIST, null)
val defaultStoriesFollowListStr = getString(PrefKeys.DEFAULT_STORIES_FOLLOW_LIST, null)
val defaultNotificationFollowListStr = getString(PrefKeys.DEFAULT_NOTIFICATION_FOLLOW_LIST, null)
val defaultDiscoveryFollowListStr = getString(PrefKeys.DEFAULT_DISCOVERY_FOLLOW_LIST, null)
val zapPaymentRequestServerStr = getString(PrefKeys.ZAP_PAYMENT_REQUEST_SERVER, null)
val defaultFileServerStr = getString(PrefKeys.DEFAULT_FILE_SERVER, null)
val pendingAttestationsStr = getString(PrefKeys.PENDING_ATTESTATIONS, null)
val latestUserMetadataStr = getString(PrefKeys.LATEST_USER_METADATA, null)
val latestContactListStr = getString(PrefKeys.LATEST_CONTACT_LIST, null)
val latestDmRelayListStr = getString(PrefKeys.LATEST_DM_RELAY_LIST, null)
val latestNip65RelayListStr = getString(PrefKeys.LATEST_NIP65_RELAY_LIST, null)
val latestSearchRelayListStr = getString(PrefKeys.LATEST_SEARCH_RELAY_LIST, null)
val latestIndexRelayListStr = getString(PrefKeys.LATEST_INDEX_RELAY_LIST, null)
val latestRelayFeedsListStr = getString(PrefKeys.LATEST_RELAY_FEEDS_LIST, null)
val latestBlockedRelayListStr = getString(PrefKeys.LATEST_BLOCKED_RELAY_LIST, null)
val latestTrustedRelayListStr = getString(PrefKeys.LATEST_TRUSTED_RELAY_LIST, null)
val latestMuteListStr = getString(PrefKeys.LATEST_MUTE_LIST, null)
val latestPrivateHomeRelayListStr = getString(PrefKeys.LATEST_PRIVATE_HOME_RELAY_LIST, null)
val latestAppSpecificDataStr = getString(PrefKeys.LATEST_APP_SPECIFIC_DATA, null)
val latestChannelListStr = getString(PrefKeys.LATEST_CHANNEL_LIST, null)
val latestCommunityListStr = getString(PrefKeys.LATEST_COMMUNITY_LIST, null)
val latestHashtagListStr = getString(PrefKeys.LATEST_HASHTAG_LIST, null)
val latestGeohashListStr = getString(PrefKeys.LATEST_GEOHASH_LIST, null)
val latestEphemeralListStr = getString(PrefKeys.LATEST_EPHEMERAL_LIST, null)
val latestTrustProviderListStr = getString(PrefKeys.LATEST_TRUST_PROVIDER_LIST, null)
val latestPaymentTargetsStr = getString(PrefKeys.LATEST_PAYMENT_TARGETS, null)
val lastReadPerRouteStr = getString(PrefKeys.LAST_READ_PER_ROUTE, null)
Log.d("LocalPreferences", "Load account from file $npub - before parsing events")
val defaultHomeFollowList = async { parseOrNull<TopFilter>(defaultHomeFollowListStr) ?: TopFilter.AllFollows }
val defaultStoriesFollowList = async { parseOrNull<TopFilter>(defaultStoriesFollowListStr) ?: TopFilter.Global }
val defaultNotificationFollowList = async { parseOrNull<TopFilter>(defaultNotificationFollowListStr) ?: TopFilter.Global }
val defaultDiscoveryFollowList = async { parseOrNull<TopFilter>(defaultDiscoveryFollowListStr) ?: TopFilter.Global }
val zapPaymentRequestServer = async { parseOrNull<Nip47WalletConnect.Nip47URI>(zapPaymentRequestServerStr) }
val defaultFileServer = async { parseOrNull<ServerName>(defaultFileServerStr) ?: DEFAULT_MEDIA_SERVERS[0] }
val pendingAttestations = async { parseOrNull<Map<HexKey, String>>(pendingAttestationsStr) ?: mapOf() }
val latestUserMetadata = async { parseEventOrNull<MetadataEvent>(latestUserMetadataStr) }
val latestContactList = async { parseEventOrNull<ContactListEvent>(latestContactListStr) }
val latestDmRelayList = async { parseEventOrNull<ChatMessageRelayListEvent>(latestDmRelayListStr) }
val latestNip65RelayList = async { parseEventOrNull<AdvertisedRelayListEvent>(latestNip65RelayListStr) }
val latestSearchRelayList = async { parseEventOrNull<SearchRelayListEvent>(latestSearchRelayListStr) }
val latestIndexRelayList = async { parseEventOrNull<IndexerRelayListEvent>(latestIndexRelayListStr) }
val latestRelayFeedsList = async { parseEventOrNull<RelayFeedsListEvent>(latestRelayFeedsListStr) }
val latestBlockedRelayList = async { parseEventOrNull<BlockedRelayListEvent>(latestBlockedRelayListStr) }
val latestTrustedRelayList = async { parseEventOrNull<TrustedRelayListEvent>(latestTrustedRelayListStr) }
val latestMuteList = async { parseEventOrNull<MuteListEvent>(latestMuteListStr) }
val latestPrivateHomeRelayList = async { parseEventOrNull<PrivateOutboxRelayListEvent>(latestPrivateHomeRelayListStr) }
val latestAppSpecificData = async { parseEventOrNull<AppSpecificDataEvent>(latestAppSpecificDataStr) }
val latestChannelList = async { parseEventOrNull<ChannelListEvent>(latestChannelListStr) }
val latestCommunityList = async { parseEventOrNull<CommunityListEvent>(latestCommunityListStr) }
val latestHashtagList = async { parseEventOrNull<HashtagListEvent>(latestHashtagListStr) }
val latestGeohashList = async { parseEventOrNull<GeohashListEvent>(latestGeohashListStr) }
val latestEphemeralList = async { parseEventOrNull<EphemeralChatListEvent>(latestEphemeralListStr) }
val latestTrustProviderList = async { parseEventOrNull<TrustProviderListEvent>(latestTrustProviderListStr) }
val latestPaymentTargets = async { parseEventOrNull<PaymentTargetsEvent>(latestPaymentTargetsStr) }
val lastReadPerRoute = val lastReadPerRoute =
parseOrNull<Map<String, Long>>(PrefKeys.LAST_READ_PER_ROUTE)?.mapValues { async {
MutableStateFlow(it.value) parseOrNull<Map<String, Long>>(lastReadPerRouteStr)?.mapValues {
} ?: mapOf() MutableStateFlow(it.value)
} ?: mapOf()
}
val keyPair = KeyPair(privKey = privKey?.hexToByteArray(), pubKey = pubKey.hexToByteArray()) Log.d("LocalPreferences", "Load account from file $npub - asyncs created")
val hasDonatedInVersion = getStringSet(PrefKeys.HAS_DONATED_IN_VERSION, null) ?: setOf()
return@with AccountSettings( return@with AccountSettings(
keyPair = keyPair, keyPair = keyPair,
transientAccount = false, transientAccount = false,
externalSignerPackageName = externalSignerPackageName, externalSignerPackageName = externalSignerPackageName,
localRelayServers = MutableStateFlow(localRelayServers), localRelayServers = MutableStateFlow(localRelayServers),
defaultFileServer = defaultFileServer, defaultFileServer = defaultFileServer.await(),
stripLocationOnUpload = stripLocationOnUpload, stripLocationOnUpload = stripLocationOnUpload,
defaultHomeFollowList = MutableStateFlow(defaultHomeFollowList), defaultHomeFollowList = MutableStateFlow(defaultHomeFollowList.await()),
defaultStoriesFollowList = MutableStateFlow(defaultStoriesFollowList), defaultStoriesFollowList = MutableStateFlow(defaultStoriesFollowList.await()),
defaultNotificationFollowList = MutableStateFlow(defaultNotificationFollowList), defaultNotificationFollowList = MutableStateFlow(defaultNotificationFollowList.await()),
defaultDiscoveryFollowList = MutableStateFlow(defaultDiscoveryFollowList), defaultDiscoveryFollowList = MutableStateFlow(defaultDiscoveryFollowList.await()),
zapPaymentRequest = MutableStateFlow(zapPaymentRequestServer?.normalize()), zapPaymentRequest = MutableStateFlow(zapPaymentRequestServer.await()?.normalize()),
hideDeleteRequestDialog = hideDeleteRequestDialog, hideDeleteRequestDialog = hideDeleteRequestDialog,
hideBlockAlertDialog = hideBlockAlertDialog, hideBlockAlertDialog = hideBlockAlertDialog,
hideNIP17WarningDialog = hideNIP17WarningDialog, hideNIP17WarningDialog = hideNIP17WarningDialog,
backupUserMetadata = latestUserMetadata, backupUserMetadata = latestUserMetadata.await(),
backupContactList = latestContactList, backupContactList = latestContactList.await(),
backupNIP65RelayList = latestNip65RelayList, backupNIP65RelayList = latestNip65RelayList.await(),
backupDMRelayList = latestDmRelayList, backupDMRelayList = latestDmRelayList.await(),
backupSearchRelayList = latestSearchRelayList, backupSearchRelayList = latestSearchRelayList.await(),
backupIndexRelayList = latestIndexRelayList, backupIndexRelayList = latestIndexRelayList.await(),
backupRelayFeedsList = latestRelayFeedsList, backupRelayFeedsList = latestRelayFeedsList.await(),
backupBlockedRelayList = latestBlockedRelayList, backupBlockedRelayList = latestBlockedRelayList.await(),
backupTrustedRelayList = latestTrustedRelayList, backupTrustedRelayList = latestTrustedRelayList.await(),
backupPrivateHomeRelayList = latestPrivateHomeRelayList, backupPrivateHomeRelayList = latestPrivateHomeRelayList.await(),
backupMuteList = latestMuteList, backupMuteList = latestMuteList.await(),
backupAppSpecificData = latestAppSpecificData, backupAppSpecificData = latestAppSpecificData.await(),
backupChannelList = latestChannelList, backupChannelList = latestChannelList.await(),
backupCommunityList = latestCommunityList, backupCommunityList = latestCommunityList.await(),
backupHashtagList = latestHashtagList, backupHashtagList = latestHashtagList.await(),
backupGeohashList = latestGeohashList, backupGeohashList = latestGeohashList.await(),
backupEphemeralChatList = latestEphemeralList, backupEphemeralChatList = latestEphemeralList.await(),
backupTrustProviderList = latestTrustProviderList, backupTrustProviderList = latestTrustProviderList.await(),
lastReadPerRoute = MutableStateFlow(lastReadPerRoute), lastReadPerRoute = MutableStateFlow(lastReadPerRoute.await()),
hasDonatedInVersion = MutableStateFlow(hasDonatedInVersion), hasDonatedInVersion = MutableStateFlow(hasDonatedInVersion),
pendingAttestations = MutableStateFlow(pendingAttestations), pendingAttestations = MutableStateFlow(pendingAttestations.await()),
backupNipA3PaymentTargets = latestPaymentTargets, backupNipA3PaymentTargets = latestPaymentTargets.await(),
) )
} }
} }
@@ -545,8 +600,7 @@ object LocalPreferences {
return result return result
} }
private inline fun <reified T : Any> SharedPreferences.parseOrNull(key: String): T? { private inline fun <reified T : Any> parseOrNull(value: String?): T? {
val value = getString(key, null)
if (value.isNullOrEmpty() || value == "null") { if (value.isNullOrEmpty() || value == "null") {
return null return null
} }
@@ -558,13 +612,12 @@ object LocalPreferences {
} }
} catch (e: Throwable) { } catch (e: Throwable) {
if (e is CancellationException) throw e if (e is CancellationException) throw e
Log.w("LocalPreferences", "Error Decoding $key from Preferences with value $value", e) Log.w("LocalPreferences", "Error Decoding ${T::class.java} from Preferences with value $value", e)
null null
} }
} }
private inline fun <reified T> SharedPreferences.parseEventOrNull(key: String): T? { private inline fun <reified T> parseEventOrNull(value: String?): T? {
val value = getString(key, null)
if (value.isNullOrEmpty() || value == "null") { if (value.isNullOrEmpty() || value == "null") {
return null return null
} }
@@ -572,7 +625,7 @@ object LocalPreferences {
Event.fromJson(value) as T? Event.fromJson(value) as T?
} catch (e: Throwable) { } catch (e: Throwable) {
if (e is CancellationException) throw e if (e is CancellationException) throw e
Log.w("LocalPreferences", "Error Decoding $key from Preferences with value $value", e) Log.w("LocalPreferences", "Error Decoding ${T::class.java} from Preferences with value $value", e)
null null
} }
} }