use anyhow::anyhow;
use std::{
collections::HashMap,
path::PathBuf,
sync::{Arc, Condvar, LazyLock, Mutex, OnceLock},
time::Duration,
};
use matrix_sdk::{
Client, RoomMemberships,
ruma::{OwnedRoomId, OwnedUserId},
};
use matrix_sdk_ui::sync_service::SyncService;
use tokio::{
sync::{
broadcast,
mpsc::{Receiver, UnboundedSender},
},
time::{Instant, interval},
};
use crate::{
events::timeline::TimelineKind,
init::session::ClientSession,
models::{
async_requests::MatrixRequest, event_bridge::EventBridge,
events::MatrixVerificationResponse,
},
submit_async_request,
};
pub static REQUEST_SENDER: OnceLock<UnboundedSender<MatrixRequest>> = OnceLock::new();
pub static SYNC_SERVICE: OnceLock<SyncService> = OnceLock::new();
pub static ALL_ROOMS_LOADED: OnceLock<bool> = OnceLock::new();
pub(crate) struct TempClientSlot {
pub(crate) lock: Mutex<Option<Client>>,
pub(crate) cvar: Condvar,
}
pub static TEMP_CLIENT: LazyLock<TempClientSlot> = LazyLock::new(|| TempClientSlot {
lock: Mutex::new(None),
cvar: Condvar::new(),
});
pub static TEMP_CLIENT_SESSION: LazyLock<Mutex<Option<ClientSession>>> =
LazyLock::new(|| Mutex::new(None));
pub static CLIENT: OnceLock<Client> = OnceLock::new();
pub static CURRENT_USER_ID: OnceLock<OwnedUserId> = OnceLock::new();
pub static LOGIN_STORE_READY: OnceLock<bool> = OnceLock::new();
pub static HAS_SESSION_STORED: OnceLock<bool> = OnceLock::new();
#[derive(Debug, Clone)]
pub enum UIUpdateMessage {
RefreshUI,
}
static GLOBAL_BROADCASTER: OnceLock<GlobalBroadcaster> = OnceLock::new();
pub struct GlobalBroadcaster {
sender: broadcast::Sender<UIUpdateMessage>,
}
pub static APP_DATA_DIR: OnceLock<PathBuf> = OnceLock::new();
impl GlobalBroadcaster {
fn new(capacity: usize) -> Self {
let (sender, _) = broadcast::channel(capacity);
Self { sender }
}
fn broadcast(
&self,
message: UIUpdateMessage,
) -> Result<usize, broadcast::error::SendError<UIUpdateMessage>> {
self.sender.send(message)
}
fn subscribe(&self) -> broadcast::Receiver<UIUpdateMessage> {
self.sender.subscribe()
}
}
pub fn init_broadcaster(capacity: usize) -> Result<(), &'static str> {
GLOBAL_BROADCASTER
.set(GlobalBroadcaster::new(capacity))
.map_err(|_| "Broadcaster already initialized")
}
pub fn broadcast_event(message: UIUpdateMessage) {
let broadcaster = GLOBAL_BROADCASTER
.get()
.expect("Broadcaster not initialized. Call init_broadcaster() first.");
let _ = broadcaster.broadcast(message);
}
pub fn subscribe_to_events() -> Result<broadcast::Receiver<UIUpdateMessage>, &'static str> {
let broadcaster = GLOBAL_BROADCASTER
.get()
.ok_or("Broadcaster not initialized. Call init_broadcaster() first.")?;
Ok(broadcaster.subscribe())
}
pub static EVENT_BRIDGE: OnceLock<EventBridge> = OnceLock::new();
pub fn get_event_bridge<'a>() -> anyhow::Result<&'a EventBridge> {
let bridge = EVENT_BRIDGE
.get()
.ok_or(anyhow!("The event bridge is not yet set"))?;
Ok(bridge)
}
pub static VERIFICATION_RESPONSE_RECEIVER: OnceLock<
tokio::sync::Mutex<Receiver<MatrixVerificationResponse>>,
> = OnceLock::new();
pub async fn get_verification_response_receiver_lock<'a>()
-> anyhow::Result<tokio::sync::MutexGuard<'a, Receiver<MatrixVerificationResponse>>> {
let recv = VERIFICATION_RESPONSE_RECEIVER
.get()
.ok_or(anyhow!("The verification response receiver is not yet set"))?;
Ok(recv.lock().await)
}
pub(crate) struct ExpiryMap {
items: Arc<Mutex<HashMap<OwnedRoomId, Instant>>>,
}
impl ExpiryMap {
fn new() -> Self {
let items: Arc<Mutex<HashMap<OwnedRoomId, Instant>>> = Arc::new(Mutex::new(HashMap::new()));
let items_clone = Arc::clone(&items);
tokio::spawn(async move {
let mut ticker = interval(Duration::from_millis(500));
loop {
ticker.tick().await;
let mut expired_ids = Vec::new();
let now = Instant::now();
{
let mut map = items_clone.lock().unwrap();
map.retain(|id, &mut expiry| {
if now >= expiry {
expired_ids.push(id.to_owned());
false
} else {
true
}
});
}
for id in expired_ids {
submit_async_request(MatrixRequest::GetRoomMembers {
timeline_kind: TimelineKind::MainRoom { room_id: id },
memberships: RoomMemberships::JOIN,
local_only: true,
});
}
}
});
ExpiryMap { items }
}
pub(crate) fn insert(&self, id: OwnedRoomId, duration: Duration) {
let expiry = Instant::now() + duration;
self.items.lock().unwrap().insert(id, expiry);
}
}
pub(crate) static MEMBERSHIP_UPDATES_EXPIRY_MAP: LazyLock<ExpiryMap> =
LazyLock::new(ExpiryMap::new);