use crate::mtp::ObjectHandle;
use crate::ptp::ResponseCode;
use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
#[error("not found")]
NotFound,
#[error("stale object handle (device re-keyed it; re-list and retry)")]
StaleHandle,
#[error("access denied")]
AccessDenied,
#[error("device is held exclusively by another process")]
ExclusiveAccess,
#[error("permission denied accessing the device")]
PermissionDenied,
#[error("operation not supported by this device")]
Unsupported,
#[error("device busy")]
Busy,
#[error("storage full")]
StorageFull,
#[error("operation cancelled")]
Cancelled,
#[error("device disconnected")]
Disconnected,
#[error("operation timed out")]
Timeout,
#[error("no device found")]
NoDevice,
#[error("invalid data: {message}")]
InvalidData {
message: String,
},
#[error("I/O error: {message}")]
Io {
message: String,
},
#[error("device error: {detail}")]
Other {
detail: String,
},
}
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::Busy | Error::Timeout)
}
#[must_use]
pub fn is_exclusive_access(&self) -> bool {
matches!(self, Error::ExclusiveAccess)
}
#[must_use]
pub fn is_permission_denied(&self) -> bool {
matches!(self, Error::PermissionDenied)
}
#[must_use]
pub fn is_stale_handle(&self) -> bool {
matches!(self, Error::StaleHandle)
}
}
impl From<crate::error::PtpError> for Error {
fn from(e: crate::error::PtpError) -> Self {
use crate::error::PtpError as Low;
use nusb::ErrorKind as Usb;
match e {
Low::Protocol { code, .. } => map_response_code(code),
Low::Usb(usb) => match usb.kind() {
Usb::Busy => Error::ExclusiveAccess,
Usb::PermissionDenied => Error::PermissionDenied,
Usb::Disconnected | Usb::NotFound => Error::Disconnected,
Usb::Unsupported => Error::Unsupported,
_ => Error::Io {
message: usb.to_string(),
},
},
Low::Io(io) => match io.kind() {
std::io::ErrorKind::PermissionDenied => Error::PermissionDenied,
_ => Error::Io {
message: io.to_string(),
},
},
Low::InvalidData { message } => Error::InvalidData { message },
Low::Timeout => Error::Timeout,
Low::Disconnected => Error::Disconnected,
Low::SessionNotOpen => Error::Disconnected,
Low::NoDevice => Error::NoDevice,
Low::Cancelled => Error::Cancelled,
}
}
}
fn map_response_code(code: ResponseCode) -> Error {
match code {
ResponseCode::InvalidObjectHandle | ResponseCode::InvalidParentObject => Error::StaleHandle,
ResponseCode::InvalidStorageId => Error::NotFound,
ResponseCode::StoreReadOnly
| ResponseCode::ObjectWriteProtected
| ResponseCode::AccessDenied => Error::AccessDenied,
ResponseCode::StoreFull | ResponseCode::ObjectTooLarge => Error::StorageFull,
ResponseCode::DeviceBusy => Error::Busy,
ResponseCode::OperationNotSupported | ResponseCode::ParameterNotSupported => {
Error::Unsupported
}
ResponseCode::TransactionCancelled => Error::Cancelled,
ResponseCode::SessionNotOpen => Error::Disconnected,
other => Error::Other {
detail: format!("{other:?}"),
},
}
}
#[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 From<crate::error::PtpUploadError> for UploadError {
fn from(e: crate::error::PtpUploadError) -> Self {
UploadError {
source: e.source.into(),
partial: e.partial.map(Into::into),
}
}
}