atagen

macltok

public
Star0
Watch0
7cdaa86b9489a6130b716572fa5d91650681196d
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git

Blame: src/mentions/state.rs

cbb715e11963atagen2026-10-24 23:24 UTC1
use std::collections::{BTreeMap, BTreeSet};
cbb715e11963atagen2026-10-24 23:24 UTC2
80381e06a573atagen2026-10-25 03:02 UTC3
use ruma::{OwnedEventId, OwnedUserId};
cbb715e11963atagen2026-10-24 23:24 UTC4
use serde_json::Value;
cbb715e11963atagen2026-10-24 23:24 UTC5
cbb715e11963atagen2026-10-24 23:24 UTC6
use crate::{
7cdaa86b9489atagen2026-10-25 08:43 UTC7
    Error, ErrorKind, EventContext, MentionCaptureState, MentionCoverage, MentionEntry, MentionGap,
7cdaa86b9489atagen2026-10-25 08:43 UTC8
    MentionGapReason, MentionKey, MentionsBoundary, MentionsPage, SavedMentionContext,
7cdaa86b9489atagen2026-10-25 08:43 UTC9
    TimelineItem, normalise::MediaRegistration,
cbb715e11963atagen2026-10-24 23:24 UTC10
};
cbb715e11963atagen2026-10-24 23:24 UTC11
7cdaa86b9489atagen2026-10-25 08:43 UTC12
const CANDIDATE_LIFETIME_MS: u64 = 7 * 24 * 60 * 60 * 1000;
7cdaa86b9489atagen2026-10-25 08:43 UTC13
cbb715e11963atagen2026-10-24 23:24 UTC14
#[derive(Clone)]
cbb715e11963atagen2026-10-24 23:24 UTC15
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
cbb715e11963atagen2026-10-24 23:24 UTC16
pub(super) struct SavedEvent {
cbb715e11963atagen2026-10-24 23:24 UTC17
    pub item: TimelineItem,
cbb715e11963atagen2026-10-24 23:24 UTC18
    pub media: Vec<MediaRegistration>,
cbb715e11963atagen2026-10-24 23:24 UTC19
    pub raw: Option<Value>,
cbb715e11963atagen2026-10-24 23:24 UTC20
}
cbb715e11963atagen2026-10-24 23:24 UTC21
7cdaa86b9489atagen2026-10-25 08:43 UTC22
impl PartialEq for SavedEvent {
7cdaa86b9489atagen2026-10-25 08:43 UTC23
    fn eq(&self, other: &Self) -> bool {
7cdaa86b9489atagen2026-10-25 08:43 UTC24
        self.item == other.item
7cdaa86b9489atagen2026-10-25 08:43 UTC25
            && self.raw == other.raw
7cdaa86b9489atagen2026-10-25 08:43 UTC26
            && self
7cdaa86b9489atagen2026-10-25 08:43 UTC27
                .media
7cdaa86b9489atagen2026-10-25 08:43 UTC28
                .iter()
7cdaa86b9489atagen2026-10-25 08:43 UTC29
                .map(|media| &media.id)
7cdaa86b9489atagen2026-10-25 08:43 UTC30
                .eq(other.media.iter().map(|media| &media.id))
7cdaa86b9489atagen2026-10-25 08:43 UTC31
    }
7cdaa86b9489atagen2026-10-25 08:43 UTC32
}
7cdaa86b9489atagen2026-10-25 08:43 UTC33
7cdaa86b9489atagen2026-10-25 08:43 UTC34
#[derive(Clone, PartialEq)]
cbb715e11963atagen2026-10-24 23:24 UTC35
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
cbb715e11963atagen2026-10-24 23:24 UTC36
pub(super) struct Candidate {
cbb715e11963atagen2026-10-24 23:24 UTC37
    pub room_id: ruma::OwnedRoomId,
cbb715e11963atagen2026-10-24 23:24 UTC38
    pub raw: Value,
cbb715e11963atagen2026-10-24 23:24 UTC39
    pub attempts: u32,
cbb715e11963atagen2026-10-24 23:24 UTC40
    pub retry_at_ms: u64,
7cdaa86b9489atagen2026-10-25 08:43 UTC41
    pub first_seen_ms: u64,
cbb715e11963atagen2026-10-24 23:24 UTC42
}
cbb715e11963atagen2026-10-24 23:24 UTC43
7cdaa86b9489atagen2026-10-25 08:43 UTC44
impl Candidate {
7cdaa86b9489atagen2026-10-25 08:43 UTC45
    pub fn new(room_id: ruma::OwnedRoomId, raw: Value, retry_at_ms: u64) -> Self {
7cdaa86b9489atagen2026-10-25 08:43 UTC46
        Self {
7cdaa86b9489atagen2026-10-25 08:43 UTC47
            room_id,
7cdaa86b9489atagen2026-10-25 08:43 UTC48
            raw,
7cdaa86b9489atagen2026-10-25 08:43 UTC49
            attempts: 0,
7cdaa86b9489atagen2026-10-25 08:43 UTC50
            retry_at_ms,
7cdaa86b9489atagen2026-10-25 08:43 UTC51
            first_seen_ms: now(),
7cdaa86b9489atagen2026-10-25 08:43 UTC52
        }
7cdaa86b9489atagen2026-10-25 08:43 UTC53
    }
7cdaa86b9489atagen2026-10-25 08:43 UTC54
7cdaa86b9489atagen2026-10-25 08:43 UTC55
    pub fn encrypted(&self) -> bool {
7cdaa86b9489atagen2026-10-25 08:43 UTC56
        self.raw["type"] == "m.room.encrypted"
7cdaa86b9489atagen2026-10-25 08:43 UTC57
    }
7cdaa86b9489atagen2026-10-25 08:43 UTC58
}
7cdaa86b9489atagen2026-10-25 08:43 UTC59
7cdaa86b9489atagen2026-10-25 08:43 UTC60
#[derive(Clone, PartialEq)]
cbb715e11963atagen2026-10-24 23:24 UTC61
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
cbb715e11963atagen2026-10-24 23:24 UTC62
pub(super) struct Window {
cbb715e11963atagen2026-10-24 23:24 UTC63
    pub before: Vec<String>,
cbb715e11963atagen2026-10-24 23:24 UTC64
    pub target: Option<String>,
cbb715e11963atagen2026-10-24 23:24 UTC65
    pub after: Vec<String>,
cbb715e11963atagen2026-10-24 23:24 UTC66
    pub start: Option<String>,
cbb715e11963atagen2026-10-24 23:24 UTC67
    pub end: Option<String>,
cbb715e11963atagen2026-10-24 23:24 UTC68
}
cbb715e11963atagen2026-10-24 23:24 UTC69
7cdaa86b9489atagen2026-10-25 08:43 UTC70
#[derive(Clone, Debug, Default, PartialEq)]
80381e06a573atagen2026-10-25 03:02 UTC71
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
80381e06a573atagen2026-10-25 03:02 UTC72
pub(crate) struct RepairCoverage {
80381e06a573atagen2026-10-25 03:02 UTC73
    pub(crate) last_confirmed_ms: Option<u64>,
80381e06a573atagen2026-10-25 03:02 UTC74
    pub(crate) boundary_ms: Option<u64>,
80381e06a573atagen2026-10-25 03:02 UTC75
    pub(crate) reliable: bool,
80381e06a573atagen2026-10-25 03:02 UTC76
    pub(crate) unresolved: bool,
80381e06a573atagen2026-10-25 03:02 UTC77
    pub(crate) latest_anchor: Option<OwnedEventId>,
80381e06a573atagen2026-10-25 03:02 UTC78
}
80381e06a573atagen2026-10-25 03:02 UTC79
80381e06a573atagen2026-10-25 03:02 UTC80
impl RepairCoverage {
80381e06a573atagen2026-10-25 03:02 UTC81
    pub(crate) fn duration_ms(&self) -> Option<u64> {
80381e06a573atagen2026-10-25 03:02 UTC82
        if !self.reliable {
80381e06a573atagen2026-10-25 03:02 UTC83
            return None;
80381e06a573atagen2026-10-25 03:02 UTC84
        }
80381e06a573atagen2026-10-25 03:02 UTC85
        self.boundary_ms?.checked_sub(self.last_confirmed_ms?)
80381e06a573atagen2026-10-25 03:02 UTC86
    }
80381e06a573atagen2026-10-25 03:02 UTC87
}
80381e06a573atagen2026-10-25 03:02 UTC88
7cdaa86b9489atagen2026-10-25 08:43 UTC89
#[derive(Clone, Default, PartialEq)]
cbb715e11963atagen2026-10-24 23:24 UTC90
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
cbb715e11963atagen2026-10-24 23:24 UTC91
pub(super) struct Data {
cbb715e11963atagen2026-10-24 23:24 UTC92
    pub revision: u64,
cbb715e11963atagen2026-10-24 23:24 UTC93
    pub entries: BTreeMap<String, MentionEntry>,
cbb715e11963atagen2026-10-24 23:24 UTC94
    pub events: BTreeMap<String, SavedEvent>,
cbb715e11963atagen2026-10-24 23:24 UTC95
    pub windows: BTreeMap<String, Window>,
cbb715e11963atagen2026-10-24 23:24 UTC96
    pub capture_attempts: BTreeMap<String, u32>,
cbb715e11963atagen2026-10-24 23:24 UTC97
    pub candidates: BTreeMap<String, Candidate>,
cbb715e11963atagen2026-10-24 23:24 UTC98
    pub redacted: BTreeMap<String, u64>,
cbb715e11963atagen2026-10-24 23:24 UTC99
    pub edits: BTreeMap<String, Value>,
cbb715e11963atagen2026-10-24 23:24 UTC100
    pub coverage: MentionCoverage,
cbb715e11963atagen2026-10-24 23:24 UTC101
    pub history_cursor: Option<String>,
cbb715e11963atagen2026-10-24 23:24 UTC102
    pub history_exhausted: bool,
cbb715e11963atagen2026-10-24 23:24 UTC103
    pub latest_cursor: Option<String>,
cbb715e11963atagen2026-10-24 23:24 UTC104
    pub latest_anchor: Option<String>,
cbb715e11963atagen2026-10-24 23:24 UTC105
    pub latest_head: Option<String>,
7cdaa86b9489atagen2026-10-25 08:43 UTC106
    pub repair_policy: crate::HistoryRepairPolicy,
7cdaa86b9489atagen2026-10-25 08:43 UTC107
}
7cdaa86b9489atagen2026-10-25 08:43 UTC108
7cdaa86b9489atagen2026-10-25 08:43 UTC109
#[derive(Clone, Default, PartialEq)]
7cdaa86b9489atagen2026-10-25 08:43 UTC110
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7cdaa86b9489atagen2026-10-25 08:43 UTC111
pub(super) struct SyncState {
7cdaa86b9489atagen2026-10-25 08:43 UTC112
    pub inbox_revision: u64,
7cdaa86b9489atagen2026-10-25 08:43 UTC113
    pub checkpoints: BTreeMap<String, Checkpoint>,
7cdaa86b9489atagen2026-10-25 08:43 UTC114
    pub observed_rooms: BTreeSet<ruma::OwnedRoomId>,
7cdaa86b9489atagen2026-10-25 08:43 UTC115
    pub repair_coverage: BTreeMap<ruma::OwnedRoomId, RepairCoverage>,
7cdaa86b9489atagen2026-10-25 08:43 UTC116
}
7cdaa86b9489atagen2026-10-25 08:43 UTC117
7cdaa86b9489atagen2026-10-25 08:43 UTC118
#[derive(Clone, Debug, PartialEq)]
7cdaa86b9489atagen2026-10-25 08:43 UTC119
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7cdaa86b9489atagen2026-10-25 08:43 UTC120
pub(super) struct Checkpoint {
7cdaa86b9489atagen2026-10-25 08:43 UTC121
    pub pos: String,
7cdaa86b9489atagen2026-10-25 08:43 UTC122
    pub previous: Option<String>,
cbb715e11963atagen2026-10-24 23:24 UTC123
}
cbb715e11963atagen2026-10-24 23:24 UTC124
cbb715e11963atagen2026-10-24 23:24 UTC125
pub(super) fn id(key: &MentionKey) -> String {
cbb715e11963atagen2026-10-24 23:24 UTC126
    format!("{}\n{}", key.room_id, key.event_id)
cbb715e11963atagen2026-10-24 23:24 UTC127
}
cbb715e11963atagen2026-10-24 23:24 UTC128
cbb715e11963atagen2026-10-24 23:24 UTC129
pub(super) fn now() -> u64 {
cbb715e11963atagen2026-10-24 23:24 UTC130
    matrix_sdk::ruma::MilliSecondsSinceUnixEpoch::now()
cbb715e11963atagen2026-10-24 23:24 UTC131
        .get()
cbb715e11963atagen2026-10-24 23:24 UTC132
        .into()
cbb715e11963atagen2026-10-24 23:24 UTC133
}
cbb715e11963atagen2026-10-24 23:24 UTC134
cbb715e11963atagen2026-10-24 23:24 UTC135
pub(super) fn invalid(message: &'static str) -> Error {
cbb715e11963atagen2026-10-24 23:24 UTC136
    Error::new(ErrorKind::Invalid, message)
cbb715e11963atagen2026-10-24 23:24 UTC137
}
cbb715e11963atagen2026-10-24 23:24 UTC138
7cdaa86b9489atagen2026-10-25 08:43 UTC139
#[cfg(feature = "serde")]
7cdaa86b9489atagen2026-10-25 08:43 UTC140
pub(super) fn json_len(value: &impl serde::Serialize) -> usize {
7cdaa86b9489atagen2026-10-25 08:43 UTC141
    struct Count(usize);
7cdaa86b9489atagen2026-10-25 08:43 UTC142
    impl std::io::Write for Count {
7cdaa86b9489atagen2026-10-25 08:43 UTC143
        fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
7cdaa86b9489atagen2026-10-25 08:43 UTC144
            self.0 += bytes.len();
7cdaa86b9489atagen2026-10-25 08:43 UTC145
            Ok(bytes.len())
7cdaa86b9489atagen2026-10-25 08:43 UTC146
        }
7cdaa86b9489atagen2026-10-25 08:43 UTC147
        fn flush(&mut self) -> std::io::Result<()> {
7cdaa86b9489atagen2026-10-25 08:43 UTC148
            Ok(())
7cdaa86b9489atagen2026-10-25 08:43 UTC149
        }
7cdaa86b9489atagen2026-10-25 08:43 UTC150
    }
7cdaa86b9489atagen2026-10-25 08:43 UTC151
    let mut count = Count(0);
7cdaa86b9489atagen2026-10-25 08:43 UTC152
    let _ = serde_json::to_writer(&mut count, value);
7cdaa86b9489atagen2026-10-25 08:43 UTC153
    count.0
7cdaa86b9489atagen2026-10-25 08:43 UTC154
}
7cdaa86b9489atagen2026-10-25 08:43 UTC155
7cdaa86b9489atagen2026-10-25 08:43 UTC156
#[cfg(not(feature = "serde"))]
7cdaa86b9489atagen2026-10-25 08:43 UTC157
pub(super) fn json_len<T>(_: &T) -> usize {
7cdaa86b9489atagen2026-10-25 08:43 UTC158
    0
7cdaa86b9489atagen2026-10-25 08:43 UTC159
}
7cdaa86b9489atagen2026-10-25 08:43 UTC160
7cdaa86b9489atagen2026-10-25 08:43 UTC161
fn timestamp(value: &Value) -> u64 {
7cdaa86b9489atagen2026-10-25 08:43 UTC162
    value["origin_server_ts"].as_u64().unwrap_or(0)
7cdaa86b9489atagen2026-10-25 08:43 UTC163
}
7cdaa86b9489atagen2026-10-25 08:43 UTC164
cbb715e11963atagen2026-10-24 23:24 UTC165
impl Data {
7cdaa86b9489atagen2026-10-25 08:43 UTC166
    pub fn gap(
7cdaa86b9489atagen2026-10-25 08:43 UTC167
        &mut self,
7cdaa86b9489atagen2026-10-25 08:43 UTC168
        room_id: Option<ruma::OwnedRoomId>,
7cdaa86b9489atagen2026-10-25 08:43 UTC169
        reason: MentionGapReason,
7cdaa86b9489atagen2026-10-25 08:43 UTC170
        token: Option<String>,
7cdaa86b9489atagen2026-10-25 08:43 UTC171
    ) {
7cdaa86b9489atagen2026-10-25 08:43 UTC172
        if self
7cdaa86b9489atagen2026-10-25 08:43 UTC173
            .coverage
7cdaa86b9489atagen2026-10-25 08:43 UTC174
            .gaps
7cdaa86b9489atagen2026-10-25 08:43 UTC175
            .iter()
7cdaa86b9489atagen2026-10-25 08:43 UTC176
            .any(|gap| gap.room_id == room_id && gap.reason == reason && gap.token == token)
7cdaa86b9489atagen2026-10-25 08:43 UTC177
        {
7cdaa86b9489atagen2026-10-25 08:43 UTC178
            return;
7cdaa86b9489atagen2026-10-25 08:43 UTC179
        }
7cdaa86b9489atagen2026-10-25 08:43 UTC180
        self.coverage.gaps.push(MentionGap {
7cdaa86b9489atagen2026-10-25 08:43 UTC181
            room_id,
7cdaa86b9489atagen2026-10-25 08:43 UTC182
            reason,
7cdaa86b9489atagen2026-10-25 08:43 UTC183
            token,
7cdaa86b9489atagen2026-10-25 08:43 UTC184
        });
7cdaa86b9489atagen2026-10-25 08:43 UTC185
        if self.coverage.gaps.len() > 128 {
7cdaa86b9489atagen2026-10-25 08:43 UTC186
            self.coverage.gaps.remove(0);
7cdaa86b9489atagen2026-10-25 08:43 UTC187
            self.coverage.truncated = true;
7cdaa86b9489atagen2026-10-25 08:43 UTC188
        }
7cdaa86b9489atagen2026-10-25 08:43 UTC189
    }
7cdaa86b9489atagen2026-10-25 08:43 UTC190
7cdaa86b9489atagen2026-10-25 08:43 UTC191
    pub fn expire_candidates(&mut self, now_ms: u64) -> bool {
7cdaa86b9489atagen2026-10-25 08:43 UTC192
        let expired: Vec<_> = self
7cdaa86b9489atagen2026-10-25 08:43 UTC193
            .candidates
7cdaa86b9489atagen2026-10-25 08:43 UTC194
            .iter()
7cdaa86b9489atagen2026-10-25 08:43 UTC195
            .filter(|(_, candidate)| {
7cdaa86b9489atagen2026-10-25 08:43 UTC196
                now_ms.saturating_sub(candidate.first_seen_ms) > CANDIDATE_LIFETIME_MS
7cdaa86b9489atagen2026-10-25 08:43 UTC197
            })
7cdaa86b9489atagen2026-10-25 08:43 UTC198
            .map(|(key, candidate)| (key.clone(), candidate.room_id.clone()))
7cdaa86b9489atagen2026-10-25 08:43 UTC199
            .collect();
7cdaa86b9489atagen2026-10-25 08:43 UTC200
        for (key, room_id) in &expired {
7cdaa86b9489atagen2026-10-25 08:43 UTC201
            self.candidates.remove(key);
7cdaa86b9489atagen2026-10-25 08:43 UTC202
            self.gap(
7cdaa86b9489atagen2026-10-25 08:43 UTC203
                Some(room_id.clone()),
7cdaa86b9489atagen2026-10-25 08:43 UTC204
                MentionGapReason::CandidateExpired,
7cdaa86b9489atagen2026-10-25 08:43 UTC205
                None,
7cdaa86b9489atagen2026-10-25 08:43 UTC206
            );
7cdaa86b9489atagen2026-10-25 08:43 UTC207
        }
7cdaa86b9489atagen2026-10-25 08:43 UTC208
        !expired.is_empty()
7cdaa86b9489atagen2026-10-25 08:43 UTC209
    }
7cdaa86b9489atagen2026-10-25 08:43 UTC210
cbb715e11963atagen2026-10-24 23:24 UTC211
    pub fn touch_contexts(&mut self, identity: &str) {
cbb715e11963atagen2026-10-24 23:24 UTC212
        for (key, window) in &self.windows {
cbb715e11963atagen2026-10-24 23:24 UTC213
            if window.target.as_deref() == Some(identity)
cbb715e11963atagen2026-10-24 23:24 UTC214
                || window
cbb715e11963atagen2026-10-24 23:24 UTC215
                    .before
cbb715e11963atagen2026-10-24 23:24 UTC216
                    .iter()
cbb715e11963atagen2026-10-24 23:24 UTC217
                    .chain(&window.after)
cbb715e11963atagen2026-10-24 23:24 UTC218
                    .any(|key| key == identity)
cbb715e11963atagen2026-10-24 23:24 UTC219
            {
cbb715e11963atagen2026-10-24 23:24 UTC220
                if let Some(entry) = self.entries.get_mut(key) {
cbb715e11963atagen2026-10-24 23:24 UTC221
                    entry.revision = self.revision + 1;
cbb715e11963atagen2026-10-24 23:24 UTC222
                }
cbb715e11963atagen2026-10-24 23:24 UTC223
            }
cbb715e11963atagen2026-10-24 23:24 UTC224
        }
cbb715e11963atagen2026-10-24 23:24 UTC225
    }
cbb715e11963atagen2026-10-24 23:24 UTC226
cbb715e11963atagen2026-10-24 23:24 UTC227
    pub fn page(
cbb715e11963atagen2026-10-24 23:24 UTC228
        &self,
cbb715e11963atagen2026-10-24 23:24 UTC229
        account: &OwnedUserId,
cbb715e11963atagen2026-10-24 23:24 UTC230
        cursor: Option<&str>,
cbb715e11963atagen2026-10-24 23:24 UTC231
        limit: u16,
cbb715e11963atagen2026-10-24 23:24 UTC232
    ) -> Result<MentionsPage, Error> {
cbb715e11963atagen2026-10-24 23:24 UTC233
        let (boundary, last) = if let Some(cursor) = cursor {
cbb715e11963atagen2026-10-24 23:24 UTC234
            let value: Value =
cbb715e11963atagen2026-10-24 23:24 UTC235
                serde_json::from_str(cursor).map_err(|_| invalid("invalid mentions cursor"))?;
cbb715e11963atagen2026-10-24 23:24 UTC236
            if value["account"].as_str() != Some(account.as_str()) {
cbb715e11963atagen2026-10-24 23:24 UTC237
                return Err(invalid("mentions cursor belongs to a different account"));
cbb715e11963atagen2026-10-24 23:24 UTC238
            }
cbb715e11963atagen2026-10-24 23:24 UTC239
            let boundary = value["revision"]
cbb715e11963atagen2026-10-24 23:24 UTC240
                .as_u64()
cbb715e11963atagen2026-10-24 23:24 UTC241
                .ok_or_else(|| invalid("invalid mentions cursor"))?;
cbb715e11963atagen2026-10-24 23:24 UTC242
            let timestamp = value["timestamp"]
cbb715e11963atagen2026-10-24 23:24 UTC243
                .as_u64()
cbb715e11963atagen2026-10-24 23:24 UTC244
                .ok_or_else(|| invalid("invalid mentions cursor"))?;
cbb715e11963atagen2026-10-24 23:24 UTC245
            let key = value["key"]
cbb715e11963atagen2026-10-24 23:24 UTC246
                .as_str()
cbb715e11963atagen2026-10-24 23:24 UTC247
                .ok_or_else(|| invalid("invalid mentions cursor"))?
cbb715e11963atagen2026-10-24 23:24 UTC248
                .to_owned();
cbb715e11963atagen2026-10-24 23:24 UTC249
            if boundary > self.revision {
cbb715e11963atagen2026-10-24 23:24 UTC250
                return Err(invalid("mentions cursor is newer than this store"));
cbb715e11963atagen2026-10-24 23:24 UTC251
            }
cbb715e11963atagen2026-10-24 23:24 UTC252
            (boundary, Some((timestamp, key)))
cbb715e11963atagen2026-10-24 23:24 UTC253
        } else {
cbb715e11963atagen2026-10-24 23:24 UTC254
            (self.revision, None)
cbb715e11963atagen2026-10-24 23:24 UTC255
        };
cbb715e11963atagen2026-10-24 23:24 UTC256
        let mut entries: Vec<_> = self
cbb715e11963atagen2026-10-24 23:24 UTC257
            .entries
cbb715e11963atagen2026-10-24 23:24 UTC258
            .iter()
cbb715e11963atagen2026-10-24 23:24 UTC259
            .filter(|(key, entry)| {
cbb715e11963atagen2026-10-24 23:24 UTC260
                entry.discovery_revision <= boundary
cbb715e11963atagen2026-10-24 23:24 UTC261
                    && last.as_ref().is_none_or(|last| {
cbb715e11963atagen2026-10-24 23:24 UTC262
                        (entry.timestamp_ms, key.as_str()) < (last.0, last.1.as_str())
cbb715e11963atagen2026-10-24 23:24 UTC263
                    })
cbb715e11963atagen2026-10-24 23:24 UTC264
            })
cbb715e11963atagen2026-10-24 23:24 UTC265
            .map(|(_, entry)| entry.clone())
cbb715e11963atagen2026-10-24 23:24 UTC266
            .collect();
cbb715e11963atagen2026-10-24 23:24 UTC267
        entries.sort_by(|a, b| (b.timestamp_ms, id(&b.key)).cmp(&(a.timestamp_ms, id(&a.key))));
cbb715e11963atagen2026-10-24 23:24 UTC268
        let limit = usize::from(limit.clamp(1, 50));
cbb715e11963atagen2026-10-24 23:24 UTC269
        let end_reached = entries.len() <= limit;
cbb715e11963atagen2026-10-24 23:24 UTC270
        entries.truncate(limit);
cbb715e11963atagen2026-10-24 23:24 UTC271
        let next_cursor = if end_reached {
cbb715e11963atagen2026-10-24 23:24 UTC272
            None
cbb715e11963atagen2026-10-24 23:24 UTC273
        } else {
cbb715e11963atagen2026-10-24 23:24 UTC274
            entries.last().map(|entry| serde_json::json!({
cbb715e11963atagen2026-10-24 23:24 UTC275
            "account": account.as_str(), "revision": boundary, "timestamp": entry.timestamp_ms, "key": id(&entry.key)
cbb715e11963atagen2026-10-24 23:24 UTC276
        }).to_string())
cbb715e11963atagen2026-10-24 23:24 UTC277
        };
cbb715e11963atagen2026-10-24 23:24 UTC278
        let mut coverage = self.coverage.clone();
cbb715e11963atagen2026-10-24 23:24 UTC279
        coverage.unresolved_candidates = self.candidates.len();
cbb715e11963atagen2026-10-24 23:24 UTC280
        Ok(MentionsPage {
cbb715e11963atagen2026-10-24 23:24 UTC281
            entries,
cbb715e11963atagen2026-10-24 23:24 UTC282
            next_cursor,
cbb715e11963atagen2026-10-24 23:24 UTC283
            end_reached,
cbb715e11963atagen2026-10-24 23:24 UTC284
            coverage,
cbb715e11963atagen2026-10-24 23:24 UTC285
            unread_count: self.entries.values().filter(|entry| !entry.read).count(),
cbb715e11963atagen2026-10-24 23:24 UTC286
            revision: self.revision,
cbb715e11963atagen2026-10-24 23:24 UTC287
            boundary: MentionsBoundary {
cbb715e11963atagen2026-10-24 23:24 UTC288
                account: account.clone(),
cbb715e11963atagen2026-10-24 23:24 UTC289
                revision: boundary,
cbb715e11963atagen2026-10-24 23:24 UTC290
            },
cbb715e11963atagen2026-10-24 23:24 UTC291
        })
cbb715e11963atagen2026-10-24 23:24 UTC292
    }
cbb715e11963atagen2026-10-24 23:24 UTC293
cbb715e11963atagen2026-10-24 23:24 UTC294
    pub fn context(
cbb715e11963atagen2026-10-24 23:24 UTC295
        &self,
cbb715e11963atagen2026-10-24 23:24 UTC296
        key: &MentionKey,
cbb715e11963atagen2026-10-24 23:24 UTC297
    ) -> Result<(SavedMentionContext, Vec<MediaRegistration>), Error> {
cbb715e11963atagen2026-10-24 23:24 UTC298
        let entry = self
cbb715e11963atagen2026-10-24 23:24 UTC299
            .entries
cbb715e11963atagen2026-10-24 23:24 UTC300
            .get(&id(key))
cbb715e11963atagen2026-10-24 23:24 UTC301
            .ok_or_else(|| Error::new(ErrorKind::EventNotFound, "mention was not found"))?;
cbb715e11963atagen2026-10-24 23:24 UTC302
        let mut media = Vec::new();
cbb715e11963atagen2026-10-24 23:24 UTC303
        let mut item = |key: &String| {
cbb715e11963atagen2026-10-24 23:24 UTC304
            self.events.get(key).map(|event| {
cbb715e11963atagen2026-10-24 23:24 UTC305
                media.extend(event.media.clone());
cbb715e11963atagen2026-10-24 23:24 UTC306
                event.item.clone()
cbb715e11963atagen2026-10-24 23:24 UTC307
            })
cbb715e11963atagen2026-10-24 23:24 UTC308
        };
cbb715e11963atagen2026-10-24 23:24 UTC309
        let context = self.windows.get(&id(key)).map(|window| EventContext {
cbb715e11963atagen2026-10-24 23:24 UTC310
            events_before: window.before.iter().filter_map(&mut item).collect(),
cbb715e11963atagen2026-10-24 23:24 UTC311
            event: window.target.as_ref().and_then(&mut item),
cbb715e11963atagen2026-10-24 23:24 UTC312
            events_after: window.after.iter().filter_map(&mut item).collect(),
cbb715e11963atagen2026-10-24 23:24 UTC313
            start_token: window.start.clone(),
cbb715e11963atagen2026-10-24 23:24 UTC314
            end_token: window.end.clone(),
cbb715e11963atagen2026-10-24 23:24 UTC315
        });
cbb715e11963atagen2026-10-24 23:24 UTC316
        Ok((
cbb715e11963atagen2026-10-24 23:24 UTC317
            SavedMentionContext {
cbb715e11963atagen2026-10-24 23:24 UTC318
                key: key.clone(),
cbb715e11963atagen2026-10-24 23:24 UTC319
                state: entry.capture.clone(),
cbb715e11963atagen2026-10-24 23:24 UTC320
                context,
cbb715e11963atagen2026-10-24 23:24 UTC321
                revision: self.revision,
cbb715e11963atagen2026-10-24 23:24 UTC322
            },
cbb715e11963atagen2026-10-24 23:24 UTC323
            media,
cbb715e11963atagen2026-10-24 23:24 UTC324
        ))
cbb715e11963atagen2026-10-24 23:24 UTC325
    }
cbb715e11963atagen2026-10-24 23:24 UTC326
7cdaa86b9489atagen2026-10-25 08:43 UTC327
    pub fn collect_events(&mut self) -> usize {
cbb715e11963atagen2026-10-24 23:24 UTC328
        let mut used: BTreeSet<String> = self.entries.keys().cloned().collect();
cbb715e11963atagen2026-10-24 23:24 UTC329
        for window in self.windows.values() {
cbb715e11963atagen2026-10-24 23:24 UTC330
            used.extend(window.before.iter().chain(&window.after).cloned());
cbb715e11963atagen2026-10-24 23:24 UTC331
            used.extend(window.target.iter().cloned());
cbb715e11963atagen2026-10-24 23:24 UTC332
        }
7cdaa86b9489atagen2026-10-25 08:43 UTC333
        let mut freed = 0;
7cdaa86b9489atagen2026-10-25 08:43 UTC334
        self.events.retain(|key, event| {
7cdaa86b9489atagen2026-10-25 08:43 UTC335
            let keep = used.contains(key);
7cdaa86b9489atagen2026-10-25 08:43 UTC336
            if !keep {
7cdaa86b9489atagen2026-10-25 08:43 UTC337
                freed += key.len() + json_len(event);
7cdaa86b9489atagen2026-10-25 08:43 UTC338
            }
7cdaa86b9489atagen2026-10-25 08:43 UTC339
            keep
7cdaa86b9489atagen2026-10-25 08:43 UTC340
        });
cbb715e11963atagen2026-10-24 23:24 UTC341
        self.capture_attempts
cbb715e11963atagen2026-10-24 23:24 UTC342
            .retain(|key, _| self.entries.contains_key(key));
7cdaa86b9489atagen2026-10-25 08:43 UTC343
        freed
cbb715e11963atagen2026-10-24 23:24 UTC344
    }
cbb715e11963atagen2026-10-24 23:24 UTC345
7cdaa86b9489atagen2026-10-25 08:43 UTC346
    /// returns roughly how many plaintext JSON bytes the eviction freed
7cdaa86b9489atagen2026-10-25 08:43 UTC347
    pub fn evict_one(&mut self) -> Option<usize> {
cbb715e11963atagen2026-10-24 23:24 UTC348
        let oldest = |keys: Vec<String>, entries: &BTreeMap<String, MentionEntry>| {
cbb715e11963atagen2026-10-24 23:24 UTC349
            keys.into_iter()
cbb715e11963atagen2026-10-24 23:24 UTC350
                .min_by_key(|key| entries.get(key).map_or(0, |entry| entry.timestamp_ms))
cbb715e11963atagen2026-10-24 23:24 UTC351
        };
7cdaa86b9489atagen2026-10-25 08:43 UTC352
        let freed = if let Some(key) = oldest(
cbb715e11963atagen2026-10-24 23:24 UTC353
            self.windows
cbb715e11963atagen2026-10-24 23:24 UTC354
                .keys()
cbb715e11963atagen2026-10-24 23:24 UTC355
                .filter(|key| self.entries.get(*key).is_some_and(|entry| entry.read))
cbb715e11963atagen2026-10-24 23:24 UTC356
                .cloned()
cbb715e11963atagen2026-10-24 23:24 UTC357
                .collect(),
cbb715e11963atagen2026-10-24 23:24 UTC358
            &self.entries,
cbb715e11963atagen2026-10-24 23:24 UTC359
        )
cbb715e11963atagen2026-10-24 23:24 UTC360
        .or_else(|| oldest(self.windows.keys().cloned().collect(), &self.entries))
cbb715e11963atagen2026-10-24 23:24 UTC361
        {
7cdaa86b9489atagen2026-10-25 08:43 UTC362
            let window = self.windows.remove(&key);
cbb715e11963atagen2026-10-24 23:24 UTC363
            if let Some(entry) = self.entries.get_mut(&key) {
cbb715e11963atagen2026-10-24 23:24 UTC364
                entry.capture = MentionCaptureState::Unavailable;
cbb715e11963atagen2026-10-24 23:24 UTC365
                entry.revision = self.revision;
cbb715e11963atagen2026-10-24 23:24 UTC366
            }
7cdaa86b9489atagen2026-10-25 08:43 UTC367
            key.len() + json_len(&window)
cbb715e11963atagen2026-10-24 23:24 UTC368
        } else if let Some(key) = self
cbb715e11963atagen2026-10-24 23:24 UTC369
            .entries
cbb715e11963atagen2026-10-24 23:24 UTC370
            .iter()
cbb715e11963atagen2026-10-24 23:24 UTC371
            .min_by_key(|(_, entry)| (!entry.read, entry.timestamp_ms))
cbb715e11963atagen2026-10-24 23:24 UTC372
            .map(|(key, _)| key.clone())
cbb715e11963atagen2026-10-24 23:24 UTC373
        {
7cdaa86b9489atagen2026-10-25 08:43 UTC374
            key.len() + json_len(&self.entries.remove(&key))
7cdaa86b9489atagen2026-10-25 08:43 UTC375
        } else if let Some(key) = self
7cdaa86b9489atagen2026-10-25 08:43 UTC376
            .candidates
7cdaa86b9489atagen2026-10-25 08:43 UTC377
            .iter()
7cdaa86b9489atagen2026-10-25 08:43 UTC378
            .min_by_key(|(_, candidate)| timestamp(&candidate.raw))
7cdaa86b9489atagen2026-10-25 08:43 UTC379
            .map(|(key, _)| key.clone())
7cdaa86b9489atagen2026-10-25 08:43 UTC380
        {
7cdaa86b9489atagen2026-10-25 08:43 UTC381
            key.len() + json_len(&self.candidates.remove(&key))
cbb715e11963atagen2026-10-24 23:24 UTC382
        } else if let Some(key) = self.edits.keys().next().cloned() {
7cdaa86b9489atagen2026-10-25 08:43 UTC383
            key.len() + json_len(&self.edits.remove(&key))
cbb715e11963atagen2026-10-24 23:24 UTC384
        } else {
7cdaa86b9489atagen2026-10-25 08:43 UTC385
            let key = self.redacted.keys().next().cloned()?;
7cdaa86b9489atagen2026-10-25 08:43 UTC386
            self.redacted.remove(&key);
7cdaa86b9489atagen2026-10-25 08:43 UTC387
            key.len() + 24
7cdaa86b9489atagen2026-10-25 08:43 UTC388
        };
cbb715e11963atagen2026-10-24 23:24 UTC389
        self.coverage.truncated = true;
7cdaa86b9489atagen2026-10-25 08:43 UTC390
        Some(freed + self.collect_events())
cbb715e11963atagen2026-10-24 23:24 UTC391
    }
cbb715e11963atagen2026-10-24 23:24 UTC392
cbb715e11963atagen2026-10-24 23:24 UTC393
    pub fn bound(&mut self, entries: usize, events: usize, candidates: usize) {
cbb715e11963atagen2026-10-24 23:24 UTC394
        while self.entries.len() > entries {
cbb715e11963atagen2026-10-24 23:24 UTC395
            if let Some(key) = self
cbb715e11963atagen2026-10-24 23:24 UTC396
                .entries
cbb715e11963atagen2026-10-24 23:24 UTC397
                .iter()
cbb715e11963atagen2026-10-24 23:24 UTC398
                .min_by_key(|(_, entry)| (!entry.read, entry.timestamp_ms))
cbb715e11963atagen2026-10-24 23:24 UTC399
                .map(|(key, _)| key.clone())
cbb715e11963atagen2026-10-24 23:24 UTC400
            {
cbb715e11963atagen2026-10-24 23:24 UTC401
                self.entries.remove(&key);
cbb715e11963atagen2026-10-24 23:24 UTC402
                self.windows.remove(&key);
cbb715e11963atagen2026-10-24 23:24 UTC403
                self.coverage.truncated = true;
cbb715e11963atagen2026-10-24 23:24 UTC404
            }
cbb715e11963atagen2026-10-24 23:24 UTC405
        }
cbb715e11963atagen2026-10-24 23:24 UTC406
        self.collect_events();
7cdaa86b9489atagen2026-10-25 08:43 UTC407
        while self.events.len() > events && self.evict_one().is_some() {}
7cdaa86b9489atagen2026-10-25 08:43 UTC408
        if self.candidates.len() > candidates {
7cdaa86b9489atagen2026-10-25 08:43 UTC409
            let mut oldest: Vec<_> = self
7cdaa86b9489atagen2026-10-25 08:43 UTC410
                .candidates
7cdaa86b9489atagen2026-10-25 08:43 UTC411
                .iter()
7cdaa86b9489atagen2026-10-25 08:43 UTC412
                .map(|(key, candidate)| (timestamp(&candidate.raw), key.clone()))
7cdaa86b9489atagen2026-10-25 08:43 UTC413
                .collect();
7cdaa86b9489atagen2026-10-25 08:43 UTC414
            oldest.sort();
7cdaa86b9489atagen2026-10-25 08:43 UTC415
            for (_, key) in oldest.into_iter().take(self.candidates.len() - candidates) {
7cdaa86b9489atagen2026-10-25 08:43 UTC416
                self.candidates.remove(&key);
7cdaa86b9489atagen2026-10-25 08:43 UTC417
            }
cbb715e11963atagen2026-10-24 23:24 UTC418
            self.coverage.truncated = true;
cbb715e11963atagen2026-10-24 23:24 UTC419
        }
cbb715e11963atagen2026-10-24 23:24 UTC420
        for map in [&mut self.redacted] {
cbb715e11963atagen2026-10-24 23:24 UTC421
            while map.len() > entries + candidates {
cbb715e11963atagen2026-10-24 23:24 UTC422
                let key = map
cbb715e11963atagen2026-10-24 23:24 UTC423
                    .iter()
cbb715e11963atagen2026-10-24 23:24 UTC424
                    .min_by_key(|(_, timestamp)| *timestamp)
cbb715e11963atagen2026-10-24 23:24 UTC425
                    .map(|(key, _)| key.clone())
cbb715e11963atagen2026-10-24 23:24 UTC426
                    .unwrap();
cbb715e11963atagen2026-10-24 23:24 UTC427
                map.remove(&key);
cbb715e11963atagen2026-10-24 23:24 UTC428
                self.coverage.truncated = true;
cbb715e11963atagen2026-10-24 23:24 UTC429
            }
cbb715e11963atagen2026-10-24 23:24 UTC430
        }
cbb715e11963atagen2026-10-24 23:24 UTC431
        while self.edits.len() > candidates {
cbb715e11963atagen2026-10-24 23:24 UTC432
            let key = self.edits.keys().next().cloned().unwrap();
cbb715e11963atagen2026-10-24 23:24 UTC433
            self.edits.remove(&key);
cbb715e11963atagen2026-10-24 23:24 UTC434
            self.coverage.truncated = true;
cbb715e11963atagen2026-10-24 23:24 UTC435
        }
cbb715e11963atagen2026-10-24 23:24 UTC436
        self.coverage.unresolved_candidates = self.candidates.len();
cbb715e11963atagen2026-10-24 23:24 UTC437
    }
cbb715e11963atagen2026-10-24 23:24 UTC438
}