use std::sync::Arc;
use std::time::Instant;
use chrono::{Local, Utc};
use tokio::time::Duration;
use crate::state::buffer::{BufferType, Message, MessageType};
use super::App;
impl App {
pub(crate) fn handle_netsplit_tick(&mut self) {
let messages = self.state.netsplit_state.tick();
for msg in messages {
for buffer_id in &msg.buffer_ids {
let id = self.state.next_message_id();
self.state.add_message(
buffer_id,
Message {
id,
timestamp: Utc::now(),
message_type: MessageType::Event,
nick: None,
nick_mode: None,
text: msg.text.clone(),
highlight: false,
event_key: Some("netsplit".to_string()),
event_params: None,
log_msg_id: None,
log_ref_id: None,
tags: None,
},
);
}
}
}
pub(crate) fn purge_expired_batches(&mut self) {
for tracker in self.batch_trackers.values_mut() {
tracker.purge_expired();
}
}
pub(crate) fn maybe_purge_old_events(&mut self) {
let hours = self.config.logging.event_retention_hours;
if hours == 0 {
return;
}
if self.last_event_purge.elapsed() < Duration::from_secs(3600) {
return;
}
self.last_event_purge = Instant::now();
let Some(storage) = &self.storage else {
return;
};
let db = Arc::clone(&storage.db);
let encrypt = storage.encrypt;
tokio::task::spawn_blocking(move || {
let Ok(conn) = db.lock() else { return };
let has_fts = !encrypt;
let removed = crate::storage::db::purge_old_events(&conn, hours, has_fts);
if removed > 0 {
tracing::info!(
"periodic purge: removed {removed} event messages older than {hours}h"
);
}
});
}
pub(crate) fn maybe_purge_old_mentions(&mut self) {
if self.last_mention_purge.elapsed() < Duration::from_secs(3600) {
return;
}
self.last_mention_purge = Instant::now();
let seven_days_ago = Utc::now().timestamp() - 7 * 24 * 3600;
if let Some(storage) = &self.storage {
let db = Arc::clone(&storage.db);
tokio::task::spawn_blocking(move || {
let Ok(conn) = db.lock() else { return };
if let Ok(removed) =
crate::storage::query::purge_old_mentions(&conn, seven_days_ago)
&& removed > 0
{
tracing::info!("periodic purge: removed {removed} mentions older than 7 days");
}
});
}
if let Some(buf) = self.state.buffers.get_mut(Self::MENTIONS_BUFFER_ID) {
let cutoff =
chrono::DateTime::from_timestamp(seven_days_ago, 0).unwrap_or_else(Utc::now);
let before = buf.messages.len();
buf.messages.retain(|m| m.timestamp >= cutoff);
while buf.messages.len() > 1000 {
buf.messages.pop_front();
}
if buf.messages.len() < before {
buf.messages.shrink_to(buf.messages.len());
}
}
}
pub(crate) fn check_day_changed(&mut self) {
let today = Local::now().date_naive();
if today == self.last_day {
return;
}
self.last_day = today;
let separator_text = super::backlog::format_date_separator(today);
let buffer_ids: Vec<String> = self
.state
.buffers
.iter()
.filter(|(_, buf)| {
matches!(
buf.buffer_type,
BufferType::Channel
| BufferType::Query
| BufferType::DccChat
| BufferType::Server
)
})
.map(|(id, _)| id.clone())
.collect();
for buf_id in buffer_ids {
let id = self.state.next_message_id();
let event_param = separator_text.clone();
self.state.add_local_message(
&buf_id,
Message {
id,
timestamp: Utc::now(),
message_type: MessageType::Event,
nick: None,
nick_mode: None,
text: separator_text.clone(),
highlight: false,
event_key: Some("date_separator".to_string()),
event_params: Some(vec![event_param]),
log_msg_id: None,
log_ref_id: None,
tags: None,
},
);
}
}
pub(crate) fn measure_lag(&mut self) {
let now = Instant::now();
let conn_ids: Vec<String> = self.irc_handles.keys().cloned().collect();
for conn_id in conn_ids {
let is_connected =
self.state.connections.get(&conn_id).is_some_and(|c| {
c.status == crate::state::connection::ConnectionStatus::Connected
});
if !is_connected {
continue;
}
if let Some(sent_at) = self.lag_pings.get(&conn_id) {
let pending = self
.state
.connections
.get(&conn_id)
.is_some_and(|c| c.lag_pending);
if pending && sent_at.elapsed().as_secs() >= 300 {
let buf_id = self.state.connections.get(&conn_id).map_or_else(
|| conn_id.clone(),
|c| crate::state::buffer::make_buffer_id(&conn_id, &c.label),
);
let msg_id = self.state.next_message_id();
self.state.add_message(
&buf_id,
crate::state::buffer::Message {
id: msg_id,
timestamp: chrono::Utc::now(),
message_type: crate::state::buffer::MessageType::Event,
nick: None,
nick_mode: None,
text: format!(
"Connection to {conn_id} timed out (no PONG for 5 minutes)"
),
highlight: false,
tags: None,
log_msg_id: None,
log_ref_id: None,
event_key: None,
event_params: Some(Vec::new()),
},
);
if let Some(handle) = self.irc_handles.get(&conn_id) {
let _ = handle.sender.send(::irc::proto::Command::QUIT(Some(
"Ping timeout".to_string(),
)));
}
continue;
}
}
let should_ping = self
.lag_pings
.get(&conn_id)
.is_none_or(|last| now.duration_since(*last).as_secs() >= 30);
if should_ping {
let ts = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis()
.to_string();
if let Some(handle) = self.irc_handles.get(&conn_id) {
let _ = handle
.sender
.send(::irc::proto::Command::Raw("PING".to_string(), vec![ts]));
}
self.lag_pings.insert(conn_id.clone(), now);
if let Some(conn) = self.state.connections.get_mut(&conn_id) {
conn.lag_pending = true;
}
}
}
}
}