use thiserror::Error;
#[derive(Debug, Error)]
#[error(transparent)]
pub struct UsbError(nusb::Error);
impl UsbError {
pub(crate) fn kind(&self) -> nusb::ErrorKind {
self.0.kind()
}
}
fn usb_kind_means_exclusive_access(kind: nusb::ErrorKind) -> bool {
kind == nusb::ErrorKind::Busy
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum PtpError {
#[error("USB error: {0}")]
Usb(UsbError),
#[error("Protocol error: {code:?} during {operation:?}")]
Protocol {
code: crate::ptp::ResponseCode,
operation: crate::ptp::OperationCode,
},
#[error("Invalid data: {message}")]
InvalidData {
message: String,
},
#[error("I/O error: {0}")]
Io(std::io::Error),
#[error("Operation timed out")]
Timeout,
#[error("Device disconnected")]
Disconnected,
#[error("Session not open")]
SessionNotOpen,
#[error("No MTP device found")]
NoDevice,
#[error("Operation cancelled")]
Cancelled,
#[error("string is {units} UTF-16 code units long; PTP strings hold at most 254")]
StringTooLong {
units: usize,
},
#[error("device was reset to recover from a wedged cancel; reopen to continue")]
DeviceReset,
}
impl PtpError {
pub(crate) fn from_nusb(e: nusb::Error) -> Self {
PtpError::Usb(UsbError(e))
}
#[must_use]
pub fn invalid_data(message: impl Into<String>) -> Self {
PtpError::InvalidData {
message: message.into(),
}
}
#[must_use]
pub fn is_retryable(&self) -> bool {
matches!(
self,
PtpError::Protocol {
code: crate::ptp::ResponseCode::DeviceBusy,
..
} | PtpError::Timeout
)
}
#[must_use]
pub fn response_code(&self) -> Option<crate::ptp::ResponseCode> {
match self {
PtpError::Protocol { code, .. } => Some(*code),
_ => None,
}
}
#[must_use]
pub fn is_exclusive_access(&self) -> bool {
matches!(self, PtpError::Usb(usb) if usb_kind_means_exclusive_access(usb.kind()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Error as IoError, ErrorKind};
#[test]
fn only_a_busy_usb_kind_means_exclusive_access() {
assert!(usb_kind_means_exclusive_access(nusb::ErrorKind::Busy));
for kind in [
nusb::ErrorKind::PermissionDenied,
nusb::ErrorKind::Disconnected,
nusb::ErrorKind::NotFound,
nusb::ErrorKind::Unsupported,
nusb::ErrorKind::Other,
] {
assert!(!usb_kind_means_exclusive_access(kind), "{kind:?}");
}
}
#[test]
fn io_errors_agree_with_the_neutral_error() {
for message in [
"could not be opened for exclusive access",
"Device or resource busy",
] {
let low = PtpError::Io(IoError::other(message));
assert!(!low.is_exclusive_access(), "{message}");
assert!(!crate::Error::from(low).is_exclusive_access(), "{message}");
}
let denied = PtpError::Io(IoError::new(
ErrorKind::PermissionDenied,
"Access is denied",
));
assert!(!denied.is_exclusive_access());
assert!(crate::Error::from(denied).is_permission_denied());
}
#[test]
fn test_is_exclusive_access_false_for_other_errors() {
assert!(!PtpError::Timeout.is_exclusive_access());
assert!(!PtpError::Disconnected.is_exclusive_access());
assert!(!PtpError::NoDevice.is_exclusive_access());
assert!(!PtpError::invalid_data("some error").is_exclusive_access());
let io_err = IoError::new(ErrorKind::NotFound, "device not found");
assert!(!PtpError::Io(io_err).is_exclusive_access());
}
}