libmtp_rs/
object.rs

1//! Everything on the Media Transfer Protocol is an *object*, this module groups common behavior
2//! and items of many higher abstractions like files, tracks, albums, etc.
3//!
4//! Note that most operations on attributes should be managed with other APIs exposed in this
5//! crate, the most useful utilities here serve to delete, move and copy objects (`Object` trait).
6
7pub mod filetypes;
8pub mod properties;
9
10use std::ffi::CString;
11
12use crate::device::MtpDevice;
13use crate::storage::Parent;
14use crate::Result;
15
16use libmtp_sys as ffi;
17use num_traits::ToPrimitive;
18use properties::Property;
19
20/// Trait to allow the usage of certain structures or plain `u32` in places where an object id is
21/// required. By default every `Object` implementor automagically implements this trait.
22///
23/// Beware that although some functions accept any `AsObjectId` implementor, this isn't going to be
24/// always correct, because some operations are made to work only on certain types of objects (like
25/// files, tracks, folders, etc). Also note that using plain `u32` is dangerous, unless you know
26/// what you are doing.
27pub trait AsObjectId {
28    /// Treat the implementor as an object id.
29    fn as_id(&self) -> u32;
30}
31
32/// All [`Object`](trait.Object.html) implementors can be treated as an object id given that they already
33/// have the [`Object::id`](trait.Object.html#tymethod.id) method.
34impl<T> AsObjectId for T
35where
36    T: Object,
37{
38    fn as_id(&self) -> u32 {
39        self.id()
40    }
41}
42
43/// Note that this is just a convenience implementaion in case you have *known valid* object id as
44/// `u32` somewhere else, or you just want to use the [`Object::id`](trait.Object.html#tymethod.id)
45/// method to pass the plain `u32`.
46impl AsObjectId for u32 {
47    fn as_id(&self) -> u32 {
48        *self
49    }
50}
51
52/// Wrapper structure that holds an object id and a reference to an `MtpDevice`, useful if you want
53/// to work with Object methods and only have an id. (see `MtpDevice::dummy_object`).
54pub struct DummyObject<'a> {
55    pub(crate) id: u32,
56    pub(crate) mtpdev: &'a MtpDevice,
57}
58
59impl Object for DummyObject<'_> {
60    fn id(&self) -> u32 {
61        self.id
62    }
63
64    fn device(&self) -> &MtpDevice {
65        self.mtpdev
66    }
67}
68
69/// Common behavior of many higher abstractions is grouped in this trait, basically everything on
70/// MTP is an object with some attributes, even though this API is exposed, it's not recommended to
71/// use it to modify or get attributes that can be managed with other specefic APIs (like files,
72/// folders, tracks, etc).
73pub trait Object {
74    /// Must return the id of the object.
75    fn id(&self) -> u32;
76
77    /// Must return a valid reference of an `MtpDevice`, where this object resides in.
78    fn device(&self) -> &MtpDevice;
79
80    /// Retrieves a string from an object attribute.
81    fn get_string(&self, property: Property) -> Result<String> {
82        let property = property.to_u32().unwrap();
83        let id = self.id();
84        let device = self.device();
85
86        let string = unsafe { ffi::LIBMTP_Get_String_From_Object(device.inner, id, property) };
87
88        if string.is_null() {
89            Err(device.latest_error().unwrap_or_default())
90        } else {
91            unsafe {
92                let u8vec = cstr_to_u8vec!(string);
93                libc::free(string as *mut _);
94                Ok(String::from_utf8(u8vec)?)
95            }
96        }
97    }
98
99    /// Sets an object attribute from a string.
100    fn set_string(&self, property: Property, string: &str) -> Result<()> {
101        let property = property.to_u32().unwrap();
102        let id = self.id();
103        let device = self.device();
104        let string = CString::new(string).expect("Nul byte");
105
106        let res =
107            unsafe { ffi::LIBMTP_Set_Object_String(device.inner, id, property, string.as_ptr()) };
108
109        if res != 0 {
110            Err(device.latest_error().unwrap_or_default())
111        } else {
112            Ok(())
113        }
114    }
115
116    /// Retrieves an `u64` from an object attribute.
117    fn get_u64(&self, property: Property) -> Result<u64> {
118        let property = property.to_u32().unwrap();
119        let id = self.id();
120        let device = self.device();
121
122        let val = unsafe { ffi::LIBMTP_Get_u64_From_Object(device.inner, id, property, 0) };
123
124        if let Some(err) = device.latest_error() {
125            Err(err)
126        } else {
127            Ok(val)
128        }
129    }
130
131    /// Retrieves an `u32` from an object attribute, returns the value of `default` on failure.
132    fn get_u32(&self, property: Property) -> Result<u32> {
133        let property = property.to_u32().unwrap();
134        let id = self.id();
135        let device = self.device();
136
137        let val = unsafe { ffi::LIBMTP_Get_u32_From_Object(device.inner, id, property, 0) };
138
139        if let Some(err) = device.latest_error() {
140            Err(err)
141        } else {
142            Ok(val)
143        }
144    }
145
146    /// Sets an object attribute from an `u32`.
147    fn set_u32(&self, property: Property, value: u32) -> Result<()> {
148        let property = property.to_u32().unwrap();
149        let id = self.id();
150        let device = self.device();
151
152        let res = unsafe { ffi::LIBMTP_Set_Object_u32(device.inner, id, property, value) };
153
154        if res != 0 {
155            Err(device.latest_error().unwrap_or_default())
156        } else {
157            Ok(())
158        }
159    }
160
161    /// Retrieves an `u16` from an object attribute, returns the value of `default` on failure.
162    fn get_u16(&self, property: Property) -> Result<u16> {
163        let property = property.to_u32().unwrap();
164        let id = self.id();
165        let device = self.device();
166
167        let val = unsafe { ffi::LIBMTP_Get_u16_From_Object(device.inner, id, property, 0) };
168
169        if let Some(err) = device.latest_error() {
170            Err(err)
171        } else {
172            Ok(val)
173        }
174    }
175
176    /// Sets an object attribute from an `u16`.
177    fn set_u16(&self, property: Property, value: u16) -> Result<()> {
178        let property = property.to_u32().unwrap();
179        let id = self.id();
180        let device = self.device();
181
182        let res = unsafe { ffi::LIBMTP_Set_Object_u16(device.inner, id, property, value) };
183
184        if res != 0 {
185            Err(device.latest_error().unwrap_or_default())
186        } else {
187            Ok(())
188        }
189    }
190
191    /// Retrieves an `u8` from an object attribute, returns the value of `default` on failure.
192    fn get_u8(&self, property: Property) -> Result<u8> {
193        let property = property.to_u32().unwrap();
194        let id = self.id();
195        let device = self.device();
196
197        let val = unsafe { ffi::LIBMTP_Get_u8_From_Object(device.inner, id, property, 0) };
198
199        if let Some(err) = device.latest_error() {
200            Err(err)
201        } else {
202            Ok(val)
203        }
204    }
205
206    /// Sets an object attribute from an `u8`.
207    fn set_u8(&self, property: Property, value: u8) -> Result<()> {
208        let property = property.to_u32().unwrap();
209        let id = self.id();
210        let device = self.device();
211
212        let res = unsafe { ffi::LIBMTP_Set_Object_u8(device.inner, id, property, value) };
213
214        if res != 0 {
215            Err(device.latest_error().unwrap_or_default())
216        } else {
217            Ok(())
218        }
219    }
220
221    /// Deletes a *single* file, track, playlist, folder or any other object off the MTP device.
222    /// Note that deleting folders may no be remove its contents, in turn this is the expected
223    /// behavior.
224    ///
225    /// If you want to delete a folder first recursively delete all files and folders contained in
226    /// this folder, then the folder itself. Finally, if the operation is sucessful you should
227    /// discard the object given that now it holds an **invalid id**.
228    fn delete(&self) -> Result<()> {
229        let id = self.id();
230        let device = self.device();
231
232        let res = unsafe { ffi::LIBMTP_Delete_Object(device.inner, id) };
233
234        if res != 0 {
235            Err(device.latest_error().unwrap_or_default())
236        } else {
237            Ok(())
238        }
239    }
240
241    /// Moves the object to the specified storage (by its id) and parent folder. Moving objects
242    /// may or not be supported on the device.
243    ///
244    /// Note that moving an object may take a significant amount of time, particularly if being
245    /// moved between storages, MTP doesn't provide any kind of progress mechanism, so the operation
246    /// will simply block for the duration.
247    fn move_to(&self, storage_id: u32, parent: Parent) -> Result<()> {
248        let id = self.id();
249        let device = self.device();
250        let parent = parent.to_id();
251
252        let res = unsafe { ffi::LIBMTP_Move_Object(device.inner, id, storage_id, parent) };
253
254        if res != 0 {
255            Err(device.latest_error().unwrap_or_default())
256        } else {
257            Ok(())
258        }
259    }
260
261    /// Copies the object to the specified storage (by its id) and parent folder. Copying objects
262    /// may or not be supported on the device.
263    ///
264    /// Note that copying an object may take a significant amount of time, particularly if being
265    /// copied between storages, MTP doesn't provide any kind of progress mechanism, so the
266    /// operation will simply block for the duration.
267    fn copy_to(&self, storage_id: u32, parent: Parent) -> Result<()> {
268        let id = self.id();
269        let device = self.device();
270        let parent = parent.to_id();
271
272        let res = unsafe { ffi::LIBMTP_Copy_Object(device.inner, id, storage_id, parent) };
273
274        if res != 0 {
275            Err(device.latest_error().unwrap_or_default())
276        } else {
277            Ok(())
278        }
279    }
280
281    /// Get partial data from an object, specifying an offset and the maximum bytes
282    /// that should be read. Note that this may return fewer bytes than the maximum.
283    fn get_partial_object(&self, offset: u64, maxbytes: u32) -> Result<Vec<u8>> {
284        let id = self.id();
285        let device = self.device();
286
287        let mut size = 0;
288        let mut data = std::ptr::null_mut();
289
290        let res = unsafe {
291            ffi::LIBMTP_GetPartialObject(device.inner, id, offset, maxbytes, &mut data, &mut size)
292        };
293
294        if res != 0 || data.is_null() {
295            if !data.is_null() {
296                unsafe {
297                    libc::free(data as *mut _);
298                }
299            }
300
301            Err(device.latest_error().unwrap_or_default())
302        } else {
303            let bytes = unsafe { prim_array_ptr_to_vec!(data, u8, size) };
304            unsafe {
305                libc::free(data as *mut _);
306            }
307
308            Ok(bytes)
309        }
310    }
311
312    /// Send partial data to an object, specifying an offset and the data you want
313    /// to write into the object.
314    fn send_partial_object(&self, offset: u64, data: impl AsRef<[u8]>) -> Result<()> {
315        let id = self.id();
316        let device = self.device();
317
318        let data = data.as_ref();
319        let size = data.len();
320
321        let res = unsafe {
322            ffi::LIBMTP_SendPartialObject(
323                device.inner,
324                id,
325                offset,
326                data.as_ptr() as *mut _,
327                size as u32,
328            )
329        };
330
331        if res != 0 {
332            Err(device.latest_error().unwrap_or_default())
333        } else {
334            Ok(())
335        }
336    }
337}