use std::{
mem::ManuallyDrop,
ops::{Deref, DerefMut},
sync::Arc,
};
use async_lock::{Semaphore, SemaphoreGuardArc};
use dashmap::DashMap;
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use std::fmt::Debug;
use teloxide::{
dispatching::dialogue::{serializer::Json, GetChatId, SqliteStorage, Storage},
prelude::Dialogue,
types::{ChatId, Update},
};
static EXCLUSIONS: Lazy<DashMap<ChatId, Arc<async_lock::Semaphore>>> = Lazy::new(DashMap::new);
type TeloDialogue<T> = Dialogue<T, SqliteStorage<Json>>;
#[derive(Clone)]
pub struct MutDialogueState<T>
where
T: Send + Sync + Clone + 'static + Default + Debug,
SqliteStorage<Json>: Storage<T>,
<SqliteStorage<Json> as Storage<T>>::Error: std::fmt::Display,
{
chat_id: ChatId,
telodial: TeloDialogue<T>,
state: ManuallyDrop<Arc<(SemaphoreGuardArc, Mutex<T>)>>,
}
impl<T> MutDialogueState<T>
where
T: Send + Sync + Clone + 'static + Default + Debug,
SqliteStorage<Json>: Storage<T>,
<SqliteStorage<Json> as Storage<T>>::Error: std::fmt::Display,
{
pub async fn new(update: Update, telodial: TeloDialogue<T>) -> Option<Self> {
let chat_id = update.chat_id()?;
let excl = EXCLUSIONS
.entry(chat_id)
.or_insert_with(|| Arc::new(Semaphore::new(1)))
;
let guard = excl.acquire_arc().await;
let state = telodial
.get()
.await
.inspect_err(|e| {
eprintln!("Error getting state: {}", e);
})
.ok()?
.unwrap_or_default();
Some(Self {
chat_id,
telodial,
state: ManuallyDrop::new(Arc::new((guard, Mutex::new(state)))),
})
}
pub fn chat_id(&self) -> ChatId {
self.chat_id
}
pub fn get(&self) -> impl Deref<Target = T> + Debug + Send + Sync + '_ {
self.state.1.lock()
}
pub fn as_mut(&self) -> impl DerefMut<Target = T> + Send + Sync + '_ {
self.state.1.lock()
}
}
impl<T> Drop for MutDialogueState<T>
where
T: Send + Sync + Clone + 'static + Default + Debug,
SqliteStorage<Json>: Storage<T>,
<SqliteStorage<Json> as Storage<T>>::Error: std::fmt::Display,
{
fn drop(&mut self) {
let arc = unsafe { ManuallyDrop::take(&mut self.state) };
let Some((guard, mutex)) = Arc::into_inner(arc) else {
return;
};
let state = mutex.into_inner();
let telodial = self.telodial.clone();
tokio::spawn(async move {
let _ = telodial.update(state).await.inspect_err(|e| {
eprintln!("Error setting state: {}", e);
});
drop(guard);
});
}
}