use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use mtp_rs::mtp::MtpDevice;
pub trait DeviceOpener: Send + Sync {
fn open(&self, rt: &tokio::runtime::Handle) -> Result<MtpDevice, mtp_rs::Error>;
fn describe(&self) -> String;
}
pub struct UsbOpener {
serial: Option<String>,
unplug: UnplugSwitch,
}
impl UsbOpener {
pub fn new(serial: Option<String>, unplug: UnplugSwitch) -> Self {
Self { serial, unplug }
}
}
impl DeviceOpener for UsbOpener {
fn open(&self, rt: &tokio::runtime::Handle) -> Result<MtpDevice, mtp_rs::Error> {
if self.unplug.is_unplugged() {
return Err(mtp_rs::Error::Disconnected);
}
match &self.serial {
Some(serial) => rt.block_on(MtpDevice::open_by_serial(serial)),
None => rt.block_on(MtpDevice::open_first()),
}
}
fn describe(&self) -> String {
match &self.serial {
Some(serial) => format!("device {serial}"),
None => "the device".to_string(),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct UnplugSwitch(Arc<AtomicBool>);
impl UnplugSwitch {
#[allow(dead_code)] pub fn unplug(&self) {
self.0.store(true, Ordering::SeqCst);
}
#[allow(dead_code)] pub fn replug(&self) {
self.0.store(false, Ordering::SeqCst);
}
pub fn is_unplugged(&self) -> bool {
self.0.load(Ordering::Relaxed)
}
}
pub fn is_link_lost(error: &mtp_rs::Error) -> bool {
matches!(
error,
mtp_rs::Error::Disconnected | mtp_rs::Error::DeviceReset | mtp_rs::Error::NoDevice
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn switch_starts_plugged_in() {
let switch = UnplugSwitch::default();
assert!(!switch.is_unplugged());
}
#[test]
fn switch_is_shared_between_clones() {
let switch = UnplugSwitch::default();
let remote = switch.clone();
remote.unplug();
assert!(switch.is_unplugged());
remote.replug();
assert!(!switch.is_unplugged());
}
#[test]
fn session_loss_triggers_reconnect_but_plain_failures_do_not() {
assert!(is_link_lost(&mtp_rs::Error::Disconnected));
assert!(is_link_lost(&mtp_rs::Error::DeviceReset));
assert!(!is_link_lost(&mtp_rs::Error::NotFound));
assert!(!is_link_lost(&mtp_rs::Error::AccessDenied));
assert!(!is_link_lost(&mtp_rs::Error::Timeout));
}
#[test]
fn link_loss_is_broader_than_mtp_rs_is_disconnected() {
assert!(mtp_rs::Error::Disconnected.is_disconnected());
assert!(!mtp_rs::Error::DeviceReset.is_disconnected());
assert!(!mtp_rs::Error::NoDevice.is_disconnected());
assert!(is_link_lost(&mtp_rs::Error::DeviceReset));
assert!(is_link_lost(&mtp_rs::Error::NoDevice));
}
}