use crate::sm::SessionList;
use crate::sm::SessionListSession;
use crate::sm::SessionListToplevel;
use crate::sm::SessionManager;
use crate::sm::SessionName;
use crate::sm::ToplevelSessionName;
use crate::sm::sm_jobs::SmDbStateHolder;
use crate::sm::sm_jobs::SmJob;
use crate::sm::sm_jobs::SmPending;
use crate::sm::sm_jobs::sm_common::CreateDbStateError;
use crate::sm::sm_wire::sm_wire_session::DeserializeSessionError;
use crate::sm::sm_wire::sm_wire_session::deserialize_session;
use crate::sm::sm_wire::sm_wire_toplevel::DeserializeToplevelError;
use crate::sm::sm_wire::sm_wire_toplevel::deserialize_toplevel;
use crate::sqlite::SqliteCtx;
use crate::sqlite::SqliteError;
use crate::sqlite::SqliteJob;
use crate::sqlite::SqliteWork;
use crate::sqlite::sqlite_api::SqliteStep;
use crate::utils::bhash::BHashMap;
use crate::utils::errorfmt::ErrorFmt;
use crate::utils::opaque::Opaque;
use crate::utils::stack::Stack;
use crate::utils::tri::Try;
use std::cell::Cell;
use std::error::Error;
use std::rc::Rc;
use std::rc::Weak;
use std::sync::Arc;
use std::time::Duration;
use std::time::SystemTime;
use thiserror::Error;
pub struct SessionListJob {
work: SessionListWork,
pending: Weak<SmPending<Self>>,
}
pub struct SessionListWork {
result: Option<Result<SessionList, SessionListError>>,
db_state: Arc<SmDbStateHolder>,
}
pub struct SessionListRequest {
pub cb: Cell<Option<Box<dyn FnOnce(Result<SessionList, Box<dyn Error>>)>>>,
}
#[derive(Debug, Error)]
enum SessionListError {
#[error(transparent)]
CreateDbState(#[from] CreateDbStateError),
#[error(transparent)]
Sqlite(#[from] SqliteError),
#[error("The session column has a wrong length")]
WrongSessionLength,
#[error("The name column has a wrong length")]
WrongToplevelLength,
#[error("The toplevel name is not UTF-8")]
ToplevelNameUtf8,
#[error("The access time overflowed")]
AtimeOverflow,
#[error("Toplevel references an unknown session")]
UnknownSession,
#[error(transparent)]
DeserializeSession(#[from] DeserializeSessionError),
#[error(transparent)]
DeserializeToplevel(#[from] DeserializeToplevelError),
}
impl SmJob for SessionListJob {
type Cb = SessionListRequest;
fn new(db_state: Arc<SmDbStateHolder>, pending: Weak<SmPending<Self>>) -> Self {
Self {
work: SessionListWork {
result: Default::default(),
db_state,
},
pending,
}
}
fn stack(sm: &SessionManager) -> &Stack<Rc<SmPending<Self>>> {
&sm.session_list_jobs
}
}
impl SqliteJob for SessionListJob {
fn work(&mut self) -> &mut dyn SqliteWork {
&mut self.work
}
fn completed(mut self: Box<Self>) {
let result = self.work.result.take().unwrap();
let Some(pending) = self.pending.upgrade() else {
return;
};
pending.job.set(Some(self));
let Some(cb) = pending.cb.take() else {
return;
};
let Some(cb) = cb.cb.take() else {
return;
};
cb(result.map_err(Box::new).map_err(Into::into));
}
}
impl SqliteWork for SessionListWork {
fn run(&mut self, ctx: &mut SqliteCtx) {
self.result = Some(self.try_run(ctx));
}
}
impl SessionListWork {
fn try_run(&mut self, ctx: &mut SqliteCtx) -> Result<SessionList, SessionListError> {
db_state!(s, self, ctx);
let tx = ctx.tx.begin_read()?;
let parse_ts = |ts: i64| {
let ts = if ts < 0 {
SystemTime::UNIX_EPOCH.checked_sub(Duration::from_secs((-ts) as u64))
} else {
SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(ts as u64))
};
ts.ok_or(SessionListError::AtimeOverflow)
};
let mut sessions = BHashMap::default();
let mut stmt = s.s.session_list.activate();
while stmt.step()? == SqliteStep::Row {
let res = SessionListError::tri(|| {
let session_id = stmt.get_i64(0)?;
let session = stmt.get_blob(1)?;
let data = stmt.get_blob(2)?;
let ctime = stmt.get_i64(3)?;
let atime = stmt.get_i64(4)?;
let Ok(session) = session.try_into() else {
return Err(SessionListError::WrongSessionLength);
};
let session = SessionName(Opaque::from_bytes(session));
let data = deserialize_session(data)?;
let ctime = parse_ts(ctime)?;
let atime = parse_ts(atime)?;
sessions.insert(
session_id,
SessionListSession {
name: session,
data,
ctime,
atime,
toplevels: Default::default(),
},
);
Ok(())
});
if let Err(e) = res {
log::error!("Could not deserialize session: {}", ErrorFmt(e));
}
}
let mut stmt = s.s.toplevel_list.activate();
while stmt.step()? == SqliteStep::Row {
let res = SessionListError::tri(|| {
let session_id = stmt.get_i64(0)?;
let name = stmt.get_blob(1)?;
let name_text = stmt.get_blob(2)?;
let data = stmt.get_blob(3)?;
let ctime = stmt.get_i64(4)?;
let atime = stmt.get_i64(5)?;
let Ok(name) = name.try_into() else {
return Err(SessionListError::WrongToplevelLength);
};
let Ok(name_text) = str::from_utf8(name_text) else {
return Err(SessionListError::ToplevelNameUtf8);
};
let name = ToplevelSessionName(name);
let data = deserialize_toplevel(data)?;
let ctime = parse_ts(ctime)?;
let atime = parse_ts(atime)?;
let data = SessionListToplevel {
name,
name_text: name_text.to_string(),
data,
ctime,
atime,
};
let Some(session) = sessions.get_mut(&session_id) else {
return Err(SessionListError::UnknownSession);
};
session.toplevels.push(data);
Ok(())
});
if let Err(e) = res {
log::error!("Could not deserialize toplevel: {}", ErrorFmt(e));
}
}
drop(tx);
let mut sessions: Vec<_> = sessions.into_values().collect();
sessions.sort_by_key(|s| s.name);
for s in &mut sessions {
s.toplevels.sort_by(|a, b| a.name_text.cmp(&b.name_text));
}
Ok(SessionList { sessions })
}
}