Committed work in progress on cldapi.dll

pull/119/head
dahall 2020-04-30 09:34:04 -06:00
parent 854ab9536d
commit ed51271853
5 changed files with 868 additions and 231 deletions

View File

@ -1,6 +1,7 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using Vanara.InteropServices;
using static Vanara.PInvoke.Kernel32;
namespace Vanara.PInvoke
@ -12,7 +13,9 @@ namespace Vanara.PInvoke
/// <param name="CallbackInfo">The callback information.</param>
/// <param name="CallbackParameters">The callback parameters.</param>
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
public delegate void CF_CALLBACK(in CF_CALLBACK_INFO CallbackInfo, IntPtr CallbackParameters);
public delegate void CF_CALLBACK(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters);
private delegate HRESULT GetInfoFunc<TParam, TEnum>(TParam p1, TEnum InfoClass, IntPtr InfoBuffer, uint InfoBufferLength, out uint ReturnedLength) where TEnum : Enum;
/// <summary>
/// Closes the file or directory handle returned by CfOpenFileWithOplock. This should not be used with standard Win32 file handles,
@ -176,6 +179,35 @@ namespace Vanara.PInvoke
public static unsafe extern HRESULT CfConvertToPlaceholder(HFILE FileHandle, [In, Optional] IntPtr FileIdentity, uint FileIdentityLength, CF_CONVERT_FLAGS ConvertFlags,
[Out, Optional] int* ConvertUsn, [In, Out, Optional] NativeOverlapped* Overlapped);
/// <summary>Creates one or more new placeholder files or directories under a sync root tree.</summary>
/// <param name="BaseDirectoryPath">Local directory path under which placeholders are created.</param>
/// <param name="PlaceholderArray">
/// 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.
/// </param>
/// <param name="PlaceholderCount">The count of placeholders in the PlaceholderArray.</param>
/// <param name="CreateFlags">Flags for configuring the creation of a placeholder.</param>
/// <param name="EntriesProcessed">The number of entries processed, including failed entries.</param>
/// <returns>If this function succeeds, it returns <c>S_OK</c>. Otherwise, it returns an <c>HRESULT</c> error code.</returns>
/// <remarks>
/// <para>
/// 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.
/// </para>
/// <para>
/// 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.
/// </para>
/// </remarks>
// https://docs.microsoft.com/en-us/windows/win32/api/cfapi/nf-cfapi-cfcreateplaceholders HRESULT CfCreatePlaceholders( LPCWSTR
// BaseDirectoryPath, CF_PLACEHOLDER_CREATE_INFO *PlaceholderArray, DWORD PlaceholderCount, CF_CREATE_FLAGS CreateFlags, PDWORD
// EntriesProcessed );
[DllImport(Lib.CldApi, SetLastError = false, ExactSpelling = true)]
[PInvokeData("cfapi.h", MSDNShortId = "96A6F62E-7F14-40B5-AB57-260DC9B1DF89")]
public static extern HRESULT CfCreatePlaceholders([MarshalAs(UnmanagedType.LPWStr)] string BaseDirectoryPath, [In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] CF_PLACEHOLDER_CREATE_INFO[] PlaceholderArray,
uint PlaceholderCount, CF_CREATE_FLAGS CreateFlags, out uint EntriesProcessed);
/// <summary>
/// Dehydrates a placeholder file by ensuring that the specified byte range is not present on-disk in the placeholder. This is valid
/// for files only.
@ -248,35 +280,6 @@ namespace Vanara.PInvoke
[PInvokeData("CfApi.h")]
public static unsafe extern HRESULT CfDehydratePlaceholder(HFILE FileHandle, long StartingOffset, long Length, CF_DEHYDRATE_FLAGS DehydrateFlags, [In, Out] NativeOverlapped* Overlapped);
/// <summary>Creates one or more new placeholder files or directories under a sync root tree.</summary>
/// <param name="BaseDirectoryPath">Local directory path under which placeholders are created.</param>
/// <param name="PlaceholderArray">
/// 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.
/// </param>
/// <param name="PlaceholderCount">The count of placeholders in the PlaceholderArray.</param>
/// <param name="CreateFlags">Flags for configuring the creation of a placeholder.</param>
/// <param name="EntriesProcessed">The number of entries processed, including failed entries.</param>
/// <returns>If this function succeeds, it returns <c>S_OK</c>. Otherwise, it returns an <c>HRESULT</c> error code.</returns>
/// <remarks>
/// <para>
/// 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.
/// </para>
/// <para>
/// 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.
/// </para>
/// </remarks>
// https://docs.microsoft.com/en-us/windows/win32/api/cfapi/nf-cfapi-cfcreateplaceholders HRESULT CfCreatePlaceholders( LPCWSTR
// BaseDirectoryPath, CF_PLACEHOLDER_CREATE_INFO *PlaceholderArray, DWORD PlaceholderCount, CF_CREATE_FLAGS CreateFlags, PDWORD
// EntriesProcessed );
[DllImport(Lib.CldApi, SetLastError = false, ExactSpelling = true)]
[PInvokeData("cfapi.h", MSDNShortId = "96A6F62E-7F14-40B5-AB57-260DC9B1DF89")]
public static extern HRESULT CfCreatePlaceholders([MarshalAs(UnmanagedType.LPWStr)] string BaseDirectoryPath, [In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] CF_PLACEHOLDER_CREATE_INFO[] PlaceholderArray,
uint PlaceholderCount, CF_CREATE_FLAGS CreateFlags, out uint EntriesProcessed);
/// <summary>Disconnects a communication channel created by CfConnectSyncRoot.</summary>
/// <param name="ConnectionKey">The connection key returned from CfConnectSyncRoot that is now used to disconnect the sync root.</param>
/// <returns>If this function succeeds, it returns <c>S_OK</c>. Otherwise, it returns an <c>HRESULT</c> error code.</returns>
@ -349,6 +352,13 @@ namespace Vanara.PInvoke
[PInvokeData("cfapi.h", MSDNShortId = "D82269CF-8056-46CF-9832-AAE8767A854B")]
public static extern HRESULT CfGetPlaceholderInfo(HFILE FileHandle, CF_PLACEHOLDER_INFO_CLASS InfoClass, [Out] IntPtr InfoBuffer, uint InfoBufferLength, out uint ReturnedLength);
/// <summary>Gets various characteristics of a placeholder file or folder.</summary>
/// <typeparam name="T">The type of information to retrieve.</typeparam>
/// <param name="FileHandle">A handle to the placeholder whose information will be queried.</param>
/// <returns>The requested information.</returns>
[PInvokeData("cfapi.h", MSDNShortId = "D82269CF-8056-46CF-9832-AAE8767A854B")]
public static T CfGetPlaceholderInfo<T>(HFILE FileHandle) where T : struct => GetInfo<T, CF_PLACEHOLDER_INFO_CLASS, HFILE>(CfGetPlaceholderInfo, FileHandle);
/// <summary>Gets range information about a placeholder file or folder.</summary>
/// <param name="FileHandle">The handle of the placeholder file to be queried.</param>
/// <param name="InfoClass">Types of the range of placeholder data.</param>
@ -440,6 +450,13 @@ namespace Vanara.PInvoke
[PInvokeData("cfapi.h", MSDNShortId = "EC96CB4E-6BCE-49D9-9CDA-A24A9303B5CF")]
public static extern HRESULT CfGetSyncRootInfoByHandle(HFILE FileHandle, CF_SYNC_ROOT_INFO_CLASS InfoClass, [Out] IntPtr InfoBuffer, uint InfoBufferLength, out uint ReturnedLength);
/// <summary>Gets various characteristics of the sync root containing a given file specified by a file handle.</summary>
/// <typeparam name="T">The type of information to retrieve.</typeparam>
/// <param name="FileHandle">Handle of the file under the sync root whose information is to be queried.</param>
/// <returns>The requested sync root information.</returns>
[PInvokeData("cfapi.h", MSDNShortId = "EC96CB4E-6BCE-49D9-9CDA-A24A9303B5CF")]
public static T CfGetSyncRootInfoByHandle<T>(HFILE FileHandle) where T : struct => GetInfo<T, CF_SYNC_ROOT_INFO_CLASS, HFILE>(CfGetSyncRootInfoByHandle, FileHandle);
/// <summary>Gets various sync root information given a file under the sync root.</summary>
/// <param name="FilePath">A fully qualified path to a file whose sync root information is to be queried</param>
/// <param name="InfoClass">Types of sync root information.</param>
@ -455,6 +472,14 @@ namespace Vanara.PInvoke
[PInvokeData("cfapi.h", MSDNShortId = "0FEEF910-3545-4D94-BFF9-88AEE084F454")]
public static extern HRESULT CfGetSyncRootInfoByPath([MarshalAs(UnmanagedType.LPWStr)] string FilePath, CF_SYNC_ROOT_INFO_CLASS InfoClass, [Out] IntPtr InfoBuffer, uint InfoBufferLength, out uint ReturnedLength);
/// <summary>Gets various sync root information given a file under the sync root.</summary>
/// <typeparam name="T">The type of information to retrieve.</typeparam>
/// <param name="FilePath">A fully qualified path to a file whose sync root information is to be queried</param>
/// <returns>The requested sync root information.</returns>
/// <exception cref="ArgumentException">Supplied type parameter is not supported. - T</exception>
[PInvokeData("cfapi.h", MSDNShortId = "0FEEF910-3545-4D94-BFF9-88AEE084F454")]
public static T CfGetSyncRootInfoByPath<T>(string FilePath) where T : struct => GetInfo<T, CF_SYNC_ROOT_INFO_CLASS, string>(CfGetSyncRootInfoByPath, FilePath);
/// <summary>Initiates a transfer of data into a placeholder file or folder.</summary>
/// <param name="FileHandle">The file handle of the placeholder.</param>
/// <param name="TransferKey">An opaque handle to the placeholder to be serviced.</param>
@ -1084,6 +1109,21 @@ namespace Vanara.PInvoke
[PInvokeData("cfapi.h", MSDNShortId = "E0CB6CA2-439A-4919-95EF-B519ABBBB085")]
public static extern HRESULT CfUpdateSyncProviderStatus(CF_CONNECTION_KEY ConnectionKey, CF_SYNC_PROVIDER_STATUS ProviderStatus);
private static T GetInfo<T, TEnum, TParam>(GetInfoFunc<TParam, TEnum> func, TParam firstParam) where TEnum : struct, Enum where T : struct
{
if (!CorrespondingTypeAttribute.CanGet<TEnum>(typeof(T), out var infoClass))
throw new ArgumentException("Supplied type parameter is not supported.", nameof(T));
using var mem = SafeHGlobalHandle.CreateFromStructure<T>();
var hr = func(firstParam, infoClass, mem, mem.Size, out var len);
while (hr == (HRESULT)(Win32Error)Win32Error.ERROR_MORE_DATA && mem.Size < 1024 * 32)
{
mem.Size = len * 4;
hr = func(firstParam, infoClass, mem, mem.Size, out len);
}
hr.ThrowIfFailed();
return mem.ToStructure<T>();
}
/// <summary>Provides a handle to a CF opened file.</summary>
[StructLayout(LayoutKind.Sequential)]
public struct HCFFILE : IHandle

View File

@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
using Vanara.Extensions;
using Vanara.InteropServices;
using static Vanara.PInvoke.Kernel32;
@ -885,7 +886,7 @@ namespace Vanara.PInvoke
CF_PLACEHOLDER_CREATE_FLAG_NONE = 0,
/// <summary>
/// The newly created child placeholder directory is considered to have all of its children present locally.Applicable to a
/// The newly created child placeholder directory is considered to have all of its children present locally. Applicable to a
/// child placeholder directory only.
/// </summary>
CF_PLACEHOLDER_CREATE_FLAG_DISABLE_ON_DEMAND_POPULATION = 1,
@ -904,9 +905,11 @@ namespace Vanara.PInvoke
public enum CF_PLACEHOLDER_INFO_CLASS
{
/// <summary>Basic placeholder information. See CF_PLACEHOLDER_BASIC_INFO.</summary>
[CorrespondingType(typeof(CF_PLACEHOLDER_BASIC_INFO), CorrespondingAction.Get)]
CF_PLACEHOLDER_INFO_BASIC,
/// <summary>Standard placeholder information. See CF_PLACEHOLDER_STANDARD_INFO.</summary>
[CorrespondingType(typeof(CF_PLACEHOLDER_STANDARD_INFO), CorrespondingAction.Get)]
CF_PLACEHOLDER_INFO_STANDARD,
}
@ -1145,12 +1148,15 @@ namespace Vanara.PInvoke
public enum CF_SYNC_ROOT_INFO_CLASS
{
/// <summary>Basic sync root information. See CF_SYNC_ROOT_BASIC_INFO.</summary>
[CorrespondingType(typeof(CF_SYNC_ROOT_BASIC_INFO), CorrespondingAction.Get)]
CF_SYNC_ROOT_INFO_BASIC,
/// <summary>Standard sync root information. See CF_SYNC_ROOT_STANDARD_INFO.</summary>
[CorrespondingType(typeof(CF_SYNC_ROOT_STANDARD_INFO), CorrespondingAction.Get)]
CF_SYNC_ROOT_INFO_STANDARD,
/// <summary>Sync root provider information. See CF_SYNC_ROOT_PROVIDER_INFO.</summary>
[CorrespondingType(typeof(CF_SYNC_ROOT_PROVIDER_INFO), CorrespondingAction.Get)]
CF_SYNC_ROOT_INFO_PROVIDER,
}
@ -1336,222 +1342,198 @@ namespace Vanara.PInvoke
// CF_CALLBACK_DELETE_COMPLETION_FLAGS Flags; } DeleteCompletion; struct { CF_CALLBACK_RENAME_FLAGS Flags; PCWSTR TargetPath; }
// Rename; struct { CF_CALLBACK_RENAME_COMPLETION_FLAGS Flags; PCWSTR SourcePath; } RenameCompletion; } DUMMYUNIONNAME; } CF_CALLBACK_PARAMETERS;
[PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")]
[StructLayout(LayoutKind.Explicit)]
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct CF_CALLBACK_PARAMETERS
{
/// <summary/>
[FieldOffset(0)]
public uint ParamSize;
/// <summary/>
[FieldOffset(4)]
public CANCEL Cancel;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 56)]
public byte[] Content;
/// <summary>Gets the parameter value for this structure.</summary>
/// <typeparam name="T">The type of the structure to retrieve.</typeparam>
/// <returns>The requested structure.</returns>
public T GetParam<T>() where T : struct
{
using var ptr = new SafeHGlobalHandle(Content);
return ptr.ToStructure<T>();
}
}
/// <summary>Contains callback specific parameters such as file offset, length, flags, etc.</summary>
[PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")]
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct CF_CALLBACK_PARAMETERS_CANCEL
{
/// <summary>Cancel data flags.</summary>
public CF_CALLBACK_CANCEL_FLAGS Flags;
/// <summary/>
public CANCELFETCHDATA FetchData;
/// <summary/>
[StructLayout(LayoutKind.Sequential)]
public struct CANCEL
public struct CANCELFETCHDATA
{
/// <summary>Cancel data flags.</summary>
public CF_CALLBACK_CANCEL_FLAGS Flags;
/// <summary>Offset, in bytes, for specifying the range of data.</summary>
public long FileOffset;
/// <summary/>
public CANCELFETCHDATA FetchData;
/// <summary/>
[StructLayout(LayoutKind.Sequential)]
public struct CANCELFETCHDATA
{
/// <summary>Offset, in bytes, for specifying the range of data.</summary>
public long FileOffset;
/// <summary>Length of the data in bytes.</summary>
public long Length;
}
/// <summary>Length of the data in bytes.</summary>
public long Length;
}
}
/// <summary>Contains callback specific parameters such as file offset, length, flags, etc.</summary>
[PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")]
[StructLayout(LayoutKind.Sequential)]
public struct CF_CALLBACK_PARAMETERS_CLOSECOMPLETION
{
/// <summary>Placeholder close completion flags.</summary>
public CF_CALLBACK_CLOSE_COMPLETION_FLAGS Flags;
}
/// <summary>Contains callback specific parameters such as file offset, length, flags, etc.</summary>
[PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")]
[StructLayout(LayoutKind.Sequential)]
public struct CF_CALLBACK_PARAMETERS_DEHYDRATE
{
/// <summary>Placeholder dehydration flags.</summary>
public CF_CALLBACK_DEHYDRATE_FLAGS Flags;
/// <summary/>
[FieldOffset(4)]
public FETCHDATA FetchData;
public CF_CALLBACK_DEHYDRATION_REASON Reason;
}
/// <summary>Contains callback specific parameters such as file offset, length, flags, etc.</summary>
[PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")]
[StructLayout(LayoutKind.Sequential)]
public struct CF_CALLBACK_PARAMETERS_DEHYDRATECOMPLETION
{
/// <summary/>
public CF_CALLBACK_DEHYDRATE_COMPLETION_FLAGS Flags;
/// <summary/>
[StructLayout(LayoutKind.Sequential)]
public struct FETCHDATA
{
/// <summary>Fetch data flags.</summary>
public CF_CALLBACK_FETCH_DATA_FLAGS Flags;
public CF_CALLBACK_DEHYDRATION_REASON Reason;
}
/// <summary>Offset, in bytes, for specifying the required range of data.</summary>
public long RequiredFileOffset;
/// <summary>Contains callback specific parameters such as file offset, length, flags, etc.</summary>
[PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")]
[StructLayout(LayoutKind.Sequential)]
public struct CF_CALLBACK_PARAMETERS_DELETE
{
/// <summary>Placeholder deletion flags.</summary>
public CF_CALLBACK_DELETE_FLAGS Flags;
}
/// <summary>Length of the required data to retrieve, in bytes.</summary>
public long RequiredLength;
/// <summary>Contains callback specific parameters such as file offset, length, flags, etc.</summary>
[PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")]
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct CF_CALLBACK_PARAMETERS_DELETECOMPLETION
{
/// <summary>Placeholder deletion complete flags.</summary>
public CF_CALLBACK_DELETE_COMPLETION_FLAGS Flags;
}
/// <summary>
/// Offset, in bytes, of a broader piece of data to provide to a sync provider. This is optional and can be used if the sync
/// provider prefers to work with larger segments of data.
/// </summary>
public long OptionalFileOffset;
/// <summary>Contains callback specific parameters such as file offset, length, flags, etc.</summary>
[PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")]
[StructLayout(LayoutKind.Sequential, Pack = 4, Size = 56)]
public struct CF_CALLBACK_PARAMETERS_FETCHDATA
{
/// <summary>Fetch data flags.</summary>
public CF_CALLBACK_FETCH_DATA_FLAGS Flags;
/// <summary>
/// Length, in bytes, of a broader piece of data to provide to a sync provider. This is optional and can be used if the sync
/// provider prefers to work with larger segments of data.
/// </summary>
public long OptionalLength;
/// <summary>Offset, in bytes, for specifying the required range of data.</summary>
public long RequiredFileOffset;
/// <summary/>
public long LastDehydrationTime;
/// <summary>Length of the required data to retrieve, in bytes.</summary>
public long RequiredLength;
/// <summary/>
public CF_CALLBACK_DEHYDRATION_REASON LastDehydrationReason;
}
/// <summary>
/// Offset, in bytes, of a broader piece of data to provide to a sync provider. This is optional and can be used if the sync
/// provider prefers to work with larger segments of data.
/// </summary>
public long OptionalFileOffset;
/// <summary>
/// Length, in bytes, of a broader piece of data to provide to a sync provider. This is optional and can be used if the sync
/// provider prefers to work with larger segments of data.
/// </summary>
public long OptionalLength;
/// <summary/>
[StructLayout(LayoutKind.Sequential)]
public struct VALIDATEDATA
{
/// <summary>Data validation flags.</summary>
public CF_CALLBACK_VALIDATE_DATA_FLAGS Flags;
/// <summary>Offset, in bytes, for specifying the range of data to validate.</summary>
public long RequiredFileOffset;
/// <summary>Length, in bytes, of the data to validate.</summary>
public long RequiredLength;
}
public long LastDehydrationTime;
/// <summary/>
[FieldOffset(4)]
public VALIDATEDATA ValidateData;
public CF_CALLBACK_DEHYDRATION_REASON LastDehydrationReason;
}
/// <summary/>
[StructLayout(LayoutKind.Sequential)]
public struct FETCHPLACEHOLDERS
{
/// <summary>Flags for fetching placeholder metadata.</summary>
public CF_CALLBACK_FETCH_PLACEHOLDERS_FLAGS Flags;
/// <summary>Contains callback specific parameters such as file offset, length, flags, etc.</summary>
[PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")]
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct CF_CALLBACK_PARAMETERS_FETCHPLACEHOLDERS
{
/// <summary>Flags for fetching placeholder metadata.</summary>
public CF_CALLBACK_FETCH_PLACEHOLDERS_FLAGS Flags;
/// <summary>
/// A standard Windows file pattern which may contain wildcard characters (?, *). All placeholders information matching
/// the pattern must be transferred, but not necessarily in one-shot, as a minimum requirement. Alternatively, a sync
/// provider may choose to not transfer placeholders matching the pattern.
/// </summary>
[MarshalAs(UnmanagedType.LPWStr)]
public string Pattern;
}
/// <summary>
/// A standard Windows file pattern which may contain wildcard characters (?, *). All placeholders information matching the
/// pattern must be transferred, but not necessarily in one-shot, as a minimum requirement. Alternatively, a sync provider may
/// choose to not transfer placeholders matching the pattern.
/// </summary>
[MarshalAs(UnmanagedType.LPWStr)]
public string Pattern;
}
/// <summary/>
[FieldOffset(4)]
public FETCHPLACEHOLDERS FetchPlaceholders;
/// <summary>Contains callback specific parameters such as file offset, length, flags, etc.</summary>
[PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")]
[StructLayout(LayoutKind.Sequential)]
public struct CF_CALLBACK_PARAMETERS_OPENCOMPLETION
{
/// <summary>Placeholder open completion flags.</summary>
public CF_CALLBACK_OPEN_COMPLETION_FLAGS Flags;
}
/// <summary/>
[StructLayout(LayoutKind.Sequential)]
public struct OPENCOMPLETION
{
/// <summary>Placeholder open completion flags.</summary>
public CF_CALLBACK_OPEN_COMPLETION_FLAGS Flags;
}
/// <summary>Contains callback specific parameters such as file offset, length, flags, etc.</summary>
[PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")]
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct CF_CALLBACK_PARAMETERS_RENAME
{
/// <summary>Rename placeholder flags.</summary>
public CF_CALLBACK_RENAME_FLAGS Flags;
/// <summary/>
[FieldOffset(4)]
public OPENCOMPLETION OpenCompletion;
/// <summary>The full rename/move target path relative to the volume.</summary>
[MarshalAs(UnmanagedType.LPWStr)]
public string TargetPath;
}
/// <summary/>
[StructLayout(LayoutKind.Sequential)]
public struct CLOSECOMPLETION
{
/// <summary>Placeholder close completion flags.</summary>
public CF_CALLBACK_CLOSE_COMPLETION_FLAGS Flags;
}
/// <summary>Contains callback specific parameters such as file offset, length, flags, etc.</summary>
[PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")]
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct CF_CALLBACK_PARAMETERS_RENAMECOMPLETION
{
/// <summary>Rename completion placeholder flags.</summary>
public CF_CALLBACK_RENAME_COMPLETION_FLAGS Flags;
/// <summary/>
[FieldOffset(4)]
public CLOSECOMPLETION CloseCompletion;
/// <summary>The full source link path relative to the volume.</summary>
[MarshalAs(UnmanagedType.LPWStr)]
public string SourcePath;
}
/// <summary/>
[StructLayout(LayoutKind.Sequential)]
public struct DEHYDRATE
{
/// <summary>Placeholder dehydration flags.</summary>
public CF_CALLBACK_DEHYDRATE_FLAGS Flags;
/// <summary>Contains callback specific parameters such as file offset, length, flags, etc.</summary>
[PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")]
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct CF_CALLBACK_PARAMETERS_VALIDATEDATA
{
/// <summary>Data validation flags.</summary>
public CF_CALLBACK_VALIDATE_DATA_FLAGS Flags;
/// <summary/>
public CF_CALLBACK_DEHYDRATION_REASON Reason;
}
/// <summary>Offset, in bytes, for specifying the range of data to validate.</summary>
public long RequiredFileOffset;
/// <summary/>
[FieldOffset(4)]
public DEHYDRATE Dehydrate;
/// <summary/>
[StructLayout(LayoutKind.Sequential)]
public struct DEHYDRATECOMPLETION
{
/// <summary/>
public CF_CALLBACK_DEHYDRATE_COMPLETION_FLAGS Flags;
/// <summary/>
public CF_CALLBACK_DEHYDRATION_REASON Reason;
}
/// <summary/>
[FieldOffset(4)]
public DEHYDRATECOMPLETION DehydrateCompletion;
/// <summary/>
[StructLayout(LayoutKind.Sequential)]
public struct DELETE
{
/// <summary>Placeholder deletion flags.</summary>
public CF_CALLBACK_DELETE_FLAGS Flags;
}
/// <summary/>
[FieldOffset(4)]
public DELETE Delete;
/// <summary/>
[StructLayout(LayoutKind.Sequential)]
public struct DELETECOMPLETION
{
/// <summary>Placeholder deletion complete flags.</summary>
public CF_CALLBACK_DELETE_COMPLETION_FLAGS Flags;
}
/// <summary/>
[FieldOffset(4)]
public DELETECOMPLETION DeleteCompletion;
/// <summary/>
[StructLayout(LayoutKind.Sequential)]
public struct RENAME
{
/// <summary>Rename placeholder flags.</summary>
public CF_CALLBACK_RENAME_FLAGS Flags;
/// <summary>The full rename/move target path relative to the volume.</summary>
[MarshalAs(UnmanagedType.LPWStr)]
public string TargetPath;
}
/// <summary/>
[FieldOffset(4)]
public RENAME Rename;
/// <summary/>
[StructLayout(LayoutKind.Sequential)]
public struct RENAMECOMPLETION
{
/// <summary>Rename completion placeholder flags.</summary>
public CF_CALLBACK_RENAME_COMPLETION_FLAGS Flags;
/// <summary>The full source link path relative to the volume.</summary>
[MarshalAs(UnmanagedType.LPWStr)]
public string SourcePath;
}
/// <summary/>
[FieldOffset(4)]
public RENAMECOMPLETION RenameCompletion;
/// <summary>Length, in bytes, of the data to validate.</summary>
public long RequiredLength;
}
/// <summary>The callbacks to be registered by the sync provider.</summary>
@ -1590,6 +1572,20 @@ namespace Vanara.PInvoke
private long Internal;
}
/// <summary>Specifies a range of data in a placeholder file.</summary>
// https://docs.microsoft.com/en-us/windows/win32/api/cfapi/ns-cfapi-cf_file_range typedef struct CF_FILE_RANGE { LARGE_INTEGER
// StartingOffset; LARGE_INTEGER Length; } CF_FILE_RANGE;
[PInvokeData("cfapi.h", MSDNShortId = "DAE43446-725E-490B-AE1B-EA6CB31F4358")]
[StructLayout(LayoutKind.Sequential)]
public struct CF_FILE_RANGE
{
/// <summary>The offset of the starting point of the data.</summary>
public long StartingOffset;
/// <summary>The length of the data, in bytes.</summary>
public long Length;
}
/// <summary>A structure to describe the location and range of data in a file.</summary>
// https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/mt844616(v=vs.85) typedef struct __CF_FILE_RANGE_BUFFER
// { LARGE_INTEGER FileOffset; LARGE_INTEGER Length; } CF_FILE_RANGE_BUFFER;
@ -1868,20 +1864,6 @@ namespace Vanara.PInvoke
public ACKDELETE AckDelete;
}
/// <summary>Specifies a range of data in a placeholder file.</summary>
// https://docs.microsoft.com/en-us/windows/win32/api/cfapi/ns-cfapi-cf_file_range
// typedef struct CF_FILE_RANGE { LARGE_INTEGER StartingOffset; LARGE_INTEGER Length; } CF_FILE_RANGE;
[PInvokeData("cfapi.h", MSDNShortId = "DAE43446-725E-490B-AE1B-EA6CB31F4358")]
[StructLayout(LayoutKind.Sequential)]
public struct CF_FILE_RANGE
{
/// <summary>The offset of the starting point of the data.</summary>
public int StartingOffset;
/// <summary>The length of the data, in bytes.</summary>
public int Length;
}
/// <summary>Basic placeholder information.</summary>
// https://docs.microsoft.com/en-us/windows/win32/api/cfapi/ns-cfapi-cf_placeholder_basic_info typedef struct
// CF_PLACEHOLDER_BASIC_INFO { CF_PIN_STATE PinState; CF_IN_SYNC_STATE InSyncState; LARGE_INTEGER FileId; LARGE_INTEGER
@ -2147,7 +2129,7 @@ namespace Vanara.PInvoke
public struct CF_SYNC_ROOT_BASIC_INFO
{
/// <summary>The file ID of the sync root.</summary>
public int SyncRootFileId;
public long SyncRootFileId;
}
/// <summary>Sync root provider information.</summary>
@ -2180,11 +2162,11 @@ namespace Vanara.PInvoke
// SyncRootIdentityLength; BYTE SyncRootIdentity[1]; } CF_SYNC_ROOT_STANDARD_INFO;
[PInvokeData("cfapi.h", MSDNShortId = "17E409FB-2997-432C-977F-BEBF53068B42")]
[VanaraMarshaler(typeof(SafeAnysizeStructMarshaler<CF_SYNC_ROOT_STANDARD_INFO>), nameof(SyncRootIdentityLength))]
[StructLayout(LayoutKind.Sequential)]
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct CF_SYNC_ROOT_STANDARD_INFO
{
/// <summary>File ID of the sync root.</summary>
public int SyncRootFileId;
public long SyncRootFileId;
/// <summary>Hydration policy of the sync root.</summary>
public CF_HYDRATION_POLICY HydrationPolicy;

View File

@ -46,6 +46,10 @@
<Project>{842d436f-598c-47d7-b5aa-12399f8ccfe9}</Project>
<Name>Vanara.PInvoke.Kernel32</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\PInvoke\SearchApi\Vanara.PInvoke.SearchApi.csproj">
<Project>{07cd630d-a4bd-45cb-bf1b-90e981f4de81}</Project>
<Name>Vanara.PInvoke.SearchApi</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\PInvoke\Shared\Vanara.PInvoke.Shared.csproj">
<Project>{a5e519e9-feba-4fe3-93a5-b8269bef72f4}</Project>
<Name>Vanara.PInvoke.Shared</Name>
@ -56,6 +60,9 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.SDK.Contracts">
<Version>10.0.18362.2005</Version>
</PackageReference>
<PackageReference Include="NUnit">
<Version>3.12.0</Version>
</PackageReference>
@ -68,6 +75,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="CldApiTests.cs" />
<Compile Include="CloudSyncProvider.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View File

@ -1,17 +1,166 @@
using Microsoft.Win32.SafeHandles;
using ICSharpCode.Decompiler.TypeSystem;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using Vanara.Extensions;
using Vanara.InteropServices;
using Windows.Storage.Streams;
using static Vanara.PInvoke.CldApi;
namespace Vanara.PInvoke.Tests
{
public class CldApiTests
{
private static readonly string syncRootPath = Environment.GetEnvironmentVariable("OneDrive");
//private const string tempSubDir = "CfSource";
private static string SetupTempDir(string subDir) => Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), subDir)).FullName;
private static void DeleteTempDir(string subDir)
{
var dirInfo = new DirectoryInfo(Path.Combine(Path.GetTempPath(), subDir));
if (dirInfo.Exists) dirInfo.Delete(true);
}
[Test]
public void CfConnectSyncRootTest()
{
const string dest = "CfDest";
var destDirPath = SetupTempDir(dest);
try
{
using var csp = new CloudSyncProvider(destDirPath, "TestSync");
}
finally
{
DeleteTempDir(dest);
}
}
[Test]
public void CfCreatePlaceholdersTest()
{
const string dest = "CfDest";
var destDirPath = SetupTempDir(dest);
try
{
using var csp = new CloudSyncProvider(destDirPath, "TestSync");
csp.CancelFetchData += (s, e) => { TestContext.WriteLine($"CancelFetchData: {e.NormalizedPath}, {e.FileSize}"); e.ParamData.WriteValues(); };
csp.CancelFetchPlaceholders += (s, e) => { TestContext.WriteLine($"CancelFetchPlaceholders: {e.NormalizedPath}, {e.FileSize}"); e.ParamData.WriteValues(); };
csp.FetchData += (s, e) => { TestContext.WriteLine($"FetchData: {e.NormalizedPath}, {e.FileSize}"); e.ParamData.WriteValues(); };
csp.FetchPlaceholders += (s, e) => { TestContext.WriteLine($"FetchPlaceholders: {e.NormalizedPath}, {e.FileSize}"); e.ParamData.WriteValues(); };
csp.NotifyDehydrate += (s, e) => { TestContext.WriteLine($"NotifyDehydrate: {e.NormalizedPath}, {e.FileSize}"); e.ParamData.WriteValues(); };
csp.NotifyDehydrateCompletion += (s, e) => { TestContext.WriteLine($"NotifyDehydrateCompletion: {e.NormalizedPath}, {e.FileSize}"); e.ParamData.WriteValues(); };
csp.NotifyDelete += (s, e) => { TestContext.WriteLine($"NotifyDelete: {e.NormalizedPath}, {e.FileSize}"); e.ParamData.WriteValues(); };
csp.NotifyDeleteCompletion += (s, e) => { TestContext.WriteLine($"NotifyDeleteCompletion: {e.NormalizedPath}, {e.FileSize}"); e.ParamData.WriteValues(); };
csp.NotifyFileCloseCompletion += (s, e) => { TestContext.WriteLine($"NotifyFileCloseCompletion: {e.NormalizedPath}, {e.FileSize}"); e.ParamData.WriteValues(); };
csp.NotifyFileOpenCompletion += (s, e) => { TestContext.WriteLine($"NotifyFileOpenCompletion: {e.NormalizedPath}, {e.FileSize}"); e.ParamData.WriteValues(); };
csp.NotifyRename += (s, e) => { TestContext.WriteLine($"NotifyRename: {e.NormalizedPath}, {e.FileSize}"); e.ParamData.WriteValues(); };
csp.NotifyRenameCompletion += (s, e) => { TestContext.WriteLine($"NotifyRenameCompletion: {e.NormalizedPath}, {e.FileSize}"); e.ParamData.WriteValues(); };
csp.ValidateData += (s, e) => { TestContext.WriteLine($"ValidateData: {e.NormalizedPath}, {e.FileSize}"); e.ParamData.WriteValues(); };
csp.CreatePlaceholderFromFile("test.bmp", new FileInfo(TestCaseSources.BmpFile), true);
Assert.That(File.Exists(Path.Combine(destDirPath, "test.bmp")), Is.True);
Assert.That(new FileInfo(Path.Combine(destDirPath, "test.bmp")).Length, Is.EqualTo(new FileInfo(TestCaseSources.BmpFile).Length));
//CfGetPlaceholderRangeInfo(hFile, CF_PLACEHOLDER_RANGE_INFO_CLASS.CF_PLACEHOLDER_RANGE_INFO_ONDISK, 0, )
//Assert.That(CfHydratePlaceholder(hFile, 0, -1, CF_HYDRATE_FLAGS.CF_HYDRATE_FLAG_NONE), ResultIs.Successful);
}
finally
{
DeleteTempDir(dest);
}
}
private string CopyFile(string src, string destDir)
{
var destFile = Path.Combine(destDir, Path.GetFileName(src));
File.Copy(src, destFile);
Assert.That(File.Exists(destFile), Is.True);
return destFile;
}
private Kernel32.SafeHFILE GetHFILE(string path) => Kernel32.CreateFile(path, Kernel32.FileAccess.FILE_READ_ATTRIBUTES, FileShare.Read, null, FileMode.Open, 0);
[Test]
public void CfGetPlaceholderInfoTest()
{
const string dest = "CfDest";
var destDirPath = SetupTempDir(dest);
try
{
using var csp = new CloudSyncProvider(destDirPath, "TestSync");
// Copy in file and convert to placeholder
var destFile = CopyFile(TestCaseSources.WordDoc, destDirPath);
Assert.That(() => csp.ConvertToPlaceholder(destFile), Throws.Nothing);
using var hFile = GetHFILE(destFile);
Assert.That(() => CfGetPlaceholderInfo<CF_PLACEHOLDER_BASIC_INFO>(hFile).WriteValues(), Throws.Nothing);
Assert.That(() => CfGetPlaceholderInfo<CF_PLACEHOLDER_STANDARD_INFO>(hFile).WriteValues(), Throws.Nothing);
}
finally
{
DeleteTempDir(dest);
}
}
[Test]
public void CfGetPlaceholderStateFromFindDataTest()
{
const string dest = "CfDest";
var destDirPath = SetupTempDir(dest);
try
{
using var csp = new CloudSyncProvider(destDirPath, "TestSync");
csp.CreatePlaceholderFromFile("test.bmp", new FileInfo(TestCaseSources.BmpFile), true);
Assert.That(File.Exists(Path.Combine(destDirPath, "test.bmp")), Is.True);
Kernel32.FindFirstFile(Path.Combine(destDirPath, "test.bmp"), out var findData).Dispose();
CfGetPlaceholderStateFromFindData(findData).WriteValues();
}
finally
{
DeleteTempDir(dest);
}
}
[Test]
public void CfGetPlatformInfoTest()
{
Assert.That(CfGetPlatformInfo(out var info), ResultIs.Successful);
info.WriteValues();
}
[Test]
public void CfGetSyncRootInfoByHandleTest()
{
using var hFile = GetHFILE(new DirectoryInfo(syncRootPath).EnumerateFiles("*.*").First().FullName);
Assert.That(hFile, ResultIs.ValidHandle);
using (var mem = SafeHGlobalHandle.CreateFromStructure<CF_SYNC_ROOT_BASIC_INFO>())
{
Assert.That(CfGetSyncRootInfoByHandle(hFile, CF_SYNC_ROOT_INFO_CLASS.CF_SYNC_ROOT_INFO_BASIC, mem, mem.Size, out var len), ResultIs.Successful);
mem.ToStructure<CF_SYNC_ROOT_BASIC_INFO>().WriteValues();
}
Assert.That(() => CfGetSyncRootInfoByHandle<CF_SYNC_ROOT_PROVIDER_INFO>(hFile).WriteValues(), Throws.Nothing);
Assert.That(() => CfGetSyncRootInfoByHandle<CF_SYNC_ROOT_STANDARD_INFO>(hFile).WriteValues(), Throws.Nothing);
}
[Test]
public void CfGetSyncRootInfoByPathTest()
{
using (var mem = SafeHGlobalHandle.CreateFromStructure<CF_SYNC_ROOT_BASIC_INFO>())
{
Assert.That(CfGetSyncRootInfoByPath(syncRootPath, CF_SYNC_ROOT_INFO_CLASS.CF_SYNC_ROOT_INFO_BASIC, mem, mem.Size, out var len), ResultIs.Successful);
mem.ToStructure<CF_SYNC_ROOT_BASIC_INFO>().WriteValues();
}
Assert.That(() => CfGetSyncRootInfoByPath<CF_SYNC_ROOT_PROVIDER_INFO>(syncRootPath).WriteValues(), Throws.Nothing);
Assert.That(() => CfGetSyncRootInfoByPath<CF_SYNC_ROOT_STANDARD_INFO>(syncRootPath).WriteValues(), Throws.Nothing);
}
[Test]
public void CfOpenFileWithOplockTest()
{

View File

@ -0,0 +1,458 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using Vanara.Extensions;
using Vanara.InteropServices;
using Windows.Security.Cryptography;
using Windows.Storage;
using Windows.Storage.Provider;
using static Vanara.PInvoke.CldApi;
using static Vanara.PInvoke.Kernel32;
using static Vanara.PInvoke.SearchApi;
namespace Vanara.PInvoke.Tests
{
public class CloudSyncCallbackArgs<T> : EventArgs where T : struct
{
public CloudSyncCallbackArgs(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters)
{
ConnectionKey = CallbackInfo.ConnectionKey;
CallbackContext = CallbackInfo.CallbackContext;
VolumeGuidName = CallbackInfo.VolumeGuidName;
VolumeDosName = CallbackInfo.VolumeDosName;
VolumeSerialNumber = CallbackInfo.VolumeSerialNumber;
SyncRootFileId = CallbackInfo.SyncRootFileId;
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;
CorrelationVector = CallbackInfo.CorrelationVector.ToNullableStructure<CORRELATION_VECTOR>();
ProcessInfo = CallbackInfo.ProcessInfo.ToNullableStructure<CF_PROCESS_INFO>();
RequestKey = CallbackInfo.RequestKey;
ParamData = CallbackParameters.GetParam<T>();
}
/// <summary>points to an opaque blob that the sync provider provides at the sync root connect time.</summary>
public IntPtr CallbackContext { get; }
/// <summary>An opaque handle created by CfConnectSyncRoot for a sync root managed by the sync provider.</summary>
public CF_CONNECTION_KEY ConnectionKey { get; }
/// <summary>An optional correlation vector.</summary>
public CORRELATION_VECTOR? CorrelationVector { get; }
/// <summary>A 64 bit file system maintained, volume-wide unique ID of the placeholder file/directory to be serviced.</summary>
public long FileId { get; }
/// <summary>Points to the opaque blob that the sync provider provides at the placeholder creation/conversion/update time.</summary>
public SafeAllocatedMemoryHandle FileIdentity { get; }
/// <summary>The logical size of the placeholder file to be serviced. It is always 0 if the subject of the callback is a directory.</summary>
public long FileSize { get; }
/// <summary>
/// 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.
/// </summary>
public string NormalizedPath { get; }
/// <summary>Contains callback specific parameters for this action.</summary>
public T ParamData { get; }
/// <summary>
/// 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).
/// </summary>
public byte PriorityHint { get; }
/// <summary>Points to a structure that contains the information about the user process that triggers this callback.</summary>
public CF_PROCESS_INFO? ProcessInfo { get; }
/// <summary/>
public CF_REQUEST_KEY RequestKey { get; }
/// <summary>
/// A 64 bit file system maintained volume-wide unique ID of the sync root under which the placeholder file/directory to be serviced resides.
/// </summary>
public long SyncRootFileId { get; }
/// <summary>Points to the opaque blob provided by the sync provider at the sync root registration time.</summary>
public SafeAllocatedMemoryHandle SyncRootIdentity { get; }
/// <summary>
/// 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.
/// </summary>
public CF_TRANSFER_KEY TransferKey { get; }
/// <summary>DOS drive letter of the volume in the form of “X:” where X is the drive letter.</summary>
public string VolumeDosName { get; }
/// <summary>GUID name of the volume on which the placeholder file/directory to be serviced resides. It is in the form: “\?\Volume{GUID}”.</summary>
public string VolumeGuidName { get; }
/// <summary>The serial number of the volume.</summary>
public uint VolumeSerialNumber { get; }
}
public class PlaceHolderDirectoryInfo : PlaceholderInfo
{
/// <summary>The newly created child placeholder directory is considered to have all of its children present locally.</summary>
public bool DisableOnDemandPopulation;
}
public class PlaceHolderFileInfo : PlaceholderInfo
{
/// <summary>The size of the file, in bytes.</summary>
public long FileSize;
/// <summary>The newly created placeholder is marked as in-sync. Applicable to both placeholder files and directories.</summary>
public bool InSync;
public static PlaceHolderFileInfo CreateFromFile(FileInfo fileInfo, bool inSync = true, string relativeFilePath = null)
{
return new PlaceHolderFileInfo
{
ChangeTime = fileInfo.LastWriteTime,
CreationTime = fileInfo.CreationTime,
FileAttributes = fileInfo.Attributes,
FileSize = fileInfo.Length,
LastAccessTime = fileInfo.LastAccessTime,
LastWriteTime = fileInfo.LastWriteTime,
RelativePath = relativeFilePath ?? fileInfo.FullName,
InSync = inSync,
};
}
}
public abstract class PlaceholderInfo
{
/// <summary>The time the file was changed in FILETIME format.</summary>
public DateTime ChangeTime;
/// <summary>The final USN value after create actions are performed.</summary>
public int CreateUsn;
/// <summary>
/// The time the file was created in FILETIME format, which is a 64-bit value representing the number of 100-nanosecond intervals
/// since January 1, 1601 (UTC).
/// </summary>
public DateTime CreationTime;
/// <summary>
/// The file attributes. For a list of attributes, see File Attribute Constants. If this is set to 0 in a <c>FILE_BASIC_INFO</c>
/// structure passed to SetFileInformationByHandle then none of the attributes are changed.
/// </summary>
public System.IO.FileAttributes FileAttributes;
/// <summary>
/// A user mode buffer containing file information supplied by the sync provider. This is required for files (not for directories).
/// </summary>
public IntPtr FileIdentity;
/// <summary>Length, in bytes, of the <c>FileIdentity</c>.</summary>
public uint FileIdentityLength;
/// <summary>The time the file was last accessed in FILETIME format.</summary>
public DateTime LastAccessTime;
/// <summary>The time the file was last written to in FILETIME format.</summary>
public DateTime LastWriteTime;
/// <summary>The name of the child placeholder file or directory to be created.</summary>
public string RelativePath;
/// <summary>The result of placeholder creation. On successful creation, the value is: STATUS_OK.</summary>
public HRESULT Result;
}
internal class CloudSyncProvider : IDisposable
{
private const string MSSEARCH_INDEX = "SystemIndex";
private CF_CONNECTION_KEY? key = null;
public CloudSyncProvider(string syncRootPath, string name, string iconResource = ",0", Version version = null, IEnumerable<(string name, int id)> customProperties = null)
{
if (name.Contains(" "))
throw new ArgumentException("Name cannot have spaces.", nameof(name));
if (!Directory.Exists(syncRootPath))
Directory.CreateDirectory(syncRootPath);
SyncRootPath = syncRootPath;
DisplayName = name;
IconResource = iconResource;
RecycleBinUri = new Uri($"http://{name.ToLower()}.test.com/recyclebin");
SyncRootId = $"{name}!{WindowsIdentity.GetCurrent().User}!{Environment.UserName}";
Version = version ?? new Version(1, 0);
PropertyDefinitions = customProperties?.Select(t => new StorageProviderItemPropertyDefinition { DisplayNameResource = t.name, Id = t.id }).ToList();
if (!IsSyncRoot(syncRootPath))
Mount();
ConnectSyncRootTransferCallbacks();
}
public event EventHandler<CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_CANCEL>> CancelFetchData;
public event EventHandler<CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_CANCEL>> CancelFetchPlaceholders;
public event EventHandler<CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_FETCHDATA>> FetchData;
public event EventHandler<CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_FETCHPLACEHOLDERS>> FetchPlaceholders;
public event EventHandler<CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_DEHYDRATE>> NotifyDehydrate;
public event EventHandler<CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_DEHYDRATECOMPLETION>> NotifyDehydrateCompletion;
public event EventHandler<CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_DELETE>> NotifyDelete;
public event EventHandler<CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_DELETECOMPLETION>> NotifyDeleteCompletion;
public event EventHandler<CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_CLOSECOMPLETION>> NotifyFileCloseCompletion;
public event EventHandler<CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_OPENCOMPLETION>> NotifyFileOpenCompletion;
public event EventHandler<CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_RENAME>> NotifyRename;
public event EventHandler<CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_RENAMECOMPLETION>> NotifyRenameCompletion;
public event EventHandler<CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_VALIDATEDATA>> ValidateData;
public string DisplayName { get; }
public string IconResource { get; }
public IEnumerable<StorageProviderItemPropertyDefinition> PropertyDefinitions { get; }
public Uri RecycleBinUri { get; }
public string SyncRootId { get; }
public string SyncRootPath { get; }
public Version Version { get; }
public static bool IsSyncRoot(string syncRootPath)
{
using var mem = SafeHGlobalHandle.CreateFromStructure<CF_SYNC_ROOT_BASIC_INFO>();
return CfGetSyncRootInfoByPath(syncRootPath, CF_SYNC_ROOT_INFO_CLASS.CF_SYNC_ROOT_INFO_BASIC, mem, mem.Size, out var len).Succeeded;
}
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);
}
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);
CfConvertToPlaceholder(fileHandle, fileIdentity, fileIdentityLength, flags, out var usn).ThrowIfFailed();
return usn;
}
public int CreatePlaceholderFromFile(string relativeFilePath, FileInfo fileInfo, bool inSync = true)
{
using var pRelativeName = new SafeCoTaskMemString(relativeFilePath);
var ph = new PlaceholderInfo[] { PlaceHolderFileInfo.CreateFromFile(fileInfo, inSync, relativeFilePath) };
ph[0].FileIdentity = pRelativeName;
ph[0].FileIdentityLength = pRelativeName.Size;
if (CreatePlaceholders(ph) != 1)
throw ph[0].Result.GetException();
return ph[0].CreateUsn;
}
public uint CreatePlaceholders(IList<PlaceholderInfo> placeholders)
{
var entries = new CF_PLACEHOLDER_CREATE_INFO[placeholders.Count];
for (int i = 0; i < placeholders.Count; i++)
{
var ph = placeholders[i];
var flags = ph is PlaceHolderFileInfo phf && phf.InSync ? CF_PLACEHOLDER_CREATE_FLAGS.CF_PLACEHOLDER_CREATE_FLAG_MARK_IN_SYNC : CF_PLACEHOLDER_CREATE_FLAGS.CF_PLACEHOLDER_CREATE_FLAG_NONE;
if (ph is PlaceHolderDirectoryInfo phd && phd.DisableOnDemandPopulation)
flags |= CF_PLACEHOLDER_CREATE_FLAGS.CF_PLACEHOLDER_CREATE_FLAG_DISABLE_ON_DEMAND_POPULATION;
entries[i] = new CF_PLACEHOLDER_CREATE_INFO
{
FileIdentity = ph.FileIdentity,
FileIdentityLength = ph.FileIdentityLength,
RelativeFileName = ph.RelativePath,
Flags = flags,
FsMetadata = new CF_FS_METADATA
{
FileSize = (ph as PlaceHolderFileInfo)?.FileSize ?? 0,
BasicInfo = new FILE_BASIC_INFO
{
FileAttributes = (FileFlagsAndAttributes)ph.FileAttributes,
CreationTime = ph.CreationTime.ToFileTimeStruct(),
LastWriteTime = ph.LastWriteTime.ToFileTimeStruct(),
LastAccessTime = ph.LastAccessTime.ToFileTimeStruct(),
ChangeTime = ph.LastWriteTime.ToFileTimeStruct(),
}
}
};
}
CfCreatePlaceholders(SyncRootPath, entries, (uint)entries.Length, CF_CREATE_FLAGS.CF_CREATE_FLAG_NONE, out var done);
for (int i = 0; i < entries.Length; i++)
{
placeholders[i].CreateUsn = entries[i].CreateUsn;
placeholders[i].Result = entries[i].Result;
}
return done;
//try
//{
// var prop = new StorageProviderItemProperty
// {
// Id = 1,
// Value = "Value1",
// // This icon is just for the sample. You should provide your own branded icon here
// IconResource = "shell32.dll,-44"
// };
// Console.Write("Applying custom state for {0}\n", relativeFilePath); Utilities.ApplyCustomStateToPlaceholderFile(destPath,
// relativeFilePath, prop);
// if ((findData.dwFileAttributes & FileAttributes.Directory) != 0)
// {
// Create(syncRootPath, relativeFilePath, destPath);
// }
//}
//catch (Exception ex)
//{
// // to_hresult() will eat the exception if it is a result of check_hresult, otherwise the exception will get rethrown and
// // this method will crash out as it should
// Console.Write("Failed to set custom state on {0} with {1:X8}\n", relativeFilePath, ex.HResult);
// // Eating it here lets other files still get a chance. Not worth crashing the sample, but certainly noteworthy for
// // production code
//}
}
public void Dispose()
{
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) =>
CancelFetchData?.Invoke(this, new CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_CANCEL>(CallbackInfo, CallbackParameters));
protected virtual void OnCancelFetchPlaceholders(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) =>
CancelFetchPlaceholders?.Invoke(this, new CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_CANCEL>(CallbackInfo, CallbackParameters));
protected virtual void OnFetchData(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) =>
FetchData?.Invoke(this, new CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_FETCHDATA>(CallbackInfo, CallbackParameters));
protected virtual void OnFetchPlaceholders(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) =>
FetchPlaceholders?.Invoke(this, new CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_FETCHPLACEHOLDERS>(CallbackInfo, CallbackParameters));
protected virtual void OnNotifyDehydrate(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) =>
NotifyDehydrate?.Invoke(this, new CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_DEHYDRATE>(CallbackInfo, CallbackParameters));
protected virtual void OnNotifyDehydrateCompletion(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) =>
NotifyDehydrateCompletion?.Invoke(this, new CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_DEHYDRATECOMPLETION>(CallbackInfo, CallbackParameters));
protected virtual void OnNotifyDelete(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) =>
NotifyDelete?.Invoke(this, new CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_DELETE>(CallbackInfo, CallbackParameters));
protected virtual void OnNotifyDeleteCompletion(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) =>
NotifyDeleteCompletion?.Invoke(this, new CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_DELETECOMPLETION>(CallbackInfo, CallbackParameters));
protected virtual void OnNotifyFileCloseCompletion(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) =>
NotifyFileCloseCompletion?.Invoke(this, new CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_CLOSECOMPLETION>(CallbackInfo, CallbackParameters));
protected virtual void OnNotifyFileOpenCompletion(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) =>
NotifyFileOpenCompletion?.Invoke(this, new CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_OPENCOMPLETION>(CallbackInfo, CallbackParameters));
protected virtual void OnNotifyRename(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) =>
NotifyRename?.Invoke(this, new CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_RENAME>(CallbackInfo, CallbackParameters));
protected virtual void OnNotifyRenameCompletion(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) =>
NotifyRenameCompletion?.Invoke(this, new CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_RENAMECOMPLETION>(CallbackInfo, CallbackParameters));
protected virtual void OnValidateData(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) =>
ValidateData?.Invoke(this, new CloudSyncCallbackArgs<CF_CALLBACK_PARAMETERS_VALIDATEDATA>(CallbackInfo, CallbackParameters));
private static void AddFolderToSearchIndexer(string folder)
{
var url = "file:///" + folder;
using var searchManager = ComReleaserFactory.Create(new ISearchManager());
using var searchCatalogManager = ComReleaserFactory.Create(searchManager.Item.GetCatalog(MSSEARCH_INDEX));
using var searchCrawlScopeManager = ComReleaserFactory.Create(searchCatalogManager.Item.GetCrawlScopeManager());
searchCrawlScopeManager.Item.AddDefaultScopeRule(url, true, FOLLOW_FLAGS.FF_INDEXCOMPLEXURLS);
searchCrawlScopeManager.Item.SaveAll();
}
private void ConnectSyncRootTransferCallbacks()
{
CF_CALLBACK_REGISTRATION[] callbackTable =
{
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 },
new CF_CALLBACK_REGISTRATION { Type = CF_CALLBACK_TYPE.CF_CALLBACK_TYPE_FETCH_DATA, Callback = OnFetchData },
new CF_CALLBACK_REGISTRATION { Type = CF_CALLBACK_TYPE.CF_CALLBACK_TYPE_FETCH_PLACEHOLDERS, Callback = OnFetchPlaceholders },
new CF_CALLBACK_REGISTRATION { Type = CF_CALLBACK_TYPE.CF_CALLBACK_TYPE_NOTIFY_DEHYDRATE, Callback = OnNotifyDehydrate },
new CF_CALLBACK_REGISTRATION { Type = CF_CALLBACK_TYPE.CF_CALLBACK_TYPE_NOTIFY_DEHYDRATE_COMPLETION, Callback = OnNotifyDehydrateCompletion },
new CF_CALLBACK_REGISTRATION { Type = CF_CALLBACK_TYPE.CF_CALLBACK_TYPE_NOTIFY_DELETE, Callback = OnNotifyDelete },
new CF_CALLBACK_REGISTRATION { Type = CF_CALLBACK_TYPE.CF_CALLBACK_TYPE_NOTIFY_DELETE_COMPLETION, Callback = OnNotifyDeleteCompletion },
new CF_CALLBACK_REGISTRATION { Type = CF_CALLBACK_TYPE.CF_CALLBACK_TYPE_NOTIFY_FILE_CLOSE_COMPLETION, Callback = OnNotifyFileCloseCompletion },
new CF_CALLBACK_REGISTRATION { Type = CF_CALLBACK_TYPE.CF_CALLBACK_TYPE_NOTIFY_FILE_OPEN_COMPLETION, Callback = OnNotifyFileOpenCompletion },
new CF_CALLBACK_REGISTRATION { Type = CF_CALLBACK_TYPE.CF_CALLBACK_TYPE_NOTIFY_RENAME, Callback = OnNotifyRename },
new CF_CALLBACK_REGISTRATION { Type = CF_CALLBACK_TYPE.CF_CALLBACK_TYPE_NOTIFY_RENAME_COMPLETION, Callback = OnNotifyRenameCompletion },
new CF_CALLBACK_REGISTRATION { Type = CF_CALLBACK_TYPE.CF_CALLBACK_TYPE_VALIDATE_DATA, Callback = OnValidateData },
CF_CALLBACK_REGISTRATION.CF_CALLBACK_REGISTRATION_END
};
CfConnectSyncRoot(SyncRootPath, callbackTable, default, CF_CONNECT_FLAGS.CF_CONNECT_FLAG_NONE, out var ckey).ThrowIfFailed();
key = ckey;
}
private void Mount()
{
AddFolderToSearchIndexer(SyncRootPath);
RegisterWithShell().Wait();
}
private async Task RegisterWithShell()
{
var info = new StorageProviderSyncRootInfo
{
Context = CryptographicBuffer.ConvertStringToBinary(SyncRootId, BinaryStringEncoding.Utf8),
DisplayNameResource = DisplayName,
HardlinkPolicy = StorageProviderHardlinkPolicy.None,
HydrationPolicy = StorageProviderHydrationPolicy.Full,
HydrationPolicyModifier = StorageProviderHydrationPolicyModifier.None,
IconResource = IconResource,
Id = SyncRootId,
InSyncPolicy = StorageProviderInSyncPolicy.FileCreationTime | StorageProviderInSyncPolicy.DirectoryCreationTime,
Path = await StorageFolder.GetFolderFromPathAsync(SyncRootPath),
PopulationPolicy = StorageProviderPopulationPolicy.AlwaysFull,
RecycleBinUri = RecycleBinUri,
ShowSiblingsAsGroup = false,
Version = Version.ToString(),
};
if (PropertyDefinitions != null)
foreach (var pd in PropertyDefinitions)
info.StorageProviderItemPropertyDefinitions.Add(pd);
StorageProviderSyncRootManager.Register(info);
// Give the cache some time to invalidate
Thread.Sleep(1000);
}
private void UnregisterWithShell()
{
StorageProviderSyncRootManager.Unregister(SyncRootId);
}
}
}