diff --git a/Common/Common.csproj b/Common/Common.csproj index 6d53edc..398eef2 100644 --- a/Common/Common.csproj +++ b/Common/Common.csproj @@ -9,6 +9,6 @@ ITHit.FileSystem.Samples.Common - + \ No newline at end of file diff --git a/Common/FileSystemItemMetadataExt.cs b/Common/FileSystemItemMetadataExt.cs index 0fb4cc2..aa88e67 100644 --- a/Common/FileSystemItemMetadataExt.cs +++ b/Common/FileSystemItemMetadataExt.cs @@ -13,7 +13,7 @@ namespace ITHit.FileSystem.Samples.Common public class FileSystemItemMetadataExt : IFileSystemItemMetadata { /// - public byte[] ItemId { get; set; } + public byte[] RemoteStorageItemId { get; set; } /// public string Name { get; set; } @@ -37,14 +37,13 @@ public class FileSystemItemMetadataExt : IFileSystemItemMetadata public DateTimeOffset ChangeTime { get; set; } /// - /// Server ETag. + /// Lock info. /// - public string ETag { get; set; } - - /// - /// Indicates if the item is locked in the remote storage. - /// - public bool IsLocked { get; set; } = false; + /// + /// If the item is locked, this property contains info about the lock. + /// It is set to null otherwise. + /// + public ServerLockInfo Lock { get; set; } /// /// Custom columns data to be displayed in the file manager. diff --git a/Common/ServerLockInfo.cs b/Common/ServerLockInfo.cs new file mode 100644 index 0000000..373a48e --- /dev/null +++ b/Common/ServerLockInfo.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ITHit.FileSystem.Samples.Common +{ + /// + /// Information about the lock returned from the remote storage as a result of the lock operation. + /// + public class ServerLockInfo + { + /// + /// Lock-token. Must be supplied during the item update and unlock operations. + /// + public string LockToken { get; set; } + + /// + /// Lock expidation date/time returned by the server. + /// + public DateTimeOffset LockExpirationDateUtc { get; set; } + + /// + /// Name of the user that locked the item. + /// + public string Owner { get; set; } + + /// + /// True if the item is locked exclusively. False in case the item has a shared lock. + /// + public bool Exclusive { get; set; } = true; + } +} diff --git a/Common/Settings.cs b/Common/Settings.cs index 2c7dea7..8bfc451 100644 --- a/Common/Settings.cs +++ b/Common/Settings.cs @@ -50,15 +50,10 @@ public class Settings public string ProductName { get; set; } /// - /// Automatically lock the file in remote storage when a file handle is being opened for writing, unlock on close. + /// 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; } - /// - /// Path to the folder that stores custom data associated with files and folders. - /// - public string ServerDataFolderPath { get; set; } - /// /// Communication channel name is used by RPC to establish connection over named pipes. /// diff --git a/Windows/Common/Core/Common.Windows.Core.csproj b/Windows/Common/Core/Common.Windows.Core.csproj index 2b12f7d..4e736a8 100644 --- a/Windows/Common/Core/Common.Windows.Core.csproj +++ b/Windows/Common/Core/Common.Windows.Core.csproj @@ -7,13 +7,14 @@ IT Hit User File System IT Hit LTD. ITHit.FileSystem.Samples.Common.Windows.Core + AnyCPU;x64 - + \ No newline at end of file diff --git a/Windows/Common/Core/Logger.cs b/Windows/Common/Core/Logger.cs index 9db98c2..43d3af9 100644 --- a/Windows/Common/Core/Logger.cs +++ b/Windows/Common/Core/Logger.cs @@ -11,7 +11,7 @@ namespace ITHit.FileSystem.Samples.Common.Windows { /// - /// Implements unified logging. + /// Implements logging. /// public class Logger : ILogger { @@ -42,7 +42,7 @@ public void LogError(string message, string sourcePath = null, string targetPath string att = FsPath.Exists(sourcePath) ? FsPath.GetAttString(sourcePath) : null; string process = null; byte? priorityHint = null; - long? clientFileId = null; + ulong? clientFileId = null; if (operationContext != null) { @@ -61,7 +61,7 @@ public void LogMessage(string message, string sourcePath = null, string targetPa string att = FsPath.Exists(sourcePath) ? FsPath.GetAttString(sourcePath) : null; string process = null; byte? priorityHint = null; - long? clientFileId = null; + ulong? clientFileId = null; string size = null; if (operationContext != null) diff --git a/Windows/Common/Core/Registrar.cs b/Windows/Common/Core/Registrar.cs index 18093d5..65a3b1e 100644 --- a/Windows/Common/Core/Registrar.cs +++ b/Windows/Common/Core/Registrar.cs @@ -59,7 +59,7 @@ public static async Task RegisterAsync(string syncRootId, string path, string di // Adds columns to Windows File Manager. - // Show/hide columns in the "More..." context menu on the columns header. + // Show/hide columns in the "More..." context menu on the columns header in Windows Explorer. var proDefinitions = storageInfo.StorageProviderItemPropertyDefinitions; proDefinitions.Add(new StorageProviderItemPropertyDefinition { DisplayNameResource = "Lock Owner" , Id = (int)CustomColumnIds.LockOwnerIcon }); proDefinitions.Add(new StorageProviderItemPropertyDefinition { DisplayNameResource = "Lock Scope" , Id = (int)CustomColumnIds.LockScope }); diff --git a/Windows/Common/Rpc.Proto/Common.Windows.Rpc.Proto.csproj b/Windows/Common/Rpc.Proto/Common.Windows.Rpc.Proto.csproj index 94f7234..b0f2161 100644 --- a/Windows/Common/Rpc.Proto/Common.Windows.Rpc.Proto.csproj +++ b/Windows/Common/Rpc.Proto/Common.Windows.Rpc.Proto.csproj @@ -9,6 +9,7 @@ IT Hit User File System IT Hit LTD. ITHit.FileSystem.Samples.Common.Windows.Rpc.Proto + AnyCPU;x64 diff --git a/Windows/Common/Rpc.Proto/ShellExtension.proto b/Windows/Common/Rpc.Proto/ShellExtension.proto index 236d653..a2f77be 100644 --- a/Windows/Common/Rpc.Proto/ShellExtension.proto +++ b/Windows/Common/Rpc.Proto/ShellExtension.proto @@ -6,6 +6,9 @@ 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) {} } message ItemsPathList { @@ -25,5 +28,34 @@ 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; +} diff --git a/Windows/Common/Rpc/Common.Windows.Rpc.csproj b/Windows/Common/Rpc/Common.Windows.Rpc.csproj index d2d7118..b468cd6 100644 --- a/Windows/Common/Rpc/Common.Windows.Rpc.csproj +++ b/Windows/Common/Rpc/Common.Windows.Rpc.csproj @@ -7,6 +7,7 @@ IT Hit User File System IT Hit LTD. ITHit.FileSystem.Samples.Common.Windows.Rpc + AnyCPU;x64 diff --git a/Windows/Common/ShellExtension/Common.Windows.ShellExtension.csproj b/Windows/Common/ShellExtension/Common.Windows.ShellExtension.csproj index b90bbdc..868994f 100644 --- a/Windows/Common/ShellExtension/Common.Windows.ShellExtension.csproj +++ b/Windows/Common/ShellExtension/Common.Windows.ShellExtension.csproj @@ -7,6 +7,7 @@ IT Hit User File System IT Hit LTD. ITHit.FileSystem.Samples.Common.Windows.ShellExtension + AnyCPU;x64 @@ -19,7 +20,7 @@ - + diff --git a/Windows/Common/ShellExtension/ContextMenusProviderBase.cs b/Windows/Common/ShellExtension/ContextMenusProviderBase.cs index e50feba..66ffb9e 100644 --- a/Windows/Common/ShellExtension/ContextMenusProviderBase.cs +++ b/Windows/Common/ShellExtension/ContextMenusProviderBase.cs @@ -5,6 +5,9 @@ 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 { @@ -37,7 +40,7 @@ public abstract class ContextMenusProviderBase : IExplorerCommand /// List of selected items. public abstract Task GetIconAsync(IEnumerable filesPath); - protected ILog Log { get; } + protected ILogger Log { get; } /// /// Creates instance of this class. @@ -46,7 +49,7 @@ public ContextMenusProviderBase() { ReferenceManager.AddObjectReference(); - Log = ShellExtensionConfiguration.GetLogger("ContextMenus.log"); + Log = new GrpcLogger("Context Menu Provider"); } ~ContextMenusProviderBase() @@ -65,7 +68,7 @@ public int GetTitle(IShellItemArray itemArray, out string title) if (!files.Any() || !files.All(File.Exists)) return WinError.E_NOTIMPL; - Log.Info($"\nGetting menu title for {string.Join(",", files)}"); + Log.LogMessage($"{nameof(ContextMenusProviderBase)}.{nameof(GetTitle)}()", string.Join(", ", files)); title = GetMenuTitleAsync(files).GetAwaiter().GetResult(); @@ -80,7 +83,7 @@ public int GetTitle(IShellItemArray itemArray, out string title) } catch (Exception ex) { - Log.Error(ex); + Log.LogError("", null, null, ex); return WinError.E_FAIL; } } @@ -94,7 +97,7 @@ public int Invoke(IShellItemArray itemArray, object bindCtx) if (!files.Any() || !files.All(File.Exists)) return WinError.E_NOTIMPL; - Log.Info($"\nInvoke menu command for {string.Join(",", files)}"); + Log.LogMessage($"{nameof(ContextMenusProviderBase)}.{nameof(Invoke)}()", string.Join(", ", files)); InvokeMenuCommandAsync(files).GetAwaiter().GetResult(); @@ -106,7 +109,7 @@ public int Invoke(IShellItemArray itemArray, object bindCtx) } catch (Exception ex) { - Log.Error(ex); + Log.LogError("", null, null, ex); return WinError.E_FAIL; } } @@ -129,7 +132,7 @@ public int GetState(IShellItemArray itemArray, bool okToBeSlow, out EXPCMDSTATE return WinError.E_NOTIMPL; } - Log.Info($"\nGetting menu state for {string.Join(",", files)}"); + Log.LogMessage($"{nameof(ContextMenusProviderBase)}.{nameof(GetState)}()", string.Join(", ", files)); commandState = GetMenuStateAsync(files).GetAwaiter().GetResult(); @@ -141,7 +144,7 @@ public int GetState(IShellItemArray itemArray, bool okToBeSlow, out EXPCMDSTATE } catch (Exception ex) { - Log.Error(ex); + Log.LogError("", null, null, ex); return WinError.E_FAIL; } } @@ -164,7 +167,7 @@ public int GetIcon(IShellItemArray itemArray, out string resourceString) if (!files.Any() || !files.All(File.Exists)) return WinError.E_NOTIMPL; - Log.Info($"\nGetting menu icon for {string.Join(",", files)}"); + Log.LogMessage($"{nameof(ContextMenusProviderBase)}.{nameof(GetIcon)}()", string.Join(", ", files)); resourceString = GetIconAsync(files).GetAwaiter().GetResult(); @@ -179,7 +182,7 @@ public int GetIcon(IShellItemArray itemArray, out string resourceString) } catch (Exception ex) { - Log.Error(ex); + Log.LogError("", null, null, ex); return WinError.E_FAIL; } } @@ -204,6 +207,5 @@ 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 new file mode 100644 index 0000000..aa54511 --- /dev/null +++ b/Windows/Common/ShellExtension/GrpcLogger.cs @@ -0,0 +1,80 @@ +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/ShellExtensionConfiguration.cs b/Windows/Common/ShellExtension/ShellExtensionConfiguration.cs index 64370a2..f67ce44 100644 --- a/Windows/Common/ShellExtension/ShellExtensionConfiguration.cs +++ b/Windows/Common/ShellExtension/ShellExtensionConfiguration.cs @@ -20,12 +20,10 @@ public static class ShellExtensionConfiguration /// public static Settings AppSettings { get; private set; } - private static ILog Log { get; set; } - /// /// Initialize or load settings. /// - public static void Initialize(Settings settings = null, ILog log = null) + public static void Initialize(Settings settings = null) { if (settings != null) { @@ -35,7 +33,6 @@ public static void Initialize(Settings settings = null, ILog log = null) { AppSettings = Load(); } - Log = log; } /// @@ -50,53 +47,6 @@ public static bool IsVirtualDriveFolder(string path) return !string.IsNullOrEmpty(path) && path.TrimStart().StartsWith(rootPath); } - /// - /// Returns logger with configured repository. - /// - public static ILog GetLogger(string logName) - { - if (Log != null) - return Log; - - string assemblyPath = Path.GetDirectoryName(typeof(ShellExtensionConfiguration).Assembly.Location); - Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository(typeof(ShellExtensionConfiguration).Assembly); - - if (!hierarchy.Configured) - { - PatternLayout patternLayout = new PatternLayout(); - patternLayout.ConversionPattern = "%date [%thread] %-5level %logger - %message%newline"; - patternLayout.ActivateOptions(); - - RollingFileAppender roller = new RollingFileAppender(); - roller.AppendToFile = true; - if (assemblyPath.Contains("WindowsApps")) - { - roller.File = - Path.Combine( - Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), - AppSettings.AppID), - logName); - } - else - { - roller.File = Path.Combine(assemblyPath, logName); - } - roller.Layout = patternLayout; - roller.MaxSizeRollBackups = 5; - roller.MaximumFileSize = "10MB"; - roller.RollingStyle = RollingFileAppender.RollingMode.Size; - roller.StaticLogFileName = true; - roller.ActivateOptions(); - hierarchy.Root.AddAppender(roller); - - hierarchy.Root.Level = Level.Info; - hierarchy.Configured = true; - } - - return LogManager.GetLogger(typeof(ShellExtensionConfiguration)); - } - private static Settings Load() { string assemblyPath = Path.GetDirectoryName(typeof(ShellExtensionConfiguration).Assembly.Location); diff --git a/Windows/Common/ShellExtension/ThumbnailProviderBase.cs b/Windows/Common/ShellExtension/ThumbnailProviderBase.cs index d5d2dcb..40d9f80 100644 --- a/Windows/Common/ShellExtension/ThumbnailProviderBase.cs +++ b/Windows/Common/ShellExtension/ThumbnailProviderBase.cs @@ -14,13 +14,13 @@ public abstract class ThumbnailProviderBase : InitializedWithItem, IThumbnailPro private string filePath = null; - protected ILog Log { get; } + protected ILogger Log { get; } public ThumbnailProviderBase() { ReferenceManager.AddObjectReference(); - Log = ShellExtensionConfiguration.GetLogger("ThumbnailProvider.log"); + Log = new GrpcLogger("Thumbnail Provider"); } ~ThumbnailProviderBase() @@ -57,7 +57,7 @@ public int GetThumbnail(uint cx, out IntPtr phbmp, out WTS_ALPHATYPE pdwAlpha) try { - Log.Info($"\nGetting thumbnail for {filePath}"); + //Log.LogMessage($"{nameof(ThumbnailProviderBase)}.{nameof(GetThumbnail)}()", filePath); byte[] bitmapData = GetThumbnailsAsync(filePath, cx).GetAwaiter().GetResult(); @@ -75,9 +75,13 @@ public int GetThumbnail(uint cx, out IntPtr phbmp, out WTS_ALPHATYPE pdwAlpha) return WinError.S_OK; } + catch (NotImplementedException) + { + return WinError.E_FAIL; + } catch (Exception ex) { - Log.Error(ex); + Log.LogError("", null, null, ex); return WinError.E_FAIL; } } diff --git a/Windows/Common/VirtualDrive/Common.Windows.VirtualDrive.csproj b/Windows/Common/VirtualDrive/Common.Windows.VirtualDrive.csproj index fbf5f18..40e33b2 100644 --- a/Windows/Common/VirtualDrive/Common.Windows.VirtualDrive.csproj +++ b/Windows/Common/VirtualDrive/Common.Windows.VirtualDrive.csproj @@ -7,13 +7,22 @@ IT Hit User File System IT Hit LTD. ITHit.FileSystem.Samples.Common.Windows.VirtualDrive + AnyCPU;x64 + + + + + + + + - + diff --git a/Windows/Common/VirtualDrive/ETagManager.cs b/Windows/Common/VirtualDrive/ETagManager.cs deleted file mode 100644 index 2e73c71..0000000 --- a/Windows/Common/VirtualDrive/ETagManager.cs +++ /dev/null @@ -1,165 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading.Tasks; - -namespace ITHit.FileSystem.Samples.Common.Windows -{ - /// - /// Provides methods for reading and writing ETags. - /// - public class ETagManager - { - private readonly string userFileSystemPath; - private readonly string userFileSystemRootPath; - private readonly string serverDataFolderPath; - private readonly ILogger logger; - private readonly string eTagFilePath; - - private const string eTagExt = ".etag"; - - /// - /// Creates instance of this class. - /// - /// User file system root path. - /// Folder where ETags are stored. - /// Logger. - public ETagManager(string userFileSystemPath, string serverDataFolderPath, string userFileSystemRootPath, ILogger logger) - { - this.userFileSystemPath = userFileSystemPath; - this.userFileSystemRootPath = userFileSystemRootPath; - this.serverDataFolderPath = serverDataFolderPath; - this.logger = logger; - this.eTagFilePath = $"{GetETagFilePath(userFileSystemPath)}{eTagExt}"; - } - - /// - /// Creates or updates ETag associated with the file. - /// - /// ETag. - /// - public async Task SetETagAsync(string eTag) - { - // Delete ETag file if null string value is passed. - if ( (eTag==null) && File.Exists(eTagFilePath)) - { - DeleteETag(); - } - - Directory.CreateDirectory(Path.GetDirectoryName(eTagFilePath)); - await File.WriteAllTextAsync(eTagFilePath, eTag); - } - - /// - /// Gets ETag associated with a file. - /// - /// ETag. - public async Task GetETagAsync() - { - if (!File.Exists(eTagFilePath)) - { - return null; - } - return await File.ReadAllTextAsync(eTagFilePath); - } - - /// - /// Returns true if the ETag file exists. False - otherwise. - /// - public bool ETagExists() - { - return File.Exists(eTagFilePath); - } - - public void EnsureETagExists() - { - Directory.CreateDirectory(Path.GetDirectoryName(eTagFilePath)); - using (FileStream stream = File.Open(eTagFilePath, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete)) - { - - } - } - - /// - /// Moves ETag to a new location. - /// - /// Path of the file in the user file system to move this Etag to. - internal async Task MoveToAsync(string userFileSystemNewPath) - { - // Move ETag file. - string eTagTargetPath = GetETagFilePath(userFileSystemNewPath); - string eTagFileTargetPath = $"{eTagTargetPath}{eTagExt}"; - - // Ensure the target directory exisit, in case we are moving into empty folder or which is offline. - new FileInfo(eTagFileTargetPath).Directory.Create(); - if (File.Exists(eTagFilePath)) - { - File.Move(eTagFilePath, eTagFileTargetPath, true); - } - - // If this is a folder, move all eTags in this folder. - string eTagSourceFolderPath = GetETagFilePath(userFileSystemPath); - if (Directory.Exists(eTagSourceFolderPath)) - { - Directory.Move(eTagSourceFolderPath, eTagTargetPath); - } - } - - /// - /// Deletes ETag associated with a file. - /// - internal void DeleteETag() - { - if (File.Exists(eTagFilePath)) - { - File.Delete(eTagFilePath); - } - - /* - // If this is a folder, delete all eTags in this folder. - string eTagFolderPath = GetETagFilePath(userFileSystemPath); - if (Directory.Exists(eTagFolderPath)) - { - Directory.Delete(eTagFolderPath, true); - } - */ - } - - /// - /// Returns true if the remote storage ETag and user file system ETags are equal. False - otherwise. - /// - /// Remote storage item info. - /// - /// ETag is updated on the server during every document update and is sent to client with a file. - /// During user file system to remote storage update it is sent back to the remote storage together with a modified content. - /// This ensures the changes in the remote storage are not overwritten if the document on the server is modified. - /// - public async Task ETagEqualsAsync(FileSystemItemMetadataExt remoteStorageItem) - { - string remoteStorageETag = remoteStorageItem.ETag; - string userFileSystemETag = await GetETagAsync(); - - if (string.IsNullOrEmpty(remoteStorageETag) && string.IsNullOrEmpty(userFileSystemETag)) - { - // We assume the remote storage is not using ETags or no ETag is ssociated with this file/folder. - return true; - } - - return remoteStorageETag == userFileSystemETag; - } - - /// - /// Gets ETag file path (without extension). - /// - /// Path of the file in user file system to get ETag path for. - private string GetETagFilePath(string userFileSystemPath) - { - // Get path relative to the virtual root. - string relativePath = userFileSystemPath.TrimEnd(Path.DirectorySeparatorChar).Substring( - userFileSystemRootPath.TrimEnd(Path.DirectorySeparatorChar).Length); - - return $"{serverDataFolderPath.TrimEnd(Path.DirectorySeparatorChar)}{relativePath}"; - } - } -} diff --git a/Windows/Common/VirtualDrive/ExternalDataManager.cs b/Windows/Common/VirtualDrive/ExternalDataManager.cs index 316c08e..b849907 100644 --- a/Windows/Common/VirtualDrive/ExternalDataManager.cs +++ b/Windows/Common/VirtualDrive/ExternalDataManager.cs @@ -20,7 +20,7 @@ namespace ITHit.FileSystem.Samples.Common.Windows /// and ) because of the MS Office and AutoCAD transactional save, /// which renames and deletes the file, so all custom data is lost. /// - public class ExternalDataManager + public class ExternalDataManager : IExternalDataManager { /// /// Path in user file system with which this custom data corresponds. @@ -61,9 +61,9 @@ public class ExternalDataManager /// Creates instance of this class. /// public ExternalDataManager( - string userFileSystemPath, - string serverDataFolderPath, - string userFileSystemRootPath, + string userFileSystemPath, + string serverDataFolderPath, + string userFileSystemRootPath, string iconsFolderPath, ILogger logger) { @@ -86,7 +86,7 @@ public ExternalDataManager( /// public bool IsNew { - get + get { // If ETag file exists, this means the data was succcesefully saved to the server. ETagManager eTagManager = new ETagManager(userFileSystemPath, serverDataFolderPath, userFileSystemRootPath, logger); @@ -95,7 +95,7 @@ public bool IsNew set { ETagManager eTagManager = new ETagManager(userFileSystemPath, serverDataFolderPath, userFileSystemRootPath, logger); - if(value) + if (value) { eTagManager.DeleteETag(); } @@ -106,6 +106,13 @@ public bool IsNew } } + /// + public byte[] RemoteStorageItemId + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + /* internal async Task ClearStateAsync() { @@ -214,16 +221,16 @@ public async Task SetLockPendingIconAsync(bool set) /// True to display the icon. False - to remove the icon. private async Task SetIconAsync(bool set, int id, string iconFile = null, string description = null) { - if(set) + if (set) { string iconFilePath = Path.Combine(iconsFolderPath, iconFile); FileSystemItemPropertyData propData = new FileSystemItemPropertyData((int)id, description, iconFilePath); - await SetCustomColumnsAsync(new []{ propData }); + await SetCustomColumnsAsync(new[] { propData }); } else { FileSystemItemPropertyData propData = new FileSystemItemPropertyData((int)id, null); - await RemoveCustomColumnsAsync(new []{ propData }); + await RemoveCustomColumnsAsync(new[] { propData }); } } @@ -310,7 +317,7 @@ private async Task SetCustomColumnsAsync(IEnumerable foreach (var curColumn in allColumns) { var column = customColumnsData.FirstOrDefault(x => x.Id == curColumn.Id); - if(column == null) + if (column == null) { newColumns.Add(curColumn); } @@ -392,7 +399,7 @@ private async Task ShowCustomColumnsAsync(IEnumerable @@ -511,7 +518,7 @@ public void Delete(bool recursive = true) ETagManager.DeleteETag(); LockManager.DeleteLock(); - if(recursive) + if (recursive) { // If this is a folder, delete all custom columns in this folder. string customColumnsFolderPath = GetColumnsFilePath(userFileSystemPath); diff --git a/Windows/Common/VirtualDrive/FilteredDocsMonitor.cs b/Windows/Common/VirtualDrive/FilteredDocsMonitor.cs deleted file mode 100644 index c354471..0000000 --- a/Windows/Common/VirtualDrive/FilteredDocsMonitor.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using log4net; - -using ITHit.FileSystem; -using ITHit.FileSystem.Windows; -using ITHit.FileSystem.Samples.Common.Windows; - -namespace ITHit.FileSystem.Samples.Common.Windows -{ - /// - /// Monitors MS Office amd AutoCAD file renames and updates in the user file system and sends changes to the remote storage. - /// Also monitors Notepad++ offline attribute removal. - /// - public class FilteredDocsMonitor : Logger, IDisposable - { - /// - /// User file system watcher. - /// - private readonly FileSystemWatcherQueued watcher = new FileSystemWatcherQueued(); - - /// - /// Engine. - /// - private readonly VirtualEngineBase engine; - - - /// - /// Creates instance of this class. - /// - /// User file system root path. - /// Engine. - /// Logger. - internal FilteredDocsMonitor(string userFileSystemRootPath, VirtualEngineBase engine, ILog log) - : base("Filtered Docs Monitor", log) - { - if(string.IsNullOrEmpty(userFileSystemRootPath)) - { - throw new ArgumentNullException(nameof(userFileSystemRootPath)); - } - this.engine = engine ?? throw new ArgumentNullException(nameof(engine)); - - watcher.IncludeSubdirectories = true; - watcher.Path = userFileSystemRootPath; - //watcher.Filter = "*.*"; - - // Some applications, such as Notpad++, remove the Offline attribute, - // Attributes filter is required to monitor the Changed event and convert the file back to the plceholder. - watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Attributes; - watcher.Error += Error; - watcher.Created += CreatedAsync; - watcher.Changed += ChangedAsync; - watcher.Deleted += DeletedAsync; - watcher.Renamed += RenamedAsync; - } - - /// - /// Starts monitoring the user file system. - /// - public void Start() - { - watcher.EnableRaisingEvents = true; - LogMessage("Started", watcher.Path); - } - - /// - /// Stops monitoring the user file system. - /// - public void Stop() - { - watcher.EnableRaisingEvents = false; - LogMessage("Stopped", watcher.Path); - } - - - /// - /// Called when a file or folder is created in the user file system. - /// - private async void CreatedAsync(object sender, FileSystemEventArgs e) - { - LogMessage(e.ChangeType.ToString(), e.FullPath); - } - - /// - /// Called when a file or folder is deleted in the user file system. - /// - private async void DeletedAsync(object sender, FileSystemEventArgs e) - { - LogMessage(e.ChangeType.ToString(), e.FullPath); - } - - /// - /// Called when an item is updated in the user file system. - /// - private async void ChangedAsync(object sender, FileSystemEventArgs e) - { - LogMessage($"{e.ChangeType}", e.FullPath); - await CreateOrUpdateAsync(sender, e); - } - - /// - /// Called when a file or folder is renamed in the user file system. - /// - private async void RenamedAsync(object sender, RenamedEventArgs e) - { - // If the item was previously filtered by EngineWindows.FilterAsync(), - // for example temp MS Office file was renamed SGE4274H -> file.xlsx, - // we need to convert the file to a pleaceholder and upload it to the remote storage. - - LogMessage($"{e.ChangeType}", e.OldFullPath, e.FullPath); - await CreateOrUpdateAsync(sender, e); - } - - /// - /// Creates the item in the remote storate if the item is new. - /// Updates the item in the remote storage if the item in not new. - /// - private async Task CreateOrUpdateAsync(object sender, FileSystemEventArgs e) - { - string userFileSystemPath = e.FullPath; - string userFileSystemOldPath = (e is RenamedEventArgs) ? (e as RenamedEventArgs).OldFullPath : null; - try - { - await ClientToServerSync.CreateOrUpdateAsync(userFileSystemPath, engine, this); - } - catch (FileNotFoundException ex) - { - // Some temp file was renamed or deleted. - LogMessage($"{e.ChangeType} failed", userFileSystemOldPath, userFileSystemPath); - } - catch (Exception ex) - { - - LogError($"{e.ChangeType} failed", userFileSystemOldPath, userFileSystemPath, ex); - } - } - - private void Error(object sender, ErrorEventArgs e) - { - LogError(null, null, null, e.GetException()); - } - - private bool disposedValue = false; // To detect redundant calls - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - watcher.Dispose(); - LogMessage($"Disposed"); - } - - // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. - // TODO: set large fields to null. - - disposedValue = true; - } - } - - // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. - // ~ServerChangesMonitor() - // { - // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - // Dispose(false); - // } - - // This code added to correctly implement the disposable pattern. - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - // TODO: uncomment the following line if the finalizer is overridden above. - // GC.SuppressFinalize(this); - } - } -} diff --git a/Windows/Common/VirtualDrive/FullSync/ClientToServerSync.cs b/Windows/Common/VirtualDrive/FullSync/ClientToServerSync.cs index d633c42..fb8edd4 100644 --- a/Windows/Common/VirtualDrive/FullSync/ClientToServerSync.cs +++ b/Windows/Common/VirtualDrive/FullSync/ClientToServerSync.cs @@ -62,11 +62,17 @@ internal async Task SyncronizeFolderAsync(string userFileSystemFolderPath) { try { - await CreateOrUpdateAsync(userFileSystemPath, engine, this); + await engine.ClientNotifications(userFileSystemPath, this).CreateOrUpdateAsync(); + } + catch (ClientLockFailedException ex) + { + // Blocked for create/update/lock/unlock operation from another thread. + // Thrown by CreateAsync()/UpdateAsync() call. This is a normal behaviour. + LogMessage(ex.Message, ex.Path); } catch (Exception ex) { - LogError("Update failed", userFileSystemPath, null, ex); + LogError("Creation or update failed", userFileSystemPath, null, ex); } // Synchronize subfolders. @@ -79,52 +85,9 @@ internal async Task SyncronizeFolderAsync(string userFileSystemFolderPath) } catch (Exception ex) { - LogError("Folder sync failed:", userFileSystemPath, null, ex); - } - } - } - - /// - /// Creates the item in the remote storate if the item is new. - /// Updates the item in the remote storage if the item in not new. - /// - /// File or folder path in the user file system. This can be a placeholder or a regular file/folder path. - /// Engine instance. - /// Logger instance. - internal static async Task CreateOrUpdateAsync(string userFileSystemPath, VirtualEngineBase engine, ILogger logger) - { - if (FsPath.Exists(userFileSystemPath) - && !FilterHelper.AvoidSync(userFileSystemPath)) - { - if (engine.ExternalDataManager(userFileSystemPath, logger).IsNew) - { - if (!PlaceholderItem.IsPlaceholder(userFileSystemPath)) - { - // New file/folder, creating new item in the remote storage. - await engine.ClientNotifications(userFileSystemPath, logger).CreateAsync(); - } - } - else - { - if (!PlaceholderItem.IsPlaceholder(userFileSystemPath)) - { - // The item was converted to a regular file during MS Office or AutoCAD transactiona save, - // converting it back to placeholder and uploading to the remote storage. - - logger.LogMessage("Converting to placeholder", userFileSystemPath); - PlaceholderItem.ConvertToPlaceholder(userFileSystemPath, null, null, false); - await engine.ClientNotifications(userFileSystemPath, logger).UpdateAsync(); - await engine.ExternalDataManager(userFileSystemPath).RefreshCustomColumnsAsync(); - } - else if (!PlaceholderItem.GetItem(userFileSystemPath).GetInSync()) - { - // The item is modified in the user file system, uploading to the remote storage. - await engine.ClientNotifications(userFileSystemPath, logger).UpdateAsync(); - await engine.ExternalDataManager(userFileSystemPath).RefreshCustomColumnsAsync(); - } + LogError("Folder sync failed", userFileSystemPath, null, ex); } } } - } } diff --git a/Windows/Common/VirtualDrive/FullSync/FullSyncService.cs b/Windows/Common/VirtualDrive/FullSync/FullSyncService.cs index 0dfc6b3..eecaa0f 100644 --- a/Windows/Common/VirtualDrive/FullSync/FullSyncService.cs +++ b/Windows/Common/VirtualDrive/FullSync/FullSyncService.cs @@ -120,7 +120,7 @@ private async void Timer_ElapsedAsync(object sender, System.Timers.ElapsedEventA await new ClientToServerSync(engine, Log).SyncronizeFolderAsync(userFileSystemRootPath); // UFS <- RS. Recursivery synchronize all updated/created/deleted file and folders present in the user file system. - await new ServerToClientSync(engine, Log).SyncronizeFolderAsync(userFileSystemRootPath); + //await new ServerToClientSync(engine, Log).SyncronizeFolderAsync(userFileSystemRootPath); InvokeSyncEvent(SynchronizationState.Idle); } diff --git a/Windows/Common/VirtualDrive/IMapping.cs b/Windows/Common/VirtualDrive/IMapping.cs index 72ca6ed..8567976 100644 --- a/Windows/Common/VirtualDrive/IMapping.cs +++ b/Windows/Common/VirtualDrive/IMapping.cs @@ -35,6 +35,17 @@ public interface IMapping /// User file system path. /// Remote storage item metadata. /// - Task IsModifiedAsync(string userFileSystemPath, FileSystemItemMetadataExt remoteStorageItemMetadata, ILogger logger); + //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/LockManager.cs b/Windows/Common/VirtualDrive/LockManager.cs deleted file mode 100644 index a87e6e9..0000000 --- a/Windows/Common/VirtualDrive/LockManager.cs +++ /dev/null @@ -1,243 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; - -using ITHit.FileSystem.Windows; - - -namespace ITHit.FileSystem.Samples.Common.Windows -{ - /// - /// Manages lock-info and lock-mode files that correspond with the file in the user file system. - /// - /// - /// - /// The lock info must be stored outside of , because the file in user file system - /// is renamed and deleted during MS Office transactional save operation. - /// The lock must remain regardless of the transactional save. - /// - /// The lock-info file contains information about the lock. The file may be locked either by this user or by another user. - /// The lock-mode file indicates if the file should be unlocked automatically. This file exists only if the file is locked by this user. - /// - public class LockManager - { - /// - /// Path in user file system with which this lock corresponds. - /// - private readonly string userFileSystemPath; - - /// - /// Path to the folder that stores custom data associated with files and folders. - /// - private readonly string serverDataFolderPath; - - /// - /// Virtual file system root path. - /// - private readonly string userFileSystemRootPath; - - /// - /// Path to the file that contains the lock mode. - /// - private readonly string lockModeFilePath; - - /// - /// Path to the file that contains the lock-token and other lock info. - /// - private readonly string lockInfoFilePath; - - /// - /// Logger. - /// - private readonly ILogger logger; - - /// - /// Lock-mode file extension. - /// - private const string lockModeExt = ".lockmode"; - - /// - /// Lock-info file extension. - /// - private const string lockInfoExt = ".lockinfo"; - - /// - /// Creates instance of this class. - /// - public LockManager(string userFileSystemPath, string serverDataFolderPath, string userFileSystemRootPath, ILogger logger) - { - this.userFileSystemPath = userFileSystemPath ?? throw new NullReferenceException(nameof(userFileSystemPath)); - this.serverDataFolderPath = serverDataFolderPath ?? throw new NullReferenceException(nameof(serverDataFolderPath)); - this.userFileSystemRootPath = userFileSystemRootPath ?? throw new NullReferenceException(nameof(userFileSystemRootPath)); - this.logger = logger ?? throw new NullReferenceException(nameof(logger)); - - // Get path relative to the virtual root. - string dataFile = GetLockFilePath(userFileSystemPath); - lockModeFilePath = $"{dataFile}{lockModeExt}"; - lockInfoFilePath = $"{dataFile}{lockInfoExt}"; - } - - /// - /// Sets lock mode associated with the file or folder. - /// - /// Lock mode. - public async Task SetLockModeAsync(LockMode lockMode) - { - if (lockMode == LockMode.None) - { - File.Delete(lockModeFilePath); - return; - } - - Directory.CreateDirectory(Path.GetDirectoryName(lockModeFilePath)); - await using (FileStream fileStream = File.Open(lockModeFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Delete)) - { - fileStream.WriteByte((byte)lockMode); - } - } - - /// - /// Gets lock mode associated with a file or folder. - /// - /// Lock mode or if the file is not locked. - public async Task GetLockModeAsync() - { - if(!File.Exists(lockModeFilePath) || (new FileInfo(lockModeFilePath).Length==0) ) - { - return LockMode.None; - } - - await using (FileStream fileStream = File.Open(lockModeFilePath, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.Read)) - { - return (LockMode)fileStream.ReadByte(); - } - } - - /// - /// Creates an empty lock-mode file to indicate that the lock was started by this user on this machine. - /// - public async Task SetLockPending() - { - Directory.CreateDirectory(Path.GetDirectoryName(lockModeFilePath)); - using (FileStream stream = File.Open(lockModeFilePath, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete)) - { - - } - } - - /// - /// Returns true if the file or folder is locked by this user on this machine. - /// - public async Task IsLockedByThisUserAsync() - { - return File.Exists(lockModeFilePath); - } - - /// - /// Gets lock info or null if the item is not locked. - /// - public async Task GetLockInfoAsync() - { - if(!File.Exists(lockInfoFilePath)) - { - return null; - } - await using (FileStream stream = File.Open(lockInfoFilePath, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.Read)) - { - stream.Seek(0, SeekOrigin.Begin); - return await JsonSerializer.DeserializeAsync(stream); - } - } - - /// - /// Sets lock info. - /// - /// Lock info. - public async Task SetLockInfoAsync(ServerLockInfo lockInfo) - { - await using (FileStream stream = File.Open(lockInfoFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Delete)) - { - stream.Seek(0, SeekOrigin.Begin); - await JsonSerializer.SerializeAsync(stream, lockInfo); - stream.SetLength(stream.Position); - } - } - - /// - /// Deletes lock-token and lock-mode files. - /// - public void DeleteLock() - { - if (File.Exists(lockModeFilePath)) - { - try - { - File.Delete(lockModeFilePath); - } - catch (Exception ex) - { - logger.LogError("Failed to delete lock-mode file.", userFileSystemPath, null, ex); - } - } - - if (File.Exists(lockInfoFilePath)) - { - try - { - File.Delete(lockInfoFilePath); - } - catch (Exception ex) - { - logger.LogError("Failed to delete lock-token file.", userFileSystemPath, null, ex); - } - } - } - - /// - /// Gets lock file and lock mode files path (without extension). - /// - /// Path of the file in user file system to get the path for. - private string GetLockFilePath(string userFileSystemPath) - { - // Get path relative to the virtual root. - string relativePath = userFileSystemPath.TrimEnd(Path.DirectorySeparatorChar).Substring( - userFileSystemRootPath.TrimEnd(Path.DirectorySeparatorChar).Length); - - return $"{serverDataFolderPath.TrimEnd(Path.DirectorySeparatorChar)}{relativePath}"; - } - - - /// - /// Moves custom columns to a new location. - /// - /// Path of the file in the user file system to move custom columns to. - internal async Task MoveToAsync(string userFileSystemNewPath) - { - // Move custom columns file. - string lockTargetPath = GetLockFilePath(userFileSystemNewPath); - string lockInfoFileTargetPath = $"{lockTargetPath}{lockModeExt}"; - string lockModeFileTargetPath = $"{lockTargetPath}{lockInfoExt}"; - - // Ensure the target directory exisit, in case we are moving into empty folder or which is offline. - new FileInfo(lockInfoFileTargetPath).Directory.Create(); - if (File.Exists(lockInfoFilePath)) - { - File.Move(lockInfoFilePath, lockInfoFileTargetPath, true); - } - if (File.Exists(lockModeFilePath)) - { - File.Move(lockModeFilePath, lockModeFileTargetPath, true); - } - - // If this is a folder, move all data in this folder. - string lockSourceFolderPath = GetLockFilePath(userFileSystemPath); - if (Directory.Exists(lockSourceFolderPath)) - { - Directory.Move(lockSourceFolderPath, lockTargetPath); - } - } - } -} diff --git a/Windows/Common/VirtualDrive/Rpc/GprcServerServiceImpl.cs b/Windows/Common/VirtualDrive/Rpc/GprcServerServiceImpl.cs index 3c43f67..b67eeeb 100644 --- a/Windows/Common/VirtualDrive/Rpc/GprcServerServiceImpl.cs +++ b/Windows/Common/VirtualDrive/Rpc/GprcServerServiceImpl.cs @@ -4,6 +4,8 @@ using ITHit.FileSystem.Windows; using ITHit.FileSystem.Samples.Common.Windows.Rpc.Generated; using Grpc.Core; +using System.Xml.Serialization; +using System.IO; namespace ITHit.FileSystem.Samples.Common.Windows.Rpc { @@ -113,7 +115,7 @@ public override async Task GetThumbnail(ThumbnailRequest thumbnailReq } catch (NotImplementedException) { - logger.LogMessage("Thumbnail is not implemented", path); + // Thumbnail is not implemented string msg = $"Thumbnail for {path} is not implemented"; throw new RpcException(new Status(StatusCode.Internal, msg)); } @@ -123,5 +125,54 @@ public override async Task GetThumbnail(ThumbnailRequest thumbnailReq 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)); + } + } } } diff --git a/Windows/Common/VirtualDrive/ServerLockInfo.cs b/Windows/Common/VirtualDrive/ServerLockInfo.cs deleted file mode 100644 index 01576bd..0000000 --- a/Windows/Common/VirtualDrive/ServerLockInfo.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace ITHit.FileSystem.Samples.Common.Windows -{ - /// - /// Information about the lock returned from the remote storage as a result of the lock operation. - /// - public class ServerLockInfo - { - /// - /// Lock-token. Must be supplied during the item update and unlock operations. - /// - public string LockToken { get; set; } - - /// - /// Lock expidation date/time returned by the server. - /// - public DateTimeOffset LockExpirationDateUtc { get; set; } - - /// - /// Name of the user that locked the item. - /// - public string Owner { get; set; } - - /// - /// True if the item is locked exclusively. False in case the item has a shared lock. - /// - public bool Exclusive { get; set; } = true; - - /// - /// Gets this lock info as a set of properties that can be visually displayed in the file manager. - /// - /// Lock icon path that will be displayed in file manager. - /// List of properties that represent this lock info. - public IEnumerable GetLockProperties(string lockIconPath) - { - List lockProps = new List(); - lockProps.Add(new FileSystemItemPropertyData((int)CustomColumnIds.LockOwnerIcon, Owner, lockIconPath)); - lockProps.Add(new FileSystemItemPropertyData((int)CustomColumnIds.LockScope, Exclusive ? "Exclusive" : "Shared")); - lockProps.Add(new FileSystemItemPropertyData((int)CustomColumnIds.LockExpirationDate, LockExpirationDateUtc != null ? LockExpirationDateUtc.ToString() : "")); - return lockProps; - } - } -} diff --git a/Windows/Common/VirtualDrive/VirtualEngineBase.cs b/Windows/Common/VirtualDrive/VirtualEngineBase.cs index 1da0e96..f2fb4e8 100644 --- a/Windows/Common/VirtualDrive/VirtualEngineBase.cs +++ b/Windows/Common/VirtualDrive/VirtualEngineBase.cs @@ -7,6 +7,7 @@ using ITHit.FileSystem.Windows; using ITHit.FileSystem.Samples.Common.Windows; using ITHit.FileSystem.Samples.Common.Windows.Rpc; +using System.Collections.Generic; namespace ITHit.FileSystem.Samples.Common.Windows { @@ -18,12 +19,6 @@ public abstract class VirtualEngineBase : EngineWindows /// // public readonly RemoteStorageMonitor RemoteStorageMonitor; - /// - /// Monitors documents renames and attributes changes in the user file system. - /// Required for transactional saves performed by MS Office, AutoCAD, as well as for Notepad++, etc. - /// - public readonly FilteredDocsMonitor FilteredDocsMonitor; - /// /// Full synchronization service. /// In case any changes are lost (app restart, lost connection, etc.) this service will sync all changes. @@ -35,11 +30,6 @@ public abstract class VirtualEngineBase : EngineWindows /// private readonly ILogger logger; - /// - /// Path to the folder that stores custom data associated with files and folders. - /// - private readonly string serverDataFolderPath; - /// /// Path to the icons folder. /// @@ -60,7 +50,6 @@ public abstract class VirtualEngineBase : EngineWindows /// Your file system tree will be located under this folder. /// /// Path to the remote storage root. - /// Path to the folder that stores custom data associated with files and folders. /// 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. @@ -69,7 +58,6 @@ public VirtualEngineBase( string license, string userFileSystemRootPath, string remoteStorageRootPath, - string serverDataFolderPath, string iconsFolderPath, string rpcCommunicationChannelName, double syncIntervalMs, @@ -77,7 +65,6 @@ public VirtualEngineBase( : base(license, userFileSystemRootPath) { logger = new Logger("File System Engine", log4net) ?? throw new NullReferenceException(nameof(log4net)); - this.serverDataFolderPath = serverDataFolderPath ?? throw new NullReferenceException(nameof(serverDataFolderPath)); this.iconsFolderPath = iconsFolderPath ?? throw new NullReferenceException(nameof(iconsFolderPath)); this.grpcServer = new GrpcServer(rpcCommunicationChannelName, this, log4net); @@ -90,11 +77,12 @@ public VirtualEngineBase( Message += Engine_Message; //RemoteStorageMonitor = new RemoteStorageMonitor(remoteStorageRootPath, this, log4net); - FilteredDocsMonitor = new FilteredDocsMonitor(userFileSystemRootPath, this, log4net); SyncService = new FullSyncService(syncIntervalMs, userFileSystemRootPath, this, log4net); } - public abstract IMapping Mapping { get; } + //public abstract IMapping Mapping { get; } + + public string IconsFolderPath => iconsFolderPath; /// public override async Task FilterAsync(OperationType operationType, string userFileSystemPath, string userFileSystemNewPath = null, IOperationContext operationContext = null) @@ -103,11 +91,12 @@ public override async Task FilterAsync(OperationType operationType, string { switch(operationType) { - case OperationType.Update: + // To send file content to the remote storage only when the MS Office or + // AutoCAD document is closed, uncommnt the Create and Update cases below. + //case OperationType.Create: + //case OperationType.Update: + case OperationType.Unlock: - // PowerPoint does not block the file for reading when the file is opened for editing. - // As a result the file will be sent to the remote storage during each file save operation. - // This also improves performance of the file save including for AutoCAD files. return FilterHelper.AvoidSync(userFileSystemPath) || FilterHelper.IsAppLocked(userFileSystemPath); @@ -135,8 +124,7 @@ public override async Task StartAsync() { await base.StartAsync(); //RemoteStorageMonitor.Start(); - FilteredDocsMonitor.Start(); - await SyncService.StartAsync(); + //await SyncService.StartAsync(); grpcServer.Start(); } @@ -144,7 +132,6 @@ public override async Task StopAsync() { await base.StopAsync(); //RemoteStorageMonitor.Stop(); - FilteredDocsMonitor.Stop(); await SyncService.StopAsync(); grpcServer.Stop(); } @@ -170,17 +157,27 @@ private void Engine_StateChanged(Engine engine, EngineWindows.StateChangeEventAr } /// - /// Manages custom data associated with the item and stored outside of the item. + /// Gets thumbnail. /// - public ExternalDataManager ExternalDataManager(string userFileSystemPath, ILogger logger = null) - { - return new ExternalDataManager(userFileSystemPath, serverDataFolderPath, Path, iconsFolderPath, logger ?? this.logger); - } + /// 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); /// - /// Returns thumbnail. + /// Gets list of item properties. /// - public abstract Task GetThumbnailAsync(string path, uint size); + /// 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; @@ -191,7 +188,6 @@ protected override void Dispose(bool disposing) if (disposing) { //RemoteStorageMonitor.Dispose(); - FilteredDocsMonitor.Dispose(); SyncService.Dispose(); grpcServer.Dispose(); } diff --git a/Windows/Common/WinRT.ShellExtension.Rpc/CommonShellExtensionRpc.csproj b/Windows/Common/WinRT.ShellExtension.Rpc/CommonShellExtensionRpc.csproj new file mode 100644 index 0000000..8542e3a --- /dev/null +++ b/Windows/Common/WinRT.ShellExtension.Rpc/CommonShellExtensionRpc.csproj @@ -0,0 +1,41 @@ + + + + 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 new file mode 100644 index 0000000..6de5b47 --- /dev/null +++ b/Windows/Common/WinRT.ShellExtension.Rpc/CustomStateProviderProxy.cs @@ -0,0 +1,50 @@ +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) + { + GrpcClient grpcClient = new GrpcClient("VirtualDrive.RPC"); + + try + { + + ItemPropertyRequest request = new() + { + Path = itemPath + }; + + var itemPropertyResult = grpcClient.RpcClient.GetItemPropertiesAsync(request).GetAwaiter().GetResult(); + + return itemPropertyResult + .Properties + .Select(i => new ItemProperty(i.Id, i.Value, i.IconResource)) + .ToArray(); + + } + catch (Exception ex) + { + LogErrorRequest request = new() + { + Message = ex.Message, + SourcePath = itemPath + }; + + grpcClient.RpcClient.LogError(request); + + return new ItemProperty[] { }; + } + } + } +} diff --git a/Windows/Common/WinRT.ShellExtension.Rpc/Google.Protobuf.dll b/Windows/Common/WinRT.ShellExtension.Rpc/Google.Protobuf.dll new file mode 100644 index 0000000..4f9473f Binary files /dev/null and b/Windows/Common/WinRT.ShellExtension.Rpc/Google.Protobuf.dll differ diff --git a/Windows/Common/WinRT.ShellExtension.Rpc/Grpc.Core.Api.dll b/Windows/Common/WinRT.ShellExtension.Rpc/Grpc.Core.Api.dll new file mode 100644 index 0000000..0034a30 Binary files /dev/null and b/Windows/Common/WinRT.ShellExtension.Rpc/Grpc.Core.Api.dll differ diff --git a/Windows/Common/WinRT.ShellExtension.Rpc/GrpcDotNetNamedPipes.dll b/Windows/Common/WinRT.ShellExtension.Rpc/GrpcDotNetNamedPipes.dll new file mode 100644 index 0000000..97c8fd1 Binary files /dev/null and b/Windows/Common/WinRT.ShellExtension.Rpc/GrpcDotNetNamedPipes.dll differ diff --git a/Windows/Common/WinRT.ShellExtension.Rpc/ItemProperty.cs b/Windows/Common/WinRT.ShellExtension.Rpc/ItemProperty.cs new file mode 100644 index 0000000..0248224 --- /dev/null +++ b/Windows/Common/WinRT.ShellExtension.Rpc/ItemProperty.cs @@ -0,0 +1,18 @@ +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/ClassFactory.h b/Windows/Common/WinRT.ShellExtension/ClassFactory.h new file mode 100644 index 0000000..82c4141 --- /dev/null +++ b/Windows/Common/WinRT.ShellExtension/ClassFactory.h @@ -0,0 +1,23 @@ +#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/Common.Windows.WinRT.ShellExtension.vcxproj b/Windows/Common/WinRT.ShellExtension/Common.Windows.WinRT.ShellExtension.vcxproj new file mode 100644 index 0000000..97f6186 --- /dev/null +++ b/Windows/Common/WinRT.ShellExtension/Common.Windows.WinRT.ShellExtension.vcxproj @@ -0,0 +1,151 @@ + + + + + + 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) + + + + + + + + + WIN32;%(PreprocessorDefinitions) + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + + + true + true + + + + + + + + + + + + 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/Common/WinRT.ShellExtension/Common.Windows.WinRT.ShellExtension.vcxproj.filters b/Windows/Common/WinRT.ShellExtension/Common.Windows.WinRT.ShellExtension.vcxproj.filters new file mode 100644 index 0000000..5f72f70 --- /dev/null +++ b/Windows/Common/WinRT.ShellExtension/Common.Windows.WinRT.ShellExtension.vcxproj.filters @@ -0,0 +1,64 @@ + + + + + + + + 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/Common/WinRT.ShellExtension/Common.Windows.WinRT.ShellExtension_TemporaryKey.pfx b/Windows/Common/WinRT.ShellExtension/Common.Windows.WinRT.ShellExtension_TemporaryKey.pfx new file mode 100644 index 0000000..eb14bc5 Binary files /dev/null and b/Windows/Common/WinRT.ShellExtension/Common.Windows.WinRT.ShellExtension_TemporaryKey.pfx differ diff --git a/Windows/Common/WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.aps b/Windows/Common/WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.aps new file mode 100644 index 0000000..4a39751 Binary files /dev/null and b/Windows/Common/WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.aps differ diff --git a/Windows/Common/WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.exe.manifest b/Windows/Common/WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.exe.manifest new file mode 100644 index 0000000..ed32231 --- /dev/null +++ b/Windows/Common/WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.exe.manifest @@ -0,0 +1,16 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Windows/Common/WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.rc b/Windows/Common/WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.rc new file mode 100644 index 0000000..6d28532 Binary files /dev/null and b/Windows/Common/WinRT.ShellExtension/Common_Windows_WinRT_ShellExtension.rc differ diff --git a/Windows/Common/WinRT.ShellExtension/CustomStateProvider.cpp b/Windows/Common/WinRT.ShellExtension/CustomStateProvider.cpp new file mode 100644 index 0000000..b78d537 --- /dev/null +++ b/Windows/Common/WinRT.ShellExtension/CustomStateProvider.cpp @@ -0,0 +1,38 @@ +#include "pch.h" +#include "CustomStateProvider.h" +#include "ShellExtensionModule.h" +#include + +namespace winrt +{ + using namespace winrt::Windows::Storage::Provider; +} + +namespace winrt::CommonWindowsRtShellExtenstion::implementation +{ + Windows::Foundation::Collections::IIterable CustomStateProvider::GetItemProperties(hstring const& itemPath) + { + auto propertyVector{ winrt::single_threaded_vector() }; + + try + { + CommonShellExtensionRpc::CustomStateProviderProxy stateProviderProxy; + auto itemProperties = stateProviderProxy.GetItemProperties(itemPath); + + 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/Common/WinRT.ShellExtension/CustomStateProvider.h b/Windows/Common/WinRT.ShellExtension/CustomStateProvider.h new file mode 100644 index 0000000..ce3d6f6 --- /dev/null +++ b/Windows/Common/WinRT.ShellExtension/CustomStateProvider.h @@ -0,0 +1,24 @@ +#pragma once + +#include "CommonWindowsRtShellExtenstion.CustomStateProvider.g.h" +#include + +/* 000562AA-2879-4CF1-89E8-0AEC9596FE19 */ +constexpr CLSID CLSID_CustomStateProvider = { 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/Common/WinRT.ShellExtension/CustomStateProvider.idl b/Windows/Common/WinRT.ShellExtension/CustomStateProvider.idl new file mode 100644 index 0000000..3da783b --- /dev/null +++ b/Windows/Common/WinRT.ShellExtension/CustomStateProvider.idl @@ -0,0 +1,9 @@ +namespace CommonWindowsRtShellExtenstion +{ + + runtimeclass CustomStateProvider : [default] Windows.Storage.Provider.IStorageProviderItemPropertySource + { + + CustomStateProvider(); + } +} diff --git a/Windows/Common/WinRT.ShellExtension/PropertySheet.props b/Windows/Common/WinRT.ShellExtension/PropertySheet.props new file mode 100644 index 0000000..e34141b --- /dev/null +++ b/Windows/Common/WinRT.ShellExtension/PropertySheet.props @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/Windows/Common/WinRT.ShellExtension/ShellExtensionModule.cpp b/Windows/Common/WinRT.ShellExtension/ShellExtensionModule.cpp new file mode 100644 index 0000000..c3f2a1b --- /dev/null +++ b/Windows/Common/WinRT.ShellExtension/ShellExtensionModule.cpp @@ -0,0 +1,29 @@ +#include "pch.h" +#include "ShellExtensionModule.h" +#include "CustomStateProvider.h" +#include "ClassFactory.h" + +using namespace winrt::CommonWindowsRtShellExtenstion::implementation; + +ShellExtensionModule::ShellExtensionModule() +{ + Start(); +} + +ShellExtensionModule::~ShellExtensionModule() +{ + Stop(); +} + +void ShellExtensionModule::Start() +{ + DWORD cookie; + + auto customStateProvider = winrt::make>(); + winrt::check_hresult(CoRegisterClassObject(CLSID_CustomStateProvider, customStateProvider.get(), CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &cookie)); +} + +void ShellExtensionModule::Stop() +{ + +} diff --git a/Windows/Common/WinRT.ShellExtension/ShellExtensionModule.h b/Windows/Common/WinRT.ShellExtension/ShellExtensionModule.h new file mode 100644 index 0000000..e9419f4 --- /dev/null +++ b/Windows/Common/WinRT.ShellExtension/ShellExtensionModule.h @@ -0,0 +1,13 @@ +#pragma once + +class ShellExtensionModule +{ +public: + ShellExtensionModule(); + virtual ~ShellExtensionModule(); + +private: + void Start(); + void Stop(); +}; + diff --git a/Windows/Common/WinRT.ShellExtension/WinMain.cpp b/Windows/Common/WinRT.ShellExtension/WinMain.cpp new file mode 100644 index 0000000..cded36a --- /dev/null +++ b/Windows/Common/WinRT.ShellExtension/WinMain.cpp @@ -0,0 +1,69 @@ +#include "pch.h" +#include "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/Common/WinRT.ShellExtension/packages.config b/Windows/Common/WinRT.ShellExtension/packages.config new file mode 100644 index 0000000..c12b9fd --- /dev/null +++ b/Windows/Common/WinRT.ShellExtension/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Windows/Common/WinRT.ShellExtension/pch.cpp b/Windows/Common/WinRT.ShellExtension/pch.cpp new file mode 100644 index 0000000..bcb5590 --- /dev/null +++ b/Windows/Common/WinRT.ShellExtension/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/Windows/Common/WinRT.ShellExtension/pch.h b/Windows/Common/WinRT.ShellExtension/pch.h new file mode 100644 index 0000000..81e55e0 --- /dev/null +++ b/Windows/Common/WinRT.ShellExtension/pch.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#ifdef GetCurrentTime +#undef GetCurrentTime +#endif +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "CustomStateProvider.h" \ No newline at end of file diff --git a/Windows/Common/WinRT.ShellExtension/readme.txt b/Windows/Common/WinRT.ShellExtension/readme.txt new file mode 100644 index 0000000..bcbabe1 --- /dev/null +++ b/Windows/Common/WinRT.ShellExtension/readme.txt @@ -0,0 +1,30 @@ +======================================================================== + 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/Common/WinRT.ShellExtension/resource.h b/Windows/Common/WinRT.ShellExtension/resource.h new file mode 100644 index 0000000..5b37dd6 --- /dev/null +++ b/Windows/Common/WinRT.ShellExtension/resource.h @@ -0,0 +1,13 @@ +//{{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/UserFileSystemSamples.sln b/Windows/UserFileSystemSamples.sln index b9ce098..ac9b12d 100644 --- a/Windows/UserFileSystemSamples.sln +++ b/Windows/UserFileSystemSamples.sln @@ -37,18 +37,20 @@ 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("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common.Windows.WinRT.ShellExtension", "Common\WinRT.ShellExtension\Common.Windows.WinRT.ShellExtension.vcxproj", "{98E623B9-BCAB-48D2-80A2-1D7AADE897D6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonShellExtensionRpc", "Common\WinRT.ShellExtension.Rpc\CommonShellExtensionRpc.csproj", "{8EA7BABA-FC44-4074-86CB-88B8F42CA055}" +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 @@ -59,8 +61,6 @@ Global {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 @@ -69,8 +69,6 @@ Global {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 @@ -79,8 +77,6 @@ Global {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 @@ -89,8 +85,6 @@ Global {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 @@ -99,8 +93,6 @@ Global {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 @@ -109,8 +101,6 @@ Global {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 @@ -119,8 +109,6 @@ Global {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 @@ -129,8 +117,6 @@ Global {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 @@ -139,8 +125,6 @@ Global {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 @@ -149,8 +133,6 @@ Global {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 @@ -163,10 +145,6 @@ Global {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 @@ -176,9 +154,6 @@ Global {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 @@ -187,8 +162,6 @@ Global {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 @@ -197,8 +170,6 @@ Global {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 @@ -207,8 +178,6 @@ Global {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 @@ -217,8 +186,6 @@ Global {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 @@ -227,8 +194,6 @@ Global {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 @@ -237,8 +202,6 @@ Global {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 @@ -247,8 +210,6 @@ Global {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 @@ -257,8 +218,6 @@ Global {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 @@ -267,8 +226,6 @@ Global {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 @@ -277,8 +234,6 @@ Global {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 @@ -287,8 +242,6 @@ Global {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 @@ -297,9 +250,6 @@ Global {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 @@ -309,10 +259,6 @@ Global {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 @@ -322,21 +268,36 @@ Global {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 + {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}.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 + {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}.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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -355,6 +316,8 @@ Global {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} + {98E623B9-BCAB-48D2-80A2-1D7AADE897D6} = {F24CBDD0-7A18-43F0-BCD2-A8FD1A8A7B54} + {8EA7BABA-FC44-4074-86CB-88B8F42CA055} = {F24CBDD0-7A18-43F0-BCD2-A8FD1A8A7B54} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {740A716A-38A7-46BC-A21F-18336D0023B7} diff --git a/Windows/VirtualDrive/VirtualDrive.Package/Package.appxmanifest b/Windows/VirtualDrive/VirtualDrive.Package/Package.appxmanifest index 843ecaa..1715856 100644 --- a/Windows/VirtualDrive/VirtualDrive.Package/Package.appxmanifest +++ b/Windows/VirtualDrive/VirtualDrive.Package/Package.appxmanifest @@ -44,9 +44,8 @@ - + - @@ -68,8 +67,13 @@ - - + + + + + + + diff --git a/Windows/VirtualDrive/VirtualDrive.Package/VirtualDrive.Package.wapproj b/Windows/VirtualDrive/VirtualDrive.Package/VirtualDrive.Package.wapproj index 57457d8..ec04237 100644 --- a/Windows/VirtualDrive/VirtualDrive.Package/VirtualDrive.Package.wapproj +++ b/Windows/VirtualDrive/VirtualDrive.Package/VirtualDrive.Package.wapproj @@ -68,6 +68,7 @@ + True diff --git a/Windows/VirtualDrive/VirtualDrive.ShellExtension/Program.cs b/Windows/VirtualDrive/VirtualDrive.ShellExtension/Program.cs index 137ef98..d51efb5 100644 --- a/Windows/VirtualDrive/VirtualDrive.ShellExtension/Program.cs +++ b/Windows/VirtualDrive/VirtualDrive.ShellExtension/Program.cs @@ -31,10 +31,7 @@ static async Task Main(string[] args) IConfiguration configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json", false, true).Build(); Settings settings = configuration.ReadSettings(); - ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - ConfigureLogger(settings); - - ShellExtensionConfiguration.Initialize(settings, log); + ShellExtensionConfiguration.Initialize(settings); try { @@ -51,24 +48,5 @@ static async Task Main(string[] args) Debug.WriteLine(ex.Message); } } - - /// - /// Configures log4net logger. - /// - /// Log file path. - private static void ConfigureLogger(Settings settings) - { - // 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 + ".ShellExtension", - Path.GetFileName(rollingFileAppender.File)); - } - } } } diff --git a/Windows/VirtualDrive/VirtualDrive.ShellExtension/VirtualDrive.ShellExtension.csproj b/Windows/VirtualDrive/VirtualDrive.ShellExtension/VirtualDrive.ShellExtension.csproj index 6b6951e..f8f60c3 100644 --- a/Windows/VirtualDrive/VirtualDrive.ShellExtension/VirtualDrive.ShellExtension.csproj +++ b/Windows/VirtualDrive/VirtualDrive.ShellExtension/VirtualDrive.ShellExtension.csproj @@ -1,7 +1,7 @@  - net5.0-windows10.0.18362.0 + net6.0-windows10.0.18362.0 10.0.18362.0 True x64 diff --git a/Windows/VirtualDrive/VirtualDrive/AppSettings.cs b/Windows/VirtualDrive/VirtualDrive/AppSettings.cs index c32c0b3..d8b9c8b 100644 --- a/Windows/VirtualDrive/VirtualDrive/AppSettings.cs +++ b/Windows/VirtualDrive/VirtualDrive/AppSettings.cs @@ -115,10 +115,6 @@ public static AppSettings ReadSettings(this IConfiguration configuration) // Load product name from entry exe file. settings.ProductName = FileVersionInfo.GetVersionInfo(assemblyLocation).ProductName; - // Folder where custom data is stored. - string localApplicationDataFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - settings.ServerDataFolderPath = Path.Combine(localApplicationDataFolderPath, settings.AppID, settings.UserFileSystemRootPath.Replace(":", ""), "ServerData"); - return settings; } diff --git a/Windows/VirtualDrive/VirtualDrive/Images/Empty.ico b/Windows/VirtualDrive/VirtualDrive/Images/Empty.ico new file mode 100644 index 0000000..23cfb33 Binary files /dev/null and b/Windows/VirtualDrive/VirtualDrive/Images/Empty.ico differ diff --git a/Windows/VirtualDrive/VirtualDrive/Mapping.cs b/Windows/VirtualDrive/VirtualDrive/Mapping.cs index bcecf95..ffdf7ad 100644 --- a/Windows/VirtualDrive/VirtualDrive/Mapping.cs +++ b/Windows/VirtualDrive/VirtualDrive/Mapping.cs @@ -8,6 +8,7 @@ using ITHit.FileSystem; using ITHit.FileSystem.Samples.Common; using ITHit.FileSystem.Samples.Common.Windows; +using ITHit.FileSystem.Windows; namespace VirtualDrive { @@ -17,7 +18,7 @@ namespace VirtualDrive /// /// You will change methods of this class to map the user file system path to your remote storage path. /// - public class Mapping : IMapping + public class Mapping //: IMapping { private readonly VirtualEngineBase engine; @@ -56,6 +57,21 @@ public static string ReverseMapPath(string remoteStorageUri) return path; } + /// + /// Gets remote storage path by remote storage item ID. + /// + /// + /// 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. + /// + /// Path in the remote storage. + public static string GetRemoteStoragePathById(byte[] remoteStorageId) + { + return WindowsFileSystemItem.GetPathByItemId(remoteStorageId); + } + /// /// Gets a user file system item info from the remote storage data. /// @@ -75,6 +91,10 @@ public static FileSystemItemMetadataExt GetUserFileSysteItemMetadata(FileSystemI userFileSystemItem = new FolderMetadataExt(); } + // Store you remote storage item ID in this property. + // It will be passed to IEngine.GetFileSystemItemAsync() during every operation. + userFileSystemItem.RemoteStorageItemId = WindowsFileSystemItem.GetItemIdByPath(remoteStorageItem.FullName); + userFileSystemItem.Name = remoteStorageItem.Name; userFileSystemItem.Attributes = remoteStorageItem.Attributes; userFileSystemItem.CreationTime = remoteStorageItem.CreationTime; @@ -82,29 +102,28 @@ public static FileSystemItemMetadataExt GetUserFileSysteItemMetadata(FileSystemI userFileSystemItem.LastAccessTime = remoteStorageItem.LastAccessTime; userFileSystemItem.ChangeTime = remoteStorageItem.LastWriteTime; - userFileSystemItem.IsLocked = false; - - string eTag = "1234567890"; - if (userFileSystemItem is IFileMetadata) - { - ((FileMetadataExt)userFileSystemItem).ETag = eTag; - }; - // Set custom columns to be displayed in file manager. // We create property definitions when registering the sync root with corresponding IDs. - List customProps = new List(); - - // Set ETag property. - if (userFileSystemItem is IFileMetadata) - { - customProps.Add(new FileSystemItemPropertyData((int)CustomColumnIds.ETag, eTag)); - }; - - userFileSystemItem.CustomProperties = customProps; + // The columns are rendered in IVirtualEngine.GetItemPropertiesAsync() call. + //userFileSystemItem.CustomProperties = ; 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); diff --git a/Windows/VirtualDrive/VirtualDrive/Program.cs b/Windows/VirtualDrive/VirtualDrive/Program.cs index 8a2dee0..4855eee 100644 --- a/Windows/VirtualDrive/VirtualDrive/Program.cs +++ b/Windows/VirtualDrive/VirtualDrive/Program.cs @@ -1,4 +1,3 @@ -using Microsoft.Extensions.Configuration; using System; using System.Diagnostics; using System.IO; @@ -7,13 +6,16 @@ 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.Samples.Common.Windows; using ITHit.FileSystem; +using ITHit.FileSystem.Windows; using ITHit.FileSystem.Samples.Common; +using ITHit.FileSystem.Samples.Common.Windows; + namespace VirtualDrive { @@ -69,13 +71,17 @@ static async Task Main(string[] args) Settings.UserFileSystemLicense, Settings.UserFileSystemRootPath, Settings.RemoteStorageRootPath, - Settings.ServerDataFolderPath, Settings.IconsFolderPath, Settings.RpcCommunicationChannelName, Settings.SyncIntervalMs, log); Engine.AutoLock = Settings.AutoLock; + // 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. + byte[] itemId = WindowsFileSystemItem.GetItemIdByPath(Settings.RemoteStorageRootPath); + Engine.Placeholders.GetItem(Settings.UserFileSystemRootPath).SetRemoteStorageItemId(itemId); + // Start processing OS file system calls. await Engine.StartAsync(); @@ -218,12 +224,13 @@ private static async Task ProcessUserInputAsync() case (char)ConsoleKey.Escape: case 'Q': - // Unregister during programm uninstall. Engine.Dispose(); + + // Call the code below during programm uninstall using classic msi. await UnregisterSyncRootAsync(); // Delete all files/folders. - CleanupAppFolders(); + await CleanupAppFoldersAsync(); return; case (char)ConsoleKey.Spacebar : @@ -251,7 +258,6 @@ private static async Task RegisterSyncRootAsync() { log.Info($"\nRegistering {Settings.UserFileSystemRootPath} sync root."); Directory.CreateDirectory(Settings.UserFileSystemRootPath); - Directory.CreateDirectory(Settings.ServerDataFolderPath); await Registrar.RegisterAsync(SyncRootId, Settings.UserFileSystemRootPath, Settings.ProductName, Path.Combine(Settings.IconsFolderPath, "Drive.ico")); @@ -280,7 +286,7 @@ private static async Task UnregisterSyncRootAsync() await Registrar.UnregisterAsync(SyncRootId); } - private static void CleanupAppFolders() + private static async Task CleanupAppFoldersAsync() { log.Info("\nDeleting all file and folder placeholders.\n"); try @@ -294,9 +300,7 @@ private static void CleanupAppFolders() try { - string localApplicationDataFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - string appDataPath = Path.Combine(localApplicationDataFolderPath, Settings.AppID); - Directory.Delete(appDataPath, true); + await ((EngineWindows)Engine).UninstallCleanupAsync(); } catch (Exception ex) { @@ -311,14 +315,6 @@ private static void CleanupAppFolders() /// This method is provided solely for the development and testing convenience. private static void ShowTestEnvironment() { - // 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 Windows File Manager with remote storage. ProcessStartInfo rsInfo = new ProcessStartInfo(Program.Settings.RemoteStorageRootPath); rsInfo.UseShellExecute = true; // Open window only if not opened already. @@ -327,10 +323,10 @@ private static void ShowTestEnvironment() } - // Open Windows File Manager with custom data storage. Uncomment this to debug custom data storage management. - ProcessStartInfo serverDataInfo = new ProcessStartInfo(Program.Settings.ServerDataFolderPath); - serverDataInfo.UseShellExecute = true; // Open window only if not opened already. - using (Process serverDataWinFileManager = Process.Start(serverDataInfo)) + // 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)) { } diff --git a/Windows/VirtualDrive/VirtualDrive/RemoteStorageMonitor.cs b/Windows/VirtualDrive/VirtualDrive/RemoteStorageMonitor.cs index 1ee0608..2c06372 100644 --- a/Windows/VirtualDrive/VirtualDrive/RemoteStorageMonitor.cs +++ b/Windows/VirtualDrive/VirtualDrive/RemoteStorageMonitor.cs @@ -114,7 +114,6 @@ private async void CreatedAsync(object sender, FileSystemEventArgs e) if (await engine.ServerNotifications(userFileSystemParentPath).CreateAsync(new[] { newItemInfo }) > 0) { LogMessage($"Created succesefully", userFileSystemPath); - engine.ExternalDataManager(userFileSystemPath, this).IsNew = false; } } } @@ -151,6 +150,9 @@ 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. if (await engine.ServerNotifications(userFileSystemPath).UpdateAsync(itemInfo)) { + // Update ETag. + //await engine.Mapping.UpdateETagAsync(remoteStoragePath, userFileSystemPath); + LogMessage("Updated succesefully", userFileSystemPath); } } @@ -180,7 +182,6 @@ private async void DeletedAsync(object sender, FileSystemEventArgs e) // 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. - Thread.Sleep(2000); // This can be removed in a real-life application. if (FsPath.Exists(userFileSystemPath)) { // Because of the on-demand population the file or folder placeholder may not exist in the user file system. @@ -188,7 +189,6 @@ private async void DeletedAsync(object sender, FileSystemEventArgs e) { LogMessage("Deleted succesefully", userFileSystemPath); } - engine.ExternalDataManager(userFileSystemPath, this).Delete(); } } catch (Exception ex) @@ -203,7 +203,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); + LogMessage("Renamed", e.OldFullPath, e.FullPath); string remoteStorageOldPath = e.OldFullPath; string remoteStorageNewPath = e.FullPath; try @@ -213,16 +213,18 @@ private async void RenamedAsync(object sender, RenamedEventArgs e) // 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. - Thread.Sleep(2000); // This can be removed in a real-life application. if (FsPath.Exists(userFileSystemOldPath)) { // 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); + // As soon as in this sample we use USN as a ETag, and USN chandes on move, + // we need to update it for hydrated files. + //await engine.Mapping.UpdateETagAsync(remoteStorageNewPath, userFileSystemNewPath); + + LogMessage("Renamed succesefully", userFileSystemOldPath, userFileSystemNewPath); } } - await engine.ExternalDataManager(userFileSystemOldPath, this).MoveToAsync(userFileSystemNewPath); } catch (Exception ex) { diff --git a/Windows/VirtualDrive/VirtualDrive/ThumbnailExtractor.cs b/Windows/VirtualDrive/VirtualDrive/ThumbnailExtractor.cs index 1927d1a..34aadb7 100644 --- a/Windows/VirtualDrive/VirtualDrive/ThumbnailExtractor.cs +++ b/Windows/VirtualDrive/VirtualDrive/ThumbnailExtractor.cs @@ -24,7 +24,7 @@ public static byte[] GetThumbnail(string userFileSystemPath, uint size) { if (bitmap == null) { - throw new NotImplementedException(); + return null; } using (MemoryStream stream = new MemoryStream()) diff --git a/Windows/VirtualDrive/VirtualDrive/VirtualDrive.csproj b/Windows/VirtualDrive/VirtualDrive/VirtualDrive.csproj index 5b1ee6e..aca8227 100644 --- a/Windows/VirtualDrive/VirtualDrive/VirtualDrive.csproj +++ b/Windows/VirtualDrive/VirtualDrive/VirtualDrive.csproj @@ -1,7 +1,7 @@  Exe - net5.0-windows10.0.18362.0 + net6.0-windows10.0.18362.0 10.0.18362.0 IT Hit LTD. IT Hit LTD. @@ -53,6 +53,9 @@ This is an advanced project with ETags support, Microsoft Office documents editi PreserveNewest + + PreserveNewest + PreserveNewest @@ -68,6 +71,9 @@ This is an advanced project with ETags support, Microsoft Office documents editi PreserveNewest + + PreserveNewest + Always diff --git a/Windows/VirtualDrive/VirtualDrive/VirtualEngine.cs b/Windows/VirtualDrive/VirtualDrive/VirtualEngine.cs index 7c1c9f9..af483f2 100644 --- a/Windows/VirtualDrive/VirtualDrive/VirtualEngine.cs +++ b/Windows/VirtualDrive/VirtualDrive/VirtualEngine.cs @@ -1,10 +1,12 @@ using System; using System.Threading.Tasks; +using System.Collections.Generic; + using log4net; using ITHit.FileSystem; using ITHit.FileSystem.Windows; +using ITHit.FileSystem.Samples.Common; using ITHit.FileSystem.Samples.Common.Windows; -using ITHit.FileSystem.Samples.Common.Windows.Rpc; namespace VirtualDrive { @@ -25,7 +27,6 @@ public class VirtualEngine : VirtualEngineBase /// Your file system tree will be located under this folder. /// /// Path to the remote storage root. - /// Path to the folder that stores custom data associated with files and folders. /// 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. @@ -34,30 +35,29 @@ public VirtualEngine( string license, string userFileSystemRootPath, string remoteStorageRootPath, - string serverDataFolderPath, string iconsFolderPath, string rpcCommunicationChannelName, double syncIntervalMs, ILog log4net) - : base(license, userFileSystemRootPath, remoteStorageRootPath, serverDataFolderPath, iconsFolderPath, rpcCommunicationChannelName, syncIntervalMs, log4net) + : base(license, userFileSystemRootPath, remoteStorageRootPath, iconsFolderPath, rpcCommunicationChannelName, syncIntervalMs, log4net) { RemoteStorageMonitor = new RemoteStorageMonitor(remoteStorageRootPath, this, log4net); } /// - public override async Task GetFileSystemItemAsync(string userFileSystemPath, FileSystemItemType itemType, byte[] itemId) + public override async Task GetFileSystemItemAsync(string userFileSystemPath, FileSystemItemType itemType, byte[] remoteStorageItemId) { if (itemType == FileSystemItemType.File) { - return new VirtualFile(userFileSystemPath, this, this); + return new VirtualFile(userFileSystemPath, remoteStorageItemId, this, this); } else { - return new VirtualFolder(userFileSystemPath, this, this); + return new VirtualFolder(userFileSystemPath, remoteStorageItemId, this, this); } } - public override IMapping Mapping { get { return new Mapping(this); } } + //public override IMapping Mapping { get { return new Mapping(this); } } /// public override async Task StartAsync() @@ -72,6 +72,85 @@ public override async Task StopAsync() RemoteStorageMonitor.Stop(); } + /// + public override async Task GetThumbnailAsync(string userFileSystemPath, uint size) + { + byte[] thumbnail = ThumbnailExtractor.GetThumbnail(userFileSystemPath, size); + + string thumbnailResult = thumbnail != null ? "Success" : "Not Impl"; + LogMessage($"{nameof(VirtualEngine)}.{nameof(GetThumbnailAsync)}() - {thumbnailResult}", userFileSystemPath); + + return thumbnail; + } + + /// + 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. + 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; + } + + private bool disposedValue; protected override void Dispose(bool disposing) @@ -89,20 +168,5 @@ protected override void Dispose(bool disposing) } base.Dispose(disposing); } - - /// - /// Returns thumbnail for specified path in the user file system. - /// - /// - /// Throw if thumbnail is not available. - /// You may also return empty array of null as indication of non existed thumbnail. - /// - /// Path in user file system. - /// Thumbnail size in pixels. - /// Thumbnail bitmap or null if the thumbnail handler is not found. - public override async Task GetThumbnailAsync(string userFileSystemPath, uint size) - { - return ThumbnailExtractor.GetThumbnail(userFileSystemPath, size); - } } } diff --git a/Windows/VirtualDrive/VirtualDrive/VirtualFile.cs b/Windows/VirtualDrive/VirtualDrive/VirtualFile.cs index 5ef4d9d..cf91227 100644 --- a/Windows/VirtualDrive/VirtualDrive/VirtualFile.cs +++ b/Windows/VirtualDrive/VirtualDrive/VirtualFile.cs @@ -20,9 +20,10 @@ public class VirtualFile : VirtualFileSystemItem, IFile /// Creates instance of this class. /// /// File path in the user file system. + /// Remote storage item ID. /// Engine instance. /// Logger. - public VirtualFile(string path, VirtualEngine engine, ILogger logger) : base(path, engine, logger) + public VirtualFile(string path, byte[] remoteStorageItemId, VirtualEngine engine, ILogger logger) : base(path, remoteStorageItemId, engine, logger) { } @@ -49,12 +50,19 @@ public async Task ReadAsync(Stream output, long offset, long length, ITransferD SimulateNetworkDelay(length, resultContext); - await using (FileStream stream = System.IO.File.OpenRead(RemoteStoragePath)) + string remoteStoragePath = Mapping.GetRemoteStoragePathById(RemoteStorageItemId); + await 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. await stream.CopyToAsync(output, bufferSize, length); } + + // Store ETag here. + // In this sample we use file USN (USN changes on every file update) as a ETag for demo purposes. + string eTag = (await WindowsFileSystemItem.GetUsnByPathAsync(remoteStoragePath)).ToString(); + PlaceholderItem placeholder = Engine.Placeholders.GetItem(UserFileSystemPath); + await placeholder.Properties.AddOrUpdateAsync("ETag", eTag); } /// @@ -77,18 +85,37 @@ public async Task WriteAsync(IFileMetadata fileMetadata, Stream content = null, { Logger.LogMessage($"{nameof(IFile)}.{nameof(WriteAsync)}()", UserFileSystemPath, default, operationContext); - ExternalDataManager customDataManager = Engine.ExternalDataManager(UserFileSystemPath); - // Send the ETag to the server as part of the update to ensure the file in the remote storge is not modified since last read. - string oldEtag = await customDataManager.ETagManager.GetETagAsync(); + // Send the ETag to the server as part of the update to ensure + // the file in the remote storge is not modified since last read. + PlaceholderItem placeholder = Engine.Placeholders.GetItem(UserFileSystemPath); + string oldEtag = await placeholder.Properties["ETag"].GetValueAsync(); // Send the lock-token to the server as part of the update. - string lockToken = (await customDataManager.LockManager.GetLockInfoAsync())?.LockToken; + string lockToken; + IDataItem propLockInfo; + if (placeholder.Properties.TryGetValue("LockInfo", out propLockInfo)) + { + ServerLockInfo lockInfo; + if (propLockInfo.TryGetValue(out lockInfo)) + { + lockToken = lockInfo.LockToken; + } + } - FileInfo remoteStorageItem = new FileInfo(RemoteStoragePath); + string remoteStoragePath = Mapping.GetRemoteStoragePathById(RemoteStorageItemId); + FileInfo remoteStorageItem = new FileInfo(remoteStoragePath); if (content != null) { - // Upload remote storage file content. + // Typically you will compare ETags on the server. + // Here we compare ETags before the update for the demo purposes. + //string eTag = (await WindowsFileSystemItem.GetUsnByPathAsync(remoteStoragePath)).ToString(); + //if(oldEtag != eTag) + //{ + // throw new ConflictException(Modified.Server, "The file is modified in remote storage."); + //} + + // Upload file content to the remote storage. await using (FileStream remoteStorageStream = remoteStorageItem.Open(FileMode.Open, FileAccess.Write, FileShare.Delete)) { await content.CopyToAsync(remoteStorageStream); @@ -104,11 +131,13 @@ public async Task WriteAsync(IFileMetadata fileMetadata, Stream content = null, remoteStorageItem.LastWriteTimeUtc = fileMetadata.LastWriteTime.UtcDateTime; // Get the new ETag from server here as part of the update and save it on the client. - string eTagNew = "1234567890"; - await customDataManager.SetCustomDataAsync( - eTagNew, - null, - new[] { new FileSystemItemPropertyData((int)CustomColumnIds.ETag, eTagNew) }); + string newEtag = (await WindowsFileSystemItem.GetUsnByPathAsync(remoteStoragePath)).ToString(); + await placeholder.Properties.AddOrUpdateAsync("ETag", newEtag); + + //await customDataManager.SetCustomDataAsync( + // eTagNew, + // null, + // new[] { new FileSystemItemPropertyData((int)CustomColumnIds.ETag, eTagNew) }); } } } diff --git a/Windows/VirtualDrive/VirtualDrive/VirtualFileSystemItem.cs b/Windows/VirtualDrive/VirtualDrive/VirtualFileSystemItem.cs index f913f0e..f7a906d 100644 --- a/Windows/VirtualDrive/VirtualDrive/VirtualFileSystemItem.cs +++ b/Windows/VirtualDrive/VirtualDrive/VirtualFileSystemItem.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using ITHit.FileSystem; +using ITHit.FileSystem.Samples.Common; using ITHit.FileSystem.Samples.Common.Windows; using ITHit.FileSystem.Windows; @@ -20,9 +21,9 @@ public abstract class VirtualFileSystemItem : IFileSystemItem, ILock protected readonly string UserFileSystemPath; /// - /// Path of this file or folder in the remote storage. + /// File or folder item ID in the remote storage. /// - protected readonly string RemoteStoragePath; + protected readonly byte[] RemoteStorageItemId; /// /// Logger. @@ -38,70 +39,54 @@ public abstract class VirtualFileSystemItem : IFileSystemItem, ILock /// Creates instance of this class. /// /// File or folder path in the user file system. + /// Remote storage item ID. /// Engine instance. /// Logger. - public VirtualFileSystemItem(string userFileSystemPath, VirtualEngine engine, ILogger logger) + public VirtualFileSystemItem(string userFileSystemPath, byte[] remoteStorageItemId, VirtualEngine engine, ILogger logger) { - if (string.IsNullOrEmpty(userFileSystemPath)) - { - throw new ArgumentNullException(nameof(userFileSystemPath)); - } + UserFileSystemPath = string.IsNullOrEmpty(userFileSystemPath) ? throw new ArgumentNullException(nameof(userFileSystemPath)) : userFileSystemPath; + RemoteStorageItemId = remoteStorageItemId ?? throw new ArgumentNullException(nameof(remoteStorageItemId)); Logger = logger ?? throw new ArgumentNullException(nameof(logger)); Engine = engine ?? throw new ArgumentNullException(nameof(engine)); - - UserFileSystemPath = userFileSystemPath; - RemoteStoragePath = Mapping.MapPath(userFileSystemPath); } /// - public async Task MoveToAsync(string userFileSystemNewPath, byte[] targetParentItemId, IOperationContext operationContext = null, IConfirmationResultContext resultContext = null) + public async Task MoveToAsync(string targetUserFileSystemPath, byte[] targetParentItemId, IOperationContext operationContext = null, IConfirmationResultContext resultContext = null) { + string userFileSystemNewPath = targetUserFileSystemPath; string userFileSystemOldPath = this.UserFileSystemPath; Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(MoveToAsync)}()", userFileSystemOldPath, userFileSystemNewPath, operationContext); + } + + /// + public async Task MoveToCompletionAsync(string targetUserFileSystemPath, byte[] targetFolderRemoteStorageItemId, IMoveCompletionContext operationContext = null, IResultContext resultContext = null) + { + string userFileSystemNewPath = targetUserFileSystemPath; + string userFileSystemOldPath = this.UserFileSystemPath; + Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(MoveToCompletionAsync)}()", userFileSystemOldPath, userFileSystemNewPath, operationContext); - if (userFileSystemNewPath.StartsWith(Program.Settings.UserFileSystemRootPath)) + string remoteStorageOldPath = Mapping.GetRemoteStoragePathById(RemoteStorageItemId); + FileSystemInfo remoteStorageOldItem = FsPath.GetFileSystemItem(remoteStorageOldPath); + + if (remoteStorageOldItem != null) { - // The item is moved within the virtual file system. + string remoteStorageNewParentPath = WindowsFileSystemItem.GetPathByItemId(targetFolderRemoteStorageItemId); + string remoteStorageNewPath = Path.Combine(remoteStorageNewParentPath, Path.GetFileName(targetUserFileSystemPath)); - string remoteStorageOldPath = RemoteStoragePath; - string remoteStorageNewPath = Mapping.MapPath(userFileSystemNewPath); - - FileSystemInfo remoteStorageOldItem = FsPath.GetFileSystemItem(remoteStorageOldPath); - if (remoteStorageOldItem != null) + if (remoteStorageOldItem is FileInfo) { - if (remoteStorageOldItem is FileInfo) - { - (remoteStorageOldItem as FileInfo).MoveTo(remoteStorageNewPath, true); - } - else - { - (remoteStorageOldItem as DirectoryInfo).MoveTo(remoteStorageNewPath); - } - - await Engine.ExternalDataManager(userFileSystemOldPath, Logger).MoveToAsync(userFileSystemNewPath); - - Logger.LogMessage("Moved item in remote storage succesefully", userFileSystemOldPath, userFileSystemNewPath, operationContext); + (remoteStorageOldItem as FileInfo).MoveTo(remoteStorageNewPath, true); + } + else + { + (remoteStorageOldItem as DirectoryInfo).MoveTo(remoteStorageNewPath); } - } - else - { - // The move target path is outside of the virtual file system - delete the item. - await DeleteAsync(operationContext, resultContext); - } - } - /// - public async Task MoveToCompletionAsync(IMoveCompletionContext moveCompletionContext = null, IResultContext resultContext = null) - { - string userFileSystemNewPath = this.UserFileSystemPath; - string userFileSystemOldPath = moveCompletionContext.SourcePath; - Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(MoveToCompletionAsync)}()", userFileSystemOldPath, userFileSystemNewPath, moveCompletionContext); + // As soon as in this sample we use USN as a ETag, and USN chandes on move, + // we need to update it for hydrated files. + //await Engine.Mapping.UpdateETagAsync(remoteStorageNewPath, userFileSystemNewPath); - if (FsPath.IsFolder(userFileSystemNewPath)) - { - // In this sample the folder does not have any metadata that can be modified on the client - // and should be synched to the remote storage, just marking the folder as in-sync after the move. - PlaceholderItem.GetItem(userFileSystemNewPath).SetInSync(true); + Logger.LogMessage("Moved in the remote storage succesefully", userFileSystemOldPath, targetUserFileSystemPath, operationContext); } } @@ -118,20 +103,6 @@ public async Task DeleteAsync(IOperationContext operationContext, IConfirmationR // https://docs.microsoft.com/en-us/answers/questions/75240/bug-report-cfapi-ackdelete-borken-on-win10-2004.html // Note that some applications, such as Windows Explorer may call delete more than one time on the same file/folder. - - FileSystemInfo remoteStorageItem = FsPath.GetFileSystemItem(RemoteStoragePath); - if (remoteStorageItem != null) - { - if (remoteStorageItem is FileInfo) - { - remoteStorageItem.Delete(); - } - else - { - (remoteStorageItem as DirectoryInfo).Delete(true); - } - Engine.ExternalDataManager(UserFileSystemPath, Logger).Delete(); - } } /// @@ -143,7 +114,30 @@ public async Task DeleteCompletionAsync(IOperationContext operationContext, IRes Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(DeleteCompletionAsync)}()", UserFileSystemPath, default, operationContext); + string remoteStoragePath; + try + { + remoteStoragePath = Mapping.GetRemoteStoragePathById(RemoteStorageItemId); + } + catch (FileNotFoundException) + { + // Windows Explorer may call delete more than one time on the same file/folder. + return; + } + FileSystemInfo remoteStorageItem = FsPath.GetFileSystemItem(remoteStoragePath); + if (remoteStorageItem != null) + { + if (remoteStorageItem is FileInfo) + { + remoteStorageItem.Delete(); + } + else + { + (remoteStorageItem as DirectoryInfo).Delete(true); + } + Logger.LogMessage("Deleted in the remote storage succesefully", UserFileSystemPath, default, operationContext); + } } /// @@ -176,35 +170,40 @@ public async Task LockAsync(LockMode lockMode, IOperationContext operationContex { Logger.LogMessage($"{nameof(ILock)}.{nameof(LockAsync)}()", UserFileSystemPath, default, operationContext); - ExternalDataManager customDataManager = Engine.ExternalDataManager(UserFileSystemPath, Logger); - LockManager lockManager = customDataManager.LockManager; - if (!Engine.ExternalDataManager(UserFileSystemPath).IsNew) - { - // Set pending icon, so the user has a feedback as lock operation may take some time. - await customDataManager.SetLockPendingIconAsync(true); - - // Call your remote storage here to lock the item. - // 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 File.WriteAsync() method. - // For demo purposes we just fill some generic data. - ServerLockInfo lockInfo = new ServerLockInfo() { LockToken = "ServerToken", Owner = "You", Exclusive = true, LockExpirationDateUtc = DateTimeOffset.Now.AddMinutes(30) }; - - // Save lock-token and lock-mode. - await lockManager.SetLockInfoAsync(lockInfo); - await lockManager.SetLockModeAsync(lockMode); - - // Set lock icon and lock info in custom columns. - await customDataManager.SetLockInfoAsync(lockInfo); - - Logger.LogMessage("Locked in remote storage succesefully.", UserFileSystemPath, default, operationContext); - } + // Call your remote storage here to lock the item. + // Save the lock token and other lock info received from your remote storage on the client. + // Supply the lock-token as part of each remote storage update in File.WriteAsync() method. + // For demo purposes we just fill some generic data. + ServerLockInfo serverLockInfo = new ServerLockInfo() + { + LockToken = "ServerToken", + Owner = "You", + Exclusive = true, + LockExpirationDateUtc = DateTimeOffset.Now.AddMinutes(30) + }; + + // Save lock-token and lock-mode. + PlaceholderItem placeholder = Engine.Placeholders.GetItem(UserFileSystemPath); + await placeholder.Properties.AddOrUpdateAsync("LockInfo", serverLockInfo); + await placeholder.Properties.AddOrUpdateAsync("LockMode", lockMode); + + Logger.LogMessage("Locked in the remote storage succesefully", UserFileSystemPath, default, operationContext); } /// public async Task GetLockModeAsync(IOperationContext operationContext = null) { - LockManager lockManager = Engine.ExternalDataManager(UserFileSystemPath, Logger).LockManager; - return await lockManager.GetLockModeAsync(); + PlaceholderItem placeholder = Engine.Placeholders.GetItem(UserFileSystemPath); + + IDataItem property; + if(placeholder.Properties.TryGetValue("LockMode", out property)) + { + return await property.GetValueAsync(); + } + else + { + return LockMode.None; + } } /// @@ -212,25 +211,17 @@ public async Task UnlockAsync(IOperationContext operationContext = null) { Logger.LogMessage($"{nameof(ILock)}.{nameof(UnlockAsync)}()", UserFileSystemPath, default, operationContext); - ExternalDataManager customDataManager = Engine.ExternalDataManager(UserFileSystemPath, Logger); - LockManager lockManager = customDataManager.LockManager; - - // Set pending icon, so the user has a feedback as unlock operation may take some time. - await customDataManager.SetLockPendingIconAsync(true); - - // Read lock-token from lock-info file. - string lockToken = (await lockManager.GetLockInfoAsync()).LockToken; + // Read the lock-token. + PlaceholderItem placeholder = Engine.Placeholders.GetItem(UserFileSystemPath); + string lockToken = (await placeholder.Properties["LockInfo"].GetValueAsync())?.LockToken; // Unlock the item in the remote storage here. // Delete lock-mode and lock-token info. - lockManager.DeleteLock(); - - // Remove lock icon and lock info in custom columns. - await customDataManager.SetLockInfoAsync(null); + placeholder.Properties.Remove("LockInfo"); + placeholder.Properties.Remove("LockMode"); Logger.LogMessage("Unlocked in the remote storage succesefully", UserFileSystemPath, default, operationContext); } - } } diff --git a/Windows/VirtualDrive/VirtualDrive/VirtualFolder.cs b/Windows/VirtualDrive/VirtualDrive/VirtualFolder.cs index e57c04f..ac02a9c 100644 --- a/Windows/VirtualDrive/VirtualDrive/VirtualFolder.cs +++ b/Windows/VirtualDrive/VirtualDrive/VirtualFolder.cs @@ -21,9 +21,10 @@ public class VirtualFolder : VirtualFileSystemItem, IFolder, IVirtualFolder /// Creates instance of this class. /// /// Folder path in the user file system. + /// Remote storage item ID. /// Engine instance. /// Logger. - public VirtualFolder(string path, VirtualEngine engine, ILogger logger) : base(path, engine, logger) + public VirtualFolder(string path, byte[] remoteStorageItemId, VirtualEngine engine, ILogger logger) : base(path, remoteStorageItemId, engine, logger) { } @@ -34,9 +35,10 @@ public async Task CreateFileAsync(IFileMetadata fileMetadata, Stream con string userFileSystemNewItemPath = Path.Combine(UserFileSystemPath, fileMetadata.Name); Logger.LogMessage($"{nameof(IFolder)}.{nameof(CreateFileAsync)}()", userFileSystemNewItemPath); - FileInfo remoteStorageItem = new FileInfo(Path.Combine(RemoteStoragePath, fileMetadata.Name)); + string remoteStoragePath = Mapping.GetRemoteStoragePathById(RemoteStorageItemId); + FileInfo remoteStorageItem = new FileInfo(Path.Combine(remoteStoragePath, fileMetadata.Name)); - // Upload remote storage file content. + // Upload file content to the remote storage. await using (FileStream remoteStorageStream = remoteStorageItem.Open(FileMode.CreateNew, FileAccess.Write, FileShare.Delete)) { if (content != null) @@ -53,26 +55,25 @@ public async Task CreateFileAsync(IFileMetadata fileMetadata, Stream con remoteStorageItem.LastAccessTimeUtc = fileMetadata.LastAccessTime.UtcDateTime; remoteStorageItem.LastWriteTimeUtc = fileMetadata.LastWriteTime.UtcDateTime; - // Get ETag from server here and save it on the client. - string eTagNew = "1234567890"; + // Get ETag from your remote storage here and save it in + // persistent placeholder properties unlil the next update. + string eTag = (await WindowsFileSystemItem.GetUsnByPathAsync(remoteStorageItem.FullName)).ToString(); + PlaceholderItem placeholder = Engine.Placeholders.GetItem(userFileSystemNewItemPath); + await placeholder.Properties.AddOrUpdateAsync("ETag", eTag); - ExternalDataManager customDataManager = Engine.ExternalDataManager(userFileSystemNewItemPath); - // Store ETag unlil the next update. - await customDataManager.SetCustomDataAsync( - eTagNew, - false, - new[] { new FileSystemItemPropertyData((int)CustomColumnIds.ETag, eTagNew) }); - - return null; + // Return remote storage item ID. It will be passed later + // into IEngine.GetFileSystemItemAsync() method on every call. + return WindowsFileSystemItem.GetItemIdByPath(remoteStorageItem.FullName); } /// public async Task CreateFolderAsync(IFolderMetadata folderMetadata) { string userFileSystemNewItemPath = Path.Combine(UserFileSystemPath, folderMetadata.Name); - Logger.LogMessage($"{nameof(IFolder)}.{nameof(CreateFolderAsync)}()", userFileSystemNewItemPath); + Logger.LogMessage($"{nameof(IFolder)}.{nameof(CreateFolderAsync)}() Start", userFileSystemNewItemPath); - DirectoryInfo remoteStorageItem = new DirectoryInfo(Path.Combine(RemoteStoragePath, folderMetadata.Name)); + string remoteStoragePath = Mapping.GetRemoteStoragePathById(RemoteStorageItemId); + DirectoryInfo remoteStorageItem = new DirectoryInfo(Path.Combine(remoteStoragePath, folderMetadata.Name)); remoteStorageItem.Create(); // Update remote storage folder metadata. @@ -82,16 +83,14 @@ public async Task CreateFolderAsync(IFolderMetadata folderMetadata) remoteStorageItem.LastAccessTimeUtc = folderMetadata.LastAccessTime.UtcDateTime; remoteStorageItem.LastWriteTimeUtc = folderMetadata.LastWriteTime.UtcDateTime; - // Get ETag from server here and save it on the client. - string eTagNew = "1234567890"; - - ExternalDataManager customDataManager = Engine.ExternalDataManager(userFileSystemNewItemPath); - await customDataManager.SetCustomDataAsync( - eTagNew, - false, - new[] { new FileSystemItemPropertyData((int)CustomColumnIds.ETag, eTagNew) }); + // Get ETag from your remote storage here and save it in persistent placeholder properties. + string eTag = (await WindowsFileSystemItem.GetUsnByPathAsync(remoteStorageItem.FullName)).ToString(); + PlaceholderItem placeholder = Engine.Placeholders.GetItem(userFileSystemNewItemPath); + await placeholder.Properties.AddOrUpdateAsync("ETag", eTag); - return null; + // Return remote storage item ID. It will be passed later + // into IEngine.GetFileSystemItemAsync() method on every call. + return WindowsFileSystemItem.GetItemIdByPath(remoteStorageItem.FullName); } /// @@ -117,31 +116,25 @@ public async Task GetChildrenAsync(string pattern, IOperationContext operationCo Logger.LogMessage("Creating", userFileSystemItemPath); userFileSystemChildren.Add(itemMetadata); } - - ExternalDataManager customDataManager = Engine.ExternalDataManager(userFileSystemItemPath); - - // Mark this item as not new, which is required for correct MS Office saving opertions. - customDataManager.IsNew = false; } // To signal that the children enumeration is completed // always call ReturnChildren(), even if the folder is empty. - resultContext.ReturnChildren(userFileSystemChildren.ToArray(), userFileSystemChildren.Count()); - - // Save ETags, the read-only attribute and all custom columns data. - foreach (FileSystemItemMetadataExt itemMetadata in userFileSystemChildren) - { - string userFileSystemItemPath = Path.Combine(UserFileSystemPath, itemMetadata.Name); - ExternalDataManager customDataManager = Engine.ExternalDataManager(userFileSystemItemPath); - - // Save ETag on the client side, to be sent to the remote storage as part of the update. - await customDataManager.SetCustomDataAsync(itemMetadata.ETag, itemMetadata.IsLocked, itemMetadata.CustomProperties); - } + await resultContext.ReturnChildrenAsync(userFileSystemChildren.ToArray(), userFileSystemChildren.Count()); + + // Save data that you wish to display in custom columns here. + //foreach (FileSystemItemMetadataExt itemMetadata in userFileSystemChildren) + //{ + // string userFileSystemItemPath = Path.Combine(UserFileSystemPath, itemMetadata.Name); + // PlaceholderItem placeholder = Engine.Placeholders.GetItem(userFileSystemItemPath); + // await placeholder.Properties.AddOrUpdateAsync("SomeData", someData); + //} } public async Task> EnumerateChildrenAsync(string pattern) { - IEnumerable remoteStorageChildren = new DirectoryInfo(RemoteStoragePath).EnumerateFileSystemInfos(pattern); + string remoteStoragePath = Mapping.GetRemoteStoragePathById(RemoteStorageItemId); + IEnumerable remoteStorageChildren = new DirectoryInfo(remoteStoragePath).EnumerateFileSystemInfos(pattern); List userFileSystemChildren = new List(); foreach (FileSystemInfo remoteStorageItem in remoteStorageChildren) @@ -157,7 +150,8 @@ public async Task WriteAsync(IFolderMetadata folderMetadata, IOperationContext o { Logger.LogMessage($"{nameof(IFolder)}.{nameof(WriteAsync)}()", UserFileSystemPath, default, operationContext); - DirectoryInfo remoteStorageItem = new DirectoryInfo(RemoteStoragePath); + string remoteStoragePath = Mapping.GetRemoteStoragePathById(RemoteStorageItemId); + DirectoryInfo remoteStorageItem = new DirectoryInfo(remoteStoragePath); // Update remote storage folder metadata. remoteStorageItem.Attributes = folderMetadata.Attributes; diff --git a/Windows/VirtualDrive/VirtualDrive/appsettings.json b/Windows/VirtualDrive/VirtualDrive/appsettings.json index b4c670f..bfbd21b 100644 --- a/Windows/VirtualDrive/VirtualDrive/appsettings.json +++ b/Windows/VirtualDrive/VirtualDrive/appsettings.json @@ -14,8 +14,9 @@ // You can specify here both absolute path and path relative to application folder. "RemoteStorageRootPath": ".\\RemoteStorage\\", - //Your virtual file system will be mounted under this path. - "UserFileSystemRootPath": "%USERPROFILE%\\VirtualDrive\\", + // Your virtual file system will be mounted under this path. + // Make sure to delete the all plceholders created by previous version of the software under the sync root. + "UserFileSystemRootPath": "%USERPROFILE%\\VirtualDriveV4\\", // Full synchronization interval in milliseconds. "SyncIntervalMs": 10000, @@ -24,7 +25,7 @@ // Set this parameter to 0 to avoid any network simulation delays. "NetworkSimulationDelayMs": 0, - // Automatically lock the file in remote storage when a file handle is being opened for writing, unlock on close. + // Automatically lock the file in the remote storage when a file handle is being opened for writing, unlock on close. "AutoLock": true, // To test performance: diff --git a/Windows/VirtualDrive/VirtualDrive/log4net.config b/Windows/VirtualDrive/VirtualDrive/log4net.config index 69c0064..027860c 100644 --- a/Windows/VirtualDrive/VirtualDrive/log4net.config +++ b/Windows/VirtualDrive/VirtualDrive/log4net.config @@ -6,14 +6,12 @@ - - diff --git a/Windows/VirtualFileSystem/Mapping.cs b/Windows/VirtualFileSystem/Mapping.cs index 4d2e4ab..f8ad96f 100644 --- a/Windows/VirtualFileSystem/Mapping.cs +++ b/Windows/VirtualFileSystem/Mapping.cs @@ -10,10 +10,10 @@ namespace VirtualFileSystem { /// - /// 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 static class Mapping { @@ -33,10 +33,29 @@ public static string ReverseMapPath(string remoteStorageUri) } /// - /// Gets a user file system item info from the remote storage data. + /// Gets remote storage path by remote storage item ID. + /// + /// + /// 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. + /// + /// Path in the remote storage. + public static string GetRemoteStoragePathById(byte[] remoteStorageId) + { + return WindowsFileSystemItem.GetPathByItemId(remoteStorageId); + } + + /// + /// Gets a user file system file/folder metadata from the remote storage file/folder data. /// /// Remote storage item info. - /// User file system 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 . public static IFileSystemItemMetadata GetUserFileSysteItemMetadata(FileSystemInfo remoteStorageItem) { IFileSystemItemMetadata userFileSystemItem; @@ -51,10 +70,9 @@ public static IFileSystemItemMetadata GetUserFileSysteItemMetadata(FileSystemInf userFileSystemItem = new FolderMetadata(); } - // Store you item ID here. It will be passed to IEngine.GetFileSystemItemAsync() during every operation. - // Note that the file is deleted during MS Office transactional save and iten ID will be deleted with it. - // See Virtual Drive sample for MS Office documents editing. - userFileSystemItem.ItemId = WindowsFileSystemItem.GetItemIdByPath(remoteStorageItem.FullName); + // Store you remote storage item ID in this property. + // It will be passed to IEngine.GetFileSystemItemAsync() during every operation. + userFileSystemItem.RemoteStorageItemId = WindowsFileSystemItem.GetItemIdByPath(remoteStorageItem.FullName); userFileSystemItem.Name = remoteStorageItem.Name; userFileSystemItem.Attributes = remoteStorageItem.Attributes; diff --git a/Windows/VirtualFileSystem/Program.cs b/Windows/VirtualFileSystem/Program.cs index 578e0b2..160b2a6 100644 --- a/Windows/VirtualFileSystem/Program.cs +++ b/Windows/VirtualFileSystem/Program.cs @@ -1,4 +1,3 @@ -using Microsoft.Extensions.Configuration; using System; using System.Diagnostics; using System.IO; @@ -7,14 +6,16 @@ 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.Samples.Common.Windows; -using ITHit.FileSystem.Windows; using ITHit.FileSystem; +using ITHit.FileSystem.Windows; using ITHit.FileSystem.Samples.Common; +using ITHit.FileSystem.Samples.Common.Windows; + namespace VirtualFileSystem { @@ -64,11 +65,6 @@ static async Task Main(string[] args) Logger.PrintHeader(log); - // Set root item ID. It will be passed to IEngine.GetFileSystemItemAsync() method - // as itemId parameter when a root folder is requested. - byte[] itemId = WindowsFileSystemItem.GetItemIdByPath(Settings.RemoteStorageRootPath); - PlaceholderFolder.GetItem(Settings.UserFileSystemRootPath).SetItemId(itemId); - try { Engine = new VirtualEngine( @@ -77,6 +73,11 @@ static async Task Main(string[] args) Settings.RemoteStorageRootPath, log); + // 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. + byte[] itemId = WindowsFileSystemItem.GetItemIdByPath(Settings.RemoteStorageRootPath); + Engine.Placeholders.GetItem(Settings.UserFileSystemRootPath).SetRemoteStorageItemId(itemId); + // Start processing OS file system calls. await Engine.StartAsync(); @@ -202,12 +203,13 @@ private static async Task ProcessUserInputAsync() case (char)ConsoleKey.Escape: case 'Q': - // Unregister during programm uninstall. Engine.Dispose(); + + // Call the code below during programm uninstall using classic msi. await UnregisterSyncRootAsync(); // Delete all files/folders. - CleanupAppFolders(); + await CleanupAppFoldersAsync(); return; case (char)ConsoleKey.Spacebar: @@ -263,7 +265,7 @@ private static async Task UnregisterSyncRootAsync() await Registrar.UnregisterAsync(SyncRootId); } - private static void CleanupAppFolders() + private static async Task CleanupAppFoldersAsync() { log.Info("\nDeleting all file and folder placeholders.\n"); try @@ -277,9 +279,7 @@ private static void CleanupAppFolders() try { - string localApplicationDataFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - string appDataPath = Path.Combine(localApplicationDataFolderPath, Settings.AppID); - Directory.Delete(appDataPath, true); + await((EngineWindows)Engine).UninstallCleanupAsync(); } catch (Exception ex) { @@ -294,13 +294,6 @@ private static void CleanupAppFolders() /// This method is provided solely for the development and testing convenience. private static void ShowTestEnvironment() { - // 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 Windows File Manager with remote storage. ProcessStartInfo rsInfo = new ProcessStartInfo(Program.Settings.RemoteStorageRootPath); @@ -309,6 +302,14 @@ private static void ShowTestEnvironment() { } + + // 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)) + { + + } } #endif diff --git a/Windows/VirtualFileSystem/RemoteStorageMonitor.cs b/Windows/VirtualFileSystem/RemoteStorageMonitor.cs index 22522a3..f5f3b77 100644 --- a/Windows/VirtualFileSystem/RemoteStorageMonitor.cs +++ b/Windows/VirtualFileSystem/RemoteStorageMonitor.cs @@ -180,7 +180,6 @@ private async void DeletedAsync(object sender, FileSystemEventArgs e) // 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. - Thread.Sleep(2000); // This can be removed in a real-life application. if (FsPath.Exists(userFileSystemPath)) { if (await engine.ServerNotifications(userFileSystemPath).DeleteAsync()) @@ -203,7 +202,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); + LogMessage("Renamed", e.OldFullPath, e.FullPath); string remoteStorageOldPath = e.OldFullPath; string remoteStorageNewPath = e.FullPath; try @@ -213,7 +212,6 @@ private async void RenamedAsync(object sender, RenamedEventArgs e) // 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. - Thread.Sleep(2000); // This can be removed in a real-life application. if (FsPath.Exists(userFileSystemOldPath)) { if (await engine.ServerNotifications(userFileSystemOldPath).MoveToAsync(userFileSystemNewPath)) diff --git a/Windows/VirtualFileSystem/VirtualEngine.cs b/Windows/VirtualFileSystem/VirtualEngine.cs index d0cc58b..402677b 100644 --- a/Windows/VirtualFileSystem/VirtualEngine.cs +++ b/Windows/VirtualFileSystem/VirtualEngine.cs @@ -49,15 +49,15 @@ public VirtualEngine(string license, string userFileSystemRootPath, string remot } /// - public override async Task GetFileSystemItemAsync(string userFileSystemPath, FileSystemItemType itemType, byte[] itemId = null) + public override async Task GetFileSystemItemAsync(string userFileSystemPath, FileSystemItemType itemType, byte[] remoteStorageItemId = null) { if (itemType == FileSystemItemType.File) { - return new VirtualFile(userFileSystemPath, itemId, this); + return new VirtualFile(userFileSystemPath, remoteStorageItemId, this); } else { - return new VirtualFolder(userFileSystemPath, itemId, this); + return new VirtualFolder(userFileSystemPath, remoteStorageItemId, this); } } diff --git a/Windows/VirtualFileSystem/VirtualFile.cs b/Windows/VirtualFileSystem/VirtualFile.cs index c3f38de..55e1460 100644 --- a/Windows/VirtualFileSystem/VirtualFile.cs +++ b/Windows/VirtualFileSystem/VirtualFile.cs @@ -20,9 +20,9 @@ public class VirtualFile : VirtualFileSystemItem, IFile /// Creates instance of this class. /// /// File path in the user file system. - /// Remote storage item ID. + /// Remote storage item ID. /// Logger. - public VirtualFile(string path, byte[] itemId, ILogger logger) : base(path, itemId, logger) + public VirtualFile(string path, byte[] remoteStorageItemId, ILogger logger) : base(path, remoteStorageItemId, logger) { } @@ -52,7 +52,8 @@ public async Task ReadAsync(Stream output, long offset, long length, ITransferDa SimulateNetworkDelay(length, resultContext); - await using (FileStream stream = System.IO.File.OpenRead(RemoteStoragePath)) + string remoteStoragePath = Mapping.GetRemoteStoragePathById(RemoteStorageItemId); + await 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. @@ -81,7 +82,8 @@ public async Task WriteAsync(IFileMetadata fileMetadata, Stream content = null, { Logger.LogMessage($"{nameof(IFile)}.{nameof(WriteAsync)}()", UserFileSystemPath, default, operationContext); - FileInfo remoteStorageItem = new FileInfo(RemoteStoragePath); + string remoteStoragePath = Mapping.GetRemoteStoragePathById(RemoteStorageItemId); + FileInfo remoteStorageItem = new FileInfo(remoteStoragePath); if (content != null) { diff --git a/Windows/VirtualFileSystem/VirtualFileSystem.csproj b/Windows/VirtualFileSystem/VirtualFileSystem.csproj index 9a4bfc8..52398eb 100644 --- a/Windows/VirtualFileSystem/VirtualFileSystem.csproj +++ b/Windows/VirtualFileSystem/VirtualFileSystem.csproj @@ -1,7 +1,7 @@  Exe - net5.0-windows10.0.18362 + net6.0-windows10.0.18362 10.0.18362.0 IT Hit LTD. IT Hit LTD. diff --git a/Windows/VirtualFileSystem/VirtualFileSystemItem.cs b/Windows/VirtualFileSystem/VirtualFileSystemItem.cs index 374babc..6cbd109 100644 --- a/Windows/VirtualFileSystem/VirtualFileSystemItem.cs +++ b/Windows/VirtualFileSystem/VirtualFileSystemItem.cs @@ -22,12 +22,7 @@ public abstract class VirtualFileSystemItem : IFileSystemItem /// /// File or folder item ID in the remote storage. /// - protected readonly byte[] ItemId; - - /// - /// Path of this file or folder in the remote storage. - /// - protected readonly string RemoteStoragePath; + protected readonly byte[] RemoteStorageItemId; /// /// Logger. @@ -38,100 +33,90 @@ public abstract class VirtualFileSystemItem : IFileSystemItem /// Creates instance of this class. /// /// File or folder path in the user file system. - /// Remote storage item ID. + /// Remote storage item ID. /// Logger. - public VirtualFileSystemItem(string userFileSystemPath, byte[] itemId, ILogger logger) + public VirtualFileSystemItem(string userFileSystemPath, byte[] remoteStorageItemId, ILogger logger) { - if (string.IsNullOrEmpty(userFileSystemPath)) - { - throw new ArgumentNullException(nameof(userFileSystemPath)); - } - ItemId = itemId ?? throw new ArgumentNullException(nameof(itemId)); + UserFileSystemPath = string.IsNullOrEmpty(userFileSystemPath) ? throw new ArgumentNullException(nameof(userFileSystemPath)) : userFileSystemPath; + RemoteStorageItemId = remoteStorageItemId ?? throw new ArgumentNullException(nameof(remoteStorageItemId)); Logger = logger ?? throw new ArgumentNullException(nameof(logger)); - - UserFileSystemPath = userFileSystemPath; - - try - { - RemoteStoragePath = WindowsFileSystemItem.GetPathByItemId(ItemId); - } - catch(ArgumentException) - { - // When a file is deleted, the IFile.CloseAsync() is called for the deleted file. - } } /// - public async Task MoveToAsync(string userFileSystemNewPath, byte[] newParentItemId, IOperationContext operationContext, IConfirmationResultContext resultContext) + public async Task MoveToAsync(string targetUserFileSystemPath, byte[] targetFolderRemoteStorageItemId, IOperationContext operationContext = null, IConfirmationResultContext resultContext = null) { + string userFileSystemNewPath = targetUserFileSystemPath; string userFileSystemOldPath = this.UserFileSystemPath; Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(MoveToAsync)}()", userFileSystemOldPath, userFileSystemNewPath, operationContext); - - if (userFileSystemNewPath.StartsWith(Program.Settings.UserFileSystemRootPath)) - { - // The item is moved within the virtual file system. - - string remoteStorageOldPath = RemoteStoragePath; - FileSystemInfo remoteStorageOldItem = FsPath.GetFileSystemItem(remoteStorageOldPath); - - // newParentItemId is null if the hydrated file is moved outside of the virtual file system, for example to the recycle bin. - if ((remoteStorageOldItem != null) && (newParentItemId != null)) - { - string remoteStorageNewParentPath = WindowsFileSystemItem.GetPathByItemId(newParentItemId); - string remoteStorageNewPath = Path.Combine(remoteStorageNewParentPath, Path.GetFileName(userFileSystemNewPath)); - - if (remoteStorageOldItem is FileInfo) - { - (remoteStorageOldItem as FileInfo).MoveTo(remoteStorageNewPath, true); - } - else - { - (remoteStorageOldItem as DirectoryInfo).MoveTo(remoteStorageNewPath); - } - - Logger.LogMessage("Moved item in remote storage succesefully", userFileSystemOldPath, userFileSystemNewPath, operationContext); - } - } - else - { - // The move target path is outside of the virtual file system - delete the item. - await DeleteAsync(operationContext, resultContext); - } } - /// - public async Task MoveToCompletionAsync(IMoveCompletionContext moveCompletionContext, IResultContext resultContext) + public async Task MoveToCompletionAsync(string targetUserFileSystemPath, byte[] targetFolderRemoteStorageItemId, IMoveCompletionContext operationContext = null, IResultContext resultContext = null) { - string userFileSystemNewPath = this.UserFileSystemPath; - string userFileSystemOldPath = moveCompletionContext.SourcePath; - Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(MoveToCompletionAsync)}()", userFileSystemOldPath, userFileSystemNewPath, moveCompletionContext); + string userFileSystemNewPath = targetUserFileSystemPath; + string userFileSystemOldPath = this.UserFileSystemPath; + Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(MoveToCompletionAsync)}()", userFileSystemOldPath, userFileSystemNewPath, operationContext); + + string remoteStorageOldPath = Mapping.GetRemoteStoragePathById(RemoteStorageItemId); + FileSystemInfo remoteStorageOldItem = FsPath.GetFileSystemItem(remoteStorageOldPath); - if(FsPath.IsFolder(userFileSystemNewPath)) + if (remoteStorageOldItem != null) { - // In this sample the folder does not have any metadata that can be modified on the client - // and should be synched to the remote storage, just marking the folder as in-sync after the move. - PlaceholderItem.GetItem(userFileSystemNewPath).SetInSync(true); + string remoteStorageNewParentPath = WindowsFileSystemItem.GetPathByItemId(targetFolderRemoteStorageItemId); + string remoteStorageNewPath = Path.Combine(remoteStorageNewParentPath, Path.GetFileName(targetUserFileSystemPath)); + + if (remoteStorageOldItem is FileInfo) + { + (remoteStorageOldItem as FileInfo).MoveTo(remoteStorageNewPath, true); + } + else + { + (remoteStorageOldItem as DirectoryInfo).MoveTo(remoteStorageNewPath); + } + + Logger.LogMessage("Moved item in remote storage succesefully", userFileSystemOldPath, targetUserFileSystemPath, operationContext); } } + /// - public async Task DeleteAsync(IOperationContext operationContext, IConfirmationResultContext resultContext) + public async Task DeleteAsync(IOperationContext operationContext = null, IConfirmationResultContext resultContext = null) { Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(DeleteAsync)}()", this.UserFileSystemPath, default, operationContext); // To cancel the operation and prevent the file from being deleted, // call the resultContext.ReturnErrorResult() method or throw any exception inside this method. - // IMPOTRTANT! See Windows Cloud API delete prevention bug description here: + // IMPOTRTANT! + // Make sure you have all Windows updates installed. + // See Windows Cloud API delete prevention bug description here: // https://stackoverflow.com/questions/68887190/delete-in-cloud-files-api-stopped-working-on-windows-21h1 // https://docs.microsoft.com/en-us/answers/questions/75240/bug-report-cfapi-ackdelete-borken-on-win10-2004.html + } + + /// + public async Task DeleteCompletionAsync(IOperationContext operationContext = null, IResultContext resultContext = null) + { + // On Windows, for rename with overwrite to function properly for folders, + // the deletion of the folder in the remote storage must be done in DeleteCompletionAsync() + // Otherwise the source folder will be deleted before files in it can be moved. + + Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(DeleteCompletionAsync)}()", this.UserFileSystemPath, default, operationContext); - // Note that some applications, such as Windows Explorer may call delete more than one time on the same file/folder. + string remoteStoragePath; + try + { + remoteStoragePath = Mapping.GetRemoteStoragePathById(RemoteStorageItemId); + } + catch(FileNotFoundException) + { + // Windows Explorer may call delete more than one time on the same file/folder. + return; + } - FileSystemInfo remoteStorageItem = FsPath.GetFileSystemItem(RemoteStoragePath); + FileSystemInfo remoteStorageItem = FsPath.GetFileSystemItem(remoteStoragePath); if (remoteStorageItem != null) { if (remoteStorageItem is FileInfo) @@ -145,16 +130,6 @@ public async Task DeleteAsync(IOperationContext operationContext, IConfirmationR Logger.LogMessage("Deleted item in remote storage succesefully", UserFileSystemPath, default, operationContext); } } - - /// - public async Task DeleteCompletionAsync(IOperationContext operationContext, IResultContext resultContext) - { - // On Windows, for rename with overwrite to function properly for folders, - // the deletion of the folder in the remote storage must be done in DeleteCompletionAsync() - // Otherwise the folder will be deleted before files in it can be moved. - - Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(DeleteCompletionAsync)}()", this.UserFileSystemPath, default, operationContext); - } /// diff --git a/Windows/VirtualFileSystem/VirtualFolder.cs b/Windows/VirtualFileSystem/VirtualFolder.cs index 3200e89..9a2d053 100644 --- a/Windows/VirtualFileSystem/VirtualFolder.cs +++ b/Windows/VirtualFileSystem/VirtualFolder.cs @@ -21,9 +21,9 @@ public class VirtualFolder : VirtualFileSystemItem, IFolder /// Creates instance of this class. /// /// Folder path in the user file system. - /// Remote storage item ID. + /// Remote storage item ID. /// Logger. - public VirtualFolder(string path, byte[] itemId, ILogger logger) : base(path, itemId, logger) + public VirtualFolder(string path, byte[] remoteStorageItemId, ILogger logger) : base(path, remoteStorageItemId, logger) { } @@ -33,11 +33,13 @@ public async Task CreateFileAsync(IFileMetadata fileMetadata, Stream con { Logger.LogMessage($"{nameof(IFolder)}.{nameof(CreateFileAsync)}()", Path.Combine(UserFileSystemPath, fileMetadata.Name)); - FileInfo remoteStorageItem = new FileInfo(Path.Combine(RemoteStoragePath, fileMetadata.Name)); + string remoteStoragePath = Mapping.GetRemoteStoragePathById(RemoteStorageItemId); + FileInfo remoteStorageNewItem = new FileInfo(Path.Combine(remoteStoragePath, fileMetadata.Name)); - // Upload remote storage file content. - await using (FileStream remoteStorageStream = remoteStorageItem.Open(FileMode.CreateNew, FileAccess.Write, FileShare.Delete)) + // Create remote storage file. + await 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) { await content.CopyToAsync(remoteStorageStream); @@ -46,14 +48,14 @@ public async Task CreateFileAsync(IFileMetadata fileMetadata, Stream con } // Update remote storage file metadata. - remoteStorageItem.Attributes = fileMetadata.Attributes; - remoteStorageItem.CreationTimeUtc = fileMetadata.CreationTime.UtcDateTime; - remoteStorageItem.LastWriteTimeUtc = fileMetadata.LastWriteTime.UtcDateTime; - remoteStorageItem.LastAccessTimeUtc = fileMetadata.LastAccessTime.UtcDateTime; - remoteStorageItem.LastWriteTimeUtc = fileMetadata.LastWriteTime.UtcDateTime; + remoteStorageNewItem.Attributes = fileMetadata.Attributes; + remoteStorageNewItem.CreationTimeUtc = fileMetadata.CreationTime.UtcDateTime; + remoteStorageNewItem.LastWriteTimeUtc = fileMetadata.LastWriteTime.UtcDateTime; + remoteStorageNewItem.LastAccessTimeUtc = fileMetadata.LastAccessTime.UtcDateTime; + remoteStorageNewItem.LastWriteTimeUtc = fileMetadata.LastWriteTime.UtcDateTime; // Return remote storage item ID. It will be passed later into IEngine.GetFileSystemItemAsync() method. - return WindowsFileSystemItem.GetItemIdByPath(remoteStorageItem.FullName); + return WindowsFileSystemItem.GetItemIdByPath(remoteStorageNewItem.FullName); } /// @@ -61,18 +63,19 @@ public async Task CreateFolderAsync(IFolderMetadata folderMetadata) { Logger.LogMessage($"{nameof(IFolder)}.{nameof(CreateFolderAsync)}()", Path.Combine(UserFileSystemPath, folderMetadata.Name)); - DirectoryInfo remoteStorageItem = new DirectoryInfo(Path.Combine(RemoteStoragePath, folderMetadata.Name)); - remoteStorageItem.Create(); + string remoteStoragePath = Mapping.GetRemoteStoragePathById(RemoteStorageItemId); + DirectoryInfo remoteStorageNewItem = new DirectoryInfo(Path.Combine(remoteStoragePath, folderMetadata.Name)); + remoteStorageNewItem.Create(); // Update remote storage folder metadata. - remoteStorageItem.Attributes = folderMetadata.Attributes; - remoteStorageItem.CreationTimeUtc = folderMetadata.CreationTime.UtcDateTime; - remoteStorageItem.LastWriteTimeUtc = folderMetadata.LastWriteTime.UtcDateTime; - remoteStorageItem.LastAccessTimeUtc = folderMetadata.LastAccessTime.UtcDateTime; - remoteStorageItem.LastWriteTimeUtc = folderMetadata.LastWriteTime.UtcDateTime; + remoteStorageNewItem.Attributes = folderMetadata.Attributes; + remoteStorageNewItem.CreationTimeUtc = folderMetadata.CreationTime.UtcDateTime; + remoteStorageNewItem.LastWriteTimeUtc = folderMetadata.LastWriteTime.UtcDateTime; + remoteStorageNewItem.LastAccessTimeUtc = folderMetadata.LastAccessTime.UtcDateTime; + remoteStorageNewItem.LastWriteTimeUtc = folderMetadata.LastWriteTime.UtcDateTime; // Return remote storage item ID. It will be passed later into IEngine.GetFileSystemItemAsync() method. - return WindowsFileSystemItem.GetItemIdByPath(remoteStorageItem.FullName); + return WindowsFileSystemItem.GetItemIdByPath(remoteStorageNewItem.FullName); } /// @@ -85,7 +88,8 @@ public async Task GetChildrenAsync(string pattern, IOperationContext operationCo Logger.LogMessage($"{nameof(IFolder)}.{nameof(GetChildrenAsync)}({pattern})", UserFileSystemPath, default, operationContext); - IEnumerable remoteStorageChildren = new DirectoryInfo(RemoteStoragePath).EnumerateFileSystemInfos(pattern); + string remoteStoragePath = Mapping.GetRemoteStoragePathById(RemoteStorageItemId); + IEnumerable remoteStorageChildren = new DirectoryInfo(remoteStoragePath).EnumerateFileSystemInfos(pattern); List userFileSystemChildren = new List(); foreach (FileSystemInfo remoteStorageItem in remoteStorageChildren) @@ -104,7 +108,7 @@ public async Task GetChildrenAsync(string pattern, IOperationContext operationCo // To signal that the children enumeration is completed // always call ReturnChildren(), even if the folder is empty. - resultContext.ReturnChildren(userFileSystemChildren.ToArray(), userFileSystemChildren.Count()); + await resultContext.ReturnChildrenAsync(userFileSystemChildren.ToArray(), userFileSystemChildren.Count()); } /// @@ -112,7 +116,8 @@ public async Task WriteAsync(IFolderMetadata folderMetadata, IOperationContext o { Logger.LogMessage($"{nameof(IFolder)}.{nameof(WriteAsync)}()", UserFileSystemPath, default, operationContext); - DirectoryInfo remoteStorageItem = new DirectoryInfo(RemoteStoragePath); + string remoteStoragePath = Mapping.GetRemoteStoragePathById(RemoteStorageItemId); + DirectoryInfo remoteStorageItem = new DirectoryInfo(remoteStoragePath); // Update remote storage folder metadata. remoteStorageItem.Attributes = folderMetadata.Attributes; diff --git a/Windows/VirtualFileSystem/appsettings.json b/Windows/VirtualFileSystem/appsettings.json index 4146988..c17e220 100644 --- a/Windows/VirtualFileSystem/appsettings.json +++ b/Windows/VirtualFileSystem/appsettings.json @@ -14,14 +14,15 @@ // You can specify here both absolute path and path relative to application folder. "RemoteStorageRootPath": ".\\RemoteStorage\\", - //Your virtual file system will be mounted under this path. - "UserFileSystemRootPath": "%USERPROFILE%\\VFS\\", + // Your virtual file system will be mounted under this path. + // Make sure to delete the all plceholders created by previous version of the software under the sync root. + "UserFileSystemRootPath": "%USERPROFILE%\\VFSV4\\", // Network delay in milliseconds. When this parameter is > 0 the file download is delayd to demonstrate file transfer progress. // Set this parameter to 0 to avoid any network simulation delays. "NetworkSimulationDelayMs": 0, - // Automatically lock the file in remote storage when a file handle is being opened for writing, unlock on close. + // Automatically lock the file in the remote storage when a file handle is being opened for writing, unlock on close. "AutoLock": true, // To test performance: diff --git a/Windows/VirtualFileSystem/log4net.config b/Windows/VirtualFileSystem/log4net.config index 69c0064..027860c 100644 --- a/Windows/VirtualFileSystem/log4net.config +++ b/Windows/VirtualFileSystem/log4net.config @@ -6,14 +6,12 @@ - - diff --git a/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/Program.cs b/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/Program.cs index 45ed7c1..ad5526f 100644 --- a/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/Program.cs +++ b/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/Program.cs @@ -10,7 +10,7 @@ using log4net; using log4net.Config; using log4net.Appender; - +using System.Diagnostics; namespace WebDAVDrive.ShellExtension { @@ -27,15 +27,11 @@ static async Task Main(string[] args) } // Load and initialize settings. - //ShellExtensionConfiguration.Initialize(); var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json", false, true).Build(); Settings settings = new Settings(); configuration.Bind(settings); - ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - ConfigureLogger(settings); - - ShellExtensionConfiguration.Initialize(settings, log); + ShellExtensionConfiguration.Initialize(settings); try { @@ -49,26 +45,7 @@ static async Task Main(string[] args) } catch (Exception ex) { - log.Error("", ex); - } - } - - /// - /// Configures log4net logger. - /// - /// Log file path. - private static void ConfigureLogger(Settings settings) - { - // 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+".ShellExtension", - Path.GetFileName(rollingFileAppender.File)); + Debug.WriteLine(ex.Message); } } } diff --git a/Windows/WebDAVDrive/WebDAVDrive.UI/WebDAVDrive.UI.csproj b/Windows/WebDAVDrive/WebDAVDrive.UI/WebDAVDrive.UI.csproj index 0ec74b9..ff0552c 100644 --- a/Windows/WebDAVDrive/WebDAVDrive.UI/WebDAVDrive.UI.csproj +++ b/Windows/WebDAVDrive/WebDAVDrive.UI/WebDAVDrive.UI.csproj @@ -9,6 +9,7 @@ IT Hit LTD. IT Hit LTD. WebDAV Drive + AnyCPU;x64 @@ -18,7 +19,7 @@ - + diff --git a/Windows/WebDAVDrive/WebDAVDrive/AppSettings.cs b/Windows/WebDAVDrive/WebDAVDrive/AppSettings.cs index 87d42ce..990bb43 100644 --- a/Windows/WebDAVDrive/WebDAVDrive/AppSettings.cs +++ b/Windows/WebDAVDrive/WebDAVDrive/AppSettings.cs @@ -98,10 +98,6 @@ public static AppSettings ReadSettings(this IConfiguration configuration) // Load product name from entry exe file. settings.ProductName = FileVersionInfo.GetVersionInfo(assemblyLocation).ProductName; - // Folder where custom data is stored. - string localApplicationDataFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - settings.ServerDataFolderPath = Path.Combine(localApplicationDataFolderPath, settings.AppID, settings.UserFileSystemRootPath.Replace(":", ""), "ServerData"); - return settings; } } diff --git a/Windows/WebDAVDrive/WebDAVDrive/Mapping.cs b/Windows/WebDAVDrive/WebDAVDrive/Mapping.cs index 8f33bec..7e8b1d2 100644 --- a/Windows/WebDAVDrive/WebDAVDrive/Mapping.cs +++ b/Windows/WebDAVDrive/WebDAVDrive/Mapping.cs @@ -6,6 +6,7 @@ using ITHit.FileSystem; using ITHit.FileSystem.Samples.Common; using ITHit.FileSystem.Samples.Common.Windows; +using ITHit.FileSystem.Windows; using Client = ITHit.WebDAV.Client; namespace WebDAVDrive @@ -14,14 +15,14 @@ namespace WebDAVDrive /// Maps a user file system path to the remote storage path and back. /// /// You will change methods of this class to map the user file system path to your remote storage path. - internal class Mapping : IMapping + internal static class Mapping // : IMapping { - private readonly VirtualEngineBase engine; + //private readonly VirtualEngineBase engine; - internal Mapping(VirtualEngineBase engine) - { - this.engine = engine; - } + //internal Mapping(VirtualEngineBase engine) + //{ + // this.engine = engine; + //} /// /// Returns a remote storage URI that corresponds to the user file system path. @@ -117,51 +118,54 @@ public static FileSystemItemMetadataExt GetUserFileSystemItemMetadata(Client.IHi userFileSystemItem.LastAccessTime = remoteStorageItem.LastModified; userFileSystemItem.ChangeTime = remoteStorageItem.LastModified; - userFileSystemItem.IsLocked = remoteStorageItem.ActiveLocks.Length > 0; - - if (remoteStorageItem is Client.IFile) - { - // We send the ETag to - // the server inside If-Match header togeter with updated content from client. - // This will make sure the changes on the server is not overwritten. - ((FileMetadataExt)userFileSystemItem).ETag = ((Client.IFile)remoteStorageItem).Etag; - }; - - // Set custom columns to be displayed in file manager. - // We create property definitions when registering the sync root with corresponding IDs. - List customProps = new List(); - - // Set lock properties. + // Ser information about third-party lock if any. Client.LockInfo lockInfo = remoteStorageItem.ActiveLocks.FirstOrDefault(); if (lockInfo != null) { - ServerLockInfo serverLockInfo = new ServerLockInfo() + userFileSystemItem.Lock = new ServerLockInfo() { LockToken = lockInfo.LockToken.LockToken, Owner = lockInfo.Owner, Exclusive = lockInfo.LockScope == Client.LockScope.Exclusive, LockExpirationDateUtc = DateTimeOffset.Now.Add(lockInfo.TimeOut) }; - customProps.AddRange( - serverLockInfo.GetLockProperties(Path.Combine(Program.Settings.IconsFolderPath, "Locked.ico")) - ); } - // Set ETag property. - if (remoteStorageItem is Client.IFile file) - { - customProps.Add(new FileSystemItemPropertyData((int)CustomColumnIds.ETag, file.Etag)); - }; - userFileSystemItem.CustomProperties = customProps; + /* + // Set custom columns to be displayed in file manager. + // We create property definitions when registering the sync root with corresponding IDs. + // The columns are rendered in IVirtualEngine.GetItemPropertiesAsync() call. + userFileSystemItem.CustomProperties = ; + */ return userFileSystemItem; } /// - public async Task IsModifiedAsync(string userFileSystemPath, FileSystemItemMetadataExt remoteStorageItemMetadata, ILogger logger) + //public async Task IsModifiedAsync(string userFileSystemPath, FileSystemItemMetadataExt remoteStorageItemMetadata, ILogger logger) + //{ + // return !await engine.ExternalDataManager(userFileSystemPath, logger).ETagManager.ETagEqualsAsync(remoteStorageItemMetadata); + //} + + + /// + /// Saves all data that is displayed in custom columns in file manager + /// as well as any additional custom data required by the client. + /// + public static async Task SavePropertiesAsync(this PlaceholderItem placeholder, FileSystemItemMetadataExt metadata) { - return !await engine.ExternalDataManager(userFileSystemPath, logger).ETagManager.ETagEqualsAsync(remoteStorageItemMetadata); + // Save lock applied by other users. + if (metadata.Lock != null) + { + await placeholder.Properties.AddOrUpdateAsync("ThirdPartyLockInfo", metadata.Lock); + } + + //foreach (FileSystemItemPropertyData prop in itemMetadata.CustomProperties) + //{ + // string key = ((CustomColumnIds)prop.Id).ToString(); + // await placeholder.Properties.AddOrUpdateAsync(key, prop); + //} } } } diff --git a/Windows/WebDAVDrive/WebDAVDrive/Program.cs b/Windows/WebDAVDrive/WebDAVDrive/Program.cs index f2007ea..20624a8 100644 --- a/Windows/WebDAVDrive/WebDAVDrive/Program.cs +++ b/Windows/WebDAVDrive/WebDAVDrive/Program.cs @@ -1,4 +1,3 @@ -using Microsoft.Extensions.Configuration; using System; using System.Diagnostics; using System.IO; @@ -10,15 +9,20 @@ 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.WebDAV.Client; using ITHit.WebDAV.Client.Exceptions; -using ITHit.FileSystem; -using ITHit.FileSystem.Samples.Common; +using System.Net.Http; + using WebDAVDrive.UI; using WebDAVDrive.UI.ViewModels; @@ -91,47 +95,47 @@ static async Task Main(string[] args) Logger.PrintHeader(log); - ConfigureWebDavSession(); - - try + using (DavClient = ConfigureWebDavSession()) { - Engine = new VirtualEngine( - Settings.UserFileSystemLicense, - Settings.UserFileSystemRootPath, - Settings.WebDAVServerUrl, - Settings.ServerDataFolderPath, - Settings.WebSocketServerUrl, - Settings.IconsFolderPath, - Settings.RpcCommunicationChannelName, - Settings.SyncIntervalMs, - log); - Engine.AutoLock = Settings.AutoLock; - - // Start tray application in a separate thread. - WindowsTrayInterface.CreateTrayInterface(Settings.ProductName, Engine, exitEvent); - - // Start processing OS file system calls. - await Engine.StartAsync(); + try + { + Engine = new VirtualEngine( + Settings.UserFileSystemLicense, + Settings.UserFileSystemRootPath, + Settings.WebDAVServerUrl, + Settings.WebSocketServerUrl, + Settings.IconsFolderPath, + Settings.RpcCommunicationChannelName, + Settings.SyncIntervalMs, + log); + Engine.AutoLock = Settings.AutoLock; + + // Start tray application in a separate thread. + WindowsTrayInterface.CreateTrayInterface(Settings.ProductName, Engine, exitEvent); + + // 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(); + } + finally + { + Engine.Dispose(); + } } } @@ -169,13 +173,13 @@ private static void ShowTestEnvironment() } - // Open Windows File Manager with custom data storage. Uncomment this to debug custom data storage management. - ProcessStartInfo serverDataInfo = new ProcessStartInfo(Program.Settings.ServerDataFolderPath); - serverDataInfo.UseShellExecute = true; // Open window only if not opened already. - using (Process serverDataWinFileManager = Process.Start(serverDataInfo)) - { + //// Open Windows File Manager with custom data storage. Uncomment this to debug custom data storage management. + //ProcessStartInfo serverDataInfo = new ProcessStartInfo(Program.Settings.ServerDataFolderPath); + //serverDataInfo.UseShellExecute = true; // Open window only if not opened already. + //using (Process serverDataWinFileManager = Process.Start(serverDataInfo)) + //{ - } + //} } #endif @@ -194,11 +198,20 @@ private static string SyncRootId /// /// Creates and configures WebDAV client to access the remote storage. /// - private static void ConfigureWebDavSession() + private static WebDavSession ConfigureWebDavSession() { - DavClient = new WebDavSession(Program.Settings.WebDAVClientLicense); - DavClient.WebDavError += DavClient_WebDavError; - DavClient.WebDavMessage += DavClient_WebDAVMessage; + HttpClientHandler handler = new HttpClientHandler() + { + AllowAutoRedirect = false, + + // Enable pre-authentication to avoid double requests. + // This option improves performance but is less secure. + // PreAuthenticate = true, + }; + WebDavSession davClient = new WebDavSession(Program.Settings.WebDAVClientLicense); + davClient.WebDavError += DavClient_WebDavError; + davClient.WebDavMessage += DavClient_WebDAVMessage; + return davClient; } /// @@ -424,23 +437,23 @@ private static async Task ProcessUserInputAsync() case 'q': // Unregister during programm uninstall. - await Engine.StopAsync(); + 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': - // Unregister during programm uninstall. - await Engine.StopAsync(); + Engine.Dispose(); + + // Call the code below during programm uninstall using classic msi. await UnregisterSyncRootAsync(); // Delete all files/folders. - CleanupAppFolders(); + await CleanupAppFoldersAsync(); return; case (char)ConsoleKey.Spacebar: - await Engine.StopAsync(); log.Info("\n\nAll downloaded file / folder placeholders remain in file system. Restart the application to continue managing files.\n"); return; @@ -465,7 +478,6 @@ private static async Task RegisterSyncRootAsync() { log.Info($"\nRegistering {Settings.UserFileSystemRootPath} sync root."); Directory.CreateDirectory(Settings.UserFileSystemRootPath); - Directory.CreateDirectory(Settings.ServerDataFolderPath); await Registrar.RegisterAsync(SyncRootId, Settings.UserFileSystemRootPath, Settings.ProductName, Path.Combine(Settings.IconsFolderPath, "Drive.ico")); @@ -494,7 +506,7 @@ private static async Task UnregisterSyncRootAsync() await Registrar.UnregisterAsync(SyncRootId); } - private static void CleanupAppFolders() + private static async Task CleanupAppFoldersAsync() { log.Info("\nDeleting all file and folder placeholders.\n"); try @@ -508,9 +520,7 @@ private static void CleanupAppFolders() try { - string localApplicationDataFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - string appDataPath = Path.Combine(localApplicationDataFolderPath, Settings.AppID); - Directory.Delete(appDataPath, true); + await ((EngineWindows)Engine).UninstallCleanupAsync(); } catch (Exception ex) { diff --git a/Windows/WebDAVDrive/WebDAVDrive/RemoteStorageMonitor.cs b/Windows/WebDAVDrive/WebDAVDrive/RemoteStorageMonitor.cs index a0ba138..d361208 100644 --- a/Windows/WebDAVDrive/WebDAVDrive/RemoteStorageMonitor.cs +++ b/Windows/WebDAVDrive/WebDAVDrive/RemoteStorageMonitor.cs @@ -1,6 +1,7 @@ using ITHit.FileSystem; using ITHit.FileSystem.Samples.Common; using ITHit.FileSystem.Samples.Common.Windows; +using ITHit.FileSystem.Windows; using ITHit.WebDAV.Client; using log4net; using System; @@ -213,9 +214,7 @@ private async Task CreatedAsync(string remoteStoragePath) FileSystemItemMetadataExt itemMetadata = Mapping.GetUserFileSystemItemMetadata(remoteStorageItem); if (await engine.ServerNotifications(userFileSystemParentPath).CreateAsync(new[] { itemMetadata }) > 0) { - ExternalDataManager customDataManager = engine.ExternalDataManager(userFileSystemPath); - - await customDataManager.SetCustomDataAsync(itemMetadata.ETag, itemMetadata.IsLocked, itemMetadata.CustomProperties); + await engine.Placeholders.GetItem(userFileSystemPath).SavePropertiesAsync(itemMetadata); // 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. @@ -252,9 +251,8 @@ private async Task ChangedAsync(string remoteStoragePath) if (remoteStorageItem != null) { FileSystemItemMetadataExt itemMetadata = Mapping.GetUserFileSystemItemMetadata(remoteStorageItem); - ExternalDataManager customDataManager = engine.ExternalDataManager(userFileSystemPath); - await customDataManager.SetCustomDataAsync(itemMetadata.ETag, itemMetadata.IsLocked, itemMetadata.CustomProperties); + await engine.Placeholders.GetItem(userFileSystemPath).SavePropertiesAsync(itemMetadata); // Can not update read-only files, read-only attribute must be removed. FileInfo userFileSystemFile = new FileInfo(userFileSystemPath); @@ -307,10 +305,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)) { - // The target parent folder exists and is online, the item moved. Move custom data. - await engine.ExternalDataManager(userFileSystemOldPath, this).MoveToAsync(userFileSystemNewPath); - - LogMessage("Moved succesefully:", userFileSystemOldPath, userFileSystemNewPath); + LogMessage("Moved succesefully", userFileSystemOldPath, userFileSystemNewPath); } else { @@ -345,8 +340,6 @@ private async Task DeletedAsync(string remoteStoragePath) { if (await engine.ServerNotifications(userFileSystemPath).DeleteAsync()) { - engine.ExternalDataManager(userFileSystemPath, this).Delete(); - // 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); @@ -373,16 +366,15 @@ private async Task LockedAsync(string remoteStoragePath) if (FsPath.Exists(userFileSystemPath)) { - ExternalDataManager customDataManager = engine.ExternalDataManager(userFileSystemPath); - IHierarchyItem remoteStorageItem = await Program.DavClient.GetItemAsync(new Uri(remoteStoragePath)); if (remoteStorageItem != null) { FileSystemItemMetadataExt itemMetadata = Mapping.GetUserFileSystemItemMetadata(remoteStorageItem); - await customDataManager.SetCustomDataAsync(itemMetadata.ETag, itemMetadata.IsLocked, itemMetadata.CustomProperties); + // Save info about the third-party lock. + await engine.Placeholders.GetItem(userFileSystemPath).SavePropertiesAsync(itemMetadata); - LogMessage("Locked succesefully", userFileSystemPath); + LogMessage("Third-party lock info added", userFileSystemPath); } } } @@ -405,18 +397,23 @@ private async Task UnlockedAsync(string remoteStoragePath) if (FsPath.Exists(userFileSystemPath)) { - ExternalDataManager customDataManager = engine.ExternalDataManager(userFileSystemPath); - - if (!await customDataManager.LockManager.IsLockedByThisUserAsync()) + if(engine.Placeholders.GetItem(userFileSystemPath).Properties.Remove("ThirdPartyLockInfo")) { - // Remove the read-only attribute and all custom columns data. - await customDataManager.SetLockedByAnotherUserAsync(false); + LogMessage("Third-party lock info deleted", userFileSystemPath); + } - // Remove lock icon and lock info in custom columns. - await customDataManager.SetLockInfoAsync(null); + //ExternalDataManager customDataManager = engine.ExternalDataManager(userFileSystemPath); - LogMessage("Unlocked succesefully", userFileSystemPath); - } + //if (!await customDataManager.LockManager.IsLockedByThisUserAsync()) + //{ + // // Remove the read-only attribute and all custom columns data. + // await customDataManager.SetLockedByAnotherUserAsync(false); + + // // Remove lock icon and lock info in custom columns. + // await customDataManager.SetLockInfoAsync(null); + + // LogMessage("Unlocked succesefully", userFileSystemPath); + //} } } catch (Exception ex) diff --git a/Windows/WebDAVDrive/WebDAVDrive/VirtualEngine.cs b/Windows/WebDAVDrive/WebDAVDrive/VirtualEngine.cs index 98f5cbf..7e2bcba 100644 --- a/Windows/WebDAVDrive/WebDAVDrive/VirtualEngine.cs +++ b/Windows/WebDAVDrive/WebDAVDrive/VirtualEngine.cs @@ -1,11 +1,17 @@ +using System; +using System.Net; +using System.Linq; +using System.Collections.Generic; using System.IO; using System.Threading.Tasks; + using log4net; using ITHit.FileSystem; using ITHit.FileSystem.Samples.Common.Windows; -using System; -using System.Net; -using System.Linq; +using ITHit.FileSystem.Windows; +using ITHit.FileSystem.Samples.Common; + +using ITHit.WebDAV.Client; namespace WebDAVDrive { @@ -31,7 +37,7 @@ public class VirtualEngine : VirtualEngineBase /// Your file system tree will be located under this folder. /// /// Path to the remote storage root. - /// Path to the folder that stores custom data associated with files and folders. + /// 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. @@ -40,13 +46,12 @@ public VirtualEngine( string license, string userFileSystemRootPath, string remoteStorageRootPath, - string serverDataFolderPath, string webSocketServerUrl, string iconsFolderPath, string rpcCommunicationChannelName, double syncIntervalMs, ILog log4net) - : base(license, userFileSystemRootPath, remoteStorageRootPath, serverDataFolderPath, iconsFolderPath, rpcCommunicationChannelName, syncIntervalMs, log4net) + : base(license, userFileSystemRootPath, remoteStorageRootPath, iconsFolderPath, rpcCommunicationChannelName, syncIntervalMs, log4net) { RemoteStorageMonitor = new RemoteStorageMonitor(webSocketServerUrl, this, log4net); } @@ -64,7 +69,7 @@ public override async Task GetFileSystemItemAsync(string userFi } } - public override IMapping Mapping { get { return new Mapping(this); } } + //public override IMapping Mapping { get { return new Mapping(this); } } /// public override async Task StartAsync() @@ -97,18 +102,11 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - /// - /// Returns thumbnail for specified path in the user file system. - /// - /// - /// Throw if thumbnail is not available. - /// You may also return empty array of null as indication of non existed thumbnail. - /// - /// Path in user file system. - /// Thumbnail size in pixels. - /// Thumbnail bitmap or null if the thumbnail handler is not found. + /// 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('.'); @@ -119,8 +117,13 @@ public override async Task GetThumbnailAsync(string userFileSystemPath, try { - using Stream stream = await Program.DavClient.DownloadAsync(new Uri(filePathRemote)); - return await StreamToByteArrayAsync(stream); + using (IWebResponse response = await Program.DavClient.DownloadAsync(new Uri(filePathRemote))) + { + using (Stream stream = await response.GetResponseStreamAsync()) + { + thumbnail = await StreamToByteArrayAsync(stream); + } + } } catch (WebException we) { @@ -128,10 +131,14 @@ public override async Task GetThumbnailAsync(string userFileSystemPath, } catch (Exception e) { - LogError("Failed to load thumbnail", userFileSystemPath, null, e); + LogError($"Failed to load thumbnail {size}px", userFileSystemPath, null, e); } } - return null; + + string thumbnailResult = thumbnail != null ? "Success" : "Not Impl"; + LogMessage($"{nameof(VirtualEngine)}.{nameof(GetThumbnailAsync)}() - {thumbnailResult}", userFileSystemPath); + + return thumbnail; } private static async Task StreamToByteArrayAsync(Stream stream) @@ -142,5 +149,83 @@ private static async Task StreamToByteArrayAsync(Stream stream) 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 f5ce44e..614e47c 100644 --- a/Windows/WebDAVDrive/WebDAVDrive/VirtualFile.cs +++ b/Windows/WebDAVDrive/WebDAVDrive/VirtualFile.cs @@ -50,15 +50,33 @@ public async Task ReadAsync(Stream output, long offset, long length, ITransferDa SimulateNetworkDelay(length, resultContext); - if (offset == 0 && length == operationContext.FileSize) { + if (offset == 0 && length == operationContext.FileSize) + { // If we read entire file, do not add Range header. Pass -1 to not add it. offset = -1; } - using (Stream stream = await Program.DavClient.DownloadAsync(new Uri(RemoteStoragePath), offset, length)) + + string eTag = null; + + // 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)) { - const int bufferSize = 0x500000; // 5Mb. Buffer size must be multiple of 4096 bytes for optimal performance. - await stream.CopyToAsync(output, bufferSize, length); + using (Stream stream = await response.GetResponseStreamAsync()) + { + await stream.CopyToAsync(output, bufferSize, length); + } + eTag = response.GetHeaderValue("ETag"); } + + //// Store ETag here. + PlaceholderItem placeholder = Engine.Placeholders.GetItem(UserFileSystemPath); + await placeholder.Properties.AddOrUpdateAsync("ETag", eTag); + + //using (Stream stream = await Program.DavClient.DownloadAsync(new Uri(RemoteStoragePath), offset, length)) + //{ + // await stream.CopyToAsync(output, bufferSize, length); + //} } /// @@ -81,33 +99,41 @@ public async Task WriteAsync(IFileMetadata fileMetadata, Stream content = null, { Logger.LogMessage($"{nameof(IFile)}.{nameof(WriteAsync)}()", UserFileSystemPath, default, operationContext); - ExternalDataManager customDataManager = Engine.ExternalDataManager(UserFileSystemPath); - // Send the ETag to the server as part of the update to ensure the file in the remote storge is not modified since last read. - string oldEtag = await customDataManager.ETagManager.GetETagAsync(); - - // Send the lock-token to the server as part of the update. - string lockToken = (await customDataManager.LockManager.GetLockInfoAsync())?.LockToken; - Client.LockUriTokenPair[] lockTokens = new Client.LockUriTokenPair[] { new Client.LockUriTokenPair(new Uri(RemoteStoragePath), lockToken) }; - if (content != null) { - long contentLength = content != null ? content.Length : 0; - - // Update remote storage file content. - // Get the new ETag returned by the server (if any). - string eTagNew = await Program.DavClient.UploadAsync(new Uri(RemoteStoragePath), async (outputStream) => { - if (content != null) + // Send the ETag to the server as part of the update to ensure + // the file in the remote storge is not modified since last read. + PlaceholderItem placeholder = Engine.Placeholders.GetItem(UserFileSystemPath); + + string oldEtag = null; //await placeholder.Properties["ETag"].GetValueAsync(); + + if (placeholder.Properties.TryGetValue("ETag", out IDataItem propETag)) + { + propETag.TryGetValue(out oldEtag); + } + + // Read the lock-token and send it to the server as part of the update. + Client.LockUriTokenPair[] lockTokens = null; + IDataItem propLockInfo; + if (placeholder.Properties.TryGetValue("LockInfo", out propLockInfo)) + { + ServerLockInfo lockInfo; + if (propLockInfo.TryGetValue(out lockInfo)) { - // Rewind for new copy (e.g. retry) - content.Position = 0; - await content.CopyToAsync(outputStream); + string lockToken = lockInfo.LockToken; + lockTokens = new Client.LockUriTokenPair[] { new Client.LockUriTokenPair(new Uri(RemoteStoragePath), lockToken) }; } - }, null, contentLength, 0, -1, lockTokens, oldEtag); + } + + // Update remote storage file content, + // also get and save a new ETag returned by the server, if any. + string newEtag = await Program.DavClient.UploadAsync(new Uri(RemoteStoragePath), async (outputStream) => { + // Setting position to 0 is required in case of retry. + content.Position = 0; + await content.CopyToAsync(outputStream); + }, null, content.Length, 0, -1, lockTokens, oldEtag); - await customDataManager.SetCustomDataAsync( - eTagNew, - null, - new[] { new FileSystemItemPropertyData((int)CustomColumnIds.ETag, eTagNew) }); + await placeholder.Properties.AddOrUpdateAsync("ETag", newEtag); } } } diff --git a/Windows/WebDAVDrive/WebDAVDrive/VirtualFileSystemItem.cs b/Windows/WebDAVDrive/WebDAVDrive/VirtualFileSystemItem.cs index a0034f2..e0dd4a1 100644 --- a/Windows/WebDAVDrive/WebDAVDrive/VirtualFileSystemItem.cs +++ b/Windows/WebDAVDrive/WebDAVDrive/VirtualFileSystemItem.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using ITHit.FileSystem; -using ITHit.FileSystem.Samples.Common.Windows; +using ITHit.FileSystem.Samples.Common; using ITHit.FileSystem.Windows; using ITHit.WebDAV.Client; @@ -55,40 +55,25 @@ public VirtualFileSystemItem(string userFileSystemPath, VirtualEngine engine, IL } /// - public async Task MoveToAsync(string userFileSystemNewPath, byte[] targetParentItemId, IOperationContext operationContext = null, IConfirmationResultContext resultContext = null) + public async Task MoveToAsync(string targetUserFileSystemPath, byte[] targetParentItemId, IOperationContext operationContext = null, IConfirmationResultContext resultContext = null) { + string userFileSystemNewPath = targetUserFileSystemPath; string userFileSystemOldPath = this.UserFileSystemPath; Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(MoveToAsync)}()", userFileSystemOldPath, userFileSystemNewPath, operationContext); - - if (userFileSystemNewPath.StartsWith(Program.Settings.UserFileSystemRootPath)) - { - // The item is moved within the virtual file system. - string remoteStorageOldPath = RemoteStoragePath; - string remoteStorageNewPath = Mapping.MapPath(userFileSystemNewPath); - - await Program.DavClient.MoveToAsync(new Uri(remoteStorageOldPath), new Uri(remoteStorageNewPath), true); - await Engine.ExternalDataManager(userFileSystemOldPath, Logger).MoveToAsync(userFileSystemNewPath); - } - else - { - // The move target path is outside of the virtual file system - delete the item. - await DeleteAsync(operationContext, resultContext); - } } /// - public async Task MoveToCompletionAsync(IMoveCompletionContext moveCompletionContext = null, IResultContext resultContext = null) + public async Task MoveToCompletionAsync(string targetUserFileSystemPath, byte[] targetFolderRemoteStorageItemId, IMoveCompletionContext operationContext = null, IResultContext resultContext = null) { - string userFileSystemNewPath = this.UserFileSystemPath; - string userFileSystemOldPath = moveCompletionContext.SourcePath; - Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(MoveToCompletionAsync)}()", userFileSystemOldPath, userFileSystemNewPath, moveCompletionContext); + string userFileSystemNewPath = targetUserFileSystemPath; + string userFileSystemOldPath = this.UserFileSystemPath; + Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(MoveToCompletionAsync)}()", userFileSystemOldPath, userFileSystemNewPath, operationContext); - if (FsPath.IsFolder(userFileSystemNewPath)) - { - // In this sample the folder does not have any metadata that can be modified on the client - // and should be synched to the remote storage, just marking the folder as in-sync after the move. - PlaceholderItem.GetItem(userFileSystemNewPath).SetInSync(true); - } + string remoteStorageOldPath = RemoteStoragePath; + string remoteStorageNewPath = Mapping.MapPath(userFileSystemNewPath); + + await Program.DavClient.MoveToAsync(new Uri(remoteStorageOldPath), new Uri(remoteStorageNewPath), true); + Logger.LogMessage("Moved in the remote storage succesefully", userFileSystemOldPath, targetUserFileSystemPath, operationContext); } /// @@ -104,16 +89,6 @@ public async Task DeleteAsync(IOperationContext operationContext, IConfirmationR // https://docs.microsoft.com/en-us/answers/questions/75240/bug-report-cfapi-ackdelete-borken-on-win10-2004.html // Note that some applications, such as Windows Explorer may call delete more than one time on the same file/folder. - - try - { - await Program.DavClient.DeleteAsync(new Uri(RemoteStoragePath)); - Engine.ExternalDataManager(UserFileSystemPath, Logger).Delete(); - } - catch(Exception ex) - { - Logger.LogMessage(ex.Message); - } } /// @@ -124,6 +99,17 @@ public async Task DeleteCompletionAsync(IOperationContext operationContext, IRes // Otherwise the folder will be deleted before files in it can be moved. Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(DeleteCompletionAsync)}()", UserFileSystemPath, default, operationContext); + + try + { + await Program.DavClient.DeleteAsync(new Uri(RemoteStoragePath)); + Logger.LogMessage("Deleted in the remote storage succesefully", UserFileSystemPath, default, operationContext); + } + catch (Exception ex) + { + // Windows Explorer may call delete more than one time on the same file/folder. + Logger.LogMessage(ex.Message); + } } /// @@ -157,38 +143,25 @@ public async Task LockAsync(LockMode lockMode, IOperationContext operationContex { Logger.LogMessage($"{nameof(ILock)}.{nameof(LockAsync)}()", UserFileSystemPath, default, operationContext); - ExternalDataManager customDataManager = Engine.ExternalDataManager(UserFileSystemPath, Logger); - LockManager lockManager = customDataManager.LockManager; - if (!Engine.ExternalDataManager(UserFileSystemPath).IsNew) - { - // Indicate that lock has started by this user on this machine. - await lockManager.SetLockPending(); - - // Set pending icon, so the user has a feedback as lock operation may take some time. - await customDataManager.SetLockPendingIconAsync(true); - - // Call your remote storage here to lock the item. - // 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); - ServerLockInfo serverLockInfo = new ServerLockInfo - { - LockToken = lockInfo.LockToken.LockToken, - Exclusive = lockInfo.LockScope == LockScope.Exclusive, - Owner = lockInfo.Owner, - LockExpirationDateUtc = DateTimeOffset.Now.Add(lockInfo.TimeOut) - }; - - // Save lock-token and lock-mode. - await lockManager.SetLockInfoAsync(serverLockInfo); - await lockManager.SetLockModeAsync(lockMode); - - // Set lock icon and lock info in custom columns. - await customDataManager.SetLockInfoAsync(serverLockInfo); + // Call your remote storage here to lock the item. + // 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. - Logger.LogMessage("Locked in remote storage succesefully.", UserFileSystemPath); - } + LockInfo lockInfo = await Program.DavClient.LockAsync(new Uri(RemoteStoragePath), LockScope.Exclusive, false, null, TimeSpan.MaxValue); + ServerLockInfo serverLockInfo = new ServerLockInfo + { + LockToken = lockInfo.LockToken.LockToken, + Exclusive = lockInfo.LockScope == LockScope.Exclusive, + Owner = lockInfo.Owner, + LockExpirationDateUtc = DateTimeOffset.Now.Add(lockInfo.TimeOut) + }; + + // Save lock-token and lock-mode. + PlaceholderItem placeholder = Engine.Placeholders.GetItem(UserFileSystemPath); + await placeholder.Properties.AddOrUpdateAsync("LockInfo", serverLockInfo); + await placeholder.Properties.AddOrUpdateAsync("LockMode", lockMode); + + Logger.LogMessage("Locked in the remote storage succesefully", UserFileSystemPath, default, operationContext); } @@ -196,8 +169,17 @@ public async Task LockAsync(LockMode lockMode, IOperationContext operationContex /// public async Task GetLockModeAsync(IOperationContext operationContext = null) { - LockManager lockManager = Engine.ExternalDataManager(UserFileSystemPath, Logger).LockManager; - return await lockManager.GetLockModeAsync(); + PlaceholderItem placeholder = Engine.Placeholders.GetItem(UserFileSystemPath); + + IDataItem property; + if (placeholder.Properties.TryGetValue("LockMode", out property)) + { + return await property.GetValueAsync(); + } + else + { + return LockMode.None; + } } @@ -207,20 +189,17 @@ public async Task UnlockAsync(IOperationContext operationContext = null) { Logger.LogMessage($"{nameof(ILock)}.{nameof(UnlockAsync)}()", UserFileSystemPath, default, operationContext); - ExternalDataManager customDataManager = Engine.ExternalDataManager(UserFileSystemPath, Logger); - LockManager lockManager = customDataManager.LockManager; - - // Set pending icon, so the user has a feedback as unlock operation may take some time. - await customDataManager.SetLockPendingIconAsync(true); + // Read the lock-token. + PlaceholderItem placeholder = Engine.Placeholders.GetItem(UserFileSystemPath); + string lockToken = (await placeholder.Properties["LockInfo"].GetValueAsync())?.LockToken; - // Read lock-token from lock-info file. - string lockToken = (await lockManager.GetLockInfoAsync()).LockToken; LockUriTokenPair[] lockTokens = new LockUriTokenPair[] { new LockUriTokenPair(new Uri(RemoteStoragePath), lockToken)}; // Unlock the item in the remote storage. try { await Program.DavClient.UnlockAsync(new Uri(RemoteStoragePath), lockTokens); + Logger.LogMessage("Unlocked in the remote storage succesefully", UserFileSystemPath, default, operationContext); } catch (ITHit.WebDAV.Client.Exceptions.ConflictException) { @@ -228,12 +207,8 @@ public async Task UnlockAsync(IOperationContext operationContext = null) } // Delete lock-mode and lock-token info. - lockManager.DeleteLock(); - - // Remove lock icon and lock info in custom columns. - await customDataManager.SetLockInfoAsync(null); - - Logger.LogMessage("Unlocked in the remote storage succesefully", UserFileSystemPath); + placeholder.Properties.Remove("LockInfo"); + placeholder.Properties.Remove("LockMode"); } } diff --git a/Windows/WebDAVDrive/WebDAVDrive/VirtualFolder.cs b/Windows/WebDAVDrive/WebDAVDrive/VirtualFolder.cs index 586a8f6..215f8f3 100644 --- a/Windows/WebDAVDrive/WebDAVDrive/VirtualFolder.cs +++ b/Windows/WebDAVDrive/WebDAVDrive/VirtualFolder.cs @@ -8,6 +8,7 @@ using ITHit.FileSystem.Samples.Common.Windows; using ITHit.FileSystem.Samples.Common; using Client = ITHit.WebDAV.Client; +using ITHit.FileSystem.Windows; namespace WebDAVDrive { @@ -31,27 +32,27 @@ public async Task CreateFileAsync(IFileMetadata fileMetadata, Stream con string userFileSystemNewItemPath = Path.Combine(UserFileSystemPath, fileMetadata.Name); Logger.LogMessage($"{nameof(IFolder)}.{nameof(CreateFileAsync)}()", userFileSystemNewItemPath); + // Create a new file in the remote storage. Uri newFileUri = new Uri(new Uri(RemoteStoragePath), fileMetadata.Name); long contentLength = content != null ? content.Length : 0; // Update remote storage file content. - // Get the new ETag returned by the server (if any). - string eTagNew = await Program.DavClient.UploadAsync(newFileUri, async (outputStream) => { + // Get the new ETag returned by the server, if any. + string eTag = await Program.DavClient.UploadAsync(newFileUri, async (outputStream) => { if (content != null) { + // Setting position to 0 is required in case of retry. + content.Position = 0; await content.CopyToAsync(outputStream); } }, null, contentLength); - ExternalDataManager customDataManager = Engine.ExternalDataManager(userFileSystemNewItemPath); - - // Store ETag unlil the next update. - await customDataManager.SetCustomDataAsync( - eTagNew, - false, - new[] { new FileSystemItemPropertyData((int)CustomColumnIds.ETag, eTagNew) }); - + // Store ETag it in persistent placeholder properties untill the next update. + PlaceholderItem placeholder = Engine.Placeholders.GetItem(userFileSystemNewItemPath); + await placeholder.Properties.AddOrUpdateAsync("ETag", eTag); + + // WebDAV does not use any item IDs, returning null. return null; } @@ -64,16 +65,12 @@ public async Task CreateFolderAsync(IFolderMetadata folderMetadata) Uri newFolderUri = new Uri(new Uri(RemoteStoragePath), folderMetadata.Name); await Program.DavClient.CreateFolderAsync(newFolderUri); - ExternalDataManager customDataManager = Engine.ExternalDataManager(userFileSystemNewItemPath); - - string eTagNew = ""; // WebDAV server sypically does not provide eTags for folders. - - // Store ETag unlil the next update. - await customDataManager.SetCustomDataAsync( - eTagNew, - false, - new[] { new FileSystemItemPropertyData((int)CustomColumnIds.ETag, eTagNew) }); + // WebDAV server sypically does not provide eTags for folders. + // Store ETag (if any) unlil the next update here. + //PlaceholderItem placeholder = Engine.Placeholders.GetItem(userFileSystemNewItemPath); + //await placeholder.Properties.AddOrUpdateAsync("ETag", eTag); + // WebDAV does not use any item IDs, returning null. return null; } @@ -100,25 +97,18 @@ public async Task GetChildrenAsync(string pattern, IOperationContext operationCo Logger.LogMessage("Creating", userFileSystemItemPath); userFileSystemChildren.Add(itemMetadata); } - - ExternalDataManager customDataManager = Engine.ExternalDataManager(userFileSystemItemPath); - - // Mark this item as not new, which is required for correct MS Office saving opertions. - customDataManager.IsNew = false; } // To signal that the children enumeration is completed // always call ReturnChildren(), even if the folder is empty. - resultContext.ReturnChildren(userFileSystemChildren.ToArray(), userFileSystemChildren.Count()); + await resultContext.ReturnChildrenAsync(userFileSystemChildren.ToArray(), userFileSystemChildren.Count()); - // Save ETags, the read-only attribute and all custom columns data. + // Save data that will be displayes in custom columns in file manager + // as well as any additional custom data required by the client. foreach (FileSystemItemMetadataExt itemMetadata in userFileSystemChildren) { string userFileSystemItemPath = Path.Combine(UserFileSystemPath, itemMetadata.Name); - ExternalDataManager customDataManager = Engine.ExternalDataManager(userFileSystemItemPath); - - // Save ETag on the client side, to be sent to the remote storage as part of the update. - await customDataManager.SetCustomDataAsync(itemMetadata.ETag, itemMetadata.IsLocked, itemMetadata.CustomProperties); + await Engine.Placeholders.GetItem(userFileSystemItemPath).SavePropertiesAsync(itemMetadata); } } @@ -141,7 +131,7 @@ public async Task> EnumerateChildrenAsync /// public async Task WriteAsync(IFolderMetadata folderMetadata, IOperationContext operationContext = null) { - // We can not change any folder metadata on a WebDAV server, so this method is empty. + // Typically we can not change any folder metadata on a WebDAV server, just logging the call. Logger.LogMessage($"{nameof(IFolder)}.{nameof(WriteAsync)}()", UserFileSystemPath, default, operationContext); } } diff --git a/Windows/WebDAVDrive/WebDAVDrive/log4net.config b/Windows/WebDAVDrive/WebDAVDrive/log4net.config index 69c0064..027860c 100644 --- a/Windows/WebDAVDrive/WebDAVDrive/log4net.config +++ b/Windows/WebDAVDrive/WebDAVDrive/log4net.config @@ -6,14 +6,12 @@ - -