1use thiserror::Error;
4
5#[derive(Debug, Error)]
7pub enum Error {
8 #[error("USB error: {0}")]
10 Usb(#[from] nusb::Error),
11
12 #[error("Protocol error: {code:?} during {operation:?}")]
14 Protocol {
15 code: crate::ptp::ResponseCode,
17 operation: crate::ptp::OperationCode,
19 },
20
21 #[error("Invalid data: {message}")]
23 InvalidData {
24 message: String,
26 },
27
28 #[error("I/O error: {0}")]
30 Io(std::io::Error),
31
32 #[error("Operation timed out")]
34 Timeout,
35
36 #[error("Device disconnected")]
38 Disconnected,
39
40 #[error("Session not open")]
42 SessionNotOpen,
43
44 #[error("No MTP device found")]
46 NoDevice,
47
48 #[error("Operation cancelled")]
50 Cancelled,
51}
52
53impl Error {
54 #[must_use]
56 pub fn invalid_data(message: impl Into<String>) -> Self {
57 Error::InvalidData {
58 message: message.into(),
59 }
60 }
61
62 #[must_use]
68 pub fn is_retryable(&self) -> bool {
69 matches!(
70 self,
71 Error::Protocol {
72 code: crate::ptp::ResponseCode::DeviceBusy,
73 ..
74 } | Error::Timeout
75 )
76 }
77
78 #[must_use]
80 pub fn response_code(&self) -> Option<crate::ptp::ResponseCode> {
81 match self {
82 Error::Protocol { code, .. } => Some(*code),
83 _ => None,
84 }
85 }
86
87 #[must_use]
107 pub fn is_exclusive_access(&self) -> bool {
108 match self {
109 Error::Usb(io_err) => {
110 let msg = io_err.to_string().to_lowercase();
111 msg.contains("exclusive access")
115 || msg.contains("device or resource busy")
116 || (msg.contains("access") && msg.contains("denied"))
117 }
118 Error::Io(io_err) => {
119 let msg = io_err.to_string().to_lowercase();
120 msg.contains("exclusive access")
121 || msg.contains("device or resource busy")
122 || (msg.contains("access") && msg.contains("denied"))
123 }
124 _ => false,
125 }
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use std::io::{Error as IoError, ErrorKind};
133
134 #[test]
135 fn test_is_exclusive_access_macos_message() {
136 let io_err = IoError::other("could not be opened for exclusive access");
138 let err = Error::Usb(io_err);
139 assert!(err.is_exclusive_access());
140 }
141
142 #[test]
143 fn test_is_exclusive_access_linux_busy() {
144 let io_err = IoError::other("Device or resource busy");
146 let err = Error::Usb(io_err);
147 assert!(err.is_exclusive_access());
148 }
149
150 #[test]
151 fn test_is_exclusive_access_windows_denied() {
152 let io_err = IoError::new(ErrorKind::PermissionDenied, "Access is denied");
154 let err = Error::Usb(io_err);
155 assert!(err.is_exclusive_access());
156 }
157
158 #[test]
159 fn test_is_exclusive_access_io_error() {
160 let io_err = IoError::other("could not be opened for exclusive access");
162 let err = Error::Io(io_err);
163 assert!(err.is_exclusive_access());
164 }
165
166 #[test]
167 fn test_is_exclusive_access_false_for_other_errors() {
168 assert!(!Error::Timeout.is_exclusive_access());
169 assert!(!Error::Disconnected.is_exclusive_access());
170 assert!(!Error::NoDevice.is_exclusive_access());
171 assert!(!Error::invalid_data("some error").is_exclusive_access());
172
173 let io_err = IoError::new(ErrorKind::NotFound, "device not found");
174 assert!(!Error::Usb(io_err).is_exclusive_access());
175 }
176}