iris-chat 0.1.39

Iris Chat command line client and shared encrypted chat core
Documentation
use super::*;
use std::collections::BTreeMap;

type MessageKey = (u64, String, String);

pub(super) fn collect_device_sync_messages(
    core: &AppCore,
    roster_at: u64,
    after: Option<&DeviceSyncCursor>,
    page_size: usize,
) -> (Vec<DeviceSyncMessage>, Option<DeviceSyncCursor>) {
    let now = unix_now().get();
    let limit = page_size.saturating_add(1);
    let mut messages = BTreeMap::new();
    for message in core.threads.values().flat_map(|thread| &thread.messages) {
        if eligible(message, roster_at, now)
            && after.is_none_or(|cursor| after_cursor(message, cursor))
        {
            if let Some(value) = from_snapshot(message) {
                insert_bounded(&mut messages, value, limit);
            }
        }
    }

    let mut db_after = after
        .map(|cursor| (cursor.created_at, cursor.chat_id.clone(), cursor.id.clone()))
        .unwrap_or((roster_at, String::new(), String::new()));
    loop {
        let rows = core
            .app_store
            .load_device_sync_messages_page(
                roster_at,
                now,
                db_after.0,
                &db_after.1,
                &db_after.2,
                limit,
            )
            .unwrap_or_default();
        let exhausted = rows.len() < limit;
        let Some(last) = rows.last() else { break };
        let frontier = (last.created_at_secs, last.chat_id.clone(), last.id.clone());
        for message in rows {
            if in_memory_message(core, &message.chat_id, &message.id).is_some() {
                continue;
            }
            insert_bounded(&mut messages, from_persisted(message), limit);
        }
        let page_is_known = messages
            .last_key_value()
            .is_some_and(|(last, _)| messages.len() >= limit && *last <= frontier);
        db_after = frontier;
        if exhausted || page_is_known {
            break;
        }
    }

    let has_more = messages.len() > page_size;
    while messages.len() > page_size {
        messages.pop_last();
    }
    let values = messages.into_values().collect::<Vec<_>>();
    let next = has_more
        .then(|| values.last().map(DeviceSyncCursor::from))
        .flatten();
    (values, next)
}

fn insert_bounded(
    messages: &mut BTreeMap<MessageKey, DeviceSyncMessage>,
    value: DeviceSyncMessage,
    limit: usize,
) {
    messages.insert(message_key(&value), value);
    while messages.len() > limit {
        messages.pop_last();
    }
}

fn in_memory_message<'a>(
    core: &'a AppCore,
    chat_id: &str,
    id: &str,
) -> Option<&'a ChatMessageSnapshot> {
    core.threads
        .get(chat_id)?
        .messages
        .iter()
        .find(|message| message.id == id)
}

fn eligible(message: &ChatMessageSnapshot, roster_at: u64, now: u64) -> bool {
    message.created_at_secs >= roster_at
        && message
            .expires_at_secs
            .is_none_or(|expires_at| expires_at > now)
        && matches!(message.kind, ChatMessageKind::User)
        && !matches!(
            message.delivery,
            DeliveryState::Queued | DeliveryState::Pending | DeliveryState::Failed
        )
}

fn after_cursor(message: &ChatMessageSnapshot, cursor: &DeviceSyncCursor) -> bool {
    (message.created_at_secs, &message.chat_id, &message.id)
        > (cursor.created_at, &cursor.chat_id, &cursor.id)
}

fn from_snapshot(message: &ChatMessageSnapshot) -> Option<DeviceSyncMessage> {
    Some(DeviceSyncMessage {
        chat_id: message.chat_id.clone(),
        id: message.id.clone(),
        body: message.body.clone(),
        author: message.author_owner_pubkey_hex.clone()?,
        created_at: message.created_at_secs,
        expires_at: message.expires_at_secs,
    })
}

fn from_persisted(message: PersistedMessage) -> DeviceSyncMessage {
    DeviceSyncMessage {
        chat_id: message.chat_id,
        id: message.id,
        body: message.body,
        author: message.author_owner_pubkey_hex.unwrap_or(message.author),
        created_at: message.created_at_secs,
        expires_at: message.expires_at_secs,
    }
}

fn message_key(message: &DeviceSyncMessage) -> MessageKey {
    (
        message.created_at,
        message.chat_id.clone(),
        message.id.clone(),
    )
}