cloud_filter/
placeholder_file.rs

1use std::{path::Path, ptr, slice};
2
3use widestring::U16CString;
4use windows::{
5    core::{self, PCWSTR},
6    Win32::{
7        Foundation,
8        Storage::CloudFilters::{self, CfCreatePlaceholders, CF_PLACEHOLDER_CREATE_INFO},
9    },
10};
11
12use crate::{metadata::Metadata, sealed, usn::Usn};
13
14/// A builder for creating new placeholder files/directories.
15#[derive(Debug)]
16pub struct PlaceholderFile(CF_PLACEHOLDER_CREATE_INFO);
17
18impl PlaceholderFile {
19    /// Creates a new [PlaceholderFile].
20    pub fn new(relative_path: impl AsRef<Path>) -> Self {
21        Self(CF_PLACEHOLDER_CREATE_INFO {
22            RelativeFileName: PCWSTR(
23                U16CString::from_os_str(relative_path.as_ref())
24                    .unwrap()
25                    .into_raw(),
26            ),
27            Flags: CloudFilters::CF_PLACEHOLDER_CREATE_FLAG_NONE,
28            Result: Foundation::S_FALSE,
29            ..Default::default()
30        })
31    }
32
33    /// Marks this [PlaceholderFile] as having no child placeholders on
34    /// creation.
35    ///
36    /// Only applicable to placeholder directories.
37    pub fn has_no_children(mut self) -> Self {
38        self.0.Flags |= CloudFilters::CF_PLACEHOLDER_CREATE_FLAG_DISABLE_ON_DEMAND_POPULATION;
39        self
40    }
41
42    /// Marks a placeholder as in sync.
43    ///
44    /// See also
45    /// [SetInSyncState](https://learn.microsoft.com/en-us/windows/win32/api/cfapi/nf-cfapi-cfsetinsyncstate),
46    /// [What does "In-Sync" Mean?](https://www.userfilesystem.com/programming/faq/#nav_whatdoesin-syncmean)
47    pub fn mark_in_sync(mut self) -> Self {
48        self.0.Flags |= CloudFilters::CF_PLACEHOLDER_CREATE_FLAG_MARK_IN_SYNC;
49        self
50    }
51
52    /// Whether or not to overwrite an existing placeholder.
53    pub fn overwrite(mut self) -> Self {
54        self.0.Flags |= CloudFilters::CF_PLACEHOLDER_CREATE_FLAG_SUPERSEDE;
55        self
56    }
57
58    /// Blocks this placeholder file from being dehydrated.
59    ///
60    /// This flag does not work on directories.
61    pub fn block_dehydration(mut self) -> Self {
62        self.0.Flags |= CloudFilters::CF_PLACEHOLDER_CREATE_FLAG_ALWAYS_FULL;
63        self
64    }
65
66    /// The metadata for the [PlaceholderFile].
67    pub fn metadata(mut self, metadata: Metadata) -> Self {
68        self.0.FsMetadata = metadata.0;
69        self
70    }
71
72    /// A buffer of bytes stored with the file that could be accessed through a
73    /// [Request::file_blob][crate::filter::Request::file_blob] or
74    /// [Placeholder::info][crate::placeholder::Placeholder::info].
75    ///
76    /// The buffer must not exceed
77    /// [4KiB](https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/Storage/CloudFilters/constant.CF_PLACEHOLDER_MAX_FILE_IDENTITY_LENGTH.html).
78    pub fn blob(mut self, blob: Vec<u8>) -> Self {
79        assert!(
80            blob.len() <= CloudFilters::CF_PLACEHOLDER_MAX_FILE_IDENTITY_LENGTH as usize,
81            "blob size must not exceed {} bytes, got {} bytes",
82            CloudFilters::CF_PLACEHOLDER_MAX_FILE_IDENTITY_LENGTH,
83            blob.len()
84        );
85
86        if blob.is_empty() {
87            self.0.FileIdentity = ptr::null();
88            self.0.FileIdentityLength = 0;
89            return self;
90        }
91
92        let leaked_blob = Box::leak(blob.into_boxed_slice());
93        self.0.FileIdentity = leaked_blob.as_ptr() as *const _;
94        self.0.FileIdentityLength = leaked_blob.len() as _;
95
96        self
97    }
98
99    pub fn result(&self) -> core::Result<Usn> {
100        self.0.Result.ok().map(|_| self.0.CreateUsn as _)
101    }
102
103    /// Creates a placeholder file/directory on the file system.
104    ///
105    /// The value returned is the final [Usn] after the placeholder is created.
106    ///
107    /// It is recommended to use this function over
108    /// [Placeholder][crate::placeholder::Placeholder::convert_to_placeholder] for efficiency purposes. If you
109    /// need to create multiple placeholders, consider using [BatchCreate::create].
110    ///
111    /// If you need to create placeholders from the
112    /// [SyncFilter::fetch_placeholders][crate::filter::SyncFilter::fetch_placeholders] callback,
113    /// do not use this method. Instead, use
114    /// [FetchPlaceholders::pass_with_placeholder][crate::filter::ticket::FetchPlaceholders::pass_with_placeholder].
115    pub fn create<P: AsRef<Path>>(self, parent: impl AsRef<Path>) -> core::Result<Usn> {
116        unsafe {
117            CfCreatePlaceholders(
118                PCWSTR(U16CString::from_os_str(parent.as_ref()).unwrap().as_ptr()),
119                &mut [self.0],
120                CloudFilters::CF_CREATE_FLAG_NONE,
121                None,
122            )?;
123        }
124
125        self.result()
126    }
127}
128
129impl Drop for PlaceholderFile {
130    fn drop(&mut self) {
131        // Safety: `self.0.RelativeFileName.0` is a valid pointer to a valid UTF-16 string
132        drop(unsafe { U16CString::from_ptr_str(self.0.RelativeFileName.0) });
133
134        if !self.0.FileIdentity.is_null() {
135            // Safety: `self.0.FileIdentity` is a valid pointer to a valid slice
136            drop(unsafe {
137                Box::from_raw(slice::from_raw_parts_mut(
138                    self.0.FileIdentity as *mut u8,
139                    self.0.FileIdentityLength as _,
140                ))
141            });
142        }
143    }
144}
145
146/// Creates multiple placeholder file/directories within the given path.
147pub trait BatchCreate: sealed::Sealed {
148    fn create<P: AsRef<Path>>(&mut self, path: P) -> core::Result<()>;
149}
150
151impl BatchCreate for [PlaceholderFile] {
152    fn create<P: AsRef<Path>>(&mut self, path: P) -> core::Result<()> {
153        unsafe {
154            CfCreatePlaceholders(
155                PCWSTR(U16CString::from_os_str(path.as_ref()).unwrap().as_ptr()),
156                slice::from_raw_parts_mut(self.as_mut_ptr() as *mut _, self.len()),
157                CloudFilters::CF_CREATE_FLAG_NONE,
158                None,
159            )
160        }
161    }
162}
163
164impl sealed::Sealed for [PlaceholderFile] {}