From fb7e504f2caf26f734e070f0d054bf8fdcfe2267 Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 13 Jun 2022 16:20:44 +0200 Subject: [PATCH] Flesh out store details --- cove-tui/src/main.rs | 1 - cove-tui/src/store.rs | 135 ++++++++++++++++++------------------ cove-tui/src/store/dummy.rs | 112 ++++++++++++++++++++++++++++++ cove-tui/src/traits.rs | 16 ----- cove-tui/src/ui.rs | 7 +- 5 files changed, 183 insertions(+), 88 deletions(-) create mode 100644 cove-tui/src/store/dummy.rs delete mode 100644 cove-tui/src/traits.rs diff --git a/cove-tui/src/main.rs b/cove-tui/src/main.rs index c5c77d3..85db896 100644 --- a/cove-tui/src/main.rs +++ b/cove-tui/src/main.rs @@ -2,7 +2,6 @@ mod chat; mod store; -mod traits; mod ui; use toss::terminal::Terminal; diff --git a/cove-tui/src/store.rs b/cove-tui/src/store.rs index 9a55802..39eab24 100644 --- a/cove-tui/src/store.rs +++ b/cove-tui/src/store.rs @@ -1,86 +1,89 @@ +pub mod dummy; + use std::collections::HashMap; +use std::hash::Hash; use async_trait::async_trait; -use chrono::{DateTime, TimeZone, Utc}; +use chrono::{DateTime, Utc}; -use crate::traits::{Msg, MsgStore}; +pub trait Msg { + type Id: Hash + Eq; + fn id(&self) -> Self::Id; + fn parent(&self) -> Option; -pub struct DummyMsg { - id: usize, - parent: Option, - time: DateTime, - nick: String, - content: String, + fn time(&self) -> DateTime; + fn nick(&self) -> String; + fn content(&self) -> String; } -impl DummyMsg { - pub fn new(id: usize, nick: S, content: S) -> Self - where - S: Into, - { +pub struct Path(Vec); + +impl Path { + pub fn new(segments: Vec) -> Self { + assert!(!segments.is_empty(), "segments must not be empty"); + Self(segments) + } + + pub fn segments(&self) -> &[I] { + &self.0 + } + + pub fn first(&self) -> &I { + self.0.first().expect("path is not empty") + } + + pub fn first_mut(&mut self) -> &mut I { + self.0.first_mut().expect("path is not empty") + } + + pub fn last(&self) -> &I { + self.0.last().expect("path is not empty") + } + + pub fn last_mut(&mut self) -> &mut I { + self.0.last_mut().expect("path is not empty") + } +} + +pub struct Tree { + root: M::Id, + msgs: HashMap, + children: HashMap>, +} + +impl Tree { + pub fn new(root: M::Id, msgs: Vec) -> Self { + let msgs: HashMap = msgs.into_iter().map(|m| (m.id(), m)).collect(); + + let mut children: HashMap> = HashMap::new(); + for msg in msgs.values() { + if let Some(parent) = msg.parent() { + children.entry(parent).or_default().push(msg.id()); + } + } + Self { - id, - parent: None, - time: Utc.timestamp(0, 0), - nick: nick.into(), - content: content.into(), + root, + msgs, + children, } } - pub fn parent(mut self, parent: usize) -> Self { - self.parent = Some(parent); - self - } -} - -impl Msg for DummyMsg { - type Id = usize; - - fn id(&self) -> Self::Id { - self.id + pub fn root(&self) -> &M::Id { + &self.root } - fn time(&self) -> DateTime { - self.time + pub fn msg(&self, id: &M::Id) -> Option<&M> { + self.msgs.get(id) } - fn nick(&self) -> String { - self.nick.clone() - } - - fn content(&self) -> String { - self.content.clone() - } -} - -pub struct DummyStore { - msgs: HashMap, -} - -impl DummyStore { - pub fn new() -> Self { - Self { - msgs: HashMap::new(), - } - } - - pub fn msg(mut self, msg: DummyMsg) -> Self { - self.msgs.insert(msg.id(), msg); - self + pub fn children(&self, id: &M::Id) -> Option<&[M::Id]> { + self.children.get(id).map(|c| c as &[M::Id]) } } #[async_trait] -impl MsgStore for DummyStore { - async fn path(&self, _room: &str, mut id: usize) -> Vec { - let mut path = vec![id]; - - while let Some(parent) = self.msgs.get(&id).and_then(|msg| msg.parent) { - path.push(parent); - id = parent; - } - - path.reverse(); - path - } +pub trait MsgStore { + async fn path(&self, room: &str, id: M::Id) -> Path; + async fn thread(&self, room: &str, root: M::Id) -> Tree; } diff --git a/cove-tui/src/store/dummy.rs b/cove-tui/src/store/dummy.rs new file mode 100644 index 0000000..af8d61c --- /dev/null +++ b/cove-tui/src/store/dummy.rs @@ -0,0 +1,112 @@ +use std::collections::HashMap; +use std::thread::Thread; + +use async_trait::async_trait; +use chrono::{DateTime, TimeZone, Utc}; + +use super::{Msg, MsgStore, Path, Tree}; + +#[derive(Clone)] +pub struct DummyMsg { + id: usize, + parent: Option, + time: DateTime, + nick: String, + content: String, +} + +impl DummyMsg { + pub fn new(id: usize, nick: S, content: S) -> Self + where + S: Into, + { + Self { + id, + parent: None, + time: Utc.timestamp(0, 0), + nick: nick.into(), + content: content.into(), + } + } + + pub fn parent(mut self, parent: usize) -> Self { + self.parent = Some(parent); + self + } +} + +impl Msg for DummyMsg { + type Id = usize; + + fn id(&self) -> Self::Id { + self.id + } + + fn parent(&self) -> Option { + self.parent + } + + fn time(&self) -> DateTime { + self.time + } + + fn nick(&self) -> String { + self.nick.clone() + } + + fn content(&self) -> String { + self.content.clone() + } +} + +pub struct DummyStore { + msgs: HashMap, + children: HashMap>, +} + +impl DummyStore { + pub fn new() -> Self { + Self { + msgs: HashMap::new(), + children: HashMap::new(), + } + } + + pub fn msg(mut self, msg: DummyMsg) -> Self { + if let Some(parent) = msg.parent { + self.children.entry(parent).or_default().push(msg.id()); + } + self.msgs.insert(msg.id(), msg); + self + } + + fn tree(&self, id: usize, result: &mut Vec) { + if let Some(msg) = self.msgs.get(&id) { + result.push(msg.clone()); + if let Some(children) = self.children.get(&id) { + for child in children { + self.tree(*child, result); + } + } + } + } +} + +#[async_trait] +impl MsgStore for DummyStore { + async fn path(&self, _room: &str, mut id: usize) -> Path { + let mut segments = vec![id]; + while let Some(parent) = self.msgs.get(&id).and_then(|msg| msg.parent) { + segments.push(parent); + id = parent; + } + segments.reverse(); + Path::new(segments) + } + + async fn thread(&self, _room: &str, root: usize) -> Tree { + let mut msgs = vec![]; + + Tree::new(root, msgs) + } +} diff --git a/cove-tui/src/traits.rs b/cove-tui/src/traits.rs deleted file mode 100644 index 3f1b0f9..0000000 --- a/cove-tui/src/traits.rs +++ /dev/null @@ -1,16 +0,0 @@ -use async_trait::async_trait; -use chrono::{DateTime, Utc}; - -pub trait Msg { - type Id; - fn id(&self) -> Self::Id; - - fn time(&self) -> DateTime; - fn nick(&self) -> String; - fn content(&self) -> String; -} - -#[async_trait] -pub trait MsgStore { - async fn path(&self, room: &str, id: M::Id) -> Vec; -} diff --git a/cove-tui/src/ui.rs b/cove-tui/src/ui.rs index 81c709d..a8f7b44 100644 --- a/cove-tui/src/ui.rs +++ b/cove-tui/src/ui.rs @@ -1,10 +1,7 @@ -use std::collections::hash_map::Entry; use std::sync::{Arc, Weak}; use std::time::Duration; -use crossterm::event::{Event, EventStream, KeyCode, KeyEvent, MouseEvent}; -use crossterm::style::ContentStyle; -use futures::StreamExt; +use crossterm::event::{Event, KeyCode, KeyEvent, MouseEvent}; use parking_lot::FairMutex; use tokio::sync::mpsc::error::TryRecvError; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; @@ -13,7 +10,7 @@ use toss::frame::{Frame, Pos, Size}; use toss::terminal::Terminal; use crate::chat::Chat; -use crate::store::{DummyMsg, DummyStore}; +use crate::store::dummy::{DummyMsg, DummyStore}; #[derive(Debug)] pub enum UiEvent {