cloud_filter/ext/
file.rs

1use std::{
2    fs::File,
3    mem,
4    ops::{Bound, RangeBounds},
5    os::windows::{io::AsRawHandle, prelude::RawHandle},
6};
7
8use widestring::U16CStr;
9use windows::{
10    core,
11    Win32::{
12        Foundation::HANDLE,
13        Storage::CloudFilters::{
14            self, CfDehydratePlaceholder, CF_SYNC_PROVIDER_STATUS, CF_SYNC_ROOT_STANDARD_INFO,
15        },
16    },
17};
18
19use crate::sealed::Sealed;
20
21/// An API extension to [File][std::fs::File].
22pub trait FileExt: AsRawHandle + Sealed {
23    /// Dehydrates a placeholder file.
24    fn dehydrate<T: RangeBounds<u64>>(&self, range: T) -> core::Result<()> {
25        dehydrate(self.as_raw_handle(), range, false)
26    }
27
28    /// Dehydrates a placeholder file as a system process running in the background. Otherwise, it
29    /// is called on behalf of a logged-in user.
30    fn background_dehydrate<T: RangeBounds<u64>>(&self, range: T) -> core::Result<()> {
31        dehydrate(self.as_raw_handle(), range, true)
32    }
33
34    /// Returns whether or not the handle is inside of a sync root.
35    fn in_sync_root() -> core::Result<bool> {
36        // TODO: this should use the uwp apis
37        todo!()
38    }
39}
40
41// TODO: is `CfDehydratePlaceholder` deprecated?
42// https://docs.microsoft.com/en-us/answers/questions/723805/what-is-the-behavior-of-file-ranges-in-different-p.html
43fn dehydrate<T: RangeBounds<u64>>(
44    handle: RawHandle,
45    range: T,
46    background: bool,
47) -> core::Result<()> {
48    unsafe {
49        CfDehydratePlaceholder(
50            HANDLE(handle),
51            match range.start_bound() {
52                Bound::Included(x) => *x as i64,
53                Bound::Excluded(x) => x.saturating_add(1) as i64,
54                Bound::Unbounded => 0,
55            },
56            match range.end_bound() {
57                Bound::Included(x) => *x as i64,
58                Bound::Excluded(x) => x.saturating_sub(1) as i64,
59                // This behavior is documented in CfDehydratePlaceholder
60                Bound::Unbounded => -1,
61            },
62            if background {
63                CloudFilters::CF_DEHYDRATE_FLAG_NONE
64            } else {
65                CloudFilters::CF_DEHYDRATE_FLAG_BACKGROUND
66            },
67            None,
68        )
69    }
70}
71
72impl FileExt for File {}
73
74impl Sealed for File {}
75
76/// Information about a sync root.
77#[derive(Debug)]
78pub struct SyncRootInfo {
79    data: Vec<u8>,
80    info: *const CF_SYNC_ROOT_STANDARD_INFO,
81}
82
83// TODO: most of the returns only have setters, no getters
84impl SyncRootInfo {
85    /// The file ID of the sync root.
86    pub fn file_id(&self) -> u64 {
87        unsafe { &*self.info }.SyncRootFileId as u64
88    }
89
90    // /// The hydration policy of the sync root.
91    // pub fn hydration_policy(&self) -> HydrationType {
92    //     unsafe { &*self.info }.HydrationPolicy.Primary.into()
93    // }
94
95    // /// The hydration type of the sync root.
96    // pub fn hydration_type(&self) -> HydrationPolicy {
97    //     unsafe { &*self.info }.HydrationPolicy.Modifier.into()
98    // }
99
100    // /// The population type of the sync root.
101    // pub fn population_type(&self) -> PopulationType {
102    //     unsafe { &*self.info }.PopulationPolicy.Primary.into()
103    // }
104
105    // /// The attributes supported by the sync root.
106    // pub fn supported_attributes(&self) -> SupportedAttributes {
107    //     unsafe { &*self.info }.InSyncPolicy.into()
108    // }
109
110    /// Whether or not hardlinks are allowed by the sync root.
111    pub fn hardlinks_allowed(&self) -> bool {
112        unsafe { &*self.info }.HardLinkPolicy == CloudFilters::CF_HARDLINK_POLICY_ALLOWED
113    }
114
115    /// The status of the sync provider.
116    pub fn status(&self) -> ProviderStatus {
117        unsafe { &*self.info }.ProviderStatus.into()
118    }
119
120    /// The name of the sync provider.
121    pub fn provider_name(&self) -> &U16CStr {
122        U16CStr::from_slice_truncate(unsafe { &*self.info }.ProviderName.as_slice()).unwrap()
123    }
124
125    /// The version of the sync provider.
126    pub fn version(&self) -> &U16CStr {
127        U16CStr::from_slice_truncate(unsafe { &*self.info }.ProviderVersion.as_slice()).unwrap()
128    }
129
130    /// The register blob associated with the sync root.
131    pub fn blob(&self) -> &[u8] {
132        &self.data[(mem::size_of::<CF_SYNC_ROOT_STANDARD_INFO>() + 1)..]
133    }
134}
135
136/// Sync provider status.
137#[derive(Debug, Clone, Copy)]
138pub enum ProviderStatus {
139    /// The sync provider is disconnected.
140    Disconnected,
141    /// The sync provider is idle.
142    Idle,
143    /// The sync provider is populating a namespace.
144    PopulateNamespace,
145    /// The sync provider is populating placeholder metadata.
146    PopulateMetadata,
147    /// The sync provider is incrementally syncing placeholder content.
148    PopulateContent,
149    /// The sync provider is incrementally syncing placeholder content.
150    SyncIncremental,
151    /// The sync provider has fully synced placeholder data.
152    SyncFull,
153    /// The sync provider has lost connectivity.
154    ConnectivityLost,
155    // TODO: if setting the sync status is added.
156    // ClearFlags,
157    /// The sync provider has been terminated.
158    Terminated,
159    /// The sync provider had an error.
160    Error,
161}
162
163impl From<CF_SYNC_PROVIDER_STATUS> for ProviderStatus {
164    fn from(status: CF_SYNC_PROVIDER_STATUS) -> Self {
165        match status {
166            CloudFilters::CF_PROVIDER_STATUS_DISCONNECTED => Self::Disconnected,
167            CloudFilters::CF_PROVIDER_STATUS_IDLE => Self::Idle,
168            CloudFilters::CF_PROVIDER_STATUS_POPULATE_NAMESPACE => Self::PopulateNamespace,
169            CloudFilters::CF_PROVIDER_STATUS_POPULATE_METADATA => Self::PopulateContent,
170            CloudFilters::CF_PROVIDER_STATUS_POPULATE_CONTENT => Self::PopulateContent,
171            CloudFilters::CF_PROVIDER_STATUS_SYNC_INCREMENTAL => Self::SyncIncremental,
172            CloudFilters::CF_PROVIDER_STATUS_SYNC_FULL => Self::SyncFull,
173            CloudFilters::CF_PROVIDER_STATUS_CONNECTIVITY_LOST => Self::ConnectivityLost,
174            // CloudFilters::CF_PROVIDER_STATUS_CLEAR_FLAGS => Self::ClearFlags,
175            CloudFilters::CF_PROVIDER_STATUS_TERMINATED => Self::Terminated,
176            CloudFilters::CF_PROVIDER_STATUS_ERROR => Self::Error,
177            _ => unreachable!(),
178        }
179    }
180}
181
182impl From<ProviderStatus> for CF_SYNC_PROVIDER_STATUS {
183    fn from(status: ProviderStatus) -> Self {
184        match status {
185            ProviderStatus::Disconnected => CloudFilters::CF_PROVIDER_STATUS_DISCONNECTED,
186            ProviderStatus::Idle => CloudFilters::CF_PROVIDER_STATUS_IDLE,
187            ProviderStatus::PopulateNamespace => {
188                CloudFilters::CF_PROVIDER_STATUS_POPULATE_NAMESPACE
189            }
190            ProviderStatus::PopulateMetadata => CloudFilters::CF_PROVIDER_STATUS_POPULATE_METADATA,
191            ProviderStatus::PopulateContent => CloudFilters::CF_PROVIDER_STATUS_POPULATE_CONTENT,
192            ProviderStatus::SyncIncremental => CloudFilters::CF_PROVIDER_STATUS_SYNC_INCREMENTAL,
193            ProviderStatus::SyncFull => CloudFilters::CF_PROVIDER_STATUS_SYNC_FULL,
194            ProviderStatus::ConnectivityLost => CloudFilters::CF_PROVIDER_STATUS_CONNECTIVITY_LOST,
195            // ProviderStatus::ClearFlags => CloudFilters::CF_PROVIDER_STATUS_CLEAR_FLAGS,
196            ProviderStatus::Terminated => CloudFilters::CF_PROVIDER_STATUS_TERMINATED,
197            ProviderStatus::Error => CloudFilters::CF_PROVIDER_STATUS_ERROR,
198        }
199    }
200}