use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use async_trait::async_trait;
use serde::Serialize;
use zbus::{
object_server::SignalEmitter,
zvariant::{OwnedObjectPath, Type, as_value},
};
use crate::{PortalError, desktop::HandleToken};
pub(crate) struct Session {
path: OwnedObjectPath,
manager: Arc<Mutex<SessionManager>>,
monitor: Option<Arc<dyn SessionImpl>>,
}
impl Session {
pub(crate) fn new(
path: OwnedObjectPath,
manager: Arc<Mutex<SessionManager>>,
monitor: Option<Arc<dyn SessionImpl>>,
) -> Self {
Self {
path,
manager,
monitor,
}
}
pub fn token(&self) -> HandleToken {
HandleToken::try_from(&self.path).unwrap()
}
pub async fn serve(&self, cnx: zbus::Connection) -> zbus::Result<bool> {
let interface = SessionInterface::new(
self.path.clone(),
Arc::clone(&self.manager),
self.monitor.clone(),
);
cnx.object_server().at(&self.path, interface).await
}
}
impl PartialEq for Session {
fn eq(&self, other: &Self) -> bool {
self.path == other.path
}
}
impl std::fmt::Debug for Session {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Session").field("path", &self.path).finish()
}
}
struct SessionInterface {
path: OwnedObjectPath,
manager: Arc<Mutex<SessionManager>>,
monitor: Option<Arc<dyn SessionImpl>>,
}
impl SessionInterface {
fn new(
path: OwnedObjectPath,
manager: Arc<Mutex<SessionManager>>,
monitor: Option<Arc<dyn SessionImpl>>,
) -> Self {
Self {
path,
manager,
monitor,
}
}
}
#[zbus::interface(name = "org.freedesktop.impl.portal.Session")]
impl SessionInterface {
#[zbus(property(emits_changed_signal = "const"), name = "version")]
fn version(&self) -> u32 {
1
}
#[doc(alias = "Close")]
async fn close(
&self,
#[zbus(object_server)] server: &zbus::ObjectServer,
) -> zbus::fdo::Result<()> {
#[cfg(feature = "tracing")]
tracing::debug!("SessionInterface::Close {}", self.path.as_str());
let token = HandleToken::try_from(&self.path).unwrap();
{
let mut manager = self.manager.lock().unwrap();
let _ = manager.remove(&token);
}
if let Some(monitor) = &self.monitor {
let _ = monitor.session_closed(token).await;
}
server.remove::<Self, _>(&self.path).await?;
Ok(())
}
#[zbus(signal)]
async fn closed(signal_emitter: &SignalEmitter<'_>) -> zbus::Result<()>;
}
#[async_trait]
pub trait SessionImpl: Send + Sync {
#[doc(alias = "Closed")]
async fn session_closed(&self, session_token: HandleToken) -> crate::backend::Result<()>;
}
#[derive(Serialize, Type, Debug)]
#[zvariant(signature = "dict")]
pub struct CreateSessionResponse {
#[serde(with = "as_value")]
session_id: HandleToken,
}
impl CreateSessionResponse {
pub fn new(token: HandleToken) -> Self {
Self { session_id: token }
}
}
#[derive(Default)]
pub(crate) struct SessionManager {
sessions: HashMap<HandleToken, Session>,
}
impl SessionManager {
pub fn try_contains(&self, token: &HandleToken) -> crate::backend::Result<&Session> {
self.sessions
.get(token)
.ok_or(PortalError::NotFound(format!("Unknown session: `{token}`")))
}
pub fn contains(&self, token: &HandleToken) -> bool {
#[cfg(feature = "tracing")]
tracing::debug!("SessionManager::contains: tracked sessions: {:?}", &self);
self.try_contains(token).is_ok()
}
pub fn add(&mut self, session: Session) {
let token = session.token();
let _ = self.sessions.insert(token.clone(), session);
}
pub fn remove(&mut self, token: &HandleToken) -> crate::backend::Result<()> {
if self.sessions.remove(token).is_some() {
Ok(())
} else {
let message = format!("Unknown session: `{token}`");
#[cfg(feature = "tracing")]
tracing::error!("{}", message.as_str());
Err(PortalError::NotFound(message))
}
}
}
impl std::fmt::Debug for SessionManager {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_list().entries(self.sessions.keys()).finish()
}
}