use crate::mtp::{Error, ObjectFormat, ObjectHandle, ObjectInfo, StorageId};
use std::ffi::c_void;
use windows::core::{Error as WinError, GUID, PWSTR};
use windows::Win32::Devices::PortableDevices::{
IPortableDeviceProperties, IPortableDeviceValues, WPD_CONTENT_TYPE_FOLDER,
WPD_CONTENT_TYPE_FUNCTIONAL_OBJECT, WPD_OBJECT_CONTENT_TYPE, WPD_OBJECT_NAME,
WPD_OBJECT_ORIGINAL_FILE_NAME, WPD_OBJECT_SIZE,
};
use windows::Win32::System::Com::CoTaskMemFree;
pub(crate) fn wide(s: &str) -> Vec<u16> {
s.encode_utf16().chain(std::iter::once(0)).collect()
}
pub(crate) unsafe fn take_pwstr(p: PWSTR) -> String {
if p.is_null() {
return String::new();
}
let s = p.to_string().unwrap_or_default();
CoTaskMemFree(Some(p.0 as *const c_void));
s
}
pub(crate) fn map_hresult(e: WinError) -> Error {
const fn win32(code: u32) -> i32 {
(0x8007_0000u32 | (code & 0xFFFF)) as i32
}
const E_ACCESSDENIED: i32 = 0x8007_0005u32 as i32;
const E_NOTIMPL: i32 = 0x8000_4001u32 as i32;
const STG_E_MEDIUMFULL: i32 = 0x8003_0070u32 as i32;
const STG_E_ACCESSDENIED: i32 = 0x8003_0005u32 as i32;
const ERROR_FILE_NOT_FOUND: u32 = 2;
const ERROR_PATH_NOT_FOUND: u32 = 3;
const ERROR_ACCESS_DENIED: u32 = 5;
const ERROR_BUSY: u32 = 170;
const ERROR_DEVICE_NOT_AVAILABLE: u32 = 4319;
const ERROR_DEVICE_REMOVED: u32 = 1617;
const ERROR_NOT_SUPPORTED: u32 = 50;
match e.code().0 {
E_ACCESSDENIED | STG_E_ACCESSDENIED => Error::AccessDenied,
x if x == win32(ERROR_ACCESS_DENIED) => Error::AccessDenied,
x if x == win32(ERROR_FILE_NOT_FOUND) || x == win32(ERROR_PATH_NOT_FOUND) => {
Error::NotFound
}
x if x == win32(ERROR_BUSY) => Error::Busy,
x if x == win32(ERROR_DEVICE_NOT_AVAILABLE) || x == win32(ERROR_DEVICE_REMOVED) => {
Error::Disconnected
}
STG_E_MEDIUMFULL => Error::StorageFull,
E_NOTIMPL => Error::Unsupported,
x if x == win32(ERROR_NOT_SUPPORTED) => Error::Unsupported,
_ => Error::Other {
detail: format!("HRESULT {:#010x}: {}", e.code().0 as u32, e.message()),
},
}
}
pub(crate) fn is_folder_content_type(ctype: &GUID) -> bool {
*ctype == WPD_CONTENT_TYPE_FOLDER || *ctype == WPD_CONTENT_TYPE_FUNCTIONAL_OBJECT
}
pub(crate) unsafe fn read_object_info(
props: &IPortableDeviceProperties,
handle: ObjectHandle,
wpd_id: &str,
parent: ObjectHandle,
storage: StorageId,
) -> ObjectInfo {
let id_w = wide(wpd_id);
let vals = props.GetValues(windows::core::PCWSTR(id_w.as_ptr()), None);
let (filename, size, folder) = match vals {
Ok(v) => {
let filename = v
.GetStringValue(&WPD_OBJECT_ORIGINAL_FILE_NAME)
.map(|p| take_pwstr(p))
.ok()
.filter(|s| !s.is_empty())
.or_else(|| {
v.GetStringValue(&WPD_OBJECT_NAME)
.map(|p| take_pwstr(p))
.ok()
})
.unwrap_or_default();
let ctype = v.GetGuidValue(&WPD_OBJECT_CONTENT_TYPE).unwrap_or_default();
let folder = is_folder_content_type(&ctype);
let size = if folder {
0
} else {
v.GetUnsignedLargeIntegerValue(&WPD_OBJECT_SIZE)
.unwrap_or(0)
};
(filename, size, folder)
}
Err(_) => (String::new(), 0, false),
};
let format = if folder {
ObjectFormat::ASSOCIATION
} else {
ObjectFormat::UNDEFINED
};
ObjectInfo {
handle,
storage_id: storage,
parent,
filename,
size,
format,
created: None,
modified: None,
image_width: 0,
image_height: 0,
folder,
}
}
pub(crate) unsafe fn set_u32(
bag: &IPortableDeviceValues,
key: &windows::Win32::Foundation::PROPERTYKEY,
value: u32,
) -> Result<(), Error> {
bag.SetUnsignedIntegerValue(key, value).map_err(map_hresult)
}