diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LargeSoftCache.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LargeSoftCache.kt index a513bedc8..fb6377c42 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LargeSoftCache.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LargeSoftCache.kt @@ -26,7 +26,7 @@ import java.util.concurrent.ConcurrentSkipListMap import java.util.function.BiConsumer class LargeSoftCache : CacheOperations { - private val cache = ConcurrentSkipListMap>() + protected val cache = ConcurrentSkipListMap>() fun keys() = cache.keys @@ -125,6 +125,16 @@ class LargeSoftCache : CacheOperations { cache.forEach(BiConsumerWrapper(this, consumer)) } + override fun forEach( + from: K, + to: K, + consumer: BiConsumer, + ) { + cache + .subMap(from, true, to, true) + .forEach(BiConsumerWrapper(this, consumer)) + } + class BiConsumerWrapper( val cache: LargeSoftCache, val inner: BiConsumer, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LargeSoftCacheAddressExt.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LargeSoftCacheAddressExt.kt new file mode 100644 index 000000000..5e1e70766 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LargeSoftCacheAddressExt.kt @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2025 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.model + +import com.vitorpamplona.quartz.nip01Core.core.Address +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.utils.cache.CacheCollectors + +const val START_KEY = "0000000000000000000000000000000000000000000000000000000000000000" +const val END_KEY = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" +const val START_D_TAG = "" +const val END_D_TAG = "\uFFFF\uFFFF\uFFFF\uFFFF" + +fun kindStart( + kind: Int, + pubKey: HexKey, +) = Address(kind, pubKey, "") + +fun kindEnd( + kind: Int, + pubKey: HexKey, +) = Address(kind, pubKey, END_D_TAG) + +fun kindStart(kind: Int) = kindStart(kind, START_KEY) + +fun kindEnd(kind: Int) = kindEnd(kind, END_KEY) + +val ACCEPT_ALL_FILTER = CacheCollectors.BiFilter { key, note -> true } + +fun LargeSoftCache.filterIntoSet( + kind: Int, + consumer: CacheCollectors.BiFilter = ACCEPT_ALL_FILTER, +): Set = filterIntoSet(kindStart(kind), kindEnd(kind), consumer) + +fun LargeSoftCache.filterIntoSet( + kinds: List, + consumer: CacheCollectors.BiFilter = ACCEPT_ALL_FILTER, +): Set { + val set = mutableSetOf() + kinds.forEach { + set.addAll(filterIntoSet(kindStart(it), kindEnd(it), consumer)) + } + return set +} + +fun LargeSoftCache.filterIntoSet( + kind: Int, + pubKey: HexKey, + consumer: CacheCollectors.BiFilter = ACCEPT_ALL_FILTER, +): Set = filterIntoSet(kindStart(kind, pubKey), kindEnd(kind, pubKey), consumer) + +fun LargeSoftCache.mapNotNullIntoSet( + kind: Int, + consumer: CacheCollectors.BiMapper, +): Set = mapNotNullIntoSet(kindStart(kind), kindEnd(kind), consumer) + +fun LargeSoftCache.mapNotNullIntoSet( + kind: Int, + pubKey: HexKey, + consumer: CacheCollectors.BiMapper, +): Set = mapNotNullIntoSet(kindStart(kind, pubKey), kindEnd(kind, pubKey), consumer) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/nip01Notifications/AccountNotificationsEoseFromInboxRelaysManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/nip01Notifications/AccountNotificationsEoseFromInboxRelaysManager.kt index d4ec17eb6..61d45d8bd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/nip01Notifications/AccountNotificationsEoseFromInboxRelaysManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/nip01Notifications/AccountNotificationsEoseFromInboxRelaysManager.kt @@ -55,12 +55,12 @@ class AccountNotificationsEoseFromInboxRelaysManager( relay = it, pubkey = user(key).pubkeyHex, since = since?.get(it)?.time ?: TimeUtils.oneWeekAgo(), - ) - filterNotificationsToPubkey( - relay = it, - pubkey = user(key).pubkeyHex, - since = since?.get(it)?.time ?: key.feedContentStates.notifications.lastNoteCreatedAtIfFilled() ?: TimeUtils.oneWeekAgo(), - ) + ) + + filterNotificationsToPubkey( + relay = it, + pubkey = user(key).pubkeyHex, + since = since?.get(it)?.time ?: key.feedContentStates.notifications.lastNoteCreatedAtIfFilled() ?: TimeUtils.oneWeekAgo(), + ) } val userJobMap = mutableMapOf>() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/DiscoverLongFormFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/DiscoverLongFormFeedFilter.kt index 3438293de..bff05caa9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/DiscoverLongFormFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/DiscoverLongFormFeedFilter.kt @@ -22,7 +22,9 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip23LongForm import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.model.LocalCache.notes import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.filterIntoSet import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder import com.vitorpamplona.amethyst.ui.dal.FilterByListParams @@ -45,13 +47,11 @@ open class DiscoverLongFormFeedFilter( override fun feed(): List { val params = buildFilterParams(account) - val notes = - LocalCache.addressables.filterIntoSet { _, it -> + LocalCache.addressables.filterIntoSet(LongTextNoteEvent.KIND) { _, it -> val noteEvent = it.event noteEvent is LongTextNoteEvent && params.match(noteEvent) } - return sort(notes) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip51FollowSets/DiscoverFollowSetsFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip51FollowSets/DiscoverFollowSetsFeedFilter.kt index 01fc98a7e..a6fc6c050 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip51FollowSets/DiscoverFollowSetsFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip51FollowSets/DiscoverFollowSetsFeedFilter.kt @@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip51FollowSets import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.filterIntoSet import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder import com.vitorpamplona.amethyst.ui.dal.FilterByListParams @@ -45,7 +46,7 @@ open class DiscoverFollowSetsFeedFilter( val params = buildFilterParams(account) val notes = - LocalCache.addressables.filterIntoSet { _, it -> + LocalCache.addressables.filterIntoSet(FollowListEvent.KIND) { _, it -> val noteEvent = it.event noteEvent is FollowListEvent && params.match(noteEvent) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip72Communities/DiscoverCommunityFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip72Communities/DiscoverCommunityFeedFilter.kt index eb649fe22..e0648c72f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip72Communities/DiscoverCommunityFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip72Communities/DiscoverCommunityFeedFilter.kt @@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip72Communities import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.mapNotNullIntoSet import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter import com.vitorpamplona.amethyst.ui.dal.FilterByListParams import com.vitorpamplona.quartz.nip01Core.core.Address @@ -54,7 +55,7 @@ open class DiscoverCommunityFeedFilter( // Here we only need to look for CommunityDefinition Events val notes = - LocalCache.addressables.mapNotNullIntoSet { key, note -> + LocalCache.addressables.mapNotNullIntoSet(CommunityDefinitionEvent.KIND) { key, note -> val noteEvent = note.event if (noteEvent == null && shouldInclude(key, filterParams, note.relays)) { // send unloaded communities to the screen diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip90DVMs/DiscoverNIP89FeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip90DVMs/DiscoverNIP89FeedFilter.kt index 1731d7248..276a55743 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip90DVMs/DiscoverNIP89FeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip90DVMs/DiscoverNIP89FeedFilter.kt @@ -24,6 +24,7 @@ import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.ParticipantListBuilder +import com.vitorpamplona.amethyst.model.filterIntoSet import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavFilter import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByProxyTopNavFilter import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavFilter @@ -55,7 +56,7 @@ open class DiscoverNIP89FeedFilter( override fun feed(): List { val notes = - LocalCache.addressables.filterIntoSet { _, it -> + LocalCache.addressables.filterIntoSet(AppDefinitionEvent.KIND) { _, it -> acceptDVM(it) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/DiscoverMarketplaceFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/DiscoverMarketplaceFeedFilter.kt index 7386a6904..a4c16b9e3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/DiscoverMarketplaceFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/DiscoverMarketplaceFeedFilter.kt @@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.filterIntoSet import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter import com.vitorpamplona.amethyst.ui.dal.FilterByListParams import com.vitorpamplona.quartz.nip51Lists.muteList.MuteListEvent @@ -46,7 +47,7 @@ open class DiscoverMarketplaceFeedFilter( val params = buildFilterParams(account) val notes = - LocalCache.addressables.filterIntoSet { _, it -> + LocalCache.addressables.filterIntoSet(ClassifiedsEvent.KIND) { _, it -> val noteEvent = it.event noteEvent is ClassifiedsEvent && noteEvent.isWellFormed() && params.match(noteEvent) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/drafts/dal/DraftEventsFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/drafts/dal/DraftEventsFeedFilter.kt index d9026aa88..21364ee0e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/drafts/dal/DraftEventsFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/drafts/dal/DraftEventsFeedFilter.kt @@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.drafts.dal import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.filterIntoSet import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent @@ -39,7 +40,7 @@ class DraftEventsFeedFilter( override fun feed(): List { val drafts = - LocalCache.addressables.filterIntoSet { _, note -> + LocalCache.addressables.filterIntoSet(DraftWrapEvent.KIND, account.userProfile().pubkeyHex) { _, note -> acceptableEvent(note) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/dal/HomeNewThreadFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/dal/HomeNewThreadFeedFilter.kt index 1f9b199ee..be69e6a00 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/dal/HomeNewThreadFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/dal/HomeNewThreadFeedFilter.kt @@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.home.dal import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.filterIntoSet import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavFilter import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByProxyTopNavFilter import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter @@ -45,6 +46,17 @@ import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceEvent class HomeNewThreadFeedFilter( val account: Account, ) : AdditiveFeedFilter() { + companion object { + val ADDRESSABLE_KINDS = + listOf( + AudioTrackEvent.KIND, + InteractiveStoryPrologueEvent.KIND, + WikiNoteEvent.KIND, + ClassifiedsEvent.KIND, + LongTextNoteEvent.KIND, + ) + } + override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + account.settings.defaultHomeFollowList.value override fun showHiddenKey(): Boolean = @@ -67,7 +79,9 @@ class HomeNewThreadFeedFilter( } val longFormNotes = - LocalCache.addressables.filterIntoSet { _, note -> + LocalCache.addressables.filterIntoSet( + kinds = ADDRESSABLE_KINDS, + ) { _, note -> acceptableEvent(note, filterParams) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/NotificationFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/NotificationFeedFilter.kt index 1f80d5230..ed5a07640 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/NotificationFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/NotificationFeedFilter.kt @@ -24,9 +24,11 @@ import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.filterIntoSet import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder import com.vitorpamplona.amethyst.ui.dal.FilterByListParams +import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent import com.vitorpamplona.quartz.experimental.forks.forkFromVersion import com.vitorpamplona.quartz.experimental.forks.isForkFromAddressWithPubkey import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent @@ -37,6 +39,7 @@ import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent import com.vitorpamplona.quartz.nip18Reposts.RepostEvent import com.vitorpamplona.quartz.nip22Comments.CommentEvent +import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent @@ -47,6 +50,11 @@ import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent import com.vitorpamplona.quartz.nip51Lists.PrivateTagArrayEvent import com.vitorpamplona.quartz.nip51Lists.muteList.MuteListEvent import com.vitorpamplona.quartz.nip51Lists.peopleList.PeopleListEvent +import com.vitorpamplona.quartz.nip52Calendar.CalendarDateSlotEvent +import com.vitorpamplona.quartz.nip52Calendar.CalendarRSVPEvent +import com.vitorpamplona.quartz.nip52Calendar.CalendarTimeSlotEvent +import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent +import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent import com.vitorpamplona.quartz.nip58Badges.BadgeDefinitionEvent @@ -58,10 +66,25 @@ import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryRequestEvent import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent import com.vitorpamplona.quartz.nip90Dvms.NIP90StatusEvent +import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent class NotificationFeedFilter( val account: Account, ) : AdditiveFeedFilter() { + companion object { + val ADDRESSABLE_KINDS = + listOf( + AudioTrackEvent.KIND, + WikiNoteEvent.KIND, + ClassifiedsEvent.KIND, + LongTextNoteEvent.KIND, + CalendarTimeSlotEvent.KIND, + CalendarDateSlotEvent.KIND, + CalendarRSVPEvent.KIND, + LiveActivitiesEvent.KIND, + ) + } + override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + account.settings.defaultNotificationFollowList.value override fun showHiddenKey(): Boolean = @@ -83,7 +106,7 @@ class NotificationFeedFilter( LocalCache.notes.filterIntoSet { _, note -> note.event !is AddressableEvent && acceptableEvent(note, filterParams) } + - LocalCache.addressables.filterIntoSet { _, note -> + LocalCache.addressables.filterIntoSet(ADDRESSABLE_KINDS) { _, note -> acceptableEvent(note, filterParams) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/dal/VideoFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/dal/VideoFeedFilter.kt index fc20626e1..92671a5dd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/dal/VideoFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/dal/VideoFeedFilter.kt @@ -24,7 +24,9 @@ import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.model.LocalCache.notes import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.filterIntoSet import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder import com.vitorpamplona.amethyst.ui.dal.FilterByListParams @@ -67,7 +69,10 @@ class VideoFeedFilter( LocalCache.notes.filterIntoSet { _, it -> acceptableEvent(it, params) } + - LocalCache.addressables.filterIntoSet { _, it -> + LocalCache.addressables.filterIntoSet(VideoHorizontalEvent.KIND) { _, it -> + acceptableEvent(it, params) + } + + LocalCache.addressables.filterIntoSet(VideoVerticalEvent.KIND) { _, it -> acceptableEvent(it, params) } diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/model/LargeCacheAddressableFilterTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/model/LargeCacheAddressableFilterTest.kt new file mode 100644 index 000000000..56df541b3 --- /dev/null +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/model/LargeCacheAddressableFilterTest.kt @@ -0,0 +1,152 @@ +/** + * Copyright (c) 2025 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.model + +import com.vitorpamplona.quartz.nip01Core.core.Address +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import junit.framework.TestCase.assertEquals +import org.junit.Test + +class LargeCacheAddressableFilterTest { + companion object { + fun LargeSoftCache.addMock( + kind: Int, + pubkey: HexKey, + dTag: String = "", + ) { + val address = Address(kind, pubkey, dTag) + put(address, AddressableNote(address)) + } + + val cache = + LargeSoftCache().apply { + addMock(32000, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") + addMock(32000, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") + addMock(32000, "3064bf97800a4b04b612fc0fd498936eae75fffbdca5bbd09d19a6dc598530ab") + addMock(32000, "f8ff11c7a7d3478355d3b4d174e5a473797a906ea4aa61aa9b6bc0652c1ea17a") + + addMock(32001, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") + addMock(32001, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad", "a") + addMock(32001, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad", "z") + addMock(32001, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad", "askldfjaljksdflkaj") + addMock(32001, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") + addMock(32001, "3064bf97800a4b04b612fc0fd498936eae75fffbdca5bbd09d19a6dc598530ab") + addMock(32001, "f8ff11c7a7d3478355d3b4d174e5a473797a906ea4aa61aa9b6bc0652c1ea17a") + + addMock(32002, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") + addMock(32002, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") + addMock(32002, "3064bf97800a4b04b612fc0fd498936eae75fffbdca5bbd09d19a6dc598530ab") + addMock(32002, "f8ff11c7a7d3478355d3b4d174e5a473797a906ea4aa61aa9b6bc0652c1ea17a") + + addMock(32003, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") + addMock(32003, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") + addMock(32003, "3064bf97800a4b04b612fc0fd498936eae75fffbdca5bbd09d19a6dc598530ab") + addMock(32003, "f8ff11c7a7d3478355d3b4d174e5a473797a906ea4aa61aa9b6bc0652c1ea17a") + } + } + + @Test + fun filterIntoSet() { + val query0 = cache.filterIntoSet(32_000) + val query1 = cache.filterIntoSet(32_001) + val query2 = cache.filterIntoSet(32_002) + val query3 = cache.filterIntoSet(32_003) + + assertEquals(4, query0.size) + assertEquals(7, query1.size) + assertEquals(4, query2.size) + assertEquals(4, query3.size) + } + + @Test + fun mapIntoSet() { + val query0 = cache.mapNotNullIntoSet(32_000) { key, note -> note } + val query1 = cache.mapNotNullIntoSet(32_001) { key, note -> note } + val query2 = cache.mapNotNullIntoSet(32_002) { key, note -> note } + val query3 = cache.mapNotNullIntoSet(32_003) { key, note -> note } + + assertEquals(4, query0.size) + assertEquals(7, query1.size) + assertEquals(4, query2.size) + assertEquals(4, query3.size) + } + + @Test + fun filterListIntoSet() { + val query = cache.filterIntoSet(listOf(32_000, 32_001, 32_002, 32_003)) + + assertEquals(19, query.size) + + val query2 = cache.filterIntoSet(listOf(32_000, 32_002, 32_003)) + + assertEquals(12, query2.size) + + val query3 = cache.filterIntoSet(listOf(32_000, 32_001, 32_003)) + + assertEquals(15, query3.size) + } + + @Test + fun filterKindKeyIntoSet() { + val query0 = cache.filterIntoSet(32_000, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") + val query1 = cache.filterIntoSet(32_001, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") + val query2 = cache.filterIntoSet(32_002, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") + val query3 = cache.filterIntoSet(32_003, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") + + assertEquals(1, query0.size) + assertEquals(1, query1.size) + assertEquals(1, query2.size) + assertEquals(1, query3.size) + + val query4 = cache.filterIntoSet(32_000, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") + val query5 = cache.filterIntoSet(32_001, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") + val query6 = cache.filterIntoSet(32_002, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") + val query7 = cache.filterIntoSet(32_003, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") + + assertEquals(1, query4.size) + assertEquals(4, query5.size) + assertEquals(1, query6.size) + assertEquals(1, query7.size) + } + + @Test + fun mapKindKeyIntoSet() { + val query0 = cache.mapNotNullIntoSet(32_000, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") { key, note -> note } + val query1 = cache.mapNotNullIntoSet(32_001, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") { key, note -> note } + val query2 = cache.mapNotNullIntoSet(32_002, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") { key, note -> note } + val query3 = cache.mapNotNullIntoSet(32_003, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") { key, note -> note } + + assertEquals(1, query0.size) + assertEquals(1, query1.size) + assertEquals(1, query2.size) + assertEquals(1, query3.size) + + val query4 = cache.mapNotNullIntoSet(32_000, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") { key, note -> note } + val query5 = cache.mapNotNullIntoSet(32_001, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") { key, note -> note } + val query6 = cache.mapNotNullIntoSet(32_002, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") { key, note -> note } + val query7 = cache.mapNotNullIntoSet(32_003, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") { key, note -> note } + + assertEquals(1, query4.size) + assertEquals(4, query5.size) + assertEquals(1, query6.size) + assertEquals(1, query7.size) + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/Address.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/Address.kt index 92eabead1..cbc23ae12 100644 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/Address.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/Address.kt @@ -40,16 +40,16 @@ actual data class Address actual constructor( dTag.bytesUsedInMemory() actual override fun compareTo(other: Address): Int { - val result = kind.compareTo(other.kind) - return if (result == 0) { - val result2 = pubKeyHex.compareTo(other.pubKeyHex) - if (result2 == 0) { + val kindComparison = kind.compareTo(other.kind) + return if (kindComparison == 0) { + val pubkeyComparison = pubKeyHex.compareTo(other.pubKeyHex) + if (pubkeyComparison == 0) { dTag.compareTo(other.dTag) } else { - result2 + pubkeyComparison } } else { - result + kindComparison } } diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/AddressSerializer.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/AddressSerializer.kt index 0e3ec2490..ca60a06cd 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/AddressSerializer.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/AddressSerializer.kt @@ -31,7 +31,13 @@ class AddressSerializer { kind: Int, pubKeyHex: HexKey, dTag: String = "", - ) = "$kind:$pubKeyHex:$dTag" + ) = buildString { + append(kind) + append(":") + append(pubKeyHex) + append(":") + append(dTag) + } fun parse(addressId: String): Address? { if (addressId.isBlank()) return null diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/cache/ICacheOperations.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/cache/ICacheOperations.kt index ec4a93d4b..882253488 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/cache/ICacheOperations.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/cache/ICacheOperations.kt @@ -70,6 +70,108 @@ interface ICacheOperations { fun associateWith(transform: (K, V) -> U?): Map + // -- + // Sub map operations + // -- + + fun filter( + from: K, + to: K, + consumer: CacheCollectors.BiFilter, + ): List + + fun filterIntoSet( + from: K, + to: K, + consumer: CacheCollectors.BiFilter, + ): Set + + fun map( + from: K, + to: K, + consumer: CacheCollectors.BiNotNullMapper, + ): List + + fun mapNotNull( + from: K, + to: K, + consumer: CacheCollectors.BiMapper, + ): List + + fun mapNotNullIntoSet( + from: K, + to: K, + consumer: CacheCollectors.BiMapper, + ): Set + + fun mapFlatten( + from: K, + to: K, + consumer: CacheCollectors.BiMapper?>, + ): List + + fun mapFlattenIntoSet( + from: K, + to: K, + consumer: CacheCollectors.BiMapper?>, + ): Set + + fun maxOrNullOf( + from: K, + to: K, + filter: CacheCollectors.BiFilter, + comparator: Comparator, + ): V? + + fun sumOf( + from: K, + to: K, + consumer: CacheCollectors.BiSumOf, + ): Int + + fun sumOfLong( + from: K, + to: K, + consumer: CacheCollectors.BiSumOfLong, + ): Long + + fun groupBy( + from: K, + to: K, + consumer: CacheCollectors.BiNotNullMapper, + ): Map> + + fun countByGroup( + from: K, + to: K, + consumer: CacheCollectors.BiNotNullMapper, + ): Map + + fun sumByGroup( + from: K, + to: K, + groupMap: CacheCollectors.BiNotNullMapper, + sumOf: CacheCollectors.BiNotNullMapper, + ): Map + + fun count( + from: K, + to: K, + consumer: CacheCollectors.BiFilter, + ): Int + + fun associate( + from: K, + to: K, + transform: (K, V) -> Pair, + ): Map + + fun associateWith( + from: K, + to: K, + transform: (K, V) -> U?, + ): Map + fun joinToString( separator: CharSequence = ", ", prefix: CharSequence = "", diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.kt index 96a18f520..10f9bcc40 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.kt @@ -92,6 +92,104 @@ expect class LargeCache() : ICacheOperations { override fun associateWith(transform: (K, V) -> U?): Map + override fun filter( + from: K, + to: K, + consumer: CacheCollectors.BiFilter, + ): List + + override fun filterIntoSet( + from: K, + to: K, + consumer: CacheCollectors.BiFilter, + ): Set + + override fun map( + from: K, + to: K, + consumer: CacheCollectors.BiNotNullMapper, + ): List + + override fun mapNotNull( + from: K, + to: K, + consumer: CacheCollectors.BiMapper, + ): List + + override fun mapNotNullIntoSet( + from: K, + to: K, + consumer: CacheCollectors.BiMapper, + ): Set + + override fun mapFlatten( + from: K, + to: K, + consumer: CacheCollectors.BiMapper?>, + ): List + + override fun mapFlattenIntoSet( + from: K, + to: K, + consumer: CacheCollectors.BiMapper?>, + ): Set + + override fun maxOrNullOf( + from: K, + to: K, + filter: CacheCollectors.BiFilter, + comparator: Comparator, + ): V? + + override fun sumOf( + from: K, + to: K, + consumer: CacheCollectors.BiSumOf, + ): Int + + override fun sumOfLong( + from: K, + to: K, + consumer: CacheCollectors.BiSumOfLong, + ): Long + + override fun groupBy( + from: K, + to: K, + consumer: CacheCollectors.BiNotNullMapper, + ): Map> + + override fun countByGroup( + from: K, + to: K, + consumer: CacheCollectors.BiNotNullMapper, + ): Map + + override fun sumByGroup( + from: K, + to: K, + groupMap: CacheCollectors.BiNotNullMapper, + sumOf: CacheCollectors.BiNotNullMapper, + ): Map + + override fun count( + from: K, + to: K, + consumer: CacheCollectors.BiFilter, + ): Int + + override fun associate( + from: K, + to: K, + transform: (K, V) -> Pair, + ): Map + + override fun associateWith( + from: K, + to: K, + transform: (K, V) -> U?, + ): Map + override fun joinToString( separator: CharSequence, prefix: CharSequence, diff --git a/quartz/src/iosMain/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.ios.kt b/quartz/src/iosMain/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.ios.kt index 6d5910ad7..91ab979bf 100644 --- a/quartz/src/iosMain/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.ios.kt +++ b/quartz/src/iosMain/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.ios.kt @@ -147,6 +147,136 @@ actual class LargeCache : ICacheOperations { TODO("Not yet implemented") } + actual override fun filter( + from: K, + to: K, + consumer: CacheCollectors.BiFilter, + ): List { + TODO("Not yet implemented") + } + + actual override fun filterIntoSet( + from: K, + to: K, + consumer: CacheCollectors.BiFilter, + ): Set { + TODO("Not yet implemented") + } + + actual override fun map( + from: K, + to: K, + consumer: CacheCollectors.BiNotNullMapper, + ): List { + TODO("Not yet implemented") + } + + actual override fun mapNotNull( + from: K, + to: K, + consumer: CacheCollectors.BiMapper, + ): List { + TODO("Not yet implemented") + } + + actual override fun mapNotNullIntoSet( + from: K, + to: K, + consumer: CacheCollectors.BiMapper, + ): Set { + TODO("Not yet implemented") + } + + actual override fun mapFlatten( + from: K, + to: K, + consumer: CacheCollectors.BiMapper?>, + ): List { + TODO("Not yet implemented") + } + + actual override fun mapFlattenIntoSet( + from: K, + to: K, + consumer: CacheCollectors.BiMapper?>, + ): Set { + TODO("Not yet implemented") + } + + actual override fun maxOrNullOf( + from: K, + to: K, + filter: CacheCollectors.BiFilter, + comparator: Comparator, + ): V? { + TODO("Not yet implemented") + } + + actual override fun sumOf( + from: K, + to: K, + consumer: CacheCollectors.BiSumOf, + ): Int { + TODO("Not yet implemented") + } + + actual override fun sumOfLong( + from: K, + to: K, + consumer: CacheCollectors.BiSumOfLong, + ): Long { + TODO("Not yet implemented") + } + + actual override fun groupBy( + from: K, + to: K, + consumer: CacheCollectors.BiNotNullMapper, + ): Map> { + TODO("Not yet implemented") + } + + actual override fun countByGroup( + from: K, + to: K, + consumer: CacheCollectors.BiNotNullMapper, + ): Map { + TODO("Not yet implemented") + } + + actual override fun sumByGroup( + from: K, + to: K, + groupMap: CacheCollectors.BiNotNullMapper, + sumOf: CacheCollectors.BiNotNullMapper, + ): Map { + TODO("Not yet implemented") + } + + actual override fun count( + from: K, + to: K, + consumer: CacheCollectors.BiFilter, + ): Int { + TODO("Not yet implemented") + } + + actual override fun associate( + from: K, + to: K, + transform: (K, V) -> Pair, + ): Map { + TODO("Not yet implemented") + } + + actual override fun associateWith( + from: K, + to: K, + transform: (K, V) -> U?, + ): Map { + TODO("Not yet implemented") + } + actual override fun joinToString( separator: CharSequence, prefix: CharSequence, diff --git a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/cache/CacheOperations.kt b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/cache/CacheOperations.kt index 00a51c43f..35a44cd1f 100644 --- a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/cache/CacheOperations.kt +++ b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/cache/CacheOperations.kt @@ -36,6 +36,12 @@ class MyBiConsumer( interface CacheOperations : ICacheOperations { fun forEach(consumer: BiConsumer) + fun forEach( + from: K, + to: K, + consumer: BiConsumer, + ) + override fun size(): Int override fun forEach(consumer: ICacheBiConsumer) { @@ -144,6 +150,171 @@ interface CacheOperations : ICacheOperations { return runner.results } + // ---- + // submap operations + // ---- + override fun filter( + from: K, + to: K, + consumer: CacheCollectors.BiFilter, + ): List { + val runner = BiFilterCollector(consumer) + forEach(from, to, runner) + return runner.results + } + + override fun filterIntoSet( + from: K, + to: K, + consumer: CacheCollectors.BiFilter, + ): Set { + val runner = BiFilterUniqueCollector(consumer) + forEach(from, to, runner) + return runner.results + } + + override fun map( + from: K, + to: K, + consumer: CacheCollectors.BiNotNullMapper, + ): List { + val runner = BiNotNullMapCollector(consumer) + forEach(from, to, runner) + return runner.results + } + + override fun mapNotNull( + from: K, + to: K, + consumer: CacheCollectors.BiMapper, + ): List { + val runner = BiMapCollector(consumer) + forEach(from, to, runner) + return runner.results + } + + override fun mapNotNullIntoSet( + from: K, + to: K, + consumer: CacheCollectors.BiMapper, + ): Set { + val runner = BiMapUniqueCollector(consumer) + forEach(from, to, runner) + return runner.results + } + + override fun mapFlatten( + from: K, + to: K, + consumer: CacheCollectors.BiMapper?>, + ): List { + val runner = BiMapFlattenCollector(consumer) + forEach(from, to, runner) + return runner.results + } + + override fun mapFlattenIntoSet( + from: K, + to: K, + consumer: CacheCollectors.BiMapper?>, + ): Set { + val runner = BiMapFlattenUniqueCollector(consumer) + forEach(from, to, runner) + return runner.results + } + + override fun maxOrNullOf( + from: K, + to: K, + filter: CacheCollectors.BiFilter, + comparator: Comparator, + ): V? { + val runner = BiMaxOfCollector(filter, comparator) + forEach(from, to, runner) + return runner.maxV + } + + override fun sumOf( + from: K, + to: K, + consumer: CacheCollectors.BiSumOf, + ): Int { + val runner = BiSumOfCollector(consumer) + forEach(from, to, runner) + return runner.sum + } + + override fun sumOfLong( + from: K, + to: K, + consumer: CacheCollectors.BiSumOfLong, + ): Long { + val runner = BiSumOfLongCollector(consumer) + forEach(from, to, runner) + return runner.sum + } + + override fun groupBy( + from: K, + to: K, + consumer: CacheCollectors.BiNotNullMapper, + ): Map> { + val runner = BiGroupByCollector(consumer) + forEach(from, to, runner) + return runner.results + } + + override fun countByGroup( + from: K, + to: K, + consumer: CacheCollectors.BiNotNullMapper, + ): Map { + val runner = BiCountByGroupCollector(consumer) + forEach(from, to, runner) + return runner.results + } + + override fun sumByGroup( + from: K, + to: K, + groupMap: CacheCollectors.BiNotNullMapper, + sumOf: CacheCollectors.BiNotNullMapper, + ): Map { + val runner = BiSumByGroupCollector(groupMap, sumOf) + forEach(from, to, runner) + return runner.results + } + + override fun count( + from: K, + to: K, + consumer: CacheCollectors.BiFilter, + ): Int { + val runner = BiCountIfCollector(consumer) + forEach(from, to, runner) + return runner.count + } + + override fun associate( + from: K, + to: K, + transform: (K, V) -> Pair, + ): Map { + val runner = BiAssociateCollector(size(), transform) + forEach(from, to, runner) + return runner.results + } + + override fun associateWith( + from: K, + to: K, + transform: (K, V) -> U?, + ): Map { + val runner = BiAssociateWithCollector(size(), transform) + forEach(from, to, runner) + return runner.results + } + override fun joinToString( separator: CharSequence, prefix: CharSequence, diff --git a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.jvmAndroid.kt b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.jvmAndroid.kt index 6a430bb86..40819f546 100644 --- a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.jvmAndroid.kt +++ b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.jvmAndroid.kt @@ -79,4 +79,14 @@ actual class LargeCache : CacheOperations { override fun forEach(consumer: BiConsumer) { cache.forEach(consumer) } + + override fun forEach( + from: K, + to: K, + consumer: BiConsumer, + ) { + cache + .subMap(from, true, to, true) + .forEach(consumer) + } }