Skip to content

Commit

Permalink
Implement the list function for windows
Browse files Browse the repository at this point in the history
  • Loading branch information
ArturKovacs committed Apr 17, 2021
1 parent 218d0d0 commit 6e77795
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 11 deletions.
22 changes: 20 additions & 2 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,21 @@ fn main() {}
#[cfg(target_os = "windows")]
fn main() {
windows::build!(
Windows::Win32::SystemServices::PWSTR,
Windows::Win32::SystemServices::{PWSTR, S_OK},
Windows::Win32::WindowsProgramming::{
SYSTEMTIME,
FILETIME,
SystemTimeToFileTime
},
Windows::Win32::WindowsAndMessaging::HWND,
Windows::Win32::WindowsPropertiesSystem::PROPERTYKEY,
Windows::Win32::Automation::{
VARIANT,
VariantClear,
VariantChangeType,
VARENUM,
VariantTimeToSystemTime
},
Windows::Win32::Shell::{
SHGetDesktopFolder,
IShellFolder2,
Expand All @@ -16,7 +29,12 @@ fn main() {
IFileOperation,
FileOperation,
IShellItem,
IFileOperationProgressSink
IFileOperationProgressSink,
IEnumIDList,
_SHCONTF,
STRRET,
StrRetToStrW,
_SHGDNF
},
Windows::Win32::Com::{
CoInitializeEx,
Expand Down
5 changes: 3 additions & 2 deletions examples/list.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use trash;

#[cfg(not(any(target_os = "windows", all(unix, not(target_os = "macos")))))]
fn main() {
println!("This is currently only supported on Linux");
println!("This is currently only supported on Windows, Linux, and other Freedesktop.org compliant OSes");
}

#[cfg(target_os = "linux")]
#[cfg(any(target_os = "windows", all(unix, not(target_os = "macos"))))]
fn main() {
let trash_items = trash::extra::list().unwrap();
println!("{:#?}", trash_items);
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ impl Hash for TrashItem {
}
}

#[cfg(all(unix, not(target_os = "macos")))]
#[cfg(any(target_os = "windows", all(unix, not(target_os = "macos"))))]
pub mod extra {
use std::{
collections::HashSet,
Expand Down
2 changes: 1 addition & 1 deletion src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ mod unix {
}
}

#[cfg(any(target_os = "linux"))]
#[cfg(any(target_os = "windows", all(unix, not(target_os = "macos"))))]
mod extra {
use super::*;

Expand Down
206 changes: 201 additions & 5 deletions src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ mod bindings {
::windows::include_bindings!();
}

use bindings::Windows::Win32::{Com::*, Shell::*, SystemServices::*, WindowsAndMessaging::*};
use windows::{Abi, IUnknown, Interface, IntoParam, Param, RuntimeType};
use bindings::Windows::Win32::{Com::*, Shell::*, SystemServices::*, WindowsAndMessaging::*, WindowsPropertiesSystem::*, Automation::*, WindowsProgramming::*};
use windows::{HRESULT, Guid, IUnknown, Interface, IntoParam, Param, RuntimeType};

struct WinNull;
impl<'a> IntoParam<'a, IBindCtx> for WinNull {
Expand All @@ -37,7 +37,19 @@ impl<'a> IntoParam<'a, IFileOperationProgressSink> for WinNull {
}
}

// These don't have bindings in windows-rs for some reason:
///////////////////////////////////////////////////////////////////////////
// These don't have bindings in windows-rs for some reason
///////////////////////////////////////////////////////////////////////////
const PSGUID_DISPLACED: Guid = Guid::from_values(0x9b174b33, 0x40ff, 0x11d2, [0xa2, 0x7e, 0x00, 0xc0, 0x4f, 0xc3, 0x8, 0x71]);
const PID_DISPLACED_FROM: u32 = 2;
const PID_DISPLACED_DATE: u32 = 3;
const SCID_ORIGINAL_LOCATION: PROPERTYKEY = PROPERTYKEY {
fmtid: PSGUID_DISPLACED, pid: PID_DISPLACED_FROM
};
const SCID_DATE_DELETED: PROPERTYKEY = PROPERTYKEY {
fmtid: PSGUID_DISPLACED, pid: PID_DISPLACED_DATE
};

const FOF_SILENT: u32 = 0x0004;
const FOF_RENAMEONCOLLISION: u32 = 0x0008;
const FOF_NOCONFIRMATION: u32 = 0x0010;
Expand All @@ -52,6 +64,7 @@ const FOF_NORECURSION: u32 = 0x1000;
const FOF_NO_CONNECTED_ELEMENTS: u32 = 0x2000;
const FOF_WANTNUKEWARNING: u32 = 0x4000;
const FOF_NO_UI: u32 = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_NOCONFIRMMKDIR;
///////////////////////////////////////////////////////////////////////////

use crate::{Error, TrashItem};

Expand All @@ -63,7 +76,7 @@ macro_rules! return_err_on_fail {
description: format!("`{}` failed with the result: {:?}", stringify!($f_name), hr)
});
}
// hr
hr
});
{$obj:ident.$f_name:ident($($args:tt)*)} => ({
return_err_on_fail!{($obj).$f_name($($args)*)}
Expand All @@ -75,7 +88,7 @@ macro_rules! return_err_on_fail {
description: format!("`{}` failed with the result: {:?}", stringify!($f_name), hr)
});
}
// hr
hr
})
}

Expand Down Expand Up @@ -127,6 +140,189 @@ pub fn delete_all_canonicalized(full_paths: Vec<PathBuf>) -> Result<(), Error> {
}
}


pub fn list() -> Result<Vec<TrashItem>, Error> {
ensure_com_initialized();
unsafe {
let mut recycle_bin: IShellFolder2 = bind_to_csidl(CSIDL_BITBUCKET as c_int)?;
let mut peidl = MaybeUninit::<Option<IEnumIDList>>::uninit();
let flags = _SHCONTF::SHCONTF_FOLDERS.0 | _SHCONTF::SHCONTF_NONFOLDERS.0;
let hr = return_err_on_fail! {
recycle_bin.EnumObjects(
HWND::NULL,
flags as u32,
peidl.as_mut_ptr(),
)
};
// WARNING `hr.is_ok()` is DIFFERENT from `hr == S_OK`, because
// `is_ok` returns true if the HRESULT as any of the several success codes
// but here we want to be more strict and only accept S_OK.
if hr != S_OK {
return Err(Error::Unknown {
description: format!("`EnumObjects` returned with HRESULT {:X}, but 0x0 was expected.", hr.0)
});
}
let peidl = peidl.assume_init().ok_or_else(|| Error::Unknown {
description: format!("`EnumObjects` set its output to None.")
})?;
let mut item_vec = Vec::new();
let mut item_uninit = MaybeUninit::<*mut ITEMIDLIST>::uninit();
while peidl.Next(1, item_uninit.as_mut_ptr(), std::ptr::null_mut()) == S_OK {
let item = item_uninit.assume_init();
defer! {{ CoTaskMemFree(item as *mut c_void); }}
let id = get_display_name((&recycle_bin).into(), item, _SHGDNF::SHGDN_FORPARSING)?;
let name = get_display_name((&recycle_bin).into(), item, _SHGDNF::SHGDN_INFOLDER)?;

let orig_loc = get_detail(&recycle_bin, item, &SCID_ORIGINAL_LOCATION as *const _)?;
let date_deleted = get_date_unix(&recycle_bin, item, &SCID_DATE_DELETED as *const _)?;

item_vec.push(TrashItem {
id,
name: name.into_string().map_err(|original| {
Error::ConvertOsString { original }
})?,
original_parent: PathBuf::from(orig_loc),
time_deleted: date_deleted,
});
}
return Ok(item_vec);
}
}


pub fn purge_all<I>(items: I) -> Result<(), Error>
where
I: IntoIterator<Item = TrashItem>,
{
todo!()
}

pub fn restore_all<I>(items: I) -> Result<(), Error>
where
I: IntoIterator<Item = TrashItem>,
{
todo!();
}

unsafe fn get_display_name(
psf: IShellFolder,
pidl: *mut ITEMIDLIST,
flags: _SHGDNF,
) -> Result<OsString, Error> {
let mut sr = MaybeUninit::<STRRET>::uninit();
return_err_on_fail! { psf.GetDisplayNameOf(pidl, flags.0 as u32, sr.as_mut_ptr()) };
let mut sr = sr.assume_init();
let mut name = MaybeUninit::<PWSTR>::uninit();
return_err_on_fail! { StrRetToStrW(&mut sr as *mut _, pidl, name.as_mut_ptr()) };
let name = name.assume_init();
let result = wstr_to_os_string(name);
CoTaskMemFree(name.0 as *mut c_void);
Ok(result)
}

unsafe fn wstr_to_os_string(wstr: PWSTR) -> OsString {
let mut len = 0;
while *(wstr.0.offset(len)) != 0 {
len += 1;
}
let wstr_slice = std::slice::from_raw_parts(wstr.0, len as usize);
OsString::from_wide(wstr_slice)
}


unsafe fn get_detail(
psf: &IShellFolder2,
pidl: *mut ITEMIDLIST,
pscid: *const PROPERTYKEY,
) -> Result<OsString, Error> {
let mut vt = MaybeUninit::<VARIANT>::uninit();
return_err_on_fail! { psf.GetDetailsEx(pidl, pscid, vt.as_mut_ptr()) };
let vt = vt.assume_init();
let mut vt = scopeguard::guard(vt, |mut vt| {
VariantClear(&mut vt as *mut _);
});
return_err_on_fail! {
VariantChangeType(vt.deref_mut() as *mut _, vt.deref_mut() as *mut _, 0, VARENUM::VT_BSTR.0 as u16)
};
let pstr = vt.Anonymous.Anonymous.Anonymous.bstrVal;
let result = Ok(wstr_to_os_string(PWSTR(pstr)));
return result;
}

unsafe fn get_date_unix(
psf: &IShellFolder2,
pidl: *mut ITEMIDLIST,
pscid: *const PROPERTYKEY,
) -> Result<i64, Error> {
let mut vt = MaybeUninit::<VARIANT>::uninit();
return_err_on_fail! { psf.GetDetailsEx(pidl, pscid, vt.as_mut_ptr()) };
let vt = vt.assume_init();
let mut vt = scopeguard::guard(vt, |mut vt| {
VariantClear(&mut vt as *mut _);
});
return_err_on_fail! {
VariantChangeType(vt.deref_mut() as *mut _, vt.deref_mut() as *mut _, 0, VARENUM::VT_DATE.0 as u16)
};
let date = vt.Anonymous.Anonymous.Anonymous.date;
let unix_time = variant_time_to_unix_time(date)?;
Ok(unix_time)
}

unsafe fn variant_time_to_unix_time(from: f64) -> Result<i64, Error> {
#[repr(C)]
#[derive(Clone, Copy)]
struct LargeIntegerParts {
low_part: u32,
high_part: u32,
}
#[repr(C)]
union LargeInteger {
parts: LargeIntegerParts,
whole: u64
}
let mut st = MaybeUninit::<SYSTEMTIME>::uninit();
if 0 == VariantTimeToSystemTime(from, st.as_mut_ptr()) {
return Err(Error::Unknown {
description: format!("`VariantTimeToSystemTime` indicated failure for the parameter {:?}", from)
});
}
let st = st.assume_init();
let mut ft = MaybeUninit::<FILETIME>::uninit();
if SystemTimeToFileTime(&st, ft.as_mut_ptr()) == false {
return Err(Error::Unknown {
description: format!("`SystemTimeToFileTime` failed with: {:?}", HRESULT::from_thread())
});
}
let ft = ft.assume_init();

let large_int = LargeInteger {
parts: LargeIntegerParts {
low_part: ft.dwLowDateTime,
high_part: ft.dwHighDateTime
}
};

// Applying assume init straight away because there's no explicit support to initialize struct
// fields one-by-one in an `MaybeUninit` as of Rust 1.39.0
// See: https://github.com/rust-lang/rust/blob/1.39.0/src/libcore/mem/maybe_uninit.rs#L170
// let mut uli = MaybeUninit::<ULARGE_INTEGER>::zeroed().assume_init();
// {
// let u_mut = uli.u_mut();
// u_mut.LowPart = ft.dwLowDateTime;
// u_mut.HighPart = std::mem::transmute(ft.dwHighDateTime);
// }
let windows_ticks: u64 = large_int.whole;
Ok(windows_ticks_to_unix_seconds(windows_ticks))
}

fn windows_ticks_to_unix_seconds(windows_ticks: u64) -> i64 {
// Fun fact: if my calculations are correct, then storing sucn ticks in an
// i64 can remain valid until about 6000 years from the very first tick
const WINDOWS_TICK: u64 = 10000000;
const SEC_TO_UNIX_EPOCH: i64 = 11644473600;
return (windows_ticks / WINDOWS_TICK) as i64 - SEC_TO_UNIX_EPOCH;
}

unsafe fn bind_to_csidl<T: Interface>(csidl: c_int) -> Result<T, Error> {
let mut pidl = MaybeUninit::<*mut ITEMIDLIST>::uninit();
return_err_on_fail! {
Expand Down

0 comments on commit 6e77795

Please sign in to comment.