diff --git a/PInvoke/CldApi/cfapi.Funcs.cs b/PInvoke/CldApi/cfapi.Funcs.cs
index 5d22fa54..4ea6b1ff 100644
--- a/PInvoke/CldApi/cfapi.Funcs.cs
+++ b/PInvoke/CldApi/cfapi.Funcs.cs
@@ -551,7 +551,7 @@ namespace Vanara.PInvoke
// FileHandle, LARGE_INTEGER StartingOffset, LARGE_INTEGER Length, CF_HYDRATE_FLAGS HydrateFlags, LPOVERLAPPED Overlapped );
[DllImport(Lib.CldApi, SetLastError = false, ExactSpelling = true)]
[PInvokeData("cfapi.h", MSDNShortId = "4FFD7580-BF59-48D0-B6D7-516559914096")]
- public static extern HRESULT CfHydratePlaceholder(HFILE FileHandle, long StartingOffset, long Length, CF_HYDRATE_FLAGS HydrateFlags, [In, Out, Optional] IntPtr Overlapped);
+ public static extern HRESULT CfHydratePlaceholder(HFILE FileHandle, long StartingOffset = 0, long Length = -1, CF_HYDRATE_FLAGS HydrateFlags = 0, [In, Out] IntPtr Overlapped = default);
///
/// Hydrates a placeholder file by ensuring that the specified byte range is present on-disk in the placeholder. This is valid for
diff --git a/PInvoke/CldApi/cfapi.cs b/PInvoke/CldApi/cfapi.cs
index 524fc1d4..620912d5 100644
--- a/PInvoke/CldApi/cfapi.cs
+++ b/PInvoke/CldApi/cfapi.cs
@@ -1348,7 +1348,7 @@ namespace Vanara.PInvoke
///
public uint ParamSize;
- private uint pad;
+ private readonly uint pad;
///
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 56)]
@@ -1395,8 +1395,8 @@ namespace Vanara.PInvoke
/// The requested structure.
public T GetParam() where T : struct
{
- using var ptr = new SafeHGlobalHandle(Content);
- return ptr.ToStructure();
+ using var ptr = new PinnedObject(Content);
+ return ((IntPtr)ptr).ToStructure();
}
/// Contains callback specific parameters such as file offset, length, flags, etc.
@@ -1607,7 +1607,7 @@ namespace Vanara.PInvoke
[StructLayout(LayoutKind.Sequential)]
public struct CF_CONNECTION_KEY
{
- private long Internal;
+ private readonly long Internal;
}
/// Specifies a range of data in a placeholder file.
@@ -1730,12 +1730,12 @@ namespace Vanara.PInvoke
public uint ParamSize;
// Yes, this is strange, but needed to deal with struct size changing based on pointer size (40/48).
- private uint pad4_8;
- private ulong pad8_16;
- private ulong pad16_24;
- private ulong pad24_32;
- private IntPtr padp1;
- private IntPtr padp2;
+ private readonly uint pad4_8;
+ private readonly ulong pad8_16;
+ private readonly ulong pad16_24;
+ private readonly ulong pad24_32;
+ private readonly IntPtr padp1;
+ private readonly IntPtr padp2;
///
public TRANSFERDATA TransferData { get => GetParam(); set => SetParam(value); }
@@ -1999,6 +1999,27 @@ namespace Vanara.PInvoke
/// The final USN value after create actions are performed.
public int CreateUsn;
+
+ ///
+ /// Initializes a new instance of the struct with info from a file.
+ ///
+ /// The file information.
+ public CF_PLACEHOLDER_CREATE_INFO(System.IO.FileInfo fileInfo) : this()
+ {
+ RelativeFileName = fileInfo.FullName;
+ FsMetadata = new CF_FS_METADATA
+ {
+ FileSize = fileInfo.Length,
+ BasicInfo = new FILE_BASIC_INFO
+ {
+ FileAttributes = (FileFlagsAndAttributes)fileInfo.Attributes,
+ CreationTime = fileInfo.CreationTime.ToFileTimeStruct(),
+ LastWriteTime = fileInfo.LastWriteTime.ToFileTimeStruct(),
+ LastAccessTime = fileInfo.LastAccessTime.ToFileTimeStruct(),
+ ChangeTime = fileInfo.LastWriteTime.ToFileTimeStruct()
+ }
+ };
+ }
}
/// Standard placeholder information.
@@ -2130,7 +2151,7 @@ namespace Vanara.PInvoke
[StructLayout(LayoutKind.Sequential)]
public struct CF_REQUEST_KEY
{
- private long Internal;
+ private readonly long Internal;
}
/// Defines the sync policies used by a sync root.
@@ -2323,7 +2344,7 @@ namespace Vanara.PInvoke
[StructLayout(LayoutKind.Sequential)]
public struct CF_TRANSFER_KEY
{
- private long Internal;
+ private readonly long Internal;
}
}
}
\ No newline at end of file
diff --git a/UnitTests/PInvoke/CldApi/CldApiTests.cs b/UnitTests/PInvoke/CldApi/CldApiTests.cs
index 9ed04b71..5b5b4138 100644
--- a/UnitTests/PInvoke/CldApi/CldApiTests.cs
+++ b/UnitTests/PInvoke/CldApi/CldApiTests.cs
@@ -35,17 +35,11 @@ namespace Vanara.PInvoke.Tests
var destDirPath = SetupTempDir(dest);
try
{
- using var csp = new CloudSyncProvider(destDirPath, "TestSync");
- csp.Status = CF_SYNC_PROVIDER_STATUS.CF_PROVIDER_STATUS_IDLE;
+ using var csp = new CloudSyncProvider(destDirPath, "TestSync") { Status = CF_SYNC_PROVIDER_STATUS.CF_PROVIDER_STATUS_IDLE };
csp.Status.WriteValues();
const string desc = "SyncStatus is good.";
- uint descLen = (uint)(desc.Length + 1) * 2;
- var ss = new CF_SYNC_STATUS { StructSize = (uint)Marshal.SizeOf() + descLen, Code = 1, DescriptionLength = descLen };
- var mem = new SafeHGlobalHandle(Marshal.SizeOf() + descLen);
- mem.Write(ss);
- StringHelper.Write(desc, ((IntPtr)mem).Offset(Marshal.SizeOf()), out _, true, CharSet.Unicode, descLen);
- Assert.That(CfReportSyncStatus(destDirPath, mem), ResultIs.Successful);
+ Assert.That(() => csp.ReportSyncStatus(desc, 1), Throws.Nothing);
}
finally
{
@@ -64,6 +58,7 @@ namespace Vanara.PInvoke.Tests
{
var tokSrc = new CancellationTokenSource();
var token = tokSrc.Token;
+ var evt = new ManualResetEventSlim();
var task = Task.Run(() =>
{
using var csp = new CloudSyncProvider(destDirPath, "TestSync");
@@ -88,8 +83,8 @@ namespace Vanara.PInvoke.Tests
Assert.That(new FileInfo(fpath).Length, Is.EqualTo(origFileInfo.Length));
Debug.WriteLine("CSP is running...................................\n");
-
- while (!token.IsCancellationRequested) { Thread.Sleep(100); }
+ evt.Set();
+ while (!token.IsCancellationRequested) { Task.Delay(100); }
static void ShowInfo(object s, CloudSyncCallbackArgs e) where T : struct
{
@@ -124,16 +119,17 @@ namespace Vanara.PInvoke.Tests
}
}, tokSrc.Token);
- Thread.Sleep(5000); // Let CSP get loaded
+ evt.Wait(); // Let CSP get loaded
using var hFile = GetHFILE(fpath);
- Assert.That(CfHydratePlaceholder(hFile, 0, -1, 0), ResultIs.Successful);
+ Assert.That(hFile, ResultIs.ValidHandle);
+ Assert.That(CfHydratePlaceholder(hFile), ResultIs.Successful);
Assert.That(CfGetCorrelationVector(hFile, out var cv), ResultIs.Successful);
using var buf = new SafeHGlobalHandle(128);
Assert.That(CfGetPlaceholderRangeInfo(hFile, CF_PLACEHOLDER_RANGE_INFO_CLASS.CF_PLACEHOLDER_RANGE_INFO_ONDISK, 0, buf.Size, buf, buf.Size, out var rngLen), ResultIs.Successful);
Assert.That(CfGetTransferKey(hFile, out var txKey), ResultIs.Successful);
Assert.That(() => CfReleaseTransferKey(hFile, txKey), Throws.Nothing);
Assert.That(CfDehydratePlaceholder(hFile, 0, -1, 0), ResultIs.Successful);
- Thread.Sleep(2000); // Wait for user interaction
+ //Thread.Sleep(2000); // Wait for user interaction
tokSrc.Cancel();
task.Wait();
@@ -273,11 +269,9 @@ namespace Vanara.PInvoke.Tests
[Test]
public void CfGetSyncRootInfoByPathTest()
{
- using (var mem = SafeHGlobalHandle.CreateFromStructure())
- {
- Assert.That(CfGetSyncRootInfoByPath(syncRootPath, CF_SYNC_ROOT_INFO_CLASS.CF_SYNC_ROOT_INFO_BASIC, mem, mem.Size, out var len), ResultIs.Successful);
- mem.ToStructure().WriteValues();
- }
+ using var mem = new SafeCoTaskMemStruct();
+ Assert.That(CfGetSyncRootInfoByPath(syncRootPath, CF_SYNC_ROOT_INFO_CLASS.CF_SYNC_ROOT_INFO_BASIC, mem, mem.Size, out var len), ResultIs.Successful);
+ mem.Value.WriteValues();
Assert.That(() => CfGetSyncRootInfoByPath(syncRootPath).WriteValues(), Throws.Nothing);
Assert.That(() => CfGetSyncRootInfoByPath(syncRootPath).WriteValues(), Throws.Nothing);
}
diff --git a/UnitTests/PInvoke/CldApi/CloudSyncProvider.cs b/UnitTests/PInvoke/CldApi/CloudSyncProvider.cs
index a3f1a6cd..417bc6b6 100644
--- a/UnitTests/PInvoke/CldApi/CloudSyncProvider.cs
+++ b/UnitTests/PInvoke/CldApi/CloudSyncProvider.cs
@@ -1,5 +1,4 @@
-using ICSharpCode.Decompiler.IL;
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -18,80 +17,79 @@ using static Vanara.PInvoke.SearchApi;
namespace Vanara.PInvoke.Tests
{
+ /// Event arguments for a cloud file callback.
+ /// The type of data handled by the callback.
+ ///
public class CloudSyncCallbackArgs : EventArgs where T : struct
{
+ private readonly CF_CALLBACK_INFO info;
+
+ /// Initializes a new instance of the class.
+ /// The callback information.
+ /// The callback parameters.
public CloudSyncCallbackArgs(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters)
{
//System.Diagnostics.Debug.WriteLine($"CloudSyncCallbackArgs<{typeof(T).Name}> : {string.Join(" ", CallbackParameters.Content.Select(b => b.ToString("X2")))}");
- ConnectionKey = CallbackInfo.ConnectionKey;
- CallbackContext = CallbackInfo.CallbackContext;
- VolumeGuidName = CallbackInfo.VolumeGuidName;
- VolumeDosName = CallbackInfo.VolumeDosName;
- VolumeSerialNumber = CallbackInfo.VolumeSerialNumber;
- SyncRootFileId = CallbackInfo.SyncRootFileId;
+ info = CallbackInfo;
SyncRootIdentity = new SafeHGlobalHandle(CallbackInfo.SyncRootIdentity, CallbackInfo.SyncRootIdentityLength, false);
- FileId = CallbackInfo.FileId;
- FileSize = CallbackInfo.FileSize;
FileIdentity = new SafeHGlobalHandle(CallbackInfo.FileIdentity, CallbackInfo.FileIdentityLength, false);
- NormalizedPath = CallbackInfo.NormalizedPath;
- TransferKey = CallbackInfo.TransferKey;
- PriorityHint = CallbackInfo.PriorityHint;
- pCorrelationVector = CallbackInfo.CorrelationVector;
ProcessInfo = CallbackInfo.ProcessInfo.ToNullableStructure();
- RequestKey = CallbackInfo.RequestKey;
try { ParamData = CallbackParameters.GetParam(); } catch { }
//catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"{ex.Message}"); }
}
/// points to an opaque blob that the sync provider provides at the sync root connect time.
- public IntPtr CallbackContext { get; }
+ public IntPtr CallbackContext => info.CallbackContext;
/// An opaque handle created by CfConnectSyncRoot for a sync root managed by the sync provider.
- public CF_CONNECTION_KEY ConnectionKey { get; }
-
- /// An optional correlation vector.
- public IntPtr pCorrelationVector { get; }
+ public CF_CONNECTION_KEY ConnectionKey => info.ConnectionKey;
/// An optional correlation vector.
public CORRELATION_VECTOR? CorrelationVector => pCorrelationVector.ToNullableStructure();
/// A 64 bit file system maintained, volume-wide unique ID of the placeholder file/directory to be serviced.
- public long FileId { get; }
+ public long FileId => info.FileId;
/// Points to the opaque blob that the sync provider provides at the placeholder creation/conversion/update time.
public SafeAllocatedMemoryHandle FileIdentity { get; }
/// The logical size of the placeholder file to be serviced. It is always 0 if the subject of the callback is a directory.
- public long FileSize { get; }
+ public long FileSize => info.FileSize;
///
/// The absolute path of the placeholder file/directory to be serviced on the volume identified by VolumeGuidName/VolumeDosName. It
/// starts from the root directory of the volume. See the Remarks section for more details.
///
- public string NormalizedPath { get; }
+ public string NormalizedPath => info.NormalizedPath;
+
+ /// The type of operation performed.
+ public CF_OPERATION_TYPE OperationType { get; internal set; }
+
+ /// Parameters of an operation on a placeholder file or folder.
+ public CF_OPERATION_PARAMETERS? OpParam { get; internal set; }
/// Contains callback specific parameters for this action.
public T ParamData { get; }
- /// Parameters of an operation on a placeholder file or folder.
- public CF_OPERATION_PARAMETERS? OpParam { get; set; }
+ /// An optional correlation vector.
+ public IntPtr pCorrelationVector => info.CorrelationVector;
///
/// A numeric scale given to the sync provider to describe the relative priority of one fetch compared to another fetch, in order to
/// provide the most responsive experience to the user. The values range from 0 (lowest possible priority) to 15 (highest possible priority).
///
- public byte PriorityHint { get; }
+ public byte PriorityHint => info.PriorityHint;
/// Points to a structure that contains the information about the user process that triggers this callback.
public CF_PROCESS_INFO? ProcessInfo { get; }
///
- public CF_REQUEST_KEY RequestKey { get; }
+ public CF_REQUEST_KEY RequestKey => info.RequestKey;
///
/// A 64 bit file system maintained volume-wide unique ID of the sync root under which the placeholder file/directory to be serviced resides.
///
- public long SyncRootFileId { get; }
+ public long SyncRootFileId => info.SyncRootFileId;
/// Points to the opaque blob provided by the sync provider at the sync root registration time.
public SafeAllocatedMemoryHandle SyncRootIdentity { get; }
@@ -100,31 +98,16 @@ namespace Vanara.PInvoke.Tests
/// An opaque handle to the placeholder file/directory to be serviced. The sync provider must pass it back to the CfExecute call in
/// order to perform the desired operation on the file/directory.
///
- public CF_TRANSFER_KEY TransferKey { get; }
+ public CF_TRANSFER_KEY TransferKey => info.TransferKey;
/// DOS drive letter of the volume in the form of “X:” where X is the drive letter.
- public string VolumeDosName { get; }
+ public string VolumeDosName => info.VolumeDosName;
/// GUID name of the volume on which the placeholder file/directory to be serviced resides. It is in the form: "\?\Volume{GUID}".
- public string VolumeGuidName { get; }
+ public string VolumeGuidName => info.VolumeGuidName;
/// The serial number of the volume.
- public uint VolumeSerialNumber { get; }
-
- /// The type of operation performed.
- public CF_OPERATION_TYPE OperationType { get; set; }
-
- ///
- /// This member is new for Windows 10, version 1803.
- /// The current sync status of the platform.
- ///
- /// The platform queries this information upon any failed operations on a cloud file placeholder. If a structure is available, the
- /// platform will use the information provided to construct a more meaningful and actionable message to the user. The platform will
- /// keep this information on the file until the last handle on it goes away. If , the platform will clear the
- /// previously set sync status, if there is one.
- ///
- ///
- public IntPtr SyncStatus { get; set; }
+ public uint VolumeSerialNumber => info.VolumeSerialNumber;
/// Makes a CF_OPERATION_INFO instance from the properties.
/// Type of the operation to set.
@@ -141,12 +124,16 @@ namespace Vanara.PInvoke.Tests
};
}
+ /// Information for a placeholder representing a directory.
+ ///
public class PlaceHolderDirectoryInfo : PlaceholderInfo
{
/// The newly created child placeholder directory is considered to have all of its children present locally.
public bool DisableOnDemandPopulation;
}
+ /// Information for a placeholder representing a file.
+ ///
public class PlaceHolderFileInfo : PlaceholderInfo
{
/// The size of the file, in bytes.
@@ -155,6 +142,11 @@ namespace Vanara.PInvoke.Tests
/// The newly created placeholder is marked as in-sync. Applicable to both placeholder files and directories.
public bool InSync;
+ /// Creates a placeholder from a file.
+ /// The file information.
+ /// if set to , information is synchronized.
+ /// The relative file path.
+ /// A platholder for the file.
public static PlaceHolderFileInfo CreateFromFile(FileInfo fileInfo, bool inSync = true, string relativeFilePath = null)
{
return new PlaceHolderFileInfo
@@ -171,8 +163,11 @@ namespace Vanara.PInvoke.Tests
}
}
+ /// Information about a placeholder.
public abstract class PlaceholderInfo
{
+ protected CF_PLACEHOLDER_CREATE_INFO info;
+
/// The time the file was changed in FILETIME format.
public DateTime ChangeTime;
@@ -212,12 +207,22 @@ namespace Vanara.PInvoke.Tests
public HRESULT Result;
}
+ /// Represents a provider (server) of a cloud file system.
+ ///
internal class CloudSyncProvider : IDisposable
{
private const string MSSEARCH_INDEX = "SystemIndex";
-
+ private CF_CALLBACK_REGISTRATION[] callbackTable;
+ private bool disposed = false;
private CF_CONNECTION_KEY? key = null;
+ /// Initializes a new instance of the class.
+ /// The path to the sync root.
+ /// An optional display name that maps to the existing sync root registration.
+ /// A path to an icon resource for the custom state of a file or folder.
+ /// The version number of the sync root.
+ /// The custom properties.
+ /// Name cannot have spaces. - name
public CloudSyncProvider(string syncRootPath, string name, string iconResource = ",0", Version version = null, IEnumerable<(string name, int id)> customProperties = null)
{
if (name.Contains(" "))
@@ -262,24 +267,31 @@ namespace Vanara.PInvoke.Tests
public event EventHandler> ValidateData;
+ /// Gets an optional display name that maps to the existing sync root registration.
public string DisplayName { get; }
+ /// Gets a path to an icon resource for the custom state of a file or folder.
public string IconResource { get; }
public IEnumerable PropertyDefinitions { get; }
+ /// Gets a Uri to a cloud storage recycle bin.
public Uri RecycleBinUri { get; }
+ /// Gets or sets the current status of the sync provider.
public CF_SYNC_PROVIDER_STATUS Status
{
get => CfQuerySyncProviderStatus(key.Value, out var stat).Succeeded ? stat : CF_SYNC_PROVIDER_STATUS.CF_PROVIDER_STATUS_ERROR;
set => CfUpdateSyncProviderStatus(key.Value, value);
}
+ /// Gets an identifier for the sync root.
public string SyncRootId { get; }
+ /// Gets the path to the sync root.
public string SyncRootPath { get; }
+ /// Gets the version number of the sync root.
public Version Version { get; }
public static bool IsSyncRoot(string syncRootPath)
@@ -288,12 +300,119 @@ namespace Vanara.PInvoke.Tests
return CfGetSyncRootInfoByPath(syncRootPath, CF_SYNC_ROOT_INFO_CLASS.CF_SYNC_ROOT_INFO_BASIC, mem, mem.Size, out var len).Succeeded;
}
+ /// Converts a normal file/directory to a placeholder file/directory using oplocks.
+ /// Handle to the file or directory to be converted.
+ ///
+ /// if set to , the platform marks the converted placeholder as in sync with cloud upon a successful
+ /// conversion of the file.
+ ///
+ ///
+ /// if set to the platform dehydrates the file after converting it to a placeholder successfully. The caller
+ /// must acquire an exclusive handle when specifying this flag or data corruptions can occur.
+ ///
+ ///
+ /// A user mode buffer that contains the opaque file or directory information supplied by the caller. Optional if the caller is not
+ /// dehydrating the file at the same time, or if the caller is converting a directory. Cannot exceed 4KB in size.
+ ///
+ /// Length, in bytes, of the FileIdentity.
+ /// The final USN value after convert actions are performed.
+ ///
+ /// To convert a placeholder:
+ ///
+ /// -
+ ///
+ /// The file or directory to be converted must be contained in a registered sync root tree; it can be the sync root directory
+ /// itself, or any descendant directory; otherwise, the call with be failed with HRESULT(ERROR_CLOUD_FILE_NOT_UNDER_SYNC_ROOT).
+ ///
+ ///
+ /// -
+ ///
+ /// If dehydration is requested, the sync root must be registered with a valid hydration policy that is not
+ /// CF_HYDRATION_POLICY_ALWAYS_FULL; otherwise the call will be failed with HRESULT(ERROR_CLOUD_FILE_NOT_SUPPORTED).
+ ///
+ ///
+ /// -
+ /// If dehydration is requested, the placeholder must not be pinned locally or the call with be failed with HRESULT(ERROR_CLOUD_FILE_PINNED).
+ ///
+ /// -
+ /// If dehydration is requested, the placeholder must be in sync or the call with be failed with HRESULT(ERROR_CLOUD_FILE_NOT_IN_SYNC).
+ ///
+ /// -
+ ///
+ /// The caller must have WRITE_DATA or WRITE_DAC access to the file or directory to be converted. Otherwise the operation will be
+ /// failed with HRESULT(ERROR_CLOUD_FILE_ACCESS_DENIED).
+ ///
+ ///
+ ///
+ ///
public int ConvertToPlaceholder(string relativeFilePath, bool inSync = true, bool dehydrate = false, IntPtr fileIdentity = default, uint fileIdentityLength = 0)
{
- using var hFile = CreateFile(Path.Combine(SyncRootPath, relativeFilePath), Kernel32.FileAccess.FILE_READ_ATTRIBUTES, FileShare.Read, null, FileMode.Open, 0);
- return ConvertToPlaceholder(hFile, inSync, dehydrate, fileIdentity, fileIdentityLength);
+ CfOpenFileWithOplock(relativeFilePath, CF_OPEN_FILE_FLAGS.CF_OPEN_FILE_FLAG_EXCLUSIVE, out var hCfFile).ThrowIfFailed();
+ using (hCfFile)
+ {
+ CfReferenceProtectedHandle(hCfFile);
+ try
+ {
+ var hFile = CfGetWin32HandleFromProtectedHandle(hCfFile);
+ return ConvertToPlaceholder(hFile, inSync, dehydrate, fileIdentity, fileIdentityLength);
+ }
+ finally
+ {
+ CfReleaseProtectedHandle(hCfFile);
+ }
+ }
}
+ /// Converts a normal file/directory to a placeholder file/directory.
+ /// Handle to the file or directory to be converted.
+ ///
+ /// if set to , the platform marks the converted placeholder as in sync with cloud upon a successful
+ /// conversion of the file.
+ ///
+ ///
+ /// if set to the platform dehydrates the file after converting it to a placeholder successfully. The caller
+ /// must acquire an exclusive handle when specifying this flag or data corruptions can occur.
+ ///
+ ///
+ /// A user mode buffer that contains the opaque file or directory information supplied by the caller. Optional if the caller is not
+ /// dehydrating the file at the same time, or if the caller is converting a directory. Cannot exceed 4KB in size.
+ ///
+ /// Length, in bytes, of the FileIdentity.
+ /// The final USN value after convert actions are performed.
+ ///
+ ///
+ /// In the file case, the caller must acquire an exclusive handle to the file if it also intends to dehydrate the file at the same
+ /// time or data corruption can occur. To minimize the impact on user applications it is highly recommended that the caller obtain
+ /// the exclusiveness using proper oplocks (via CfOpenFileWithOplock) as opposed to using a share-nothing handle.
+ ///
+ /// To convert a placeholder:
+ ///
+ /// -
+ ///
+ /// The file or directory to be converted must be contained in a registered sync root tree; it can be the sync root directory
+ /// itself, or any descendant directory; otherwise, the call with be failed with HRESULT(ERROR_CLOUD_FILE_NOT_UNDER_SYNC_ROOT).
+ ///
+ ///
+ /// -
+ ///
+ /// If dehydration is requested, the sync root must be registered with a valid hydration policy that is not
+ /// CF_HYDRATION_POLICY_ALWAYS_FULL; otherwise the call will be failed with HRESULT(ERROR_CLOUD_FILE_NOT_SUPPORTED).
+ ///
+ ///
+ /// -
+ /// If dehydration is requested, the placeholder must not be pinned locally or the call with be failed with HRESULT(ERROR_CLOUD_FILE_PINNED).
+ ///
+ /// -
+ /// If dehydration is requested, the placeholder must be in sync or the call with be failed with HRESULT(ERROR_CLOUD_FILE_NOT_IN_SYNC).
+ ///
+ /// -
+ ///
+ /// The caller must have WRITE_DATA or WRITE_DAC access to the file or directory to be converted. Otherwise the operation will be
+ /// failed with HRESULT(ERROR_CLOUD_FILE_ACCESS_DENIED).
+ ///
+ ///
+ ///
+ ///
public int ConvertToPlaceholder(HFILE fileHandle, bool inSync = true, bool dehydrate = false, IntPtr fileIdentity = default, uint fileIdentityLength = 0)
{
var flags = (inSync ? CF_CONVERT_FLAGS.CF_CONVERT_FLAG_MARK_IN_SYNC : 0) | (dehydrate ? CF_CONVERT_FLAGS.CF_CONVERT_FLAG_DEHYDRATE : 0);
@@ -301,6 +420,11 @@ namespace Vanara.PInvoke.Tests
return usn;
}
+ /// Creates a new placeholder files under a sync root tree.
+ /// The relative file path of the file.
+ /// The file information of the file.
+ /// if set to , the file is synchronized.
+ /// The final USN value for the file.
public int CreatePlaceholderFromFile(string relativeFilePath, FileInfo fileInfo, bool inSync = true)
{
using var pRelativeName = new SafeCoTaskMemString(relativeFilePath);
@@ -312,6 +436,23 @@ namespace Vanara.PInvoke.Tests
return ph[0].CreateUsn;
}
+ /// Creates one or more new placeholder files or directories under a sync root tree.
+ ///
+ /// On successful creation, the PlaceholderArray contains the final USN value and a STATUS_OK message. On return, this array
+ /// contains an HRESULT value describing whether the placeholder was created or not.
+ ///
+ /// The number of entries processed, including failed entries.
+ ///
+ ///
+ /// Creating a placeholder with this function is preferred compared to creating a new file with CreateFile and then converting it to
+ /// a placeholder with CfConvertToPlaceholder; both for efficiency and because it eliminates the time window where the file is not a
+ /// placeholder. The function can also create multiple files or directories in a batch, which can also be more efficient.
+ ///
+ ///
+ /// This function is useful when performing an initial sync of files or directories from the cloud down to the client, or when
+ /// syncing down a newly created single file or directory from the cloud.
+ ///
+ ///
public uint CreatePlaceholders(IList placeholders)
{
var entries = new CF_PLACEHOLDER_CREATE_INFO[placeholders.Count];
@@ -378,43 +519,52 @@ namespace Vanara.PInvoke.Tests
//}
}
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
public void Dispose()
{
+ disposed = true;
if (!key.HasValue) return;
CfDisconnectSyncRoot(key.Value).ThrowIfFailed();
key = null;
UnregisterWithShell();
}
- protected virtual void OnCancelFetchData(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(CancelFetchData, CallbackInfo, CallbackParameters);
+ /// Allows a sync provider to notify the platform of its status.
+ ///
+ /// A localized string that is expected to contain more meaningful and actionable information about the file in question. Sync
+ /// providers are expected to balance the requirement of providing more actionable information and maintaining an as small as
+ /// possible memory footprint.
+ ///
+ ///
+ /// The use of this parameter is completely up to the sync provider that supports this rich sync status construct.
+ /// For a particular sync provider, it is expected that there is a 1:1 mapping between the code and the description string.
+ ///
+ /// It is recommended that you use the highest bit order to describe the type of error code: 1 for an error-level code, and 0
+ /// for an information-level code.
+ ///
+ /// NoteCode is opaque to the platform, and is used only for tracking purposes.
+ ///
+ public void ReportSyncStatus(string description, uint code)
+ {
+ var ssLen = (uint)Marshal.SizeOf();
+ uint descLen = (uint)(description.Length + 1) * 2;
+ var ss = new CF_SYNC_STATUS { StructSize = ssLen + descLen, Code = code, DescriptionLength = descLen, DescriptionOffset = ssLen };
+ using var mem = new SafeCoTaskMemStruct(ss, ss.StructSize);
+ StringHelper.Write(description, ((IntPtr)mem).Offset(Marshal.SizeOf()), out _, true, CharSet.Unicode, descLen);
+ CfReportSyncStatus(SyncRootPath, mem).ThrowIfFailed();
+ }
- protected virtual void OnCancelFetchPlaceholders(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(CancelFetchPlaceholders, CallbackInfo, CallbackParameters);
-
- protected virtual void OnFetchData(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(FetchData, CallbackInfo, CallbackParameters);
-
- protected virtual void OnFetchPlaceholders(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(FetchPlaceholders, CallbackInfo, CallbackParameters);
-
- protected virtual void OnNotifyDehydrate(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(NotifyDehydrate, CallbackInfo, CallbackParameters);
-
- protected virtual void OnNotifyDehydrateCompletion(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(NotifyDehydrateCompletion, CallbackInfo, CallbackParameters);
-
- protected virtual void OnNotifyDelete(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(NotifyDelete, CallbackInfo, CallbackParameters);
-
- protected virtual void OnNotifyDeleteCompletion(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(NotifyDeleteCompletion, CallbackInfo, CallbackParameters);
-
- protected virtual void OnNotifyFileCloseCompletion(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(NotifyFileCloseCompletion, CallbackInfo, CallbackParameters);
-
- protected virtual void OnNotifyFileOpenCompletion(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(NotifyFileOpenCompletion, CallbackInfo, CallbackParameters);
-
- protected virtual void OnNotifyRename(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(NotifyRename, CallbackInfo, CallbackParameters);
-
- protected virtual void OnNotifyRenameCompletion(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(NotifyRenameCompletion, CallbackInfo, CallbackParameters);
-
- protected virtual void OnValidateData(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(ValidateData, CallbackInfo, CallbackParameters);
+ /// Clears the previously-saved sync status.
+ public void ClearSyncStatus() => CfReportSyncStatus(SyncRootPath).ThrowIfFailed();
+ /// Handles the registered event.
+ /// The type contained with CF_CALLBACK_PARAMETERS.
+ /// The handler method.
+ /// The callback information.
+ /// The callback parameters.
protected virtual void HandleEvent(EventHandler> handler, in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) where T : struct
{
- if (handler != null)
+ if (handler != null && !disposed)
{
var args = new CloudSyncCallbackArgs(CallbackInfo, CallbackParameters);
handler.Invoke(this, args);
@@ -427,6 +577,55 @@ namespace Vanara.PInvoke.Tests
}
}
+ /// Callback to cancel an ongoing placeholder hydration.
+ protected virtual void OnCancelFetchData(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(CancelFetchData, CallbackInfo, CallbackParameters);
+
+ /// Callback to cancel a request for the contents of placeholder files.
+ protected virtual void OnCancelFetchPlaceholders(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(CancelFetchPlaceholders, CallbackInfo, CallbackParameters);
+
+ /// Callback to satisfy an I/O request, or a placeholder hydration request.
+ protected virtual void OnFetchData(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(FetchData, CallbackInfo, CallbackParameters);
+
+ /// Callback to request information about the contents of placeholder files.
+ protected virtual void OnFetchPlaceholders(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(FetchPlaceholders, CallbackInfo, CallbackParameters);
+
+ /// Callback to inform the sync provider that a placeholder under one of its sync roots is about to be dehydrated.
+ protected virtual void OnNotifyDehydrate(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(NotifyDehydrate, CallbackInfo, CallbackParameters);
+
+ /// Callback to inform the sync provider that a placeholder under one of its sync roots has been successfully dehydrated.
+ protected virtual void OnNotifyDehydrateCompletion(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(NotifyDehydrateCompletion, CallbackInfo, CallbackParameters);
+
+ /// Callback to inform the sync provider that a placeholder under one of its sync roots is about to be deleted.
+ protected virtual void OnNotifyDelete(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(NotifyDelete, CallbackInfo, CallbackParameters);
+
+ /// Callback to inform the sync provider that a placeholder under one of its sync roots has been successfully deleted.
+ protected virtual void OnNotifyDeleteCompletion(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(NotifyDeleteCompletion, CallbackInfo, CallbackParameters);
+
+ ///
+ /// Callback to inform the sync provider that a placeholder under one of its sync roots that has been previously opened for
+ /// read/write/delete access is now closed.
+ ///
+ protected virtual void OnNotifyFileCloseCompletion(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(NotifyFileCloseCompletion, CallbackInfo, CallbackParameters);
+
+ ///
+ /// Callback to inform the sync provider that a placeholder under one of its sync roots has been successfully opened for
+ /// read/write/delete access.
+ ///
+ protected virtual void OnNotifyFileOpenCompletion(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(NotifyFileOpenCompletion, CallbackInfo, CallbackParameters);
+
+ ///
+ /// Callback to inform the sync provider that a placeholder under one of its sync roots is about to be renamed or moved.
+ ///
+ protected virtual void OnNotifyRename(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(NotifyRename, CallbackInfo, CallbackParameters);
+
+ ///
+ /// Callback to inform the sync provider that a placeholder under one of its sync roots has been successfully renamed or moved.
+ ///
+ protected virtual void OnNotifyRenameCompletion(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(NotifyRenameCompletion, CallbackInfo, CallbackParameters);
+
+ /// Callback to validate placeholder data.
+ protected virtual void OnValidateData(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(ValidateData, CallbackInfo, CallbackParameters);
+
private static void AddFolderToSearchIndexer(string folder)
{
var url = "file:///" + folder;
@@ -440,7 +639,7 @@ namespace Vanara.PInvoke.Tests
private void ConnectSyncRootTransferCallbacks()
{
- CF_CALLBACK_REGISTRATION[] callbackTable =
+ callbackTable ??= new CF_CALLBACK_REGISTRATION[]
{
new CF_CALLBACK_REGISTRATION { Type = CF_CALLBACK_TYPE.CF_CALLBACK_TYPE_CANCEL_FETCH_DATA, Callback = OnCancelFetchData },
new CF_CALLBACK_REGISTRATION { Type = CF_CALLBACK_TYPE.CF_CALLBACK_TYPE_CANCEL_FETCH_PLACEHOLDERS, Callback = OnCancelFetchPlaceholders },