1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
//! Everything on the Media Transfer Protocol is an *object*, this module groups common behavior
//! and items of many higher abstractions like files, tracks, albums, etc.
//!
//! Note that most operations on attributes should be managed with other APIs exposed in this
//! crate, the most useful utilities here serve to delete, move and copy objects (`Object` trait).

pub mod filetypes;
pub mod properties;

use std::ffi::CString;

use crate::device::MtpDevice;
use crate::storage::Parent;
use crate::Result;

use libmtp_sys as ffi;
use num_traits::ToPrimitive;
use properties::Property;

/// Trait to allow the usage of certain structures or plain `u32` in places where an object id is
/// required. By default every `Object` implementor automagically implements this trait.
///
/// Beware that although some functions accept any `AsObjectId` implementor, this isn't going to be
/// always correct, because some operations are made to work only on certain types of objects (like
/// files, tracks, folders, etc). Also note that using plain `u32` is dangerous, unless you know
/// what you are doing.
pub trait AsObjectId {
    /// Treat the implementor as an object id.
    fn as_id(&self) -> u32;
}

/// All [`Object`](trait.Object.html) implementors can be treated as an object id given that they already
/// have the [`Object::id`](trait.Object.html#tymethod.id) method.
impl<T> AsObjectId for T
where
    T: Object,
{
    fn as_id(&self) -> u32 {
        self.id()
    }
}

/// Note that this is just a convenience implementaion in case you have *known valid* object id as
/// `u32` somewhere else, or you just want to use the [`Object::id`](trait.Object.html#tymethod.id)
/// method to pass the plain `u32`.
impl AsObjectId for u32 {
    fn as_id(&self) -> u32 {
        *self
    }
}

/// Wrapper structure that holds an object id and a reference to an `MtpDevice`, useful if you want
/// to work with Object methods and only have an id. (see `MtpDevice::dummy_object`).
pub struct DummyObject<'a> {
    pub(crate) id: u32,
    pub(crate) mtpdev: &'a MtpDevice,
}

impl Object for DummyObject<'_> {
    fn id(&self) -> u32 {
        self.id
    }

    fn device(&self) -> &MtpDevice {
        self.mtpdev
    }
}

/// Common behavior of many higher abstractions is grouped in this trait, basically everything on
/// MTP is an object with some attributes, even though this API is exposed, it's not recommended to
/// use it to modify or get attributes that can be managed with other specefic APIs (like files,
/// folders, tracks, etc).
pub trait Object {
    /// Must return the id of the object.
    fn id(&self) -> u32;

    /// Must return a valid reference of an `MtpDevice`, where this object resides in.
    fn device(&self) -> &MtpDevice;

    /// Retrieves a string from an object attribute.
    fn get_string(&self, property: Property) -> Result<String> {
        let property = property.to_u32().unwrap();
        let id = self.id();
        let device = self.device();

        let string = unsafe { ffi::LIBMTP_Get_String_From_Object(device.inner, id, property) };

        if string.is_null() {
            Err(device.latest_error().unwrap_or_default())
        } else {
            unsafe {
                let u8vec = cstr_to_u8vec!(string);
                libc::free(string as *mut _);
                Ok(String::from_utf8(u8vec)?)
            }
        }
    }

    /// Sets an object attribute from a string.
    fn set_string(&self, property: Property, string: &str) -> Result<()> {
        let property = property.to_u32().unwrap();
        let id = self.id();
        let device = self.device();
        let string = CString::new(string).expect("Nul byte");

        let res =
            unsafe { ffi::LIBMTP_Set_Object_String(device.inner, id, property, string.as_ptr()) };

        if res != 0 {
            Err(device.latest_error().unwrap_or_default())
        } else {
            Ok(())
        }
    }

    /// Retrieves an `u64` from an object attribute.
    fn get_u64(&self, property: Property) -> Result<u64> {
        let property = property.to_u32().unwrap();
        let id = self.id();
        let device = self.device();

        let val = unsafe { ffi::LIBMTP_Get_u64_From_Object(device.inner, id, property, 0) };

        if let Some(err) = device.latest_error() {
            Err(err)
        } else {
            Ok(val)
        }
    }

    /// Retrieves an `u32` from an object attribute, returns the value of `default` on failure.
    fn get_u32(&self, property: Property) -> Result<u32> {
        let property = property.to_u32().unwrap();
        let id = self.id();
        let device = self.device();

        let val = unsafe { ffi::LIBMTP_Get_u32_From_Object(device.inner, id, property, 0) };

        if let Some(err) = device.latest_error() {
            Err(err)
        } else {
            Ok(val)
        }
    }

    /// Sets an object attribute from an `u32`.
    fn set_u32(&self, property: Property, value: u32) -> Result<()> {
        let property = property.to_u32().unwrap();
        let id = self.id();
        let device = self.device();

        let res = unsafe { ffi::LIBMTP_Set_Object_u32(device.inner, id, property, value) };

        if res != 0 {
            Err(device.latest_error().unwrap_or_default())
        } else {
            Ok(())
        }
    }

    /// Retrieves an `u16` from an object attribute, returns the value of `default` on failure.
    fn get_u16(&self, property: Property) -> Result<u16> {
        let property = property.to_u32().unwrap();
        let id = self.id();
        let device = self.device();

        let val = unsafe { ffi::LIBMTP_Get_u16_From_Object(device.inner, id, property, 0) };

        if let Some(err) = device.latest_error() {
            Err(err)
        } else {
            Ok(val)
        }
    }

    /// Sets an object attribute from an `u16`.
    fn set_u16(&self, property: Property, value: u16) -> Result<()> {
        let property = property.to_u32().unwrap();
        let id = self.id();
        let device = self.device();

        let res = unsafe { ffi::LIBMTP_Set_Object_u16(device.inner, id, property, value) };

        if res != 0 {
            Err(device.latest_error().unwrap_or_default())
        } else {
            Ok(())
        }
    }

    /// Retrieves an `u8` from an object attribute, returns the value of `default` on failure.
    fn get_u8(&self, property: Property) -> Result<u8> {
        let property = property.to_u32().unwrap();
        let id = self.id();
        let device = self.device();

        let val = unsafe { ffi::LIBMTP_Get_u8_From_Object(device.inner, id, property, 0) };

        if let Some(err) = device.latest_error() {
            Err(err)
        } else {
            Ok(val)
        }
    }

    /// Sets an object attribute from an `u8`.
    fn set_u8(&self, property: Property, value: u8) -> Result<()> {
        let property = property.to_u32().unwrap();
        let id = self.id();
        let device = self.device();

        let res = unsafe { ffi::LIBMTP_Set_Object_u8(device.inner, id, property, value) };

        if res != 0 {
            Err(device.latest_error().unwrap_or_default())
        } else {
            Ok(())
        }
    }

    /// Deletes a *single* file, track, playlist, folder or any other object off the MTP device.
    /// Note that deleting folders may no be remove its contents, in turn this is the expected
    /// behavior.
    ///
    /// If you want to delete a folder first recursively delete all files and folders contained in
    /// this folder, then the folder itself. Finally, if the operation is sucessful you should
    /// discard the object given that now it holds an **invalid id**.
    fn delete(&self) -> Result<()> {
        let id = self.id();
        let device = self.device();

        let res = unsafe { ffi::LIBMTP_Delete_Object(device.inner, id) };

        if res != 0 {
            Err(device.latest_error().unwrap_or_default())
        } else {
            Ok(())
        }
    }

    /// Moves the object to the specified storage (by its id) and parent folder. Moving objects
    /// may or not be supported on the device.
    ///
    /// Note that moving an object may take a significant amount of time, particularly if being
    /// moved between storages, MTP doesn't provide any kind of progress mechanism, so the operation
    /// will simply block for the duration.
    fn move_to(&self, storage_id: u32, parent: Parent) -> Result<()> {
        let id = self.id();
        let device = self.device();
        let parent = parent.to_id();

        let res = unsafe { ffi::LIBMTP_Move_Object(device.inner, id, storage_id, parent) };

        if res != 0 {
            Err(device.latest_error().unwrap_or_default())
        } else {
            Ok(())
        }
    }

    /// Copies the object to the specified storage (by its id) and parent folder. Copying objects
    /// may or not be supported on the device.
    ///
    /// Note that copying an object may take a significant amount of time, particularly if being
    /// copied between storages, MTP doesn't provide any kind of progress mechanism, so the
    /// operation will simply block for the duration.
    fn copy_to(&self, storage_id: u32, parent: Parent) -> Result<()> {
        let id = self.id();
        let device = self.device();
        let parent = parent.to_id();

        let res = unsafe { ffi::LIBMTP_Copy_Object(device.inner, id, storage_id, parent) };

        if res != 0 {
            Err(device.latest_error().unwrap_or_default())
        } else {
            Ok(())
        }
    }

    /// Get partial data from an object, specifying an offset and the maximum bytes
    /// that should be read. Note that this may return fewer bytes than the maximum.
    fn get_partial_object(&self, offset: u64, maxbytes: u32) -> Result<Vec<u8>> {
        let id = self.id();
        let device = self.device();

        let mut size = 0;
        let mut data = std::ptr::null_mut();

        let res = unsafe {
            ffi::LIBMTP_GetPartialObject(device.inner, id, offset, maxbytes, &mut data, &mut size)
        };

        if res != 0 || data.is_null() {
            if !data.is_null() {
                unsafe {
                    libc::free(data as *mut _);
                }
            }

            Err(device.latest_error().unwrap_or_default())
        } else {
            let bytes = unsafe { prim_array_ptr_to_vec!(data, u8, size) };
            unsafe {
                libc::free(data as *mut _);
            }

            Ok(bytes)
        }
    }

    /// Send partial data to an object, specifying an offset and the data you want
    /// to write into the object.
    fn send_partial_object(&self, offset: u64, data: impl AsRef<[u8]>) -> Result<()> {
        let id = self.id();
        let device = self.device();

        let data = data.as_ref();
        let size = data.len();

        let res = unsafe {
            ffi::LIBMTP_SendPartialObject(
                device.inner,
                id,
                offset,
                data.as_ptr() as *mut _,
                size as u32,
            )
        };

        if res != 0 {
            Err(device.latest_error().unwrap_or_default())
        } else {
            Ok(())
        }
    }
}