libmtp_rs/storage/
folders.rs

1//! Contains relevant items to handle folder objects in the device.
2
3use std::borrow::Cow;
4use std::ffi::{CStr, CString};
5use std::fmt::{self, Debug};
6
7use libmtp_sys as ffi;
8
9use crate::device::MtpDevice;
10use crate::object::Object;
11use crate::storage::Parent;
12use crate::Result;
13
14pub struct Folder<'a> {
15    inner: *mut ffi::LIBMTP_folder_t,
16    owner: &'a MtpDevice,
17
18    sibling_or_child: bool,
19}
20
21impl Drop for Folder<'_> {
22    fn drop(&mut self) {
23        // (Recursively) destroy this folder only if this one was the
24        // first folder gathered
25        if !self.sibling_or_child {
26            unsafe {
27                ffi::LIBMTP_destroy_folder_t(self.inner);
28            }
29        }
30    }
31}
32
33impl Object for Folder<'_> {
34    fn id(&self) -> u32 {
35        unsafe { (*self.inner).folder_id }
36    }
37
38    fn device(&self) -> &MtpDevice {
39        self.owner
40    }
41}
42
43impl Object for &Folder<'_> {
44    fn id(&self) -> u32 {
45        unsafe { (*self.inner).folder_id }
46    }
47
48    fn device(&self) -> &MtpDevice {
49        self.owner
50    }
51}
52
53impl Debug for Folder<'_> {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        f.debug_struct("Folder")
56            .field("parent_id", &self.parent_id())
57            .field("name", &self.name())
58            .finish()
59    }
60}
61
62impl<'a> Folder<'a> {
63    pub fn parent_id(&self) -> u32 {
64        unsafe { (*self.inner).parent_id }
65    }
66
67    pub fn name(&self) -> &str {
68        unsafe {
69            let cstr = CStr::from_ptr((*self.inner).name);
70            cstr.to_str().expect("Invalid UTF-8 on folder name")
71        }
72    }
73
74    pub fn sibling(&self) -> Option<Folder<'a>> {
75        unsafe {
76            if (*self.inner).sibling.is_null() {
77                None
78            } else {
79                Some(Folder {
80                    inner: (*self.inner).sibling,
81                    owner: self.owner,
82                    sibling_or_child: true,
83                })
84            }
85        }
86    }
87
88    pub fn child(&self) -> Option<Folder<'a>> {
89        unsafe {
90            if (*self.inner).child.is_null() {
91                None
92            } else {
93                Some(Folder {
94                    inner: (*self.inner).child,
95                    owner: self.owner,
96                    sibling_or_child: true,
97                })
98            }
99        }
100    }
101
102    pub fn find(&self, folder_id: u32) -> Option<Folder<'a>> {
103        let folder = unsafe { ffi::LIBMTP_Find_Folder(self.inner, folder_id) };
104
105        if folder.is_null() {
106            None
107        } else {
108            Some(Folder {
109                inner: folder,
110                owner: self.owner,
111                sibling_or_child: true,
112            })
113        }
114    }
115
116    pub fn rename(&mut self, new_name: &str) -> Result<()> {
117        let new_name = CString::new(new_name).expect("Nul byte");
118
119        let res =
120            unsafe { ffi::LIBMTP_Set_Folder_Name(self.owner.inner, self.inner, new_name.as_ptr()) };
121
122        if res != 0 {
123            Err(self.owner.latest_error().unwrap_or_default())
124        } else {
125            Ok(())
126        }
127    }
128}
129
130pub(crate) fn get_folder_list(mtpdev: &MtpDevice) -> Option<Folder<'_>> {
131    let folder = unsafe { ffi::LIBMTP_Get_Folder_List(mtpdev.inner) };
132
133    if folder.is_null() {
134        None
135    } else {
136        Some(Folder {
137            inner: folder,
138            owner: mtpdev,
139            sibling_or_child: false,
140        })
141    }
142}
143
144pub(crate) fn get_folder_list_storage(mtpdev: &MtpDevice, storage_id: u32) -> Option<Folder<'_>> {
145    let folder = unsafe { ffi::LIBMTP_Get_Folder_List_For_Storage(mtpdev.inner, storage_id) };
146
147    if folder.is_null() {
148        None
149    } else {
150        Some(Folder {
151            inner: folder,
152            owner: mtpdev,
153            sibling_or_child: false,
154        })
155    }
156}
157
158pub(crate) fn create_folder<'a>(
159    mtpdev: &MtpDevice,
160    name: &'a str,
161    parent: Parent,
162    storage_id: u32,
163) -> Result<(u32, Cow<'a, str>)> {
164    let name_cstr = CString::new(name).expect("Nul byte");
165    let parent = parent.faf_id();
166
167    let name_in_c = unsafe { libc::strdup(name_cstr.as_ptr()) };
168    let folder_id =
169        unsafe { ffi::LIBMTP_Create_Folder(mtpdev.inner, name_in_c, parent, storage_id) };
170
171    let name_from_c = unsafe { CStr::from_ptr(name_in_c) };
172    let name_from_c = name_from_c.to_str().expect("Invalid UTF-8");
173
174    let name = if name_from_c == name {
175        Cow::Borrowed(name)
176    } else {
177        Cow::Owned(name_from_c.to_string())
178    };
179
180    unsafe {
181        // Starting from here `name_from_c` is INVALID!  Note that `name` is perfecly
182        // valid since it borrows original `name` or creates a new Rust `String`from the
183        // contents of `name_from_c` (before it was invalidated)
184        libc::free(name_in_c as *mut _);
185    }
186
187    if folder_id == 0 {
188        Err(mtpdev.latest_error().unwrap_or_default())
189    } else {
190        Ok((folder_id, name))
191    }
192}