1use chrono::{DateTime, TimeZone, Utc};
4use libmtp_sys as ffi;
5use num_traits::FromPrimitive;
6use std::ffi::{CStr, CString};
7use std::fmt::{self, Debug};
8use std::path::Path;
9
10#[cfg(unix)]
11use std::os::unix::io::AsRawFd;
12
13use crate::device::MtpDevice;
14use crate::object::filetypes::Filetype;
15use crate::object::{AsObjectId, Object};
16use crate::storage::Parent;
17use crate::util::data_get_func_handler;
18use crate::util::data_put_func_handler;
19use crate::util::progress_func_handler;
20use crate::util::{CallbackReturn, HandlerReturn};
21use crate::Result;
22
23pub struct File<'a> {
26 pub(crate) inner: *mut ffi::LIBMTP_file_t,
27 pub(crate) owner: &'a MtpDevice,
28}
29
30impl Drop for File<'_> {
31 fn drop(&mut self) {
32 unsafe {
33 ffi::LIBMTP_destroy_file_t(self.inner);
34 }
35 }
36}
37
38impl Object for File<'_> {
39 fn id(&self) -> u32 {
40 unsafe { (*self.inner).item_id }
41 }
42
43 fn device(&self) -> &MtpDevice {
44 self.owner
45 }
46}
47
48impl Object for &File<'_> {
49 fn id(&self) -> u32 {
50 unsafe { (*self.inner).item_id }
51 }
52
53 fn device(&self) -> &MtpDevice {
54 self.owner
55 }
56}
57
58impl Debug for File<'_> {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 f.debug_struct("File")
61 .field("id", &self.id())
62 .field("parent_id", &self.parent_id())
63 .field("storage_id", &self.storage_id())
64 .field("size", &self.size())
65 .field("name", &self.name())
66 .field("ftype", &self.ftype())
67 .field("modification_date", &self.modification_date())
68 .finish()
69 }
70}
71
72impl File<'_> {
73 pub fn storage_id(&self) -> u32 {
75 unsafe { (*self.inner).storage_id }
76 }
77
78 pub fn parent_id(&self) -> Parent {
80 let id = unsafe { (*self.inner).parent_id };
81
82 if id == ffi::LIBMTP_FILES_AND_FOLDERS_ROOT {
83 Parent::Root
84 } else {
85 Parent::Folder(id)
86 }
87 }
88
89 pub fn size(&self) -> u64 {
91 unsafe { (*self.inner).filesize }
92 }
93
94 pub fn name(&self) -> &str {
96 unsafe {
97 let cstr = CStr::from_ptr((*self.inner).filename);
98 cstr.to_str().expect("Invalid UTF-8 on file name")
99 }
100 }
101
102 pub fn ftype(&self) -> Filetype {
104 let ftype = unsafe { (*self.inner).filetype };
105 Filetype::from_u32(ftype).expect("Unexpected raw variant of Filetype")
106 }
107
108 pub fn modification_date(&self) -> DateTime<Utc> {
110 let epoch = unsafe { (*self.inner).modificationdate };
111 Utc.timestamp(epoch, 0)
112 }
113
114 pub fn rename(&mut self, new_name: &str) -> Result<()> {
116 let new_name = CString::new(new_name).expect("Nul byte");
117
118 let res = unsafe {
119 ffi::LIBMTP_Set_File_Name(self.owner.inner, self.inner, new_name.as_ptr() as *const _)
120 };
121
122 if res != 0 {
123 Err(self.owner.latest_error().unwrap_or_default())
124 } else {
125 Ok(())
126 }
127 }
128}
129
130#[derive(Debug, Clone)]
132pub struct FileMetadata<'a> {
133 pub file_size: u64,
134 pub file_name: &'a str,
135 pub file_type: Filetype,
136 pub modification_date: DateTime<Utc>,
137}
138
139pub(crate) fn get_file_to_path(
140 mtpdev: &MtpDevice,
141 file: impl AsObjectId,
142 path: impl AsRef<Path>,
143) -> Result<()> {
144 let path = path.as_ref();
145 let path = path_to_cvec!(path);
146
147 let res = unsafe {
148 ffi::LIBMTP_Get_File_To_File(
149 mtpdev.inner,
150 file.as_id(),
151 path.as_ptr() as *const _,
152 None,
153 std::ptr::null(),
154 )
155 };
156
157 if res != 0 {
158 Err(mtpdev.latest_error().unwrap_or_default())
159 } else {
160 Ok(())
161 }
162}
163
164pub(crate) fn get_file_to_path_with_callback<C>(
165 mtpdev: &MtpDevice,
166 file: impl AsObjectId,
167 path: impl AsRef<Path>,
168 mut callback: C,
169) -> Result<()>
170where
171 C: FnMut(u64, u64) -> CallbackReturn,
172{
173 let path = path.as_ref();
174 let path = path_to_cvec!(path);
175
176 let mut callback: &mut dyn FnMut(u64, u64) -> CallbackReturn = &mut callback;
177 let callback = &mut callback as *mut _ as *mut libc::c_void as *const _;
178
179 let res = unsafe {
180 ffi::LIBMTP_Get_File_To_File(
181 mtpdev.inner,
182 file.as_id(),
183 path.as_ptr() as *const _,
184 Some(progress_func_handler),
185 callback,
186 )
187 };
188
189 if res != 0 {
190 Err(mtpdev.latest_error().unwrap_or_default())
191 } else {
192 Ok(())
193 }
194}
195
196#[cfg(unix)]
197pub(crate) fn get_file_to_descriptor(
198 mtpdev: &MtpDevice,
199 file: impl AsObjectId,
200 descriptor: impl AsRawFd,
201) -> Result<()> {
202 let res = unsafe {
203 ffi::LIBMTP_Get_File_To_File_Descriptor(
204 mtpdev.inner,
205 file.as_id(),
206 descriptor.as_raw_fd(),
207 None,
208 std::ptr::null(),
209 )
210 };
211
212 if res != 0 {
213 Err(mtpdev.latest_error().unwrap_or_default())
214 } else {
215 Ok(())
216 }
217}
218
219#[cfg(unix)]
220pub(crate) fn get_file_to_descriptor_with_callback<C>(
221 mtpdev: &MtpDevice,
222 file: impl AsObjectId,
223 descriptor: impl AsRawFd,
224 mut callback: C,
225) -> Result<()>
226where
227 C: FnMut(u64, u64) -> CallbackReturn,
228{
229 let mut callback: &mut dyn FnMut(u64, u64) -> CallbackReturn = &mut callback;
230 let callback = &mut callback as *mut _ as *mut libc::c_void as *const _;
231
232 let res = unsafe {
233 ffi::LIBMTP_Get_File_To_File_Descriptor(
234 mtpdev.inner,
235 file.as_id(),
236 descriptor.as_raw_fd(),
237 Some(progress_func_handler),
238 callback,
239 )
240 };
241
242 if res != 0 {
243 Err(mtpdev.latest_error().unwrap_or_default())
244 } else {
245 Ok(())
246 }
247}
248
249pub(crate) fn get_file_to_handler<H>(
250 mtpdev: &MtpDevice,
251 file: impl AsObjectId,
252 mut handler: H,
253) -> Result<()>
254where
255 H: FnMut(&[u8]) -> HandlerReturn,
256{
257 let handler: &mut dyn FnMut(&[u8]) -> HandlerReturn = &mut handler;
258 let mut handler_return = HandlerReturn::Ok(0);
259
260 let private = &mut (&mut handler_return, handler) as *mut _ as *mut libc::c_void;
261
262 let res = unsafe {
263 ffi::LIBMTP_Get_File_To_Handler(
264 mtpdev.inner,
265 file.as_id(),
266 Some(data_put_func_handler),
267 private,
268 None,
269 std::ptr::null(),
270 )
271 };
272
273 if res != 0 && handler_return.is_error() {
274 Err(mtpdev.latest_error().unwrap_or_default())
275 } else {
276 if handler_return.is_cancel() {
277 let _ = mtpdev.latest_error();
278 }
279
280 Ok(())
281 }
282}
283
284pub(crate) fn get_file_to_handler_with_callback<H, C>(
285 mtpdev: &MtpDevice,
286 file: impl AsObjectId,
287 mut handler: H,
288 mut callback: C,
289) -> Result<()>
290where
291 H: FnMut(&[u8]) -> HandlerReturn,
292 C: FnMut(u64, u64) -> CallbackReturn,
293{
294 let handler: &mut dyn FnMut(&[u8]) -> HandlerReturn = &mut handler;
295 let mut handler_return = HandlerReturn::Ok(0);
296
297 let private = &mut (&mut handler_return, handler) as *mut _ as *mut libc::c_void;
298
299 let mut callback: &mut dyn FnMut(u64, u64) -> CallbackReturn = &mut callback;
300 let callback = &mut callback as *mut _ as *mut libc::c_void as *const _;
301
302 let res = unsafe {
303 ffi::LIBMTP_Get_File_To_Handler(
304 mtpdev.inner,
305 file.as_id(),
306 Some(data_put_func_handler),
307 private,
308 Some(progress_func_handler),
309 callback,
310 )
311 };
312
313 if res != 0 && handler_return.is_error() {
314 Err(mtpdev.latest_error().unwrap_or_default())
315 } else {
316 if handler_return.is_cancel() {
317 let _ = mtpdev.latest_error();
318 }
319
320 Ok(())
321 }
322}
323
324pub(crate) fn send_file_from_path<'a>(
325 mtpdev: &'a MtpDevice,
326 storage_id: u32,
327 path: impl AsRef<Path>,
328 parent: Parent,
329 metadata: FileMetadata<'_>,
330) -> Result<File<'a>> {
331 let path = path.as_ref();
332 let path = path_to_cvec!(path);
333
334 let file_t = unsafe { ffi::LIBMTP_new_file_t() };
335 unsafe { fill_file_t!(metadata, parent.to_id(), storage_id, file_t) };
336
337 let res = unsafe {
338 ffi::LIBMTP_Send_File_From_File(
339 mtpdev.inner,
340 path.as_ptr() as *const _,
341 file_t,
342 None,
343 std::ptr::null(),
344 )
345 };
346
347 if res != 0 {
348 Err(mtpdev.latest_error().unwrap_or_default())
349 } else {
350 Ok(File {
351 inner: file_t,
352 owner: mtpdev,
353 })
354 }
355}
356
357pub(crate) fn send_file_from_path_with_callback<'a, C>(
358 mtpdev: &'a MtpDevice,
359 storage_id: u32,
360 path: impl AsRef<Path>,
361 parent: Parent,
362 metadata: FileMetadata<'_>,
363 mut callback: C,
364) -> Result<File<'a>>
365where
366 C: FnMut(u64, u64) -> CallbackReturn,
367{
368 let path = path.as_ref();
369 let path = path_to_cvec!(path);
370
371 let file_t = unsafe { ffi::LIBMTP_new_file_t() };
372 unsafe { fill_file_t!(metadata, parent.to_id(), storage_id, file_t) };
373
374 let mut callback: &mut dyn FnMut(u64, u64) -> CallbackReturn = &mut callback;
375 let callback = &mut callback as *mut _ as *mut libc::c_void as *const _;
376
377 let res = unsafe {
378 ffi::LIBMTP_Send_File_From_File(
379 mtpdev.inner,
380 path.as_ptr() as *const _,
381 file_t,
382 Some(progress_func_handler),
383 callback,
384 )
385 };
386
387 if res != 0 {
388 Err(mtpdev.latest_error().unwrap_or_default())
389 } else {
390 Ok(File {
391 inner: file_t,
392 owner: mtpdev,
393 })
394 }
395}
396
397#[cfg(unix)]
398pub(crate) fn send_file_from_descriptor<'a>(
399 mtpdev: &'a MtpDevice,
400 storage_id: u32,
401 descriptor: impl AsRawFd,
402 parent: Parent,
403 metadata: FileMetadata<'_>,
404) -> Result<File<'a>> {
405 let file_t = unsafe { ffi::LIBMTP_new_file_t() };
406 unsafe { fill_file_t!(metadata, parent.to_id(), storage_id, file_t) };
407
408 let res = unsafe {
409 ffi::LIBMTP_Send_File_From_File_Descriptor(
410 mtpdev.inner,
411 descriptor.as_raw_fd(),
412 file_t,
413 None,
414 std::ptr::null(),
415 )
416 };
417
418 if res != 0 {
419 Err(mtpdev.latest_error().unwrap_or_default())
420 } else {
421 Ok(File {
422 inner: file_t,
423 owner: mtpdev,
424 })
425 }
426}
427
428#[cfg(unix)]
429pub(crate) fn send_file_from_descriptor_with_callback<'a, C>(
430 mtpdev: &'a MtpDevice,
431 storage_id: u32,
432 descriptor: impl AsRawFd,
433 parent: Parent,
434 metadata: FileMetadata<'_>,
435 mut callback: C,
436) -> Result<File<'a>>
437where
438 C: FnMut(u64, u64) -> CallbackReturn,
439{
440 let file_t = unsafe { ffi::LIBMTP_new_file_t() };
441 unsafe { fill_file_t!(metadata, parent.to_id(), storage_id, file_t) };
442
443 let mut callback: &mut dyn FnMut(u64, u64) -> CallbackReturn = &mut callback;
444 let callback = &mut callback as *mut _ as *mut libc::c_void as *const _;
445
446 let res = unsafe {
447 ffi::LIBMTP_Send_File_From_File_Descriptor(
448 mtpdev.inner,
449 descriptor.as_raw_fd(),
450 file_t,
451 Some(progress_func_handler),
452 callback,
453 )
454 };
455
456 if res != 0 {
457 Err(mtpdev.latest_error().unwrap_or_default())
458 } else {
459 Ok(File {
460 inner: file_t,
461 owner: mtpdev,
462 })
463 }
464}
465
466pub(crate) fn send_file_from_handler<'a, H>(
467 mtpdev: &'a MtpDevice,
468 storage_id: u32,
469 parent: Parent,
470 metadata: FileMetadata<'_>,
471 mut handler: H,
472) -> Result<File<'a>>
473where
474 H: FnMut(&mut [u8]) -> HandlerReturn,
475{
476 let handler: &mut dyn FnMut(&mut [u8]) -> HandlerReturn = &mut handler;
477 let mut handler_return = HandlerReturn::Ok(0);
478
479 let private = &mut (&mut handler_return, handler) as *mut _ as *mut libc::c_void;
480
481 let file_t = unsafe { ffi::LIBMTP_new_file_t() };
482 unsafe { fill_file_t!(metadata, parent.to_id(), storage_id, file_t) };
483
484 let res = unsafe {
485 ffi::LIBMTP_Send_File_From_Handler(
486 mtpdev.inner,
487 Some(data_get_func_handler),
488 private,
489 file_t,
490 None,
491 std::ptr::null(),
492 )
493 };
494
495 if res != 0 && handler_return.is_error() {
496 Err(mtpdev.latest_error().unwrap_or_default())
497 } else {
498 if handler_return.is_cancel() {
499 let _ = mtpdev.latest_error();
500 }
501
502 Ok(File {
503 inner: file_t,
504 owner: mtpdev,
505 })
506 }
507}
508
509pub(crate) fn send_file_from_handler_with_callback<'a, H, C>(
510 mtpdev: &'a MtpDevice,
511 storage_id: u32,
512 parent: Parent,
513 metadata: FileMetadata<'_>,
514 mut handler: H,
515 mut callback: C,
516) -> Result<File<'a>>
517where
518 H: FnMut(&mut [u8]) -> HandlerReturn,
519 C: FnMut(u64, u64) -> CallbackReturn,
520{
521 let handler: &mut dyn FnMut(&mut [u8]) -> HandlerReturn = &mut handler;
522 let mut handler_return = HandlerReturn::Ok(0);
523
524 let private = &mut (&mut handler_return, handler) as *mut _ as *mut libc::c_void;
525
526 let file_t = unsafe { ffi::LIBMTP_new_file_t() };
527 unsafe { fill_file_t!(metadata, parent.to_id(), storage_id, file_t) };
528
529 let mut callback: &mut dyn FnMut(u64, u64) -> CallbackReturn = &mut callback;
530 let callback = &mut callback as *mut _ as *mut libc::c_void as *const _;
531
532 let res = unsafe {
533 ffi::LIBMTP_Send_File_From_Handler(
534 mtpdev.inner,
535 Some(data_get_func_handler),
536 private,
537 file_t,
538 Some(progress_func_handler),
539 callback,
540 )
541 };
542
543 if res != 0 && handler_return.is_error() {
544 Err(mtpdev.latest_error().unwrap_or_default())
545 } else {
546 if handler_return.is_cancel() {
547 let _ = mtpdev.latest_error();
548 }
549
550 Ok(File {
551 inner: file_t,
552 owner: mtpdev,
553 })
554 }
555}