From 1efa3a860afcea499ccd971cdd671f861e88325a Mon Sep 17 00:00:00 2001 From: dahall Date: Fri, 1 May 2020 15:26:16 -0600 Subject: [PATCH] Completed work on CldApi with unit tests --- PInvoke/CldApi/cfapi.Funcs.cs | 4 +- PInvoke/CldApi/cfapi.cs | 428 ++++++++++++++------------ UnitTests/PInvoke/CldApi/CldApiTests.cs | 158 +++++++++- UnitTests/PInvoke/CldApi/CloudSyncProvider.cs | 135 +++++--- 4 files changed, 468 insertions(+), 257 deletions(-) diff --git a/PInvoke/CldApi/cfapi.Funcs.cs b/PInvoke/CldApi/cfapi.Funcs.cs index 9f57e2ab..5d22fa54 100644 --- a/PInvoke/CldApi/cfapi.Funcs.cs +++ b/PInvoke/CldApi/cfapi.Funcs.cs @@ -377,7 +377,7 @@ namespace Vanara.PInvoke // InfoBuffer, DWORD InfoBufferLength, PDWORD ReturnedLength ); [DllImport(Lib.CldApi, SetLastError = false, ExactSpelling = true)] [PInvokeData("cfapi.h", MSDNShortId = "B7FE94BC-DC59-407D-85A6-9657E38975AB")] - public static extern HRESULT CfGetPlaceholderRangeInfo(HFILE FileHandle, CF_PLACEHOLDER_RANGE_INFO_CLASS InfoClass, long StartingOffset, long Length, [Out] IntPtr InfoBuffer, uint InfoBufferLength, ref uint ReturnedLength); + public static extern HRESULT CfGetPlaceholderRangeInfo(HFILE FileHandle, CF_PLACEHOLDER_RANGE_INFO_CLASS InfoClass, long StartingOffset, long Length, [Out] IntPtr InfoBuffer, uint InfoBufferLength, out uint ReturnedLength); /// Gets a set of placeholder states based on the FileAttributes and ReparseTag values of the file. /// The file attribute information. @@ -679,7 +679,7 @@ namespace Vanara.PInvoke // FileHandle, CF_TRANSFER_KEY *TransferKey ); [DllImport(Lib.CldApi, SetLastError = false, ExactSpelling = true)] [PInvokeData("cfapi.h", MSDNShortId = "53B40C34-EB1F-445B-B1B3-B539C2FADECE")] - public static extern void CfReleaseTransferKey(HFILE FileHandle, out CF_TRANSFER_KEY TransferKey); + public static extern void CfReleaseTransferKey(HFILE FileHandle, in CF_TRANSFER_KEY TransferKey); /// Allows a sync provider to report progress out-of-band. /// A connection key representing a communication channel with the sync filter. diff --git a/PInvoke/CldApi/cfapi.cs b/PInvoke/CldApi/cfapi.cs index 959f86f8..524fc1d4 100644 --- a/PInvoke/CldApi/cfapi.cs +++ b/PInvoke/CldApi/cfapi.cs @@ -147,7 +147,7 @@ namespace Vanara.PInvoke CF_CALLBACK_FETCH_DATA_FLAG_RECOVERY = 1, /// - /// Note This value is new for Windows 10, version 1803.Flag to be used if the callback is invoked as a result of a call to CfHydratePlaceholder. + /// Note This value is new for Windows 10, version 1803. Flag to be used if the callback is invoked as a result of a call to CfHydratePlaceholder. /// CF_CALLBACK_FETCH_DATA_FLAG_EXPLICIT_HYDRATION = 2, } @@ -1342,51 +1342,53 @@ 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.Sequential, Pack = 8)] + [StructLayout(LayoutKind.Sequential)] public struct CF_CALLBACK_PARAMETERS { /// public uint ParamSize; + private uint pad; + /// [MarshalAs(UnmanagedType.ByValArray, SizeConst = 56)] public byte[] Content; /// - public CF_CALLBACK_PARAMETERS_CANCEL Cancel => GetParam(); + public CANCEL Cancel => GetParam(); /// - public CF_CALLBACK_PARAMETERS_FETCHDATA FetchData => GetParam(); + public FETCHDATA FetchData => GetParam(); /// - public CF_CALLBACK_PARAMETERS_VALIDATEDATA ValidateData => GetParam(); + public VALIDATEDATA ValidateData => GetParam(); /// - public CF_CALLBACK_PARAMETERS_FETCHPLACEHOLDERS FetchPlaceholders => GetParam(); + public FETCHPLACEHOLDERS FetchPlaceholders => GetParam(); /// - public CF_CALLBACK_PARAMETERS_OPENCOMPLETION OpenCompletion => GetParam(); + public OPENCOMPLETION OpenCompletion => GetParam(); /// - public CF_CALLBACK_PARAMETERS_CLOSECOMPLETION CloseCompletion => GetParam(); + public CLOSECOMPLETION CloseCompletion => GetParam(); /// - public CF_CALLBACK_PARAMETERS_DEHYDRATE Dehydrate => GetParam(); + public DEHYDRATE Dehydrate => GetParam(); /// - public CF_CALLBACK_PARAMETERS_DEHYDRATECOMPLETION DehydrateCompletion => GetParam(); + public DEHYDRATECOMPLETION DehydrateCompletion => GetParam(); /// - public CF_CALLBACK_PARAMETERS_DELETE Delete => GetParam(); + public DELETE Delete => GetParam(); /// - public CF_CALLBACK_PARAMETERS_DELETECOMPLETION DeleteCompletion => GetParam(); + public DELETECOMPLETION DeleteCompletion => GetParam(); /// - public CF_CALLBACK_PARAMETERS_RENAME Rename => GetParam(); + public RENAME Rename => GetParam(); /// - public CF_CALLBACK_PARAMETERS_RENAMECOMPLETION RenameCompletion => GetParam(); + public RENAMECOMPLETION RenameCompletion => GetParam(); /// Gets the parameter value for this structure. /// The type of the structure to retrieve. @@ -1396,180 +1398,180 @@ namespace Vanara.PInvoke using var ptr = new SafeHGlobalHandle(Content); return ptr.ToStructure(); } - } - /// Contains callback specific parameters such as file offset, length, flags, etc. - [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] - [StructLayout(LayoutKind.Sequential, Pack = 8)] - public struct CF_CALLBACK_PARAMETERS_CANCEL - { - /// Cancel data flags. - public CF_CALLBACK_CANCEL_FLAGS Flags; - - /// - public CANCELFETCHDATA FetchData; - - /// - [StructLayout(LayoutKind.Sequential)] - public struct CANCELFETCHDATA + /// Contains callback specific parameters such as file offset, length, flags, etc. + [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] + [StructLayout(LayoutKind.Sequential, Pack = 8)] + public struct CANCEL { - /// Offset, in bytes, for specifying the range of data. - public long FileOffset; + /// Cancel data flags. + public CF_CALLBACK_CANCEL_FLAGS Flags; - /// Length of the data in bytes. - public long Length; + /// + public CANCELFETCHDATA FetchData; + + /// + [StructLayout(LayoutKind.Sequential)] + public struct CANCELFETCHDATA + { + /// Offset, in bytes, for specifying the range of data. + public long FileOffset; + + /// Length of the data in bytes. + public long Length; + } } - } - /// Contains callback specific parameters such as file offset, length, flags, etc. - [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] - [StructLayout(LayoutKind.Sequential)] - public struct CF_CALLBACK_PARAMETERS_CLOSECOMPLETION - { - /// Placeholder close completion flags. - public CF_CALLBACK_CLOSE_COMPLETION_FLAGS Flags; - } + /// Contains callback specific parameters such as file offset, length, flags, etc. + [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] + [StructLayout(LayoutKind.Sequential)] + public struct CLOSECOMPLETION + { + /// Placeholder close completion flags. + public CF_CALLBACK_CLOSE_COMPLETION_FLAGS Flags; + } - /// Contains callback specific parameters such as file offset, length, flags, etc. - [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] - [StructLayout(LayoutKind.Sequential)] - public struct CF_CALLBACK_PARAMETERS_DEHYDRATE - { - /// Placeholder dehydration flags. - public CF_CALLBACK_DEHYDRATE_FLAGS Flags; + /// Contains callback specific parameters such as file offset, length, flags, etc. + [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] + [StructLayout(LayoutKind.Sequential)] + public struct DEHYDRATE + { + /// Placeholder dehydration flags. + public CF_CALLBACK_DEHYDRATE_FLAGS Flags; - /// - public CF_CALLBACK_DEHYDRATION_REASON Reason; - } + /// + public CF_CALLBACK_DEHYDRATION_REASON Reason; + } - /// Contains callback specific parameters such as file offset, length, flags, etc. - [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] - [StructLayout(LayoutKind.Sequential)] - public struct CF_CALLBACK_PARAMETERS_DEHYDRATECOMPLETION - { - /// - public CF_CALLBACK_DEHYDRATE_COMPLETION_FLAGS Flags; + /// Contains callback specific parameters such as file offset, length, flags, etc. + [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] + [StructLayout(LayoutKind.Sequential)] + public struct DEHYDRATECOMPLETION + { + /// + public CF_CALLBACK_DEHYDRATE_COMPLETION_FLAGS Flags; - /// - public CF_CALLBACK_DEHYDRATION_REASON Reason; - } + /// + public CF_CALLBACK_DEHYDRATION_REASON Reason; + } - /// Contains callback specific parameters such as file offset, length, flags, etc. - [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] - [StructLayout(LayoutKind.Sequential)] - public struct CF_CALLBACK_PARAMETERS_DELETE - { - /// Placeholder deletion flags. - public CF_CALLBACK_DELETE_FLAGS Flags; - } + /// Contains callback specific parameters such as file offset, length, flags, etc. + [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] + [StructLayout(LayoutKind.Sequential)] + public struct DELETE + { + /// Placeholder deletion flags. + public CF_CALLBACK_DELETE_FLAGS Flags; + } - /// Contains callback specific parameters such as file offset, length, flags, etc. - [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct CF_CALLBACK_PARAMETERS_DELETECOMPLETION - { - /// Placeholder deletion complete flags. - public CF_CALLBACK_DELETE_COMPLETION_FLAGS Flags; - } + /// Contains callback specific parameters such as file offset, length, flags, etc. + [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] + [StructLayout(LayoutKind.Sequential)] + public struct DELETECOMPLETION + { + /// Placeholder deletion complete flags. + public CF_CALLBACK_DELETE_COMPLETION_FLAGS Flags; + } - /// Contains callback specific parameters such as file offset, length, flags, etc. - [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] - [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 56)] - public struct CF_CALLBACK_PARAMETERS_FETCHDATA - { - /// Fetch data flags. - public CF_CALLBACK_FETCH_DATA_FLAGS Flags; + /// Contains callback specific parameters such as file offset, length, flags, etc. + [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] + [StructLayout(LayoutKind.Sequential)] + public struct FETCHDATA + { + /// Fetch data flags. + public CF_CALLBACK_FETCH_DATA_FLAGS Flags; - /// Offset, in bytes, for specifying the required range of data. - public long RequiredFileOffset; + /// Offset, in bytes, for specifying the required range of data. + public long RequiredFileOffset; - /// Length of the required data to retrieve, in bytes. - public long RequiredLength; + /// Length of the required data to retrieve, in bytes. + public long RequiredLength; - /// - /// 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. - /// - public long OptionalFileOffset; + /// + /// 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. + /// + public long OptionalFileOffset; - /// - /// 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. - /// - public long OptionalLength; + /// + /// 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. + /// + public long OptionalLength; - /// - public long LastDehydrationTime; + /// + public long LastDehydrationTime; - /// - public CF_CALLBACK_DEHYDRATION_REASON LastDehydrationReason; - } + /// + public CF_CALLBACK_DEHYDRATION_REASON LastDehydrationReason; + } - /// Contains callback specific parameters such as file offset, length, flags, etc. - [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] - [StructLayout(LayoutKind.Sequential, Pack = 8)] - public struct CF_CALLBACK_PARAMETERS_FETCHPLACEHOLDERS - { - /// Flags for fetching placeholder metadata. - public CF_CALLBACK_FETCH_PLACEHOLDERS_FLAGS Flags; + /// Contains callback specific parameters such as file offset, length, flags, etc. + [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] + [StructLayout(LayoutKind.Sequential)] + public struct FETCHPLACEHOLDERS + { + /// Flags for fetching placeholder metadata. + public CF_CALLBACK_FETCH_PLACEHOLDERS_FLAGS Flags; - /// - /// 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. - /// - [MarshalAs(UnmanagedType.LPWStr)] - public string Pattern; - } + /// + /// 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. + /// + [MarshalAs(UnmanagedType.LPWStr)] + public string Pattern; + } - /// Contains callback specific parameters such as file offset, length, flags, etc. - [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] - [StructLayout(LayoutKind.Sequential)] - public struct CF_CALLBACK_PARAMETERS_OPENCOMPLETION - { - /// Placeholder open completion flags. - public CF_CALLBACK_OPEN_COMPLETION_FLAGS Flags; - } + /// Contains callback specific parameters such as file offset, length, flags, etc. + [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] + [StructLayout(LayoutKind.Sequential)] + public struct OPENCOMPLETION + { + /// Placeholder open completion flags. + public CF_CALLBACK_OPEN_COMPLETION_FLAGS Flags; + } - /// Contains callback specific parameters such as file offset, length, flags, etc. - [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] - [StructLayout(LayoutKind.Sequential, Pack = 8)] - public struct CF_CALLBACK_PARAMETERS_RENAME - { - /// Rename placeholder flags. - public CF_CALLBACK_RENAME_FLAGS Flags; + /// Contains callback specific parameters such as file offset, length, flags, etc. + [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] + [StructLayout(LayoutKind.Sequential)] + public struct RENAME + { + /// Rename placeholder flags. + public CF_CALLBACK_RENAME_FLAGS Flags; - /// The full rename/move target path relative to the volume. - [MarshalAs(UnmanagedType.LPWStr)] - public string TargetPath; - } + /// The full rename/move target path relative to the volume. + [MarshalAs(UnmanagedType.LPWStr)] + public string TargetPath; + } - /// Contains callback specific parameters such as file offset, length, flags, etc. - [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] - [StructLayout(LayoutKind.Sequential, Pack = 8)] - public struct CF_CALLBACK_PARAMETERS_RENAMECOMPLETION - { - /// Rename completion placeholder flags. - public CF_CALLBACK_RENAME_COMPLETION_FLAGS Flags; + /// Contains callback specific parameters such as file offset, length, flags, etc. + [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] + [StructLayout(LayoutKind.Sequential)] + public struct RENAMECOMPLETION + { + /// Rename completion placeholder flags. + public CF_CALLBACK_RENAME_COMPLETION_FLAGS Flags; - /// The full source link path relative to the volume. - [MarshalAs(UnmanagedType.LPWStr)] - public string SourcePath; - } + /// The full source link path relative to the volume. + [MarshalAs(UnmanagedType.LPWStr)] + public string SourcePath; + } - /// Contains callback specific parameters such as file offset, length, flags, etc. - [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] - [StructLayout(LayoutKind.Sequential, Pack = 8)] - public struct CF_CALLBACK_PARAMETERS_VALIDATEDATA - { - /// Data validation flags. - public CF_CALLBACK_VALIDATE_DATA_FLAGS Flags; + /// Contains callback specific parameters such as file offset, length, flags, etc. + [PInvokeData("cfapi.h", MSDNShortId = "FA403E9E-5EFA-4285-9619-614DB0044FFB")] + [StructLayout(LayoutKind.Sequential)] + public struct VALIDATEDATA + { + /// Data validation flags. + public CF_CALLBACK_VALIDATE_DATA_FLAGS Flags; - /// Offset, in bytes, for specifying the range of data to validate. - public long RequiredFileOffset; + /// Offset, in bytes, for specifying the range of data to validate. + public long RequiredFileOffset; - /// Length, in bytes, of the data to validate. - public long RequiredLength; + /// Length, in bytes, of the data to validate. + public long RequiredLength; + } } /// The callbacks to be registered by the sync provider. @@ -1721,13 +1723,83 @@ namespace Vanara.PInvoke // FileIdentityLength; } AckDehydrate; struct { CF_OPERATION_ACK_RENAME_FLAGS Flags; NTSTATUS CompletionStatus; } AckRename; struct // { CF_OPERATION_ACK_DELETE_FLAGS Flags; NTSTATUS CompletionStatus; } AckDelete; } DUMMYUNIONNAME; } CF_OPERATION_PARAMETERS; [PInvokeData("cfapi.h", MSDNShortId = "668C682E-47C2-41BC-A4F9-AA2F2B516F54")] - [StructLayout(LayoutKind.Explicit)] + [StructLayout(LayoutKind.Sequential)] public struct CF_OPERATION_PARAMETERS { /// - [FieldOffset(0)] 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; + + /// + public TRANSFERDATA TransferData { get => GetParam(); set => SetParam(value); } + + /// + public RETRIEVEDATA RetrieveData { get => GetParam(); set => SetParam(value); } + + /// + public ACKDATA AckData { get => GetParam(); set => SetParam(value); } + + /// + public RESTARTHYDRATION RestartHydration { get => GetParam(); set => SetParam(value); } + + /// + public TRANSFERPLACEHOLDERS TransferPlaceholders { get => GetParam(); set => SetParam(value); } + + /// + public ACKDEHYDRATE AckDehydrate { get => GetParam(); set => SetParam(value); } + + /// + public ACKRENAME AckRename { get => GetParam(); set => SetParam(value); } + + /// + public ACKDELETE AckDelete { get => GetParam(); set => SetParam(value); } + + /// Gets the parameter value for this structure. + /// The type of the structure to retrieve. + /// The requested structure. + public unsafe T GetParam() where T : struct + { + using var ptr = new PinnedObject(this); + return ((IntPtr)ptr).ToStructure(Marshal.SizeOf(typeof(CF_OPERATION_PARAMETERS)), 8); + } + + /// Sets the parameter value for this structure. + /// The type of the structure to set. + /// The value to set. + public void SetParam(T value) where T : struct + { + unsafe + { + fixed (ulong* p = &pad8_16) + { + ((IntPtr)(void*)p).Write(value, 0, Marshal.SizeOf(typeof(CF_OPERATION_PARAMETERS)) - 8); + } + } + } + + /// Creates a CF_OPERATION_PARAMETERS instance with the specified parameter value. + /// The parameter type. + /// The parameter value. + /// A CF_OPERATION_PARAMETERS instance initialized with and the correct ParamSize. + public static CF_OPERATION_PARAMETERS Create(T paramValue = default) where T : struct + { + var op = new CF_OPERATION_PARAMETERS { ParamSize = CF_SIZE_OF_OP_PARAM() }; + op.SetParam(paramValue); + return op; + } + + /// Gets the size value used in ParamSize given a specific parameter type. + /// The parameter type. + /// The size of the structure. + public static uint CF_SIZE_OF_OP_PARAM() where T : struct => (uint)(Marshal.OffsetOf(typeof(CF_OPERATION_PARAMETERS), nameof(pad8_16)).ToInt32() + Marshal.SizeOf(typeof(T))); + /// [StructLayout(LayoutKind.Sequential)] public struct TRANSFERDATA @@ -1748,10 +1820,6 @@ namespace Vanara.PInvoke public long Length; } - /// - [FieldOffset(4)] - public TRANSFERDATA TransferData; - /// [StructLayout(LayoutKind.Sequential)] public struct RETRIEVEDATA @@ -1772,10 +1840,6 @@ namespace Vanara.PInvoke public long ReturnedLength; } - /// - [FieldOffset(4)] - public RETRIEVEDATA RetrieveData; - /// [StructLayout(LayoutKind.Sequential)] public struct ACKDATA @@ -1796,10 +1860,6 @@ namespace Vanara.PInvoke public long Length; } - /// - [FieldOffset(4)] - public ACKDATA AckData; - /// [StructLayout(LayoutKind.Sequential)] public struct RESTARTHYDRATION @@ -1817,10 +1877,6 @@ namespace Vanara.PInvoke public uint FileIdentityLength; } - /// - [FieldOffset(4)] - public RESTARTHYDRATION RestartHydration; - /// [StructLayout(LayoutKind.Sequential)] public struct TRANSFERPLACEHOLDERS @@ -1844,10 +1900,6 @@ namespace Vanara.PInvoke public uint EntriesProcessed; } - /// - [FieldOffset(4)] - public TRANSFERPLACEHOLDERS TransferPlaceholders; - /// [StructLayout(LayoutKind.Sequential)] public struct ACKDEHYDRATE @@ -1865,10 +1917,6 @@ namespace Vanara.PInvoke public uint FileIdentityLength; } - /// - [FieldOffset(4)] - public ACKDEHYDRATE AckDehydrate; - /// [StructLayout(LayoutKind.Sequential)] public struct ACKRENAME @@ -1880,10 +1928,6 @@ namespace Vanara.PInvoke public NTStatus CompletionStatus; } - /// - [FieldOffset(4)] - public ACKRENAME AckRename; - /// [StructLayout(LayoutKind.Sequential)] public struct ACKDELETE @@ -1894,10 +1938,6 @@ namespace Vanara.PInvoke /// The completion status of the operation. public NTStatus CompletionStatus; } - - /// - [FieldOffset(4)] - public ACKDELETE AckDelete; } /// Basic placeholder information. diff --git a/UnitTests/PInvoke/CldApi/CldApiTests.cs b/UnitTests/PInvoke/CldApi/CldApiTests.cs index 2caefd2e..9ed04b71 100644 --- a/UnitTests/PInvoke/CldApi/CldApiTests.cs +++ b/UnitTests/PInvoke/CldApi/CldApiTests.cs @@ -2,9 +2,13 @@ using ICSharpCode.Decompiler.TypeSystem; using NUnit.Framework; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Vanara.Extensions; using Vanara.InteropServices; using Windows.Storage.Streams; using static Vanara.PInvoke.CldApi; @@ -32,6 +36,16 @@ namespace Vanara.PInvoke.Tests try { using var csp = new CloudSyncProvider(destDirPath, "TestSync"); + csp.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); } finally { @@ -43,28 +57,86 @@ namespace Vanara.PInvoke.Tests public void CfCreatePlaceholdersTest() { const string dest = "CfDest"; + const string fname = "test.bmp"; var destDirPath = SetupTempDir(dest); + var fpath = Path.Combine(destDirPath, fname); try { - using var csp = new CloudSyncProvider(destDirPath, "TestSync"); + var tokSrc = new CancellationTokenSource(); + var token = tokSrc.Token; + var task = Task.Run(() => + { + 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.CancelFetchData += ShowInfo; + csp.CancelFetchPlaceholders += ShowInfo; + csp.FetchData += ShowInfo; + csp.FetchPlaceholders += ShowInfo; + csp.NotifyDehydrate += ShowInfo; + csp.NotifyDehydrateCompletion += ShowInfo; + csp.NotifyDelete += ShowInfo; + csp.NotifyDeleteCompletion += ShowInfo; + csp.NotifyFileCloseCompletion += ShowInfo; + csp.NotifyFileOpenCompletion += ShowInfo; + csp.NotifyRename += ShowInfo; + csp.NotifyRenameCompletion += ShowInfo; + csp.ValidateData += ShowInfo; - 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)); + var origFileInfo = new FileInfo(TestCaseSources.BmpFile); + csp.CreatePlaceholderFromFile(fname, origFileInfo, true); + Assert.That(File.Exists(fpath), Is.True); + Assert.That(new FileInfo(fpath).Length, Is.EqualTo(origFileInfo.Length)); + + Debug.WriteLine("CSP is running...................................\n"); + + while (!token.IsCancellationRequested) { Thread.Sleep(100); } + + static void ShowInfo(object s, CloudSyncCallbackArgs e) where T : struct + { + Debug.WriteLine($"\n{typeof(T).Name}: {e.NormalizedPath ?? "(null)"}, {e.FileSize}\n" + + Newtonsoft.Json.JsonConvert.SerializeObject(e.ParamData, Newtonsoft.Json.Formatting.Indented, new Newtonsoft.Json.Converters.StringEnumConverter())); + + if (typeof(T) == typeof(CF_CALLBACK_PARAMETERS.RENAME)) + { + e.OperationType = CF_OPERATION_TYPE.CF_OPERATION_TYPE_ACK_RENAME; + e.OpParam = CF_OPERATION_PARAMETERS.Create(new CF_OPERATION_PARAMETERS.ACKRENAME()); + } + else if (typeof(T) == typeof(CF_CALLBACK_PARAMETERS.DEHYDRATE)) + { + e.OperationType = CF_OPERATION_TYPE.CF_OPERATION_TYPE_ACK_DEHYDRATE; + e.OpParam = CF_OPERATION_PARAMETERS.Create(new CF_OPERATION_PARAMETERS.ACKDEHYDRATE()); + } + else if (typeof(T) == typeof(CF_CALLBACK_PARAMETERS.DELETE)) + { + e.OperationType = CF_OPERATION_TYPE.CF_OPERATION_TYPE_ACK_DELETE; + e.OpParam = CF_OPERATION_PARAMETERS.Create(new CF_OPERATION_PARAMETERS.ACKDELETE()); + } + else if (typeof(T) == typeof(CF_CALLBACK_PARAMETERS.FETCHDATA)) + { + var opInfo = e.MakeOpInfo(CF_OPERATION_TYPE.CF_OPERATION_TYPE_TRANSFER_DATA); + using var buf = new PinnedObject(File.ReadAllBytes(TestCaseSources.BmpFile)); + var opParam = CF_OPERATION_PARAMETERS.Create(new CF_OPERATION_PARAMETERS.TRANSFERDATA { Buffer = buf, Length = new FileInfo(TestCaseSources.BmpFile).Length }); + var hr = CfExecute(opInfo, ref opParam); + if (hr.Failed) Debug.WriteLine("CfExecute for transfer failed: " + hr.FormatMessage()); + hr = CfReportProviderProgress(e.ConnectionKey, e.TransferKey, 100, 100); + if (hr.Failed) Debug.WriteLine("CfReportProviderProgress for transfer failed: " + hr.FormatMessage()); + } + } + }, tokSrc.Token); + + Thread.Sleep(5000); // Let CSP get loaded + using var hFile = GetHFILE(fpath); + Assert.That(CfHydratePlaceholder(hFile, 0, -1, 0), 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 + + tokSrc.Cancel(); + task.Wait(); //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); @@ -128,6 +200,55 @@ namespace Vanara.PInvoke.Tests } } + [Test] + public void CfSetInSyncStateTest() + { + 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); + var destFile = Path.Combine(destDirPath, "test.bmp"); + Assert.That(File.Exists(destFile), Is.True); + + using var hFile = GetHFILE(destFile); + var usn = 0; + Assert.That(CfSetInSyncState(hFile, CF_IN_SYNC_STATE.CF_IN_SYNC_STATE_IN_SYNC, CF_SET_IN_SYNC_FLAGS.CF_SET_IN_SYNC_FLAG_NONE, ref usn), ResultIs.Successful); + Assert.That(CfSetPinState(hFile, CF_PIN_STATE.CF_PIN_STATE_PINNED, CF_SET_PIN_FLAGS.CF_SET_PIN_FLAG_NONE), ResultIs.Successful); + } + finally + { + DeleteTempDir(dest); + } + } + + [Test] + public void CfGetPlaceholderStateFromAttributeTagTest() + { + const string dest = "CfDest"; + var destDirPath = SetupTempDir(dest); + try + { + using var csp = new CloudSyncProvider(destDirPath, "TestSync"); + var destFile = CopyFile(TestCaseSources.WordDoc, destDirPath); + Assert.That(() => csp.ConvertToPlaceholder(destFile), Throws.Nothing); + + using var hFile = GetHFILE(destFile); + Kernel32.FILE_ATTRIBUTE_TAG_INFO info = default; + Assert.That(() => info = Kernel32.GetFileInformationByHandleEx(hFile, Kernel32.FILE_INFO_BY_HANDLE_CLASS.FileAttributeTagInfo), Throws.Nothing); + info.WriteValues(); + CfGetPlaceholderStateFromAttributeTag(info.FileAttributes, info.ReparseTag).WriteValues(); + + using var mem = SafeHGlobalHandle.CreateFromStructure(info); + CfGetPlaceholderStateFromFileInfo(mem, Kernel32.FILE_INFO_BY_HANDLE_CLASS.FileAttributeTagInfo).WriteValues(); + } + finally + { + DeleteTempDir(dest); + } + } + [Test] public void CfGetPlatformInfoTest() { @@ -166,6 +287,9 @@ namespace Vanara.PInvoke.Tests { Assert.That(CfOpenFileWithOplock(TestCaseSources.SmallFile, CF_OPEN_FILE_FLAGS.CF_OPEN_FILE_FLAG_EXCLUSIVE, out var handle), ResultIs.Successful); Assert.That(handle, ResultIs.ValidHandle); + Assert.That(CfReferenceProtectedHandle(handle), ResultIs.Successful); + Assert.That(CfGetWin32HandleFromProtectedHandle(handle), ResultIs.ValidHandle); + Assert.That(() => CfReleaseProtectedHandle(handle), Throws.Nothing); Assert.That(() => handle.Dispose(), Throws.Nothing); } } diff --git a/UnitTests/PInvoke/CldApi/CloudSyncProvider.cs b/UnitTests/PInvoke/CldApi/CloudSyncProvider.cs index 43cf1b94..a3f1a6cd 100644 --- a/UnitTests/PInvoke/CldApi/CloudSyncProvider.cs +++ b/UnitTests/PInvoke/CldApi/CloudSyncProvider.cs @@ -1,7 +1,9 @@ -using System; +using ICSharpCode.Decompiler.IL; +using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; @@ -20,6 +22,7 @@ namespace Vanara.PInvoke.Tests { 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; @@ -33,10 +36,11 @@ namespace Vanara.PInvoke.Tests NormalizedPath = CallbackInfo.NormalizedPath; TransferKey = CallbackInfo.TransferKey; PriorityHint = CallbackInfo.PriorityHint; - CorrelationVector = CallbackInfo.CorrelationVector.ToNullableStructure(); + pCorrelationVector = CallbackInfo.CorrelationVector; ProcessInfo = CallbackInfo.ProcessInfo.ToNullableStructure(); RequestKey = CallbackInfo.RequestKey; - ParamData = CallbackParameters.GetParam(); + 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. @@ -46,7 +50,10 @@ namespace Vanara.PInvoke.Tests public CF_CONNECTION_KEY ConnectionKey { get; } /// An optional correlation vector. - public CORRELATION_VECTOR? CorrelationVector { get; } + public IntPtr pCorrelationVector { get; } + + /// 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; } @@ -66,6 +73,9 @@ namespace Vanara.PInvoke.Tests /// 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; } + /// /// 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). @@ -95,11 +105,40 @@ namespace Vanara.PInvoke.Tests /// DOS drive letter of the volume in the form of “X:” where X is the drive letter. public string VolumeDosName { get; } - /// GUID name of the volume on which the placeholder file/directory to be serviced resides. It is in the form: “\?\Volume{GUID}”. + /// 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; } /// 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; } + + /// Makes a CF_OPERATION_INFO instance from the properties. + /// Type of the operation to set. + /// A CF_OPERATION_INFO instance. + public CF_OPERATION_INFO MakeOpInfo(CF_OPERATION_TYPE opType, IntPtr syncStatus = default) => new CF_OPERATION_INFO + { + StructSize = (uint)Marshal.SizeOf(), + Type = opType, + ConnectionKey = ConnectionKey, + TransferKey = TransferKey, + CorrelationVector = pCorrelationVector, + RequestKey = RequestKey, + SyncStatus = syncStatus + }; } public class PlaceHolderDirectoryInfo : PlaceholderInfo @@ -197,31 +236,31 @@ namespace Vanara.PInvoke.Tests ConnectSyncRootTransferCallbacks(); } - public event EventHandler> CancelFetchData; + public event EventHandler> CancelFetchData; - public event EventHandler> CancelFetchPlaceholders; + public event EventHandler> CancelFetchPlaceholders; - public event EventHandler> FetchData; + public event EventHandler> FetchData; - public event EventHandler> FetchPlaceholders; + public event EventHandler> FetchPlaceholders; - public event EventHandler> NotifyDehydrate; + public event EventHandler> NotifyDehydrate; - public event EventHandler> NotifyDehydrateCompletion; + public event EventHandler> NotifyDehydrateCompletion; - public event EventHandler> NotifyDelete; + public event EventHandler> NotifyDelete; - public event EventHandler> NotifyDeleteCompletion; + public event EventHandler> NotifyDeleteCompletion; - public event EventHandler> NotifyFileCloseCompletion; + public event EventHandler> NotifyFileCloseCompletion; - public event EventHandler> NotifyFileOpenCompletion; + public event EventHandler> NotifyFileOpenCompletion; - public event EventHandler> NotifyRename; + public event EventHandler> NotifyRename; - public event EventHandler> NotifyRenameCompletion; + public event EventHandler> NotifyRenameCompletion; - public event EventHandler> ValidateData; + public event EventHandler> ValidateData; public string DisplayName { get; } @@ -231,6 +270,12 @@ namespace Vanara.PInvoke.Tests public Uri RecycleBinUri { get; } + 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); + } + public string SyncRootId { get; } public string SyncRootPath { get; } @@ -341,44 +386,46 @@ namespace Vanara.PInvoke.Tests UnregisterWithShell(); } - protected virtual void OnCancelFetchData(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => - CancelFetchData?.Invoke(this, new CloudSyncCallbackArgs(CallbackInfo, CallbackParameters)); + protected virtual void OnCancelFetchData(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(CancelFetchData, CallbackInfo, CallbackParameters); - protected virtual void OnCancelFetchPlaceholders(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => - CancelFetchPlaceholders?.Invoke(this, new CloudSyncCallbackArgs(CallbackInfo, CallbackParameters)); + 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) => - FetchData?.Invoke(this, new CloudSyncCallbackArgs(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) => - FetchPlaceholders?.Invoke(this, new CloudSyncCallbackArgs(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) => - NotifyDehydrate?.Invoke(this, new CloudSyncCallbackArgs(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) => - NotifyDehydrateCompletion?.Invoke(this, new CloudSyncCallbackArgs(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) => - NotifyDelete?.Invoke(this, new CloudSyncCallbackArgs(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) => - NotifyDeleteCompletion?.Invoke(this, new CloudSyncCallbackArgs(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) => - NotifyFileCloseCompletion?.Invoke(this, new CloudSyncCallbackArgs(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) => - NotifyFileOpenCompletion?.Invoke(this, new CloudSyncCallbackArgs(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) => - NotifyRename?.Invoke(this, new CloudSyncCallbackArgs(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) => - NotifyRenameCompletion?.Invoke(this, new CloudSyncCallbackArgs(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) => - ValidateData?.Invoke(this, new CloudSyncCallbackArgs(CallbackInfo, CallbackParameters)); + protected virtual void OnValidateData(in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) => HandleEvent(ValidateData, CallbackInfo, CallbackParameters); + + protected virtual void HandleEvent(EventHandler> handler, in CF_CALLBACK_INFO CallbackInfo, in CF_CALLBACK_PARAMETERS CallbackParameters) where T : struct + { + if (handler != null) + { + var args = new CloudSyncCallbackArgs(CallbackInfo, CallbackParameters); + handler.Invoke(this, args); + if (args.OperationType != 0 && args.OpParam.HasValue) + { + var opInfo = args.MakeOpInfo(args.OperationType, args.SyncStatus); + var opParam = args.OpParam.Value; + CfExecute(opInfo, ref opParam).ThrowIfFailed(); + } + } + } private static void AddFolderToSearchIndexer(string folder) {