use super::{HistoryItem, MessageType, format_timestamp};
use std::collections::{BTreeMap, VecDeque};
use std::sync::RwLock;
use std::time::{SystemTime, UNIX_EPOCH, Duration};
#[derive(Debug, Clone)]
pub struct HistoryConfig {
pub max_messages_per_target: usize,
pub max_age: Duration,
pub store_joins: bool,
pub store_modes: bool,
pub store_nicks: bool,
}
impl Default for HistoryConfig {
fn default() -> Self {
Self {
max_messages_per_target: 1000,
max_age: Duration::from_secs(30 * 24 * 60 * 60), store_joins: true,
store_modes: true,
store_nicks: true,
}
}
}
#[derive(Debug)]
struct HistoryBuffer {
messages: VecDeque<HistoryItem>,
max_size: usize,
max_age: Duration,
}
impl HistoryBuffer {
fn new(config: &HistoryConfig) -> Self {
Self {
messages: VecDeque::with_capacity(config.max_messages_per_target),
max_size: config.max_messages_per_target,
max_age: config.max_age,
}
}
fn add_message(&mut self, item: HistoryItem) {
self.cleanup_old_messages();
self.messages.push_back(item);
while self.messages.len() > self.max_size {
self.messages.pop_front();
}
}
fn cleanup_old_messages(&mut self) {
let cutoff = SystemTime::now()
.checked_sub(self.max_age)
.unwrap_or(UNIX_EPOCH);
while let Some(front) = self.messages.front() {
if front.timestamp < cutoff {
self.messages.pop_front();
} else {
break;
}
}
}
fn get_messages_between(
&self,
start: Option<SystemTime>,
end: Option<SystemTime>,
limit: usize,
ascending: bool,
) -> Vec<HistoryItem> {
let mut results = Vec::new();
for item in &self.messages {
if let Some(start_time) = start {
if item.timestamp <= start_time {
continue;
}
}
if let Some(end_time) = end {
if item.timestamp >= end_time {
continue;
}
}
results.push(item.clone());
}
if ascending {
results.sort_by_key(|item| item.timestamp);
} else {
results.sort_by_key(|item| std::cmp::Reverse(item.timestamp));
}
results.truncate(limit);
results
}
fn get_messages_around(
&self,
center_time: SystemTime,
center_msgid: Option<&str>,
limit: usize,
) -> Vec<HistoryItem> {
let mut before = Vec::new();
let mut after = Vec::new();
for item in &self.messages {
if let Some(msgid) = center_msgid {
if item.msgid == msgid {
continue;
}
} else if item.timestamp == center_time {
continue;
}
if item.timestamp < center_time {
before.push(item.clone());
} else if item.timestamp > center_time {
after.push(item.clone());
}
}
before.sort_by_key(|item| std::cmp::Reverse(item.timestamp));
after.sort_by_key(|item| item.timestamp);
let half_limit = limit / 2;
before.truncate(half_limit);
after.truncate(limit - before.len());
let mut result = before;
result.extend(after);
result.sort_by_key(|item| item.timestamp);
result
}
fn find_by_msgid(&self, msgid: &str) -> Option<&HistoryItem> {
self.messages.iter().find(|item| item.msgid == msgid)
}
fn get_targets(&self) -> Vec<(String, SystemTime)> {
let mut targets = BTreeMap::new();
for item in &self.messages {
let target_name = item.correspondent.as_ref().unwrap_or(&item.target).clone();
targets.entry(target_name)
.and_modify(|timestamp| {
if item.timestamp > *timestamp {
*timestamp = item.timestamp;
}
})
.or_insert(item.timestamp);
}
targets.into_iter().collect()
}
}
pub struct HistoryStorage {
buffers: RwLock<BTreeMap<String, HistoryBuffer>>,
config: HistoryConfig,
}
impl HistoryStorage {
pub fn new(config: HistoryConfig) -> Self {
Self {
buffers: RwLock::new(BTreeMap::new()),
config,
}
}
pub fn store_message(&self, item: HistoryItem) {
match item.message_type {
MessageType::Join | MessageType::Part | MessageType::Quit if !self.config.store_joins => return,
MessageType::Mode if !self.config.store_modes => return,
MessageType::Nick if !self.config.store_nicks => return,
_ => {}
}
let target = item.target.clone();
let mut buffers = self.buffers.write().unwrap();
let buffer = buffers.entry(target).or_insert_with(|| HistoryBuffer::new(&self.config));
buffer.add_message(item);
}
pub fn get_messages_between(
&self,
target: &str,
start: Option<SystemTime>,
end: Option<SystemTime>,
limit: usize,
ascending: bool,
) -> Vec<HistoryItem> {
let buffers = self.buffers.read().unwrap();
if let Some(buffer) = buffers.get(target) {
buffer.get_messages_between(start, end, limit, ascending)
} else {
Vec::new()
}
}
pub fn get_messages_around(
&self,
target: &str,
center_time: SystemTime,
center_msgid: Option<&str>,
limit: usize,
) -> Vec<HistoryItem> {
let buffers = self.buffers.read().unwrap();
if let Some(buffer) = buffers.get(target) {
buffer.get_messages_around(center_time, center_msgid, limit)
} else {
Vec::new()
}
}
pub fn find_message_by_id(&self, target: &str, msgid: &str) -> Option<HistoryItem> {
let buffers = self.buffers.read().unwrap();
if let Some(buffer) = buffers.get(target) {
buffer.find_by_msgid(msgid).cloned()
} else {
None
}
}
pub fn get_targets_for_user(&self, _user: &str) -> Vec<(String, SystemTime)> {
let buffers = self.buffers.read().unwrap();
let mut all_targets = Vec::new();
for buffer in buffers.values() {
all_targets.extend(buffer.get_targets());
}
all_targets.sort_by_key(|(_, timestamp)| std::cmp::Reverse(*timestamp));
let mut seen = std::collections::HashSet::new();
all_targets.retain(|(name, _)| seen.insert(name.clone()));
all_targets
}
pub fn cleanup_old_messages(&self) {
let mut buffers = self.buffers.write().unwrap();
for buffer in buffers.values_mut() {
buffer.cleanup_old_messages();
}
}
}
impl Default for HistoryStorage {
fn default() -> Self {
Self::new(HistoryConfig::default())
}
}