qt-chat-app

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

message_cache.rs (5047B)


      1 //! Message caching with O(1) deduplication
      2 //!
      3 //! Stores messages by chat_id with HashSet-based dedup to avoid
      4 //! duplicate messages from SSE reconnections.
      5 
      6 use crate::generated::{Chat, Message, UserChat};
      7 use serde::Serialize;
      8 use std::collections::{HashMap, HashSet};
      9 
     10 /// Display-ready chat for QML (with computed/default fields)
     11 #[derive(Debug, Clone, Serialize)]
     12 pub struct DisplayChat {
     13     pub id: i64,
     14     pub title: String,
     15     pub created_at: String,
     16     pub created_by: String,
     17     // Display-only fields (defaults for now)
     18     pub avatar: String,
     19     pub is_online: bool,
     20     pub last_message: String,
     21     pub unread_count: i32,
     22 }
     23 
     24 impl DisplayChat {
     25     pub fn from_chat(chat: &Chat) -> Self {
     26         Self {
     27             id: chat.id,
     28             title: chat.title.clone(),
     29             created_at: chat.created_at.clone(),
     30             created_by: chat.created_by.clone(),
     31             avatar: String::new(),
     32             is_online: false,
     33             last_message: String::new(),
     34             unread_count: 0,
     35         }
     36     }
     37 
     38     pub fn from_user_chat(chat: &UserChat) -> Self {
     39         Self {
     40             id: chat.id,
     41             title: chat.title.clone(),
     42             created_at: chat.created_at.clone(),
     43             created_by: chat.created_by.clone(),
     44             avatar: String::new(),
     45             is_online: false,
     46             last_message: String::new(),
     47             unread_count: 0,
     48         }
     49     }
     50 }
     51 
     52 /// Display-ready message for QML (pre-computed fields)
     53 #[derive(Debug, Clone, Serialize)]
     54 pub struct DisplayMessage {
     55     pub id: i64,
     56     pub chat_id: i64,
     57     pub sender_id: String,
     58     pub content: String,
     59     pub sent_at: String,
     60     pub is_outgoing: bool,
     61     pub is_read: bool,
     62 }
     63 
     64 impl DisplayMessage {
     65     /// Transform a Message into a DisplayMessage
     66     pub fn from_message(msg: &Message, current_user_id: &str) -> Self {
     67         let sender_id = msg.sender_id.clone().unwrap_or_default();
     68         Self {
     69             id: msg.id,
     70             chat_id: msg.chat_id,
     71             sender_id: sender_id.clone(),
     72             content: msg.content.clone(),
     73             sent_at: msg.sent_at.clone(),
     74             is_outgoing: sender_id == current_user_id,
     75             is_read: true,
     76         }
     77     }
     78 }
     79 
     80 /// Message cache with O(1) deduplication
     81 pub struct MessageCache {
     82     /// Messages by chat_id
     83     messages: HashMap<i64, Vec<DisplayMessage>>,
     84     /// Seen message IDs for deduplication
     85     seen_ids: HashSet<i64>,
     86 }
     87 
     88 impl Default for MessageCache {
     89     fn default() -> Self {
     90         Self::new()
     91     }
     92 }
     93 
     94 impl MessageCache {
     95     pub fn new() -> Self {
     96         Self {
     97             messages: HashMap::new(),
     98             seen_ids: HashSet::new(),
     99         }
    100     }
    101 
    102     /// Add a message if not already seen. Returns Some(DisplayMessage) if new, None if duplicate.
    103     pub fn add_message(&mut self, msg: Message, current_user_id: &str) -> Option<DisplayMessage> {
    104         if self.seen_ids.contains(&msg.id) {
    105             return None;
    106         }
    107 
    108         self.seen_ids.insert(msg.id);
    109         let display_msg = DisplayMessage::from_message(&msg, current_user_id);
    110 
    111         self.messages
    112             .entry(msg.chat_id)
    113             .or_default()
    114             .push(display_msg.clone());
    115 
    116         Some(display_msg)
    117     }
    118 
    119     /// Get all messages for a chat
    120     pub fn get_messages(&self, chat_id: i64) -> &[DisplayMessage] {
    121         self.messages.get(&chat_id).map(|v| v.as_slice()).unwrap_or(&[])
    122     }
    123 
    124     /// Set messages for a chat (initial load), replacing any existing
    125     pub fn set_messages(&mut self, chat_id: i64, messages: Vec<Message>, current_user_id: &str) -> Vec<DisplayMessage> {
    126         let display_messages: Vec<DisplayMessage> = messages
    127             .iter()
    128             .map(|msg| {
    129                 self.seen_ids.insert(msg.id);
    130                 DisplayMessage::from_message(msg, current_user_id)
    131             })
    132             .collect();
    133 
    134         self.messages.insert(chat_id, display_messages.clone());
    135         display_messages
    136     }
    137 
    138     /// Prepend older messages (for pagination)
    139     pub fn prepend_messages(&mut self, chat_id: i64, messages: Vec<Message>, current_user_id: &str) -> Vec<DisplayMessage> {
    140         let display_messages: Vec<DisplayMessage> = messages
    141             .iter()
    142             .filter_map(|msg| {
    143                 if self.seen_ids.contains(&msg.id) {
    144                     None
    145                 } else {
    146                     self.seen_ids.insert(msg.id);
    147                     Some(DisplayMessage::from_message(msg, current_user_id))
    148                 }
    149             })
    150             .collect();
    151 
    152         let chat_messages = self.messages.entry(chat_id).or_default();
    153         let mut new_messages = display_messages.clone();
    154         new_messages.append(chat_messages);
    155         *chat_messages = new_messages;
    156 
    157         display_messages
    158     }
    159 
    160     /// Clear messages for a chat
    161     pub fn clear_chat(&mut self, chat_id: i64) {
    162         if let Some(messages) = self.messages.remove(&chat_id) {
    163             for msg in messages {
    164                 self.seen_ids.remove(&msg.id);
    165             }
    166         }
    167     }
    168 }