cloud_filter/filter/
info.rs

1use std::{ffi::OsString, fmt::Debug, ops::Range, path::PathBuf};
2
3use nt_time::FileTime;
4use widestring::U16CStr;
5use windows::Win32::Storage::CloudFilters::{
6    self, CF_CALLBACK_DEHYDRATION_REASON, CF_CALLBACK_PARAMETERS_0_0, CF_CALLBACK_PARAMETERS_0_1,
7    CF_CALLBACK_PARAMETERS_0_10, CF_CALLBACK_PARAMETERS_0_11, CF_CALLBACK_PARAMETERS_0_2,
8    CF_CALLBACK_PARAMETERS_0_3, CF_CALLBACK_PARAMETERS_0_4, CF_CALLBACK_PARAMETERS_0_5,
9    CF_CALLBACK_PARAMETERS_0_6, CF_CALLBACK_PARAMETERS_0_7, CF_CALLBACK_PARAMETERS_0_8,
10    CF_CALLBACK_PARAMETERS_0_9,
11};
12
13/// Information for the [SyncFilter::fetch_data][crate::filter::SyncFilter::fetch_data] callback.
14pub struct FetchData(pub(crate) CF_CALLBACK_PARAMETERS_0_6);
15
16impl FetchData {
17    /// Whether or not the callback was called from an interrupted hydration.
18    pub fn interrupted_hydration(&self) -> bool {
19        (self.0.Flags & CloudFilters::CF_CALLBACK_FETCH_DATA_FLAG_RECOVERY).0 != 0
20    }
21
22    /// Whether or not the callback was called from an explicit hydration via
23    /// [Placeholder::hydrate][crate::placeholder::Placeholder::hydrate].
24    pub fn explicit_hydration(&self) -> bool {
25        (self.0.Flags & CloudFilters::CF_CALLBACK_FETCH_DATA_FLAG_EXPLICIT_HYDRATION).0 != 0
26    }
27
28    /// The amount of bytes that must be written to the placeholder.
29    pub fn required_file_range(&self) -> Range<u64> {
30        (self.0.RequiredFileOffset as u64)
31            ..(self.0.RequiredFileOffset + self.0.RequiredLength) as u64
32    }
33
34    /// The amount of bytes that must be written to the placeholder.
35    ///
36    /// If the sync provider prefer to give data in larger chunks, use this range instead.
37    ///
38    /// [Discussion](https://docs.microsoft.com/en-us/answers/questions/748214/what-is-fetchdataoptionalfileoffset-cfapi.html).
39    pub fn optional_file_range(&self) -> Range<u64> {
40        (self.0.OptionalFileOffset as u64)
41            ..(self.0.OptionalFileOffset + self.0.OptionalLength) as u64
42    }
43
44    /// The last time the file was dehydrated.
45    pub fn last_dehydration_time(&self) -> FileTime {
46        self.0.LastDehydrationTime.try_into().unwrap()
47    }
48
49    /// The reason the file was last dehydrated.
50    pub fn last_dehydration_reason(&self) -> Option<DehydrationReason> {
51        DehydrationReason::from_win32(self.0.LastDehydrationReason)
52    }
53}
54
55impl Debug for FetchData {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        f.debug_struct("FetchData")
58            .field("interrupted_hydration", &self.interrupted_hydration())
59            .field("required_file_range", &self.required_file_range())
60            .field("optional_file_range", &self.optional_file_range())
61            .field("last_dehydration_time", &self.last_dehydration_time())
62            .field("last_dehydration_reason", &self.last_dehydration_reason())
63            .finish()
64    }
65}
66
67/// Information for the [SyncFilter::cancel_fetch_data][crate::filter::SyncFilter::cancel_fetch_data] callback.
68pub struct CancelFetchData(pub(crate) CF_CALLBACK_PARAMETERS_0_0);
69
70impl CancelFetchData {
71    /// Whether or not the callback failed as a result of the 60 second timeout.
72    pub fn timeout(&self) -> bool {
73        (self.0.Flags & CloudFilters::CF_CALLBACK_CANCEL_FLAG_IO_TIMEOUT).0 != 0
74    }
75
76    /// The user has cancelled the request manually.
77    ///
78    /// A user could cancel a request through a download toast?
79    pub fn user_cancelled(&self) -> bool {
80        (self.0.Flags & CloudFilters::CF_CALLBACK_CANCEL_FLAG_IO_ABORTED).0 != 0
81    }
82
83    /// The range of the file data that is no longer required.
84    pub fn file_range(&self) -> Range<u64> {
85        let range = unsafe { self.0.Anonymous.FetchData };
86        (range.FileOffset as u64)..(range.FileOffset + range.Length) as u64
87    }
88}
89
90impl Debug for CancelFetchData {
91    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92        f.debug_struct("CancelFetchData")
93            .field("timeout", &self.timeout())
94            .field("user_cancelled", &self.user_cancelled())
95            .field("file_range", &self.file_range())
96            .finish()
97    }
98}
99
100/// Information for the [SyncFilter::validate_data][crate::filter::SyncFilter::validate_data] callback.
101pub struct ValidateData(pub(crate) CF_CALLBACK_PARAMETERS_0_11);
102
103impl ValidateData {
104    /// Whether or not the callback failed as a result of the 60 second timeout.
105    pub fn explicit_hydration(&self) -> bool {
106        (self.0.Flags & CloudFilters::CF_CALLBACK_VALIDATE_DATA_FLAG_EXPLICIT_HYDRATION).0 != 0
107    }
108
109    /// The range of data to validate.
110    pub fn file_range(&self) -> Range<u64> {
111        (self.0.RequiredFileOffset as u64)
112            ..(self.0.RequiredFileOffset + self.0.RequiredLength) as u64
113    }
114}
115
116impl Debug for ValidateData {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        f.debug_struct("ValidateData")
119            .field("explicit_hydration", &self.explicit_hydration())
120            .field("file_range", &self.file_range())
121            .finish()
122    }
123}
124
125/// Information for the [SyncFilter::fetch_placeholders][crate::filter::SyncFilter::fetch_placeholders]
126/// callback.
127pub struct FetchPlaceholders(pub(crate) CF_CALLBACK_PARAMETERS_0_7);
128
129impl FetchPlaceholders {
130    /// A glob pattern specifying the files that should be fetched.
131    ///
132    /// This field is completely optional and does not have to be respected.
133    #[cfg(feature = "globs")]
134    pub fn pattern(&self) -> Result<globset::Glob, globset::Error> {
135        let pattern = unsafe { U16CStr::from_ptr_str(self.0.Pattern.0) }.to_string_lossy();
136        globset::Glob::new(&pattern)
137    }
138
139    /// A glob pattern specifying the files that should be fetched.
140    ///
141    /// This field is completely optional and does not have to be respected.
142    #[cfg(not(feature = "globs"))]
143    pub fn pattern(&self) -> String {
144        unsafe { U16CStr::from_ptr_str(self.0.Pattern.0) }.to_string_lossy()
145    }
146}
147
148impl Debug for FetchPlaceholders {
149    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150        f.debug_struct("FetchPlaceholders")
151            .field("pattern", &self.pattern())
152            .finish()
153    }
154}
155
156/// Information for the
157/// [SyncFilter::cancel_fetch_placeholders][super::SyncFilter::cancel_fetch_placeholders] callback.
158pub struct CancelFetchPlaceholders(pub(crate) CF_CALLBACK_PARAMETERS_0_0);
159
160impl CancelFetchPlaceholders {
161    /// Whether or not the callback failed as a result of the 60 second timeout.
162    ///
163    // Read more [here][crate::filter::Request::reset_timeout].
164    pub fn timeout(&self) -> bool {
165        (self.0.Flags & CloudFilters::CF_CALLBACK_CANCEL_FLAG_IO_TIMEOUT).0 != 0
166    }
167
168    /// The user has cancelled the request manually.
169    ///
170    /// A user could cancel a request through a download toast?
171    pub fn user_cancelled(&self) -> bool {
172        (self.0.Flags & CloudFilters::CF_CALLBACK_CANCEL_FLAG_IO_ABORTED).0 != 0
173    }
174}
175
176impl Debug for CancelFetchPlaceholders {
177    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178        f.debug_struct("CancelFetchPlaceholders")
179            .field("timeout", &self.timeout())
180            .field("user_cancelled", &self.user_cancelled())
181            .finish()
182    }
183}
184
185/// Information for the [SyncFilter::opened][super::SyncFilter::opened] callback.
186pub struct Opened(pub(crate) CF_CALLBACK_PARAMETERS_0_8);
187
188impl Opened {
189    /// The placeholder metadata is corrupt.
190    pub fn metadata_corrupt(&self) -> bool {
191        (self.0.Flags & CloudFilters::CF_CALLBACK_OPEN_COMPLETION_FLAG_PLACEHOLDER_UNKNOWN).0 != 0
192    }
193
194    /// The placeholder metadata is not supported.
195    pub fn metadata_unsupported(&self) -> bool {
196        (self.0.Flags & CloudFilters::CF_CALLBACK_OPEN_COMPLETION_FLAG_PLACEHOLDER_UNSUPPORTED).0
197            != 0
198    }
199}
200
201impl Debug for Opened {
202    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203        f.debug_struct("Opened")
204            .field("metadata_corrupt", &self.metadata_corrupt())
205            .field("metadata_unsupported", &self.metadata_unsupported())
206            .finish()
207    }
208}
209
210/// Information for the [SyncFilter::closed][super::SyncFilter::closed] callback.
211pub struct Closed(pub(crate) CF_CALLBACK_PARAMETERS_0_1);
212
213impl Closed {
214    /// Whether or not the placeholder was deleted as a result of the close.
215    pub fn deleted(&self) -> bool {
216        (self.0.Flags & CloudFilters::CF_CALLBACK_CLOSE_COMPLETION_FLAG_DELETED).0 != 0
217    }
218}
219
220impl Debug for Closed {
221    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222        f.debug_struct("Closed")
223            .field("deleted", &self.deleted())
224            .finish()
225    }
226}
227
228/// Information for the [SyncFilter::dehydrate][super::SyncFilter::dehydrate] callback.
229pub struct Dehydrate(pub(crate) CF_CALLBACK_PARAMETERS_0_3);
230
231impl Dehydrate {
232    /// Whether or not the callback was called from a system background service.
233    pub fn background(&self) -> bool {
234        (self.0.Flags & CloudFilters::CF_CALLBACK_DEHYDRATE_FLAG_BACKGROUND).0 != 0
235    }
236
237    /// The reason the file is being dehydrated.
238    pub fn reason(&self) -> Option<DehydrationReason> {
239        DehydrationReason::from_win32(self.0.Reason)
240    }
241}
242
243impl Debug for Dehydrate {
244    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245        f.debug_struct("Dehydrate")
246            .field("background", &self.background())
247            .field("reason", &self.reason())
248            .finish()
249    }
250}
251
252/// Information for the [SyncFilter::dehydrated][super::SyncFilter::dehydrated] callback.
253pub struct Dehydrated(pub(crate) CF_CALLBACK_PARAMETERS_0_2);
254
255impl Dehydrated {
256    /// Whether or not the callback was called from a system background service.
257    pub fn background(&self) -> bool {
258        (self.0.Flags & CloudFilters::CF_CALLBACK_DEHYDRATE_COMPLETION_FLAG_BACKGROUND).0 != 0
259    }
260
261    /// Whether or not the placeholder was already hydrated.
262    pub fn already_hydrated(&self) -> bool {
263        (self.0.Flags & CloudFilters::CF_CALLBACK_DEHYDRATE_COMPLETION_FLAG_DEHYDRATED).0 != 0
264    }
265
266    /// The reason the file is being dehydrated.
267    pub fn reason(&self) -> Option<DehydrationReason> {
268        DehydrationReason::from_win32(self.0.Reason)
269    }
270}
271
272impl Debug for Dehydrated {
273    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
274        f.debug_struct("Dehydrated")
275            .field("background", &self.background())
276            .field("already_hydrated", &self.already_hydrated())
277            .field("reason", &self.reason())
278            .finish()
279    }
280}
281
282/// Information for the [SyncFilter::delete][super::SyncFilter::delete] callback.
283pub struct Delete(pub(crate) CF_CALLBACK_PARAMETERS_0_5);
284
285impl Delete {
286    /// Whether or not the placeholder being deleted is a directory.
287    pub fn is_directory(&self) -> bool {
288        (self.0.Flags & CloudFilters::CF_CALLBACK_DELETE_FLAG_IS_DIRECTORY).0 != 0
289    }
290
291    // TODO: missing docs
292    /// The placeholder is being undeleted.
293    pub fn is_undelete(&self) -> bool {
294        (self.0.Flags & CloudFilters::CF_CALLBACK_DELETE_FLAG_IS_UNDELETE).0 != 0
295    }
296}
297
298impl Debug for Delete {
299    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
300        f.debug_struct("Delete")
301            .field("is_directory", &self.is_directory())
302            .field("is_undelete", &self.is_undelete())
303            .finish()
304    }
305}
306
307/// Information for the [SyncFilter::deleted][crate::filter::SyncFilter::deleted] callback.
308#[derive(Debug)]
309#[allow(dead_code)]
310pub struct Deleted(pub(crate) CF_CALLBACK_PARAMETERS_0_4);
311
312/// Information for the [SyncFilter::rename][crate::filter::SyncFilter::rename] callback.
313pub struct Rename(pub(crate) CF_CALLBACK_PARAMETERS_0_10, pub(crate) OsString);
314
315impl Rename {
316    /// Whether or not the placeholder being renamed is a directory.
317    pub fn is_directory(&self) -> bool {
318        (self.0.Flags & CloudFilters::CF_CALLBACK_RENAME_FLAG_IS_DIRECTORY).0 != 0
319    }
320
321    /// Whether or not the placeholder was originally in the sync root.
322    pub fn source_in_scope(&self) -> bool {
323        (self.0.Flags & CloudFilters::CF_CALLBACK_RENAME_FLAG_SOURCE_IN_SCOPE).0 != 0
324    }
325
326    /// Whether or not the placeholder is being moved inside the sync root.
327    pub fn target_in_scope(&self) -> bool {
328        (self.0.Flags & CloudFilters::CF_CALLBACK_RENAME_FLAG_TARGET_IN_SCOPE).0 != 0
329    }
330
331    /// The full path the placeholder is being moved to.
332    pub fn target_path(&self) -> PathBuf {
333        let mut path = PathBuf::from(&self.1);
334        path.push(unsafe { U16CStr::from_ptr_str(self.0.TargetPath.0) }.to_os_string());
335        path
336    }
337}
338
339impl Debug for Rename {
340    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
341        f.debug_struct("Rename")
342            .field("is_directory", &self.is_directory())
343            .field("source_in_scope", &self.source_in_scope())
344            .field("target_in_scope", &self.target_in_scope())
345            .field("target_path", &self.target_path())
346            .finish()
347    }
348}
349
350/// Information for the [SyncFilter::renamed][crate::filter::SyncFilter::renamed] callback.
351pub struct Renamed(pub(crate) CF_CALLBACK_PARAMETERS_0_9, pub(crate) OsString);
352
353impl Renamed {
354    /// The full path the placeholder has been moved from.
355    pub fn source_path(&self) -> PathBuf {
356        let mut path = PathBuf::from(&self.1);
357        path.push(unsafe { U16CStr::from_ptr_str(self.0.SourcePath.0) }.to_os_string());
358        path
359    }
360}
361
362impl Debug for Renamed {
363    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
364        f.debug_struct("Renamed")
365            .field("source_path", &self.source_path())
366            .finish()
367    }
368}
369
370/// The reason a placeholder has been dehydrated.
371#[derive(Debug, Clone, Copy, PartialEq, Eq)]
372pub enum DehydrationReason {
373    /// The user manually dehydrated the placeholder.
374    UserManually,
375    /// The operating system automatically dehydrated the placeholder due to low disk space on the
376    /// volume.
377    LowSpace,
378    /// The operating system automatically dehydrated the placeholder due to low activity.
379    ///
380    /// This is based on the Windows Storage Sense settings.
381    Inactive,
382    /// The operating system automatically dehydrated this file to make room for an operating
383    /// system upgrade.
384    OsUpgrade,
385}
386
387impl DehydrationReason {
388    fn from_win32(reason: CF_CALLBACK_DEHYDRATION_REASON) -> Option<DehydrationReason> {
389        match reason {
390            CloudFilters::CF_CALLBACK_DEHYDRATION_REASON_USER_MANUAL => Some(Self::UserManually),
391            CloudFilters::CF_CALLBACK_DEHYDRATION_REASON_SYSTEM_LOW_SPACE => Some(Self::LowSpace),
392            CloudFilters::CF_CALLBACK_DEHYDRATION_REASON_SYSTEM_INACTIVITY => Some(Self::Inactive),
393            CloudFilters::CF_CALLBACK_DEHYDRATION_REASON_SYSTEM_OS_UPGRADE => Some(Self::OsUpgrade),
394            _ => None,
395        }
396    }
397}