From eb231de63750cbeaa7a4f4f255236ed9712d2f06 Mon Sep 17 00:00:00 2001 From: IT Hit Date: Sat, 4 Jun 2022 02:16:39 +0300 Subject: [PATCH] v5.0.14726.0-Beta --- Common/Common.csproj | 5 +- Common/Settings.cs | 10 - .../Common/Core/Common.Windows.Core.csproj | 12 +- Windows/Common/Core/FsPath.cs | 1 - Windows/Common/Core/LogFormatter.cs | 189 +++++++++ Windows/Common/Core/Logger.cs | 90 ----- Windows/Common/Core/Registrar.cs | 23 +- .../Rpc.Proto/Common.Windows.Rpc.Proto.csproj | 27 -- Windows/Common/Rpc.Proto/ShellExtension.proto | 81 ---- Windows/Common/Rpc/Common.Windows.Rpc.csproj | 19 - Windows/Common/Rpc/GrpcClient.cs | 49 --- .../ComInfrastructure/BasicClassFactory.cs | 114 ------ .../ComInfrastructure/GcReferencesCleaner.cs | 26 -- .../ComInfrastructure/LocalServer.cs | 146 ------- .../Common.Windows.ShellExtension.csproj | 28 -- .../ContextMenusProviderBase.cs | 215 ----------- Windows/Common/ShellExtension/GrpcLogger.cs | 80 ---- .../ShellExtension/Interop/EXPCMDFLAGS.cs | 20 - .../ShellExtension/Interop/EXPCMDSTATE.cs | 15 - .../Interop/IEnumExplorerCommand.cs | 26 -- .../ShellExtension/Interop/IEnumShellItems.cs | 25 -- .../Interop/IExplorerCommand.cs | 96 ----- .../Interop/IInitializedWithItem.cs | 14 - .../ShellExtension/Interop/IShellItem.cs | 35 -- .../ShellExtension/Interop/IShellItemArray.cs | 49 --- .../Interop/IThumbnailProvider.cs | 37 -- .../Interop/InitializedWithItem.cs | 16 - .../Common/ShellExtension/Interop/Ole32.cs | 22 -- .../ShellExtension/Interop/SIATTRIBFLAGS.cs | 11 - .../Common/ShellExtension/Interop/SIGDN.cs | 17 - Windows/Common/ShellExtension/Interop/STGM.cs | 27 -- .../Common/ShellExtension/Interop/Shell32.cs | 22 -- .../ShellExtension/Interop/ShellItemHelper.cs | 38 -- .../ShellExtension/Interop/WTS_ALPHATYPE.cs | 10 - .../Common/ShellExtension/Interop/WinError.cs | 14 - .../Common/ShellExtension/ReferenceManager.cs | 52 --- .../ShellExtensionConfiguration.cs | 61 --- .../ShellExtension/ShellExtensionRegistrar.cs | 43 --- .../ShellExtension/ThumbnailProviderBase.cs | 115 ------ .../ShellExtension/ThumbnailProviderCommon.cs | 32 -- .../Common.Windows.VirtualDrive.csproj | 10 +- .../Filter/MsOfficeFilterHelper.cs | 2 +- .../VirtualDrive/FullSync/FullSyncService.cs | 36 +- Windows/Common/VirtualDrive/IMapping.cs | 11 - Windows/Common/VirtualDrive/IVirtualFolder.cs | 2 +- .../Common/VirtualDrive/MenuCommandLock.cs | 131 +++++++ Windows/Common/VirtualDrive/Rpc/GrpcServer.cs | 92 ----- .../VirtualDrive/Rpc/GrpcServerServiceImpl.cs | 206 ---------- .../Rpc/StorageProviderUriSourceStatus.cs | 9 - .../Common/VirtualDrive/VirtualEngineBase.cs | 76 +--- .../CommonShellExtensionRpc.csproj | 41 -- .../CustomStateProviderProxy.cs | 56 --- .../Google.Protobuf.dll | Bin 390128 -> 0 bytes .../Grpc.Core.Api.dll | Bin 53744 -> 0 bytes .../GrpcDotNetNamedPipes.dll | Bin 56320 -> 0 bytes .../WinRT.ShellExtension.Rpc/ItemProperty.cs | 18 - ...rageProviderGetContentInfoForPathResult.cs | 18 - ...orageProviderGetPathForContentUriResult.cs | 15 - .../UriSourceProxy.cs | 76 ---- .../WinRT.ShellExtension/ClassFactory.h | 23 -- .../CustomStateProvider.idl | 7 - .../ShellExtensionModule.h | 13 - Windows/UserFileSystemSamples.sln | 365 ------------------ .../VirtualDrive.Common.csproj | 15 - .../VirtualDrive.Package/Package.appxmanifest | 14 +- .../VirtualDrive.Package.wapproj | 7 +- .../ContextMenusProvider.cs | 121 +----- .../CustomStateProvider.cs | 20 + .../VirtualDrive.ShellExtension/Mapping.cs | 25 -- .../VirtualDrive.ShellExtension/Program.cs | 21 +- .../ThumbnailProvider.cs | 27 +- .../VirtualDrive.ShellExtension/UriSource.cs | 17 + .../VirtualDrive.ShellExtension.csproj | 31 +- ...ndows.WinRT.ShellExtension.vcxproj.filters | 64 --- ...dows.WinRT.ShellExtension_TemporaryKey.pfx | Bin 2520 -> 0 bytes .../Common_Windows_WinRT_ShellExtension.aps | Bin 1476 -> 0 bytes ..._Windows_WinRT_ShellExtension.exe.manifest | 31 -- .../Common_Windows_WinRT_ShellExtension.rc | Bin 2664 -> 0 bytes .../CustomStateProvider.cpp | 40 -- .../CustomStateProvider.h | 24 -- .../PropertySheet.props | 16 - .../ShellExtensionModule.cpp | 33 -- .../StorageProviderUriSourceStatus.cpp | 2 - .../StorageProviderUriSourceStatus.h | 5 - .../UriSource.cpp | 28 -- .../UriSource.h | 27 -- .../UriSource.idl | 7 - .../VirtualDrive.WinRT.ShellExtension.vcxproj | 160 -------- .../WinMain.cpp | 69 ---- .../packages.config | 5 - .../VirtualDrive.WinRT.ShellExtension/pch.cpp | 1 - .../VirtualDrive.WinRT.ShellExtension/pch.h | 25 -- .../readme.txt | 30 -- .../resource.h | 13 - .../AppSettings.cs | 2 +- Windows/VirtualDrive/VirtualDrive/Mapping.cs | 125 ++---- Windows/VirtualDrive/VirtualDrive/Program.cs | 228 +++++------ .../VirtualDrive/RemoteStorage/General.docx | Bin 11801 -> 11878 bytes .../VirtualDrive/RemoteStorageMonitor.cs | 122 ++++-- .../VirtualDrive/ShellExtensionRegistrar.cs | 29 +- .../SparsePackage/appxmanifest.xml | 76 ++++ .../VirtualDrive/ThumbnailExtractor.cs | 15 +- .../VirtualDrive/VirtualDrive.csproj | 12 +- .../VirtualDrive_TemporaryKey.pfx | Bin 0 -> 2528 bytes .../VirtualDrive/VirtualEngine.cs | 135 ++----- .../VirtualDrive/VirtualDrive/VirtualFile.cs | 8 +- .../VirtualDrive/VirtualFileSystemItem.cs | 95 ++++- .../VirtualDrive/VirtualFolder.cs | 12 +- .../VirtualDrive/appsettings.json | 15 +- Windows/VirtualFileSystem/Mapping.cs | 12 +- Windows/VirtualFileSystem/Program.cs | 150 +++---- .../VirtualFileSystem/RemoteStorageMonitor.cs | 58 +-- Windows/VirtualFileSystem/VirtualEngine.cs | 41 +- Windows/VirtualFileSystem/VirtualFile.cs | 8 +- .../VirtualFileSystem.csproj | 3 +- .../VirtualFileSystemItem.cs | 32 +- Windows/VirtualFileSystem/VirtualFolder.cs | 3 +- Windows/VirtualFileSystem/appsettings.json | 10 +- .../WebDAVDrive.Package/Package.appxmanifest | 6 +- .../WebDAVDrive.Package.wapproj | 7 +- .../ContextMenusProvider.cs | 118 +----- .../CustomStateProvider.cs | 18 + .../WebDAVDrive.ShellExtension/Program.cs | 20 +- .../ThumbnailProvider.cs | 29 +- .../WebDAVDrive.ShellExtension.csproj | 34 +- .../WebDAVDrive.UI/WebDAVDrive.UI.csproj | 5 +- ...ndows.WinRT.ShellExtension.vcxproj.filters | 64 --- ...dows.WinRT.ShellExtension_TemporaryKey.pfx | Bin 2520 -> 0 bytes .../Common_Windows_WinRT_ShellExtension.aps | Bin 1476 -> 0 bytes ..._Windows_WinRT_ShellExtension.exe.manifest | 16 - .../Common_Windows_WinRT_ShellExtension.rc | Bin 2664 -> 0 bytes .../CustomStateProvider.cpp | 40 -- .../CustomStateProvider.h | 23 -- .../PropertySheet.props | 16 - .../ShellExtensionModule.cpp | 28 -- .../WebDAVDrive.WinRT.ShellExtension.vcxproj | 148 ------- .../WinMain.cpp | 69 ---- .../packages.config | 5 - .../WebDAVDrive.WinRT.ShellExtension/pch.cpp | 1 - .../WebDAVDrive.WinRT.ShellExtension/pch.h | 24 -- .../resource.h | 13 - Windows/WebDAVDrive/WebDAVDrive/Program.cs | 319 ++++++++------- .../WebDAVDrive/RemoteStorageMonitor.cs | 52 +-- .../WebDAVDrive/ShellExtensionRegistrar.cs | 25 +- .../SparsePackage/appxmanifest.xml | 77 ++++ .../WebDAVDrive/WebDAVDrive/VirtualEngine.cs | 164 ++------ .../WebDAVDrive/WebDAVDrive/VirtualFile.cs | 8 +- .../WebDAVDrive/VirtualFileSystemItem.cs | 141 ++++++- .../WebDAVDrive/WebDAVDrive/VirtualFolder.cs | 6 +- .../WebDAVDrive/WebDAVDrive.csproj | 10 +- .../WebDAVDrive/WebDAVDrive_TemporaryKey.pfx | Bin 0 -> 2528 bytes .../WebDAVDrive/WebDAVDrive/appsettings.json | 15 +- macOS/README.md | 6 + 153 files changed, 1549 insertions(+), 5146 deletions(-) create mode 100644 Windows/Common/Core/LogFormatter.cs delete mode 100644 Windows/Common/Core/Logger.cs delete mode 100644 Windows/Common/Rpc.Proto/Common.Windows.Rpc.Proto.csproj delete mode 100644 Windows/Common/Rpc.Proto/ShellExtension.proto delete mode 100644 Windows/Common/Rpc/Common.Windows.Rpc.csproj delete mode 100644 Windows/Common/Rpc/GrpcClient.cs delete mode 100644 Windows/Common/ShellExtension/ComInfrastructure/BasicClassFactory.cs delete mode 100644 Windows/Common/ShellExtension/ComInfrastructure/GcReferencesCleaner.cs delete mode 100644 Windows/Common/ShellExtension/ComInfrastructure/LocalServer.cs delete mode 100644 Windows/Common/ShellExtension/Common.Windows.ShellExtension.csproj delete mode 100644 Windows/Common/ShellExtension/ContextMenusProviderBase.cs delete mode 100644 Windows/Common/ShellExtension/GrpcLogger.cs delete mode 100644 Windows/Common/ShellExtension/Interop/EXPCMDFLAGS.cs delete mode 100644 Windows/Common/ShellExtension/Interop/EXPCMDSTATE.cs delete mode 100644 Windows/Common/ShellExtension/Interop/IEnumExplorerCommand.cs delete mode 100644 Windows/Common/ShellExtension/Interop/IEnumShellItems.cs delete mode 100644 Windows/Common/ShellExtension/Interop/IExplorerCommand.cs delete mode 100644 Windows/Common/ShellExtension/Interop/IInitializedWithItem.cs delete mode 100644 Windows/Common/ShellExtension/Interop/IShellItem.cs delete mode 100644 Windows/Common/ShellExtension/Interop/IShellItemArray.cs delete mode 100644 Windows/Common/ShellExtension/Interop/IThumbnailProvider.cs delete mode 100644 Windows/Common/ShellExtension/Interop/InitializedWithItem.cs delete mode 100644 Windows/Common/ShellExtension/Interop/Ole32.cs delete mode 100644 Windows/Common/ShellExtension/Interop/SIATTRIBFLAGS.cs delete mode 100644 Windows/Common/ShellExtension/Interop/SIGDN.cs delete mode 100644 Windows/Common/ShellExtension/Interop/STGM.cs delete mode 100644 Windows/Common/ShellExtension/Interop/Shell32.cs delete mode 100644 Windows/Common/ShellExtension/Interop/ShellItemHelper.cs delete mode 100644 Windows/Common/ShellExtension/Interop/WTS_ALPHATYPE.cs delete mode 100644 Windows/Common/ShellExtension/Interop/WinError.cs delete mode 100644 Windows/Common/ShellExtension/ReferenceManager.cs delete mode 100644 Windows/Common/ShellExtension/ShellExtensionConfiguration.cs delete mode 100644 Windows/Common/ShellExtension/ShellExtensionRegistrar.cs delete mode 100644 Windows/Common/ShellExtension/ThumbnailProviderBase.cs delete mode 100644 Windows/Common/ShellExtension/ThumbnailProviderCommon.cs create mode 100644 Windows/Common/VirtualDrive/MenuCommandLock.cs delete mode 100644 Windows/Common/VirtualDrive/Rpc/GrpcServer.cs delete mode 100644 Windows/Common/VirtualDrive/Rpc/GrpcServerServiceImpl.cs delete mode 100644 Windows/Common/VirtualDrive/Rpc/StorageProviderUriSourceStatus.cs delete mode 100644 Windows/Common/WinRT.ShellExtension.Rpc/CommonShellExtensionRpc.csproj delete mode 100644 Windows/Common/WinRT.ShellExtension.Rpc/CustomStateProviderProxy.cs delete mode 100644 Windows/Common/WinRT.ShellExtension.Rpc/Google.Protobuf.dll delete mode 100644 Windows/Common/WinRT.ShellExtension.Rpc/Grpc.Core.Api.dll delete mode 100644 Windows/Common/WinRT.ShellExtension.Rpc/GrpcDotNetNamedPipes.dll delete mode 100644 Windows/Common/WinRT.ShellExtension.Rpc/ItemProperty.cs delete mode 100644 Windows/Common/WinRT.ShellExtension.Rpc/StorageProviderGetContentInfoForPathResult.cs delete mode 100644 Windows/Common/WinRT.ShellExtension.Rpc/StorageProviderGetPathForContentUriResult.cs delete mode 100644 Windows/Common/WinRT.ShellExtension.Rpc/UriSourceProxy.cs delete mode 100644 Windows/Common/WinRT.ShellExtension/ClassFactory.h delete mode 100644 Windows/Common/WinRT.ShellExtension/CustomStateProvider.idl delete mode 100644 Windows/Common/WinRT.ShellExtension/ShellExtensionModule.h delete mode 100644 Windows/VirtualDrive/VirtualDrive.Common/VirtualDrive.Common.csproj create mode 100644 Windows/VirtualDrive/VirtualDrive.ShellExtension/CustomStateProvider.cs delete mode 100644 Windows/VirtualDrive/VirtualDrive.ShellExtension/Mapping.cs create mode 100644 Windows/VirtualDrive/VirtualDrive.ShellExtension/UriSource.cs delete mode 100644 Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/Common.Windows.WinRT.ShellExtension.vcxproj.filters delete mode 100644 Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/Common.Windows.WinRT.ShellExtension_TemporaryKey.pfx delete mode 100644 Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.aps delete mode 100644 Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.exe.manifest delete mode 100644 Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.rc delete mode 100644 Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/CustomStateProvider.cpp delete mode 100644 Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/CustomStateProvider.h delete mode 100644 Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/PropertySheet.props delete mode 100644 Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/ShellExtensionModule.cpp delete mode 100644 Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/StorageProviderUriSourceStatus.cpp delete mode 100644 Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/StorageProviderUriSourceStatus.h delete mode 100644 Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/UriSource.cpp delete mode 100644 Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/UriSource.h delete mode 100644 Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/UriSource.idl delete mode 100644 Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/VirtualDrive.WinRT.ShellExtension.vcxproj delete mode 100644 Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/WinMain.cpp delete mode 100644 Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/packages.config delete mode 100644 Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/pch.cpp delete mode 100644 Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/pch.h delete mode 100644 Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/readme.txt delete mode 100644 Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/resource.h rename Windows/VirtualDrive/{VirtualDrive.Common => VirtualDrive}/AppSettings.cs (99%) create mode 100644 Windows/VirtualDrive/VirtualDrive/SparsePackage/appxmanifest.xml create mode 100644 Windows/VirtualDrive/VirtualDrive/VirtualDrive_TemporaryKey.pfx create mode 100644 Windows/WebDAVDrive/WebDAVDrive.ShellExtension/CustomStateProvider.cs delete mode 100644 Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/Common.Windows.WinRT.ShellExtension.vcxproj.filters delete mode 100644 Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/Common.Windows.WinRT.ShellExtension_TemporaryKey.pfx delete mode 100644 Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.aps delete mode 100644 Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.exe.manifest delete mode 100644 Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.rc delete mode 100644 Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/CustomStateProvider.cpp delete mode 100644 Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/CustomStateProvider.h delete mode 100644 Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/PropertySheet.props delete mode 100644 Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/ShellExtensionModule.cpp delete mode 100644 Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/WebDAVDrive.WinRT.ShellExtension.vcxproj delete mode 100644 Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/WinMain.cpp delete mode 100644 Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/packages.config delete mode 100644 Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/pch.cpp delete mode 100644 Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/pch.h delete mode 100644 Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/resource.h create mode 100644 Windows/WebDAVDrive/WebDAVDrive/SparsePackage/appxmanifest.xml create mode 100644 Windows/WebDAVDrive/WebDAVDrive/WebDAVDrive_TemporaryKey.pfx diff --git a/Common/Common.csproj b/Common/Common.csproj index b4258b3..2a22d73 100644 --- a/Common/Common.csproj +++ b/Common/Common.csproj @@ -1,14 +1,15 @@ - netstandard2.1 + netstandard2.0 IT Hit LTD. IT Hit LTD. IT Hit User File System IT Hit User File System Contains functionality common for all Virtual Drive samples, both for Windows and macOS. ITHit.FileSystem.Samples.Common + AnyCPU;x64 - + \ No newline at end of file diff --git a/Common/Settings.cs b/Common/Settings.cs index e4bff52..317c872 100644 --- a/Common/Settings.cs +++ b/Common/Settings.cs @@ -47,15 +47,5 @@ public class Settings /// Automatically lock the file in the remote storage when a file handle is being opened for writing, unlock on close. /// public bool AutoLock { get; set; } - - /// - /// Communication channel name is used by RPC to establish connection over named pipes. - /// - public string RpcCommunicationChannelName { get; set; } - - /// - /// Gets or sets the maximum number of concurrent tasks - /// - public int MaxDegreeOfParallelism { get; set; } } } diff --git a/Windows/Common/Core/Common.Windows.Core.csproj b/Windows/Common/Core/Common.Windows.Core.csproj index 48268a9..f492889 100644 --- a/Windows/Common/Core/Common.Windows.Core.csproj +++ b/Windows/Common/Core/Common.Windows.Core.csproj @@ -1,7 +1,6 @@ - net5.0-windows10.0.18362.0 - 10.0.18362.0 + net48;net5.0-windows10.0.19041.0 Contains functionality common for all Windows Virtual Drive samples. IT Hit LTD. IT Hit User File System @@ -9,12 +8,19 @@ ITHit.FileSystem.Samples.Common.Windows.Core AnyCPU;x64 + + + + + + + - + \ No newline at end of file diff --git a/Windows/Common/Core/FsPath.cs b/Windows/Common/Core/FsPath.cs index befc3b5..9e3329b 100644 --- a/Windows/Common/Core/FsPath.cs +++ b/Windows/Common/Core/FsPath.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using Windows.Storage; using FileAttributes = System.IO.FileAttributes; -using ITHit.FileSystem.Windows; namespace ITHit.FileSystem.Samples.Common.Windows { diff --git a/Windows/Common/Core/LogFormatter.cs b/Windows/Common/Core/LogFormatter.cs new file mode 100644 index 0000000..fcf7010 --- /dev/null +++ b/Windows/Common/Core/LogFormatter.cs @@ -0,0 +1,189 @@ +using System; +using System.Runtime.InteropServices; +using System.IO; +using System.Reflection; +using System.Linq; +using System.Threading.Tasks; +using Windows.Storage; + +using log4net; +using log4net.Config; +using log4net.Appender; + +using ITHit.FileSystem.Windows; + +namespace ITHit.FileSystem.Samples.Common.Windows +{ + /// + /// Outputs logging. + /// + public class LogFormatter + { + /// + /// Log file path. + /// + public readonly string LogFilePath; + + /// + /// Indicates if more debugging and performance information should be logged. + /// + public bool DebugLoggingEnabled + { + get { return debugLoggingEnabled; } + set + { + debugLoggingEnabled = value; + string debugLoggingState = debugLoggingEnabled ? "Enabled" : "Disabled"; + log.Info($"{Environment.NewLine}Debug logging {debugLoggingState}"); + } + } + + public bool debugLoggingEnabled = false; + + private readonly ILog log; + + private readonly string appId; + + /// + /// Creates instance of this class. + /// + /// Log4net logger. + public LogFormatter(ILog log, string appId) + { + this.log = log; + this.appId = appId; + LogFilePath = ConfigureLogger(); + } + + /// + /// Configures log4net logger. + /// + /// Log file path. + private string ConfigureLogger() + { + // Load Log4Net for net configuration. + var logRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); + XmlConfigurator.Configure(logRepository, new FileInfo(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "log4net.config"))); + + // Update log file path for msix package. + RollingFileAppender rollingFileAppender = logRepository.GetAppenders().Where(p => p.GetType() == typeof(RollingFileAppender)).FirstOrDefault() as RollingFileAppender; + if (rollingFileAppender != null && rollingFileAppender.File.Contains("WindowsApps")) + { + rollingFileAppender.File = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), appId, + Path.GetFileName(rollingFileAppender.File)); + } + return rollingFileAppender?.File; + } + + /// + /// Prints environment description. + /// + public void PrintEnvironmentDescription() + { + // Log environment description. + log.Info($"\n{"AppID:",-15} {appId}"); + log.Info($"\n{"Engine version:",-15} {typeof(IEngine).Assembly.GetName().Version}"); + log.Info($"\n{"OS version:",-15} {RuntimeInformation.OSDescription}"); + log.Info($"\n{"Env version:",-15} {RuntimeInformation.FrameworkDescription} {IntPtr.Size * 8}bit."); + //log.Info($"\n{"Is UWP:",-15} {PackageRegistrar.IsRunningAsUwp()}"); + log.Info($"\n{"Admin mode:",-15} {new System.Security.Principal.WindowsPrincipal(System.Security.Principal.WindowsIdentity.GetCurrent()).IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator)}"); + } + + /// + /// Prints indexing state. + /// + /// File system path. + public async Task PrintIndexingStateAsync(string path) + { + StorageFolder userFileSystemRootFolder = await StorageFolder.GetFolderFromPathAsync(path); + log.Info($"\nIndexed state: {(await userFileSystemRootFolder.GetIndexedStateAsync())}"); + } + + /// + /// Prints console commands. + /// + public void PrintHelp() + { + log.Info("\n\nPress Esc to unregister file system, delete all files/folders and exit (simulate uninstall)."); + log.Info("\nPress Spacebar to exit without unregistering (simulate reboot)."); + log.Info("\nPress 'p' to unregister sparse package."); + log.Info("\nPress 'e' to start/stop the Engine and all sync services."); + log.Info("\nPress 's' to start/stop full synchronization service."); + log.Info("\nPress 'm' to start/stop remote storage monitor."); + log.Info("\nPress 'd' to enable/disable debug and performance logging."); + log.Info($"\nPress 'l' to open log file. ({LogFilePath})"); + log.Info($"\nPress 'b' to submit support tickets, report bugs, suggest features. (https://userfilesystem.com/support/)"); + log.Info("\n----------------------\n"); + } + + public void LogError(IEngine sender, EngineErrorEventArgs e) + { + WriteLog(e, log4net.Core.Level.Error); + } + + public void LogMessage(IEngine sender, EngineMessageEventArgs e) + { + WriteLog(e, log4net.Core.Level.Info); + } + + public void LogDebug(IEngine sender, EngineMessageEventArgs e) + { + WriteLog(e, log4net.Core.Level.Debug); + } + + /// + /// Outputs log message. + /// + /// log4net + /// Message or error description. + /// Log level. + private void WriteLog(EngineMessageEventArgs e, log4net.Core.Level level) + { + string att = FsPath.Exists(e.SourcePath) ? FsPath.GetAttString(e.SourcePath) : null; + string process = null; + byte? priorityHint = null; + ulong? clientFileId = null; + string size = null; + + if (e.OperationContext != null) + { + process = System.IO.Path.GetFileName(e.OperationContext.ProcessInfo?.ImagePath); + priorityHint = e.OperationContext.PriorityHint; + clientFileId = (e.OperationContext as IWindowsOperationContext).FileId; + size = FsPath.FormatBytes((e.OperationContext as IWindowsOperationContext).FileSize); + } + + string message = Format(DateTimeOffset.Now.ToString("hh:mm:ss.fff"), process, priorityHint?.ToString(), e.ComponentName, e.Message, e.SourcePath, att, e.TargetPath); + + if (level == log4net.Core.Level.Error) + { + Exception ex = ((EngineErrorEventArgs)e).Exception; + message += Environment.NewLine; + log.Error(message, ex); + } + else if (level == log4net.Core.Level.Info) + { + log.Info(message); + } + else if (level == log4net.Core.Level.Debug && DebugLoggingEnabled) + { + log.Debug(message); + } + + } + + private static string Format(string date, string process, string priorityHint, string componentName, string message, string sourcePath, string attributes, string targetPath) + { + return $"{Environment.NewLine}|{date, -12}| {process,-25}| {priorityHint,-5}| {componentName,-26}| {message,-45}| {sourcePath,-80}| {attributes, -22}| {targetPath}"; + } + + /// + /// Prints logging data headers. + /// + public void PrintHeader() + { + log.Info(Format("Time", "Process Name", "Prty", "Component", "Operation", "Source Path", "Attributes", "Target Path")); + log.Info(Format("----", "------------", "----", "---------", "---------", "-----------", "----------", "-----------")); + } + } +} diff --git a/Windows/Common/Core/Logger.cs b/Windows/Common/Core/Logger.cs deleted file mode 100644 index 43d3af9..0000000 --- a/Windows/Common/Core/Logger.cs +++ /dev/null @@ -1,90 +0,0 @@ -using log4net; -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; - -using ITHit.FileSystem; -using ITHit.FileSystem.Windows; -using System.Runtime.InteropServices; - -namespace ITHit.FileSystem.Samples.Common.Windows -{ - /// - /// Implements logging. - /// - public class Logger : ILogger - { - /// - /// Name of the component that is writing to the log. - /// - private readonly string componentName; - - /// - /// Log4Net Logger. - /// - protected readonly ILog Log; - - /// - /// Creates instance of this class. - /// - /// Name of the component that is writing to the log. - /// Log4Net Logger. - public Logger(string componentName, ILog logger) - { - this.componentName = componentName; - this.Log = logger; - } - - /// - public void LogError(string message, string sourcePath = null, string targetPath = null, Exception ex = null, IOperationContext operationContext = null) - { - string att = FsPath.Exists(sourcePath) ? FsPath.GetAttString(sourcePath) : null; - string process = null; - byte? priorityHint = null; - ulong? clientFileId = null; - - if (operationContext != null) - { - ProcessInfo processInfo = Marshal.PtrToStructure(operationContext.ProcessInfo); - process = System.IO.Path.GetFileName(processInfo.ImagePath); - priorityHint = operationContext.PriorityHint; - clientFileId = (operationContext as IWindowsOperationContext).FileId; - } - - Log.Error($"{Format(DateTimeOffset.Now.ToString("hh:mm:ss.fff"), process, priorityHint?.ToString(), componentName, message, sourcePath, att, targetPath)}{Environment.NewLine}", ex); - } - - /// - public void LogMessage(string message, string sourcePath = null, string targetPath = null, IOperationContext operationContext = null) - { - string att = FsPath.Exists(sourcePath) ? FsPath.GetAttString(sourcePath) : null; - string process = null; - byte? priorityHint = null; - ulong? clientFileId = null; - string size = null; - - if (operationContext != null) - { - ProcessInfo processInfo = Marshal.PtrToStructure(operationContext.ProcessInfo); - process = System.IO.Path.GetFileName(processInfo.ImagePath); - priorityHint = operationContext.PriorityHint; - clientFileId = (operationContext as IWindowsOperationContext).FileId; - size = FsPath.FormatBytes((operationContext as IWindowsOperationContext).FileSize); - } - - Log.Debug(Format(DateTimeOffset.Now.ToString("hh:mm:ss.fff"), process, priorityHint?.ToString(), componentName, message, sourcePath, att, targetPath)); - } - - private static string Format(string date, string process, string priorityHint, string componentName, string message, string sourcePath, string attributes, string targetPath) - { - return $"{Environment.NewLine}|{date, -12}| {process,-25}| {priorityHint,-5}| {componentName,-26}| {message,-45}| {sourcePath,-80}| {attributes, -22}| {targetPath}"; - } - - public static void PrintHeader(ILog logger) - { - logger.Info(Format("Time", "Process Name", "Prty", "Component", "Operation", "Source Path", "Attributes", "Target Path")); - logger.Info(Format("----", "------------", "----", "---------", "---------", "-----------", "----------", "-----------")); - } - } -} diff --git a/Windows/Common/Core/Registrar.cs b/Windows/Common/Core/Registrar.cs index 4f5b759..29ec6b0 100644 --- a/Windows/Common/Core/Registrar.cs +++ b/Windows/Common/Core/Registrar.cs @@ -16,6 +16,7 @@ namespace ITHit.FileSystem.Samples.Common.Windows /// public static class Registrar { + /// /// Registers sync root. /// @@ -72,6 +73,7 @@ public static async Task RegisterAsync(string syncRootId, string path, string di StorageProviderSyncRootManager.Register(storageInfo); } + /// /// Ensures that minimum required properties for are set. @@ -165,26 +167,5 @@ public static async Task UnregisterAsync(string syncRootId) { StorageProviderSyncRootManager.Unregister(syncRootId); } - - /// - /// Helper method to determine if the process is running in a packaged context. - /// - /// True if the application is running in a packaged context, false otherwise. - public static bool IsRunningAsUwp() - { - const long APPMODEL_ERROR_NO_PACKAGE = 15700L; - - int length = 0; - return GetCurrentPackageFullName(ref length, null) != APPMODEL_ERROR_NO_PACKAGE; - } - - /// - /// Gets the package full name for the calling process. - /// - /// On input, the size of the packageFullName buffer, in characters. On output, the size of the package full name returned, in characters, including the null terminator. - /// The package full name. - /// If the function succeeds it returns ERROR_SUCCESS. Otherwise, the function returns an error code. - [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - static extern int GetCurrentPackageFullName(ref int packageFullNameLength, StringBuilder packageFullName); } } diff --git a/Windows/Common/Rpc.Proto/Common.Windows.Rpc.Proto.csproj b/Windows/Common/Rpc.Proto/Common.Windows.Rpc.Proto.csproj deleted file mode 100644 index b0f2161..0000000 --- a/Windows/Common/Rpc.Proto/Common.Windows.Rpc.Proto.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - - net5.0-windows10.0.18362.0 - 10.0.18362.0 - This project contains protocol description, common for IT Hit Virtual Drive samples. - IT Hit LTD. - IT Hit LTD. - IT Hit User File System - IT Hit LTD. - ITHit.FileSystem.Samples.Common.Windows.Rpc.Proto - AnyCPU;x64 - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - diff --git a/Windows/Common/Rpc.Proto/ShellExtension.proto b/Windows/Common/Rpc.Proto/ShellExtension.proto deleted file mode 100644 index ea80b7a..0000000 --- a/Windows/Common/Rpc.Proto/ShellExtension.proto +++ /dev/null @@ -1,81 +0,0 @@ -syntax = "proto3"; - -package ITHit.FileSystem.Samples.Common.Windows.Rpc.Generated; - -service ShellExtensionRpc { - rpc SetLockStatus (ItemsStatusList) returns (EmptyMessage) {} - rpc GetLockStatus (ItemsPathList) returns (ItemsStatusList) {} - rpc GetThumbnail (ThumbnailRequest) returns (Thumbnail) {} - rpc LogMessage (LogMessageRequest) returns (EmptyMessage) {} - rpc LogError (LogErrorRequest) returns (EmptyMessage) {} - rpc GetItemProperties (ItemPropertyRequest) returns (ItemsPropertyList) {} - - rpc GetPathForContentUri (UriSourceRequest) returns (GetPathForContentUriResult) {} - rpc GetContentInfoForPath (UriSourceRequest) returns (GetContentInfoForPathResult) {} -} - -message ItemsPathList { - repeated string files = 1; -} - -message ItemsStatusList { - map filesStatus = 1; -} - -message ThumbnailRequest { - string path = 1; - uint32 size = 2; -} - -message Thumbnail { - bytes image = 1; -} - -message LogMessageRequest { - string componentName = 1; - string message = 2; - string sourcePath = 3; - string targetPath = 4; -} - -message LogErrorRequest { - string componentName = 1; - string message = 2; - string sourcePath = 3; - string targetPath = 4; - string exSerialized = 5; -} - -message EmptyMessage { -} - -message ItemProperty { - string iconResource = 1; - int32 id = 2; - string value = 3; -} - -message ItemsPropertyList { - repeated ItemProperty properties = 1; -} - -message ItemPropertyRequest { - string path = 1; -} - -message UriSourceRequest { - string pathOrUri = 1; -} - -message GetPathForContentUriResult { - string path = 1; - int32 status = 2; -} - -message GetContentInfoForPathResult { - string contentId = 1; - string contentUri = 2; - int32 status = 3; -} - - diff --git a/Windows/Common/Rpc/Common.Windows.Rpc.csproj b/Windows/Common/Rpc/Common.Windows.Rpc.csproj deleted file mode 100644 index b468cd6..0000000 --- a/Windows/Common/Rpc/Common.Windows.Rpc.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - net5.0-windows10.0.18362.0 - 10.0.18362.0 - Contains functionality common for all Windows Virtual Drive samples. - IT Hit LTD. - IT Hit User File System - IT Hit LTD. - ITHit.FileSystem.Samples.Common.Windows.Rpc - AnyCPU;x64 - - - - - - - - - \ No newline at end of file diff --git a/Windows/Common/Rpc/GrpcClient.cs b/Windows/Common/Rpc/GrpcClient.cs deleted file mode 100644 index f417c57..0000000 --- a/Windows/Common/Rpc/GrpcClient.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using ITHit.FileSystem.Samples.Common.Windows.Rpc.Generated; -using GrpcDotNetNamedPipes; -using System.IO; -using System.Security.Principal; - -namespace ITHit.FileSystem.Samples.Common.Windows.Rpc -{ - /// - /// GrpcClient establish connection to rpc server and provides api methods. - /// - public class GrpcClient - { - private static string rpcCommunicationChannelName = null; - - public GrpcClient(string channelName) - { - rpcCommunicationChannelName = channelName; - } - - public const int ConnectionTimeoutMs = 1000; - - private static Lazy namedPipeChannel = new Lazy(Connect); - - /// - /// Returns rpc client. - /// - public ShellExtensionRpc.ShellExtensionRpcClient RpcClient => namedPipeChannel.Value; - - /// - /// Establish connection. - /// - private static ShellExtensionRpc.ShellExtensionRpcClient Connect() - { - NamedPipeChannelOptions options = new NamedPipeChannelOptions - { - ConnectionTimeout = ConnectionTimeoutMs, - CurrentUserOnly = true, - ImpersonationLevel = TokenImpersonationLevel.None - }; - - var pipeName = Path.Combine(WindowsIdentity.GetCurrent().Owner.Value, rpcCommunicationChannelName); - - NamedPipeChannel channel = new NamedPipeChannel(".", pipeName, options); - ShellExtensionRpc.ShellExtensionRpcClient client = new ShellExtensionRpc.ShellExtensionRpcClient(channel); - return client; - } - } -} diff --git a/Windows/Common/ShellExtension/ComInfrastructure/BasicClassFactory.cs b/Windows/Common/ShellExtension/ComInfrastructure/BasicClassFactory.cs deleted file mode 100644 index 7f0c5a8..0000000 --- a/Windows/Common/ShellExtension/ComInfrastructure/BasicClassFactory.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using System.Diagnostics; - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension.ComInfrastructure -{ - [ComImport] - [ComVisible(false)] - [Guid("00000001-0000-0000-C000-000000000046")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IClassFactory - { - void CreateInstance( - [MarshalAs(UnmanagedType.Interface)] object pUnkOuter, - ref Guid riid, - out IntPtr ppvObject); - - void LockServer([MarshalAs(UnmanagedType.Bool)] bool fLock); - } - - /// - /// Implementation of ClassFactory to create com objects. - /// This is standard mechanism in case out-of-proc com servers. - /// - [ComVisible(true)] - internal class BasicClassFactory : IClassFactory where T : new() - { - public void CreateInstance( - [MarshalAs(UnmanagedType.Interface)] object pUnkOuter, - ref Guid riid, - out IntPtr ppvObject) - { - Type interfaceType = GetValidatedInterfaceType(typeof(T), ref riid, pUnkOuter); - - object obj = new T(); - if (pUnkOuter != null) - { - obj = CreateAggregatedObject(pUnkOuter, obj); - } - - ppvObject = GetObjectAsInterface(obj, interfaceType); - } - - public void LockServer([MarshalAs(UnmanagedType.Bool)] bool serverLock) - { - if (serverLock) - ReferenceManager.LockServer(); - else - ReferenceManager.UnlockServer(); - } - - private static readonly Guid IID_IUnknown = Guid.Parse("00000000-0000-0000-C000-000000000046"); - - private static Type GetValidatedInterfaceType(Type classType, ref Guid riid, object outer) - { - if (riid == IID_IUnknown) - { - return typeof(object); - } - - // Aggregation can only be done when requesting IUnknown. - if (outer != null) - { - throw new COMException(string.Empty, unchecked((int)0x80040110)); - } - - // Verify the class implements the desired interface - foreach (Type i in classType.GetInterfaces()) - { - if (i.GUID == riid) - { - return i; - } - } - - // E_NOINTERFACE - throw new InvalidCastException(); - } - - private static IntPtr GetObjectAsInterface(object obj, Type interfaceType) - { - // If the requested "interface type" is type object then return as IUnknown - if (interfaceType == typeof(object)) - { - return Marshal.GetIUnknownForObject(obj); - } - - IntPtr interfaceMaybe = Marshal.GetComInterfaceForObject(obj, interfaceType, CustomQueryInterfaceMode.Ignore); - if (interfaceMaybe == IntPtr.Zero) - { - // E_NOINTERFACE - throw new InvalidCastException(); - } - - return interfaceMaybe; - } - - private static object CreateAggregatedObject(object pUnkOuter, object comObject) - { - IntPtr outerPtr = Marshal.GetIUnknownForObject(pUnkOuter); - - try - { - IntPtr innerPtr = Marshal.CreateAggregatedObject(outerPtr, comObject); - return Marshal.GetObjectForIUnknown(innerPtr); - } - finally - { - // Decrement the above 'Marshal.GetIUnknownForObject()' - Marshal.Release(outerPtr); - } - } - } -} diff --git a/Windows/Common/ShellExtension/ComInfrastructure/GcReferencesCleaner.cs b/Windows/Common/ShellExtension/ComInfrastructure/GcReferencesCleaner.cs deleted file mode 100644 index c0745df..0000000 --- a/Windows/Common/ShellExtension/ComInfrastructure/GcReferencesCleaner.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Threading; - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension.ComInfrastructure -{ - /// - /// GcReferencesCleaner forces to cleanup references. - /// It allows to have actual state of Com objects. - /// - internal class GcReferencesCleaner : IDisposable - { - private const int CheckIntervalMs = 10000; - - private readonly Timer timer; - - public GcReferencesCleaner() - { - timer = new Timer(o => GC.Collect(), null, CheckIntervalMs, CheckIntervalMs); - } - - public void Dispose() - { - timer.Dispose(); - } - } -} diff --git a/Windows/Common/ShellExtension/ComInfrastructure/LocalServer.cs b/Windows/Common/ShellExtension/ComInfrastructure/LocalServer.cs deleted file mode 100644 index 3e4694d..0000000 --- a/Windows/Common/ShellExtension/ComInfrastructure/LocalServer.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop; - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension.ComInfrastructure -{ - /// - /// LocalServer encapsulates Com Exe server lifecycle - /// from Com class registration till exit if no object references or server locks. - /// - public class LocalServer : IDisposable - { - private readonly List registrationCookies = new List(); - - private int objectRefs = 0; - private int serverLocks = 0; - - private GcReferencesCleaner referenceCleaner = new GcReferencesCleaner(); - - private object exitLock = new object(); - - private TaskCompletionSource completionSource; - - public LocalServer() - { - ReferenceManager.ObjectCreated += OnObjectCreated; - ReferenceManager.ObjectDestroyed += OnObjectDestroyed; - - ReferenceManager.ServerLocked += OnServerLocked; - ReferenceManager.ServerUnlocked += OnServerUnlocked; - } - - /// - /// Com class registration. - /// - public void RegisterClass(Guid clsid) where T : new() - { - int cookie; - int hr = Ole32.CoRegisterClassObject(ref clsid, new BasicClassFactory(), Ole32.CLSCTX_LOCAL_SERVER, Ole32.REGCLS_MULTIPLEUSE | Ole32.REGCLS_SUSPENDED, out cookie); - if (hr < 0) - { - Marshal.ThrowExceptionForHR(hr); - } - - registrationCookies.Add(cookie); - - hr = Ole32.CoResumeClassObjects(); - if (hr < 0) - { - Marshal.ThrowExceptionForHR(hr); - } - } - - /// - /// Returns task with lidecycle of com server. - /// - public async Task Run() - { - completionSource = new TaskCompletionSource(); - - return await completionSource.Task; - } - - /// - /// Performs application-defined tasks associated with releasing resources. - /// - public void Dispose() - { - foreach (int cookie in registrationCookies) - { - int hr = Ole32.CoRevokeClassObject(cookie); - Debug.Assert(hr >= 0, $"CoRevokeClassObject failed ({hr:x}). Cookie: {cookie}"); - } - - ReferenceManager.ObjectCreated -= OnObjectCreated; - ReferenceManager.ObjectDestroyed -= OnObjectDestroyed; - - ReferenceManager.ServerLocked -= OnServerLocked; - ReferenceManager.ServerUnlocked -= OnServerUnlocked; - - referenceCleaner.Dispose(); - } - - /// - /// Handles com object creation. - /// - private void OnObjectCreated() - { - lock (exitLock) - { - objectRefs++; - } - } - - /// - /// Handles com object release. - /// - private void OnObjectDestroyed() - { - lock (exitLock) - { - objectRefs--; - - ExitIfNoRefs(); - } - } - - /// - /// Handles com server lock. - /// - private void OnServerLocked() - { - lock (exitLock) - { - serverLocks++; - } - } - - /// - /// Handles com server unlock. - /// - private void OnServerUnlocked() - { - lock (exitLock) - { - serverLocks--; - - ExitIfNoRefs(); - } - } - - /// - /// Checks condition to exit and complete lifecycle task. - /// - private void ExitIfNoRefs() - { - if ((objectRefs <= 0) && (serverLocks <= 0)) - { - completionSource.SetResult(true); - } - } - } -} diff --git a/Windows/Common/ShellExtension/Common.Windows.ShellExtension.csproj b/Windows/Common/ShellExtension/Common.Windows.ShellExtension.csproj deleted file mode 100644 index 97b4bcd..0000000 --- a/Windows/Common/ShellExtension/Common.Windows.ShellExtension.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - net5.0-windows10.0.18362.0 - 10.0.18362.0 - Windows shell extension implementation, such as Windows Explorer thumbnails provider and Windows Explorer context menu common for in IT Hit Virtual Drive samples. - IT Hit LTD. - IT Hit User File System - IT Hit LTD. - ITHit.FileSystem.Samples.Common.Windows.ShellExtension - AnyCPU;x64 - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Windows/Common/ShellExtension/ContextMenusProviderBase.cs b/Windows/Common/ShellExtension/ContextMenusProviderBase.cs deleted file mode 100644 index fe20a87..0000000 --- a/Windows/Common/ShellExtension/ContextMenusProviderBase.cs +++ /dev/null @@ -1,215 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System.IO; -using log4net; -using ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop; -using ITHit.FileSystem.Samples.Common.Windows.Rpc; -using ITHit.FileSystem.Samples.Common.Windows.Rpc.Generated; -using System.Xml.Serialization; - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension -{ - /// - /// Common code to extend Windows Explorer commands. - /// - /// - /// You will derive your class from this class to implement your Windows Explorer context menu. - /// Typically you do not need to make any changes in this class. - /// - public abstract class ContextMenusProviderBase : IExplorerCommand - { - /// - /// Method should be overridden to return menu title. - /// - /// List of selected items. - public abstract Task GetMenuTitleAsync(IEnumerable filesPath); - - /// - /// Method should be overridden to handle menu item call. - /// - /// List of selected items. - public abstract Task InvokeMenuCommandAsync(IEnumerable filesPath); - - /// - /// Method should be overridden to return menu state. - /// - /// List of selected items. - public abstract Task GetMenuStateAsync(IEnumerable filesPath); - - /// - /// Method should be overridden to return menu item icon. - /// - /// List of selected items. - public abstract Task GetIconAsync(IEnumerable filesPath); - - protected ILogger Log { get; } - - /// - /// Creates instance of this class. - /// - public ContextMenusProviderBase() - { - ReferenceManager.AddObjectReference(); - - Log = new GrpcLogger("Context Menu Provider"); - } - - ~ContextMenusProviderBase() - { - ReferenceManager.ReleaseObjectReference(); - } - - /// - public int GetTitle(IShellItemArray itemArray, out string title) - { - title = null; - - try - { - IEnumerable files = itemArray.GetFilesPath().Where(ShellExtensionConfiguration.IsVirtualDriveFolder); - if (!files.Any() || !files.All(File.Exists)) - return WinError.E_NOTIMPL; - - Log.LogMessage($"{nameof(ContextMenusProviderBase)}.{nameof(GetTitle)}()", string.Join(", ", files)); - - title = GetMenuTitleAsync(files).GetAwaiter().GetResult(); - - if (string.IsNullOrEmpty(title)) - return WinError.E_NOTIMPL; - - return WinError.S_OK; - } - catch (NotImplementedException) - { - return WinError.E_NOTIMPL; - } - catch (Exception ex) - { - Log.LogError("", null, null, ex); - return WinError.E_FAIL; - } - } - - /// - public int Invoke(IShellItemArray itemArray, object bindCtx) - { - try - { - IEnumerable files = itemArray.GetFilesPath().Where(ShellExtensionConfiguration.IsVirtualDriveFolder); - if (!files.Any() || !files.All(File.Exists)) - return WinError.E_NOTIMPL; - - Log.LogMessage($"{nameof(ContextMenusProviderBase)}.{nameof(Invoke)}()", string.Join(", ", files)); - - InvokeMenuCommandAsync(files).GetAwaiter().GetResult(); - - return WinError.S_OK; - } - catch (NotImplementedException) - { - return WinError.E_NOTIMPL; - } - catch (Exception ex) - { - Log.LogError("", null, null, ex); - return WinError.E_FAIL; - } - } - - /// - public int GetState(IShellItemArray itemArray, bool okToBeSlow, out EXPCMDSTATE commandState) - { - commandState = EXPCMDSTATE.ECS_ENABLED; - - try - { - if (itemArray is null) - { - return WinError.E_NOTIMPL; - } - - IEnumerable files = itemArray.GetFilesPath().Where(ShellExtensionConfiguration.IsVirtualDriveFolder); - if (!files.Any() || !files.All(File.Exists)) - { - return WinError.E_NOTIMPL; - } - - Log.LogMessage($"{nameof(ContextMenusProviderBase)}.{nameof(GetState)}()", string.Join(", ", files)); - - commandState = GetMenuStateAsync(files).GetAwaiter().GetResult(); - - return WinError.S_OK; - } - catch (NotImplementedException) - { - return WinError.E_NOTIMPL; - } - catch (Exception ex) - { - Log.LogError("", null, null, ex); - return WinError.E_FAIL; - } - } - - /// - public int GetFlags(out EXPCMDFLAGS flags) - { - flags = EXPCMDFLAGS.ECF_DEFAULT; - return WinError.S_OK; - } - - /// - public int GetIcon(IShellItemArray itemArray, out string resourceString) - { - resourceString = null; - - try - { - IEnumerable files = itemArray.GetFilesPath().Where(ShellExtensionConfiguration.IsVirtualDriveFolder); - if (!files.Any() || !files.All(File.Exists)) - return WinError.E_NOTIMPL; - - Log.LogMessage($"{nameof(ContextMenusProviderBase)}.{nameof(GetIcon)}()", string.Join(", ", files)); - - resourceString = GetIconAsync(files).GetAwaiter().GetResult(); - - if (string.IsNullOrEmpty(resourceString)) - return WinError.E_NOTIMPL; - - return WinError.S_OK; - } - catch (NotImplementedException) - { - return WinError.E_NOTIMPL; - } - catch (Exception ex) - { - Log.LogError("", null, null, ex); - return WinError.E_FAIL; - } - } - - /// - public int GetToolTip(IShellItemArray itemArray, out string tooltip) - { - tooltip = null; - return WinError.E_NOTIMPL; - } - - /// - public int GetCanonicalName(out Guid guid) - { - guid = Guid.Empty; - return WinError.E_NOTIMPL; - } - - /// - public int EnumSubCommands(out IEnumExplorerCommand commandEnum) - { - commandEnum = null; - return WinError.E_NOTIMPL; - } - } -} diff --git a/Windows/Common/ShellExtension/GrpcLogger.cs b/Windows/Common/ShellExtension/GrpcLogger.cs deleted file mode 100644 index aa54511..0000000 --- a/Windows/Common/ShellExtension/GrpcLogger.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.Serialization.Formatters.Binary; -using System.Text; -using System.Threading.Tasks; -using System.Xml.Serialization; - -using ITHit.FileSystem.Samples.Common.Windows.Rpc; -using ITHit.FileSystem.Samples.Common.Windows.Rpc.Generated; - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension -{ - public class GrpcLogger : ILogger - { - private readonly string componentName; - - private GrpcClient grpcClient = new GrpcClient(ShellExtensionConfiguration.AppSettings.RpcCommunicationChannelName); - - public GrpcLogger(string componentName) - { - this.componentName = componentName ?? throw new ArgumentNullException(nameof(componentName)); - } - - public void LogError(string message, string sourcePath = null, string targetPath = null, Exception ex = null, IOperationContext operationContext = null) - { - LogErrorRequest request = new(); - request.ComponentName = componentName; - request.Message = message ?? ""; - request.SourcePath = sourcePath ?? ""; - request.TargetPath = targetPath ?? ""; - request.ExSerialized = ex.ToString(); - grpcClient.RpcClient.LogError(request); - } - - public void LogMessage(string message, string sourcePath = null, string targetPath = null, IOperationContext operationContext = null) - { - LogMessageRequest request = new(); - request.ComponentName = componentName; - request.Message = message ?? ""; - request.SourcePath = sourcePath ?? ""; - request.TargetPath = targetPath ?? ""; - grpcClient.RpcClient.LogMessage(request); - } - - public static string XmlSerialize(T toSerialize) - { - XmlSerializer xmlSerializer = new XmlSerializer(typeof(T)); - using (StringWriter textWriter = new StringWriter()) - { - xmlSerializer.Serialize(textWriter, toSerialize); - return textWriter.ToString(); - } - } - } - - public class GrpcException: Exception - { - public GrpcException(Exception ex):base("", ex) - {} - - public override IDictionary Data { get { return null; } } - } - - public class GrpcExceptionRequest - { - public string Message { get; set; } - public string StackTrace { get; set; } - - public GrpcExceptionRequest(Exception ex) - { - Message = ex.Message; - StackTrace = ex.StackTrace; - - Exception ex1 = new Exception(); - } - } -} diff --git a/Windows/Common/ShellExtension/Interop/EXPCMDFLAGS.cs b/Windows/Common/ShellExtension/Interop/EXPCMDFLAGS.cs deleted file mode 100644 index d4f93d9..0000000 --- a/Windows/Common/ShellExtension/Interop/EXPCMDFLAGS.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop -{ - [Flags] - public enum EXPCMDFLAGS : uint - { - ECF_DEFAULT = 0, - ECF_HASSUBCOMMANDS = 0x1, - ECF_HASSPLITBUTTON = 0x2, - ECF_HIDELABEL = 0x4, - ECF_ISSEPARATOR = 0x8, - ECF_HASLUASHIELD = 0x10, - ECF_SEPARATORBEFORE = 0x20, - ECF_SEPARATORAFTER = 0x40, - ECF_ISDROPDOWN = 0x80, - ECF_TOGGLEABLE = 0x100, - ECF_AUTOMENUICONS = 0x200 - } -} diff --git a/Windows/Common/ShellExtension/Interop/EXPCMDSTATE.cs b/Windows/Common/ShellExtension/Interop/EXPCMDSTATE.cs deleted file mode 100644 index 10356fa..0000000 --- a/Windows/Common/ShellExtension/Interop/EXPCMDSTATE.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop -{ - [Flags] - public enum EXPCMDSTATE : uint - { - ECS_ENABLED = 0, - ECS_DISABLED = 0x1, - ECS_HIDDEN = 0x2, - ECS_CHECKBOX = 0x4, - ECS_CHECKED = 0x8, - ECS_RADIOCHECK = 0x10 - } -} diff --git a/Windows/Common/ShellExtension/Interop/IEnumExplorerCommand.cs b/Windows/Common/ShellExtension/Interop/IEnumExplorerCommand.cs deleted file mode 100644 index 83ab706..0000000 --- a/Windows/Common/ShellExtension/Interop/IEnumExplorerCommand.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop -{ - [ComImport] - [Guid("A88826F8-186F-4987-AADE-EA0CEF8FBFE8")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - public interface IEnumExplorerCommand - { - [PreserveSig] - int Next(uint celt, - [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Interface, SizeParamIndex = 0)] - out IExplorerCommand[] pUICommand, - out uint pceltFetched); - - [PreserveSig] - int Skip(uint celt); - - [PreserveSig] - int Reset(); - - [PreserveSig] - int Clone(out IEnumExplorerCommand ppenum); - } -} diff --git a/Windows/Common/ShellExtension/Interop/IEnumShellItems.cs b/Windows/Common/ShellExtension/Interop/IEnumShellItems.cs deleted file mode 100644 index c1e04ed..0000000 --- a/Windows/Common/ShellExtension/Interop/IEnumShellItems.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop -{ - [ComImport] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - [Guid("70629033-E363-4A28-A567-0DB78006E6D7")] - public interface IEnumShellItems - { - [PreserveSig] - int Next(uint celt, - [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Interface, SizeParamIndex = 0)] out IShellItem[] rgelt, - out uint pceltFetched); - - [PreserveSig] - int Skip(uint celt); - - [PreserveSig] - int Reset(); - - [PreserveSig] - int Clone(out IEnumShellItems ppenum); - } -} diff --git a/Windows/Common/ShellExtension/Interop/IExplorerCommand.cs b/Windows/Common/ShellExtension/Interop/IExplorerCommand.cs deleted file mode 100644 index d7eedbb..0000000 --- a/Windows/Common/ShellExtension/Interop/IExplorerCommand.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop -{ - /// - /// Represents Windows Explorer context menu. - /// - /// - /// None of the methods of this interface should communicate with network resources. - /// These methods are called on the UI thread, so communication with network resources - /// could cause the UI to stop responding. - /// - [ComImport] - [Guid("A08CE4D0-FA25-44AB-B57C-C7B1C323E0B9")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - public interface IExplorerCommand - { - /// - /// Gets the title text of the button or menu item that launches a specified Windows Explorer command item. - /// - /// Shell Item array. - /// Title string. - /// If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code. - [PreserveSig] - int GetTitle( - IShellItemArray itemArray, - [MarshalAs(UnmanagedType.LPWStr)] out string title); - - /// - /// Gets an icon resource string of the icon associated with the specified Windows Explorer command item. - /// - /// Shell Item array. - /// resource string that identifies the icon source. - /// If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code. - [PreserveSig] - int GetIcon(IShellItemArray itemArray, - [MarshalAs(UnmanagedType.LPWStr)] out string resourceString); - - /// - /// Gets the tooltip string associated with a specified Windows Explorer command item. - /// - /// Shell Item array. - /// Tooltip string. - /// If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code. - [PreserveSig] - int GetToolTip(IShellItemArray itemArray, - [MarshalAs(UnmanagedType.LPWStr)] out string tooltip); - - /// - /// Gets the GUID of a Windows Explorer command. - /// - /// Command's GUID, under which it is declared in the registry. - /// If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code. - [PreserveSig] - int GetCanonicalName(out Guid guid); - - /// - /// Gets state information associated with a specified Windows Explorer command item. - /// - /// Shell Item array. - /// FALSE if a verb object should not perform any memory intensive computations that could cause the UI thread to stop responding. The verb object should return E_PENDING in that case. If TRUE, those computations can be completed. - /// One or more Windows Explorer command states. - /// If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code. - [PreserveSig] - int GetState(IShellItemArray itemArray, - [MarshalAs(UnmanagedType.Bool)] bool okToBeSlow, - out EXPCMDSTATE commandState); - - /// - /// Invokes a Windows Explorer command. - /// - /// Shell Item array. - /// A pointer to an IBindCtx interface, which provides access to a bind context. This value can be NULL if no bind context is needed. - /// If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code. - [PreserveSig] - int Invoke(IShellItemArray itemArray, - [MarshalAs(UnmanagedType.Interface)] object bindCtx); - - /// - /// Gets the flags associated with a Windows Explorer command. - /// - /// Item flags. - /// If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code. - [PreserveSig] - int GetFlags(out EXPCMDFLAGS flags); - - /// - /// Retrieves an enumerator for a command's subcommands. - /// - /// Contains an IEnumExplorerCommand interface pointer that can be used to walk the set of subcommands. - /// If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code. - [PreserveSig] - int EnumSubCommands(out IEnumExplorerCommand commandEnum); - } -} diff --git a/Windows/Common/ShellExtension/Interop/IInitializedWithItem.cs b/Windows/Common/ShellExtension/Interop/IInitializedWithItem.cs deleted file mode 100644 index 2584123..0000000 --- a/Windows/Common/ShellExtension/Interop/IInitializedWithItem.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop -{ - [ComImport] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - [Guid("7F73BE3F-FB79-493C-A6C7-7EE14E245841")] - public interface IInitializedWithItem - { - [PreserveSig] - int Initialize(IShellItem shellItem, STGM accessMode); - } -} diff --git a/Windows/Common/ShellExtension/Interop/IShellItem.cs b/Windows/Common/ShellExtension/Interop/IShellItem.cs deleted file mode 100644 index 97105bf..0000000 --- a/Windows/Common/ShellExtension/Interop/IShellItem.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop -{ - /// - /// Exposes methods that retrieve information about a Shell item. - /// - [ComImport] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - [Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")] - public interface IShellItem - { - /// - /// Binds to a handler for an item as specified by the handler ID value (BHID). - /// - [PreserveSig] - int BindToHandler(IntPtr pbc, - [MarshalAs(UnmanagedType.LPStruct)] Guid bhid, - [MarshalAs(UnmanagedType.LPStruct)] Guid riid, - out IntPtr ppv); - - [PreserveSig] - int GetParent(out IShellItem ppsi); - - [PreserveSig] - int GetDisplayName(SIGDN sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName); - - [PreserveSig] - int GetAttributes(uint sfgaoMask, out uint psfgaoAttribs); - - [PreserveSig] - int Compare(IShellItem psi, uint hint, out int piOrder); - } -} diff --git a/Windows/Common/ShellExtension/Interop/IShellItemArray.cs b/Windows/Common/ShellExtension/Interop/IShellItemArray.cs deleted file mode 100644 index 1f903df..0000000 --- a/Windows/Common/ShellExtension/Interop/IShellItemArray.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop -{ - [ComImport] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - [Guid("B63EA76D-1F85-456F-A19C-48159EFA858B")] - public interface IShellItemArray - { - [PreserveSig] - int BindToHandler(IntPtr pbc, - [MarshalAs(UnmanagedType.LPStruct)] Guid bhid, - [MarshalAs(UnmanagedType.LPStruct)] Guid riid, - out IntPtr ppv); - - [PreserveSig] - int GetPropertyStore( - int flags, - [MarshalAs(UnmanagedType.LPStruct)] Guid riid, - out IntPtr ppv); - - [PreserveSig] - int GetPropertyDescriptionList( - int keyType, - [MarshalAs(UnmanagedType.LPStruct)] Guid riid, - out IntPtr ppv); - - [PreserveSig] - int GetAttributes( - SIATTRIBFLAGS dwAttribFlags, - uint sfgaoMask, - out int psfgaoAttribs); - - [PreserveSig] - int GetCount(out ushort pdwNumItems); - - [PreserveSig] - int GetItemAt( - ushort dwIndex, - [MarshalAs(UnmanagedType.Interface)] - out IShellItem ppsi); - - [PreserveSig] - int EnumItems( - [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Interface, SizeParamIndex = 0)] - out IEnumShellItems[] ppenumShellItems); - } -} diff --git a/Windows/Common/ShellExtension/Interop/IThumbnailProvider.cs b/Windows/Common/ShellExtension/Interop/IThumbnailProvider.cs deleted file mode 100644 index 58a8f1f..0000000 --- a/Windows/Common/ShellExtension/Interop/IThumbnailProvider.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop -{ - /// - /// Exposes a method for getting a thumbnail image and is intended to be - /// implemented for thumbnail handlers. - /// - [ComImport] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - [Guid("E357FCCD-A995-4576-B01F-234630154E96")] - public interface IThumbnailProvider - { - /// - /// Gets a thumbnail image and alpha type. - /// - /// - /// The maximum thumbnail size, in pixels. The Shell draws the returned bitmap at this size or smaller. - /// The returned bitmap should fit into a square of width and height cx, though it does not need to be - /// a square image. The Shell scales the bitmap to render at lower sizes. For example, if the image has - /// a 6:4 aspect ratio, then the returned bitmap should also have a 6:4 aspect ratio. - /// - /// - /// When this method returns, contains a pointer to the thumbnail image handle. The image must be a DIB - /// section and 32 bits per pixel. The Shell scales down the bitmap if its width or height is larger - /// than the size specified by cx. The Shell always respects the aspect ratio and never scales a bitmap - /// larger than its original size. - /// - /// - /// - /// - /// - [PreserveSig] - int GetThumbnail(UInt32 cx, out IntPtr phbmp, out WTS_ALPHATYPE pdwAlpha); - } -} diff --git a/Windows/Common/ShellExtension/Interop/InitializedWithItem.cs b/Windows/Common/ShellExtension/Interop/InitializedWithItem.cs deleted file mode 100644 index 7afe8e3..0000000 --- a/Windows/Common/ShellExtension/Interop/InitializedWithItem.cs +++ /dev/null @@ -1,16 +0,0 @@ - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop -{ - public abstract class InitializedWithItem : IInitializedWithItem - { - public virtual int Initialize(IShellItem shellItem, STGM accessMode) - { - SelectedShellItem = shellItem; - SelectedShellItemAccessMode = accessMode; - return WinError.S_OK; - } - - public IShellItem SelectedShellItem { get; private set; } - public STGM SelectedShellItemAccessMode { get; private set; } - } -} diff --git a/Windows/Common/ShellExtension/Interop/Ole32.cs b/Windows/Common/ShellExtension/Interop/Ole32.cs deleted file mode 100644 index a681b5f..0000000 --- a/Windows/Common/ShellExtension/Interop/Ole32.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop -{ - public static class Ole32 - { - public const int CLSCTX_LOCAL_SERVER = 0x4; - - public const int REGCLS_MULTIPLEUSE = 1; - public const int REGCLS_SUSPENDED = 4; - - [DllImport(nameof(Ole32))] - public static extern int CoRegisterClassObject(ref Guid guid, [MarshalAs(UnmanagedType.IUnknown)] object obj, int context, int flags, out int register); - - [DllImport(nameof(Ole32))] - public static extern int CoResumeClassObjects(); - - [DllImport(nameof(Ole32))] - public static extern int CoRevokeClassObject(int register); - } -} diff --git a/Windows/Common/ShellExtension/Interop/SIATTRIBFLAGS.cs b/Windows/Common/ShellExtension/Interop/SIATTRIBFLAGS.cs deleted file mode 100644 index 838cc38..0000000 --- a/Windows/Common/ShellExtension/Interop/SIATTRIBFLAGS.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop -{ - public enum SIATTRIBFLAGS - { - SIATTRIBFLAGS_AND = 0x1, - SIATTRIBFLAGS_OR = 0x2, - SIATTRIBFLAGS_APPCOMPAT = 0x3, - SIATTRIBFLAGS_MASK = 0x3, - SIATTRIBFLAGS_ALLITEMS = 0x4000 - } -} diff --git a/Windows/Common/ShellExtension/Interop/SIGDN.cs b/Windows/Common/ShellExtension/Interop/SIGDN.cs deleted file mode 100644 index c3d38ec..0000000 --- a/Windows/Common/ShellExtension/Interop/SIGDN.cs +++ /dev/null @@ -1,17 +0,0 @@ - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop -{ - public enum SIGDN : uint - { - SIGDN_DESKTOPABSOLUTEEDITING = 0x8004c000, - SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000, - SIGDN_FILESYSPATH = 0x80058000, - SIGDN_NORMALDISPLAY = 0, - SIGDN_PARENTRELATIVE = 0x80080001, - SIGDN_PARENTRELATIVEEDITING = 0x80031001, - SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8007c001, - SIGDN_PARENTRELATIVEFORUI = 0x80094001, - SIGDN_PARENTRELATIVEPARSING = 0x80018001, - SIGDN_URL = 0x80068000 - } -} diff --git a/Windows/Common/ShellExtension/Interop/STGM.cs b/Windows/Common/ShellExtension/Interop/STGM.cs deleted file mode 100644 index a9dc5c5..0000000 --- a/Windows/Common/ShellExtension/Interop/STGM.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop -{ - [Flags] - public enum STGM - { - DIRECT = 0x00000000, - TRANSACTED = 0x00010000, - SIMPLE = 0x08000000, - READ = 0x00000000, - WRITE = 0x00000001, - READWRITE = 0x00000002, - SHARE_DENY_NONE = 0x00000040, - SHARE_DENY_READ = 0x00000030, - SHARE_DENY_WRITE = 0x00000020, - SHARE_EXCLUSIVE = 0x00000010, - PRIORITY = 0x00040000, - DELETEONRELEASE = 0x04000000, - NOSCRATCH = 0x00100000, - CREATE = 0x00001000, - CONVERT = 0x00020000, - FAILIFTHERE = 0x00000000, - NOSNAPSHOT = 0x00200000, - DIRECT_SWMR = 0x00400000, - } -} diff --git a/Windows/Common/ShellExtension/Interop/Shell32.cs b/Windows/Common/ShellExtension/Interop/Shell32.cs deleted file mode 100644 index d328c48..0000000 --- a/Windows/Common/ShellExtension/Interop/Shell32.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop -{ - public static class Shell32 - { - public static Guid BHID_ThumbnailHandler = new Guid("7b2e650a-8e20-4f4a-b09e-6597afc72fb0"); - - /// - /// Creates and initializes a Shell item object from a parsing name. - /// - /// A pointer to a display name. - [DllImport("shell32.dll", CharSet = CharSet.Unicode)] - public static extern uint SHCreateItemFromParsingName( - [MarshalAs(UnmanagedType.LPWStr)] string pszPath, - IBindCtx pbc, - [MarshalAs(UnmanagedType.LPStruct)] Guid riid, - out IShellItem ppv); - } -} diff --git a/Windows/Common/ShellExtension/Interop/ShellItemHelper.cs b/Windows/Common/ShellExtension/Interop/ShellItemHelper.cs deleted file mode 100644 index 416c49d..0000000 --- a/Windows/Common/ShellExtension/Interop/ShellItemHelper.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop -{ - public static class ShellItemHelper - { - public static IEnumerable GetFilesPath(this IShellItemArray shellItems) - { - if (shellItems.GetCount(out ushort shellItemCount) != WinError.S_OK) - throw new ArgumentException(); - - List files = new List(); - - for (ushort i = 0; i < shellItemCount; i++) - { - if (shellItems.GetItemAt(i, out IShellItem shellItem) != WinError.S_OK) - continue; - - if (shellItem.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out string path) != WinError.S_OK) - continue; - - files.Add(path); - } - - return files; - } - - public static string GetFilePath(this IShellItem shellItem) - { - if (shellItem.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out string path) != WinError.S_OK) - throw new ArgumentException(); - - return path; - } - } -} diff --git a/Windows/Common/ShellExtension/Interop/WTS_ALPHATYPE.cs b/Windows/Common/ShellExtension/Interop/WTS_ALPHATYPE.cs deleted file mode 100644 index bee3a56..0000000 --- a/Windows/Common/ShellExtension/Interop/WTS_ALPHATYPE.cs +++ /dev/null @@ -1,10 +0,0 @@ - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop -{ - public enum WTS_ALPHATYPE - { - WTSAT_UNKNOWN = 0x0, - WTSAT_RGB = 0x1, - WTSAT_ARGB = 0x2 - } -} diff --git a/Windows/Common/ShellExtension/Interop/WinError.cs b/Windows/Common/ShellExtension/Interop/WinError.cs deleted file mode 100644 index f4fb15c..0000000 --- a/Windows/Common/ShellExtension/Interop/WinError.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop -{ - public static class WinError - { - public const int S_OK = 0x0000; - public const int S_FALSE = 0x0001; - public const int E_FAIL = -2147467259; - public const int E_INVALIDARG = -2147024809; - public const int E_OUTOFMEMORY = -2147024882; - public const int E_UNEXPECTED = unchecked((int)0x8000FFFF); - public const int E_NOTIMPL = unchecked((int)0x80004001); - public const int E_NOINTERFACE = unchecked((int)0x80004002); - } -} diff --git a/Windows/Common/ShellExtension/ReferenceManager.cs b/Windows/Common/ShellExtension/ReferenceManager.cs deleted file mode 100644 index a5f9701..0000000 --- a/Windows/Common/ShellExtension/ReferenceManager.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension -{ - public delegate void ReferenceEventHandler(); - - /// - /// ReferenceManager provides centralised methods to notify that new com objects created/released or server locked/unlocked. - /// - public static class ReferenceManager - { - /// - /// Notify that com object was created. - /// - public static void AddObjectReference() - { - ObjectCreated?.Invoke(); - } - - /// - /// Notify that com object was released. - /// - public static void ReleaseObjectReference() - { - ObjectDestroyed?.Invoke(); - } - - /// - /// Notify that server was locked. - /// - public static void LockServer() - { - ServerLocked?.Invoke(); - } - - /// - /// Notify that server was unlocked. - /// - public static void UnlockServer() - { - ServerUnlocked?.Invoke(); - } - - public static event ReferenceEventHandler ObjectCreated; - public static event ReferenceEventHandler ObjectDestroyed; - - public static event ReferenceEventHandler ServerLocked; - public static event ReferenceEventHandler ServerUnlocked; - } -} diff --git a/Windows/Common/ShellExtension/ShellExtensionConfiguration.cs b/Windows/Common/ShellExtension/ShellExtensionConfiguration.cs deleted file mode 100644 index f67ce44..0000000 --- a/Windows/Common/ShellExtension/ShellExtensionConfiguration.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.IO; -using log4net; -using log4net.Appender; -using log4net.Core; -using log4net.Layout; -using log4net.Repository.Hierarchy; -using Microsoft.Extensions.Configuration; - - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension -{ - /// - /// ShellExtension configuration class. - /// - public static class ShellExtensionConfiguration - { - /// - /// ShellExtension settings. - /// - public static Settings AppSettings { get; private set; } - - /// - /// Initialize or load settings. - /// - public static void Initialize(Settings settings = null) - { - if (settings != null) - { - AppSettings = settings; - } - else - { - AppSettings = Load(); - } - } - - /// - /// Returns is path to virtual drive folder - /// - /// - /// - public static bool IsVirtualDriveFolder(string path) - { - string rootPath = Environment.ExpandEnvironmentVariables(AppSettings.UserFileSystemRootPath); - - return !string.IsNullOrEmpty(path) && path.TrimStart().StartsWith(rootPath); - } - - private static Settings Load() - { - string assemblyPath = Path.GetDirectoryName(typeof(ShellExtensionConfiguration).Assembly.Location); - string path = Path.Combine(assemblyPath, "appsettings.json"); - Settings settings = new Settings(); - IConfiguration configuration = new ConfigurationBuilder().AddJsonFile(path, false, true).Build(); - configuration.Bind(settings); - - return settings; - } - } -} diff --git a/Windows/Common/ShellExtension/ShellExtensionRegistrar.cs b/Windows/Common/ShellExtension/ShellExtensionRegistrar.cs deleted file mode 100644 index 1a61ffd..0000000 --- a/Windows/Common/ShellExtension/ShellExtensionRegistrar.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Microsoft.Win32; -using System; - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension -{ - public class ShellExtensionRegistrar - { - private static readonly string ClsidKeyPathFormat = @"SOFTWARE\Classes\CLSID\{0:B}"; - private static readonly string LocalServer32PathFormat = @$"{ClsidKeyPathFormat}\LocalServer32"; - private static readonly string SyncRootPathFormat = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\SyncRootManager\{0}"; - - /// - /// Register shell service provider COM class and register it for sync root. - /// - /// Sync root identifier - /// Name of shell service provider - /// CLSID of shell service provider - /// Absolute path to COM server executable - public static void Register(string syncRootId, string handlerName, Guid handlerGuid, string comServerPath) - { - string handlerPath = handlerGuid.ToString("B").ToUpper(); - string syncRootPath = string.Format(SyncRootPathFormat, syncRootId); - - using RegistryKey syncRootKey = Registry.LocalMachine.OpenSubKey(syncRootPath, true); - syncRootKey.SetValue(handlerName, handlerPath); - - string localServer32Path = string.Format(LocalServer32PathFormat, handlerPath); - using RegistryKey localServer32Key = Registry.CurrentUser.CreateSubKey(localServer32Path); - localServer32Key.SetValue(null, comServerPath); - } - - /// - /// Unregister shell service provider COM class. - /// - /// - public static void Unregister(Guid handlerClsid) - { - string thumbnailProviderGuid = handlerClsid.ToString("B").ToUpper(); - string clsidKeyPath = string.Format(ClsidKeyPathFormat, thumbnailProviderGuid); - Registry.CurrentUser.DeleteSubKeyTree(clsidKeyPath, false); - } - } -} diff --git a/Windows/Common/ShellExtension/ThumbnailProviderBase.cs b/Windows/Common/ShellExtension/ThumbnailProviderBase.cs deleted file mode 100644 index a649efb..0000000 --- a/Windows/Common/ShellExtension/ThumbnailProviderBase.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using System.Drawing; -using System.Drawing.Imaging; -using System.IO; -using System.Threading.Tasks; -using log4net; -using ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop; - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Thumbnails -{ - - /// - /// Common code for a thumbnails provider. - /// - /// - /// You will derive your class from this class to implement your Thumbnails provider Windows Shell Extension - /// Typically you do not need to make any changes in this class. - /// - public abstract class ThumbnailProviderBase : InitializedWithItem, IThumbnailProvider - { - public abstract Task GetThumbnailsAsync(string filePath, uint size); - - private string filePath = null; - - protected ILogger Log { get; } - - public ThumbnailProviderBase() - { - ReferenceManager.AddObjectReference(); - - Log = new GrpcLogger("Thumbnail Provider"); - } - - ~ThumbnailProviderBase() - { - ReferenceManager.ReleaseObjectReference(); - } - - public override int Initialize(IShellItem shellItem, STGM accessMode) - { - if (shellItem.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out string path) != WinError.S_OK) - { - return WinError.E_UNEXPECTED; - } - - if (!ShellExtensionConfiguration.IsVirtualDriveFolder(path)) - { - return WinError.E_UNEXPECTED; - } - - // Show thumbnails only for files (Dont show thumbnails for directories) - if (!File.Exists(path)) - { - return WinError.E_UNEXPECTED; - } - filePath = path; - - return base.Initialize(shellItem, accessMode); - } - - public int GetThumbnail(uint cx, out IntPtr phbmp, out WTS_ALPHATYPE pdwAlpha) - { - phbmp = IntPtr.Zero; - pdwAlpha = WTS_ALPHATYPE.WTSAT_UNKNOWN; - - try - { - //Log.LogMessage($"{nameof(ThumbnailProviderBase)}.{nameof(GetThumbnail)}()", filePath); - - byte[] bitmapData = GetThumbnailsAsync(filePath, cx).GetAwaiter().GetResult(); - - using (MemoryStream stream = new MemoryStream(bitmapData)) - using (Bitmap thumbnail = new Bitmap(stream)) - { - if (thumbnail == null) - { - return WinError.E_FAIL; - } - - phbmp = thumbnail.GetHbitmap(); - pdwAlpha = GetAlphaType(thumbnail); - } - - return WinError.S_OK; - } - catch (NotImplementedException) - { - return WinError.E_FAIL; - } - catch (Exception ex) - { - Log.LogError("", null, null, ex); - return WinError.E_FAIL; - } - } - - private static WTS_ALPHATYPE GetAlphaType(Bitmap image) - { - PixelFormat pixelFormat = image.PixelFormat; - - if (Bitmap.IsAlphaPixelFormat(pixelFormat) || Bitmap.IsCanonicalPixelFormat(pixelFormat)) - { - return WTS_ALPHATYPE.WTSAT_ARGB; - } - else if (pixelFormat != PixelFormat.Undefined) - { - return WTS_ALPHATYPE.WTSAT_RGB; - } - else - { - return WTS_ALPHATYPE.WTSAT_UNKNOWN; - } - } - } -} diff --git a/Windows/Common/ShellExtension/ThumbnailProviderCommon.cs b/Windows/Common/ShellExtension/ThumbnailProviderCommon.cs deleted file mode 100644 index 46d3123..0000000 --- a/Windows/Common/ShellExtension/ThumbnailProviderCommon.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Drawing; -using System.Threading.Tasks; -using System.IO; - -using ITHit.FileSystem.Samples.Common.Windows.Rpc; -using ITHit.FileSystem.Samples.Common.Windows.Rpc.Generated; - -namespace ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Thumbnails -{ - public class ThumbnailProviderCommon : ThumbnailProviderBase - { - public override async Task GetThumbnailsAsync(string filePath, uint size) - { - try - { - GrpcClient grpcClient = new GrpcClient(ShellExtensionConfiguration.AppSettings.RpcCommunicationChannelName); - ThumbnailRequest thumbnailRequest = new ThumbnailRequest(); - thumbnailRequest.Path = filePath; - thumbnailRequest.Size = size; - - Thumbnail thumbnail = await grpcClient.RpcClient.GetThumbnailAsync(thumbnailRequest); - - return thumbnail.Image.ToByteArray(); - } - catch (Exception ex) - { - throw new NotImplementedException(ex.Message); - } - } - } -} diff --git a/Windows/Common/VirtualDrive/Common.Windows.VirtualDrive.csproj b/Windows/Common/VirtualDrive/Common.Windows.VirtualDrive.csproj index 25159b3..622fb7a 100644 --- a/Windows/Common/VirtualDrive/Common.Windows.VirtualDrive.csproj +++ b/Windows/Common/VirtualDrive/Common.Windows.VirtualDrive.csproj @@ -1,7 +1,6 @@ - net5.0-windows10.0.18362.0 - 10.0.18362.0 + net48;net5.0-windows10.0.19041.0 Contains functionality common for all Windows Virtual Drive samples. IT Hit LTD. IT Hit User File System @@ -19,13 +18,8 @@ - - - - - + - \ No newline at end of file diff --git a/Windows/Common/VirtualDrive/Filter/MsOfficeFilterHelper.cs b/Windows/Common/VirtualDrive/Filter/MsOfficeFilterHelper.cs index 59d8da1..26717a5 100644 --- a/Windows/Common/VirtualDrive/Filter/MsOfficeFilterHelper.cs +++ b/Windows/Common/VirtualDrive/Filter/MsOfficeFilterHelper.cs @@ -40,7 +40,7 @@ private static bool IsMsOfficeTemp(string path) { string fileName = Path.GetFileNameWithoutExtension(path); string extension = Path.GetExtension(path); - return (fileName.StartsWith('~') && extension.Equals(".tmp", StringComparison.InvariantCultureIgnoreCase)) // Word temp files + return (fileName.StartsWith("~") && extension.Equals(".tmp", StringComparison.InvariantCultureIgnoreCase)) // Word temp files || (fileName.StartsWith("ppt") && extension.Equals(".tmp", StringComparison.InvariantCultureIgnoreCase)) // PowerPoint temp files || (string.IsNullOrEmpty(extension) && (fileName.Length == 8) && System.IO.File.Exists(path)) // Excel temp files type 1 || (((fileName.Length == 8) || (fileName.Length == 7)) && extension.Equals(".tmp", StringComparison.InvariantCultureIgnoreCase)); // Excel temp files type 2 diff --git a/Windows/Common/VirtualDrive/FullSync/FullSyncService.cs b/Windows/Common/VirtualDrive/FullSync/FullSyncService.cs index 415516b..779a3cc 100644 --- a/Windows/Common/VirtualDrive/FullSync/FullSyncService.cs +++ b/Windows/Common/VirtualDrive/FullSync/FullSyncService.cs @@ -23,7 +23,7 @@ namespace ITHit.FileSystem.Samples.Common.Windows /// You can use this class in your project out of the box or replace it with a more advanced algorithm. /// This sample does not do UFS->RS sync for moved and deleted items. All items moved and deleted in user file system while the app is not running are lost. /// - public class FullSyncService : Logger, IDisposable + public class FullSyncService : IDisposable { /// /// Current synchronization state. @@ -38,30 +38,36 @@ public class FullSyncService : Logger, IDisposable /// /// Virtual drive instance to which this synchronization service belongs. /// - private VirtualEngineBase engine; + private readonly VirtualEngineBase engine; + + /// + /// Logger. + /// + public readonly ILogger Logger; /// /// Timer to start synchronization. /// - private System.Timers.Timer timer = null; + private readonly System.Timers.Timer timer = null; /// - /// User file system path. + /// User file system root path. /// - private string userFileSystemRootPath; + private readonly string userFileSystemRootPath; /// /// Creates instance of this class. /// /// Synchronization interval in milliseconds. /// User file system root path. - /// Logger. - internal FullSyncService(double syncIntervalMs, string userFileSystemRootPath, VirtualEngineBase engine, ILog log) : base("Sync Service", log) + /// Logger. + internal FullSyncService(double syncIntervalMs, string userFileSystemRootPath, VirtualEngineBase engine, ILogger logger) { timer = new System.Timers.Timer(syncIntervalMs); timer.Elapsed += Timer_ElapsedAsync; this.userFileSystemRootPath = userFileSystemRootPath; this.engine = engine; + this.Logger = logger.CreateLogger("Sync Service"); } /// @@ -74,11 +80,11 @@ public async Task StartAsync() return; } - // Do not start next synchronyzation automatically, wait until previous synchronyzation completed. + // Do not start next synchronyzation automatically, wait until a previous synchronyzation is completed. timer.AutoReset = false; timer.Start(); InvokeSyncEvent(SynchronizationState.Enabled); - LogMessage("Started", userFileSystemRootPath); + Logger.LogMessage("Started", userFileSystemRootPath); } /// @@ -93,7 +99,7 @@ public async Task StopAsync() timer.Stop(); InvokeSyncEvent(SynchronizationState.Disabled); - LogMessage("Stopped", userFileSystemRootPath); + Logger.LogMessage("Stopped", userFileSystemRootPath); } private void InvokeSyncEvent(SynchronizationState newState) @@ -116,8 +122,8 @@ private async void Timer_ElapsedAsync(object sender, System.Timers.ElapsedEventA { InvokeSyncEvent(SynchronizationState.Synchronizing); - // UFS -> RS. Recursivery synchronize all updated/created file and folders present in the user file system. - //await new ClientToServerSync(engine, Log).SyncronizeFolderAsync(userFileSystemRootPath); + // UFS -> RS. Synchronize all created/updated/moved/deleted from the user file system to the remote storage. + await engine.ProcessAsync(); // UFS <- RS. Recursivery synchronize all updated/created/deleted file and folders present in the user file system. //await new ServerToClientSync(engine, Log).SyncronizeFolderAsync(userFileSystemRootPath); @@ -126,7 +132,7 @@ private async void Timer_ElapsedAsync(object sender, System.Timers.ElapsedEventA } catch(Exception ex) { - LogError(null, null, null, ex); + Logger.LogError(null, null, null, ex); } finally { @@ -137,7 +143,7 @@ private async void Timer_ElapsedAsync(object sender, System.Timers.ElapsedEventA private void Error(object sender, ErrorEventArgs e) { - LogError(null, null, null, e.GetException()); + Logger.LogError(null, null, null, e.GetException()); } private bool disposedValue = false; // To detect redundant calls @@ -150,7 +156,7 @@ protected virtual void Dispose(bool disposing) { timer.Stop(); timer.Dispose(); - LogMessage($"Disposed"); + Logger.LogMessage($"Disposed"); } // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. diff --git a/Windows/Common/VirtualDrive/IMapping.cs b/Windows/Common/VirtualDrive/IMapping.cs index 8567976..ec1e751 100644 --- a/Windows/Common/VirtualDrive/IMapping.cs +++ b/Windows/Common/VirtualDrive/IMapping.cs @@ -36,16 +36,5 @@ public interface IMapping /// Remote storage item metadata. /// //Task IsModifiedAsync(string userFileSystemPath, FileSystemItemMetadataExt remoteStorageItemMetadata, ILogger logger); - - /// - /// Reads ETag from the remote storage item and updates it on the user file system item. - /// - /// Remote storage path. - /// User file system path. - /// - /// True if the ETag was updated succesefully. False - if the call was ignored - /// (becuse the user file system item is offline or the call is for a folder item). - /// - //Task UpdateETagAsync(string remoteStoragePath, string userFileSystemPath); } } diff --git a/Windows/Common/VirtualDrive/IVirtualFolder.cs b/Windows/Common/VirtualDrive/IVirtualFolder.cs index d0f55c4..4f2502f 100644 --- a/Windows/Common/VirtualDrive/IVirtualFolder.cs +++ b/Windows/Common/VirtualDrive/IVirtualFolder.cs @@ -9,6 +9,6 @@ namespace ITHit.FileSystem.Samples.Common.Windows { public interface IVirtualFolder { - public Task> EnumerateChildrenAsync(string pattern, CancellationToken cancellationToken); + Task> EnumerateChildrenAsync(string pattern, CancellationToken cancellationToken); } } diff --git a/Windows/Common/VirtualDrive/MenuCommandLock.cs b/Windows/Common/VirtualDrive/MenuCommandLock.cs new file mode 100644 index 0000000..1caf763 --- /dev/null +++ b/Windows/Common/VirtualDrive/MenuCommandLock.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +using ITHit.FileSystem.Windows; + + +namespace ITHit.FileSystem.Samples.Common.Windows +{ + + /// + /// Implements lock and unlock context menu command displayed in a file manager. + /// The menu is shown only if all selected items are locked by this client or all items are unlocked. + /// Otherwise the menu is hidden. + /// + public class MenuCommandLock : IMenuCommand + { + private readonly EngineWindows engine; + private readonly ILogger logger; + + private const string lockCommandIcon = @"Images\Locked.ico"; + private const string unlockCommandIcon = @"Images\Unlocked.ico"; + + /// + /// Creates instance of this class. + /// + /// Engine instance. + /// Logger. + public MenuCommandLock(EngineWindows engine, ILogger logger) + { + this.engine = engine; + this.logger = logger.CreateLogger("Lock Command"); + } + + /// + public async Task GetTitleAsync(IEnumerable filesPath) + { + bool isLocked = await IsLockedAsync(filesPath) == true; + return isLocked ? "Unlock" : "Lock"; + } + + /// + public async Task GetIconAsync(IEnumerable filesPath) + { + string iconName = await IsLockedAsync(filesPath) == false ? lockCommandIcon : unlockCommandIcon; + string iconPath = Path.Combine(Path.GetDirectoryName(typeof(MenuCommandLock).Assembly.Location), iconName); + return iconPath; + } + + /// + public async Task GetStateAsync(IEnumerable filesPath) + { + bool? isLocked = await IsLockedAsync(filesPath); + return isLocked.HasValue ? MenuState.Enabled : MenuState.Hidden; + } + + /// + public async Task InvokeAsync(IEnumerable filesPath) + { + // If you need a remote storage ID for each item use the following code: + //foreach (string userFileSystemPath in filesPath) + //{ + // if(engine.Placeholders.TryGetItem(userFileSystemPath, out PlaceholderItem placeholder)) + // { + // byte[] remoteStorageId = placeholder.GetRemoteStorageItemId(); + // } + //} + + bool isLocked = await IsLockedAsync(filesPath) == true; + foreach (string userFileSystemPath in filesPath) + { + try + { + IClientNotifications clientNotifications = engine.ClientNotifications(userFileSystemPath); + if (isLocked) + await clientNotifications.UnlockAsync(); + else + await clientNotifications.LockAsync(); + } + catch (Exception ex) + { + logger.LogError("Failed to lock item", userFileSystemPath, null, ex); + } + } + } + + /// + public async Task GetToolTipAsync(IEnumerable filesPath) + { + bool isLocked = await IsLockedAsync(filesPath) == true; + return isLocked ? "Unlock item(s)" : "Lock item(s)"; + } + + /// + /// Returns files lock status. + /// + /// + /// True - if all items are locked. False - if all items are unlocked. null - if some items are locked, others unlocked. + /// + private async Task IsLockedAsync(IEnumerable filesPath, CancellationToken cancellationToken = default) + { + bool? allLocked = null; + foreach (string userFileSystemPath in filesPath) + { + try + { + IClientNotifications clientNotifications = engine.ClientNotifications(userFileSystemPath); + LockMode lockMode = await clientNotifications.GetLockModeAsync(cancellationToken); + + bool isLocked = lockMode != LockMode.None; + + if(allLocked.HasValue && (allLocked != isLocked)) + { + return null; + } + + allLocked = isLocked; + } + catch (Exception ex) + { + logger.LogError("Failed to get lock state", userFileSystemPath, null, ex); + } + } + + return allLocked; + } + } + +} diff --git a/Windows/Common/VirtualDrive/Rpc/GrpcServer.cs b/Windows/Common/VirtualDrive/Rpc/GrpcServer.cs deleted file mode 100644 index a2be4ac..0000000 --- a/Windows/Common/VirtualDrive/Rpc/GrpcServer.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using ITHit.FileSystem.Samples.Common.Windows.Rpc.Generated; -using GrpcDotNetNamedPipes; -using log4net; -using System.IO.Pipes; -using System.Security.Principal; -using System.Security.AccessControl; -using System.IO; - -namespace ITHit.FileSystem.Samples.Common.Windows.Rpc -{ - /// - /// Provides RPC methods thought name pipes channel and protobuf protocol. - /// - public class GrpcServer : Logger, IDisposable - { - private readonly string rpcCommunicationChannelName; - - private IDisposable namedPipeServer; - - private readonly VirtualEngineBase engine; - - public GrpcServer(string rpcCommunicationChannelName, VirtualEngineBase engine, ILog log4net) - : base("gRPC Server", log4net) - { - this.rpcCommunicationChannelName = rpcCommunicationChannelName; - - this.engine = engine; - } - - /// - /// Starts server and bind RPC methods handler. - /// - public void Start() - { - try - { - if (namedPipeServer != null) - { - Stop(); - } - - var pipeName = Path.Combine(WindowsIdentity.GetCurrent().Owner.Value, rpcCommunicationChannelName); - - NamedPipeServer server = new NamedPipeServer(pipeName, new NamedPipeServerOptions() { - CurrentUserOnly = true - }); - ShellExtensionRpc.BindService(server.ServiceBinder, new GrpcServerServiceImpl(engine, this)); - server.Start(); - namedPipeServer = server; - - LogMessage("Started", pipeName); - } - catch (Exception ex) - { - LogError(ex.Message); - } - } - - // Creates a PipeSecurity that allows users read/write access - PipeSecurity CreateSystemIOPipeSecurity() - { - PipeSecurity pipeSecurity = new PipeSecurity(); - - var id = new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null); - - // Allow Everyone read and write access to the pipe. - pipeSecurity.SetAccessRule(new PipeAccessRule(id, PipeAccessRights.ReadWrite, AccessControlType.Allow)); - - return pipeSecurity; - } - - /// - /// Stops server. - /// - public void Stop() - { - if (namedPipeServer != null) - { - namedPipeServer.Dispose(); - namedPipeServer = null; - - LogMessage("Stopped", rpcCommunicationChannelName); - } - } - - public void Dispose() - { - Stop(); - } - } -} diff --git a/Windows/Common/VirtualDrive/Rpc/GrpcServerServiceImpl.cs b/Windows/Common/VirtualDrive/Rpc/GrpcServerServiceImpl.cs deleted file mode 100644 index f32cd26..0000000 --- a/Windows/Common/VirtualDrive/Rpc/GrpcServerServiceImpl.cs +++ /dev/null @@ -1,206 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using ITHit.FileSystem.Windows; -using ITHit.FileSystem.Samples.Common.Windows.Rpc.Generated; -using ITHit.FileSystem.Samples.Common.Windows.VirtualDrive.Rpc; -using Grpc.Core; -using System.Xml.Serialization; -using System.IO; - -namespace ITHit.FileSystem.Samples.Common.Windows.Rpc -{ - /// - /// RPC methods handler. - /// - public class GrpcServerServiceImpl : ShellExtensionRpc.ShellExtensionRpcBase - { - private static EmptyMessage EmptyMessage = new EmptyMessage(); - - private VirtualEngineBase engine; - private ILogger logger; - - public GrpcServerServiceImpl(VirtualEngineBase engine, ILogger logger) - { - this.engine = engine; - this.logger = logger; - } - - /// - /// Set lock/unlock status of files. - /// - public override async Task SetLockStatus(ItemsStatusList request, ServerCallContext context) - { - bool failed = false; - List errorMessages = new List(); - - foreach (KeyValuePair pair in request.FilesStatus) - { - try - { - string filePath = pair.Key; - bool fileStatus = pair.Value; - - IClientNotifications clientNotifications = engine.ClientNotifications(filePath); - if (fileStatus) - await clientNotifications.LockAsync(cancellationToken: context.CancellationToken); - else - await clientNotifications.UnlockAsync(cancellationToken: context.CancellationToken); - } - catch (Exception ex) - { - failed = true; - errorMessages.Add(ex.Message); - logger.LogError(ex.Message); - } - } - - if (failed) - throw new RpcException(new Status(StatusCode.Internal, string.Join(". ", errorMessages))); - - return EmptyMessage; - } - - /// - /// Returns files lock status. - /// - public override async Task GetLockStatus(ItemsPathList request, ServerCallContext context) - { - try - { - ItemsStatusList itemsStatusList = new ItemsStatusList(); - - foreach (string filePath in request.Files) - { - IClientNotifications clientNotifications = engine.ClientNotifications(filePath); - LockMode lockMode = await clientNotifications.GetLockModeAsync(cancellationToken: context.CancellationToken); - bool lockStatus = lockMode != LockMode.None; - - itemsStatusList.FilesStatus.Add(filePath, lockStatus); - } - - return itemsStatusList; - } - catch (Exception ex) - { - logger.LogError(ex.Message); - throw new RpcException(new Status(StatusCode.Internal, ex.Message)); - } - } - - /// - /// Returns Thumbnail. - /// - public override async Task GetThumbnail(ThumbnailRequest thumbnailRequest, ServerCallContext context) - { - if (thumbnailRequest == null) - { - throw new RpcException(new Status(StatusCode.Internal, "thumbnailRequest is null")); - } - - string path = thumbnailRequest.Path; - uint size = thumbnailRequest.Size; - - try - { - byte[] bitmap = await engine.GetThumbnailAsync(path, size); - if ((bitmap == null) || (bitmap.Length == 0)) - { - throw new NotImplementedException(); - } - - Thumbnail thumbnail = new Thumbnail(); - thumbnail.Image = Google.Protobuf.ByteString.CopyFrom(bitmap); - - return thumbnail; - } - catch (NotImplementedException) - { - // Thumbnail is not implemented - string msg = $"Thumbnail for {path} is not implemented"; - throw new RpcException(new Status(StatusCode.Internal, msg)); - } - catch (Exception ex) - { - logger.LogError("Error getting thumbnail", path, null, ex); - throw new RpcException(new Status(StatusCode.Internal, ex.Message)); - } - } - - /// - /// Logs Message. - /// - public override async Task LogMessage(LogMessageRequest request, ServerCallContext context) - { - logger.LogMessage(request.Message, request.SourcePath, request.TargetPath); - return EmptyMessage; - } - - /// - /// Logs Error. - /// - public override async Task LogError(LogErrorRequest request, ServerCallContext context) - { - logger.LogError(request.Message, request.SourcePath, request.TargetPath, new Exception(request.ExSerialized)); - return EmptyMessage; - } - - /// - /// Set lock/unlock status of files. - /// - public override async Task GetItemProperties(ItemPropertyRequest request, ServerCallContext context) - { - try - { - ItemsPropertyList grpcProps = new ItemsPropertyList(); - - IEnumerable props = await engine.GetItemPropertiesAsync(request.Path); - - foreach (FileSystemItemPropertyData prop in props) - { - ItemProperty grpcProp = new ItemProperty() - { - Id = prop.Id, - Value = prop.Value, - IconResource = prop.IconResource ?? Path.Combine(engine.IconsFolderPath, "Empty.ico") - }; - grpcProps.Properties.Add(grpcProp); - } - - return grpcProps; - } - catch (Exception ex) - { - logger.LogError(ex.Message, request.Path, default, ex); - throw new RpcException(new Status(StatusCode.Internal, ex.Message)); - } - } - - public override async Task GetPathForContentUri(UriSourceRequest request, ServerCallContext context) - { - logger.LogMessage($"{nameof(GetPathForContentUri)}()", request.PathOrUri); - - GetPathForContentUriResult result = new GetPathForContentUriResult() - { - Path = request.PathOrUri + "\\test", - Status = (int)StorageProviderUriSourceStatus.Success - }; - - return result; - } - - public override async Task GetContentInfoForPath(UriSourceRequest request, ServerCallContext context) - { - logger.LogMessage($"{nameof(GetContentInfoForPath)}()", request.PathOrUri); - - GetContentInfoForPathResult result = new GetContentInfoForPathResult() - { - ContentId = "id. " + request.PathOrUri, - ContentUri = "uri\\" + request.PathOrUri, - Status = (int)StorageProviderUriSourceStatus.Success - }; - - return result; - } - } -} diff --git a/Windows/Common/VirtualDrive/Rpc/StorageProviderUriSourceStatus.cs b/Windows/Common/VirtualDrive/Rpc/StorageProviderUriSourceStatus.cs deleted file mode 100644 index 5a9e608..0000000 --- a/Windows/Common/VirtualDrive/Rpc/StorageProviderUriSourceStatus.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace ITHit.FileSystem.Samples.Common.Windows.VirtualDrive.Rpc -{ - public enum StorageProviderUriSourceStatus : int - { - Success = 0, - NoSyncRoot = 1, - FileNotFound = 2, - } -} diff --git a/Windows/Common/VirtualDrive/VirtualEngineBase.cs b/Windows/Common/VirtualDrive/VirtualEngineBase.cs index a052e18..4fcaedd 100644 --- a/Windows/Common/VirtualDrive/VirtualEngineBase.cs +++ b/Windows/Common/VirtualDrive/VirtualEngineBase.cs @@ -6,10 +6,10 @@ using ITHit.FileSystem; using ITHit.FileSystem.Windows; using ITHit.FileSystem.Samples.Common.Windows; -using ITHit.FileSystem.Samples.Common.Windows.Rpc; using System.Collections.Generic; using System.Threading; + namespace ITHit.FileSystem.Samples.Common.Windows { /// @@ -22,25 +22,21 @@ public abstract class VirtualEngineBase : EngineWindows /// /// Full synchronization service. - /// In case any changes are lost (app restart, lost connection, etc.) this service will sync all changes. + /// In case any changes are lost (the file is blocked, app restart, lost connection, etc.) this service will sync all changes. /// public readonly FullSyncService SyncService; - /// - /// Logger. - /// - private readonly ILogger logger; - /// /// Path to the icons folder. /// private readonly string iconsFolderPath; /// - /// Grpc server control to communicate with Windows Explorer - /// context menu and other components on this machine. + /// Folder that contains images displayed in Status column, context menu, etc. /// - private readonly GrpcServer grpcServer; + public string IconsFolderPath => iconsFolderPath; + + //public abstract IMapping Mapping { get; } /// /// Creates a vitual file system Engine. @@ -52,41 +48,32 @@ public abstract class VirtualEngineBase : EngineWindows /// /// Path to the remote storage root. /// Path to the icons folder. - /// Channel name to communicate with Windows Explorer context menu and other components on this machine. /// Full synchronization interval in milliseconds. - /// A maximum number of concurrent tasks. - /// Log4net logger. + /// Logger. public VirtualEngineBase( string license, string userFileSystemRootPath, string remoteStorageRootPath, string iconsFolderPath, - string rpcCommunicationChannelName, double syncIntervalMs, - int maxDegreeOfParallelism, - ILog log4net) - : base(license, userFileSystemRootPath, maxDegreeOfParallelism) + LogFormatter logFormatter) + : base(license, userFileSystemRootPath) { - logger = new Logger("File System Engine", log4net) ?? throw new NullReferenceException(nameof(log4net)); this.iconsFolderPath = iconsFolderPath ?? throw new NullReferenceException(nameof(iconsFolderPath)); - this.grpcServer = new GrpcServer(rpcCommunicationChannelName, this, log4net); // We want our file system to run regardless of any errors. // If any request to file system fails in user code or in Engine itself we continue processing. ThrowExceptions = false; StateChanged += Engine_StateChanged; - Error += Engine_Error; - Message += Engine_Message; + Error += logFormatter.LogError; + Message += logFormatter.LogMessage; + Debug += logFormatter.LogDebug; //RemoteStorageMonitor = new RemoteStorageMonitor(remoteStorageRootPath, this, log4net); - SyncService = new FullSyncService(syncIntervalMs, userFileSystemRootPath, this, log4net); + SyncService = new FullSyncService(syncIntervalMs, userFileSystemRootPath, this, this.Logger); } - //public abstract IMapping Mapping { get; } - - public string IconsFolderPath => iconsFolderPath; - /// public override async Task FilterAsync(OperationType operationType, string userFileSystemPath, string userFileSystemNewPath = null, IOperationContext operationContext = null) { @@ -127,8 +114,7 @@ public override async Task StartAsync(bool processModified = true, CancellationT { await base.StartAsync(processModified, cancellationToken); //RemoteStorageMonitor.Start(); - //await SyncService.StartAsync(); - grpcServer.Start(); + await SyncService.StartAsync(); } public override async Task StopAsync() @@ -136,17 +122,6 @@ public override async Task StopAsync() await base.StopAsync(); //RemoteStorageMonitor.Stop(); await SyncService.StopAsync(); - grpcServer.Stop(); - } - - private void Engine_Message(IEngine sender, EngineMessageEventArgs e) - { - logger.LogMessage(e.Message, e.SourcePath, e.TargetPath, e.OperationContext); - } - - private void Engine_Error(IEngine sender, EngineErrorEventArgs e) - { - logger.LogError(e.Message, e.SourcePath, e.TargetPath, e.Exception, e.OperationContext); } /// @@ -159,28 +134,6 @@ private void Engine_StateChanged(Engine engine, EngineWindows.StateChangeEventAr engine.LogMessage($"{e.NewState}"); } - /// - /// Gets thumbnail. - /// - /// Path in the user file system. - /// Thumbnail size in pixels. - /// - /// Throws if thumbnail is not available. - /// - /// - /// Thumbnail bitmap or null if no thumbnail should be displayed in the file manager for this item. - /// - public abstract Task GetThumbnailAsync(string userFileSystemPath, uint size); - - /// - /// Gets list of item properties. - /// - /// Path in the user file system. - /// - /// List of properties to be displayed in the file manager that correspond to the path - /// provided in the parameter. - /// - public abstract Task> GetItemPropertiesAsync(string userFileSystemPath); private bool disposedValue; @@ -192,7 +145,6 @@ protected override void Dispose(bool disposing) { //RemoteStorageMonitor.Dispose(); SyncService.Dispose(); - grpcServer.Dispose(); } // TODO: free unmanaged resources (unmanaged objects) and override finalizer diff --git a/Windows/Common/WinRT.ShellExtension.Rpc/CommonShellExtensionRpc.csproj b/Windows/Common/WinRT.ShellExtension.Rpc/CommonShellExtensionRpc.csproj deleted file mode 100644 index 8542e3a..0000000 --- a/Windows/Common/WinRT.ShellExtension.Rpc/CommonShellExtensionRpc.csproj +++ /dev/null @@ -1,41 +0,0 @@ - - - - net5.0-windows10.0.18362.0 - - x64 - - - - - true - 10.0.18362.0 - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - diff --git a/Windows/Common/WinRT.ShellExtension.Rpc/CustomStateProviderProxy.cs b/Windows/Common/WinRT.ShellExtension.Rpc/CustomStateProviderProxy.cs deleted file mode 100644 index a14f1bc..0000000 --- a/Windows/Common/WinRT.ShellExtension.Rpc/CustomStateProviderProxy.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.IO; -using System.Threading.Tasks; -using Windows.Storage.Provider; - -using ITHit.FileSystem.Samples.Common.Windows.Rpc; -using ITHit.FileSystem.Samples.Common.Windows.Rpc.Generated; - -namespace CommonShellExtensionRpc -{ - public sealed class CustomStateProviderProxy - { - public ItemProperty[] GetItemProperties(string itemPath, bool virtualDrivePlatform) - { - string channelName = virtualDrivePlatform ? "VirtualDrive.RPC" : "WebDAVDrive.RPC"; - - GrpcClient grpcClient = new GrpcClient(channelName); - - try - { - ItemPropertyRequest request = new() - { - Path = itemPath - }; - - var itemPropertyResult = grpcClient.RpcClient.GetItemPropertiesAsync(request).GetAwaiter().GetResult(); - - return itemPropertyResult - .Properties - .Select(i => new ItemProperty(i.Id, ValueValidator(i.Value), i.IconResource)) - .ToArray(); - - } - catch (Exception ex) - { - LogErrorRequest request = new() - { - Message = ex.Message, - SourcePath = itemPath - }; - - grpcClient.RpcClient.LogError(request); - - return new ItemProperty[] { }; - } - } - - private string ValueValidator(string val) - { - return string.IsNullOrWhiteSpace(val) ? "n/a" : val; - } - } -} diff --git a/Windows/Common/WinRT.ShellExtension.Rpc/Google.Protobuf.dll b/Windows/Common/WinRT.ShellExtension.Rpc/Google.Protobuf.dll deleted file mode 100644 index 4f9473fbfb124a15f7ff7c7aa7b4db207fece39c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 390128 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 OS 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 LB5f~&RNub<;x*NI diff --git a/Windows/Common/WinRT.ShellExtension.Rpc/Grpc.Core.Api.dll b/Windows/Common/WinRT.ShellExtension.Rpc/Grpc.Core.Api.dll deleted file mode 100644 index 0034a3045482e266ca77e4f277453669bc30ad25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53744 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 diff --git a/Windows/Common/WinRT.ShellExtension.Rpc/GrpcDotNetNamedPipes.dll b/Windows/Common/WinRT.ShellExtension.Rpc/GrpcDotNetNamedPipes.dll deleted file mode 100644 index 97c8fd1d0db61b34f12777c1110dbe8fffca73e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56320 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 diff --git a/Windows/Common/WinRT.ShellExtension.Rpc/ItemProperty.cs b/Windows/Common/WinRT.ShellExtension.Rpc/ItemProperty.cs deleted file mode 100644 index 0248224..0000000 --- a/Windows/Common/WinRT.ShellExtension.Rpc/ItemProperty.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace CommonShellExtensionRpc -{ - public sealed class ItemProperty - { - public ItemProperty(int id, string value, string iconResource) - { - Id = id; - Value = value; - IconResource = iconResource; - } - - public string IconResource { get; set; } - - public int Id { get; set; } - - public string Value { get; set; } - } -} diff --git a/Windows/Common/WinRT.ShellExtension.Rpc/StorageProviderGetContentInfoForPathResult.cs b/Windows/Common/WinRT.ShellExtension.Rpc/StorageProviderGetContentInfoForPathResult.cs deleted file mode 100644 index 2136ae2..0000000 --- a/Windows/Common/WinRT.ShellExtension.Rpc/StorageProviderGetContentInfoForPathResult.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace CommonShellExtensionRpc -{ - public sealed class StorageProviderGetContentInfoForPathResult - { - public StorageProviderGetContentInfoForPathResult(string contentId, string contentUri, int status) - { - ContentId = contentId; - ContentUri = contentUri; - Status = status; - } - - public string ContentId { get; set; } - - public string ContentUri { get; set; } - - public int Status { get; set; } - } -} diff --git a/Windows/Common/WinRT.ShellExtension.Rpc/StorageProviderGetPathForContentUriResult.cs b/Windows/Common/WinRT.ShellExtension.Rpc/StorageProviderGetPathForContentUriResult.cs deleted file mode 100644 index c98172c..0000000 --- a/Windows/Common/WinRT.ShellExtension.Rpc/StorageProviderGetPathForContentUriResult.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace CommonShellExtensionRpc -{ - public sealed class StorageProviderGetPathForContentUriResult - { - public StorageProviderGetPathForContentUriResult(string path, int status) - { - Path = path; - Status = status; - } - - public string Path { get; set; } - - public int Status { get; set; } - } -} diff --git a/Windows/Common/WinRT.ShellExtension.Rpc/UriSourceProxy.cs b/Windows/Common/WinRT.ShellExtension.Rpc/UriSourceProxy.cs deleted file mode 100644 index 804bb8e..0000000 --- a/Windows/Common/WinRT.ShellExtension.Rpc/UriSourceProxy.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using ITHit.FileSystem.Samples.Common.Windows.Rpc; -using ITHit.FileSystem.Samples.Common.Windows.Rpc.Generated; - -namespace CommonShellExtensionRpc -{ - public sealed class UriSourceProxy - { - public StorageProviderGetPathForContentUriResult GetPathForContentUri(string contentUri) - { - string channelName = "VirtualDrive.RPC"; - - GrpcClient grpcClient = new GrpcClient(channelName); - - try - { - UriSourceRequest request = new() - { - PathOrUri = contentUri - }; - - GetPathForContentUriResult result = grpcClient.RpcClient.GetPathForContentUriAsync(request).GetAwaiter().GetResult(); - - return new StorageProviderGetPathForContentUriResult(result.Path, result.Status); - } - catch (Exception ex) - { - LogErrorRequest request = new() - { - Message = ex.Message, - SourcePath = contentUri - }; - - grpcClient.RpcClient.LogError(request); - } - - return new StorageProviderGetPathForContentUriResult("", 0); - } - - public StorageProviderGetContentInfoForPathResult GetContentInfoForPath(string path) - { - string channelName = "VirtualDrive.RPC"; - - GrpcClient grpcClient = new GrpcClient(channelName); - - try - { - UriSourceRequest request = new() - { - PathOrUri = path - }; - - GetContentInfoForPathResult result = grpcClient.RpcClient.GetContentInfoForPathAsync(request).GetAwaiter().GetResult(); - - return new StorageProviderGetContentInfoForPathResult(result.ContentId, result.ContentUri, result.Status); - } - catch (Exception ex) - { - LogErrorRequest request = new() - { - Message = ex.Message, - SourcePath = path - }; - - grpcClient.RpcClient.LogError(request); - } - - return new StorageProviderGetContentInfoForPathResult("", "", 0); - } - } -} diff --git a/Windows/Common/WinRT.ShellExtension/ClassFactory.h b/Windows/Common/WinRT.ShellExtension/ClassFactory.h deleted file mode 100644 index 82c4141..0000000 --- a/Windows/Common/WinRT.ShellExtension/ClassFactory.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -template -class ClassFactory : public winrt::implements, IClassFactory> -{ -public: - - IFACEMETHODIMP CreateInstance(_In_opt_ IUnknown* unkOuter, REFIID riid, _COM_Outptr_ void** object) - { - try - { - auto provider = winrt::make(); - winrt::com_ptr unkn{ provider.as() }; - winrt::check_hresult(unkn->QueryInterface(riid, object)); - return S_OK; - } - catch (...) - { - return winrt::to_hresult(); - } - } - IFACEMETHODIMP LockServer(BOOL lock) { return S_OK; } -}; diff --git a/Windows/Common/WinRT.ShellExtension/CustomStateProvider.idl b/Windows/Common/WinRT.ShellExtension/CustomStateProvider.idl deleted file mode 100644 index d6ee934..0000000 --- a/Windows/Common/WinRT.ShellExtension/CustomStateProvider.idl +++ /dev/null @@ -1,7 +0,0 @@ -namespace CommonWindowsRtShellExtenstion -{ - runtimeclass CustomStateProvider : [default] Windows.Storage.Provider.IStorageProviderItemPropertySource - { - CustomStateProvider(); - } -} diff --git a/Windows/Common/WinRT.ShellExtension/ShellExtensionModule.h b/Windows/Common/WinRT.ShellExtension/ShellExtensionModule.h deleted file mode 100644 index e9419f4..0000000 --- a/Windows/Common/WinRT.ShellExtension/ShellExtensionModule.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -class ShellExtensionModule -{ -public: - ShellExtensionModule(); - virtual ~ShellExtensionModule(); - -private: - void Start(); - void Stop(); -}; - diff --git a/Windows/UserFileSystemSamples.sln b/Windows/UserFileSystemSamples.sln index c500675..2cedfde 100644 --- a/Windows/UserFileSystemSamples.sln +++ b/Windows/UserFileSystemSamples.sln @@ -23,12 +23,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{F24CBD EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common.Windows.Core", "Common\Core\Common.Windows.Core.csproj", "{AA64B9BF-C18A-4818-A260-0A32ACFCC809}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common.Windows.Rpc", "Common\Rpc\Common.Windows.Rpc.csproj", "{D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common.Windows.Rpc.Proto", "Common\Rpc.Proto\Common.Windows.Rpc.Proto.csproj", "{CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common.Windows.ShellExtension", "Common\ShellExtension\Common.Windows.ShellExtension.csproj", "{61F4EB6E-6C70-43DB-89ED-ECABEBA4FDDE}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common.Windows.VirtualDrive", "Common\VirtualDrive\Common.Windows.VirtualDrive.csproj", "{E589600E-97A4-4B1D-8921-0CCC63B03E96}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common", "..\Common\Common.csproj", "{C1585095-4C3B-4CD1-B8B2-ECDD378D41BE}" @@ -37,384 +31,32 @@ Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "WebDAVDrive.Package", "WebD EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebDAVDrive.ShellExtension", "WebDAVDrive\WebDAVDrive.ShellExtension\WebDAVDrive.ShellExtension.csproj", "{2EC2F0CD-4E7D-47ED-AAD0-E6DCCB5138B1}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonShellExtensionRpc", "Common\WinRT.ShellExtension.Rpc\CommonShellExtensionRpc.csproj", "{8EA7BABA-FC44-4074-86CB-88B8F42CA055}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VirtualDrive.WinRT.ShellExtension", "VirtualDrive\VirtualDrive.WinRT.ShellExtension\VirtualDrive.WinRT.ShellExtension.vcxproj", "{98E623B9-BCAB-48D2-80A2-1D7AADE897D6}" - ProjectSection(ProjectDependencies) = postProject - {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27} = {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27} - {8EA7BABA-FC44-4074-86CB-88B8F42CA055} = {8EA7BABA-FC44-4074-86CB-88B8F42CA055} - {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F} = {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WebDAVDrive.WinRT.ShellExtension", "WebDAVDrive\WebDAVDrive.WinRT.ShellExtension\WebDAVDrive.WinRT.ShellExtension.vcxproj", "{5730488F-9F58-4951-9502-49A5A9A42B07}" - ProjectSection(ProjectDependencies) = postProject - {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27} = {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27} - {8EA7BABA-FC44-4074-86CB-88B8F42CA055} = {8EA7BABA-FC44-4074-86CB-88B8F42CA055} - {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F} = {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F} - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VirtualDrive.Common", "VirtualDrive\VirtualDrive.Common\VirtualDrive.Common.csproj", "{C6806A0E-50E7-401D-B474-CA2C7CEAD78A}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|ARM = Debug|ARM - Debug|ARM64 = Debug|ARM64 Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|ARM = Release|ARM - Release|ARM64 = Release|ARM64 Release|x64 = Release|x64 - Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {C624F9B5-3EA1-416C-8592-37E6064C8247}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C624F9B5-3EA1-416C-8592-37E6064C8247}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C624F9B5-3EA1-416C-8592-37E6064C8247}.Debug|ARM.ActiveCfg = Debug|Any CPU - {C624F9B5-3EA1-416C-8592-37E6064C8247}.Debug|ARM.Build.0 = Debug|Any CPU - {C624F9B5-3EA1-416C-8592-37E6064C8247}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {C624F9B5-3EA1-416C-8592-37E6064C8247}.Debug|ARM64.Build.0 = Debug|Any CPU - {C624F9B5-3EA1-416C-8592-37E6064C8247}.Debug|x64.ActiveCfg = Debug|Any CPU - {C624F9B5-3EA1-416C-8592-37E6064C8247}.Debug|x64.Build.0 = Debug|Any CPU - {C624F9B5-3EA1-416C-8592-37E6064C8247}.Debug|x86.ActiveCfg = Debug|Any CPU - {C624F9B5-3EA1-416C-8592-37E6064C8247}.Debug|x86.Build.0 = Debug|Any CPU - {C624F9B5-3EA1-416C-8592-37E6064C8247}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C624F9B5-3EA1-416C-8592-37E6064C8247}.Release|Any CPU.Build.0 = Release|Any CPU - {C624F9B5-3EA1-416C-8592-37E6064C8247}.Release|ARM.ActiveCfg = Release|Any CPU - {C624F9B5-3EA1-416C-8592-37E6064C8247}.Release|ARM.Build.0 = Release|Any CPU - {C624F9B5-3EA1-416C-8592-37E6064C8247}.Release|ARM64.ActiveCfg = Release|Any CPU - {C624F9B5-3EA1-416C-8592-37E6064C8247}.Release|ARM64.Build.0 = Release|Any CPU - {C624F9B5-3EA1-416C-8592-37E6064C8247}.Release|x64.ActiveCfg = Release|Any CPU - {C624F9B5-3EA1-416C-8592-37E6064C8247}.Release|x64.Build.0 = Release|Any CPU - {C624F9B5-3EA1-416C-8592-37E6064C8247}.Release|x86.ActiveCfg = Release|Any CPU - {C624F9B5-3EA1-416C-8592-37E6064C8247}.Release|x86.Build.0 = Release|Any CPU - {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Debug|Any CPU.Build.0 = Debug|Any CPU - {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Debug|ARM.ActiveCfg = Debug|Any CPU - {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Debug|ARM.Build.0 = Debug|Any CPU - {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Debug|ARM64.Build.0 = Debug|Any CPU - {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Debug|x64.ActiveCfg = Debug|Any CPU - {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Debug|x64.Build.0 = Debug|Any CPU - {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Debug|x86.ActiveCfg = Debug|Any CPU - {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Debug|x86.Build.0 = Debug|Any CPU - {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Release|Any CPU.ActiveCfg = Release|Any CPU - {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Release|Any CPU.Build.0 = Release|Any CPU - {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Release|ARM.ActiveCfg = Release|Any CPU - {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Release|ARM.Build.0 = Release|Any CPU - {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Release|ARM64.ActiveCfg = Release|Any CPU - {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Release|ARM64.Build.0 = Release|Any CPU - {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Release|x64.ActiveCfg = Release|Any CPU - {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Release|x64.Build.0 = Release|Any CPU - {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Release|x86.ActiveCfg = Release|Any CPU - {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Release|x86.Build.0 = Release|Any CPU - {648FB01F-0C4A-409E-A48A-E6722F626AB8}.Debug|Any CPU.ActiveCfg = Debug|x64 - {648FB01F-0C4A-409E-A48A-E6722F626AB8}.Debug|Any CPU.Build.0 = Debug|x64 - {648FB01F-0C4A-409E-A48A-E6722F626AB8}.Debug|ARM.ActiveCfg = Debug|Any CPU - {648FB01F-0C4A-409E-A48A-E6722F626AB8}.Debug|ARM.Build.0 = Debug|Any CPU - {648FB01F-0C4A-409E-A48A-E6722F626AB8}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {648FB01F-0C4A-409E-A48A-E6722F626AB8}.Debug|ARM64.Build.0 = Debug|Any CPU {648FB01F-0C4A-409E-A48A-E6722F626AB8}.Debug|x64.ActiveCfg = Debug|x64 {648FB01F-0C4A-409E-A48A-E6722F626AB8}.Debug|x64.Build.0 = Debug|x64 - {648FB01F-0C4A-409E-A48A-E6722F626AB8}.Debug|x86.ActiveCfg = Debug|Any CPU - {648FB01F-0C4A-409E-A48A-E6722F626AB8}.Debug|x86.Build.0 = Debug|Any CPU - {648FB01F-0C4A-409E-A48A-E6722F626AB8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {648FB01F-0C4A-409E-A48A-E6722F626AB8}.Release|Any CPU.Build.0 = Release|Any CPU - {648FB01F-0C4A-409E-A48A-E6722F626AB8}.Release|ARM.ActiveCfg = Release|Any CPU - {648FB01F-0C4A-409E-A48A-E6722F626AB8}.Release|ARM.Build.0 = Release|Any CPU - {648FB01F-0C4A-409E-A48A-E6722F626AB8}.Release|ARM64.ActiveCfg = Release|Any CPU - {648FB01F-0C4A-409E-A48A-E6722F626AB8}.Release|ARM64.Build.0 = Release|Any CPU - {648FB01F-0C4A-409E-A48A-E6722F626AB8}.Release|x64.ActiveCfg = Release|Any CPU - {648FB01F-0C4A-409E-A48A-E6722F626AB8}.Release|x64.Build.0 = Release|Any CPU - {648FB01F-0C4A-409E-A48A-E6722F626AB8}.Release|x86.ActiveCfg = Release|Any CPU - {648FB01F-0C4A-409E-A48A-E6722F626AB8}.Release|x86.Build.0 = Release|Any CPU - {1E765516-497B-4546-8C38-DB452915ACBF}.Debug|Any CPU.ActiveCfg = Debug|x64 - {1E765516-497B-4546-8C38-DB452915ACBF}.Debug|Any CPU.Build.0 = Debug|x64 - {1E765516-497B-4546-8C38-DB452915ACBF}.Debug|ARM.ActiveCfg = Debug|Any CPU - {1E765516-497B-4546-8C38-DB452915ACBF}.Debug|ARM.Build.0 = Debug|Any CPU - {1E765516-497B-4546-8C38-DB452915ACBF}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {1E765516-497B-4546-8C38-DB452915ACBF}.Debug|ARM64.Build.0 = Debug|Any CPU {1E765516-497B-4546-8C38-DB452915ACBF}.Debug|x64.ActiveCfg = Debug|x64 {1E765516-497B-4546-8C38-DB452915ACBF}.Debug|x64.Build.0 = Debug|x64 - {1E765516-497B-4546-8C38-DB452915ACBF}.Debug|x86.ActiveCfg = Debug|Any CPU - {1E765516-497B-4546-8C38-DB452915ACBF}.Debug|x86.Build.0 = Debug|Any CPU - {1E765516-497B-4546-8C38-DB452915ACBF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1E765516-497B-4546-8C38-DB452915ACBF}.Release|Any CPU.Build.0 = Release|Any CPU - {1E765516-497B-4546-8C38-DB452915ACBF}.Release|ARM.ActiveCfg = Release|Any CPU - {1E765516-497B-4546-8C38-DB452915ACBF}.Release|ARM.Build.0 = Release|Any CPU - {1E765516-497B-4546-8C38-DB452915ACBF}.Release|ARM64.ActiveCfg = Release|Any CPU - {1E765516-497B-4546-8C38-DB452915ACBF}.Release|ARM64.Build.0 = Release|Any CPU - {1E765516-497B-4546-8C38-DB452915ACBF}.Release|x64.ActiveCfg = Release|Any CPU - {1E765516-497B-4546-8C38-DB452915ACBF}.Release|x64.Build.0 = Release|Any CPU - {1E765516-497B-4546-8C38-DB452915ACBF}.Release|x86.ActiveCfg = Release|Any CPU - {1E765516-497B-4546-8C38-DB452915ACBF}.Release|x86.Build.0 = Release|Any CPU - {06E5D212-CAD4-4880-832D-69D8D69B8E3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {06E5D212-CAD4-4880-832D-69D8D69B8E3D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {06E5D212-CAD4-4880-832D-69D8D69B8E3D}.Debug|ARM.ActiveCfg = Debug|Any CPU - {06E5D212-CAD4-4880-832D-69D8D69B8E3D}.Debug|ARM.Build.0 = Debug|Any CPU - {06E5D212-CAD4-4880-832D-69D8D69B8E3D}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {06E5D212-CAD4-4880-832D-69D8D69B8E3D}.Debug|ARM64.Build.0 = Debug|Any CPU - {06E5D212-CAD4-4880-832D-69D8D69B8E3D}.Debug|x64.ActiveCfg = Debug|Any CPU - {06E5D212-CAD4-4880-832D-69D8D69B8E3D}.Debug|x64.Build.0 = Debug|Any CPU - {06E5D212-CAD4-4880-832D-69D8D69B8E3D}.Debug|x86.ActiveCfg = Debug|Any CPU - {06E5D212-CAD4-4880-832D-69D8D69B8E3D}.Debug|x86.Build.0 = Debug|Any CPU - {06E5D212-CAD4-4880-832D-69D8D69B8E3D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {06E5D212-CAD4-4880-832D-69D8D69B8E3D}.Release|Any CPU.Build.0 = Release|Any CPU - {06E5D212-CAD4-4880-832D-69D8D69B8E3D}.Release|ARM.ActiveCfg = Release|Any CPU - {06E5D212-CAD4-4880-832D-69D8D69B8E3D}.Release|ARM.Build.0 = Release|Any CPU - {06E5D212-CAD4-4880-832D-69D8D69B8E3D}.Release|ARM64.ActiveCfg = Release|Any CPU - {06E5D212-CAD4-4880-832D-69D8D69B8E3D}.Release|ARM64.Build.0 = Release|Any CPU - {06E5D212-CAD4-4880-832D-69D8D69B8E3D}.Release|x64.ActiveCfg = Release|Any CPU - {06E5D212-CAD4-4880-832D-69D8D69B8E3D}.Release|x64.Build.0 = Release|Any CPU - {06E5D212-CAD4-4880-832D-69D8D69B8E3D}.Release|x86.ActiveCfg = Release|Any CPU - {06E5D212-CAD4-4880-832D-69D8D69B8E3D}.Release|x86.Build.0 = Release|Any CPU - {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Debug|Any CPU.ActiveCfg = Debug|x64 - {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Debug|Any CPU.Build.0 = Debug|x64 - {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Debug|Any CPU.Deploy.0 = Debug|x64 - {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Debug|ARM.ActiveCfg = Debug|ARM - {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Debug|ARM.Build.0 = Debug|ARM - {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Debug|ARM.Deploy.0 = Debug|ARM - {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Debug|ARM64.Build.0 = Debug|ARM64 - {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Debug|ARM64.Deploy.0 = Debug|ARM64 {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Debug|x64.ActiveCfg = Debug|x64 {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Debug|x64.Build.0 = Debug|x64 {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Debug|x64.Deploy.0 = Debug|x64 - {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Debug|x86.ActiveCfg = Debug|x86 - {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Debug|x86.Build.0 = Debug|x86 - {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Debug|x86.Deploy.0 = Debug|x86 - {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Release|Any CPU.ActiveCfg = Release|x86 - {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Release|ARM.ActiveCfg = Release|ARM - {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Release|ARM.Build.0 = Release|ARM - {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Release|ARM.Deploy.0 = Release|ARM - {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Release|ARM64.ActiveCfg = Release|ARM64 - {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Release|ARM64.Build.0 = Release|ARM64 - {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Release|ARM64.Deploy.0 = Release|ARM64 {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Release|x64.ActiveCfg = Release|x64 {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Release|x64.Build.0 = Release|x64 {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Release|x64.Deploy.0 = Release|x64 - {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Release|x86.ActiveCfg = Release|x86 - {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Release|x86.Build.0 = Release|x86 - {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9}.Release|x86.Deploy.0 = Release|x86 - {AA64B9BF-C18A-4818-A260-0A32ACFCC809}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AA64B9BF-C18A-4818-A260-0A32ACFCC809}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AA64B9BF-C18A-4818-A260-0A32ACFCC809}.Debug|ARM.ActiveCfg = Debug|Any CPU - {AA64B9BF-C18A-4818-A260-0A32ACFCC809}.Debug|ARM.Build.0 = Debug|Any CPU - {AA64B9BF-C18A-4818-A260-0A32ACFCC809}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {AA64B9BF-C18A-4818-A260-0A32ACFCC809}.Debug|ARM64.Build.0 = Debug|Any CPU - {AA64B9BF-C18A-4818-A260-0A32ACFCC809}.Debug|x64.ActiveCfg = Debug|Any CPU - {AA64B9BF-C18A-4818-A260-0A32ACFCC809}.Debug|x64.Build.0 = Debug|Any CPU - {AA64B9BF-C18A-4818-A260-0A32ACFCC809}.Debug|x86.ActiveCfg = Debug|Any CPU - {AA64B9BF-C18A-4818-A260-0A32ACFCC809}.Debug|x86.Build.0 = Debug|Any CPU - {AA64B9BF-C18A-4818-A260-0A32ACFCC809}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AA64B9BF-C18A-4818-A260-0A32ACFCC809}.Release|Any CPU.Build.0 = Release|Any CPU - {AA64B9BF-C18A-4818-A260-0A32ACFCC809}.Release|ARM.ActiveCfg = Release|Any CPU - {AA64B9BF-C18A-4818-A260-0A32ACFCC809}.Release|ARM.Build.0 = Release|Any CPU - {AA64B9BF-C18A-4818-A260-0A32ACFCC809}.Release|ARM64.ActiveCfg = Release|Any CPU - {AA64B9BF-C18A-4818-A260-0A32ACFCC809}.Release|ARM64.Build.0 = Release|Any CPU - {AA64B9BF-C18A-4818-A260-0A32ACFCC809}.Release|x64.ActiveCfg = Release|Any CPU - {AA64B9BF-C18A-4818-A260-0A32ACFCC809}.Release|x64.Build.0 = Release|Any CPU - {AA64B9BF-C18A-4818-A260-0A32ACFCC809}.Release|x86.ActiveCfg = Release|Any CPU - {AA64B9BF-C18A-4818-A260-0A32ACFCC809}.Release|x86.Build.0 = Release|Any CPU - {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F}.Debug|ARM.ActiveCfg = Debug|Any CPU - {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F}.Debug|ARM.Build.0 = Debug|Any CPU - {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F}.Debug|ARM64.Build.0 = Debug|Any CPU - {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F}.Debug|x64.ActiveCfg = Debug|Any CPU - {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F}.Debug|x64.Build.0 = Debug|Any CPU - {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F}.Debug|x86.ActiveCfg = Debug|Any CPU - {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F}.Debug|x86.Build.0 = Debug|Any CPU - {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F}.Release|Any CPU.Build.0 = Release|Any CPU - {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F}.Release|ARM.ActiveCfg = Release|Any CPU - {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F}.Release|ARM.Build.0 = Release|Any CPU - {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F}.Release|ARM64.ActiveCfg = Release|Any CPU - {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F}.Release|ARM64.Build.0 = Release|Any CPU - {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F}.Release|x64.ActiveCfg = Release|Any CPU - {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F}.Release|x64.Build.0 = Release|Any CPU - {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F}.Release|x86.ActiveCfg = Release|Any CPU - {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F}.Release|x86.Build.0 = Release|Any CPU - {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27}.Debug|ARM.ActiveCfg = Debug|Any CPU - {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27}.Debug|ARM.Build.0 = Debug|Any CPU - {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27}.Debug|ARM64.Build.0 = Debug|Any CPU - {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27}.Debug|x64.ActiveCfg = Debug|Any CPU - {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27}.Debug|x64.Build.0 = Debug|Any CPU - {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27}.Debug|x86.ActiveCfg = Debug|Any CPU - {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27}.Debug|x86.Build.0 = Debug|Any CPU - {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27}.Release|Any CPU.Build.0 = Release|Any CPU - {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27}.Release|ARM.ActiveCfg = Release|Any CPU - {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27}.Release|ARM.Build.0 = Release|Any CPU - {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27}.Release|ARM64.ActiveCfg = Release|Any CPU - {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27}.Release|ARM64.Build.0 = Release|Any CPU - {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27}.Release|x64.ActiveCfg = Release|Any CPU - {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27}.Release|x64.Build.0 = Release|Any CPU - {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27}.Release|x86.ActiveCfg = Release|Any CPU - {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27}.Release|x86.Build.0 = Release|Any CPU - {61F4EB6E-6C70-43DB-89ED-ECABEBA4FDDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {61F4EB6E-6C70-43DB-89ED-ECABEBA4FDDE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {61F4EB6E-6C70-43DB-89ED-ECABEBA4FDDE}.Debug|ARM.ActiveCfg = Debug|Any CPU - {61F4EB6E-6C70-43DB-89ED-ECABEBA4FDDE}.Debug|ARM.Build.0 = Debug|Any CPU - {61F4EB6E-6C70-43DB-89ED-ECABEBA4FDDE}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {61F4EB6E-6C70-43DB-89ED-ECABEBA4FDDE}.Debug|ARM64.Build.0 = Debug|Any CPU - {61F4EB6E-6C70-43DB-89ED-ECABEBA4FDDE}.Debug|x64.ActiveCfg = Debug|Any CPU - {61F4EB6E-6C70-43DB-89ED-ECABEBA4FDDE}.Debug|x64.Build.0 = Debug|Any CPU - {61F4EB6E-6C70-43DB-89ED-ECABEBA4FDDE}.Debug|x86.ActiveCfg = Debug|Any CPU - {61F4EB6E-6C70-43DB-89ED-ECABEBA4FDDE}.Debug|x86.Build.0 = Debug|Any CPU - {61F4EB6E-6C70-43DB-89ED-ECABEBA4FDDE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {61F4EB6E-6C70-43DB-89ED-ECABEBA4FDDE}.Release|Any CPU.Build.0 = Release|Any CPU - {61F4EB6E-6C70-43DB-89ED-ECABEBA4FDDE}.Release|ARM.ActiveCfg = Release|Any CPU - {61F4EB6E-6C70-43DB-89ED-ECABEBA4FDDE}.Release|ARM.Build.0 = Release|Any CPU - {61F4EB6E-6C70-43DB-89ED-ECABEBA4FDDE}.Release|ARM64.ActiveCfg = Release|Any CPU - {61F4EB6E-6C70-43DB-89ED-ECABEBA4FDDE}.Release|ARM64.Build.0 = Release|Any CPU - {61F4EB6E-6C70-43DB-89ED-ECABEBA4FDDE}.Release|x64.ActiveCfg = Release|Any CPU - {61F4EB6E-6C70-43DB-89ED-ECABEBA4FDDE}.Release|x64.Build.0 = Release|Any CPU - {61F4EB6E-6C70-43DB-89ED-ECABEBA4FDDE}.Release|x86.ActiveCfg = Release|Any CPU - {61F4EB6E-6C70-43DB-89ED-ECABEBA4FDDE}.Release|x86.Build.0 = Release|Any CPU - {E589600E-97A4-4B1D-8921-0CCC63B03E96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E589600E-97A4-4B1D-8921-0CCC63B03E96}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E589600E-97A4-4B1D-8921-0CCC63B03E96}.Debug|ARM.ActiveCfg = Debug|Any CPU - {E589600E-97A4-4B1D-8921-0CCC63B03E96}.Debug|ARM.Build.0 = Debug|Any CPU - {E589600E-97A4-4B1D-8921-0CCC63B03E96}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {E589600E-97A4-4B1D-8921-0CCC63B03E96}.Debug|ARM64.Build.0 = Debug|Any CPU - {E589600E-97A4-4B1D-8921-0CCC63B03E96}.Debug|x64.ActiveCfg = Debug|Any CPU - {E589600E-97A4-4B1D-8921-0CCC63B03E96}.Debug|x64.Build.0 = Debug|Any CPU - {E589600E-97A4-4B1D-8921-0CCC63B03E96}.Debug|x86.ActiveCfg = Debug|Any CPU - {E589600E-97A4-4B1D-8921-0CCC63B03E96}.Debug|x86.Build.0 = Debug|Any CPU - {E589600E-97A4-4B1D-8921-0CCC63B03E96}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E589600E-97A4-4B1D-8921-0CCC63B03E96}.Release|Any CPU.Build.0 = Release|Any CPU - {E589600E-97A4-4B1D-8921-0CCC63B03E96}.Release|ARM.ActiveCfg = Release|Any CPU - {E589600E-97A4-4B1D-8921-0CCC63B03E96}.Release|ARM.Build.0 = Release|Any CPU - {E589600E-97A4-4B1D-8921-0CCC63B03E96}.Release|ARM64.ActiveCfg = Release|Any CPU - {E589600E-97A4-4B1D-8921-0CCC63B03E96}.Release|ARM64.Build.0 = Release|Any CPU - {E589600E-97A4-4B1D-8921-0CCC63B03E96}.Release|x64.ActiveCfg = Release|Any CPU - {E589600E-97A4-4B1D-8921-0CCC63B03E96}.Release|x64.Build.0 = Release|Any CPU - {E589600E-97A4-4B1D-8921-0CCC63B03E96}.Release|x86.ActiveCfg = Release|Any CPU - {E589600E-97A4-4B1D-8921-0CCC63B03E96}.Release|x86.Build.0 = Release|Any CPU - {C1585095-4C3B-4CD1-B8B2-ECDD378D41BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C1585095-4C3B-4CD1-B8B2-ECDD378D41BE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C1585095-4C3B-4CD1-B8B2-ECDD378D41BE}.Debug|ARM.ActiveCfg = Debug|Any CPU - {C1585095-4C3B-4CD1-B8B2-ECDD378D41BE}.Debug|ARM.Build.0 = Debug|Any CPU - {C1585095-4C3B-4CD1-B8B2-ECDD378D41BE}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {C1585095-4C3B-4CD1-B8B2-ECDD378D41BE}.Debug|ARM64.Build.0 = Debug|Any CPU - {C1585095-4C3B-4CD1-B8B2-ECDD378D41BE}.Debug|x64.ActiveCfg = Debug|Any CPU - {C1585095-4C3B-4CD1-B8B2-ECDD378D41BE}.Debug|x64.Build.0 = Debug|Any CPU - {C1585095-4C3B-4CD1-B8B2-ECDD378D41BE}.Debug|x86.ActiveCfg = Debug|Any CPU - {C1585095-4C3B-4CD1-B8B2-ECDD378D41BE}.Debug|x86.Build.0 = Debug|Any CPU - {C1585095-4C3B-4CD1-B8B2-ECDD378D41BE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C1585095-4C3B-4CD1-B8B2-ECDD378D41BE}.Release|Any CPU.Build.0 = Release|Any CPU - {C1585095-4C3B-4CD1-B8B2-ECDD378D41BE}.Release|ARM.ActiveCfg = Release|Any CPU - {C1585095-4C3B-4CD1-B8B2-ECDD378D41BE}.Release|ARM.Build.0 = Release|Any CPU - {C1585095-4C3B-4CD1-B8B2-ECDD378D41BE}.Release|ARM64.ActiveCfg = Release|Any CPU - {C1585095-4C3B-4CD1-B8B2-ECDD378D41BE}.Release|ARM64.Build.0 = Release|Any CPU - {C1585095-4C3B-4CD1-B8B2-ECDD378D41BE}.Release|x64.ActiveCfg = Release|Any CPU - {C1585095-4C3B-4CD1-B8B2-ECDD378D41BE}.Release|x64.Build.0 = Release|Any CPU - {C1585095-4C3B-4CD1-B8B2-ECDD378D41BE}.Release|x86.ActiveCfg = Release|Any CPU - {C1585095-4C3B-4CD1-B8B2-ECDD378D41BE}.Release|x86.Build.0 = Release|Any CPU - {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Debug|Any CPU.ActiveCfg = Debug|x86 - {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Debug|ARM.ActiveCfg = Debug|ARM - {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Debug|ARM.Build.0 = Debug|ARM - {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Debug|ARM.Deploy.0 = Debug|ARM - {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Debug|ARM64.Build.0 = Debug|ARM64 - {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Debug|ARM64.Deploy.0 = Debug|ARM64 {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Debug|x64.ActiveCfg = Debug|x64 {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Debug|x64.Build.0 = Debug|x64 {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Debug|x64.Deploy.0 = Debug|x64 - {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Debug|x86.ActiveCfg = Debug|x86 - {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Debug|x86.Build.0 = Debug|x86 - {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Debug|x86.Deploy.0 = Debug|x86 - {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Release|Any CPU.ActiveCfg = Release|x86 - {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Release|ARM.ActiveCfg = Release|ARM - {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Release|ARM.Build.0 = Release|ARM - {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Release|ARM.Deploy.0 = Release|ARM - {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Release|ARM64.ActiveCfg = Release|ARM64 - {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Release|ARM64.Build.0 = Release|ARM64 - {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Release|ARM64.Deploy.0 = Release|ARM64 {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Release|x64.ActiveCfg = Release|x64 {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Release|x64.Build.0 = Release|x64 {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Release|x64.Deploy.0 = Release|x64 - {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Release|x86.ActiveCfg = Release|x86 - {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Release|x86.Build.0 = Release|x86 - {86767A2F-1559-4DFB-925D-B8E7FCDE74CA}.Release|x86.Deploy.0 = Release|x86 - {2EC2F0CD-4E7D-47ED-AAD0-E6DCCB5138B1}.Debug|Any CPU.ActiveCfg = Debug|x64 - {2EC2F0CD-4E7D-47ED-AAD0-E6DCCB5138B1}.Debug|ARM.ActiveCfg = Debug|x64 - {2EC2F0CD-4E7D-47ED-AAD0-E6DCCB5138B1}.Debug|ARM64.ActiveCfg = Debug|x64 {2EC2F0CD-4E7D-47ED-AAD0-E6DCCB5138B1}.Debug|x64.ActiveCfg = Debug|x64 {2EC2F0CD-4E7D-47ED-AAD0-E6DCCB5138B1}.Debug|x64.Build.0 = Debug|x64 - {2EC2F0CD-4E7D-47ED-AAD0-E6DCCB5138B1}.Debug|x86.ActiveCfg = Debug|x64 - {2EC2F0CD-4E7D-47ED-AAD0-E6DCCB5138B1}.Release|Any CPU.ActiveCfg = Release|x64 - {2EC2F0CD-4E7D-47ED-AAD0-E6DCCB5138B1}.Release|ARM.ActiveCfg = Release|x64 - {2EC2F0CD-4E7D-47ED-AAD0-E6DCCB5138B1}.Release|ARM64.ActiveCfg = Release|x64 {2EC2F0CD-4E7D-47ED-AAD0-E6DCCB5138B1}.Release|x64.ActiveCfg = Release|x64 {2EC2F0CD-4E7D-47ED-AAD0-E6DCCB5138B1}.Release|x64.Build.0 = Release|x64 - {2EC2F0CD-4E7D-47ED-AAD0-E6DCCB5138B1}.Release|x86.ActiveCfg = Release|x64 - {8EA7BABA-FC44-4074-86CB-88B8F42CA055}.Debug|Any CPU.ActiveCfg = Debug|x64 - {8EA7BABA-FC44-4074-86CB-88B8F42CA055}.Debug|ARM.ActiveCfg = Debug|x64 - {8EA7BABA-FC44-4074-86CB-88B8F42CA055}.Debug|ARM64.ActiveCfg = Debug|x64 - {8EA7BABA-FC44-4074-86CB-88B8F42CA055}.Debug|x64.ActiveCfg = Debug|x64 - {8EA7BABA-FC44-4074-86CB-88B8F42CA055}.Debug|x64.Build.0 = Debug|x64 - {8EA7BABA-FC44-4074-86CB-88B8F42CA055}.Debug|x86.ActiveCfg = Debug|x64 - {8EA7BABA-FC44-4074-86CB-88B8F42CA055}.Release|Any CPU.ActiveCfg = Release|x64 - {8EA7BABA-FC44-4074-86CB-88B8F42CA055}.Release|ARM.ActiveCfg = Release|x64 - {8EA7BABA-FC44-4074-86CB-88B8F42CA055}.Release|ARM64.ActiveCfg = Release|x64 - {8EA7BABA-FC44-4074-86CB-88B8F42CA055}.Release|x64.ActiveCfg = Release|x64 - {8EA7BABA-FC44-4074-86CB-88B8F42CA055}.Release|x64.Build.0 = Release|x64 - {8EA7BABA-FC44-4074-86CB-88B8F42CA055}.Release|x86.ActiveCfg = Release|x64 - {98E623B9-BCAB-48D2-80A2-1D7AADE897D6}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {98E623B9-BCAB-48D2-80A2-1D7AADE897D6}.Debug|ARM.ActiveCfg = Debug|Win32 - {98E623B9-BCAB-48D2-80A2-1D7AADE897D6}.Debug|ARM64.ActiveCfg = Debug|Win32 - {98E623B9-BCAB-48D2-80A2-1D7AADE897D6}.Debug|x64.ActiveCfg = Debug|x64 - {98E623B9-BCAB-48D2-80A2-1D7AADE897D6}.Debug|x64.Build.0 = Debug|x64 - {98E623B9-BCAB-48D2-80A2-1D7AADE897D6}.Debug|x86.ActiveCfg = Debug|Win32 - {98E623B9-BCAB-48D2-80A2-1D7AADE897D6}.Debug|x86.Build.0 = Debug|Win32 - {98E623B9-BCAB-48D2-80A2-1D7AADE897D6}.Release|Any CPU.ActiveCfg = Release|Win32 - {98E623B9-BCAB-48D2-80A2-1D7AADE897D6}.Release|ARM.ActiveCfg = Release|Win32 - {98E623B9-BCAB-48D2-80A2-1D7AADE897D6}.Release|ARM64.ActiveCfg = Release|Win32 - {98E623B9-BCAB-48D2-80A2-1D7AADE897D6}.Release|x64.ActiveCfg = Release|x64 - {98E623B9-BCAB-48D2-80A2-1D7AADE897D6}.Release|x64.Build.0 = Release|x64 - {98E623B9-BCAB-48D2-80A2-1D7AADE897D6}.Release|x86.ActiveCfg = Release|Win32 - {98E623B9-BCAB-48D2-80A2-1D7AADE897D6}.Release|x86.Build.0 = Release|Win32 - {5730488F-9F58-4951-9502-49A5A9A42B07}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {5730488F-9F58-4951-9502-49A5A9A42B07}.Debug|ARM.ActiveCfg = Debug|Win32 - {5730488F-9F58-4951-9502-49A5A9A42B07}.Debug|ARM64.ActiveCfg = Debug|Win32 - {5730488F-9F58-4951-9502-49A5A9A42B07}.Debug|x64.ActiveCfg = Debug|x64 - {5730488F-9F58-4951-9502-49A5A9A42B07}.Debug|x64.Build.0 = Debug|x64 - {5730488F-9F58-4951-9502-49A5A9A42B07}.Debug|x86.ActiveCfg = Debug|Win32 - {5730488F-9F58-4951-9502-49A5A9A42B07}.Debug|x86.Build.0 = Debug|Win32 - {5730488F-9F58-4951-9502-49A5A9A42B07}.Release|Any CPU.ActiveCfg = Release|Win32 - {5730488F-9F58-4951-9502-49A5A9A42B07}.Release|ARM.ActiveCfg = Release|Win32 - {5730488F-9F58-4951-9502-49A5A9A42B07}.Release|ARM64.ActiveCfg = Release|Win32 - {5730488F-9F58-4951-9502-49A5A9A42B07}.Release|x64.ActiveCfg = Release|x64 - {5730488F-9F58-4951-9502-49A5A9A42B07}.Release|x64.Build.0 = Release|x64 - {5730488F-9F58-4951-9502-49A5A9A42B07}.Release|x86.ActiveCfg = Release|Win32 - {5730488F-9F58-4951-9502-49A5A9A42B07}.Release|x86.Build.0 = Release|Win32 - {C6806A0E-50E7-401D-B474-CA2C7CEAD78A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C6806A0E-50E7-401D-B474-CA2C7CEAD78A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C6806A0E-50E7-401D-B474-CA2C7CEAD78A}.Debug|ARM.ActiveCfg = Debug|Any CPU - {C6806A0E-50E7-401D-B474-CA2C7CEAD78A}.Debug|ARM.Build.0 = Debug|Any CPU - {C6806A0E-50E7-401D-B474-CA2C7CEAD78A}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {C6806A0E-50E7-401D-B474-CA2C7CEAD78A}.Debug|ARM64.Build.0 = Debug|Any CPU - {C6806A0E-50E7-401D-B474-CA2C7CEAD78A}.Debug|x64.ActiveCfg = Debug|Any CPU - {C6806A0E-50E7-401D-B474-CA2C7CEAD78A}.Debug|x64.Build.0 = Debug|Any CPU - {C6806A0E-50E7-401D-B474-CA2C7CEAD78A}.Debug|x86.ActiveCfg = Debug|Any CPU - {C6806A0E-50E7-401D-B474-CA2C7CEAD78A}.Debug|x86.Build.0 = Debug|Any CPU - {C6806A0E-50E7-401D-B474-CA2C7CEAD78A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C6806A0E-50E7-401D-B474-CA2C7CEAD78A}.Release|Any CPU.Build.0 = Release|Any CPU - {C6806A0E-50E7-401D-B474-CA2C7CEAD78A}.Release|ARM.ActiveCfg = Release|Any CPU - {C6806A0E-50E7-401D-B474-CA2C7CEAD78A}.Release|ARM.Build.0 = Release|Any CPU - {C6806A0E-50E7-401D-B474-CA2C7CEAD78A}.Release|ARM64.ActiveCfg = Release|Any CPU - {C6806A0E-50E7-401D-B474-CA2C7CEAD78A}.Release|ARM64.Build.0 = Release|Any CPU - {C6806A0E-50E7-401D-B474-CA2C7CEAD78A}.Release|x64.ActiveCfg = Release|Any CPU - {C6806A0E-50E7-401D-B474-CA2C7CEAD78A}.Release|x64.Build.0 = Release|Any CPU - {C6806A0E-50E7-401D-B474-CA2C7CEAD78A}.Release|x86.ActiveCfg = Release|Any CPU - {C6806A0E-50E7-401D-B474-CA2C7CEAD78A}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -426,17 +68,10 @@ Global {1E765516-497B-4546-8C38-DB452915ACBF} = {CAE8E8A6-2721-4C90-BCD9-F4B03C3D8F13} {9CC25823-7D6C-4AAA-95A0-EE0514CCABD9} = {CAE8E8A6-2721-4C90-BCD9-F4B03C3D8F13} {AA64B9BF-C18A-4818-A260-0A32ACFCC809} = {F24CBDD0-7A18-43F0-BCD2-A8FD1A8A7B54} - {D0DA8CE2-2AC3-42AE-BAF4-103E663EB70F} = {F24CBDD0-7A18-43F0-BCD2-A8FD1A8A7B54} - {CEFD557A-7366-4F4B-AF2B-17C6EDDD7A27} = {F24CBDD0-7A18-43F0-BCD2-A8FD1A8A7B54} - {61F4EB6E-6C70-43DB-89ED-ECABEBA4FDDE} = {F24CBDD0-7A18-43F0-BCD2-A8FD1A8A7B54} {E589600E-97A4-4B1D-8921-0CCC63B03E96} = {F24CBDD0-7A18-43F0-BCD2-A8FD1A8A7B54} {C1585095-4C3B-4CD1-B8B2-ECDD378D41BE} = {F24CBDD0-7A18-43F0-BCD2-A8FD1A8A7B54} {86767A2F-1559-4DFB-925D-B8E7FCDE74CA} = {264745B0-DF86-41E1-B400-3CAA1B403830} {2EC2F0CD-4E7D-47ED-AAD0-E6DCCB5138B1} = {264745B0-DF86-41E1-B400-3CAA1B403830} - {8EA7BABA-FC44-4074-86CB-88B8F42CA055} = {F24CBDD0-7A18-43F0-BCD2-A8FD1A8A7B54} - {98E623B9-BCAB-48D2-80A2-1D7AADE897D6} = {CAE8E8A6-2721-4C90-BCD9-F4B03C3D8F13} - {5730488F-9F58-4951-9502-49A5A9A42B07} = {264745B0-DF86-41E1-B400-3CAA1B403830} - {C6806A0E-50E7-401D-B474-CA2C7CEAD78A} = {CAE8E8A6-2721-4C90-BCD9-F4B03C3D8F13} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {740A716A-38A7-46BC-A21F-18336D0023B7} diff --git a/Windows/VirtualDrive/VirtualDrive.Common/VirtualDrive.Common.csproj b/Windows/VirtualDrive/VirtualDrive.Common/VirtualDrive.Common.csproj deleted file mode 100644 index b393e2b..0000000 --- a/Windows/VirtualDrive/VirtualDrive.Common/VirtualDrive.Common.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - net6.0 - - - - - - - - - - - diff --git a/Windows/VirtualDrive/VirtualDrive.Package/Package.appxmanifest b/Windows/VirtualDrive/VirtualDrive.Package/Package.appxmanifest index a5efaf2..f938896 100644 --- a/Windows/VirtualDrive/VirtualDrive.Package/Package.appxmanifest +++ b/Windows/VirtualDrive/VirtualDrive.Package/Package.appxmanifest @@ -11,12 +11,12 @@ VirtualDrive.Package - VirtualDrive.PackagePublisher + VirtualDrivePackagePublisher Images\StoreLogo.png @@ -42,6 +42,7 @@ + @@ -58,11 +59,9 @@ - + - - @@ -71,17 +70,18 @@ - + - + + diff --git a/Windows/VirtualDrive/VirtualDrive.Package/VirtualDrive.Package.wapproj b/Windows/VirtualDrive/VirtualDrive.Package/VirtualDrive.Package.wapproj index 1b939b9..2746793 100644 --- a/Windows/VirtualDrive/VirtualDrive.Package/VirtualDrive.Package.wapproj +++ b/Windows/VirtualDrive/VirtualDrive.Package/VirtualDrive.Package.wapproj @@ -43,8 +43,8 @@ 9cc25823-7d6c-4aaa-95a0-ee0514ccabd9 - 10.0.18362.0 - 10.0.18362.0 + 10.0.19041.0 + 10.0.19041.0 en-US false ..\VirtualDrive\VirtualDrive.csproj @@ -65,11 +65,10 @@ - + - True diff --git a/Windows/VirtualDrive/VirtualDrive.ShellExtension/ContextMenusProvider.cs b/Windows/VirtualDrive/VirtualDrive.ShellExtension/ContextMenusProvider.cs index 036208e..27adc5b 100644 --- a/Windows/VirtualDrive/VirtualDrive.ShellExtension/ContextMenusProvider.cs +++ b/Windows/VirtualDrive/VirtualDrive.ShellExtension/ContextMenusProvider.cs @@ -1,130 +1,19 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Runtime.InteropServices; -using System.Threading.Tasks; -using ITHit.FileSystem.Samples.Common.Windows.Rpc; -using ITHit.FileSystem.Samples.Common.Windows.Rpc.Generated; -using ITHit.FileSystem.Samples.Common.Windows.ShellExtension; -using ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop; +using ITHit.FileSystem.Windows.ShellExtension; + namespace VirtualDrive.ShellExtension { + /// /// Implements Windows Explorer context menu. /// [ComVisible(true)] [ProgId("VirtualDrive.ContextMenusProvider")] [Guid("9C923BF3-3A4B-487B-AB4E-B4CF87FD1C25")] - public class ContextMenusProvider : ContextMenusProviderBase + public class ContextMenusProvider : CloudFilesContextMenuVerbRpcBase { - public const string LockCommandIcon = "Locked.ico"; - public const string UnlockCommandIcon = "Unlocked.ico"; - - /// - /// Selected items status. True - if all items are locked. False - if all items are unlocked. - /// - private bool isLocked = true; - - /// - /// Gets menu title. - /// - /// List of selected items. - /// Menu title string. - public override async Task GetMenuTitleAsync(IEnumerable filesPath) - { - isLocked = await GetLockStatusAsync(filesPath); - - bool multyFiles = filesPath.Count() > 1; - - string action = isLocked ? "Unlock " : "Lock "; - string objects = multyFiles ? "files" : "file"; - - return action + objects; - } - - /// - /// Sets files lock status. - /// - /// List of selected items. - public override async Task InvokeMenuCommandAsync(IEnumerable filesPath) - { - if (!filesPath.Any()) - throw new NotImplementedException(); - - GrpcClient grpcClient = new GrpcClient(ShellExtensionConfiguration.AppSettings.RpcCommunicationChannelName); - - ItemsStatusList itemStatusList = new ItemsStatusList(); - - foreach (string path in filesPath) - { - itemStatusList.FilesStatus.Add(path, !isLocked); - } - - await grpcClient.RpcClient.SetLockStatusAsync(itemStatusList); - - await Task.CompletedTask; - } - - /// - /// Gets menu state - visible or hidden, depending on the selected items lock state. - /// - /// List of selected items. - /// Item state. - public override async Task GetMenuStateAsync(IEnumerable filesPath) - { - try - { - isLocked = await GetLockStatusAsync(filesPath); - - return EXPCMDSTATE.ECS_ENABLED; - } - catch (NotImplementedException) - { - return EXPCMDSTATE.ECS_HIDDEN; - } - } - - /// - /// Returns path to icon file or resource. - /// - /// List of selected items. - /// Path to icon file or resource. - public override async Task GetIconAsync(IEnumerable filesPath) - { - string iconName = isLocked ? UnlockCommandIcon : LockCommandIcon; - string iconPath = Path.Combine(Path.GetDirectoryName(typeof(ContextMenusProvider).Assembly.Location), iconName); - - return iconPath; - } - - /// - /// Calls main application to get files lock state and checks if all items have the same state. - /// - /// List of items to get state for. - /// True if all items has locked state. False if all items has unlocked state. - private async Task GetLockStatusAsync(IEnumerable filesPath) - { - if (!filesPath.Any()) - throw new NotImplementedException(); - - GrpcClient grpcClient = new GrpcClient(ShellExtensionConfiguration.AppSettings.RpcCommunicationChannelName); - - var request = new ItemsPathList(); - request.Files.AddRange(filesPath); - - ItemsStatusList filesStatus = await grpcClient.RpcClient.GetLockStatusAsync(request); - - if (filesStatus.FilesStatus.Count() != filesPath.Count()) - throw new NotImplementedException(); - - bool allHasSameValue = filesStatus.FilesStatus.Values.Distinct().Count() == 1; - if (!allHasSameValue) - throw new NotImplementedException(); - bool lockStatus = filesStatus.FilesStatus.Values.First(); - return lockStatus; - } } + } diff --git a/Windows/VirtualDrive/VirtualDrive.ShellExtension/CustomStateProvider.cs b/Windows/VirtualDrive/VirtualDrive.ShellExtension/CustomStateProvider.cs new file mode 100644 index 0000000..461cdcf --- /dev/null +++ b/Windows/VirtualDrive/VirtualDrive.ShellExtension/CustomStateProvider.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; +using ITHit.FileSystem.Windows.ShellExtension; + + +namespace VirtualDrive.ShellExtension +{ + + /// + /// Implements custom state provider for virtual drive. + /// Displays custom colums and custom state icons in Status column in Windows Explorer. + /// + [ComVisible(true)] + [ProgId("VirtualDrive.CustomStateProvider")] + [Guid("000562AA-2879-4CF1-89E8-0AEC9596FE19")] + public class CustomStateProvider : CustomStateHandlerRpcBase + { + + } + +} diff --git a/Windows/VirtualDrive/VirtualDrive.ShellExtension/Mapping.cs b/Windows/VirtualDrive/VirtualDrive.ShellExtension/Mapping.cs deleted file mode 100644 index 171b6d0..0000000 --- a/Windows/VirtualDrive/VirtualDrive.ShellExtension/Mapping.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.IO; -using ITHit.FileSystem.Samples.Common.Windows.ShellExtension; - -namespace VirtualDrive.ShellExtension -{ - /// - /// Maps a user file system path to the remote storage path and back. - /// - internal static class Mapping - { - public static AppSettings AppSettings => ShellExtensionConfiguration.AppSettings as AppSettings; - - /// - /// Returns a remote storage URI that corresponds to the user file system path. - /// - public static string MapPath(string userFileSystemPath) - { - string relativePath = Path.TrimEndingDirectorySeparator(userFileSystemPath).Substring( - Path.TrimEndingDirectorySeparator(AppSettings.UserFileSystemRootPath).Length); - - string path = $"{Path.TrimEndingDirectorySeparator(AppSettings.RemoteStorageRootPath)}{relativePath}"; - return path; - } - } -} diff --git a/Windows/VirtualDrive/VirtualDrive.ShellExtension/Program.cs b/Windows/VirtualDrive/VirtualDrive.ShellExtension/Program.cs index cba360d..2174fa8 100644 --- a/Windows/VirtualDrive/VirtualDrive.ShellExtension/Program.cs +++ b/Windows/VirtualDrive/VirtualDrive.ShellExtension/Program.cs @@ -1,11 +1,10 @@ using System; using System.Diagnostics; using System.Threading.Tasks; -using Microsoft.Extensions.Configuration; -using ITHit.FileSystem.Samples.Common.Windows.ShellExtension; -using ITHit.FileSystem.Samples.Common.Windows.ShellExtension.ComInfrastructure; -using ITHit.FileSystem.Samples.Common; -using VirtualDrive.Common; +using Windows.Storage.Provider; + +using ITHit.FileSystem.Windows.ShellExtension.ComInfrastructure; + namespace VirtualDrive.ShellExtension { @@ -13,18 +12,14 @@ class Program { static async Task Main(string[] args) { - // Load and initialize settings. - IConfiguration configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json", false, true).Build(); - Settings settings = configuration.ReadSettings(); - - ShellExtensionConfiguration.Initialize(settings); - try { using (var server = new LocalServer()) { - server.RegisterClass(typeof(ThumbnailProvider).GUID); - server.RegisterClass(typeof(ContextMenusProvider).GUID); + server.RegisterClass(); + server.RegisterClass(); + server.RegisterWinRTClass(); + server.RegisterWinRTClass(); await server.Run(); } diff --git a/Windows/VirtualDrive/VirtualDrive.ShellExtension/ThumbnailProvider.cs b/Windows/VirtualDrive/VirtualDrive.ShellExtension/ThumbnailProvider.cs index f8f3c8c..56feae1 100644 --- a/Windows/VirtualDrive/VirtualDrive.ShellExtension/ThumbnailProvider.cs +++ b/Windows/VirtualDrive/VirtualDrive.ShellExtension/ThumbnailProvider.cs @@ -1,10 +1,6 @@ -using System; using System.Runtime.InteropServices; -using System.Threading.Tasks; -using ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Thumbnails; -using ITHit.FileSystem.Samples.Common.Windows.Rpc; -using ITHit.FileSystem.Samples.Common.Windows.Rpc.Generated; -using ITHit.FileSystem.Samples.Common.Windows.ShellExtension; +using ITHit.FileSystem.Windows.ShellExtension.Thumbnails; + namespace VirtualDrive.ShellExtension { @@ -14,25 +10,8 @@ namespace VirtualDrive.ShellExtension [ComVisible(true)] [ProgId("VirtualDrive.ThumbnailProvider")] [Guid("05CF065E-E135-4B2B-9D4D-CFB3FBAC73A4")] - public class ThumbnailProvider : ThumbnailProviderBase + public class ThumbnailProvider : ThumbnailProviderHandlerRpcBase { - public override async Task GetThumbnailsAsync(string filePath, uint size) - { - try - { - GrpcClient grpcClient = new GrpcClient(ShellExtensionConfiguration.AppSettings.RpcCommunicationChannelName); - ThumbnailRequest thumbnailRequest = new ThumbnailRequest(); - thumbnailRequest.Path = filePath; - thumbnailRequest.Size = size; - - Thumbnail thumbnail = await grpcClient.RpcClient.GetThumbnailAsync(thumbnailRequest); - return thumbnail.Image.ToByteArray(); - } - catch (Exception ex) - { - throw new NotImplementedException(ex.Message); - } - } } } diff --git a/Windows/VirtualDrive/VirtualDrive.ShellExtension/UriSource.cs b/Windows/VirtualDrive/VirtualDrive.ShellExtension/UriSource.cs new file mode 100644 index 0000000..4e314ba --- /dev/null +++ b/Windows/VirtualDrive/VirtualDrive.ShellExtension/UriSource.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; +using ITHit.FileSystem.Windows.ShellExtension; + + +namespace VirtualDrive.ShellExtension +{ + /// + /// Implements custom content uri source provider. + /// + [ComVisible(true)] + [ProgId("VirtualDrive.UriSource")] + [Guid("6D45BC7A-D0B7-4913-8984-FD7261550C08")] + public class UriSource : UriSourceRpcBase + { + + } +} diff --git a/Windows/VirtualDrive/VirtualDrive.ShellExtension/VirtualDrive.ShellExtension.csproj b/Windows/VirtualDrive/VirtualDrive.ShellExtension/VirtualDrive.ShellExtension.csproj index c6b8c78..2e2ed72 100644 --- a/Windows/VirtualDrive/VirtualDrive.ShellExtension/VirtualDrive.ShellExtension.csproj +++ b/Windows/VirtualDrive/VirtualDrive.ShellExtension/VirtualDrive.ShellExtension.csproj @@ -1,8 +1,6 @@ - - + - net6.0-windows10.0.18362.0 - 10.0.18362.0 + net5.0-windows10.0.19041.0 True x64 @@ -13,40 +11,19 @@ IT Hit LTD. IT Hit LTD. - - - - - Always - - - Always - - - - - - Always - - - - - - + - Always - - + \ No newline at end of file diff --git a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/Common.Windows.WinRT.ShellExtension.vcxproj.filters b/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/Common.Windows.WinRT.ShellExtension.vcxproj.filters deleted file mode 100644 index 5f72f70..0000000 --- a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/Common.Windows.WinRT.ShellExtension.vcxproj.filters +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - - - - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;ipp;xsd - - - - - Header Files - - - - - Source Files - - - \ No newline at end of file diff --git a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/Common.Windows.WinRT.ShellExtension_TemporaryKey.pfx b/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/Common.Windows.WinRT.ShellExtension_TemporaryKey.pfx deleted file mode 100644 index eb14bc50dd76ba7824350e61b1852757ccefacd7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2520 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 diff --git a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.aps b/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.aps deleted file mode 100644 index 4a39751fafc2becb343d5a5145971ccd77b1550d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1476 zcmb7EO>fgc5Pg9h2#5;@&PnN^m0BxlD};Irj;mTEPGvhukt|u2W0AD}h@3naY88;3YXN&1TU2OBRN zeeP(p4&S6j)IP7nG8>h$hI2UN(8B}^#7J<*wT>Bkb3$pcqo)LybEu>xLSI8%(*5M5hR+C+(Fw%%V0Ur(bjPBDir9DL^GG38OAzJie zIx1yVa=()NK5!N+s<@zk%A|?|Mty_VI9%n;Syl0lxO6`a_NCZ!#2p3P*^qf1*1S%J zLK=*5$|xst?GO>tzL5J!Ii}3O+`rfU`&R93Hkxjz?OuxwCawG2)4KNLkJ{6fR^Wl7 zt#dB!Sbompztf`Z`tOLl|C91RcVFIj=Ks4-!G)pa5V!WzPM__2{Xn13^eOlkcRGy< ah<^XVbOom9_Z%b6<%s{+0#lqzj`;=PMHY?# diff --git a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.exe.manifest b/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.exe.manifest deleted file mode 100644 index 6c00cd1..0000000 --- a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.exe.manifest +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.rc b/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.rc deleted file mode 100644 index 6d285329154a138f9216537b866148988d06f675..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2664 zcmdUxTTAOe5Xb+|g5M#wFN$KlJo%`t7CE&_FQQN=VvQDRJTxu7_}M+@HyamYlPF#u zBFlC*)0x?s|6G!P&o$K)=tN^(YpMsu>`*g!=kQ|9b)^YUb*-}k-RedWdkTLB9l@JI zO>fTWnsdODSsUvwGMie~61UVGt-_7?cY^fD$yPG@o4QlQNtyg zL&rMRP#qn@ZE$X@rAs(neou4&r^VUdZ$6Z7dG9<8)C8ABj6+a*&^__bK*wX?I-RZ>XYU#ks|yti8}>b(odZbbi0 z$W%-X1X?BM5l_AkyPT4)jJ1|i1#zmU#tyHQH@!8&;=Ycks-&m7^iQAes&n&@>T0pf z?h`90)a+rSLk$nnYNNmAZf(JB!|g+xu1@!~ftBqApNcbSb$*UVf6#9Hx}MJOR-ap= zwzol~Sn`0#o37tYHh5Y2^K+oBmlPO~3yZ_E?q+hqO25VR5}je2>_7V)mVz&HI09JUAu* diff --git a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/CustomStateProvider.cpp b/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/CustomStateProvider.cpp deleted file mode 100644 index 4fc4ee0..0000000 --- a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/CustomStateProvider.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "pch.h" -#include "CustomStateProvider.h" -#include "..\..\Common\WinRT.ShellExtension\ShellExtensionModule.h" - -namespace winrt -{ - using namespace winrt::Windows::Storage::Provider; -} - -namespace winrt::CommonWindowsRtShellExtenstion::implementation -{ - using namespace Windows::Foundation::Collections; - using namespace Windows::Storage::Provider; - - IIterable CustomStateProvider::GetItemProperties(hstring const& itemPath) - { - auto propertyVector{ winrt::single_threaded_vector() }; - - try - { - CommonShellExtensionRpc::CustomStateProviderProxy stateProviderProxy; - auto itemProperties = stateProviderProxy.GetItemProperties(itemPath, true); - - for (const auto& itemProp : itemProperties) - { - winrt::StorageProviderItemProperty storageItemProperty; - storageItemProperty.Id(itemProp.Id()); - storageItemProperty.Value(itemProp.Value()); - storageItemProperty.IconResource(itemProp.IconResource()); - - propertyVector.Append(storageItemProperty); - } - } - catch (...) - { - } - - return propertyVector; - } -} diff --git a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/CustomStateProvider.h b/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/CustomStateProvider.h deleted file mode 100644 index ade6803..0000000 --- a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/CustomStateProvider.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include "CommonWindowsRtShellExtenstion.CustomStateProvider.g.h" -#include - -// 000562AA-2879-4CF1-89E8-0AEC9596FE19 -constexpr CLSID CLSID_CustomStateProviderVirtualDrive = { 0x562aa, 0x2879, 0x4cf1, { 0x89, 0xe8, 0xa, 0xec, 0x95, 0x96, 0xfe, 0x19 } }; - -namespace winrt::CommonWindowsRtShellExtenstion::implementation -{ - struct CustomStateProvider : CustomStateProviderT - { - CustomStateProvider() = default; - - Windows::Foundation::Collections::IIterable GetItemProperties(_In_ hstring const& itemPath); - }; -} - -namespace winrt::CommonWindowsRtShellExtenstion::factory_implementation -{ - struct CustomStateProvider : CustomStateProviderT - { - }; -} diff --git a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/PropertySheet.props b/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/PropertySheet.props deleted file mode 100644 index e34141b..0000000 --- a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/PropertySheet.props +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/ShellExtensionModule.cpp b/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/ShellExtensionModule.cpp deleted file mode 100644 index d0b9eb2..0000000 --- a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/ShellExtensionModule.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "pch.h" -#include "..\..\Common\WinRT.ShellExtension\ShellExtensionModule.h" -#include "..\..\Common\WinRT.ShellExtension\ClassFactory.h" - -using namespace winrt::CommonWindowsRtShellExtenstion::implementation; - -ShellExtensionModule::ShellExtensionModule() -{ - Start(); -} - -ShellExtensionModule::~ShellExtensionModule() -{ - Stop(); -} - -void ShellExtensionModule::Start() -{ - DWORD cookie = 0; - - auto customStateProviderVirtualDrive = winrt::make>(); - winrt::check_hresult(CoRegisterClassObject(CLSID_CustomStateProviderVirtualDrive, customStateProviderVirtualDrive.get(), CLSCTX_LOCAL_SERVER, REGCLS_MULTI_SEPARATE, &cookie)); - - cookie = 0; - auto uriSourceVirtualDrive = winrt::make>(); - winrt::check_hresult(CoRegisterClassObject(CLSID_UriSource, uriSourceVirtualDrive.get(), CLSCTX_LOCAL_SERVER, REGCLS_MULTI_SEPARATE, &cookie)); - -} - -void ShellExtensionModule::Stop() -{ - -} diff --git a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/StorageProviderUriSourceStatus.cpp b/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/StorageProviderUriSourceStatus.cpp deleted file mode 100644 index a149c37..0000000 --- a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/StorageProviderUriSourceStatus.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#include "pch.h" -#include "StorageProviderUriSourceStatus.h" diff --git a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/StorageProviderUriSourceStatus.h b/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/StorageProviderUriSourceStatus.h deleted file mode 100644 index ae2cdf1..0000000 --- a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/StorageProviderUriSourceStatus.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -class StorageProviderUriSourceStatus -{ -}; diff --git a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/UriSource.cpp b/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/UriSource.cpp deleted file mode 100644 index 6c8aa64..0000000 --- a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/UriSource.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "pch.h" -#include "UriSource.h" - -using namespace winrt::Windows::Storage::Provider; - -namespace winrt::CommonWindowsRtShellExtenstion::implementation -{ - void UriSource::GetPathForContentUri(hstring const& contentUri, Windows::Storage::Provider::StorageProviderGetPathForContentUriResult const& result) - { - CommonShellExtensionRpc::UriSourceProxy uriSourceProxy; - - auto pathResult = uriSourceProxy.GetPathForContentUri(contentUri.c_str()); - - result.Path(pathResult.Path()); - result.Status((StorageProviderUriSourceStatus)pathResult.Status()); - } - - void UriSource::GetContentInfoForPath(hstring const& path, Windows::Storage::Provider::StorageProviderGetContentInfoForPathResult const& result) - { - CommonShellExtensionRpc::UriSourceProxy uriSourceProxy; - - auto pathResult = uriSourceProxy.GetContentInfoForPath(path); - - result.ContentId(pathResult.ContentId()); - result.ContentUri(pathResult.ContentUri()); - result.Status((StorageProviderUriSourceStatus)pathResult.Status()); - } -} diff --git a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/UriSource.h b/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/UriSource.h deleted file mode 100644 index 8b76f26..0000000 --- a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/UriSource.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include "CommonWindowsRtShellExtenstion.UriSource.g.h" -#include - -// {6D45BC7A-D0B7-4913-8984-FD7261550C08} -static const GUID CLSID_UriSource = -{ 0x6d45bc7a, 0xd0b7, 0x4913, { 0x89, 0x84, 0xfd, 0x72, 0x61, 0x55, 0xc, 0x8 } }; - - -namespace winrt::CommonWindowsRtShellExtenstion::implementation -{ - struct UriSource : UriSourceT - { - UriSource() = default; - - void GetPathForContentUri(_In_ hstring const& contentUri, _Out_ Windows::Storage::Provider::StorageProviderGetPathForContentUriResult const& result); - void GetContentInfoForPath(_In_ hstring const& path, _Out_ Windows::Storage::Provider::StorageProviderGetContentInfoForPathResult const& result); - }; -} - -namespace winrt::CommonWindowsRtShellExtenstion::factory_implementation -{ - struct UriSource : UriSourceT - { - }; -} diff --git a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/UriSource.idl b/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/UriSource.idl deleted file mode 100644 index 8ed7a9c..0000000 --- a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/UriSource.idl +++ /dev/null @@ -1,7 +0,0 @@ -namespace CommonWindowsRtShellExtenstion -{ - runtimeclass UriSource : [default] Windows.Storage.Provider.IStorageProviderUriSource - { - UriSource(); - } -} \ No newline at end of file diff --git a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/VirtualDrive.WinRT.ShellExtension.vcxproj b/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/VirtualDrive.WinRT.ShellExtension.vcxproj deleted file mode 100644 index 74f059e..0000000 --- a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/VirtualDrive.WinRT.ShellExtension.vcxproj +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - true - true - true - true - 15.0 - {98e623b9-bcab-48d2-80a2-1d7aade897d6} - Win32Proj - Common_Windows_WinRT_ShellExtension - 10.0.18362.0 - 10.0.18362.0 - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - Application - v143 - v142 - v141 - v140 - Unicode - - - true - true - - - false - true - false - - - - - - - - - - - - - - - - Use - pch.h - $(IntDir)pch.pch - _CONSOLE;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) - Level4 - %(AdditionalOptions) /permissive- /bigobj - - - Windows - false - - - - - Disabled - _DEBUG;%(PreprocessorDefinitions) - - - - - - - winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;cldapi.lib;onecore.lib;onecoreuap.lib;%(AdditionalDependencies) - - - - - WIN32;%(PreprocessorDefinitions) - - - - - MaxSpeed - true - true - NDEBUG;%(PreprocessorDefinitions) - - - true - true - winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;cldapi.lib;onecore.lib;onecoreuap.lib;%(AdditionalDependencies) - - - - - - - - - - - - - - - Create - - - - - - - - - false - - - - - {96fb01be-3def-418d-8ab0-69cc0d1813d3} - - - {1f61a031-cdfe-4b81-bac3-7760fa777a2a} - - - {e64b361d-8934-401e-b4fd-64786e4e1dc7} - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/WinMain.cpp b/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/WinMain.cpp deleted file mode 100644 index 6b7b4d7..0000000 --- a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/WinMain.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "pch.h" -#include "..\..\Common\WinRT.ShellExtension\ShellExtensionModule.h" - -using namespace winrt; -using namespace Windows::Foundation; - -void __stdcall TimerProc(HWND hWnd, UINT message, UINT idTimer, DWORD dwTime) -{ - PostQuitMessage(0); -} - -LRESULT __stdcall WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - switch (uMsg) - { - case WM_DESTROY: - KillTimer(hWnd, 0); - PostQuitMessage(0); - break; - } - - return DefWindowProc(hWnd, uMsg, wParam, lParam); -} - -void RunMessageLoop(HINSTANCE hInstance) -{ - std::wstring className = L"ShellExtension Window Class"; - - WNDCLASS wc = { }; - - wc.lpfnWndProc = WindowProc; - wc.hInstance = hInstance; - wc.lpszClassName = className.c_str(); - - RegisterClass(&wc); - - HWND hWnd = CreateWindowEx( - 0, - className.c_str(), - L"ShellExtension", - WS_OVERLAPPEDWINDOW, - CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, - nullptr, - nullptr, - hInstance, - nullptr - ); - - ShowWindow(hWnd, SW_HIDE); - - SetTimer(hWnd, 0, 20000, (TIMERPROC)TimerProc); - - MSG msg; - - while (GetMessage(&msg, nullptr, 0, 0)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } -} - -int __stdcall wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) -{ - init_apartment(); - - ShellExtensionModule module; - - RunMessageLoop(hInstance); -} diff --git a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/packages.config b/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/packages.config deleted file mode 100644 index c12b9fd..0000000 --- a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/pch.cpp b/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/pch.cpp deleted file mode 100644 index bcb5590..0000000 --- a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/pch.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "pch.h" diff --git a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/pch.h b/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/pch.h deleted file mode 100644 index 203a095..0000000 --- a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/pch.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include -#ifdef GetCurrentTime -#undef GetCurrentTime -#endif -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "CustomStateProvider.h" -#include "UriSource.h" \ No newline at end of file diff --git a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/readme.txt b/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/readme.txt deleted file mode 100644 index bcbabe1..0000000 --- a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/readme.txt +++ /dev/null @@ -1,30 +0,0 @@ -======================================================================== - C++/WinRT Common.Windows.WinRT.ShellExtension Project Overview -======================================================================== - -This project demonstrates how to get started consuming Windows Runtime -classes directly from standard C++, using platform projection headers -generated from Windows SDK metadata files. - -Steps to generate and consume SDK platform projection: -1. Build project initially to generate platform projection headers into - your Generated Files folder. -2. Include a projection namespace header in your pch.h, such as - . -3. Consume winrt namespace and any Windows Runtime namespaces, such as - winrt::Windows::Foundation, from source code. -4. Initialize apartment via init_apartment() and consume winrt classes. - -Steps to generate and consume a projection from third party metadata: -1. Add a WinMD reference by right-clicking the References project node - and selecting "Add Reference...". In the Add References dialog, - browse to the component WinMD you want to consume and add it. -2. Build the project once to generate projection headers for the - referenced WinMD file under the "Generated Files" subfolder. -3. As above, include projection headers in pch or source code - to consume projected Windows Runtime classes. - -======================================================================== -Learn more about C++/WinRT here: -http://aka.ms/cppwinrt/ -======================================================================== diff --git a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/resource.h b/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/resource.h deleted file mode 100644 index 5b37dd6..0000000 --- a/Windows/VirtualDrive/VirtualDrive.WinRT.ShellExtension/resource.h +++ /dev/null @@ -1,13 +0,0 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 101 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif diff --git a/Windows/VirtualDrive/VirtualDrive.Common/AppSettings.cs b/Windows/VirtualDrive/VirtualDrive/AppSettings.cs similarity index 99% rename from Windows/VirtualDrive/VirtualDrive.Common/AppSettings.cs rename to Windows/VirtualDrive/VirtualDrive/AppSettings.cs index d8f15df..ebb90e2 100644 --- a/Windows/VirtualDrive/VirtualDrive.Common/AppSettings.cs +++ b/Windows/VirtualDrive/VirtualDrive/AppSettings.cs @@ -6,7 +6,7 @@ using System; using System.IO; -namespace VirtualDrive.Common +namespace VirtualDrive { /// /// Strongly binded project settings. diff --git a/Windows/VirtualDrive/VirtualDrive/Mapping.cs b/Windows/VirtualDrive/VirtualDrive/Mapping.cs index db6c59a..d1a7c28 100644 --- a/Windows/VirtualDrive/VirtualDrive/Mapping.cs +++ b/Windows/VirtualDrive/VirtualDrive/Mapping.cs @@ -13,33 +13,32 @@ namespace VirtualDrive { /// - /// Maps a user file system path to the remote storage path and back. + /// Maps a the remote storage path and data to the user file system path and data. /// /// - /// You will change methods of this class to map the user file system path to your remote storage path. + /// You will change methods of this class to map to your own remote storage. /// - public class Mapping //: IMapping + public class Mapping { - private readonly VirtualEngineBase engine; + /// + /// Remote storage root path. + /// + private readonly string remoteStorageRootPath; - internal Mapping(VirtualEngineBase engine) - { - this.engine = engine; - } + /// + /// User file system root path. + /// + private readonly string userFileSystemRootPath; /// - /// Returns a remote storage URI that corresponds to the user file system path. + /// Creates an instance of this class. /// - /// Full path in the user file system. - /// Remote storage URI that corresponds to the . - public static string MapPath(string userFileSystemPath) + /// Remote storage root path. + /// User file system root path. + public Mapping(string userFileSystemRootPath, string remoteStorageRootPath) { - // Get path relative to the virtual root. - string relativePath = Path.TrimEndingDirectorySeparator(userFileSystemPath).Substring( - Path.TrimEndingDirectorySeparator(Program.Settings.UserFileSystemRootPath).Length); - - string path = $"{Path.TrimEndingDirectorySeparator(Program.Settings.RemoteStorageRootPath)}{relativePath}"; - return path; + this.userFileSystemRootPath = userFileSystemRootPath; + this.remoteStorageRootPath = remoteStorageRootPath; } /// @@ -47,13 +46,13 @@ public static string MapPath(string userFileSystemPath) /// /// Remote storage URI. /// Path in the user file system that corresponds to the . - public static string ReverseMapPath(string remoteStorageUri) + public string ReverseMapPath(string remoteStorageUri) { // Get path relative to the virtual root. string relativePath = Path.TrimEndingDirectorySeparator(remoteStorageUri).Substring( - Path.TrimEndingDirectorySeparator(Program.Settings.RemoteStorageRootPath).Length); + Path.TrimEndingDirectorySeparator(remoteStorageRootPath).Length); - string path = $"{Path.TrimEndingDirectorySeparator(Program.Settings.UserFileSystemRootPath)}{relativePath}"; + string path = $"{Path.TrimEndingDirectorySeparator(userFileSystemRootPath)}{relativePath}"; return path; } @@ -63,8 +62,9 @@ public static string ReverseMapPath(string remoteStorageUri) /// /// As soon as System.IO .NET classes require path as an input parameter, /// this function maps remote storage ID to the remote storge path. - /// In your real-life file system you will typically request your remote storage - /// items by ID instead of using this method. + /// + /// In your real-life file system you will typically request your + /// remote storage items directly by ID instead of using this method. /// /// Path in the remote storage. public static string GetRemoteStoragePathById(byte[] remoteStorageId) @@ -73,9 +73,13 @@ public static string GetRemoteStoragePathById(byte[] remoteStorageId) } /// - /// Gets a user file system item info from the remote storage data. + /// Gets a user file system file/folder metadata from the remote storage file/folder data. /// /// Remote storage item info. + /// + /// In your real-life file system you will change the input parameter type of this method and rewrite it + /// to map your remote storage item data to the user file system data. + /// /// File or folder metadata that corresponds to the parameter. public static FileSystemItemMetadataExt GetUserFileSysteItemMetadata(FileSystemInfo remoteStorageItem) { @@ -109,78 +113,5 @@ public static FileSystemItemMetadataExt GetUserFileSysteItemMetadata(FileSystemI return userFileSystemItem; } - - - //public async Task UpdateETagAsync(string remoteStoragePath, string userFileSystemPath) - //{ - // PlaceholderItem placeholder = engine.Placeholders.GetItem(userFileSystemPath); - // if (placeholder.Type == FileSystemItemType.File - // && ((File.GetAttributes(userFileSystemPath) & System.IO.FileAttributes.Offline) == 0)) - // { - // string eTag = (await WindowsFileSystemItem.GetUsnByPathAsync(remoteStoragePath)).ToString(); - // await placeholder.Properties.AddOrUpdateAsync("ETag", eTag); - // return true; - // } - // return false; - //} - - public async Task IsModifiedAsync(string userFileSystemPath, FileSystemItemMetadataExt remoteStorageItemMetadata, ILogger logger) - { - string remoteStoragePath = MapPath(userFileSystemPath); - - return IsModified(userFileSystemPath, remoteStoragePath); - } - - /// - /// Compares two files contents. - /// - /// File or folder 1 to compare. - /// File or folder 2 to compare. - /// True if file is modified. False - otherwise. - internal static bool IsModified(string filePath1, string filePath2) - { - if (FsPath.IsFolder(filePath1) && FsPath.IsFolder(filePath2)) - { - return false; - } - - try - { - if (new FileInfo(filePath1).Length == new FileInfo(filePath2).Length) - { - // Verify that the file is not offline, - // therwise the file will be hydrated when the file stream is opened. - if (new FileInfo(filePath1).Attributes.HasFlag(System.IO.FileAttributes.Offline) - || new FileInfo(filePath1).Attributes.HasFlag(System.IO.FileAttributes.Offline)) - { - return false; - } - - byte[] hash1; - byte[] hash2; - using (var alg = System.Security.Cryptography.MD5.Create()) - { - // This code for demo purposes only. We do not block files for writing, which is required by some apps, for example by AutoCAD. - using (FileStream stream = new FileStream(filePath1, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete)) - { - hash1 = alg.ComputeHash(stream); - } - using (FileStream stream = new FileStream(filePath2, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete)) - { - hash2 = alg.ComputeHash(stream); - } - } - - return !hash1.SequenceEqual(hash2); - } - } - catch (IOException) - { - // One of the files is blocked. Can not compare files and start sychronization. - return false; - } - - return true; - } } } diff --git a/Windows/VirtualDrive/VirtualDrive/Program.cs b/Windows/VirtualDrive/VirtualDrive/Program.cs index 23d25c7..dc2a816 100644 --- a/Windows/VirtualDrive/VirtualDrive/Program.cs +++ b/Windows/VirtualDrive/VirtualDrive/Program.cs @@ -1,22 +1,17 @@ using System; using System.Diagnostics; using System.IO; -using System.Linq; using System.Reflection; -using System.Runtime.InteropServices; using System.Threading.Tasks; -using Windows.Storage; using Microsoft.Extensions.Configuration; using log4net; -using log4net.Appender; -using log4net.Config; + using ITHit.FileSystem; using ITHit.FileSystem.Windows; using ITHit.FileSystem.Samples.Common; using ITHit.FileSystem.Samples.Common.Windows; -using VirtualDrive.Common; -using System.Threading; +using ITHit.FileSystem.Windows.ShellExtension; namespace VirtualDrive { @@ -25,60 +20,63 @@ class Program /// /// Application settings. /// - internal static AppSettings Settings; + private static AppSettings Settings; /// /// Log4Net logger. /// private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - /// - /// Log file path. - /// - private static string LogFilePath; - /// /// Processes OS file system calls, /// synchronizes user file system to remote storage. /// - public static VirtualEngine Engine; - - private static CancellationTokenSource ctsProcessAsync = new CancellationTokenSource(); - private static CancellationToken ctProcessAsync = default; + private static VirtualEngine Engine; + /// + /// Outputs logging information. + /// + private static LogFormatter logFormatter; static async Task Main(string[] args) { - // Load Settings. - IConfiguration configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json", false, true).Build(); - Settings = configuration.ReadSettings(); - - // Configure log4net and set log file path. - LogFilePath = ConfigureLogger(); + // In a real world application the user system should have trusted certificate installed or an installer (msi) should install it. + // This method should be omitted for packaged application. + CertificateRegistrar.InstallDeveloperCertificate(); - PrintHelp(); + // In the case of a regular installer (msi) call this method during installation. + // This method should be omitted for packaged application. + await PackageRegistrar.RegisterSparsePackageAsync(); - // Register sync root and create app folders. - await RegisterSyncRootAsync(); - - // Log indexed state. - StorageFolder userFileSystemRootFolder = await StorageFolder.GetFolderFromPathAsync(Settings.UserFileSystemRootPath); - log.Info($"\nIndexed state: {(await userFileSystemRootFolder.GetIndexedStateAsync())}\n"); - - Logger.PrintHeader(log); + // Load Settings. + Settings = new ConfigurationBuilder().AddJsonFile("appsettings.json", false, true).Build().ReadSettings(); + logFormatter = new LogFormatter(log, Settings.AppID); - using (Engine = new VirtualEngine( - Settings.UserFileSystemLicense, - Settings.UserFileSystemRootPath, - Settings.RemoteStorageRootPath, - Settings.IconsFolderPath, - Settings.RpcCommunicationChannelName, - Settings.SyncIntervalMs, - Settings.MaxDegreeOfParallelism, - log)) + try { - try + // Log environment description. + logFormatter.PrintEnvironmentDescription(); + + // Register sync root and create app folders. + await RegisterSyncRootAsync(); + + // Log indexing state. Sync root must be indexed. + await logFormatter.PrintIndexingStateAsync(Settings.UserFileSystemRootPath); + + // Log console commands. + logFormatter.PrintHelp(); + + // Log logging columns headers. + logFormatter.PrintHeader(); + + using (Engine = new VirtualEngine( + Settings.UserFileSystemLicense, + Settings.UserFileSystemRootPath, + Settings.RemoteStorageRootPath, + Settings.IconsFolderPath, + Settings.SyncIntervalMs, + logFormatter)) { Engine.AutoLock = Settings.AutoLock; @@ -97,64 +95,29 @@ static async Task Main(string[] args) // Keep this application running and reading user input. await ProcessUserInputAsync(); } - catch (Exception ex) - { - log.Error(ex); - await ProcessUserInputAsync(); - } } - } - - /// - /// Configures log4net logger. - /// - /// Log file path. - private static string ConfigureLogger() - { - // Load Log4Net for net configuration. - var logRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); - XmlConfigurator.Configure(logRepository, new FileInfo(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "log4net.config"))); - - // Update log file path for msix package. - RollingFileAppender rollingFileAppender = logRepository.GetAppenders().Where(p => p.GetType() == typeof(RollingFileAppender)).FirstOrDefault() as RollingFileAppender; - if (rollingFileAppender != null && rollingFileAppender.File.Contains("WindowsApps")) + catch (Exception ex) { - rollingFileAppender.File = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), Settings.AppID, - Path.GetFileName(rollingFileAppender.File)); + log.Error(ex); + await ProcessUserInputAsync(); } - return rollingFileAppender?.File; - } - - private static void PrintHelp() - { - log.Info($"\n{"AppID:", -15} {Settings.AppID}"); - log.Info($"\n{"Engine version:",-15} {typeof(IEngine).Assembly.GetName().Version}"); - log.Info($"\n{"OS version:", -15} {RuntimeInformation.OSDescription}"); - log.Info($"\n{"Env version:", -15} {RuntimeInformation.FrameworkDescription} {IntPtr.Size * 8}bit."); - log.Info("\n\nPress Esc to unregister file system, delete all files/folders and exit (simulate uninstall)."); - log.Info("\nPress Spacebar to exit without unregistering (simulate reboot)."); - log.Info("\nPress 'e' to start/stop the Engine and all sync services."); - //log.Info("\nPress 's' to start/stop full synchronization service."); - //log.Info("\nPress 'u' to start/stop user file system to remote storage sync (EngineWindows.ProcessAsync())."); - log.Info("\nPress 'm' to start/stop remote storage monitor."); - log.Info($"\nPress 'l' to open log file. ({LogFilePath})"); - log.Info($"\nPress 'b' to submit support tickets, report bugs, suggest features. (https://userfilesystem.com/support/)"); - log.Info("\n----------------------\n"); } private static async Task ProcessUserInputAsync() { do { - switch (Console.ReadKey(true).KeyChar) + ConsoleKeyInfo keyInfo = Console.ReadKey(true); + + switch (keyInfo.Key) { - case (char)ConsoleKey.F1: - case 'h': + case ConsoleKey.F1: + case ConsoleKey.H: // Print help info. - PrintHelp(); + logFormatter.PrintHelp(); break; - case 'e': + case ConsoleKey.E: // Start/stop the Engine and all sync services. if (Engine.State == EngineState.Running) { @@ -165,52 +128,36 @@ private static async Task ProcessUserInputAsync() await Engine.StartAsync(); } break; - /* - case 's': - // Start/stop full synchronization. - if (Engine.SyncService.SyncState == SynchronizationState.Disabled) - { - if(Engine.State != EngineState.Running) - { - Engine.SyncService.LogError("Failed to start. The Engine must be running."); - break; - } - await Engine.SyncService.StartAsync(); - } - else - { - await Engine.SyncService.StopAsync(); - } - break; - */ - /* - case 'u': + + case ConsoleKey.S: // Start/stop full synchronization. - if (!ctProcessAsync.CanBeCanceled) + if (Engine.SyncService.SyncState == SynchronizationState.Disabled) { - if (Engine.State != EngineState.Running) + if(Engine.State != EngineState.Running) { - Engine.LogError("Failed to start. The Engine must be running."); + Engine.SyncService.Logger.LogError("Failed to start. The Engine must be running."); break; } - ctProcessAsync = ctsProcessAsync.Token; - await Engine.ProcessAsync(ctProcessAsync); - ctProcessAsync = default; + await Engine.SyncService.StartAsync(); } else { - ctsProcessAsync.Cancel(); - ctProcessAsync = default; + await Engine.SyncService.StopAsync(); } break; - */ - case 'm': + + case ConsoleKey.D: + // Enables/disables debug logging. + logFormatter.DebugLoggingEnabled = !logFormatter.DebugLoggingEnabled; + break; + + case ConsoleKey.M: // Start/stop remote storage monitor. if (Engine.RemoteStorageMonitor.SyncState == SynchronizationState.Disabled) { if (Engine.State != EngineState.Running) { - Engine.RemoteStorageMonitor.LogError("Failed to start. The Engine must be running."); + Engine.RemoteStorageMonitor.Logger.LogError("Failed to start. The Engine must be running."); break; } Engine.RemoteStorageMonitor.Start(); @@ -221,16 +168,16 @@ private static async Task ProcessUserInputAsync() } break; - case 'l': + case ConsoleKey.L: // Open log file. - ProcessStartInfo psiLog = new ProcessStartInfo(LogFilePath); + ProcessStartInfo psiLog = new ProcessStartInfo(logFormatter.LogFilePath); psiLog.UseShellExecute = true; using (Process.Start(psiLog)) { } break; - case 'b': + case ConsoleKey.B: // Submit support tickets, report bugs, suggest features. ProcessStartInfo psiSupport = new ProcessStartInfo("https://www.userfilesystem.com/support/"); psiSupport.UseShellExecute = true; @@ -239,15 +186,7 @@ private static async Task ProcessUserInputAsync() } break; - case 'q': - // Unregister during programm uninstall. - Engine.Dispose(); - await UnregisterSyncRootAsync(); - log.Info("\nAll empty file and folder placeholders are deleted. Hydrated placeholders are converted to regular files / folders.\n"); - return; - - case (char)ConsoleKey.Escape: - case 'Q': + case ConsoleKey.Escape: if (Engine.State == EngineState.Running) { await Engine.StopAsync(); @@ -258,9 +197,12 @@ private static async Task ProcessUserInputAsync() // Delete all files/folders. await CleanupAppFoldersAsync(); + + // Unregister sparse package. + await UnregisterSparsePackageAsync(); return; - case (char)ConsoleKey.Spacebar : + case ConsoleKey.Spacebar: if (Engine.State == EngineState.Running) { await Engine.StopAsync(); @@ -268,11 +210,15 @@ private static async Task ProcessUserInputAsync() log.Info("\n\nAll downloaded file / folder placeholders remain in file system. Restart the application to continue managing files.\n"); return; + case ConsoleKey.P: + // Unregister sparse package. + await UnregisterSparsePackageAsync(); + break; + default: break; } - } while (true); } @@ -287,14 +233,13 @@ private static async Task RegisterSyncRootAsync() { if (!await Registrar.IsRegisteredAsync(Settings.UserFileSystemRootPath)) { - log.Info($"\nRegistering {Settings.UserFileSystemRootPath} sync root."); + log.Info($"\n\nRegistering {Settings.UserFileSystemRootPath} sync root."); Directory.CreateDirectory(Settings.UserFileSystemRootPath); await Registrar.RegisterAsync(SyncRootId, Settings.UserFileSystemRootPath, Settings.ProductName, Path.Combine(Settings.IconsFolderPath, "Drive.ico")); - log.Info("\nRegistering shell extensions...\n"); - ShellExtensionRegistrar.Register(SyncRootId); + ShellExtensionRegistrar.Register(SyncRootId, log); } else { @@ -319,8 +264,7 @@ private static async Task UnregisterSyncRootAsync() log.Info($"\n\nUnregistering {Settings.UserFileSystemRootPath} sync root."); await Registrar.UnregisterAsync(SyncRootId); - log.Info("\nUnregistering shell extensions...\n"); - ShellExtensionRegistrar.Unregister(); + ShellExtensionRegistrar.Unregister(log); } private static async Task CleanupAppFoldersAsync() @@ -345,6 +289,16 @@ private static async Task CleanupAppFoldersAsync() } } + /// + /// Unregisters sparse package. + /// + private static async Task UnregisterSparsePackageAsync() + { + log.Info("\nUnregistering sparse package..."); + await PackageRegistrar.UnregisterSparsePackageAsync(SyncRootId); + log.Info("\nSparse package unregistered sucessfully."); + } + #if DEBUG /// /// Opens Windows File Manager with both remote storage and user file system for testing. diff --git a/Windows/VirtualDrive/VirtualDrive/RemoteStorage/General.docx b/Windows/VirtualDrive/VirtualDrive/RemoteStorage/General.docx index e6f73c1e607fec685fe0e54e82cb6d86828462ca..3a0e91b462810138cdc71a1a00a76db1264b14c3 100644 GIT binary patch delta 2881 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 E2gOr2-~a#s delta 2738 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 diff --git a/Windows/VirtualDrive/VirtualDrive/RemoteStorageMonitor.cs b/Windows/VirtualDrive/VirtualDrive/RemoteStorageMonitor.cs index 03b8079..8a2e970 100644 --- a/Windows/VirtualDrive/VirtualDrive/RemoteStorageMonitor.cs +++ b/Windows/VirtualDrive/VirtualDrive/RemoteStorageMonitor.cs @@ -15,15 +15,15 @@ namespace VirtualDrive { /// /// Monitors changes in the remote storage, notifies the client and updates the user file system. - /// If any file or folder is is modified, created, delated, renamed or attributes changed in the remote storage, - /// triggers an event with information about changes. + /// If any file or folder is modified, created, delated, renamed or attributes changed in the remote storage, + /// this class triggers an event with information about changes. /// /// /// Here, for demo purposes we simulate server by monitoring source file path using FileWatchWrapper. /// In your application, instead of using FileWatchWrapper, you will connect to your remote storage using web sockets /// or use any other technology to get notifications about changes in your remote storage. /// - public class RemoteStorageMonitor : Logger, IDisposable + public class RemoteStorageMonitor : IDisposable { /// /// Current synchronization state. @@ -37,25 +37,35 @@ public virtual SynchronizationState SyncState } /// - /// Virtul drive instance. This class will call methods + /// Logger. + /// + public readonly ILogger Logger; + + /// + /// Engine instance. We will call methods /// to update user file system when any data is changed in the remote storage. /// - private readonly VirtualEngine engine; + private readonly Engine engine; /// /// Watches for changes in the remote storage file system. /// private readonly FileSystemWatcherQueued watcher = new FileSystemWatcherQueued(); + private readonly Mapping mapping; + /// /// Creates instance of this class. /// /// Remote storage path. Folder that contains source files to monitor changes. /// Virtual drive to send notifications about changes in the remote storage. - /// Logger. - internal RemoteStorageMonitor(string remoteStorageRootPath, VirtualEngine engine, ILog log) : base("Remote Storage Monitor", log) + /// Logger. + internal RemoteStorageMonitor(string remoteStorageRootPath, Engine engine, ILogger logger) { this.engine = engine; + this.Logger = logger.CreateLogger("Remote Storage Monitor"); + + mapping = new Mapping(engine.Path, remoteStorageRootPath); watcher.IncludeSubdirectories = true; watcher.Path = remoteStorageRootPath; @@ -75,7 +85,7 @@ internal RemoteStorageMonitor(string remoteStorageRootPath, VirtualEngine engine internal void Start() { watcher.EnableRaisingEvents = true; - LogMessage($"Started", watcher.Path); + Logger.LogMessage($"Started", watcher.Path); } /// @@ -84,7 +94,7 @@ internal void Start() internal void Stop() { watcher.EnableRaisingEvents = false; - LogMessage($"Stopped", watcher.Path); + Logger.LogMessage($"Stopped", watcher.Path); } /// @@ -93,11 +103,11 @@ internal void Stop() /// In this method we create a new file/folder in the user file system. private async void CreatedAsync(object sender, FileSystemEventArgs e) { - LogMessage(e.ChangeType.ToString(), e.FullPath); + Logger.LogMessage(e.ChangeType.ToString(), e.FullPath); string remoteStoragePath = e.FullPath; try { - string userFileSystemPath = Mapping.ReverseMapPath(remoteStoragePath); + string userFileSystemPath = mapping.ReverseMapPath(remoteStoragePath); // This check is only required because we can not prevent circular calls because of the simplicity of this example. // In your real-life application you will not send updates from server back to client that issued the update. @@ -113,14 +123,14 @@ private async void CreatedAsync(object sender, FileSystemEventArgs e) IFileSystemItemMetadata newItemInfo = Mapping.GetUserFileSysteItemMetadata(remoteStorageItem); if (await engine.ServerNotifications(userFileSystemParentPath).CreateAsync(new[] { newItemInfo }) > 0) { - LogMessage($"Created succesefully", userFileSystemPath); + Logger.LogMessage($"Created succesefully", userFileSystemPath); } } } } catch (Exception ex) { - LogError($"{e.ChangeType} failed", remoteStoragePath, null, ex); + Logger.LogError($"{e.ChangeType} failed", remoteStoragePath, null, ex); } } @@ -133,16 +143,16 @@ private async void CreatedAsync(object sender, FileSystemEventArgs e) /// private async void ChangedAsync(object sender, FileSystemEventArgs e) { - LogMessage(e.ChangeType.ToString(), e.FullPath); + Logger.LogMessage(e.ChangeType.ToString(), e.FullPath); string remoteStoragePath = e.FullPath; string userFileSystemPath = null; try { - userFileSystemPath = Mapping.ReverseMapPath(remoteStoragePath); + userFileSystemPath = mapping.ReverseMapPath(remoteStoragePath); // This check is only required because we can not prevent circular calls because of the simplicity of this example. // In your real-life application you will not send updates from server back to client that issued the update. - if (Mapping.IsModified(userFileSystemPath, remoteStoragePath)) + if (IsModified(userFileSystemPath, remoteStoragePath)) { FileSystemInfo remoteStorageItem = FsPath.GetFileSystemItem(remoteStoragePath); IFileSystemItemMetadata itemInfo = Mapping.GetUserFileSysteItemMetadata(remoteStorageItem); @@ -153,18 +163,18 @@ private async void ChangedAsync(object sender, FileSystemEventArgs e) // Update ETag. //await engine.Mapping.UpdateETagAsync(remoteStoragePath, userFileSystemPath); - LogMessage("Updated succesefully", userFileSystemPath); + Logger.LogMessage("Updated succesefully", userFileSystemPath); } } } catch (IOException ex) { // The file is blocked in the user file system. This is a normal behaviour. - LogMessage(ex.Message); + Logger.LogMessage(ex.Message); } catch (Exception ex) { - LogError($"{e.ChangeType} failed", remoteStoragePath, null, ex); + Logger.LogError($"{e.ChangeType} failed", remoteStoragePath, null, ex); } } @@ -174,11 +184,11 @@ private async void ChangedAsync(object sender, FileSystemEventArgs e) /// In this method we delete corresponding file/folder in user file system. private async void DeletedAsync(object sender, FileSystemEventArgs e) { - LogMessage(e.ChangeType.ToString(), e.FullPath); + Logger.LogMessage(e.ChangeType.ToString(), e.FullPath); string remoteStoragePath = e.FullPath; try { - string userFileSystemPath = Mapping.ReverseMapPath(remoteStoragePath); + string userFileSystemPath = mapping.ReverseMapPath(remoteStoragePath); // This check is only required because we can not prevent circular calls because of the simplicity of this example. // In your real-life application you will not send updates from server back to client that issued the update. @@ -187,13 +197,13 @@ private async void DeletedAsync(object sender, FileSystemEventArgs e) // Because of the on-demand population the file or folder placeholder may not exist in the user file system. if (await engine.ServerNotifications(userFileSystemPath).DeleteAsync()) { - LogMessage("Deleted succesefully", userFileSystemPath); + Logger.LogMessage("Deleted succesefully", userFileSystemPath); } } } catch (Exception ex) { - LogError($"{e.ChangeType} failed", remoteStoragePath, null, ex); + Logger.LogError($"{e.ChangeType} failed", remoteStoragePath, null, ex); } } @@ -203,13 +213,13 @@ private async void DeletedAsync(object sender, FileSystemEventArgs e) /// In this method we rename corresponding file/folder in user file system. private async void RenamedAsync(object sender, RenamedEventArgs e) { - LogMessage("Renamed", e.OldFullPath, e.FullPath); + Logger.LogMessage("Renamed", e.OldFullPath, e.FullPath); string remoteStorageOldPath = e.OldFullPath; string remoteStorageNewPath = e.FullPath; try { - string userFileSystemOldPath = Mapping.ReverseMapPath(remoteStorageOldPath); - string userFileSystemNewPath = Mapping.ReverseMapPath(remoteStorageNewPath); + string userFileSystemOldPath = mapping.ReverseMapPath(remoteStorageOldPath); + string userFileSystemNewPath = mapping.ReverseMapPath(remoteStorageNewPath); // This check is only required because we can not prevent circular calls because of the simplicity of this example. // In your real-life application you will not send updates from server back to client that issued the update. @@ -218,19 +228,19 @@ private async void RenamedAsync(object sender, RenamedEventArgs e) // Because of the on-demand population the file or folder placeholder may not exist in the user file system. if (await engine.ServerNotifications(userFileSystemOldPath).MoveToAsync(userFileSystemNewPath)) { - LogMessage("Renamed succesefully", userFileSystemOldPath, userFileSystemNewPath); + Logger.LogMessage("Renamed succesefully", userFileSystemOldPath, userFileSystemNewPath); } } } catch (Exception ex) { - LogError($"{e.ChangeType} failed", remoteStorageOldPath, remoteStorageNewPath, ex); + Logger.LogError($"{e.ChangeType} failed", remoteStorageOldPath, remoteStorageNewPath, ex); } } private void Error(object sender, ErrorEventArgs e) { - LogError(null, null, null, e.GetException()); + Logger.LogError(null, null, null, e.GetException()); } @@ -243,7 +253,7 @@ protected virtual void Dispose(bool disposing) if (disposing) { watcher.Dispose(); - LogMessage($"Disposed"); + Logger.LogMessage($"Disposed"); } // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. @@ -268,5 +278,57 @@ public void Dispose() // TODO: uncomment the following line if the finalizer is overridden above. // GC.SuppressFinalize(this); } + + /// + /// Compares two files contents. + /// + /// File or folder 1 to compare. + /// File or folder 2 to compare. + /// True if file is modified. False - otherwise. + internal static bool IsModified(string filePath1, string filePath2) + { + if (FsPath.IsFolder(filePath1) && FsPath.IsFolder(filePath2)) + { + return false; + } + + try + { + if (new FileInfo(filePath1).Length == new FileInfo(filePath2).Length) + { + // Verify that the file is not offline, + // therwise the file will be hydrated when the file stream is opened. + if (new FileInfo(filePath1).Attributes.HasFlag(System.IO.FileAttributes.Offline) + || new FileInfo(filePath1).Attributes.HasFlag(System.IO.FileAttributes.Offline)) + { + return false; + } + + byte[] hash1; + byte[] hash2; + using (var alg = System.Security.Cryptography.MD5.Create()) + { + // This code for demo purposes only. We do not block files for writing, which is required by some apps, for example by AutoCAD. + using (FileStream stream = new FileStream(filePath1, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete)) + { + hash1 = alg.ComputeHash(stream); + } + using (FileStream stream = new FileStream(filePath2, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete)) + { + hash2 = alg.ComputeHash(stream); + } + } + + return !hash1.SequenceEqual(hash2); + } + } + catch (IOException) + { + // One of the files is blocked. Can not compare files and start sychronization. + return false; + } + + return true; + } } } diff --git a/Windows/VirtualDrive/VirtualDrive/ShellExtensionRegistrar.cs b/Windows/VirtualDrive/VirtualDrive/ShellExtensionRegistrar.cs index a63ab79..8a99c0b 100644 --- a/Windows/VirtualDrive/VirtualDrive/ShellExtensionRegistrar.cs +++ b/Windows/VirtualDrive/VirtualDrive/ShellExtensionRegistrar.cs @@ -1,31 +1,44 @@ -using ITHit.FileSystem.Samples.Common.Windows; using System.IO; using System.Reflection; +using log4net; +using CommonShellExtension = ITHit.FileSystem.Windows.ShellExtension; +using ITHit.FileSystem.Windows.ShellExtension; using VirtualDrive.ShellExtension; -using CommonShellExtension = ITHit.FileSystem.Samples.Common.Windows.ShellExtension; + namespace VirtualDrive { internal class ShellExtensionRegistrar { - private static readonly string ComServerRelativePath = @"VirtualDrive.ShellExtension.exe"; + private static readonly string ComServerRelativePath = "VirtualDrive.ShellExtension.exe"; - public static void Register(string syncRootId) + internal static void Register(string syncRootId, ILog log) { - if (!Registrar.IsRunningAsUwp()) + if (!PackageRegistrar.IsRunningAsUwp()) { - string comServerPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), ComServerRelativePath)); + log.Info("\nRegistering shell extensions...\n"); + + string applicationDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); + string comServerPath = Path.Combine(applicationDirectory, ComServerRelativePath); + CommonShellExtension.ShellExtensionRegistrar.Register(syncRootId, "ThumbnailProvider", typeof(ThumbnailProvider).GUID, comServerPath); CommonShellExtension.ShellExtensionRegistrar.Register(syncRootId, "MenuVerbHandler_0", typeof(ContextMenusProvider).GUID, comServerPath); + CommonShellExtension.ShellExtensionRegistrar.Register(syncRootId, "CustomStateHandler", typeof(CustomStateProvider).GUID, comServerPath); + CommonShellExtension.ShellExtensionRegistrar.Register(syncRootId, "UriHandler", typeof(UriSource).GUID, comServerPath); + CommonShellExtension.ShellExtensionRegistrar.RegisterPackage(syncRootId); } } - public static void Unregister() + internal static void Unregister(ILog log) { - if (!Registrar.IsRunningAsUwp()) + if (!PackageRegistrar.IsRunningAsUwp()) { + log.Info("\nUnregistering shell extensions...\n"); + CommonShellExtension.ShellExtensionRegistrar.Unregister(typeof(ThumbnailProvider).GUID); CommonShellExtension.ShellExtensionRegistrar.Unregister(typeof(ContextMenusProvider).GUID); + CommonShellExtension.ShellExtensionRegistrar.Unregister(typeof(CustomStateProvider).GUID); + CommonShellExtension.ShellExtensionRegistrar.Unregister(typeof(UriSource).GUID); } } } diff --git a/Windows/VirtualDrive/VirtualDrive/SparsePackage/appxmanifest.xml b/Windows/VirtualDrive/VirtualDrive/SparsePackage/appxmanifest.xml new file mode 100644 index 0000000..8141d95 --- /dev/null +++ b/Windows/VirtualDrive/VirtualDrive/SparsePackage/appxmanifest.xml @@ -0,0 +1,76 @@ + + + + + + + + Virtual Drive sparse package + IT Hit LTD + stub.png + true + disabled + disabled + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Windows/VirtualDrive/VirtualDrive/ThumbnailExtractor.cs b/Windows/VirtualDrive/VirtualDrive/ThumbnailExtractor.cs index 34aadb7..8320024 100644 --- a/Windows/VirtualDrive/VirtualDrive/ThumbnailExtractor.cs +++ b/Windows/VirtualDrive/VirtualDrive/ThumbnailExtractor.cs @@ -4,7 +4,7 @@ using System.IO; using System.Runtime.InteropServices; -using ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop; +using ITHit.FileSystem.Windows.ShellExtension.Interop; namespace VirtualDrive { @@ -13,14 +13,12 @@ internal static class ThumbnailExtractor /// /// Generates thumbnail for a file using existing local registered thumbnail handler if any. /// - /// File path in user file system to generate thumbnail for. + /// Path to get thumbnail for. /// The maximum thumbnail size, in pixels. /// Returns a thumbnail bitmap or null if the thumbnail handler is not found. - public static byte[] GetThumbnail(string userFileSystemPath, uint size) + public static byte[] GetRemoteThumbnail(string path, uint size) { - string remoteStorageItemPath = Mapping.MapPath(userFileSystemPath); - - using (Bitmap bitmap = GetThumbnailBitmap(remoteStorageItemPath, size)) + using (Bitmap bitmap = GetThumbnailBitmap(path, size)) { if (bitmap == null) { @@ -43,6 +41,11 @@ private static Bitmap GetThumbnailBitmap(string filePath, uint size) try { + if (filePath.StartsWith(@"\\")) + { + filePath = filePath.Remove(0, 4); + } + uint itemResult = Shell32.SHCreateItemFromParsingName(filePath, null, typeof(IShellItem).GUID, out destinationItem); diff --git a/Windows/VirtualDrive/VirtualDrive/VirtualDrive.csproj b/Windows/VirtualDrive/VirtualDrive/VirtualDrive.csproj index 2e75246..722a608 100644 --- a/Windows/VirtualDrive/VirtualDrive/VirtualDrive.csproj +++ b/Windows/VirtualDrive/VirtualDrive/VirtualDrive.csproj @@ -1,8 +1,7 @@ - + Exe - net6.0-windows10.0.18362.0 - 10.0.18362.0 + net5.0-windows10.0.19041.0 IT Hit LTD. IT Hit LTD. Virtual Drive @@ -40,9 +39,9 @@ This is an advanced project with ETags support, Microsoft Office documents editi - + + - @@ -80,4 +79,7 @@ This is an advanced project with ETags support, Microsoft Office documents editi Always + + + \ No newline at end of file diff --git a/Windows/VirtualDrive/VirtualDrive/VirtualDrive_TemporaryKey.pfx b/Windows/VirtualDrive/VirtualDrive/VirtualDrive_TemporaryKey.pfx new file mode 100644 index 0000000000000000000000000000000000000000..1b3d0539406c13ba65c6ef53469f28a8a35a0293 GIT binary patch literal 2528 zcmZXUc|6p67sr2N#xj->MvL4*gt6RaY@=jfhJ+#eMfRn!4#`~G7+VM-k?gxL$d)xb z*<-|&M5ru}k|kN6sn_$|`+8o_>-#$2^EvNx&iDKM=X+3O=o}pgj3PtR8K7cO+EL%x zK#ZVVGBgH4hDOqG7>Z1P@INW~bqJaM6AdrX>^%d^f4W$iL3Fuf$O(!JIY99;F#i|R z4u?UQun3PN6&+qBIy%O22pKY?VU?;(R9cHNv#Bj;&B=n#0>f{~Zz2m+zc^qvcvddW zC`Y8kX9Q{*XK-j_TSQ@y(Z6$F`5!p z^s|P^i9WFcIp#Gitd{S0)HRU4)3H935jFaFywJq{n!9Y|V|dZithbi8I$6ScE0Y>U zOs!?&=QXEVB0lM{d^4EeO`*={vEZBOqg?K7Wyd#;$Vtf;h;A62Ikz;MSkoMMgSTMu zA>*Em2*TdO)K+%z$Pqp#!>x&l&fs{grgV~6)@1PQRgY<%7ALZT$tF6Fs^O$;$^YbK z8&z61A{X){aK%@9!270RL`nH5=})@7O}t~w(T(*qu5p=$l%^nE^QM~|Bk0Iu%Y=ju z2H`l%;-+z=2a!+JiqqBB;MQ!r=Q3=2jn%n6-5^F(OeN?UG6Flz?Y&UW`PB~jI?aeP zc|vYE9wQy8@;m3D+>ogH-jXfQmuT6Y`Qf2zS3k*IXFJv#ccX>G#3%8Yox5u)!QOQs zGy3@xi$P&RKL4&6ki5Ek%H|a1*iDzU=+=@HSNOrCMe^NJtz<{&<&@YvoyPRLK3QY2 zP`W8)KXgDQl0+(d`3T`BY&5MYAmd_fR@}bxkj;TSV59T!PS#ouU0=~08?Y;gIT_Y( zBDoWl9y|>$IAv~xjV`Gu7NTq6BfL1m0m z3D-VHI7ZA4`HFl1Dx?3RsW{|1&jq&)Xs&*rVC-ynEY-j% zgi8w=#a&Hd5>{qM^T``Npazq)*?!o&@5ZAz`SSA3&wfHX52fk&E9}XuC6@>Txw$~Z( zujB&jwvCe54_rL~FCNGju2Ut;c?b~Ju7IG60pr3oRk~lO1@``|DnR1xWyGhUf=Y?C zIk){_U)IHpkaYFvy41x({?5ccf+iS$*%OYE5hLO&Uo1}66$Tl!;+eEw`V?!4-Ap~) zDOtMT&}>>Yt2BThbf0sPe0@ppz0UE~L_j~5;$mg@y5qq#JnxD`-+*6_rG+Wcz0maD za=r0{q10Z!L4s#8X$~8vA8e)IcE5J3mUyyr&T24`BkuWd$|qc=xpOz_7TV6CRdIpy ze*1N6ucGo*v29(8Y}JsfW}-@22$e>|`I$BJ)`WzP&~Ub>ufLrl*N?9jRhP5Igy;f8={KYQjxZGmd_ad=eQfpGUa+!&}N<rKbQO8IO1|ta>TyelUo7BL}B$ zdyV>PW0l_Lv}y&LlqlabRBfxpbr&U>|C?+Yn+#*^ar*12!J|TQK%hl`^Rdke#83_S z^36!Xy^>(z+c$Qud^7J%k(!LwnE5P1Xj(RE<9yZc>gvzNN;))DAWj|i14^l4Ex0~6 z(9wpbCufZ`cWlr@L0>lV1oZ*tx6aVDLHSqM-n$2|+NbS$?U5CIDoDkiA_Vv?LXZ!5 zcwoPog4~?4oZeff8!>#dXc&_qJ|Fo4DHQPZmY*AkKzCb1S4(Bl6T?Di6y z*`sT%ckw~(FeQMc9U0KNrlUGKT`lL_Y^PLWarLsyv;|0uC-rn9pg};mR}fNMT_dPM4o_c3Nt2U)w` zD#o6mMcJ>l6ypX3)g>rf0*T3g**0}P>dA}ga-w?NvAn(x7Ad}CZ$8fgr>35DlcRuR zw3yVr;mRjg zO?TZi>^sT@Im7@P)TCH!K3|m(EHgX8;}^A_j#5DJp%@wH#bIDN9ta#wjB?pOymX=9 oyjH7-(tuS}A%y~g-|Wo%n18K_4&201y82y|??qC3)z6RjZ+Jy$ZU6uP literal 0 HcmV?d00001 diff --git a/Windows/VirtualDrive/VirtualDrive/VirtualEngine.cs b/Windows/VirtualDrive/VirtualDrive/VirtualEngine.cs index 048513e..5359275 100644 --- a/Windows/VirtualDrive/VirtualDrive/VirtualEngine.cs +++ b/Windows/VirtualDrive/VirtualDrive/VirtualEngine.cs @@ -1,13 +1,10 @@ using System; using System.Threading.Tasks; -using System.Collections.Generic; +using System.Threading; -using log4net; using ITHit.FileSystem; -using ITHit.FileSystem.Windows; -using ITHit.FileSystem.Samples.Common; using ITHit.FileSystem.Samples.Common.Windows; -using System.Threading; + namespace VirtualDrive { @@ -19,6 +16,8 @@ public class VirtualEngine : VirtualEngineBase /// public readonly RemoteStorageMonitor RemoteStorageMonitor; + //public override IMapping Mapping { get { return new Mapping(this); } } + /// /// Creates a vitual file system Engine. /// @@ -29,22 +28,18 @@ public class VirtualEngine : VirtualEngineBase /// /// Path to the remote storage root. /// Path to the icons folder. - /// Channel name to communicate with Windows Explorer context menu and other components on this machine. /// Full synchronization interval in milliseconds. - /// A maximum number of concurrent tasks. - /// Log4net logger. + /// Logger. public VirtualEngine( string license, string userFileSystemRootPath, string remoteStorageRootPath, string iconsFolderPath, - string rpcCommunicationChannelName, double syncIntervalMs, - int maxDegreeOfParallelism, - ILog log4net) - : base(license, userFileSystemRootPath, remoteStorageRootPath, iconsFolderPath, rpcCommunicationChannelName, syncIntervalMs, maxDegreeOfParallelism, log4net) + LogFormatter logFormatter) + : base(license, userFileSystemRootPath, remoteStorageRootPath, iconsFolderPath, syncIntervalMs, logFormatter) { - RemoteStorageMonitor = new RemoteStorageMonitor(remoteStorageRootPath, this, log4net); + RemoteStorageMonitor = new RemoteStorageMonitor(remoteStorageRootPath, this, this.Logger); } /// @@ -60,114 +55,40 @@ public override async Task GetFileSystemItemAsync(string userFi } } - //public override IMapping Mapping { get { return new Mapping(this); } } - + /// - public override async Task StartAsync(bool processModified = true, CancellationToken cancellationToken = default) + public override async Task GetMenuCommandAsync(Guid menuGuid) { - await base.StartAsync(processModified, cancellationToken); - RemoteStorageMonitor.Start(); - } + // For this method to be called you need to register a menu command handler. + // See method description for more details. - public override async Task StopAsync() - { - await base.StopAsync(); - RemoteStorageMonitor.Stop(); - } + Logger.LogDebug($"{nameof(IEngine)}.{nameof(GetMenuCommandAsync)}()", menuGuid.ToString()); - /// - public override async Task GetThumbnailAsync(string userFileSystemPath, uint size) - { - // For this method to be called you need to run the Package project. + Guid menuCommandLockGuid = typeof(VirtualDrive.ShellExtension.ContextMenusProvider).GUID; - /* - // Get remote storage ID to read thumbnail from the remote storage. - if (PlaceholderItem.IsPlaceholder(userFileSystemPath)) + if (menuGuid == menuCommandLockGuid) { - PlaceholderItem placeholder = this.Placeholders.GetItem(userFileSystemPath); - byte[] remoteStorageItemId = placeholder.GetRemoteStorageItemId(); + return new MenuCommandLock(this, this.Logger); } - */ - - byte[] thumbnail = ThumbnailExtractor.GetThumbnail(userFileSystemPath, size); - - string thumbnailResult = thumbnail != null ? "Success" : "Not Impl"; - LogMessage($"{nameof(VirtualEngine)}.{nameof(GetThumbnailAsync)}() - {thumbnailResult}", userFileSystemPath); - return thumbnail; + Logger.LogError($"Menu not found", menuGuid.ToString()); + throw new NotImplementedException(); } + /// - public override async Task> GetItemPropertiesAsync(string userFileSystemPath) + public override async Task StartAsync(bool processModified = true, CancellationToken cancellationToken = default) { - // For this method to be called you need to run the Package project. - - //LogMessage($"{nameof(VirtualEngine)}.{nameof(GetItemPropertiesAsync)}()", userFileSystemPath); - - IList props = new List(); - - if (this.Placeholders.TryGetItem(userFileSystemPath, out PlaceholderItem placeholder)) - { - - // Read LockInfo. - if (placeholder.Properties.TryGetValue("LockInfo", out IDataItem propLockInfo)) - { - if (propLockInfo.TryGetValue(out ServerLockInfo lockInfo)) - { - // Get Lock Owner. - FileSystemItemPropertyData propertyLockOwner = new FileSystemItemPropertyData() - { - Id = (int)CustomColumnIds.LockOwnerIcon, - Value = lockInfo.Owner, - IconResource = System.IO.Path.Combine(this.IconsFolderPath, "Locked.ico") - }; - props.Add(propertyLockOwner); - - // Get Lock Expires. - FileSystemItemPropertyData propertyLockExpires = new FileSystemItemPropertyData() - { - Id = (int)CustomColumnIds.LockExpirationDate, - Value = lockInfo.LockExpirationDateUtc.ToString(), - IconResource = System.IO.Path.Combine(this.IconsFolderPath, "Empty.ico") - }; - props.Add(propertyLockExpires); - } - } - - // Read LockMode. - if (placeholder.Properties.TryGetValue("LockMode", out IDataItem propLockMode)) - { - if (propLockMode.TryGetValue(out LockMode lockMode) && lockMode != LockMode.None) - { - FileSystemItemPropertyData propertyLockMode = new FileSystemItemPropertyData() - { - Id = (int)CustomColumnIds.LockScope, - Value = "Locked", - IconResource = System.IO.Path.Combine(this.IconsFolderPath, "Empty.ico") - }; - props.Add(propertyLockMode); - } - } - - // Read ETag. - if (placeholder.Properties.TryGetValue("ETag", out IDataItem propETag)) - { - if (propETag.TryGetValue(out string eTag)) - { - FileSystemItemPropertyData propertyETag = new FileSystemItemPropertyData() - { - Id = (int)CustomColumnIds.ETag, - Value = eTag, - IconResource = System.IO.Path.Combine(this.IconsFolderPath, "Empty.ico") - }; - props.Add(propertyETag); - } - } - } - - return props; + await base.StartAsync(processModified, cancellationToken); + RemoteStorageMonitor.Start(); } + /// + public override async Task StopAsync() + { + await base.StopAsync(); + RemoteStorageMonitor.Stop(); + } private bool disposedValue; diff --git a/Windows/VirtualDrive/VirtualDrive/VirtualFile.cs b/Windows/VirtualDrive/VirtualDrive/VirtualFile.cs index 40ede6a..9859bc7 100644 --- a/Windows/VirtualDrive/VirtualDrive/VirtualFile.cs +++ b/Windows/VirtualDrive/VirtualDrive/VirtualFile.cs @@ -31,13 +31,13 @@ public VirtualFile(string path, byte[] remoteStorageItemId, VirtualEngine engine /// public async Task OpenCompletionAsync(IOperationContext operationContext, IResultContext context, CancellationToken cancellationToken) { - Logger.LogMessage($"{nameof(IFileWindows)}.{nameof(OpenCompletionAsync)}()", UserFileSystemPath, default, operationContext); + Logger.LogDebug($"{nameof(IFileWindows)}.{nameof(OpenCompletionAsync)}()", UserFileSystemPath, default, operationContext); } /// public async Task CloseCompletionAsync(IOperationContext operationContext, IResultContext context, CancellationToken cancellationToken) { - Logger.LogMessage($"{nameof(IFileWindows)}.{nameof(CloseCompletionAsync)}()", UserFileSystemPath, default, operationContext); + Logger.LogDebug($"{nameof(IFileWindows)}.{nameof(CloseCompletionAsync)}()", UserFileSystemPath, default, operationContext); } /// @@ -51,7 +51,7 @@ public async Task ReadAsync(Stream output, long offset, long length, ITransferDa cancellationToken.Register(() => { Logger.LogMessage($"{nameof(IFile)}.{nameof(ReadAsync)}({offset}, {length}) cancelled", UserFileSystemPath, default, operationContext); }); string remoteStoragePath = Mapping.GetRemoteStoragePathById(RemoteStorageItemId); - await using (FileStream stream = System.IO.File.OpenRead(remoteStoragePath)) + using (FileStream stream = System.IO.File.OpenRead(remoteStoragePath)) { stream.Seek(offset, SeekOrigin.Begin); const int bufferSize = 0x500000; // 5Mb. Buffer size must be multiple of 4096 bytes for optimal performance. @@ -122,7 +122,7 @@ public async Task WriteAsync(IFileMetadata fileMetadata, Stream content = null, //} // Upload file content to the remote storage. - await using (FileStream remoteStorageStream = remoteStorageItem.Open(FileMode.Open, FileAccess.Write, FileShare.Delete)) + using (FileStream remoteStorageStream = remoteStorageItem.Open(FileMode.Open, FileAccess.Write, FileShare.Delete)) { await content.CopyToAsync(remoteStorageStream); remoteStorageStream.SetLength(content.Length); diff --git a/Windows/VirtualDrive/VirtualDrive/VirtualFileSystemItem.cs b/Windows/VirtualDrive/VirtualDrive/VirtualFileSystemItem.cs index a89275b..74111ce 100644 --- a/Windows/VirtualDrive/VirtualDrive/VirtualFileSystemItem.cs +++ b/Windows/VirtualDrive/VirtualDrive/VirtualFileSystemItem.cs @@ -75,7 +75,11 @@ public async Task MoveToCompletionAsync(string targetUserFileSystemPath, byte[] if (remoteStorageOldItem is FileInfo) { - (remoteStorageOldItem as FileInfo).MoveTo(remoteStorageNewPath, true); + if (File.Exists(remoteStorageNewPath)) + { + File.Delete(remoteStorageNewPath); + } + (remoteStorageOldItem as FileInfo).MoveTo(remoteStorageNewPath); } else { @@ -141,6 +145,95 @@ public async Task DeleteCompletionAsync(IOperationContext operationContext, IInS } } + /// + public async Task GetThumbnailAsync(uint size) + { + // For this method to be called you need to register a thumbnail handler. + // See method description for more details. + + string remoteStorageItemPath = Mapping.GetRemoteStoragePathById(RemoteStorageItemId); + byte[] thumbnail = ThumbnailExtractor.GetRemoteThumbnail(remoteStorageItemPath, size); + + string thumbnailResult = thumbnail != null ? "Success" : "Not Impl"; + Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(GetThumbnailAsync)}() - {thumbnailResult}", UserFileSystemPath); + + return thumbnail; + } + + + /// + public async Task> GetPropertiesAsync() + { + // For this method to be called you need to register a properties handler. + // See method description for more details. + + Logger.LogDebug($"{nameof(IFileSystemItem)}.{nameof(GetPropertiesAsync)}()", UserFileSystemPath); + + IList props = new List(); + + if (Engine.Placeholders.TryGetItem(UserFileSystemPath, out PlaceholderItem placeholder)) + { + + // Read LockInfo. + if (placeholder.Properties.TryGetValue("LockInfo", out IDataItem propLockInfo)) + { + if (propLockInfo.TryGetValue(out ServerLockInfo lockInfo)) + { + // Get Lock Owner. + FileSystemItemPropertyData propertyLockOwner = new FileSystemItemPropertyData() + { + Id = (int)CustomColumnIds.LockOwnerIcon, + Value = lockInfo.Owner, + IconResource = System.IO.Path.Combine(Engine.IconsFolderPath, "Locked.ico") + }; + props.Add(propertyLockOwner); + + // Get Lock Expires. + FileSystemItemPropertyData propertyLockExpires = new FileSystemItemPropertyData() + { + Id = (int)CustomColumnIds.LockExpirationDate, + Value = lockInfo.LockExpirationDateUtc.ToString(), + IconResource = System.IO.Path.Combine(Engine.IconsFolderPath, "Empty.ico") + }; + props.Add(propertyLockExpires); + } + } + + // Read LockMode. + if (placeholder.Properties.TryGetValue("LockMode", out IDataItem propLockMode)) + { + if (propLockMode.TryGetValue(out LockMode lockMode) && lockMode != LockMode.None) + { + FileSystemItemPropertyData propertyLockMode = new FileSystemItemPropertyData() + { + Id = (int)CustomColumnIds.LockScope, + Value = "Locked", + IconResource = System.IO.Path.Combine(Engine.IconsFolderPath, "Empty.ico") + }; + props.Add(propertyLockMode); + } + } + + // Read ETag. + if (placeholder.Properties.TryGetValue("ETag", out IDataItem propETag)) + { + if (propETag.TryGetValue(out string eTag)) + { + FileSystemItemPropertyData propertyETag = new FileSystemItemPropertyData() + { + Id = (int)CustomColumnIds.ETag, + Value = eTag, + IconResource = System.IO.Path.Combine(Engine.IconsFolderPath, "Empty.ico") + }; + props.Add(propertyETag); + } + } + } + + return props; + } + + /// public Task GetMetadataAsync() { diff --git a/Windows/VirtualDrive/VirtualDrive/VirtualFolder.cs b/Windows/VirtualDrive/VirtualDrive/VirtualFolder.cs index 4aacb95..3f48bbf 100644 --- a/Windows/VirtualDrive/VirtualDrive/VirtualFolder.cs +++ b/Windows/VirtualDrive/VirtualDrive/VirtualFolder.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.IO.Enumeration; using System.Linq; using System.Text; using System.Threading; @@ -39,13 +38,13 @@ public async Task CreateFileAsync(IFileMetadata fileMetadata, Stream con FileInfo remoteStorageItem = new FileInfo(Path.Combine(remoteStoragePath, fileMetadata.Name)); // Upload file content to the remote storage. - await using (FileStream remoteStorageStream = remoteStorageItem.Open(FileMode.CreateNew, FileAccess.Write, FileShare.Delete)) + using (FileStream remoteStorageStream = remoteStorageItem.Open(FileMode.CreateNew, FileAccess.Write, FileShare.Delete)) { if (content != null) { try { - await content.CopyToAsync(remoteStorageStream, cancellationToken); + await content.CopyToAsync(remoteStorageStream); } catch (OperationCanceledException) { @@ -116,12 +115,17 @@ public async Task GetChildrenAsync(string pattern, IOperationContext operationCo var watch = System.Diagnostics.Stopwatch.StartNew(); IEnumerable remoteStorageChildren = await EnumerateChildrenAsync(pattern, cancellationToken); + foreach(FileSystemItemMetadataExt child in remoteStorageChildren) + { + Logger.LogDebug("Creating", child.Name); + } + long totalCount = remoteStorageChildren.Count(); // To signal that the children enumeration is completed // always call ReturnChildren(), even if the folder is empty. await resultContext.ReturnChildrenAsync(remoteStorageChildren.ToArray(), totalCount); - Engine.LogMessage($"Listed {totalCount} item(s). Took: {watch.ElapsedMilliseconds:N0}ms", UserFileSystemPath); + Engine.LogDebug($"Listed {totalCount} item(s). Took: {watch.ElapsedMilliseconds:N0}ms", UserFileSystemPath); // Save data that you wish to display in custom columns here. //foreach (FileSystemItemMetadataExt itemMetadata in userFileSystemChildren) diff --git a/Windows/VirtualDrive/VirtualDrive/appsettings.json b/Windows/VirtualDrive/VirtualDrive/appsettings.json index d84c0bb..5b243b7 100644 --- a/Windows/VirtualDrive/VirtualDrive/appsettings.json +++ b/Windows/VirtualDrive/VirtualDrive/appsettings.json @@ -7,7 +7,7 @@ // License to activate the IT Hit User File System Engine. // Set the license content directly as value. Make sure to escape quotes: \": - // "UserFileSystemLicense": " - /// Remote storage path. + /// Remote storage root path. /// private readonly string remoteStorageRootPath; /// - /// User file system path. + /// User file system root path. /// private readonly string userFileSystemRootPath; /// - /// Creates a Mapping. + /// Creates an instance of this class. /// /// Remote storage path. /// User file system path. @@ -46,10 +46,10 @@ public Mapping(string userFileSystemRootPath, string remoteStorageRootPath) public string ReverseMapPath(string remoteStorageUri) { // Get path relative to the virtual root. - string relativePath = Path.TrimEndingDirectorySeparator(remoteStorageUri).Substring( - Path.TrimEndingDirectorySeparator(remoteStorageRootPath).Length); + string relativePath = remoteStorageUri.TrimEnd(Path.DirectorySeparatorChar).Substring( + remoteStorageRootPath.TrimEnd(Path.DirectorySeparatorChar).Length); - string path = $"{Path.TrimEndingDirectorySeparator(userFileSystemRootPath)}{relativePath}"; + string path = $"{userFileSystemRootPath.TrimEnd(Path.DirectorySeparatorChar)}{relativePath}"; return path; } diff --git a/Windows/VirtualFileSystem/Program.cs b/Windows/VirtualFileSystem/Program.cs index c67c9eb..c3129d1 100644 --- a/Windows/VirtualFileSystem/Program.cs +++ b/Windows/VirtualFileSystem/Program.cs @@ -1,16 +1,12 @@ using System; using System.Diagnostics; using System.IO; -using System.Linq; using System.Reflection; -using System.Runtime.InteropServices; using System.Threading.Tasks; -using Windows.Storage; using Microsoft.Extensions.Configuration; using log4net; -using log4net.Appender; -using log4net.Config; + using ITHit.FileSystem; using ITHit.FileSystem.Windows; using ITHit.FileSystem.Samples.Common; @@ -24,63 +20,53 @@ public class Program /// /// Application settings. /// - public static AppSettings Settings; - - /// - /// Process modified files and folders. - /// - public static bool ProcessModified = true; - - /// - /// Opens remote and virtual files folders - /// - public static bool OpenTestEnvironment = true; + private static AppSettings Settings; /// /// Log4Net logger. /// private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - /// - /// Log file path. - /// - private static string LogFilePath; - /// /// Processes OS file system calls, /// synchronizes user file system to remote storage. /// public static VirtualEngine Engine; + /// + /// Outputs logging information. + /// + private static LogFormatter logFormatter; + public static async Task Main(string[] args) { // Load Settings. - IConfiguration configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json", false, true).Build(); - Settings = configuration.ReadSettings(); + Settings = new ConfigurationBuilder().AddJsonFile("appsettings.json", false, true).Build().ReadSettings(); - // Configure log4net and set log file path. - LogFilePath = ConfigureLogger(); + logFormatter = new LogFormatter(log, Settings.AppID); - PrintHelp(); + try + { + // Log environment description. + logFormatter.PrintEnvironmentDescription(); - // Register sync root and create app folders. - await RegisterSyncRootAsync(); + // Register sync root and create app folders. + await RegisterSyncRootAsync(); - // Log indexed state. - StorageFolder userFileSystemRootFolder = await StorageFolder.GetFolderFromPathAsync(Settings.UserFileSystemRootPath); - log.Info($"\nIndexed state: {(await userFileSystemRootFolder.GetIndexedStateAsync())}\n"); + // Log indexing state. Sync root must be indexed. + await logFormatter.PrintIndexingStateAsync(Settings.UserFileSystemRootPath); - Logger.PrintHeader(log); + // Log console commands. + logFormatter.PrintHelp(); + // Log logging columns headers. + logFormatter.PrintHeader(); - using (Engine = new VirtualEngine( + using (Engine = new VirtualEngine( Settings.UserFileSystemLicense, Settings.UserFileSystemRootPath, Settings.RemoteStorageRootPath, - Settings.MaxDegreeOfParallelism, - log)) - { - try + logFormatter)) { // Set the remote storage item ID for the root item. It will be passed to the IEngine.GetFileSystemItemAsync() // method as a remoteStorageItemId parameter when a root folder is requested. @@ -88,7 +74,7 @@ public static async Task Main(string[] args) Engine.Placeholders.GetRootItem().SetRemoteStorageItemId(itemId); // Start processing OS file system calls. - await Engine.StartAsync(ProcessModified); + await Engine.StartAsync(); #if DEBUG // Opens Windows File Manager with user file system folder and remote storage folder. @@ -97,63 +83,29 @@ public static async Task Main(string[] args) // Keep this application running and reading user input. await ProcessUserInputAsync(); } - catch (Exception ex) - { - log.Error(ex); - await ProcessUserInputAsync(); - } } - } - - /// - /// Configures log4net logger. - /// - /// Log file path. - private static string ConfigureLogger() - { - // Load Log4Net for net configuration. - var logRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); - XmlConfigurator.Configure(logRepository, new FileInfo(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "log4net.config"))); - - // Update log file path for msix package. - RollingFileAppender rollingFileAppender = logRepository.GetAppenders().Where(p => p.GetType() == typeof(RollingFileAppender)).FirstOrDefault() as RollingFileAppender; - if (rollingFileAppender != null && rollingFileAppender.File.Contains("WindowsApps")) + catch (Exception ex) { - rollingFileAppender.File = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), Settings.AppID, - Path.GetFileName(rollingFileAppender.File)); + log.Error(ex); + await ProcessUserInputAsync(); } - return rollingFileAppender?.File; - } - - private static void PrintHelp() - { - log.Info($"\n{"AppID:",-15} {Settings.AppID}"); - log.Info($"\n{"Engine version:",-15} {typeof(IEngine).Assembly.GetName().Version}"); - log.Info($"\n{"OS version:",-15} {RuntimeInformation.OSDescription}"); - log.Info($"\n{"Env version:",-15} {RuntimeInformation.FrameworkDescription} {IntPtr.Size * 8}bit."); - log.Info("\n\nPress Esc to unregister file system, delete all files/folders and exit (simulate uninstall)."); - log.Info("\nPress Spacebar to exit without unregistering (simulate reboot)."); - log.Info("\nPress 'e' to start/stop the Engine and all sync services."); - //log.Info("\nPress 's' to start/stop full synchronization service."); - log.Info("\nPress 'm' to start/stop remote storage monitor."); - log.Info($"\nPress 'l' to open log file. ({LogFilePath})"); - log.Info($"\nPress 'b' to submit support tickets, report bugs, suggest features. (https://userfilesystem.com/support/)"); - log.Info("\n----------------------\n"); } private static async Task ProcessUserInputAsync() { do { - switch (Console.ReadKey(true).KeyChar) + ConsoleKeyInfo keyInfo = Console.ReadKey(true); + + switch (keyInfo.Key) { - case (char)ConsoleKey.F1: - case 'h': + case ConsoleKey.F1: + case ConsoleKey.H: // Print help info. - PrintHelp(); + logFormatter.PrintHelp(); break; - case 'e': + case ConsoleKey.E: // Start/stop the Engine and all sync services. if (Engine.State == EngineState.Running) { @@ -165,13 +117,18 @@ private static async Task ProcessUserInputAsync() } break; - case 'm': + case ConsoleKey.D: + // Enables/disables debug logging. + logFormatter.DebugLoggingEnabled = !logFormatter.DebugLoggingEnabled; + break; + + case ConsoleKey.M: // Start/stop remote storage monitor. if (Engine.RemoteStorageMonitor.SyncState == SynchronizationState.Disabled) { if (Engine.State != EngineState.Running) { - Engine.RemoteStorageMonitor.LogError("Failed to start. The Engine must be running."); + Engine.RemoteStorageMonitor.Logger.LogError("Failed to start. The Engine must be running."); break; } Engine.RemoteStorageMonitor.Start(); @@ -182,16 +139,16 @@ private static async Task ProcessUserInputAsync() } break; - case 'l': + case ConsoleKey.L: // Open log file. - ProcessStartInfo psiLog = new ProcessStartInfo(LogFilePath); + ProcessStartInfo psiLog = new ProcessStartInfo(logFormatter.LogFilePath); psiLog.UseShellExecute = true; using (Process.Start(psiLog)) { } break; - case 'b': + case ConsoleKey.B: // Submit support tickets, report bugs, suggest features. ProcessStartInfo psiSupport = new ProcessStartInfo("https://www.userfilesystem.com/support/"); psiSupport.UseShellExecute = true; @@ -200,15 +157,7 @@ private static async Task ProcessUserInputAsync() } break; - case 'q': - // Unregister during programm uninstall. - Engine.Dispose(); - await UnregisterSyncRootAsync(); - log.Info("\nAll empty file and folder placeholders are deleted. Hydrated placeholders are converted to regular files / folders.\n"); - return; - - case (char)ConsoleKey.Escape: - case 'Q': + case ConsoleKey.Escape: if (Engine.State == EngineState.Running) { await Engine.StopAsync(); @@ -221,7 +170,7 @@ private static async Task ProcessUserInputAsync() await CleanupAppFoldersAsync(); return; - case (char)ConsoleKey.Spacebar: + case ConsoleKey.Spacebar: if (Engine.State == EngineState.Running) { await Engine.StopAsync(); @@ -233,7 +182,6 @@ private static async Task ProcessUserInputAsync() break; } - } while (true); } @@ -248,7 +196,7 @@ private static async Task RegisterSyncRootAsync() { if (!await Registrar.IsRegisteredAsync(Settings.UserFileSystemRootPath)) { - log.Info($"\nRegistering {Settings.UserFileSystemRootPath} sync root."); + log.Info($"\n\nRegistering {Settings.UserFileSystemRootPath} sync root."); Directory.CreateDirectory(Settings.UserFileSystemRootPath); await Registrar.RegisterAsync(SyncRootId, Settings.UserFileSystemRootPath, Settings.ProductName, @@ -272,13 +220,13 @@ await Registrar.RegisterAsync(SyncRootId, Settings.UserFileSystemRootPath, Setti /// In the case of a regular installer (msi) call this method during uninstall. /// /// - public static async Task UnregisterSyncRootAsync() + private static async Task UnregisterSyncRootAsync() { log.Info($"\n\nUnregistering {Settings.UserFileSystemRootPath} sync root."); await Registrar.UnregisterAsync(SyncRootId); } - public static async Task CleanupAppFoldersAsync() + private static async Task CleanupAppFoldersAsync() { log.Info("\nDeleting all file and folder placeholders.\n"); try diff --git a/Windows/VirtualFileSystem/RemoteStorageMonitor.cs b/Windows/VirtualFileSystem/RemoteStorageMonitor.cs index d2aab18..87c7105 100644 --- a/Windows/VirtualFileSystem/RemoteStorageMonitor.cs +++ b/Windows/VirtualFileSystem/RemoteStorageMonitor.cs @@ -15,15 +15,15 @@ namespace VirtualFileSystem { /// /// Monitors changes in the remote storage, notifies the client and updates the user file system. - /// If any file or folder is is modified, created, delated, renamed or attributes changed in the remote storage, - /// triggers an event with information about changes. + /// If any file or folder is modified, created, delated, renamed or attributes changed in the remote storage, + /// this class triggers an event with information about changes. /// /// /// Here, for demo purposes we simulate server by monitoring source file path using FileWatchWrapper. /// In your application, instead of using FileWatchWrapper, you will connect to your remote storage using web sockets /// or use any other technology to get notifications about changes in your remote storage. /// - internal class RemoteStorageMonitor : Logger, IDisposable + public class RemoteStorageMonitor : IDisposable { /// /// Current synchronization state. @@ -36,6 +36,11 @@ public virtual SynchronizationState SyncState } } + /// + /// Logger. + /// + public readonly ILogger Logger; + /// /// Engine instance. We will call methods /// to update user file system when any data is changed in the remote storage. @@ -54,10 +59,12 @@ public virtual SynchronizationState SyncState /// /// Remote storage path. Folder that contains source files to monitor changes. /// Virtual drive to send notifications about changes in the remote storage. - /// Logger. - internal RemoteStorageMonitor(string remoteStorageRootPath, Engine engine, ILog log) : base("Remote Storage Monitor", log) + /// Logger. + internal RemoteStorageMonitor(string remoteStorageRootPath, Engine engine, ILogger logger) { this.engine = engine; + this.Logger = logger.CreateLogger("Remote Storage Monitor"); + mapping = new Mapping(engine.Path, remoteStorageRootPath); watcher.IncludeSubdirectories = true; @@ -78,7 +85,7 @@ internal RemoteStorageMonitor(string remoteStorageRootPath, Engine engine, ILog internal void Start() { watcher.EnableRaisingEvents = true; - LogMessage($"Started", watcher.Path); + Logger.LogMessage($"Started", watcher.Path); } /// @@ -87,7 +94,7 @@ internal void Start() internal void Stop() { watcher.EnableRaisingEvents = false; - LogMessage($"Stopped", watcher.Path); + Logger.LogMessage($"Stopped", watcher.Path); } /// @@ -96,7 +103,7 @@ internal void Stop() /// In this method we create a new file/folder in the user file system. private async void CreatedAsync(object sender, FileSystemEventArgs e) { - LogMessage(e.ChangeType.ToString(), e.FullPath); + Logger.LogMessage(e.ChangeType.ToString(), e.FullPath); string remoteStoragePath = e.FullPath; try { @@ -116,14 +123,14 @@ private async void CreatedAsync(object sender, FileSystemEventArgs e) { // Because of the on-demand population, the parent folder placeholder may not exist in the user file system // or the folder may be offline. In this case the IServerNotifications.CreateAsync() call is ignored. - LogMessage($"Created succesefully", userFileSystemPath); + Logger.LogMessage($"Created succesefully", userFileSystemPath); } } } } catch (Exception ex) { - LogError($"{e.ChangeType} failed", remoteStoragePath, null, ex); + Logger.LogError($"{e.ChangeType} failed", remoteStoragePath, null, ex); } } @@ -136,7 +143,7 @@ private async void CreatedAsync(object sender, FileSystemEventArgs e) /// private async void ChangedAsync(object sender, FileSystemEventArgs e) { - LogMessage(e.ChangeType.ToString(), e.FullPath); + Logger.LogMessage(e.ChangeType.ToString(), e.FullPath); string remoteStoragePath = e.FullPath; string userFileSystemPath = null; try @@ -154,18 +161,18 @@ private async void ChangedAsync(object sender, FileSystemEventArgs e) { // Because of the on-demand population the file or folder placeholder may not exist in the user file system. // In this case the IServerNotifications.UpdateAsync() call is ignored. - LogMessage("Updated succesefully", userFileSystemPath); + Logger.LogMessage("Updated succesefully", userFileSystemPath); } } } catch (IOException ex) { // The file is blocked in the user file system. This is a normal behaviour. - LogMessage(ex.Message); + Logger.LogMessage(ex.Message); } catch (Exception ex) { - LogError($"{e.ChangeType} failed", remoteStoragePath, null, ex); + Logger.LogError($"{e.ChangeType} failed", remoteStoragePath, null, ex); } } @@ -175,7 +182,7 @@ private async void ChangedAsync(object sender, FileSystemEventArgs e) /// In this method we delete corresponding file/folder in user file system. private async void DeletedAsync(object sender, FileSystemEventArgs e) { - LogMessage(e.ChangeType.ToString(), e.FullPath); + Logger.LogMessage(e.ChangeType.ToString(), e.FullPath); string remoteStoragePath = e.FullPath; try { @@ -189,13 +196,13 @@ private async void DeletedAsync(object sender, FileSystemEventArgs e) { // Because of the on-demand population the file or folder placeholder may not exist in the user file system. // In this case the IServerNotifications.DeleteAsync() call is ignored. - LogMessage("Deleted succesefully", userFileSystemPath); + Logger.LogMessage("Deleted succesefully", userFileSystemPath); } } } catch (Exception ex) { - LogError($"{e.ChangeType} failed", remoteStoragePath, null, ex); + Logger.LogError($"{e.ChangeType} failed", remoteStoragePath, null, ex); } } @@ -205,7 +212,7 @@ private async void DeletedAsync(object sender, FileSystemEventArgs e) /// In this method we rename corresponding file/folder in user file system. private async void RenamedAsync(object sender, RenamedEventArgs e) { - LogMessage("Renamed", e.OldFullPath, e.FullPath); + Logger.LogMessage("Renamed", e.OldFullPath, e.FullPath); string remoteStorageOldPath = e.OldFullPath; string remoteStorageNewPath = e.FullPath; try @@ -221,19 +228,19 @@ private async void RenamedAsync(object sender, RenamedEventArgs e) { // Because of the on-demand population the file or folder placeholder may not exist in the user file system. // In this case the IServerNotifications.MoveToAsync() call is ignored. - LogMessage("Renamed succesefully", userFileSystemOldPath, userFileSystemNewPath); + Logger.LogMessage("Renamed succesefully", userFileSystemOldPath, userFileSystemNewPath); } } } catch (Exception ex) { - LogError($"{e.ChangeType} failed", remoteStorageOldPath, remoteStorageNewPath, ex); + Logger.LogError($"{e.ChangeType} failed", remoteStorageOldPath, remoteStorageNewPath, ex); } } private void Error(object sender, ErrorEventArgs e) { - LogError(null, null, null, e.GetException()); + Logger.LogError(null, null, null, e.GetException()); } @@ -246,7 +253,7 @@ protected virtual void Dispose(bool disposing) if (disposing) { watcher.Dispose(); - LogMessage($"Disposed"); + Logger.LogMessage($"Disposed"); } // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. @@ -278,9 +285,9 @@ public void Dispose() /// File or folder 1 to compare. /// File or folder 2 to compare. /// True if file is modified. False - otherwise. - private static bool IsModified(string filePath1, string filePath2) + internal static bool IsModified(string filePath1, string filePath2) { - if(FsPath.IsFolder(filePath1) && FsPath.IsFolder(filePath2)) + if (FsPath.IsFolder(filePath1) && FsPath.IsFolder(filePath2)) { return false; } @@ -301,6 +308,7 @@ private static bool IsModified(string filePath1, string filePath2) byte[] hash2; using (var alg = System.Security.Cryptography.MD5.Create()) { + // This code for demo purposes only. We do not block files for writing, which is required by some apps, for example by AutoCAD. using (FileStream stream = new FileStream(filePath1, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete)) { hash1 = alg.ComputeHash(stream); @@ -314,7 +322,7 @@ private static bool IsModified(string filePath1, string filePath2) return !hash1.SequenceEqual(hash2); } } - catch(IOException) + catch (IOException) { // One of the files is blocked. Can not compare files and start sychronization. return false; diff --git a/Windows/VirtualFileSystem/VirtualEngine.cs b/Windows/VirtualFileSystem/VirtualEngine.cs index bd974ba..bb5f50b 100644 --- a/Windows/VirtualFileSystem/VirtualEngine.cs +++ b/Windows/VirtualFileSystem/VirtualEngine.cs @@ -7,6 +7,7 @@ using ITHit.FileSystem.Samples.Common.Windows; using System.Threading; +using System; namespace VirtualFileSystem { @@ -14,11 +15,6 @@ namespace VirtualFileSystem /// public class VirtualEngine : EngineWindows { - /// - /// Logger. - /// - private ILogger logger; - /// /// Monitors changes in the remote storage, notifies the client and updates the user file system. /// @@ -33,23 +29,23 @@ public class VirtualEngine : EngineWindows /// Your file system tree will be located under this folder. /// /// A maximum number of concurrent tasks. - /// Logger. - public VirtualEngine(string license, string userFileSystemRootPath, string remoteStorageRootPath, int maxDegreeOfParallelism, ILog log) : - base(license, userFileSystemRootPath, maxDegreeOfParallelism) + /// Log4net Logger. + public VirtualEngine(string license, string userFileSystemRootPath, string remoteStorageRootPath, LogFormatter logFormatter) : + base(license, userFileSystemRootPath) { - logger = new Logger("File System Engine", log); // We want our file system to run regardless of any errors. // If any request to file system fails in user code or in Engine itself we continue processing. ThrowExceptions = false; StateChanged += Engine_StateChanged; - Error += Engine_Error; - Message += Engine_Message; + Error += logFormatter.LogError; + Message += logFormatter.LogMessage; + Debug += logFormatter.LogDebug; - RemoteStorageMonitor = new RemoteStorageMonitor(remoteStorageRootPath, this, log); + RemoteStorageMonitor = new RemoteStorageMonitor(remoteStorageRootPath, this, this.Logger); } - + /// public override async Task GetFileSystemItemAsync(string userFileSystemPath, FileSystemItemType itemType, byte[] remoteStorageItemId = null) { @@ -63,6 +59,15 @@ public override async Task GetFileSystemItemAsync(string userFi } } + /// + public override async Task GetMenuCommandAsync(Guid menuGuid) + { + // For this method to be called you need to register a menu command handler. + // See method description for more details. + + throw new NotImplementedException(); + } + /// public override async Task StartAsync(bool processModified = true, CancellationToken cancellationToken = default) { @@ -76,16 +81,6 @@ public override async Task StopAsync() RemoteStorageMonitor.Stop(); } - private void Engine_Message(IEngine sender, EngineMessageEventArgs e) - { - logger.LogMessage(e.Message, e.SourcePath, e.TargetPath, e.OperationContext); - } - - private void Engine_Error(IEngine sender, EngineErrorEventArgs e) - { - logger.LogError(e.Message, e.SourcePath, e.TargetPath, e.Exception, e.OperationContext); - } - /// /// Show status change. /// diff --git a/Windows/VirtualFileSystem/VirtualFile.cs b/Windows/VirtualFileSystem/VirtualFile.cs index 5c0294c..bd5f44c 100644 --- a/Windows/VirtualFileSystem/VirtualFile.cs +++ b/Windows/VirtualFileSystem/VirtualFile.cs @@ -31,14 +31,14 @@ public VirtualFile(string path, byte[] remoteStorageItemId, ILogger logger) : ba /// public async Task OpenCompletionAsync(IOperationContext operationContext, IResultContext context, CancellationToken cancellationToken) { - Logger.LogMessage($"{nameof(IFileWindows)}.{nameof(OpenCompletionAsync)}()", UserFileSystemPath, default, operationContext); + Logger.LogDebug($"{nameof(IFileWindows)}.{nameof(OpenCompletionAsync)}()", UserFileSystemPath, default, operationContext); } /// public async Task CloseCompletionAsync(IOperationContext operationContext, IResultContext context, CancellationToken cancellationToken) { - Logger.LogMessage($"{nameof(IFileWindows)}.{nameof(CloseCompletionAsync)}()", UserFileSystemPath, default, operationContext); + Logger.LogDebug($"{nameof(IFileWindows)}.{nameof(CloseCompletionAsync)}()", UserFileSystemPath, default, operationContext); } @@ -52,7 +52,7 @@ public async Task ReadAsync(Stream output, long offset, long length, ITransferDa Logger.LogMessage($"{nameof(IFile)}.{nameof(ReadAsync)}({offset}, {length})", UserFileSystemPath, default, operationContext); string remoteStoragePath = Mapping.GetRemoteStoragePathById(RemoteStorageItemId); - await using (FileStream stream = System.IO.File.OpenRead(remoteStoragePath)) + using (FileStream stream = System.IO.File.OpenRead(remoteStoragePath)) { stream.Seek(offset, SeekOrigin.Begin); const int bufferSize = 0x500000; // 5Mb. Buffer size must be multiple of 4096 bytes for optimal performance. @@ -93,7 +93,7 @@ public async Task WriteAsync(IFileMetadata fileMetadata, Stream content = null, if (content != null) { // Upload remote storage file content. - await using (FileStream remoteStorageStream = remoteStorageItem.Open(FileMode.Open, FileAccess.Write, FileShare.Delete)) + using (FileStream remoteStorageStream = remoteStorageItem.Open(FileMode.Open, FileAccess.Write, FileShare.Delete)) { await content.CopyToAsync(remoteStorageStream); remoteStorageStream.SetLength(content.Length); diff --git a/Windows/VirtualFileSystem/VirtualFileSystem.csproj b/Windows/VirtualFileSystem/VirtualFileSystem.csproj index 52398eb..39f2aa1 100644 --- a/Windows/VirtualFileSystem/VirtualFileSystem.csproj +++ b/Windows/VirtualFileSystem/VirtualFileSystem.csproj @@ -1,8 +1,7 @@  Exe - net6.0-windows10.0.18362 - 10.0.18362.0 + net48;net5.0-windows10.0.19041.0 IT Hit LTD. IT Hit LTD. Virtual File System diff --git a/Windows/VirtualFileSystem/VirtualFileSystemItem.cs b/Windows/VirtualFileSystem/VirtualFileSystemItem.cs index cc5474c..60ead49 100644 --- a/Windows/VirtualFileSystem/VirtualFileSystemItem.cs +++ b/Windows/VirtualFileSystem/VirtualFileSystemItem.cs @@ -43,7 +43,7 @@ public VirtualFileSystemItem(string userFileSystemPath, byte[] remoteStorageItem } - /// + /// public async Task MoveToAsync(string targetUserFileSystemPath, byte[] targetFolderRemoteStorageItemId, IOperationContext operationContext = null, IConfirmationResultContext resultContext = null, CancellationToken cancellationToken = default) { string userFileSystemNewPath = targetUserFileSystemPath; @@ -68,7 +68,11 @@ public async Task MoveToCompletionAsync(string targetUserFileSystemPath, byte[] if (remoteStorageOldItem is FileInfo) { - (remoteStorageOldItem as FileInfo).MoveTo(remoteStorageNewPath, true); + if(File.Exists(remoteStorageNewPath)) + { + File.Delete(remoteStorageNewPath); + } + (remoteStorageOldItem as FileInfo).MoveTo(remoteStorageNewPath); } else { @@ -81,7 +85,7 @@ public async Task MoveToCompletionAsync(string targetUserFileSystemPath, byte[] - /// + /// public async Task DeleteAsync(IOperationContext operationContext = null, IConfirmationResultContext resultContext = null, CancellationToken cancellationToken = default) { Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(DeleteAsync)}()", this.UserFileSystemPath, default, operationContext); @@ -133,11 +137,29 @@ public async Task DeleteCompletionAsync(IOperationContext operationContext = nul } - /// + /// + public Task GetThumbnailAsync(uint size) + { + // For this method to be called you need to register a thumbnail handler. + // See method description for more details. + + throw new NotImplementedException(); + } + + /// + public async Task> GetPropertiesAsync() + { + // For this method to be called you need to register a properties handler. + // See method description for more details. + + throw new NotImplementedException(); + } + + /// public Task GetMetadataAsync() { // Return IFileMetadata for a file, IFolderMetadata for a folder. throw new NotImplementedException(); - } + } } } diff --git a/Windows/VirtualFileSystem/VirtualFolder.cs b/Windows/VirtualFileSystem/VirtualFolder.cs index 5010c1d..2abfbdb 100644 --- a/Windows/VirtualFileSystem/VirtualFolder.cs +++ b/Windows/VirtualFileSystem/VirtualFolder.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.IO.Enumeration; using System.Linq; using System.Text; using System.Threading; @@ -37,7 +36,7 @@ public async Task CreateFileAsync(IFileMetadata fileMetadata, Stream con FileInfo remoteStorageNewItem = new FileInfo(Path.Combine(remoteStoragePath, fileMetadata.Name)); // Create remote storage file. - await using (FileStream remoteStorageStream = remoteStorageNewItem.Open(FileMode.CreateNew, FileAccess.Write, FileShare.Delete)) + using (FileStream remoteStorageStream = remoteStorageNewItem.Open(FileMode.CreateNew, FileAccess.Write, FileShare.Delete)) { // Upload content. Note that if the file is blocked - content parameter is null. if (content != null) diff --git a/Windows/VirtualFileSystem/appsettings.json b/Windows/VirtualFileSystem/appsettings.json index b076d14..1621d89 100644 --- a/Windows/VirtualFileSystem/appsettings.json +++ b/Windows/VirtualFileSystem/appsettings.json @@ -7,7 +7,7 @@ // License to activate the IT Hit User File System Engine. // Set the license content directly as value. Make sure to escape quotes: \": - // "UserFileSystemLicense": " + @@ -55,7 +56,7 @@ - + @@ -68,13 +69,14 @@ - + + diff --git a/Windows/WebDAVDrive/WebDAVDrive.Package/WebDAVDrive.Package.wapproj b/Windows/WebDAVDrive/WebDAVDrive.Package/WebDAVDrive.Package.wapproj index 4bf9522..fb90e7c 100644 --- a/Windows/WebDAVDrive/WebDAVDrive.Package/WebDAVDrive.Package.wapproj +++ b/Windows/WebDAVDrive/WebDAVDrive.Package/WebDAVDrive.Package.wapproj @@ -43,8 +43,8 @@ {86767A2F-1559-4DFB-925D-B8E7FCDE74CA} - 10.0.18362.0 - 10.0.18362.0 + 10.0.19041.0 + 10.0.19041.0 en-US false ..\WebDAVDrive\WebDAVDrive.csproj @@ -65,11 +65,10 @@ - + - True diff --git a/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/ContextMenusProvider.cs b/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/ContextMenusProvider.cs index 53a8768..f13b495 100644 --- a/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/ContextMenusProvider.cs +++ b/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/ContextMenusProvider.cs @@ -1,13 +1,5 @@ -using ITHit.FileSystem.Samples.Common.Windows.Rpc; -using ITHit.FileSystem.Samples.Common.Windows.Rpc.Generated; -using ITHit.FileSystem.Samples.Common.Windows.ShellExtension; -using ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Interop; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Runtime.InteropServices; -using System.Threading.Tasks; +using ITHit.FileSystem.Windows.ShellExtension; namespace WebDAVDrive.ShellExtension { @@ -17,114 +9,8 @@ namespace WebDAVDrive.ShellExtension [ComVisible(true)] [ProgId("WebDAVDrive.ContextMenusProvider")] [Guid("A22EBD03-343E-433C-98DF-372C6B3A1538")] - public class ContextMenusProvider : ContextMenusProviderBase + public class ContextMenusProvider : CloudFilesContextMenuVerbRpcBase { - public const string LockCommandIcon = "Locked.ico"; - public const string UnlockCommandIcon = "Unlocked.ico"; - /// - /// Selected items status. True - if all items are locked. False - if all items are unlocked. - /// - private bool isLocked = true; - - /// - /// Gets menu title. - /// - /// List of selected items. - /// Menu title string. - public override async Task GetMenuTitleAsync(IEnumerable filesPath) - { - isLocked = await GetLockStatusAsync(filesPath); - - bool multyFiles = filesPath.Count() > 1; - - string action = isLocked ? "Unlock " : "Lock "; - string objects = multyFiles ? "files" : "file"; - - return action + objects; - } - - /// - /// Sets files lock status. - /// - /// List of selected items. - public override async Task InvokeMenuCommandAsync(IEnumerable filesPath) - { - if (!filesPath.Any()) - throw new NotImplementedException(); - - GrpcClient grpcClient = new GrpcClient(ShellExtensionConfiguration.AppSettings.RpcCommunicationChannelName); - - ItemsStatusList itemStatusList = new ItemsStatusList(); - - foreach (string path in filesPath) - { - itemStatusList.FilesStatus.Add(path, !isLocked); - } - - await grpcClient.RpcClient.SetLockStatusAsync(itemStatusList); - - await Task.CompletedTask; - } - - /// - /// Gets menu state - visible or hidden, depending on the selected items lock state. - /// - /// List of selected items. - /// Item state. - public override async Task GetMenuStateAsync(IEnumerable filesPath) - { - try - { - isLocked = await GetLockStatusAsync(filesPath); - - return EXPCMDSTATE.ECS_ENABLED; - } - catch (NotImplementedException) - { - return EXPCMDSTATE.ECS_HIDDEN; - } - } - - /// - /// Returns path to icon file or resource. - /// - /// List of selected items. - /// Path to icon file or resource. - public override async Task GetIconAsync(IEnumerable filesPath) - { - string iconName = isLocked ? UnlockCommandIcon : LockCommandIcon; - string iconPath = Path.Combine(Path.GetDirectoryName(typeof(ContextMenusProvider).Assembly.Location), iconName); - - return iconPath; - } - - /// - /// Calls main application to get files lock state and checks if all items have the same state. - /// - /// List of items to get state for. - /// True if all items has locked state. False if all items has unlocked state. - private async Task GetLockStatusAsync(IEnumerable filesPath) - { - if (!filesPath.Any()) - throw new NotImplementedException(); - - GrpcClient grpcClient = new GrpcClient(ShellExtensionConfiguration.AppSettings.RpcCommunicationChannelName); - - var request = new ItemsPathList(); - request.Files.AddRange(filesPath); - - ItemsStatusList filesStatus = await grpcClient.RpcClient.GetLockStatusAsync(request); - - if (filesStatus.FilesStatus.Count() != filesPath.Count()) - throw new NotImplementedException(); - - bool allHasSameValue = filesStatus.FilesStatus.Values.Distinct().Count() == 1; - if (!allHasSameValue) - throw new NotImplementedException(); - - bool lockStatus = filesStatus.FilesStatus.Values.First(); - return lockStatus; - } } } diff --git a/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/CustomStateProvider.cs b/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/CustomStateProvider.cs new file mode 100644 index 0000000..8d1d6ff --- /dev/null +++ b/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/CustomStateProvider.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; +using ITHit.FileSystem.Windows.ShellExtension; + + +namespace WebDAVDrive.ShellExtension +{ + /// + /// Implements custom state provider for virtual drive. + /// Displays custom colums and custom state icons in Status column in Windows Explorer. + /// + [ComVisible(true)] + [ProgId("VirtualDrive.CustomStateProvider")] + [Guid("754F334F-095C-46CD-B033-B2C0523D2829")] + public class CustomStateProvider : CustomStateHandlerRpcBase + { + + } +} diff --git a/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/Program.cs b/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/Program.cs index bd03adf..c99146e 100644 --- a/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/Program.cs +++ b/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/Program.cs @@ -1,10 +1,10 @@ using System; using System.Threading.Tasks; -using Microsoft.Extensions.Configuration; -using ITHit.FileSystem.Samples.Common.Windows.ShellExtension; -using ITHit.FileSystem.Samples.Common.Windows.ShellExtension.ComInfrastructure; -using ITHit.FileSystem.Samples.Common; using System.Diagnostics; +using Windows.Storage.Provider; + +using ITHit.FileSystem.Windows.ShellExtension.ComInfrastructure; + namespace WebDAVDrive.ShellExtension { @@ -12,19 +12,13 @@ class Program { static async Task Main(string[] args) { - // Load and initialize settings. - var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json", false, true).Build(); - Settings settings = new Settings(); - configuration.Bind(settings); - - ShellExtensionConfiguration.Initialize(settings); - try { using (var server = new LocalServer()) { - server.RegisterClass(typeof(ThumbnailProvider).GUID); - server.RegisterClass(typeof(ContextMenusProvider).GUID); + server.RegisterClass(); + server.RegisterClass(); + server.RegisterWinRTClass(); await server.Run(); } diff --git a/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/ThumbnailProvider.cs b/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/ThumbnailProvider.cs index 058be1b..0d38f08 100644 --- a/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/ThumbnailProvider.cs +++ b/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/ThumbnailProvider.cs @@ -1,38 +1,19 @@ -using System; using System.Runtime.InteropServices; -using System.Threading.Tasks; -using ITHit.FileSystem.Samples.Common.Windows.ShellExtension.Thumbnails; -using ITHit.FileSystem.Samples.Common.Windows.Rpc; -using ITHit.FileSystem.Samples.Common.Windows.ShellExtension; -using ITHit.FileSystem.Samples.Common.Windows.Rpc.Generated; +using ITHit.FileSystem.Windows.ShellExtension.Thumbnails; + namespace WebDAVDrive.ShellExtension { + /// /// Thumbnails provider Windows Shell Extension. /// [ComVisible(true)] [ProgId("WebDAVDrive.ThumbnailProvider")] [Guid("A5B0C82F-50AA-445C-A404-66DEB510E84B")] - public class ThumbnailProvider : ThumbnailProviderBase + public class ThumbnailProvider : ThumbnailProviderHandlerRpcBase { - public override async Task GetThumbnailsAsync(string filePath, uint size) - { - try - { - GrpcClient grpcClient = new GrpcClient(ShellExtensionConfiguration.AppSettings.RpcCommunicationChannelName); - ThumbnailRequest thumbnailRequest = new ThumbnailRequest(); - thumbnailRequest.Path = filePath; - thumbnailRequest.Size = size; - - Thumbnail thumbnail = await grpcClient.RpcClient.GetThumbnailAsync(thumbnailRequest); - return thumbnail.Image.ToByteArray(); - } - catch (Exception ex) - { - throw new NotImplementedException(ex.Message); - } - } } + } diff --git a/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/WebDAVDrive.ShellExtension.csproj b/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/WebDAVDrive.ShellExtension.csproj index d01a26a..70d574a 100644 --- a/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/WebDAVDrive.ShellExtension.csproj +++ b/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/WebDAVDrive.ShellExtension.csproj @@ -1,8 +1,6 @@ - - + - net5.0-windows10.0.18362.0 - 10.0.18362.0 + net5.0-windows10.0.19041.0 True x64 @@ -13,36 +11,12 @@ IT HIT LTD. IT HIT LTD. - - - Always - - - Always - - - - - - Always - - - - - - - - - - - + - Always - - + \ No newline at end of file diff --git a/Windows/WebDAVDrive/WebDAVDrive.UI/WebDAVDrive.UI.csproj b/Windows/WebDAVDrive/WebDAVDrive.UI/WebDAVDrive.UI.csproj index ff0552c..f346169 100644 --- a/Windows/WebDAVDrive/WebDAVDrive.UI/WebDAVDrive.UI.csproj +++ b/Windows/WebDAVDrive/WebDAVDrive.UI/WebDAVDrive.UI.csproj @@ -1,8 +1,7 @@  - net5.0-windows10.0.18362.0 - 10.0.19041.0 + net5.0-windows10.0.19041.0 true True IT Hit LTD. @@ -19,7 +18,7 @@ - + diff --git a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/Common.Windows.WinRT.ShellExtension.vcxproj.filters b/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/Common.Windows.WinRT.ShellExtension.vcxproj.filters deleted file mode 100644 index 5f72f70..0000000 --- a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/Common.Windows.WinRT.ShellExtension.vcxproj.filters +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - - - - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;ipp;xsd - - - - - Header Files - - - - - Source Files - - - \ No newline at end of file diff --git a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/Common.Windows.WinRT.ShellExtension_TemporaryKey.pfx b/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/Common.Windows.WinRT.ShellExtension_TemporaryKey.pfx deleted file mode 100644 index eb14bc50dd76ba7824350e61b1852757ccefacd7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2520 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 diff --git a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.aps b/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.aps deleted file mode 100644 index 4a39751fafc2becb343d5a5145971ccd77b1550d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1476 zcmb7EO>fgc5Pg9h2#5;@&PnN^m0BxlD};Irj;mTEPGvhukt|u2W0AD}h@3naY88;3YXN&1TU2OBRN zeeP(p4&S6j)IP7nG8>h$hI2UN(8B}^#7J<*wT>Bkb3$pcqo)LybEu>xLSI8%(*5M5hR+C+(Fw%%V0Ur(bjPBDir9DL^GG38OAzJie zIx1yVa=()NK5!N+s<@zk%A|?|Mty_VI9%n;Syl0lxO6`a_NCZ!#2p3P*^qf1*1S%J zLK=*5$|xst?GO>tzL5J!Ii}3O+`rfU`&R93Hkxjz?OuxwCawG2)4KNLkJ{6fR^Wl7 zt#dB!Sbompztf`Z`tOLl|C91RcVFIj=Ks4-!G)pa5V!WzPM__2{Xn13^eOlkcRGy< ah<^XVbOom9_Z%b6<%s{+0#lqzj`;=PMHY?# diff --git a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.exe.manifest b/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.exe.manifest deleted file mode 100644 index ed32231..0000000 --- a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.exe.manifest +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.rc b/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.rc deleted file mode 100644 index 6d285329154a138f9216537b866148988d06f675..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2664 zcmdUxTTAOe5Xb+|g5M#wFN$KlJo%`t7CE&_FQQN=VvQDRJTxu7_}M+@HyamYlPF#u zBFlC*)0x?s|6G!P&o$K)=tN^(YpMsu>`*g!=kQ|9b)^YUb*-}k-RedWdkTLB9l@JI zO>fTWnsdODSsUvwGMie~61UVGt-_7?cY^fD$yPG@o4QlQNtyg zL&rMRP#qn@ZE$X@rAs(neou4&r^VUdZ$6Z7dG9<8)C8ABj6+a*&^__bK*wX?I-RZ>XYU#ks|yti8}>b(odZbbi0 z$W%-X1X?BM5l_AkyPT4)jJ1|i1#zmU#tyHQH@!8&;=Ycks-&m7^iQAes&n&@>T0pf z?h`90)a+rSLk$nnYNNmAZf(JB!|g+xu1@!~ftBqApNcbSb$*UVf6#9Hx}MJOR-ap= zwzol~Sn`0#o37tYHh5Y2^K+oBmlPO~3yZ_E?q+hqO25VR5}je2>_7V)mVz&HI09JUAu* diff --git a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/CustomStateProvider.cpp b/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/CustomStateProvider.cpp deleted file mode 100644 index f2b860b..0000000 --- a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/CustomStateProvider.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "pch.h" -#include "CustomStateProvider.h" -#include "..\..\Common\WinRT.ShellExtension\ShellExtensionModule.h" - -namespace winrt -{ - using namespace winrt::Windows::Storage::Provider; -} - -namespace winrt::CommonWindowsRtShellExtenstion::implementation -{ - using namespace Windows::Foundation::Collections; - using namespace Windows::Storage::Provider; - - IIterable CustomStateProvider::GetItemProperties(hstring const& itemPath) - { - auto propertyVector{ winrt::single_threaded_vector() }; - - try - { - CommonShellExtensionRpc::CustomStateProviderProxy stateProviderProxy; - auto itemProperties = stateProviderProxy.GetItemProperties(itemPath, false); - - for (const auto& itemProp : itemProperties) - { - winrt::StorageProviderItemProperty storageItemProperty; - storageItemProperty.Id(itemProp.Id()); - storageItemProperty.Value(itemProp.Value()); - storageItemProperty.IconResource(itemProp.IconResource()); - - propertyVector.Append(storageItemProperty); - } - } - catch (...) - { - } - - return propertyVector; - } -} diff --git a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/CustomStateProvider.h b/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/CustomStateProvider.h deleted file mode 100644 index 7451a06..0000000 --- a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/CustomStateProvider.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include "CommonWindowsRtShellExtenstion.CustomStateProvider.g.h" -#include - -constexpr CLSID CLSID_CustomStateProviderWebDav = { 0x754f334f, 0x95c, 0x46cd, { 0xb0, 0x33, 0xb2, 0xc0, 0x52, 0x3d, 0x28, 0x29 } }; - -namespace winrt::CommonWindowsRtShellExtenstion::implementation -{ - struct CustomStateProvider : CustomStateProviderT - { - CustomStateProvider() = default; - - Windows::Foundation::Collections::IIterable GetItemProperties(_In_ hstring const& itemPath); - }; -} - -namespace winrt::CommonWindowsRtShellExtenstion::factory_implementation -{ - struct CustomStateProvider : CustomStateProviderT - { - }; -} diff --git a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/PropertySheet.props b/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/PropertySheet.props deleted file mode 100644 index e34141b..0000000 --- a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/PropertySheet.props +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/ShellExtensionModule.cpp b/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/ShellExtensionModule.cpp deleted file mode 100644 index 2e50b95..0000000 --- a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/ShellExtensionModule.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "pch.h" -#include "..\..\Common\WinRT.ShellExtension\ShellExtensionModule.h" -#include "..\..\Common\WinRT.ShellExtension\ClassFactory.h" - -using namespace winrt::CommonWindowsRtShellExtenstion::implementation; - -ShellExtensionModule::ShellExtensionModule() -{ - Start(); -} - -ShellExtensionModule::~ShellExtensionModule() -{ - Stop(); -} - -void ShellExtensionModule::Start() -{ - DWORD cookie = 0; - - auto customStateProviderWebDav = winrt::make>(); - winrt::check_hresult(CoRegisterClassObject(CLSID_CustomStateProviderWebDav, customStateProviderWebDav.get(), CLSCTX_LOCAL_SERVER, REGCLS_MULTI_SEPARATE, &cookie)); -} - -void ShellExtensionModule::Stop() -{ - -} diff --git a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/WebDAVDrive.WinRT.ShellExtension.vcxproj b/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/WebDAVDrive.WinRT.ShellExtension.vcxproj deleted file mode 100644 index 2f5f766..0000000 --- a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/WebDAVDrive.WinRT.ShellExtension.vcxproj +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - true - true - true - true - 15.0 - {5730488F-9F58-4951-9502-49A5A9A42B07} - Win32Proj - Common_Windows_WinRT_ShellExtension - 10.0.18362.0 - 10.0.18362.0 - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - Application - v143 - v142 - v141 - v140 - Unicode - - - true - true - - - false - true - false - - - - - - - - - - - - - - - - Use - pch.h - $(IntDir)pch.pch - _CONSOLE;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) - Level4 - %(AdditionalOptions) /permissive- /bigobj - - - Windows - false - - - - - Disabled - _DEBUG;%(PreprocessorDefinitions) - - - - - - - - - WIN32;%(PreprocessorDefinitions) - - - - - MaxSpeed - true - true - NDEBUG;%(PreprocessorDefinitions) - - - true - true - - - - - - - - - - - Create - - - - - - - - - - - {96fb01be-3def-418d-8ab0-69cc0d1813d3} - - - {1f61a031-cdfe-4b81-bac3-7760fa777a2a} - - - {e64b361d-8934-401e-b4fd-64786e4e1dc7} - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/WinMain.cpp b/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/WinMain.cpp deleted file mode 100644 index 6b7b4d7..0000000 --- a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/WinMain.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "pch.h" -#include "..\..\Common\WinRT.ShellExtension\ShellExtensionModule.h" - -using namespace winrt; -using namespace Windows::Foundation; - -void __stdcall TimerProc(HWND hWnd, UINT message, UINT idTimer, DWORD dwTime) -{ - PostQuitMessage(0); -} - -LRESULT __stdcall WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - switch (uMsg) - { - case WM_DESTROY: - KillTimer(hWnd, 0); - PostQuitMessage(0); - break; - } - - return DefWindowProc(hWnd, uMsg, wParam, lParam); -} - -void RunMessageLoop(HINSTANCE hInstance) -{ - std::wstring className = L"ShellExtension Window Class"; - - WNDCLASS wc = { }; - - wc.lpfnWndProc = WindowProc; - wc.hInstance = hInstance; - wc.lpszClassName = className.c_str(); - - RegisterClass(&wc); - - HWND hWnd = CreateWindowEx( - 0, - className.c_str(), - L"ShellExtension", - WS_OVERLAPPEDWINDOW, - CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, - nullptr, - nullptr, - hInstance, - nullptr - ); - - ShowWindow(hWnd, SW_HIDE); - - SetTimer(hWnd, 0, 20000, (TIMERPROC)TimerProc); - - MSG msg; - - while (GetMessage(&msg, nullptr, 0, 0)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } -} - -int __stdcall wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) -{ - init_apartment(); - - ShellExtensionModule module; - - RunMessageLoop(hInstance); -} diff --git a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/packages.config b/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/packages.config deleted file mode 100644 index c12b9fd..0000000 --- a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/pch.cpp b/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/pch.cpp deleted file mode 100644 index bcb5590..0000000 --- a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/pch.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "pch.h" diff --git a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/pch.h b/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/pch.h deleted file mode 100644 index 9efea8c..0000000 --- a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/pch.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include -#ifdef GetCurrentTime -#undef GetCurrentTime -#endif - -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "CustomStateProvider.h" \ No newline at end of file diff --git a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/resource.h b/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/resource.h deleted file mode 100644 index 5b37dd6..0000000 --- a/Windows/WebDAVDrive/WebDAVDrive.WinRT.ShellExtension/resource.h +++ /dev/null @@ -1,13 +0,0 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 101 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif diff --git a/Windows/WebDAVDrive/WebDAVDrive/Program.cs b/Windows/WebDAVDrive/WebDAVDrive/Program.cs index 806a2be..adc394f 100644 --- a/Windows/WebDAVDrive/WebDAVDrive/Program.cs +++ b/Windows/WebDAVDrive/WebDAVDrive/Program.cs @@ -4,20 +4,18 @@ using System.Linq; using System.Net; using System.Reflection; -using System.Runtime.InteropServices; using System.Security; using System.Threading; using System.Threading.Tasks; -using Windows.Storage; using Microsoft.Extensions.Configuration; using log4net; -using log4net.Appender; -using log4net.Config; + using ITHit.FileSystem; using ITHit.FileSystem.Windows; using ITHit.FileSystem.Samples.Common; using ITHit.FileSystem.Samples.Common.Windows; +using ITHit.FileSystem.Windows.ShellExtension; using ITHit.WebDAV.Client; using ITHit.WebDAV.Client.Exceptions; @@ -37,25 +35,25 @@ class Program internal static AppSettings Settings; /// - /// Processes OS file system calls, - /// synchronizes user file system to remote storage. + /// Log4Net logger. /// - internal static VirtualEngine Engine; + private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); /// - /// WebDAV client for accessing the WebDAV server. + /// Processes OS file system calls, + /// synchronizes user file system to remote storage. /// - internal static WebDavSession DavClient; + private static VirtualEngine Engine; /// - /// Log4Net logger. + /// Outputs logging information. /// - private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static LogFormatter logFormatter; /// - /// Log file path. + /// WebDAV client for accessing the WebDAV server. /// - private static string LogFilePath; + internal static WebDavSession DavClient; /// /// Event to be fired when the tray app exits or an exit key in the console is selected. @@ -72,69 +70,76 @@ class Program /// private static uint loginRetriesCurrent = 0; + static async Task Main(string[] args) { + // In a real world application the user system should have trusted certificate installed or an installer (msi) should install it. + // This method should be omitted for packaged application. + CertificateRegistrar.InstallDeveloperCertificate(); + + // In the case of a regular installer (msi) call this method during installation. + // This method should be omitted for packaged application. + await PackageRegistrar.RegisterSparsePackageAsync(); + // Load Settings. - IConfiguration configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json", false, true).Build(); - Settings = configuration.ReadSettings(); + Settings = new ConfigurationBuilder().AddJsonFile("appsettings.json", false, true).Build().ReadSettings(); - // Configure log4net and set log file path. - LogFilePath = ConfigureLogger(); + logFormatter = new LogFormatter(log, Settings.AppID); - PrintHelp(); + try + { + // Log environment description. + logFormatter.PrintEnvironmentDescription(); - // Register sync root and create app folders. - await RegisterSyncRootAsync(); + // Register sync root and create app folders. + await RegisterSyncRootAsync(); - // Log indexed state. - StorageFolder userFileSystemRootFolder = await StorageFolder.GetFolderFromPathAsync(Settings.UserFileSystemRootPath); - log.Info($"\nIndexed state: {(await userFileSystemRootFolder.GetIndexedStateAsync())}\n"); + // Log indexing state. Sync root must be indexed. + await logFormatter.PrintIndexingStateAsync(Settings.UserFileSystemRootPath); - Logger.PrintHeader(log); + // Log console commands. + logFormatter.PrintHelp(); - using (DavClient = ConfigureWebDavSession()) - { - try + // Log logging columns headers. + logFormatter.PrintHeader(); + + using (DavClient = ConfigureWebDavSession()) { - Engine = new VirtualEngine( + using (Engine = new VirtualEngine( Settings.UserFileSystemLicense, Settings.UserFileSystemRootPath, Settings.WebDAVServerUrl, Settings.WebSocketServerUrl, Settings.IconsFolderPath, - Settings.RpcCommunicationChannelName, Settings.SyncIntervalMs, - Settings.MaxDegreeOfParallelism, - log); - Engine.AutoLock = Settings.AutoLock; + logFormatter)) + { + Engine.AutoLock = Settings.AutoLock; - // Start tray application in a separate thread. - WindowsTrayInterface.CreateTrayInterface(Settings.ProductName, Engine, exitEvent); + // Start tray application in a separate thread. + WindowsTrayInterface.CreateTrayInterface(Settings.ProductName, Engine, exitEvent); - // Start processing OS file system calls. - await Engine.StartAsync(); + // Start processing OS file system calls. + await Engine.StartAsync(); #if DEBUG - // Opens Windows File Manager with user file system folder and remote storage folder. - ShowTestEnvironment(); + // Opens Windows File Manager with user file system folder and remote storage folder. + ShowTestEnvironment(); #endif - // Read console input in a separate thread. - await ConsoleReadKeyAsync(); + // Read console input in a separate thread. + await ConsoleReadKeyAsync(); - // Keep this application running and reading user input - // untill the tray app exits or an exit key in the console is selected. - exitEvent.WaitOne(); - } - catch (Exception ex) - { - log.Error(ex); - await ProcessUserInputAsync(); - } - finally - { - Engine.Dispose(); + // Keep this application running and reading user input + // untill the tray app exits or an exit key in the console is selected. + exitEvent.WaitOne(); + } } } + catch (Exception ex) + { + log.Error(ex); + await ProcessUserInputAsync(); + } } private static async Task ConsoleReadKeyAsync() @@ -148,48 +153,6 @@ private static async Task ConsoleReadKeyAsync() readKeyThread.Start(); } -#if DEBUG - /// - /// Opens Windows File Manager with both remote storage and user file system for testing. - /// - /// This method is provided solely for the development and testing convenience. - private static void ShowTestEnvironment() - { - // Enable UTF8 for Console Window and set width. - Console.OutputEncoding = System.Text.Encoding.UTF8; - Console.SetWindowSize(Console.LargestWindowWidth, Console.LargestWindowHeight / 3); - Console.SetBufferSize(Console.LargestWindowWidth * 2, Console.BufferHeight); - - // Open Windows File Manager with user file system. - ProcessStartInfo ufsInfo = new ProcessStartInfo(Program.Settings.UserFileSystemRootPath); - ufsInfo.UseShellExecute = true; // Open window only if not opened already. - using (Process ufsWinFileManager = Process.Start(ufsInfo)) - { - - } - - // Open web browser with WebDAV content. - ProcessStartInfo rsInfo = new ProcessStartInfo(Program.Settings.WebDAVServerUrl); - rsInfo.UseShellExecute = true; // Open window only if not opened already. - using (Process rsWinFileManager = Process.Start(rsInfo)) - { - - } - } -#endif - - /// - /// Gets automatically generated Sync Root ID. - /// - /// An identifier in the form: [Storage Provider ID]![Windows SID]![Account ID] - private static string SyncRootId - { - get - { - return $"{Settings.AppID}!{System.Security.Principal.WindowsIdentity.GetCurrent().User}!User"; - } - } - /// /// Creates and configures WebDAV client to access the remote storage. /// @@ -217,10 +180,18 @@ private static WebDavSession ConfigureWebDavSession() private static void DavClient_WebDAVMessage(ISession client, WebDavMessageEventArgs e) { string msg = $"\n{e.Message}"; - //if (e.LogLevel == LogLevel.Debug) - // log.Debug($"{msg}\n"); - //else + + if(logFormatter.DebugLoggingEnabled) + { + log.Debug($"{msg}\n"); + } + + /* + if (e.LogLevel == ITHit.WebDAV.Client.Logger.LogLevel.Debug) + log.Debug($"{msg}\n"); + else log.Info($"{msg}\n"); + */ } /// @@ -318,55 +289,21 @@ private static void DavClient_WebDavError(ISession sender, WebDavErrorEventArgs } } - /// - /// Configures log4net logger. - /// - /// Log file path. - private static string ConfigureLogger() - { - // Load Log4Net for net configuration. - var logRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); - XmlConfigurator.Configure(logRepository, new FileInfo(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "log4net.config"))); - - // Update log file path for msix package. - RollingFileAppender rollingFileAppender = logRepository.GetAppenders().Where(p => p.GetType() == typeof(RollingFileAppender)).FirstOrDefault() as RollingFileAppender; - if (rollingFileAppender != null && rollingFileAppender.File.Contains("WindowsApps")) - { - rollingFileAppender.File = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), Settings.AppID, - Path.GetFileName(rollingFileAppender.File)); - } - return rollingFileAppender?.File; - } - - private static void PrintHelp() - { - log.Info($"\n{"AppID:",-15} {Settings.AppID}"); - log.Info($"\n{"Engine version:",-15} {typeof(IEngine).Assembly.GetName().Version}"); - log.Info($"\n{"OS version:",-15} {RuntimeInformation.OSDescription}"); - log.Info($"\n{"Env version:",-15} {RuntimeInformation.FrameworkDescription} {IntPtr.Size * 8}bit."); - log.Info("\n\nPress Esc to unregister file system, delete all files/folders and exit (simulate uninstall)."); - log.Info("\nPress Spacebar to exit without unregistering (simulate reboot)."); - log.Info("\nPress 'e' to start/stop the Engine and all sync services."); - log.Info("\nPress 's' to start/stop full synchronization service."); - log.Info("\nPress 'm' to start/stop remote storage monitor."); - log.Info($"\nPress 'l' to open log file. ({LogFilePath})"); - log.Info($"\nPress 'b' to submit support tickets, report bugs, suggest features. (https://userfilesystem.com/support/)"); - log.Info("\n----------------------\n"); - } - private static async Task ProcessUserInputAsync() { do { - switch (Console.ReadKey(true).KeyChar) + ConsoleKeyInfo keyInfo = Console.ReadKey(true); + + switch (keyInfo.Key) { - case (char)ConsoleKey.F1: - case 'h': + case ConsoleKey.F1: + case ConsoleKey.H: // Print help info. - PrintHelp(); + logFormatter.PrintHelp(); break; - case 'e': + case ConsoleKey.E: // Start/stop the Engine and all sync services. if (Engine.State == EngineState.Running) { @@ -378,13 +315,13 @@ private static async Task ProcessUserInputAsync() } break; - case 's': + case ConsoleKey.S: // Start/stop full synchronization. if (Engine.SyncService.SyncState == SynchronizationState.Disabled) { if (Engine.State != EngineState.Running) { - Engine.SyncService.LogError("Failed to start. The Engine must be running."); + Engine.SyncService.Logger.LogError("Failed to start. The Engine must be running."); break; } await Engine.SyncService.StartAsync(); @@ -395,13 +332,18 @@ private static async Task ProcessUserInputAsync() } break; - case 'm': + case ConsoleKey.D: + // Enables/disables debug logging. + logFormatter.DebugLoggingEnabled = !logFormatter.DebugLoggingEnabled; + break; + + case ConsoleKey.M: // Start/stop remote storage monitor. if (Engine.RemoteStorageMonitor.SyncState == SynchronizationState.Disabled) { if (Engine.State != EngineState.Running) { - Engine.RemoteStorageMonitor.LogError("Failed to start. The Engine must be running."); + Engine.RemoteStorageMonitor.Logger.LogError("Failed to start. The Engine must be running."); break; } await Engine.RemoteStorageMonitor.StartAsync(); @@ -412,16 +354,16 @@ private static async Task ProcessUserInputAsync() } break; - case 'l': + case ConsoleKey.L: // Open log file. - ProcessStartInfo psiLog = new ProcessStartInfo(LogFilePath); + ProcessStartInfo psiLog = new ProcessStartInfo(logFormatter.LogFilePath); psiLog.UseShellExecute = true; using (Process.Start(psiLog)) { } break; - case 'b': + case ConsoleKey.B: // Submit support tickets, report bugs, suggest features. ProcessStartInfo psiSupport = new ProcessStartInfo("https://www.userfilesystem.com/support/"); psiSupport.UseShellExecute = true; @@ -430,33 +372,39 @@ private static async Task ProcessUserInputAsync() } break; - case 'q': - // Unregister during programm uninstall. - Engine.Dispose(); - await UnregisterSyncRootAsync(); - log.Info("\nAll empty file and folder placeholders are deleted. Hydrated placeholders are converted to regular files / folders.\n"); - return; - - case (char)ConsoleKey.Escape: - case 'Q': - Engine.Dispose(); + case ConsoleKey.Escape: + if (Engine.State == EngineState.Running) + { + await Engine.StopAsync(); + } // Call the code below during programm uninstall using classic msi. await UnregisterSyncRootAsync(); // Delete all files/folders. await CleanupAppFoldersAsync(); + + // Unregister sparse package. + await UnregisterSparsePackageAsync(); return; - case (char)ConsoleKey.Spacebar: + case ConsoleKey.Spacebar: + if (Engine.State == EngineState.Running) + { + await Engine.StopAsync(); + } log.Info("\n\nAll downloaded file / folder placeholders remain in file system. Restart the application to continue managing files.\n"); return; + case ConsoleKey.P: + // Unregister sparse package. + await UnregisterSparsePackageAsync(); + break; + default: break; } - } while (true); } @@ -471,14 +419,13 @@ private static async Task RegisterSyncRootAsync() { if (!await Registrar.IsRegisteredAsync(Settings.UserFileSystemRootPath)) { - log.Info($"\nRegistering {Settings.UserFileSystemRootPath} sync root."); + log.Info($"\n\nRegistering {Settings.UserFileSystemRootPath} sync root."); Directory.CreateDirectory(Settings.UserFileSystemRootPath); await Registrar.RegisterAsync(SyncRootId, Settings.UserFileSystemRootPath, Settings.ProductName, Path.Combine(Settings.IconsFolderPath, "Drive.ico")); - log.Info("\nRegistering shell extensions...\n"); - ShellExtensionRegistrar.Register(SyncRootId); + ShellExtensionRegistrar.Register(SyncRootId, log); } else { @@ -503,8 +450,7 @@ private static async Task UnregisterSyncRootAsync() log.Info($"\n\nUnregistering {Settings.UserFileSystemRootPath} sync root."); await Registrar.UnregisterAsync(SyncRootId); - log.Info("\nUnregistering shell extensions...\n"); - ShellExtensionRegistrar.Unregister(); + ShellExtensionRegistrar.Unregister(log); } private static async Task CleanupAppFoldersAsync() @@ -528,5 +474,54 @@ private static async Task CleanupAppFoldersAsync() log.Error($"\n{ex}"); } } + private static async Task UnregisterSparsePackageAsync() + { + // Unregister sparse package. + log.Info("\nUnregistering sparse package..."); + await PackageRegistrar.UnregisterSparsePackageAsync(SyncRootId); + log.Info("\nSparse package unregistered sucessfully."); + } + +#if DEBUG + /// + /// Opens Windows File Manager with both remote storage and user file system for testing. + /// + /// This method is provided solely for the development and testing convenience. + private static void ShowTestEnvironment() + { + // Enable UTF8 for Console Window and set width. + Console.OutputEncoding = System.Text.Encoding.UTF8; + Console.SetWindowSize(Console.LargestWindowWidth, Console.LargestWindowHeight / 3); + Console.SetBufferSize(Console.LargestWindowWidth * 2, Console.BufferHeight); + + // Open Windows File Manager with user file system. + ProcessStartInfo ufsInfo = new ProcessStartInfo(Program.Settings.UserFileSystemRootPath); + ufsInfo.UseShellExecute = true; // Open window only if not opened already. + using (Process ufsWinFileManager = Process.Start(ufsInfo)) + { + + } + + // Open web browser with WebDAV content. + ProcessStartInfo rsInfo = new ProcessStartInfo(Program.Settings.WebDAVServerUrl); + rsInfo.UseShellExecute = true; // Open window only if not opened already. + using (Process rsWinFileManager = Process.Start(rsInfo)) + { + + } + } +#endif + + /// + /// Gets automatically generated Sync Root ID. + /// + /// An identifier in the form: [Storage Provider ID]![Windows SID]![Account ID] + private static string SyncRootId + { + get + { + return $"{Settings.AppID}!{System.Security.Principal.WindowsIdentity.GetCurrent().User}!User"; + } + } } } diff --git a/Windows/WebDAVDrive/WebDAVDrive/RemoteStorageMonitor.cs b/Windows/WebDAVDrive/WebDAVDrive/RemoteStorageMonitor.cs index d361208..a5b74ff 100644 --- a/Windows/WebDAVDrive/WebDAVDrive/RemoteStorageMonitor.cs +++ b/Windows/WebDAVDrive/WebDAVDrive/RemoteStorageMonitor.cs @@ -21,7 +21,7 @@ namespace WebDAVDrive /// If any file or folder is modified, created, delated, renamed or attributes changed in the remote storage, /// triggers an event with information about changes being made. /// - internal class RemoteStorageMonitor : Logger, IDisposable + internal class RemoteStorageMonitor : IDisposable { /// /// Current synchronization state. @@ -38,8 +38,9 @@ public virtual SynchronizationState SyncState } /// - /// Gets or sets a value that indicates whether to send an authenticate header with the websocket. + /// Logger. /// + public readonly ILogger Logger; /// /// WebSocket client. @@ -67,11 +68,12 @@ public virtual SynchronizationState SyncState /// /// WebSocket server url. /// Engine to send notifications about changes in the remote storage. - /// Logger. - internal RemoteStorageMonitor(string webSocketServerUrl, VirtualEngine engine, ILog log) : base("Remote Storage Monitor", log) + /// Logger. + internal RemoteStorageMonitor(string webSocketServerUrl, VirtualEngine engine, ILogger logger) { this.webSocketServerUrl = webSocketServerUrl; this.engine = engine; + this.Logger = logger.CreateLogger("Remote Storage Monitor"); } /// @@ -86,7 +88,7 @@ private async Task StartMonitoringAsync(NetworkCredential credentials) await clientWebSocket.ConnectAsync(new Uri(webSocketServerUrl), CancellationToken.None); - LogMessage("Started", webSocketServerUrl); + Logger.LogMessage("Started", webSocketServerUrl); var rcvBuffer = new ArraySegment(new byte[2048]); while (true) @@ -119,7 +121,7 @@ await Task.Factory.StartNew( // Start socket after first success webdav propfind. Restart socket when it disconnects. if (clientWebSocket != null && clientWebSocket?.State != WebSocketState.Closed) { - LogError(e.Message, webSocketServerUrl); + Logger.LogError(e.Message, webSocketServerUrl); } // Delay websocket connect to not overload it on network disappear. @@ -145,10 +147,10 @@ internal async Task StopAsync() } } catch (WebSocketException ex) { - LogError("Failed to close websocket.", webSocketServerUrl, null, ex); + Logger.LogError("Failed to close websocket.", webSocketServerUrl, null, ex); }; - LogMessage("Stoped", webSocketServerUrl); + Logger.LogMessage("Stoped", webSocketServerUrl); } /// @@ -162,7 +164,7 @@ internal async Task ProcessAsync(string jsonString) // Check if remote URL starts with WebDAVServerUrl. if (remoteStoragePath.StartsWith(Program.Settings.WebDAVServerUrl, StringComparison.InvariantCultureIgnoreCase)) { - LogMessage($"EventType: {jsonMessage.EventType}", jsonMessage.ItemPath, jsonMessage.TargetPath); + Logger.LogMessage($"EventType: {jsonMessage.EventType}", jsonMessage.ItemPath, jsonMessage.TargetPath); switch (jsonMessage.EventType) { case "created": @@ -218,14 +220,14 @@ private async Task CreatedAsync(string remoteStoragePath) // Because of the on-demand population, the parent folder placeholder may not exist in the user file system // or the folder may be offline. In this case the IServerNotifications.CreateAsync() call is ignored. - LogMessage($"Created succesefully", userFileSystemPath); + Logger.LogMessage($"Created succesefully", userFileSystemPath); } } } } catch (Exception ex) { - LogError(nameof(CreatedAsync), remoteStoragePath, null, ex); + Logger.LogError(nameof(CreatedAsync), remoteStoragePath, null, ex); } } @@ -264,7 +266,7 @@ private async Task ChangedAsync(string remoteStoragePath) if (await engine.ServerNotifications(userFileSystemPath).UpdateAsync(itemMetadata)) { - LogMessage("Updated succesefully", userFileSystemPath); + Logger.LogMessage("Updated succesefully", userFileSystemPath); } // Restore the read-only attribute. @@ -275,11 +277,11 @@ private async Task ChangedAsync(string remoteStoragePath) catch (IOException ex) { // The file is blocked in the user file system. This is a normal behaviour. - LogMessage(ex.Message); + Logger.LogMessage(ex.Message); } catch (Exception ex) { - LogError(nameof(ChangedAsync), remoteStoragePath, null, ex); + Logger.LogError(nameof(ChangedAsync), remoteStoragePath, null, ex); } } @@ -305,7 +307,7 @@ private async Task MovedAsync(string remoteStorageOldPath, string remoteStorageN // Source item is loaded, move it to a new location or delete. if (await engine.ServerNotifications(userFileSystemOldPath).MoveToAsync(userFileSystemNewPath)) { - LogMessage("Moved succesefully", userFileSystemOldPath, userFileSystemNewPath); + Logger.LogMessage("Moved succesefully", userFileSystemOldPath, userFileSystemNewPath); } else { @@ -321,7 +323,7 @@ private async Task MovedAsync(string remoteStorageOldPath, string remoteStorageN } catch (Exception ex) { - LogError(nameof(MovedAsync), remoteStorageOldPath, remoteStorageNewPath, ex); + Logger.LogError(nameof(MovedAsync), remoteStorageOldPath, remoteStorageNewPath, ex); } } @@ -342,13 +344,13 @@ private async Task DeletedAsync(string remoteStoragePath) { // Because of the on-demand population the file or folder placeholder may not exist in the user file system. // In this case the IServerNotifications.DeleteAsync() call is ignored. - LogMessage("Deleted succesefully", userFileSystemPath); + Logger.LogMessage("Deleted succesefully", userFileSystemPath); } } } catch (Exception ex) { - LogError(nameof(DeletedAsync), remoteStoragePath, null, ex); + Logger.LogError(nameof(DeletedAsync), remoteStoragePath, null, ex); } } @@ -374,13 +376,13 @@ private async Task LockedAsync(string remoteStoragePath) // Save info about the third-party lock. await engine.Placeholders.GetItem(userFileSystemPath).SavePropertiesAsync(itemMetadata); - LogMessage("Third-party lock info added", userFileSystemPath); + Logger.LogMessage("Third-party lock info added", userFileSystemPath); } } } catch (Exception ex) { - LogError(nameof(LockedAsync), remoteStoragePath, null, ex); + Logger.LogError(nameof(LockedAsync), remoteStoragePath, null, ex); } } @@ -399,7 +401,7 @@ private async Task UnlockedAsync(string remoteStoragePath) { if(engine.Placeholders.GetItem(userFileSystemPath).Properties.Remove("ThirdPartyLockInfo")) { - LogMessage("Third-party lock info deleted", userFileSystemPath); + Logger.LogMessage("Third-party lock info deleted", userFileSystemPath); } //ExternalDataManager customDataManager = engine.ExternalDataManager(userFileSystemPath); @@ -412,20 +414,20 @@ private async Task UnlockedAsync(string remoteStoragePath) // // Remove lock icon and lock info in custom columns. // await customDataManager.SetLockInfoAsync(null); - // LogMessage("Unlocked succesefully", userFileSystemPath); + // Logger.LogMessage("Unlocked succesefully", userFileSystemPath); //} } } catch (Exception ex) { - LogError(nameof(UnlockedAsync), remoteStoragePath, null, ex); + Logger.LogError(nameof(UnlockedAsync), remoteStoragePath, null, ex); } } private void Error(object sender, ErrorEventArgs e) { - LogError(null, null, null, e.GetException()); + Logger.LogError(null, null, null, e.GetException()); } @@ -438,7 +440,7 @@ protected virtual void Dispose(bool disposing) if (disposing) { clientWebSocket.Dispose(); - LogMessage($"Disposed"); + Logger.LogMessage($"Disposed"); } // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. diff --git a/Windows/WebDAVDrive/WebDAVDrive/ShellExtensionRegistrar.cs b/Windows/WebDAVDrive/WebDAVDrive/ShellExtensionRegistrar.cs index d63a1c1..bc467ff 100644 --- a/Windows/WebDAVDrive/WebDAVDrive/ShellExtensionRegistrar.cs +++ b/Windows/WebDAVDrive/WebDAVDrive/ShellExtensionRegistrar.cs @@ -1,8 +1,9 @@ -using ITHit.FileSystem.Samples.Common.Windows; using System.IO; using System.Reflection; +using log4net; +using CommonShellExtension = ITHit.FileSystem.Windows.ShellExtension; +using ITHit.FileSystem.Windows.ShellExtension; using WebDAVDrive.ShellExtension; -using CommonShellExtension = ITHit.FileSystem.Samples.Common.Windows.ShellExtension; namespace WebDAVDrive { @@ -10,24 +11,32 @@ internal class ShellExtensionRegistrar { private static readonly string ComServerRelativePath = @"WebDAVDrive.ShellExtension.exe"; - public static void Register(string syncRootId) + internal static void Register(string syncRootId, ILog log) { - if (!Registrar.IsRunningAsUwp()) + if (!PackageRegistrar.IsRunningAsUwp()) { - string comServerPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), ComServerRelativePath)); + log.Info("\nRegistering shell extensions...\n"); + + string applicationDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); + string comServerPath = Path.Combine(applicationDirectory, ComServerRelativePath); + CommonShellExtension.ShellExtensionRegistrar.Register(syncRootId, "ThumbnailProvider", typeof(ThumbnailProvider).GUID, comServerPath); CommonShellExtension.ShellExtensionRegistrar.Register(syncRootId, "MenuVerbHandler_0", typeof(ContextMenusProvider).GUID, comServerPath); + CommonShellExtension.ShellExtensionRegistrar.Register(syncRootId, "CustomStateHandler", typeof(CustomStateProvider).GUID, comServerPath); + CommonShellExtension.ShellExtensionRegistrar.RegisterPackage(syncRootId); } } - public static void Unregister() + internal static void Unregister(ILog log) { - if (!Registrar.IsRunningAsUwp()) + if (!PackageRegistrar.IsRunningAsUwp()) { + log.Info("\nUnregistering shell extensions...\n"); + CommonShellExtension.ShellExtensionRegistrar.Unregister(typeof(ThumbnailProvider).GUID); CommonShellExtension.ShellExtensionRegistrar.Unregister(typeof(ContextMenusProvider).GUID); + CommonShellExtension.ShellExtensionRegistrar.Unregister(typeof(CustomStateProvider).GUID); } } - } } diff --git a/Windows/WebDAVDrive/WebDAVDrive/SparsePackage/appxmanifest.xml b/Windows/WebDAVDrive/WebDAVDrive/SparsePackage/appxmanifest.xml new file mode 100644 index 0000000..61f952d --- /dev/null +++ b/Windows/WebDAVDrive/WebDAVDrive/SparsePackage/appxmanifest.xml @@ -0,0 +1,77 @@ + + + + + + + + WebDAV Drive sparse package + IT Hit LTD + stub.png + true + disabled + disabled + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Windows/WebDAVDrive/WebDAVDrive/VirtualEngine.cs b/Windows/WebDAVDrive/WebDAVDrive/VirtualEngine.cs index a11fbaf..8f8edae 100644 --- a/Windows/WebDAVDrive/WebDAVDrive/VirtualEngine.cs +++ b/Windows/WebDAVDrive/WebDAVDrive/VirtualEngine.cs @@ -1,18 +1,11 @@ using System; using System.Net; -using System.Linq; -using System.Collections.Generic; -using System.IO; using System.Threading.Tasks; +using System.Threading; -using log4net; using ITHit.FileSystem; using ITHit.FileSystem.Samples.Common.Windows; -using ITHit.FileSystem.Windows; -using ITHit.FileSystem.Samples.Common; -using ITHit.WebDAV.Client; -using System.Threading; namespace WebDAVDrive { @@ -40,9 +33,7 @@ public class VirtualEngine : VirtualEngineBase /// Path to the remote storage root. /// Web sockets server that sends notifications about changes on the server. /// Path to the icons folder. - /// Channel name to communicate with Windows Explorer context menu and other components on this machine. /// Full synchronization interval in milliseconds. - /// A maximum number of concurrent tasks. /// Log4net logger. public VirtualEngine( string license, @@ -50,13 +41,11 @@ public VirtualEngine( string remoteStorageRootPath, string webSocketServerUrl, string iconsFolderPath, - string rpcCommunicationChannelName, double syncIntervalMs, - int maxDegreeOfParallelism, - ILog log4net) - : base(license, userFileSystemRootPath, remoteStorageRootPath, iconsFolderPath, rpcCommunicationChannelName, syncIntervalMs, maxDegreeOfParallelism, log4net) + LogFormatter logFormatter) + : base(license, userFileSystemRootPath, remoteStorageRootPath, iconsFolderPath, syncIntervalMs, logFormatter) { - RemoteStorageMonitor = new RemoteStorageMonitor(webSocketServerUrl, this, log4net); + RemoteStorageMonitor = new RemoteStorageMonitor(webSocketServerUrl, this, this.Logger); } /// @@ -72,6 +61,25 @@ public override async Task GetFileSystemItemAsync(string userFi } } + /// + public override async Task GetMenuCommandAsync(Guid menuGuid) + { + // For this method to be called you need to register a menu command handler. + // See method description for more details. + + Logger.LogDebug($"{nameof(IEngine)}.{nameof(GetMenuCommandAsync)}()", menuGuid.ToString()); + + Guid menuCommandLockGuid = typeof(WebDAVDrive.ShellExtension.ContextMenusProvider).GUID; + + if (menuGuid == menuCommandLockGuid) + { + return new MenuCommandLock(this, this.Logger); + } + + Logger.LogError($"Menu not found", menuGuid.ToString()); + throw new NotImplementedException(); + } + //public override IMapping Mapping { get { return new Mapping(this); } } /// @@ -104,131 +112,5 @@ protected override void Dispose(bool disposing) } base.Dispose(disposing); } - - /// - public override async Task GetThumbnailAsync(string userFileSystemPath, uint size) - { - byte[] thumbnail = null; - - string[] exts = Program.Settings.RequestThumbnailsFor.Trim().Split("|"); - string ext = System.IO.Path.GetExtension(userFileSystemPath).TrimStart('.'); - - if (exts.Any(ext.Equals) || exts.Any("*".Equals)) - { - string ThumbnailGeneratorUrl = Program.Settings.ThumbnailGeneratorUrl.Replace("{thumbnail width}", ""+size).Replace("{thumbnail height}", "" + size); - string filePathRemote = ThumbnailGeneratorUrl.Replace("{path to file}", WebDAVDrive.Mapping.MapPath(userFileSystemPath)); - - try - { - using (IWebResponse response = await Program.DavClient.DownloadAsync(new Uri(filePathRemote))) - { - using (Stream stream = await response.GetResponseStreamAsync()) - { - thumbnail = await StreamToByteArrayAsync(stream); - } - } - } - catch (WebException we) - { - LogMessage(we.Message, userFileSystemPath); - } - catch (Exception e) - { - LogError($"Failed to load thumbnail {size}px", userFileSystemPath, null, e); - } - } - - string thumbnailResult = thumbnail != null ? "Success" : "Not Impl"; - LogMessage($"{nameof(VirtualEngine)}.{nameof(GetThumbnailAsync)}() - {thumbnailResult}", userFileSystemPath); - - return thumbnail; - } - - private static async Task StreamToByteArrayAsync(Stream stream) - { - using (MemoryStream memoryStream = new()) - { - await stream.CopyToAsync(memoryStream); - return memoryStream.ToArray(); - } - } - - /// - public override async Task> GetItemPropertiesAsync(string userFileSystemPath) - { - //LogMessage($"{nameof(VirtualEngine)}.{nameof(GetItemPropertiesAsync)}()", userFileSystemPath); - - IList props = new List(); - - PlaceholderItem placeholder = this.Placeholders.GetItem(userFileSystemPath); - - // Read LockInfo and choose the lock icon. - string lockIconName = null; - if (placeholder.Properties.TryGetValue("LockInfo", out IDataItem propLockInfo)) - { - // The file is locked by this user. - lockIconName = "Locked.ico"; - } - else if (placeholder.Properties.TryGetValue("ThirdPartyLockInfo", out propLockInfo)) - { - // The file is locked by somebody else on the server. - lockIconName = "LockedByAnotherUser.ico"; - } - - if (propLockInfo != null && propLockInfo.TryGetValue(out ServerLockInfo lockInfo)) - { - - // Get Lock Owner. - FileSystemItemPropertyData propertyLockOwner = new FileSystemItemPropertyData() - { - Id = (int)CustomColumnIds.LockOwnerIcon, - Value = lockInfo.Owner, - IconResource = System.IO.Path.Combine(this.IconsFolderPath, lockIconName) - }; - props.Add(propertyLockOwner); - - // Get Lock Expires. - FileSystemItemPropertyData propertyLockExpires = new FileSystemItemPropertyData() - { - Id = (int)CustomColumnIds.LockExpirationDate, - Value = lockInfo.LockExpirationDateUtc.ToString(), - IconResource = System.IO.Path.Combine(this.IconsFolderPath, "Empty.ico") - }; - props.Add(propertyLockExpires); - } - - - // Read LockMode. - if (placeholder.Properties.TryGetValue("LockMode", out IDataItem propLockMode)) - { - if (propLockMode.TryGetValue(out LockMode lockMode) && lockMode != LockMode.None) - { - FileSystemItemPropertyData propertyLockMode = new FileSystemItemPropertyData() - { - Id = (int)CustomColumnIds.LockScope, - Value = "Locked", - IconResource = System.IO.Path.Combine(this.IconsFolderPath, "Empty.ico") - }; - props.Add(propertyLockMode); - } - } - - // Read ETag. - if (placeholder.Properties.TryGetValue("ETag", out IDataItem propETag)) - { - if (propETag.TryGetValue(out string eTag)) - { - FileSystemItemPropertyData propertyETag = new FileSystemItemPropertyData() - { - Id = (int)CustomColumnIds.ETag, - Value = eTag, - IconResource = System.IO.Path.Combine(this.IconsFolderPath, "Empty.ico") - }; - props.Add(propertyETag); - } - } - - return props; - } } } diff --git a/Windows/WebDAVDrive/WebDAVDrive/VirtualFile.cs b/Windows/WebDAVDrive/WebDAVDrive/VirtualFile.cs index 38e9c7b..5242106 100644 --- a/Windows/WebDAVDrive/WebDAVDrive/VirtualFile.cs +++ b/Windows/WebDAVDrive/WebDAVDrive/VirtualFile.cs @@ -31,13 +31,13 @@ public VirtualFile(string path, VirtualEngine engine, ILogger logger) : base(pat /// public async Task OpenCompletionAsync(IOperationContext operationContext, IResultContext context, CancellationToken cancellationToken) { - Logger.LogMessage($"{nameof(IFileWindows)}.{nameof(OpenCompletionAsync)}()", UserFileSystemPath, default, operationContext); + Logger.LogDebug($"{nameof(IFileWindows)}.{nameof(OpenCompletionAsync)}()", UserFileSystemPath, default, operationContext); } /// public async Task CloseCompletionAsync(IOperationContext operationContext, IResultContext context, CancellationToken cancellationToken) { - Logger.LogMessage($"{nameof(IFileWindows)}.{nameof(CloseCompletionAsync)}()", UserFileSystemPath, default, operationContext); + Logger.LogDebug($"{nameof(IFileWindows)}.{nameof(CloseCompletionAsync)}()", UserFileSystemPath, default, operationContext); } /// @@ -58,7 +58,7 @@ public async Task ReadAsync(Stream output, long offset, long length, ITransferDa // Buffer size must be multiple of 4096 bytes for optimal performance. const int bufferSize = 0x500000; // 5Mb. - using (Client.IWebResponse response = await Program.DavClient.DownloadAsync(new Uri(RemoteStoragePath), offset, length)) + using (Client.IWebResponse response = await Program.DavClient.DownloadAsync(new Uri(RemoteStoragePath), offset, length, cancellationToken)) { using (Stream stream = await response.GetResponseStreamAsync()) { @@ -136,7 +136,7 @@ public async Task WriteAsync(IFileMetadata fileMetadata, Stream content = null, // Setting position to 0 is required in case of retry. content.Position = 0; await content.CopyToAsync(outputStream); - }, null, content.Length, 0, -1, lockTokens, oldEtag); + }, null, content.Length, 0, -1, lockTokens, oldEtag, cancellationToken); await placeholder.Properties.AddOrUpdateAsync("ETag", newEtag); } diff --git a/Windows/WebDAVDrive/WebDAVDrive/VirtualFileSystemItem.cs b/Windows/WebDAVDrive/WebDAVDrive/VirtualFileSystemItem.cs index afd269f..b9085bb 100644 --- a/Windows/WebDAVDrive/WebDAVDrive/VirtualFileSystemItem.cs +++ b/Windows/WebDAVDrive/WebDAVDrive/VirtualFileSystemItem.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -73,7 +74,7 @@ public async Task MoveToCompletionAsync(string targetUserFileSystemPath, byte[] string remoteStorageOldPath = RemoteStoragePath; string remoteStorageNewPath = Mapping.MapPath(userFileSystemNewPath); - await Program.DavClient.MoveToAsync(new Uri(remoteStorageOldPath), new Uri(remoteStorageNewPath), true); + await Program.DavClient.MoveToAsync(new Uri(remoteStorageOldPath), new Uri(remoteStorageNewPath), true, null, cancellationToken); Logger.LogMessage("Moved in the remote storage succesefully", userFileSystemOldPath, targetUserFileSystemPath, operationContext); } @@ -103,7 +104,7 @@ public async Task DeleteCompletionAsync(IOperationContext operationContext, IInS try { - await Program.DavClient.DeleteAsync(new Uri(RemoteStoragePath)); + await Program.DavClient.DeleteAsync(new Uri(RemoteStoragePath), null, cancellationToken); Logger.LogMessage("Deleted in the remote storage succesefully", UserFileSystemPath, default, operationContext); } catch (WebDavHttpException ex) @@ -113,6 +114,138 @@ public async Task DeleteCompletionAsync(IOperationContext operationContext, IInS } } + + public async Task GetThumbnailAsync(uint size) + { + byte[] thumbnail = null; + + string[] exts = Program.Settings.RequestThumbnailsFor.Trim().Split("|"); + string ext = System.IO.Path.GetExtension(UserFileSystemPath).TrimStart('.'); + + if (exts.Any(ext.Equals) || exts.Any("*".Equals)) + { + string ThumbnailGeneratorUrl = Program.Settings.ThumbnailGeneratorUrl.Replace("{thumbnail width}", "" + size).Replace("{thumbnail height}", "" + size); + string filePathRemote = ThumbnailGeneratorUrl.Replace("{path to file}", WebDAVDrive.Mapping.MapPath(UserFileSystemPath)); + + try + { + using (IWebResponse response = await Program.DavClient.DownloadAsync(new Uri(filePathRemote))) + { + using (Stream stream = await response.GetResponseStreamAsync()) + { + thumbnail = await StreamToByteArrayAsync(stream); + } + } + } + catch (System.Net.WebException we) + { + Logger.LogMessage(we.Message, UserFileSystemPath); + } + catch (Exception e) + { + Logger.LogError($"Failed to load thumbnail {size}px", UserFileSystemPath, null, e); + } + } + + string thumbnailResult = thumbnail != null ? "Success" : "Not Impl"; + Logger.LogMessage($"{nameof(VirtualEngine)}.{nameof(GetThumbnailAsync)}() - {thumbnailResult}", UserFileSystemPath); + + return thumbnail; + } + + /// + public async Task> GetPropertiesAsync() + { + // For this method to be called you need to register a properties handler. + // See method description for more details. + + Logger.LogDebug($"{nameof(IFileSystemItem)}.{nameof(GetPropertiesAsync)}()", UserFileSystemPath); + + IList props = new List(); + + if (Engine.Placeholders.TryGetItem(UserFileSystemPath, out PlaceholderItem placeholder)) + { + + // Read LockInfo and choose the lock icon. + string lockIconName = null; + if (placeholder.Properties.TryGetValue("LockInfo", out IDataItem propLockInfo)) + { + // The file is locked by this user. + lockIconName = "Locked.ico"; + } + else if (placeholder.Properties.TryGetValue("ThirdPartyLockInfo", out propLockInfo)) + { + // The file is locked by somebody else on the server. + lockIconName = "LockedByAnotherUser.ico"; + } + + if (propLockInfo != null && propLockInfo.TryGetValue(out ServerLockInfo lockInfo)) + { + + // Get Lock Owner. + FileSystemItemPropertyData propertyLockOwner = new FileSystemItemPropertyData() + { + Id = (int)CustomColumnIds.LockOwnerIcon, + Value = lockInfo.Owner, + IconResource = System.IO.Path.Combine(Engine.IconsFolderPath, lockIconName) + }; + props.Add(propertyLockOwner); + + // Get Lock Expires. + FileSystemItemPropertyData propertyLockExpires = new FileSystemItemPropertyData() + { + Id = (int)CustomColumnIds.LockExpirationDate, + Value = lockInfo.LockExpirationDateUtc.ToString(), + IconResource = System.IO.Path.Combine(Engine.IconsFolderPath, "Empty.ico") + }; + props.Add(propertyLockExpires); + } + + + // Read LockMode. + if (placeholder.Properties.TryGetValue("LockMode", out IDataItem propLockMode)) + { + if (propLockMode.TryGetValue(out LockMode lockMode) && lockMode != LockMode.None) + { + FileSystemItemPropertyData propertyLockMode = new FileSystemItemPropertyData() + { + Id = (int)CustomColumnIds.LockScope, + Value = "Locked", + IconResource = System.IO.Path.Combine(Engine.IconsFolderPath, "Empty.ico") + }; + props.Add(propertyLockMode); + } + } + + // Read ETag. + if (placeholder.Properties.TryGetValue("ETag", out IDataItem propETag)) + { + if (propETag.TryGetValue(out string eTag)) + { + FileSystemItemPropertyData propertyETag = new FileSystemItemPropertyData() + { + Id = (int)CustomColumnIds.ETag, + Value = eTag, + IconResource = System.IO.Path.Combine(Engine.IconsFolderPath, "Empty.ico") + }; + props.Add(propertyETag); + } + } + } + + return props; + } + + private static async Task StreamToByteArrayAsync(Stream stream) + { + using (MemoryStream memoryStream = new()) + { + await stream.CopyToAsync(memoryStream); + return memoryStream.ToArray(); + } + } + + /// public Task GetMetadataAsync() { @@ -130,7 +263,7 @@ public async Task LockAsync(LockMode lockMode, IOperationContext operationContex // Save the lock token and other lock info received from the remote storage on the client. // Supply the lock-token as part of each remote storage update in IFile.WriteAsync() method. - LockInfo lockInfo = await Program.DavClient.LockAsync(new Uri(RemoteStoragePath), LockScope.Exclusive, false, null, TimeSpan.MaxValue); + LockInfo lockInfo = await Program.DavClient.LockAsync(new Uri(RemoteStoragePath), LockScope.Exclusive, false, null, TimeSpan.MaxValue, cancellationToken); ServerLockInfo serverLockInfo = new ServerLockInfo { LockToken = lockInfo.LockToken.LockToken, @@ -181,7 +314,7 @@ public async Task UnlockAsync(IOperationContext operationContext = null, Cancell // Unlock the item in the remote storage. try { - await Program.DavClient.UnlockAsync(new Uri(RemoteStoragePath), lockTokens); + await Program.DavClient.UnlockAsync(new Uri(RemoteStoragePath), lockTokens, cancellationToken); Logger.LogMessage("Unlocked in the remote storage succesefully", UserFileSystemPath, default, operationContext); } catch (ITHit.WebDAV.Client.Exceptions.ConflictException) diff --git a/Windows/WebDAVDrive/WebDAVDrive/VirtualFolder.cs b/Windows/WebDAVDrive/WebDAVDrive/VirtualFolder.cs index 2319b61..1150598 100644 --- a/Windows/WebDAVDrive/WebDAVDrive/VirtualFolder.cs +++ b/Windows/WebDAVDrive/WebDAVDrive/VirtualFolder.cs @@ -47,7 +47,7 @@ public async Task CreateFileAsync(IFileMetadata fileMetadata, Stream con content.Position = 0; await content.CopyToAsync(outputStream); } - }, null, contentLength); + }, null, contentLength, 0, -1, null, null, cancellationToken); // Store ETag it in persistent placeholder properties untill the next update. PlaceholderItem placeholder = Engine.Placeholders.GetItem(userFileSystemNewItemPath); @@ -64,7 +64,7 @@ public async Task CreateFolderAsync(IFolderMetadata folderMetadata, IInS Logger.LogMessage($"{nameof(IFolder)}.{nameof(CreateFolderAsync)}()", userFileSystemNewItemPath); Uri newFolderUri = new Uri(new Uri(RemoteStoragePath), folderMetadata.Name); - await Program.DavClient.CreateFolderAsync(newFolderUri); + await Program.DavClient.CreateFolderAsync(newFolderUri, null, cancellationToken); // WebDAV server sypically does not provide eTags for folders. // Store ETag (if any) unlil the next update here. @@ -116,7 +116,7 @@ public async Task GetChildrenAsync(string pattern, IOperationContext operationCo public async Task> EnumerateChildrenAsync(string pattern, CancellationToken cancellationToken = default) { // WebDAV Client lib will retry the request in case authentication is requested by the server. - Client.IHierarchyItem[] remoteStorageChildren = await Program.DavClient.GetChildrenAsync(new Uri(RemoteStoragePath)); + Client.IHierarchyItem[] remoteStorageChildren = await Program.DavClient.GetChildrenAsync(new Uri(RemoteStoragePath), false, null, cancellationToken); List userFileSystemChildren = new List(); diff --git a/Windows/WebDAVDrive/WebDAVDrive/WebDAVDrive.csproj b/Windows/WebDAVDrive/WebDAVDrive/WebDAVDrive.csproj index d3e4416..94e644f 100644 --- a/Windows/WebDAVDrive/WebDAVDrive/WebDAVDrive.csproj +++ b/Windows/WebDAVDrive/WebDAVDrive/WebDAVDrive.csproj @@ -1,13 +1,12 @@ Exe - net5.0-windows10.0.18362.0 - 10.0.19041.0 + net5.0-windows10.0.19041.0 IT Hit LTD. IT Hit LTD. WebDAV Drive IT Hit LTD. - AnyCPU + x64 A virtual file system in .NET/C# that displays documents from a WebDAV server. You can edit documents, upload and download files as well as manage folders structure using Windows File Manager. This application provides automatic documents locking for Microsoft Office and AutoCAD documents. It supports synchronization, on-demand loading, selective offline files support, upload and download progress, and error reporting. It synchronizes files and folders both from a WebDAV server to the local user file system and from the local user file system to WebDAV server. @@ -33,7 +32,7 @@ - + @@ -70,4 +69,7 @@ Always + + + \ No newline at end of file diff --git a/Windows/WebDAVDrive/WebDAVDrive/WebDAVDrive_TemporaryKey.pfx b/Windows/WebDAVDrive/WebDAVDrive/WebDAVDrive_TemporaryKey.pfx new file mode 100644 index 0000000000000000000000000000000000000000..1b3d0539406c13ba65c6ef53469f28a8a35a0293 GIT binary patch literal 2528 zcmZXUc|6p67sr2N#xj->MvL4*gt6RaY@=jfhJ+#eMfRn!4#`~G7+VM-k?gxL$d)xb z*<-|&M5ru}k|kN6sn_$|`+8o_>-#$2^EvNx&iDKM=X+3O=o}pgj3PtR8K7cO+EL%x zK#ZVVGBgH4hDOqG7>Z1P@INW~bqJaM6AdrX>^%d^f4W$iL3Fuf$O(!JIY99;F#i|R z4u?UQun3PN6&+qBIy%O22pKY?VU?;(R9cHNv#Bj;&B=n#0>f{~Zz2m+zc^qvcvddW zC`Y8kX9Q{*XK-j_TSQ@y(Z6$F`5!p z^s|P^i9WFcIp#Gitd{S0)HRU4)3H935jFaFywJq{n!9Y|V|dZithbi8I$6ScE0Y>U zOs!?&=QXEVB0lM{d^4EeO`*={vEZBOqg?K7Wyd#;$Vtf;h;A62Ikz;MSkoMMgSTMu zA>*Em2*TdO)K+%z$Pqp#!>x&l&fs{grgV~6)@1PQRgY<%7ALZT$tF6Fs^O$;$^YbK z8&z61A{X){aK%@9!270RL`nH5=})@7O}t~w(T(*qu5p=$l%^nE^QM~|Bk0Iu%Y=ju z2H`l%;-+z=2a!+JiqqBB;MQ!r=Q3=2jn%n6-5^F(OeN?UG6Flz?Y&UW`PB~jI?aeP zc|vYE9wQy8@;m3D+>ogH-jXfQmuT6Y`Qf2zS3k*IXFJv#ccX>G#3%8Yox5u)!QOQs zGy3@xi$P&RKL4&6ki5Ek%H|a1*iDzU=+=@HSNOrCMe^NJtz<{&<&@YvoyPRLK3QY2 zP`W8)KXgDQl0+(d`3T`BY&5MYAmd_fR@}bxkj;TSV59T!PS#ouU0=~08?Y;gIT_Y( zBDoWl9y|>$IAv~xjV`Gu7NTq6BfL1m0m z3D-VHI7ZA4`HFl1Dx?3RsW{|1&jq&)Xs&*rVC-ynEY-j% zgi8w=#a&Hd5>{qM^T``Npazq)*?!o&@5ZAz`SSA3&wfHX52fk&E9}XuC6@>Txw$~Z( zujB&jwvCe54_rL~FCNGju2Ut;c?b~Ju7IG60pr3oRk~lO1@``|DnR1xWyGhUf=Y?C zIk){_U)IHpkaYFvy41x({?5ccf+iS$*%OYE5hLO&Uo1}66$Tl!;+eEw`V?!4-Ap~) zDOtMT&}>>Yt2BThbf0sPe0@ppz0UE~L_j~5;$mg@y5qq#JnxD`-+*6_rG+Wcz0maD za=r0{q10Z!L4s#8X$~8vA8e)IcE5J3mUyyr&T24`BkuWd$|qc=xpOz_7TV6CRdIpy ze*1N6ucGo*v29(8Y}JsfW}-@22$e>|`I$BJ)`WzP&~Ub>ufLrl*N?9jRhP5Igy;f8={KYQjxZGmd_ad=eQfpGUa+!&}N<rKbQO8IO1|ta>TyelUo7BL}B$ zdyV>PW0l_Lv}y&LlqlabRBfxpbr&U>|C?+Yn+#*^ar*12!J|TQK%hl`^Rdke#83_S z^36!Xy^>(z+c$Qud^7J%k(!LwnE5P1Xj(RE<9yZc>gvzNN;))DAWj|i14^l4Ex0~6 z(9wpbCufZ`cWlr@L0>lV1oZ*tx6aVDLHSqM-n$2|+NbS$?U5CIDoDkiA_Vv?LXZ!5 zcwoPog4~?4oZeff8!>#dXc&_qJ|Fo4DHQPZmY*AkKzCb1S4(Bl6T?Di6y z*`sT%ckw~(FeQMc9U0KNrlUGKT`lL_Y^PLWarLsyv;|0uC-rn9pg};mR}fNMT_dPM4o_c3Nt2U)w` zD#o6mMcJ>l6ypX3)g>rf0*T3g**0}P>dA}ga-w?NvAn(x7Ad}CZ$8fgr>35DlcRuR zw3yVr;mRjg zO?TZi>^sT@Im7@P)TCH!K3|m(EHgX8;}^A_j#5DJp%@wH#bIDN9ta#wjB?pOymX=9 oyjH7-(tuS}A%y~g-|Wo%n18K_4&201y82y|??qC3)z6RjZ+Jy$ZU6uP literal 0 HcmV?d00001 diff --git a/Windows/WebDAVDrive/WebDAVDrive/appsettings.json b/Windows/WebDAVDrive/WebDAVDrive/appsettings.json index fad5e70..932635f 100644 --- a/Windows/WebDAVDrive/WebDAVDrive/appsettings.json +++ b/Windows/WebDAVDrive/WebDAVDrive/appsettings.json @@ -7,7 +7,7 @@ // License to activate the IT Hit User File System Engine. // Set the license content directly as value. Make sure to escape quotes: \": - // "UserFileSystemLicense": "To run the sample open the project in Visual Studio and run the project. The application adds an application to the macOS Status Bar. To mount the file system select the 'Install Extension' command in the Status Bar.

Virtual File System Mac in .NET/C#

Note, that every File Provider Extension runs in a sandbox, so access to the local filesystem restricted by OS except Downloads, Pictures, Music, Movies public directories.

+

Troubleshooting

+

If you experience issues on application start it may be caused by an incorrect app configuration. You can find what may be wrong using a macOS Console:

+

Virtual Drive .NET macOS Console

+

If your application started successfully but you experience issues with the file system you may need to filter logs to find information sent by the file system provider by using the 'ITHit' search:

+

Virtual File System macOS Console

+

You can select and copy the console output and submit it to the Help & Support system.

Next Article:

Virtual Drive Sample in .NET, C#