libmtp_rs/
device.rs

1//! This module groups all the operations you can do on an MTP device, like gathering
2//! information, properties, support for filetypes, and update/gather storage in order
3//! to be able to send or get files, folders, tracks, etc.
4
5pub mod capabilities;
6pub mod raw;
7
8use capabilities::DeviceCapability;
9use libmtp_sys as ffi;
10use num_derive::ToPrimitive;
11use num_traits::{FromPrimitive, ToPrimitive};
12use std::ffi::CString;
13use std::fmt::{self, Debug};
14
15use crate::error::Error;
16use crate::object::filetypes::Filetype;
17use crate::object::properties::Property;
18use crate::object::{AsObjectId, DummyObject};
19use crate::storage::files::File;
20use crate::storage::StoragePool;
21use crate::values::AllowedValues;
22use crate::Result;
23
24/// Sorting logic to apply after the update of storages.
25#[derive(Debug, Clone, Copy, ToPrimitive)]
26pub enum StorageSort {
27    /// Do not sort the storages
28    NotSorted = 0,
29    /// Sort storages by their free space.
30    ByFreeSpace,
31    /// Sort storages by their maximum space.
32    ByMaximumSpace,
33}
34
35/// Result given when updating the inner storage list of an MTP device with
36/// [`MtpDevice::update_storage`](struct.MtpDevice.html#method.battery_level).
37///
38/// This is mostly useful for the developer to show some sort of message, depending on
39/// whether there isn't enough information about the storage (`OnlyIds` where retrieved).
40/// Note that `StoragePool` and `Storage` instances have knowledge about the result
41/// of `update_storage`.
42#[derive(Debug, Clone, Copy)]
43pub enum UpdateResult {
44    /// No errors, everything went fine.
45    Success,
46    /// Partial success, couldn't get storage properties.
47    OnlyIds,
48}
49
50/// Information about the battery level gather from a device with
51/// [`MtpDevice::battery_level`](struct.MtpDevice.html#method.battery_level).
52///
53/// ## Example
54/// ```no_run
55/// let (level, max_level) = mtp_device.battery_level().expect("Failed to get battery level");
56/// match level {
57///     BatteryLevel::OnBattery(level) => println!("Using battery, current level {}", level),
58///     BatteryLevel::OnExternalPower => println!("Using external power, connected to AC"),
59/// }
60/// ```
61#[derive(Debug, Copy, Clone)]
62pub enum BatteryLevel {
63    /// The device is currently on battery.
64    OnBattery(u8),
65    /// The device is currently on external power.
66    OnExternalPower,
67}
68
69/// Result from opening a raw device descriptor, holds information about the device like
70/// default folders, battery level, manufacturer, model, storage, etc.
71///
72/// Storage is directly tied to an MTP device by the `StoragePool` struct abstraction,
73/// which you may get with [`storage_pool`](struct.MtpDevice.html#method.storage_pool) after
74/// updating the storage with [`update_storage`](struct.MtpDevice.html#method.update_storage).
75///
76/// ## Example
77/// ```no_run
78/// mtp_device.update_storage().expect("Couldn't update storage");
79/// let storage_pool = mtp_device.storage_pool();
80/// ```
81pub struct MtpDevice {
82    pub(crate) inner: *mut ffi::LIBMTP_mtpdevice_t,
83}
84
85impl Drop for MtpDevice {
86    fn drop(&mut self) {
87        unsafe {
88            ffi::LIBMTP_Release_Device(self.inner);
89        }
90    }
91}
92
93impl Debug for MtpDevice {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        let max_bat_level = unsafe { (*self.inner).maximum_battery_level };
96
97        f.debug_struct("MTPDevice")
98            .field("maximum_battery_level", &max_bat_level)
99            .field("default_music_folder", &self.default_music_folder())
100            .field("default_playlist_folder", &self.default_playlist_folder())
101            .field("default_picture_folder", &self.default_picture_folder())
102            .field("default_video_folder", &self.default_video_folder())
103            .field("default_organizer_folder", &self.default_organizer_folder())
104            .field("default_zencast_folder", &self.default_zencast_folder())
105            .field("default_album_folder", &self.default_album_folder())
106            .field("default_text_folder", &self.default_text_folder())
107            .finish()
108    }
109}
110
111impl MtpDevice {
112    pub(crate) fn latest_error(&self) -> Option<Error> {
113        unsafe {
114            let list = ffi::LIBMTP_Get_Errorstack(self.inner);
115            let err = Error::from_latest_error(list)?;
116            ffi::LIBMTP_Clear_Errorstack(self.inner);
117            Some(err)
118        }
119    }
120}
121
122impl MtpDevice {
123    /// Retrieves the default music folder, if there isn't one this value may be garbage.
124    /// Therefore, it's not recommended to depend on this value, unless you know exactly
125    /// how the device you are interacting with handles this setting.
126    pub fn default_music_folder(&self) -> u32 {
127        unsafe { (*self.inner).default_music_folder }
128    }
129
130    /// Retrieves the default playlist folder, if there isn't one this value may be garbage.
131    /// Therefore, it's not recommended to depend on this value, unless you know exactly
132    /// how the device you are interacting with handles this setting.
133    pub fn default_playlist_folder(&self) -> u32 {
134        unsafe { (*self.inner).default_playlist_folder }
135    }
136
137    /// Retrieves the default picture folder, if there isn't one this value may be garbage.
138    /// Therefore, it's not recommended to depend on this value, unless you know exactly
139    /// how the device you are interacting with handles this setting.
140    pub fn default_picture_folder(&self) -> u32 {
141        unsafe { (*self.inner).default_picture_folder }
142    }
143
144    /// Retrieves the default video folder, if there isn't one this value may be garbage.
145    /// Therefore, it's not recommended to depend on this value, unless you know exactly
146    /// how the device you are interacting with handles this setting.
147    pub fn default_video_folder(&self) -> u32 {
148        unsafe { (*self.inner).default_video_folder }
149    }
150
151    /// Retrieves the default organizer folder, if there isn't one this value may be garbage.
152    /// Therefore, it's not recommended to depend on this value, unless you know exactly
153    /// how the device you are interacting with handles this setting.
154    pub fn default_organizer_folder(&self) -> u32 {
155        unsafe { (*self.inner).default_organizer_folder }
156    }
157
158    /// Retrieves the default zencast folder, if there isn't one this value may be garbage.
159    /// Therefore, it's not recommended to depend on this value, unless you know exactly
160    /// how the device you are interacting with handles this setting.
161    pub fn default_zencast_folder(&self) -> u32 {
162        unsafe { (*self.inner).default_zencast_folder }
163    }
164
165    /// Retrieves the default album folder, if there isn't one this value may be garbage.
166    /// Therefore, it's not recommended to depend on this value, unless you know exactly
167    /// how the device you are interacting with handles this setting.
168    pub fn default_album_folder(&self) -> u32 {
169        unsafe { (*self.inner).default_album_folder }
170    }
171
172    /// Retrieves the default text folder, if there isn't one this value may be garbage.
173    /// Therefore, it's not recommended to depend on this value, unless you know exactly
174    /// how the device you are interacting with handles this setting.
175    pub fn default_text_folder(&self) -> u32 {
176        unsafe { (*self.inner).default_text_folder }
177    }
178
179    /// Gets the friendly name of this device, e.g. "Kevin's Android"
180    pub fn get_friendly_name(&self) -> Result<String> {
181        unsafe {
182            let friendly_name = ffi::LIBMTP_Get_Friendlyname(self.inner);
183
184            if friendly_name.is_null() {
185                Err(self.latest_error().unwrap_or_default())
186            } else {
187                let u8vec = cstr_to_u8vec!(friendly_name);
188                libc::free(friendly_name as *mut _);
189                Ok(String::from_utf8(u8vec)?)
190            }
191        }
192    }
193
194    /// Sets the friendly name of this device
195    pub fn set_friendly_name(&self, name: &str) -> Result<()> {
196        let name = CString::new(name).expect("Nul byte");
197
198        unsafe {
199            let res = ffi::LIBMTP_Set_Friendlyname(self.inner, name.as_ptr());
200
201            if res != 0 {
202                Err(self.latest_error().unwrap_or_default())
203            } else {
204                Ok(())
205            }
206        }
207    }
208
209    /// Retrieves the synchronization partner of this device.
210    pub fn get_sync_partner(&self) -> Result<String> {
211        unsafe {
212            let partner = ffi::LIBMTP_Get_Syncpartner(self.inner);
213            let u8vec = cstr_to_u8vec!(partner);
214            libc::free(partner as *mut _);
215            Ok(String::from_utf8(u8vec)?)
216        }
217    }
218
219    /// Sets the synchronization partner of this device.
220    pub fn set_sync_partner(&self, partner: &str) -> Result<()> {
221        let partner = CString::new(partner).expect("Nul byte");
222
223        unsafe {
224            let res = ffi::LIBMTP_Set_Syncpartner(self.inner, partner.as_ptr());
225
226            if res != 0 {
227                Err(self.latest_error().unwrap_or_default())
228            } else {
229                Ok(())
230            }
231        }
232    }
233
234    /// Returns the manufacturer name of this device.
235    pub fn manufacturer_name(&self) -> Result<String> {
236        unsafe {
237            let manufacturer = ffi::LIBMTP_Get_Manufacturername(self.inner);
238
239            if manufacturer.is_null() {
240                Err(self.latest_error().unwrap_or_default())
241            } else {
242                let u8vec = cstr_to_u8vec!(manufacturer);
243                libc::free(manufacturer as *mut _);
244                Ok(String::from_utf8(u8vec)?)
245            }
246        }
247    }
248
249    /// Returns the model name of this device.
250    pub fn model_name(&self) -> Result<String> {
251        unsafe {
252            let model = ffi::LIBMTP_Get_Modelname(self.inner);
253
254            if model.is_null() {
255                Err(self.latest_error().unwrap_or_default())
256            } else {
257                let u8vec = cstr_to_u8vec!(model);
258                libc::free(model as *mut _);
259                Ok(String::from_utf8(u8vec)?)
260            }
261        }
262    }
263
264    /// Returns the serial number of this device.
265    pub fn serial_number(&self) -> Result<String> {
266        unsafe {
267            let serial = ffi::LIBMTP_Get_Serialnumber(self.inner);
268
269            if serial.is_null() {
270                Err(self.latest_error().unwrap_or_default())
271            } else {
272                let u8vec = cstr_to_u8vec!(serial);
273                libc::free(serial as *mut _);
274                Ok(String::from_utf8(u8vec)?)
275            }
276        }
277    }
278
279    /// Returns the device (public key) certificate as an XML document string.
280    pub fn device_certificate(&self) -> Result<String> {
281        unsafe {
282            let mut devcert = std::ptr::null_mut();
283            let res = ffi::LIBMTP_Get_Device_Certificate(self.inner, &mut devcert);
284
285            if res != 0 || devcert.is_null() {
286                Err(self.latest_error().unwrap_or_default())
287            } else {
288                let u8vec = cstr_to_u8vec!(devcert);
289                libc::free(devcert as *mut _);
290                Ok(String::from_utf8(u8vec)?)
291            }
292        }
293    }
294
295    /// Retrieves the current and maximum battery level of this device.
296    pub fn battery_level(&self) -> Result<(BatteryLevel, u8)> {
297        unsafe {
298            let mut max_level = 0;
299            let mut cur_level = 0;
300
301            let res = ffi::LIBMTP_Get_Batterylevel(self.inner, &mut max_level, &mut cur_level);
302
303            if res != 0 {
304                Err(self.latest_error().unwrap_or_default())
305            } else {
306                let cur_level = if cur_level == 0 {
307                    BatteryLevel::OnExternalPower
308                } else {
309                    BatteryLevel::OnBattery(cur_level)
310                };
311
312                Ok((cur_level, max_level))
313            }
314        }
315    }
316
317    /// Returns the secure time as an XML document string.
318    pub fn secure_time(&self) -> Result<String> {
319        unsafe {
320            let mut secure_time = std::ptr::null_mut();
321            let res = ffi::LIBMTP_Get_Secure_Time(self.inner, &mut secure_time);
322
323            if res != 0 || secure_time.is_null() {
324                Err(self.latest_error().unwrap_or_default())
325            } else {
326                let u8vec = cstr_to_u8vec!(secure_time);
327                libc::free(secure_time as *mut _);
328                Ok(String::from_utf8(u8vec)?)
329            }
330        }
331    }
332
333    /// Retrieves a list of supported file types that this device claims it supports.  
334    /// This list is mitigated to include the filetypes that `libmtp` (C library) can handle.
335    pub fn supported_filetypes(&self) -> Result<Vec<Filetype>> {
336        unsafe {
337            let mut filetypes = std::ptr::null_mut();
338            let mut len = 0;
339
340            let res = ffi::LIBMTP_Get_Supported_Filetypes(self.inner, &mut filetypes, &mut len);
341
342            if res != 0 || filetypes.is_null() {
343                Err(self.latest_error().unwrap_or_default())
344            } else {
345                let mut filetypes_vec = Vec::with_capacity(len as usize);
346                for i in 0..(len as isize) {
347                    let ftype = Filetype::from_u16(*filetypes.offset(i)).unwrap();
348                    filetypes_vec.push(ftype);
349                }
350
351                libc::free(filetypes as *mut _);
352                Ok(filetypes_vec)
353            }
354        }
355    }
356
357    /// Check whether this device has some specific capabilitiy.
358    pub fn check_capability(&self, capability: DeviceCapability) -> bool {
359        unsafe {
360            let cap_code = capability.to_u32().unwrap();
361            let res = ffi::LIBMTP_Check_Capability(self.inner, cap_code);
362            res != 0
363        }
364    }
365
366    /// Reset the device only if this one supports the `PTP_OC_ResetDevice` operation code
367    /// (`0x1010`)
368    pub fn reset_device(&self) -> Result<()> {
369        unsafe {
370            let res = ffi::LIBMTP_Reset_Device(self.inner);
371
372            if res != 0 {
373                Err(self.latest_error().unwrap_or_default())
374            } else {
375                Ok(())
376            }
377        }
378    }
379
380    /// Updates all the internal storage ids and properties of this device, it can also
381    /// optionally sort the list. This operation may success, partially success
382    /// (only ids were retrieved) or fail.
383    pub fn update_storage(&mut self, sort_by: StorageSort) -> Result<UpdateResult> {
384        unsafe {
385            let res = ffi::LIBMTP_Get_Storage(self.inner, sort_by.to_i32().unwrap());
386            match res {
387                0 => Ok(UpdateResult::Success),
388                1 => Ok(UpdateResult::OnlyIds),
389                _ => Err(self.latest_error().unwrap_or_default()),
390            }
391        }
392    }
393
394    /// Returns the inner storage pool, you need to call this if you updated
395    /// the storage with `update_storage`. Note that the pool may be empty.
396    pub fn storage_pool(&self) -> StoragePool<'_> {
397        unsafe {
398            let storage = (*self.inner).storage;
399            StoragePool::from_raw(&self, storage)
400        }
401    }
402
403    /// Dumps out a large chunk of textual information provided from the PTP protocol and
404    /// additionally some extra MTP specific information where applicable.
405    pub fn dump_device_info(&self) {
406        unsafe {
407            ffi::LIBMTP_Dump_Device_Info(self.inner);
408        }
409    }
410
411    /// Determines wheter a property is supported for a given file type.
412    pub fn is_property_supported(&self, property: Property, filetype: Filetype) -> Result<bool> {
413        let property = property.to_u32().unwrap();
414        let filetype = filetype.to_u32().unwrap();
415
416        unsafe {
417            let res = ffi::LIBMTP_Is_Property_Supported(self.inner, property, filetype);
418            match res {
419                0 => Ok(false),
420                r if r > 0 => Ok(true),
421                _ => Err(self.latest_error().unwrap_or_default()),
422            }
423        }
424    }
425
426    /// Retrieves the allowes values (range or enumeration) for an specific property.
427    pub fn allowed_property_values(
428        &self,
429        property: Property,
430        filetype: Filetype,
431    ) -> Result<AllowedValues> {
432        let property = property.to_u32().unwrap();
433        let filetype = filetype.to_u32().unwrap();
434
435        unsafe {
436            let allowed_values_ptr = std::ptr::null_mut();
437
438            let res = ffi::LIBMTP_Get_Allowed_Property_Values(
439                self.inner,
440                property,
441                filetype,
442                allowed_values_ptr,
443            );
444
445            if res != 0 || allowed_values_ptr.is_null() {
446                Err(self.latest_error().unwrap_or_default())
447            } else {
448                let allowed_values =
449                    AllowedValues::from_raw(allowed_values_ptr).ok_or_else(|| Error::Unknown)?;
450                ffi::LIBMTP_destroy_allowed_values_t(allowed_values_ptr);
451                Ok(allowed_values)
452            }
453        }
454    }
455
456    /// Build a dummy object, it's useful to work with objects when we only have an
457    /// id.
458    ///
459    /// ## Example
460    /// ```no_run
461    /// let id = 30; // stored id
462    /// let dummy_object = mtp_device.dummy_object(id);
463    /// let string = dummy_object.get_string(Property::ObjectFileName);
464    /// ```
465    pub fn dummy_object(&self, id: impl AsObjectId) -> DummyObject<'_> {
466        DummyObject {
467            id: id.as_id(),
468            mtpdev: self,
469        }
470    }
471
472    /// Search for a file with the given id in this device, note that you don't need to use a
473    /// Storage for this, since ids are unique across all the device.  Don't call this function
474    /// repeatedly, the search is `O(n)`and the call may involve slow USB traffic. Instead use
475    /// `Storage::files_and_folders` to cache files.
476    pub fn search_file(&self, id: impl AsObjectId) -> Result<File<'_>> {
477        let file = unsafe { ffi::LIBMTP_Get_Filemetadata(self.inner, id.as_id()) };
478
479        if file.is_null() {
480            Err(self.latest_error().unwrap_or_default())
481        } else {
482            Ok(File {
483                inner: file,
484                owner: self,
485            })
486        }
487    }
488
489    // TODO: Custom operation function (c_variadic nightly feature)
490    // pub fn custom_operation(&self, code: u16, params: &[u32]) -> Result<(), ErrorKind>;
491}