mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-09-28 19:47:13 +02:00
Moves private zap decryption to async, all at once.
This commit is contained in:
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
|
import coil3.util.CoilUtils.result
|
||||||
import com.vitorpamplona.amethyst.model.nip51Lists.HiddenUsersState
|
import com.vitorpamplona.amethyst.model.nip51Lists.HiddenUsersState
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
import com.vitorpamplona.amethyst.service.firstFullCharOrEmoji
|
import com.vitorpamplona.amethyst.service.firstFullCharOrEmoji
|
||||||
@@ -68,6 +69,7 @@ import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprov
|
|||||||
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
|
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
|
||||||
import com.vitorpamplona.quartz.utils.Hex
|
import com.vitorpamplona.quartz.utils.Hex
|
||||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
import com.vitorpamplona.quartz.utils.anyAsync
|
||||||
import com.vitorpamplona.quartz.utils.containsAny
|
import com.vitorpamplona.quartz.utils.containsAny
|
||||||
import com.vitorpamplona.quartz.utils.launchAndWaitAll
|
import com.vitorpamplona.quartz.utils.launchAndWaitAll
|
||||||
import com.vitorpamplona.quartz.utils.tryAndWait
|
import com.vitorpamplona.quartz.utils.tryAndWait
|
||||||
@@ -496,6 +498,8 @@ open class Note(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val parallelDecrypt = mutableListOf<Pair<LnZapRequestEvent, LnZapEvent?>>()
|
||||||
|
|
||||||
zapEvents.forEach { next ->
|
zapEvents.forEach { next ->
|
||||||
val zapRequest = next.key.event as LnZapRequestEvent
|
val zapRequest = next.key.event as LnZapRequestEvent
|
||||||
val zapEvent = next.value?.event as? LnZapEvent
|
val zapEvent = next.value?.event as? LnZapEvent
|
||||||
@@ -518,21 +522,27 @@ open class Note(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (account.isWriteable()) {
|
if (account.isWriteable()) {
|
||||||
val result =
|
parallelDecrypt.add(Pair(zapRequest, zapEvent))
|
||||||
tryAndWait { continuation ->
|
|
||||||
zapRequest.decryptPrivateZap(account.signer) {
|
|
||||||
continuation.resume(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result?.pubKey == user.pubkeyHex && (option == null || option == zapEvent?.zappedPollOption())) {
|
|
||||||
onWasZappedByAuthor()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val result =
|
||||||
|
anyAsync(parallelDecrypt) { pair ->
|
||||||
|
val result =
|
||||||
|
tryAndWait { continuation ->
|
||||||
|
pair.first.decryptPrivateZap(account.signer) {
|
||||||
|
continuation.resume(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result?.pubKey == user.pubkeyHex && (option == null || option == pair.second?.zappedPollOption())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
onWasZappedByAuthor()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun isZappedBy(
|
suspend fun isZappedBy(
|
||||||
|
@@ -64,7 +64,9 @@ class LnZapRequestEvent(
|
|||||||
|
|
||||||
override fun linkedAddressIds() = tags.mapNotNull(ATag::parseAddressId)
|
override fun linkedAddressIds() = tags.mapNotNull(ATag::parseAddressId)
|
||||||
|
|
||||||
@Transient private var privateZapEvent: LnZapPrivateEvent? = null
|
// TODO: Create a per key map with resulting options to account for rejections and avoiding reasking.
|
||||||
|
@Transient
|
||||||
|
private var privateZapEvent: LnZapPrivateEvent? = null
|
||||||
|
|
||||||
override fun countMemory(): Long = super.countMemory() + pointerSizeInBytes + (privateZapEvent?.countMemory() ?: 0)
|
override fun countMemory(): Long = super.countMemory() + pointerSizeInBytes + (privateZapEvent?.countMemory() ?: 0)
|
||||||
|
|
||||||
|
@@ -25,9 +25,11 @@ import kotlinx.coroutines.async
|
|||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.joinAll
|
import kotlinx.coroutines.joinAll
|
||||||
|
import kotlinx.coroutines.selects.select
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import kotlin.coroutines.Continuation
|
import kotlin.coroutines.Continuation
|
||||||
|
import kotlin.coroutines.cancellation.CancellationException
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -153,3 +155,63 @@ suspend fun <T, K> mapNotNullAsync(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a mapping function asynchronously on each input in the list.
|
||||||
|
* Returns true as soon as the first mapping returns true, cancelling all other ongoing operations.
|
||||||
|
*
|
||||||
|
* @param inputs A list of input objects to process.
|
||||||
|
* @param mappingFunction A suspend function that takes an input object and returns a Boolean.
|
||||||
|
* @return True if any mapping function returns true, false otherwise.
|
||||||
|
*/
|
||||||
|
suspend fun <T> anyAsync(
|
||||||
|
inputs: List<T>,
|
||||||
|
timeoutMillis: Long = 30000,
|
||||||
|
mappingFunction: suspend (T) -> Boolean,
|
||||||
|
): Boolean =
|
||||||
|
coroutineScope {
|
||||||
|
// Create a list to hold all our deferred results
|
||||||
|
val deferredResults =
|
||||||
|
inputs.map { input ->
|
||||||
|
async {
|
||||||
|
// Each async block will execute the mapping function.
|
||||||
|
// If this coroutine gets cancelled, CancellationException will be thrown,
|
||||||
|
// and we'll catch it to ensure it doesn't propagate further.
|
||||||
|
try {
|
||||||
|
mappingFunction(input)
|
||||||
|
} catch (e: CancellationException) {
|
||||||
|
// When cancelled, we treat it as if it didn't return true
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use select to wait for the first deferred to complete with 'true'
|
||||||
|
val foundTrue =
|
||||||
|
withTimeoutOrNull(timeoutMillis) {
|
||||||
|
select {
|
||||||
|
deferredResults.forEach { deferred ->
|
||||||
|
// For each deferred, if it completes and its result is 'true',
|
||||||
|
// this branch of the select expression will be chosen.
|
||||||
|
deferred.onAwait { result ->
|
||||||
|
if (result) {
|
||||||
|
true // Return true from the select expression
|
||||||
|
} else {
|
||||||
|
// If a deferred completes with false, we don't want to
|
||||||
|
// immediately end the select, so we return false, which
|
||||||
|
// lets select continue waiting for other branches.
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once select returns (either with true or after all deferreds complete/are cancelled),
|
||||||
|
// cancel any remaining ongoing operations.
|
||||||
|
// If foundTrue is true, all other deferreds are implicitly cancelled by the select winning.
|
||||||
|
// If foundTrue is false, it means all completed with false or were cancelled.
|
||||||
|
deferredResults.forEach { it.cancel() } // Ensure all are cancelled.
|
||||||
|
|
||||||
|
return@coroutineScope foundTrue == true
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user