Fixes the index after deletions

This commit is contained in:
Vitor Pamplona
2025-08-01 11:40:19 -04:00
parent 4cd24e5363
commit 26cadfee88
9 changed files with 84 additions and 50 deletions
@@ -200,9 +200,6 @@ import kotlinx.coroutines.launch
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.concurrent.ConcurrentHashMap
interface ILocalCache {
@@ -520,12 +517,6 @@ object LocalCache : ILocalCache {
wasVerified: Boolean,
) = consumeBaseReplaceable(event, relay, wasVerified)
fun formattedDateTime(timestamp: Long): String =
Instant
.ofEpochSecond(timestamp)
.atZone(ZoneId.systemDefault())
.format(DateTimeFormatter.ofPattern("uuuu MMM d hh:mm a"))
fun consume(
event: TextNoteEvent,
relay: NormalizedRelayUrl? = null,
@@ -831,7 +822,7 @@ object LocalCache : ILocalCache {
event.mapTaggedEventId { checkGetOrCreateNote(it) } + event.mapTaggedAddress { checkGetOrCreateAddressableNote(it) }
}
else -> emptyList<Note>()
else -> emptyList()
}
fun consume(
@@ -1339,6 +1330,9 @@ object LocalCache : ILocalCache {
masterNote.removeReport(deleteNote)
}
deleteNote.inChatroom?.removeMessageSync(deleteNote)
deleteNote.inChannel?.removeNote(deleteNote)
getAnyChannel(deleteNote)?.removeNote(deleteNote)
(deletedEvent as? TorrentCommentEvent)?.torrentIds()?.let {
@@ -1357,18 +1351,18 @@ object LocalCache : ILocalCache {
}
fun deleteWraps(event: WrappedEvent) {
event.host?.let {
event.host?.let { hostStub ->
// seal
getNoteIfExists(it.id)?.let {
val noteEvent = it.event
getNoteIfExists(hostStub.id)?.let { hostNote ->
val noteEvent = hostNote.event
if (noteEvent is WrappedEvent) {
deleteWraps(noteEvent)
}
it.clearFlow()
refreshDeletedNoteObservers(it)
hostNote.clearFlow()
refreshDeletedNoteObservers(hostNote)
}
notes.remove(it.id)
notes.remove(hostStub.id)
}
}
@@ -1590,7 +1584,7 @@ object LocalCache : ILocalCache {
oldChannel.updateChannelInfo(author, event)
true
} else {
wasVerified
false
}
} else {
wasVerified
@@ -2055,11 +2049,7 @@ object LocalCache : ILocalCache {
if (note.event?.tags?.tagValueContains(text, true) == true ||
note.idHex.startsWith(text, true)
) {
if (!note.isHiddenFor(hiddenUsers.flow.value)) {
return@filter true
} else {
return@filter false
}
return@filter !note.isHiddenFor(hiddenUsers.flow.value)
}
if (note.event?.isContentEncoded() == false) {
@@ -2080,11 +2070,7 @@ object LocalCache : ILocalCache {
if (addressable.event?.tags?.tagValueContains(text, true) == true ||
addressable.idHex.startsWith(text, true)
) {
if (!addressable.isHiddenFor(hiddenUsers.flow.value)) {
return@filter true
} else {
return@filter false
}
return@filter !addressable.isHiddenFor(hiddenUsers.flow.value)
}
if (addressable.event?.isContentEncoded() == false) {
@@ -2187,7 +2173,7 @@ object LocalCache : ILocalCache {
suspend fun findLatestModificationForNote(note: Note): List<Note> {
checkNotInMainThread()
val originalAuthor = note.author?.pubkeyHex ?: return emptyList()
val noteAuthor = note.author ?: return emptyList()
modificationCache[note.idHex]?.let {
return it
@@ -2200,7 +2186,7 @@ object LocalCache : ILocalCache {
.filter { _, item ->
val noteEvent = item.event
noteEvent is TextNoteModificationEvent && note.author == item.author && noteEvent.isTaggedEvent(note.idHex) && !noteEvent.isExpirationBefore(time)
noteEvent is TextNoteModificationEvent && noteAuthor == item.author && noteEvent.isTaggedEvent(note.idHex) && !noteEvent.isExpirationBefore(time)
}.sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
modificationCache.put(note.idHex, newNotes)
@@ -2315,8 +2301,7 @@ object LocalCache : ILocalCache {
val childrenToBeRemoved = mutableListOf<Note>()
toBeRemoved.forEach {
// TODO: NEED TO TEST IF WRAPS COME BACK WHEN NEEDED BEFORE ACTIVATING
// childrenToBeRemoved.addAll(removeIfWrap(it))
childrenToBeRemoved.addAll(removeIfWrap(it))
removeFromCache(it)
childrenToBeRemoved.addAll(it.removeAllChildNotes())
@@ -2428,6 +2413,9 @@ object LocalCache : ILocalCache {
masterNote.removeReport(note)
}
note.inChatroom?.removeMessageSync(note)
note.inChannel?.removeNote(note)
val noteEvent = note.event
if (noteEvent is LnZapEvent) {
@@ -2896,6 +2884,9 @@ class LocalCacheFlow {
// Refreshes observers in batches.
private val bundler = BundledInsert<Note>(1000, Dispatchers.Default)
// Refreshes observers in batches.
private val bundler2 = BundledInsert<Note>(1000, Dispatchers.Default)
fun newNote(newNote: Note) {
bundler.invalidateList(newNote) { bundledNewNotes ->
_newEventBundles.emit(bundledNewNotes)
@@ -2903,7 +2894,7 @@ class LocalCacheFlow {
}
fun removedNote(newNote: Note) {
bundler.invalidateList(newNote) { bundledNewNotes ->
bundler2.invalidateList(newNote) { bundledNewNotes ->
_deletedEventBundles.emit(bundledNewNotes)
}
}
@@ -126,6 +126,13 @@ class FeedContentState(
}
}
fun deleteFromFeed(deletedNotes: Set<Note>) {
val feed = _feedContent.value
if (feed is FeedState.Loaded) {
updateFeed((feed.feed.value.list - deletedNotes).toImmutableList())
}
}
fun refreshFromOldState(newItems: Set<Note>) {
val oldNotesState = _feedContent.value
if (localFilter is AdditiveFeedFilter && lastFeedKey == localFilter.feedKey()) {
@@ -46,7 +46,10 @@ import com.vitorpamplona.amethyst.ui.note.AboutDisplay
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.search.AnimateOnNewSearch
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Composable
fun ShowUserSuggestionList(
@@ -62,10 +65,17 @@ fun ShowUserSuggestionList(
AnimateOnNewSearch(userSuggestions, listState)
LaunchedEffect(Unit) {
launch(Dispatchers.Default) {
LocalCache.live.newEventBundles.collect {
userSuggestions.invalidateData()
}
}
launch(Dispatchers.Default) {
LocalCache.live.deletedEventBundles.collect {
userSuggestions.invalidateData()
}
}
}
WatchResponses(userSuggestions, listState, onSelect, accountViewModel, modifier)
}
@@ -55,6 +55,13 @@ abstract class FeedViewModel(
feedState.updateFeedWith(newNotes)
}
}
viewModelScope.launch(Dispatchers.Default) {
LocalCache.live.deletedEventBundles.collect { newNotes ->
Log.d("Rendering Metrics", "Delete from feeds: ${this@FeedViewModel.javaClass.simpleName} with ${newNotes.size}")
feedState.deleteFromFeed(newNotes)
}
}
}
override fun onCleared() {
@@ -106,6 +106,13 @@ open class UserFeedViewModel(
invalidateData()
}
}
viewModelScope.launch(Dispatchers.Default) {
LocalCache.live.deletedEventBundles.collect { newNotes ->
Log.d("Rendering Metrics", "Delete from feeds: ${this@UserFeedViewModel.javaClass.simpleName} with ${newNotes.size}")
invalidateData()
}
}
}
override fun onCleared() {
@@ -198,7 +198,8 @@ class DecryptAndIndexProcessor(
is DraftEvent -> {
// Avoid decrypting over and over again if the event already exist.
if (event.pubKey == account.signer.pubKey) {
deindexDraftAsRealEvent(eventNote)
// already deindexed by LocalCache
// deindexDraftAsRealEvent(eventNote)
}
}
@@ -303,14 +304,6 @@ class DecryptAndIndexProcessor(
}
}
fun deindexDraftAsRealEvent(draftEventWrap: Note) {
draftEventWrap.replyTo?.forEach { it.removeReply(draftEventWrap) }
draftEventWrap.replyTo = null
draftEventWrap.inChatroom?.removeMessageSync(draftEventWrap)
draftEventWrap.inChannel?.removeNote(draftEventWrap)
}
suspend fun runNew(newNotes: Set<Note>) {
try {
newNotes.forEach {
@@ -87,11 +87,12 @@ open class LnZapFeedViewModel(
init {
Log.d("Init", "${this.javaClass.simpleName}")
viewModelScope.launch(Dispatchers.IO) {
checkNotInMainThread()
LocalCache.live.newEventBundles.collect { newNotes ->
checkNotInMainThread()
invalidateData()
}
}
viewModelScope.launch(Dispatchers.IO) {
LocalCache.live.deletedEventBundles.collect { newNotes ->
invalidateData()
}
}
@@ -77,7 +77,9 @@ import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
import com.vitorpamplona.amethyst.ui.theme.StdTopPadding
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.launch
@Composable
fun SearchScreen(
@@ -142,6 +144,7 @@ private fun SearchBar(
TextSearchDataSourceSubscription(searchBarViewModel, accountViewModel)
LaunchedEffect(Unit) {
launch(Dispatchers.Default) {
LocalCache.live.newEventBundles.collect {
if (searchBarViewModel.isSearchingFun()) {
searchBarViewModel.invalidateData()
@@ -149,6 +152,15 @@ private fun SearchBar(
}
}
launch(Dispatchers.Default) {
LocalCache.live.deletedEventBundles.collect {
if (searchBarViewModel.isSearchingFun()) {
searchBarViewModel.invalidateData()
}
}
}
}
AnimateOnNewSearch(searchBarViewModel, listState)
SearchTextField(searchBarViewModel, Modifier.statusBarsPadding())
@@ -105,6 +105,12 @@ open class StringFeedViewModel(
invalidateData()
}
}
viewModelScope.launch(Dispatchers.Default) {
LocalCache.live.deletedEventBundles.collect { newNotes ->
Log.d("Rendering Metrics", "Update feeds: ${this@StringFeedViewModel.javaClass.simpleName} with ${newNotes.size}")
invalidateData()
}
}
}
override fun onCleared() {