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 @@
-
-