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
use windows::Win32::System::Com::{CoCreateInstance, CLSCTX_ALL};
use windows::core::{GUID, PCWSTR} ;
use windows::Win32::Devices::PortableDevices::{PortableDeviceFTM, IPortableDevice};
use widestring::U16CString;

use crate::device::device_values::AppIdentifiers;

pub mod device_values;

mod content;
pub use content::Content;

/// Basic info about an MTP device
///
/// To access its content, you must call [`BasicDevice::open`]
#[derive(Clone)]
pub struct BasicDevice {
    device_id: U16CString,
    friendly_name: String,
}

impl BasicDevice {
    pub(crate) fn new(device_id: U16CString, friendly_name: String) -> Self {
        Self{ device_id, friendly_name }
    }

    pub fn device_id(&self) -> String {
        self.device_id.to_string_lossy() // We trust Windows for not providing invalid UTF-16 characters
    }

    pub fn friendly_name(&self) -> &str {
        &self.friendly_name
    }

    /// Turns this device into an "opened" device, that has more features
    /// (e.g. being able to browse its content)
    ///
    /// Some devices (e.g. ones that are backed by a FAT filesystem) use case-insensitive paths. In this case, you want to set `case_sensitive` to false.
    /// Otherwise, you would often get `Err`s, e.g. when you try to create or replace a file (or folder) with a similar name but different casing.<br/>
    /// Unfortunately, the Windows API does not look to be able to give this info.
    pub fn open(&self, app_identifiers: &AppIdentifiers, case_sensitive_fs: bool) -> crate::WindowsResult<Device> {
        // Fill out information about your application, so the device knows
        // who they are speaking to.
        let device_values = device_values::make_values_for_open_device(app_identifiers)?;

        let com_device: IPortableDevice = unsafe {
            CoCreateInstance(
                &PortableDeviceFTM as *const GUID,
                None,
                CLSCTX_ALL
            )
        }?;

        unsafe { com_device.Open(PCWSTR::from_raw(self.device_id.as_ptr()), &device_values) }.unwrap();

        Ok(Device{
            com_device,
            case_sensitive_fs,
        })
    }
}

/// An MTP device that as been opened
pub struct Device {
    com_device: IPortableDevice,
    case_sensitive_fs: bool,
}

impl Device {
    /// Returns the underlying COM object
    ///
    /// This is useful in case you want to call a function that has no Rust wrapper (yet?) in this crate
    pub fn raw_device(&self) -> &IPortableDevice {
        &self.com_device
    }

    pub fn content(&self) -> crate::WindowsResult<Content> {
        let com_content = unsafe { self.com_device.Content() }?;
        Ok(Content::new(com_content, self.case_sensitive_fs))
    }
}