1use std::ffi::c_void;
4use std::path::Path;
5use std::{
6 io,
7 ptr,
8};
9
10use num_enum::IntoPrimitive;
11use windows::Win32::Foundation::HANDLE;
12use windows::Win32::Storage::FileSystem::{
13 COPY_FILE_COPY_SYMLINK,
14 COPY_FILE_FAIL_IF_EXISTS,
15 COPYPROGRESSROUTINE_PROGRESS,
16 CopyFileExW,
17 LPPROGRESS_ROUTINE,
18 LPPROGRESS_ROUTINE_CALLBACK_REASON,
19 MOVEFILE_COPY_ALLOWED,
20 MOVEFILE_WRITE_THROUGH,
21 MoveFileWithProgressW,
22 PROGRESS_CANCEL,
23 PROGRESS_CONTINUE,
24 PROGRESS_QUIET,
25 PROGRESS_STOP,
26};
27
28use crate::internal::catch_unwind_and_abort;
29use crate::string::{
30 ZeroTerminatedWideString,
31 max_path_extend,
32};
33
34#[derive(Clone, Debug)]
41#[repr(transparent)]
42pub struct ProgressCallback<F>(Option<F>);
43
44impl<F> ProgressCallback<F>
45where
46 F: FnMut(ProgressStatus) -> ProgressRetVal,
47{
48 pub fn new(value: F) -> Self {
49 ProgressCallback(Some(value))
51 }
52
53 fn typed_raw_progress_callback(&self) -> LPPROGRESS_ROUTINE {
54 if self.0.is_some() {
55 Some(transfer_internal_callback::<F> as _)
56 } else {
57 None
58 }
59 }
60
61 fn as_raw_lpdata(&mut self) -> Option<*const c_void> {
62 self.0
63 .as_mut()
64 .map(|callback| ptr::from_mut::<F>(callback).cast_const().cast::<c_void>())
65 }
66}
67
68impl Default for ProgressCallback<fn(ProgressStatus) -> ProgressRetVal> {
69 fn default() -> Self {
70 Self(None)
71 }
72}
73
74#[derive(Copy, Clone, PartialEq, Eq, Debug)]
76pub struct ProgressStatus {
77 pub total_file_bytes: u64,
79 pub total_transferred_bytes: u64,
81}
82
83#[derive(IntoPrimitive, Copy, Clone, Eq, PartialEq, Default, Debug)]
85#[repr(u32)]
86pub enum ProgressRetVal {
87 #[default]
89 Continue = PROGRESS_CONTINUE.0,
90 Stop = PROGRESS_STOP.0,
92 Cancel = PROGRESS_CANCEL.0,
94 Quiet = PROGRESS_QUIET.0,
96}
97
98impl From<ProgressRetVal> for COPYPROGRESSROUTINE_PROGRESS {
99 fn from(value: ProgressRetVal) -> Self {
100 COPYPROGRESSROUTINE_PROGRESS(u32::from(value))
101 }
102}
103
104pub trait PathExt: AsRef<Path> {
106 fn copy_file_to<Q, F>(
116 &self,
117 new_path: Q,
118 mut progress_callback: ProgressCallback<F>,
119 ) -> io::Result<()>
120 where
121 Q: AsRef<Path>,
122 F: FnMut(ProgressStatus) -> ProgressRetVal,
123 {
124 let source =
125 ZeroTerminatedWideString::from_os_str(max_path_extend(self.as_ref().as_os_str()));
126 let target =
127 ZeroTerminatedWideString::from_os_str(max_path_extend(new_path.as_ref().as_os_str()));
128 unsafe {
129 CopyFileExW(
130 source.as_raw_pcwstr(),
131 target.as_raw_pcwstr(),
132 progress_callback.typed_raw_progress_callback(),
133 progress_callback.as_raw_lpdata(),
134 None,
135 COPY_FILE_COPY_SYMLINK | COPY_FILE_FAIL_IF_EXISTS,
136 )?;
137 }
138 Ok(())
139 }
140
141 fn move_to<Q, F>(
154 &self,
155 new_path: Q,
156 mut progress_callback: ProgressCallback<F>,
157 ) -> io::Result<()>
158 where
159 Q: AsRef<Path>,
160 F: FnMut(ProgressStatus) -> ProgressRetVal,
161 {
162 let source =
163 ZeroTerminatedWideString::from_os_str(max_path_extend(self.as_ref().as_os_str()));
164 let target =
165 ZeroTerminatedWideString::from_os_str(max_path_extend(new_path.as_ref().as_os_str()));
166 unsafe {
167 MoveFileWithProgressW(
168 source.as_raw_pcwstr(),
169 target.as_raw_pcwstr(),
170 progress_callback.typed_raw_progress_callback(),
171 progress_callback.as_raw_lpdata(),
172 MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH,
173 )?;
174 }
175 Ok(())
176 }
177}
178
179impl<T: AsRef<Path>> PathExt for T {}
180
181unsafe extern "system" fn transfer_internal_callback<F>(
182 totalfilesize: i64,
183 totalbytestransferred: i64,
184 _streamsize: i64,
185 _streambytestransferred: i64,
186 _dwstreamnumber: u32,
187 _dwcallbackreason: LPPROGRESS_ROUTINE_CALLBACK_REASON,
188 _hsourcefile: HANDLE,
189 _hdestinationfile: HANDLE,
190 lpdata: *const c_void,
191) -> COPYPROGRESSROUTINE_PROGRESS
192where
193 F: FnMut(ProgressStatus) -> ProgressRetVal,
194{
195 let call = move || {
196 let user_callback: &mut F = unsafe { &mut *(lpdata.cast_mut().cast::<F>()) };
197 user_callback(ProgressStatus {
198 total_file_bytes: totalfilesize.try_into().unwrap_or_else(|_| unreachable!()),
199 total_transferred_bytes: totalbytestransferred
200 .try_into()
201 .unwrap_or_else(|_| unreachable!()),
202 })
203 };
204 catch_unwind_and_abort(call).into()
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn check_transfer_internal_callback() -> io::Result<()> {
213 let target_progress_status = ProgressStatus {
214 total_file_bytes: 1,
215 total_transferred_bytes: 1,
216 };
217 let progress_ret_val = ProgressRetVal::Stop;
218 let mut progress_callback = ProgressCallback::new(|progress_status| {
219 assert_eq!(progress_status, target_progress_status);
220 progress_ret_val
221 });
222 let raw_progress_callback = progress_callback
223 .typed_raw_progress_callback()
224 .unwrap_or_else(|| unreachable!());
225 let raw_call_result = unsafe {
226 raw_progress_callback(
227 target_progress_status
228 .total_file_bytes
229 .try_into()
230 .unwrap_or_else(|_| unreachable!()),
231 target_progress_status
232 .total_transferred_bytes
233 .try_into()
234 .unwrap_or_else(|_| unreachable!()),
235 Default::default(),
236 Default::default(),
237 Default::default(),
238 Default::default(),
239 Default::default(),
240 Default::default(),
241 progress_callback
242 .as_raw_lpdata()
243 .unwrap_or_else(|| unreachable!()),
244 )
245 };
246 assert_eq!(raw_call_result, progress_ret_val.into());
247 Ok(())
248 }
249}