use std::collections::VecDeque;
use std::os::fd::RawFd;
use std::path::{Path, PathBuf};
use crate::SessionError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(
clippy::exhaustive_structs,
reason = "a value defined by its fields; there is nothing to add to it"
)]
pub struct DeviceId(pub i32);
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct TakenDevice {
pub path: PathBuf,
pub id: DeviceId,
pub fd: RawFd,
}
pub trait Backend {
fn open_device(&mut self, path: &Path) -> Result<(DeviceId, RawFd), SessionError>;
fn close_device(&mut self, id: DeviceId) -> Result<(), SessionError>;
fn acknowledge_pause(&mut self) -> Result<(), SessionError>;
fn switch_session(&mut self, session: i32) -> Result<(), SessionError>;
fn poll_fd(&self) -> Option<RawFd>;
fn dispatch(&mut self, timeout_ms: i32, events: &mut Vec<RawEvent>)
-> Result<(), SessionError>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum RawEvent {
Enable,
Disable,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum SessionEvent {
Paused,
Resumed {
devices: Vec<TakenDevice>,
},
DeviceLost {
path: PathBuf,
error: String,
},
}
#[derive(Debug)]
pub struct Session<B: Backend> {
backend: B,
devices: Vec<TakenDevice>,
active: bool,
pending: VecDeque<SessionEvent>,
raw: Vec<RawEvent>,
}
impl<B: Backend> Session<B> {
pub fn new(backend: B) -> Self {
Self {
backend,
devices: Vec::new(),
active: true,
pending: VecDeque::new(),
raw: Vec::new(),
}
}
#[must_use]
pub const fn is_active(&self) -> bool {
self.active
}
#[must_use]
pub fn devices(&self) -> &[TakenDevice] {
&self.devices
}
#[cfg(test)]
pub(crate) const fn backend(&self) -> &B {
&self.backend
}
#[cfg(test)]
pub(crate) const fn backend_mut(&mut self) -> &mut B {
&mut self.backend
}
#[must_use]
pub fn poll_fd(&self) -> Option<RawFd> {
self.backend.poll_fd()
}
pub fn take_device(&mut self, path: impl AsRef<Path>) -> Result<TakenDevice, SessionError> {
let path = path.as_ref();
if !self.active {
return Err(SessionError::Inactive);
}
if let Some(existing) = self.devices.iter().find(|device| device.path == path) {
return Ok(existing.clone());
}
let (id, fd) = self.backend.open_device(path)?;
let device = TakenDevice {
path: path.to_path_buf(),
id,
fd,
};
self.devices.push(device.clone());
Ok(device)
}
pub fn release_device(&mut self, path: impl AsRef<Path>) -> Result<(), SessionError> {
let path = path.as_ref();
let index = self
.devices
.iter()
.position(|device| device.path == path)
.ok_or(SessionError::UnknownDevice)?;
let device = self.devices.remove(index);
self.backend.close_device(device.id)
}
pub fn switch_session(&mut self, session: i32) -> Result<(), SessionError> {
self.backend.switch_session(session)
}
pub fn dispatch(&mut self, timeout_ms: i32) -> Result<(), SessionError> {
self.raw.clear();
let mut raw = std::mem::take(&mut self.raw);
let result = self.backend.dispatch(timeout_ms, &mut raw);
for event in raw.drain(..) {
match event {
RawEvent::Disable => self.on_disable()?,
RawEvent::Enable => self.on_enable(),
}
}
self.raw = raw;
result
}
pub fn next_event(&mut self) -> Option<SessionEvent> {
self.pending.pop_front()
}
fn on_disable(&mut self) -> Result<(), SessionError> {
if !self.active {
return Ok(());
}
self.active = false;
drmkit_log::log_info!(
"session paused: {} device(s) are no longer ours",
self.devices.len()
);
self.pending.push_back(SessionEvent::Paused);
self.backend.acknowledge_pause()
}
fn on_enable(&mut self) {
if self.active {
return;
}
self.active = true;
let mut refreshed = Vec::with_capacity(self.devices.len());
for device in std::mem::take(&mut self.devices) {
if let Err(error) = self.backend.close_device(device.id) {
drmkit_log::log_debug!(
"closing {} before reopening it failed: {error}",
device.path.display()
);
}
match self.backend.open_device(&device.path) {
Ok((id, fd)) => refreshed.push(TakenDevice {
path: device.path,
id,
fd,
}),
Err(error) => {
drmkit_log::log_error!(
"reopening {} on resume failed: {error}",
device.path.display()
);
self.pending.push_back(SessionEvent::DeviceLost {
path: device.path,
error: error.to_string(),
});
}
}
}
drmkit_log::log_info!("session resumed: {} device(s) reopened", refreshed.len());
self.devices.clone_from(&refreshed);
self.pending
.push_back(SessionEvent::Resumed { devices: refreshed });
}
}