use crate::ptp::ObjectHandle;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("USB error: {0}")]
Usb(#[from] nusb::Error),
#[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,
}
#[derive(Debug, Error)]
#[error("{source}")]
pub struct UploadError {
#[source]
pub source: Error,
pub partial: Option<ObjectHandle>,
}
impl From<UploadError> for Error {
fn from(e: UploadError) -> Self {
e.source
}
}
impl Error {
#[must_use]
pub fn invalid_data(message: impl Into<String>) -> Self {
Error::InvalidData {
message: message.into(),
}
}
#[must_use]
pub fn is_retryable(&self) -> bool {
matches!(
self,
Error::Protocol {
code: crate::ptp::ResponseCode::DeviceBusy,
..
} | Error::Timeout
)
}
#[must_use]
pub fn response_code(&self) -> Option<crate::ptp::ResponseCode> {
match self {
Error::Protocol { code, .. } => Some(*code),
_ => None,
}
}
#[must_use]
pub fn is_exclusive_access(&self) -> bool {
match self {
Error::Usb(io_err) => {
let msg = io_err.to_string().to_lowercase();
msg.contains("exclusive access")
|| msg.contains("device or resource busy")
|| (msg.contains("access") && msg.contains("denied"))
}
Error::Io(io_err) => {
let msg = io_err.to_string().to_lowercase();
msg.contains("exclusive access")
|| msg.contains("device or resource busy")
|| (msg.contains("access") && msg.contains("denied"))
}
_ => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Error as IoError, ErrorKind};
#[test]
fn test_is_exclusive_access_macos_message() {
let io_err = IoError::other("could not be opened for exclusive access");
let err = Error::Io(io_err);
assert!(err.is_exclusive_access());
}
#[test]
fn test_is_exclusive_access_linux_busy() {
let io_err = IoError::other("Device or resource busy");
let err = Error::Io(io_err);
assert!(err.is_exclusive_access());
}
#[test]
fn test_is_exclusive_access_windows_denied() {
let io_err = IoError::new(ErrorKind::PermissionDenied, "Access is denied");
let err = Error::Io(io_err);
assert!(err.is_exclusive_access());
}
#[test]
fn test_is_exclusive_access_io_error() {
let io_err = IoError::other("could not be opened for exclusive access");
let err = Error::Io(io_err);
assert!(err.is_exclusive_access());
}
#[test]
fn test_is_exclusive_access_false_for_other_errors() {
assert!(!Error::Timeout.is_exclusive_access());
assert!(!Error::Disconnected.is_exclusive_access());
assert!(!Error::NoDevice.is_exclusive_access());
assert!(!Error::invalid_data("some error").is_exclusive_access());
let io_err = IoError::new(ErrorKind::NotFound, "device not found");
assert!(!Error::Io(io_err).is_exclusive_access());
}
}