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