From 6d55a3fe9f662b1a753c3e331d5bdc2cc5a657d9 Mon Sep 17 00:00:00 2001 From: IT Hit Date: Fri, 23 Apr 2021 01:45:33 +0300 Subject: [PATCH] v2.5.5083.0 --- .../ClientLockFailedException.cs | 2 +- .../CustomData.cs | 7 +- .../ETagManager.cs | 140 ++++ .../FsPath.cs | 19 +- ...t.FileSystem.Samples.Common.Windows.csproj | 18 + .../Locks/LockManager.cs | 185 +++++ .../Locks/LockSync.cs | 185 +++++ .../Logger.cs | 2 +- .../Registrar.cs | 14 +- .../Syncronyzation/ClientToServerSync.cs | 24 +- .../Syncronyzation/FileAttributesExt.cs | 4 +- .../Syncronyzation/FullSyncService.cs | 62 +- .../Syncronyzation/IRemoteStorageRawItem.cs | 37 + .../Syncronyzation/RemoteStorageRawItem.cs | 663 ++++++++++++++++++ .../Syncronyzation/ServerToClientSync.cs | 54 +- .../Syncronyzation/UserFileSystemMonitor.cs | 52 +- .../Syncronyzation/UserFileSystemRawItem.cs | 179 +++-- .../VfsEngine.cs | 10 +- .../VfsFile.cs | 73 +- .../VfsFileSystemItem.cs | 85 ++- .../VfsFolder.cs | 39 +- .../VirtualDriveBase.cs | 228 ++++++ .../CustomColumnIds.cs | 38 + ITHit.FileSystem.Samples.Common/ETag.cs | 90 --- .../{FileBasicInfo.cs => FileMetadata.cs} | 4 +- ...BasicInfo.cs => FileSystemItemMetadata.cs} | 9 +- .../{FolderBasicInfo.cs => FolderMetadata.cs} | 4 +- .../IClientNotifications.cs | 39 ++ .../IServerNotifications.cs | 92 +++ .../ITHit.FileSystem.Samples.Common.csproj | 12 +- ITHit.FileSystem.Samples.Common/IUserFile.cs | 13 - .../IUserFileSystemItem.cs | 12 - .../IUserFolder.cs | 15 - .../IVirtualDrive.cs | 83 +++ .../IVirtualFile.cs | 39 ++ .../IVirtualFileSystemItem.cs | 21 + .../IVirtualFolder.cs | 50 ++ .../IVirtualLock.cs | 37 + .../{Locks => }/LockMode.cs | 4 +- ITHit.FileSystem.Samples.Common/Locks/Lock.cs | 294 -------- .../{Locks => }/ServerLockInfo.cs | 11 +- ITHit.FileSystem.Samples.Common/Settings.cs | 16 +- .../SynchronizationState.cs | 59 ++ .../Syncronyzation/RemoteStorageRawItem.cs | 472 ------------- .../VirtualDriveBase.cs | 146 ---- UserFileSystemSamples.sln | 2 + VirtualFileSystem/AppSettings.cs | 2 +- VirtualFileSystem/Mapping.cs | 14 +- VirtualFileSystem/Program.cs | 29 +- VirtualFileSystem/RemoteStorageMonitor.cs | 58 +- VirtualFileSystem/UserFile.cs | 65 -- VirtualFileSystem/VirtualDrive.cs | 71 +- VirtualFileSystem/VirtualFile.cs | 89 +++ VirtualFileSystem/VirtualFileSystem.csproj | 3 +- ...SystemItem.cs => VirtualFileSystemItem.cs} | 108 ++- .../{UserFolder.cs => VirtualFolder.cs} | 27 +- VirtualFileSystemMac/README.md | 12 +- WebDAVDrive.Setup/Product.wxs | 230 ++++++ WebDAVDrive.Setup/WebDAVDrive.Setup.wixproj | 55 ++ WebDAVDrive.Setup/WixUI_Install.wxs | 47 ++ WebDAVDrive.Setup/background.png | Bin 0 -> 16355 bytes WebDAVDrive.Setup/banner.png | Bin 0 -> 1625 bytes WebDAVDrive.UI/ConsoleManager.cs | 34 +- .../CredentialManager.cs | 0 WebDAVDrive.UI/RegistryManager.cs | 5 +- WebDAVDrive.UI/WebDAVDrive.UI.csproj | 4 +- WebDAVDrive.UI/WindowsTrayInterface.cs | 46 +- WebDAVDrive/AppSettings.cs | 2 +- WebDAVDrive/Mapping.cs | 19 +- WebDAVDrive/Program.cs | 30 +- WebDAVDrive/RemoteStorageMonitor.cs | 27 +- WebDAVDrive/UserFile.cs | 72 -- WebDAVDrive/VirtualDrive.cs | 71 +- WebDAVDrive/VirtualFile.cs | 89 +++ ...SystemItem.cs => VirtualFileSystemItem.cs} | 58 +- .../{UserFolder.cs => VirtualFolder.cs} | 39 +- WebDAVDrive/WebDAVDrive.csproj | 4 +- WebDAVDrive/appsettings.json | 2 + 78 files changed, 3231 insertions(+), 1725 deletions(-) rename {ITHit.FileSystem.Samples.Common => ITHit.FileSystem.Samples.Common.Windows}/ClientLockFailedException.cs (97%) rename {ITHit.FileSystem.Samples.Common => ITHit.FileSystem.Samples.Common.Windows}/CustomData.cs (95%) create mode 100644 ITHit.FileSystem.Samples.Common.Windows/ETagManager.cs rename {ITHit.FileSystem.Samples.Common => ITHit.FileSystem.Samples.Common.Windows}/FsPath.cs (93%) create mode 100644 ITHit.FileSystem.Samples.Common.Windows/ITHit.FileSystem.Samples.Common.Windows.csproj create mode 100644 ITHit.FileSystem.Samples.Common.Windows/Locks/LockManager.cs create mode 100644 ITHit.FileSystem.Samples.Common.Windows/Locks/LockSync.cs rename {ITHit.FileSystem.Samples.Common => ITHit.FileSystem.Samples.Common.Windows}/Logger.cs (97%) rename {ITHit.FileSystem.Samples.Common => ITHit.FileSystem.Samples.Common.Windows}/Registrar.cs (91%) rename {ITHit.FileSystem.Samples.Common => ITHit.FileSystem.Samples.Common.Windows}/Syncronyzation/ClientToServerSync.cs (84%) rename {ITHit.FileSystem.Samples.Common => ITHit.FileSystem.Samples.Common.Windows}/Syncronyzation/FileAttributesExt.cs (91%) rename {ITHit.FileSystem.Samples.Common => ITHit.FileSystem.Samples.Common.Windows}/Syncronyzation/FullSyncService.cs (79%) create mode 100644 ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/IRemoteStorageRawItem.cs create mode 100644 ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/RemoteStorageRawItem.cs rename {ITHit.FileSystem.Samples.Common => ITHit.FileSystem.Samples.Common.Windows}/Syncronyzation/ServerToClientSync.cs (69%) rename {ITHit.FileSystem.Samples.Common => ITHit.FileSystem.Samples.Common.Windows}/Syncronyzation/UserFileSystemMonitor.cs (83%) rename {ITHit.FileSystem.Samples.Common => ITHit.FileSystem.Samples.Common.Windows}/Syncronyzation/UserFileSystemRawItem.cs (72%) rename {ITHit.FileSystem.Samples.Common => ITHit.FileSystem.Samples.Common.Windows}/VfsEngine.cs (83%) rename {ITHit.FileSystem.Samples.Common => ITHit.FileSystem.Samples.Common.Windows}/VfsFile.cs (66%) rename {ITHit.FileSystem.Samples.Common => ITHit.FileSystem.Samples.Common.Windows}/VfsFileSystemItem.cs (53%) rename {ITHit.FileSystem.Samples.Common => ITHit.FileSystem.Samples.Common.Windows}/VfsFolder.cs (65%) create mode 100644 ITHit.FileSystem.Samples.Common.Windows/VirtualDriveBase.cs create mode 100644 ITHit.FileSystem.Samples.Common/CustomColumnIds.cs delete mode 100644 ITHit.FileSystem.Samples.Common/ETag.cs rename ITHit.FileSystem.Samples.Common/{FileBasicInfo.cs => FileMetadata.cs} (65%) rename ITHit.FileSystem.Samples.Common/{FileSystemItemBasicInfo.cs => FileSystemItemMetadata.cs} (75%) rename ITHit.FileSystem.Samples.Common/{FolderBasicInfo.cs => FolderMetadata.cs} (55%) create mode 100644 ITHit.FileSystem.Samples.Common/IClientNotifications.cs create mode 100644 ITHit.FileSystem.Samples.Common/IServerNotifications.cs delete mode 100644 ITHit.FileSystem.Samples.Common/IUserFile.cs delete mode 100644 ITHit.FileSystem.Samples.Common/IUserFileSystemItem.cs delete mode 100644 ITHit.FileSystem.Samples.Common/IUserFolder.cs create mode 100644 ITHit.FileSystem.Samples.Common/IVirtualDrive.cs create mode 100644 ITHit.FileSystem.Samples.Common/IVirtualFile.cs create mode 100644 ITHit.FileSystem.Samples.Common/IVirtualFileSystemItem.cs create mode 100644 ITHit.FileSystem.Samples.Common/IVirtualFolder.cs create mode 100644 ITHit.FileSystem.Samples.Common/IVirtualLock.cs rename ITHit.FileSystem.Samples.Common/{Locks => }/LockMode.cs (88%) delete mode 100644 ITHit.FileSystem.Samples.Common/Locks/Lock.cs rename ITHit.FileSystem.Samples.Common/{Locks => }/ServerLockInfo.cs (62%) create mode 100644 ITHit.FileSystem.Samples.Common/SynchronizationState.cs delete mode 100644 ITHit.FileSystem.Samples.Common/Syncronyzation/RemoteStorageRawItem.cs delete mode 100644 ITHit.FileSystem.Samples.Common/VirtualDriveBase.cs delete mode 100644 VirtualFileSystem/UserFile.cs create mode 100644 VirtualFileSystem/VirtualFile.cs rename VirtualFileSystem/{UserFileSystemItem.cs => VirtualFileSystemItem.cs} (72%) rename VirtualFileSystem/{UserFolder.cs => VirtualFolder.cs} (67%) create mode 100644 WebDAVDrive.Setup/Product.wxs create mode 100644 WebDAVDrive.Setup/WebDAVDrive.Setup.wixproj create mode 100644 WebDAVDrive.Setup/WixUI_Install.wxs create mode 100644 WebDAVDrive.Setup/background.png create mode 100644 WebDAVDrive.Setup/banner.png rename {WebDAVDrive => WebDAVDrive.UI}/CredentialManager.cs (100%) delete mode 100644 WebDAVDrive/UserFile.cs create mode 100644 WebDAVDrive/VirtualFile.cs rename WebDAVDrive/{UserFileSystemItem.cs => VirtualFileSystemItem.cs} (71%) rename WebDAVDrive/{UserFolder.cs => VirtualFolder.cs} (62%) diff --git a/ITHit.FileSystem.Samples.Common/ClientLockFailedException.cs b/ITHit.FileSystem.Samples.Common.Windows/ClientLockFailedException.cs similarity index 97% rename from ITHit.FileSystem.Samples.Common/ClientLockFailedException.cs rename to ITHit.FileSystem.Samples.Common.Windows/ClientLockFailedException.cs index e8f01e1..5dcf309 100644 --- a/ITHit.FileSystem.Samples.Common/ClientLockFailedException.cs +++ b/ITHit.FileSystem.Samples.Common.Windows/ClientLockFailedException.cs @@ -3,7 +3,7 @@ using System.IO; using System.Text; -namespace ITHit.FileSystem.Samples.Common +namespace ITHit.FileSystem.Samples.Common.Windows { /// /// Thrown when a file can not be locked. For example when a lock-token file is blocked diff --git a/ITHit.FileSystem.Samples.Common/CustomData.cs b/ITHit.FileSystem.Samples.Common.Windows/CustomData.cs similarity index 95% rename from ITHit.FileSystem.Samples.Common/CustomData.cs rename to ITHit.FileSystem.Samples.Common.Windows/CustomData.cs index 3b27f57..f65de74 100644 --- a/ITHit.FileSystem.Samples.Common/CustomData.cs +++ b/ITHit.FileSystem.Samples.Common.Windows/CustomData.cs @@ -6,7 +6,7 @@ using System.Text; using System.Threading.Tasks; -namespace ITHit.FileSystem.Samples.Common +namespace ITHit.FileSystem.Samples.Common.Windows { /// /// Custom data stored with a file or folder placeholder, such original file/folder path. Max 4KB. @@ -114,11 +114,12 @@ public static bool IsMoved(this PlaceholderItem placeholder) /// /// Returns true if the item was created and must be synched to remote storage. /// + /// Vitrual drive. /// /// True if the item was created in the user file system and does not exists /// in the remote storage. False otherwise. /// - public static bool IsNew(this PlaceholderItem placeholder) + public static bool IsNew(this PlaceholderItem placeholder, VirtualDriveBase virtualDrive) { // ETag absence signals that the item is new. // However, ETag file may not exists during move operation, @@ -128,7 +129,7 @@ public static bool IsNew(this PlaceholderItem placeholder) string originalPath = placeholder.GetOriginalPath(); - bool eTagFileExists = File.Exists(ETag.GetETagFilePath(placeholder.Path)); + bool eTagFileExists = File.Exists(virtualDrive.GetETagManager(placeholder.Path).ETagFilePath); return !eTagFileExists && string.IsNullOrEmpty(originalPath); } diff --git a/ITHit.FileSystem.Samples.Common.Windows/ETagManager.cs b/ITHit.FileSystem.Samples.Common.Windows/ETagManager.cs new file mode 100644 index 0000000..f191322 --- /dev/null +++ b/ITHit.FileSystem.Samples.Common.Windows/ETagManager.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; + +namespace ITHit.FileSystem.Samples.Common.Windows +{ + /// + /// Provides method for reading and writing ETags. + /// + public class ETagManager + { + private readonly string userFileSystemPath; + private readonly string userFileSystemRootPath; + private readonly string serverDataFolderPath; + private readonly ILogger logger; + internal readonly string ETagFilePath; + + private const string eTagExt = ".etag"; + + /// + /// Creates instance of this class. + /// + /// User file system root path. + /// Folder where ETags are stored. + /// Logger. + internal 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 or empty string value is passed. + if (string.IsNullOrEmpty(eTag) && 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); + } + + /// + /// 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(); + File.Move(ETagFilePath, eTagFileTargetPath); + + // 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() + { + 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(FileSystemItemMetadata 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/ITHit.FileSystem.Samples.Common/FsPath.cs b/ITHit.FileSystem.Samples.Common.Windows/FsPath.cs similarity index 93% rename from ITHit.FileSystem.Samples.Common/FsPath.cs rename to ITHit.FileSystem.Samples.Common.Windows/FsPath.cs index c8001e1..acc565b 100644 --- a/ITHit.FileSystem.Samples.Common/FsPath.cs +++ b/ITHit.FileSystem.Samples.Common.Windows/FsPath.cs @@ -6,7 +6,7 @@ using Windows.Storage; using FileAttributes = System.IO.FileAttributes; -namespace ITHit.FileSystem.Samples.Common +namespace ITHit.FileSystem.Samples.Common.Windows { /// /// Provides file system operations. Helps determining file and folder existence and creating file and folder items. @@ -47,6 +47,11 @@ public static bool Exists(string path) return File.Exists(path) || Directory.Exists(path); } + public static FileSystemItemTypeEnum GetItemType(string path) + { + return FsPath.IsFile(path) ? FileSystemItemTypeEnum.File : FileSystemItemTypeEnum.Folder; + } + /// /// Returns true if the path point to a recycle bin folder. /// @@ -124,7 +129,7 @@ private static bool IsMsOfficeTemp(string path) return (Path.GetFileName(path).StartsWith('~') && Path.GetExtension(path).Equals(".tmp", StringComparison.InvariantCultureIgnoreCase)) // Word temp files || (Path.GetFileName(path).StartsWith("ppt") && Path.GetExtension(path).Equals(".tmp", StringComparison.InvariantCultureIgnoreCase)) // PowerPoint temp files || (string.IsNullOrEmpty(Path.GetExtension(path)) && (Path.GetFileName(path).Length == 8) && File.Exists(path)) // Excel temp files - || ((Path.GetFileNameWithoutExtension(path).Length == 8) && Path.GetExtension(path).Equals(".tmp", StringComparison.InvariantCultureIgnoreCase)); // Excel temp files + || ( ((Path.GetFileNameWithoutExtension(path).Length == 8) || (Path.GetFileNameWithoutExtension(path).Length == 7)) && Path.GetExtension(path).Equals(".tmp", StringComparison.InvariantCultureIgnoreCase)); // Excel temp files } /// @@ -246,6 +251,16 @@ public static string Size(string path) return null; } + return FormatBytes(length); + } + + /// + /// Formats bytes to string. + /// + /// Bytes to format. + /// Human readable bytes string. + public static string FormatBytes(long length) + { string[] suf = { "b ", "KB", "MB", "GB", "TB", "PB", "EB" }; if (length == 0) { diff --git a/ITHit.FileSystem.Samples.Common.Windows/ITHit.FileSystem.Samples.Common.Windows.csproj b/ITHit.FileSystem.Samples.Common.Windows/ITHit.FileSystem.Samples.Common.Windows.csproj new file mode 100644 index 0000000..01c82e0 --- /dev/null +++ b/ITHit.FileSystem.Samples.Common.Windows/ITHit.FileSystem.Samples.Common.Windows.csproj @@ -0,0 +1,18 @@ + + + netstandard2.1 + Contains functionality common for all Windows Virtual Drive samples. + IT Hit LTD. + IT Hit User File System + IT Hit LTD. + + + + + + + + + + + \ No newline at end of file diff --git a/ITHit.FileSystem.Samples.Common.Windows/Locks/LockManager.cs b/ITHit.FileSystem.Samples.Common.Windows/Locks/LockManager.cs new file mode 100644 index 0000000..5a577a4 --- /dev/null +++ b/ITHit.FileSystem.Samples.Common.Windows/Locks/LockManager.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace ITHit.FileSystem.Samples.Common.Windows +{ + /// + /// Manages lock-sync, lock-info and lock mode files that correspond with the file in the user file system. + /// + /// + /// + /// When a file must be sent from user file system to remote storage, create a lock-sync using + /// method to prevent concurrent threads sending the file to + /// the remote storage, so only one thread can start the synchronization. + /// + /// + /// The lock info must be stored outside of CustomData, 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. + /// + /// + internal class LockManager + { + /// + /// Path in user file system with which this lock corresponds. + /// + private readonly string userFileSystemPath; + + /// + /// Path to the file that serves as lock-sync. + /// + private readonly string lockSyncFilePath; + + /// + /// 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; + + /// + /// Creates instance of this class. + /// + internal LockManager(string userFileSystemPath, string serverDataFolderPath, string userFileSystemRootPath, ILogger logger) + { + this.userFileSystemPath = userFileSystemPath; + this.logger = logger; + + // Get path relative to the virtual root. + string relativePath = userFileSystemPath.TrimEnd(Path.DirectorySeparatorChar).Substring( + userFileSystemRootPath.TrimEnd(Path.DirectorySeparatorChar).Length); + + string dataFile = $"{serverDataFolderPath.TrimEnd(Path.DirectorySeparatorChar)}{relativePath}"; + + lockSyncFilePath = $"{dataFile}.locksync"; + + lockModeFilePath = $"{dataFile}.lockmode"; + + lockInfoFilePath = $"{dataFile}.lockinfo"; + } + + /// + /// Gets existing file lock or creates a new lock. + /// + /// + /// Thrown when a file can not be locked. For example when a lock-token file is blocked + /// from another thread, during update, lock and unlock operations. + /// + /// Lock sync. + internal async Task LockAsync() + { + return new LockSync(lockSyncFilePath, userFileSystemPath, logger); + } + + /// + /// Sets lock mode associated with the file or folder. + /// + /// Lock mode. + internal 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. + internal async Task GetLockModeAsync() + { + if(!File.Exists(lockModeFilePath)) + { + return LockMode.None; + } + + await using (FileStream fileStream = File.Open(lockModeFilePath, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.Read)) + { + return (LockMode)fileStream.ReadByte(); + } + } + + /// + /// Returns true if the file or folder is locked. + /// + internal async Task IsLockedAsync() + { + return File.Exists(lockInfoFilePath); + } + + /// + /// Gets lock info. + /// + internal async Task GetLockInfoAsync() + { + if(!File.Exists(lockInfoFilePath)) + { + return null; + } + await using (FileStream lockTokenFileStream = File.Open(lockInfoFilePath, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.Read)) + { + lockTokenFileStream.Seek(0, SeekOrigin.Begin); + return await JsonSerializer.DeserializeAsync(lockTokenFileStream); + } + } + + /// + /// Sets lock info. + /// + /// Lock info. + internal async Task SetLockInfoAsync(ServerLockInfo lockInfo) + { + await using (FileStream lockTokenFileStream = File.Open(lockInfoFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Delete)) + { + lockTokenFileStream.Seek(0, SeekOrigin.Begin); + await JsonSerializer.SerializeAsync(lockTokenFileStream, lockInfo); + //lockTokenFileStream.SetLength(lockTokenFileStream.Position); + } + } + + /// + /// Deletes lock-token and lock-mode files. + /// + internal async Task DeleteLockAsync() + { + try + { + File.Delete(lockModeFilePath); + } + catch (Exception ex) + { + logger.LogError("Failed to delete lock-mode file.", userFileSystemPath, null, ex); + } + + try + { + File.Delete(lockInfoFilePath); + } + catch (Exception ex) + { + logger.LogError("Failed to delete lock-token file.", userFileSystemPath, null, ex); + } + } + + } +} diff --git a/ITHit.FileSystem.Samples.Common.Windows/Locks/LockSync.cs b/ITHit.FileSystem.Samples.Common.Windows/Locks/LockSync.cs new file mode 100644 index 0000000..5995339 --- /dev/null +++ b/ITHit.FileSystem.Samples.Common.Windows/Locks/LockSync.cs @@ -0,0 +1,185 @@ +using ITHit.FileSystem; +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Windows.System; + +namespace ITHit.FileSystem.Samples.Common.Windows +{ + /// + /// Protects file from concurrent updates during user file system to remote storage synchronization. + /// + internal class LockSync : IDisposable + { + /// + /// Path in user file system with which this lock corresponds. + /// + private readonly string userFileSystemPath; + + /// + /// Open lock-info file stream. + /// + private readonly FileStream lockTokenFileStream = null; + + /// + /// Logger. + /// + private readonly ILogger logger; + + /// + /// Creates instance of this class. + /// + /// Lock-sync file path. + internal LockSync(string lockSyncFilePath, string userFileSystemPath, ILogger logger) + { + this.logger = logger; + + // Create lock-token file or open existing lock-token file. + try + { + // Blocks the file for reading and writing, + // so other threads can not start file update, lock and unlock file until this lock request is completed + // and lock-toked is saved in a lock-token file. + // Lock-token and lock-mode files can still be updated independently, we do not want to block their reading/writing. + lockTokenFileStream = File.Open(lockSyncFilePath, FileMode.OpenOrCreate, FileAccess.Read, FileShare.Delete); + } + catch (IOException ex) + { + // Possibly blocked in another thread. This is a normal behaviour. + throw new ClientLockFailedException("Can't open or create lock-sync file.", ex); + } + + logger.LogMessage("Lock-sync created succesefully", userFileSystemPath); + } + + /* + /// + /// Gets existing file lock or creates a new lock. + /// + /// + /// Indicates if a new lock should be created or existing lock file to be opened. + /// Allowed options are , and . + /// + /// + /// Thrown when a file can not be locked. For example when a lock-token file is blocked + /// from another thread, during update, lock and unlock operations. + /// + /// File lock info. + internal static async Task CreateAsync(string lockFilePath, FileMode lockFileOpenMode, ILogger logger) + { + if ((lockFileOpenMode != FileMode.OpenOrCreate) + && (lockFileOpenMode != FileMode.Open) + && (lockFileOpenMode != FileMode.CreateNew)) + { + throw new ArgumentOutOfRangeException(nameof(lockFileOpenMode), $"Must be {FileMode.OpenOrCreate} or {FileMode.Open} or {FileMode.CreateNew}"); + } + + // Create lock-token file or open existing lock-token file. + FileStream lockTokenFileStream = null; + try + { + // Blocks the file for reading and writing, + // so other threads can not start file update, lock and unlock file until this lock request is completed + // and lock-toked is saved in a lock-token file. + // Lock-token and lock-mode files can still be updated independently, we do not want to block their reading/writing. + lockTokenFileStream = File.Open(lockFilePath, lockFileOpenMode, FileAccess.ReadWrite, FileShare.Delete); + } + catch (IOException ex) + { + // Possibly blocked in another thread. This is a normal behaviour. + throw new ClientLockFailedException("Can't open or create lock-sync file.", ex); + } + + return new LockSync(lockTokenFileStream, logger); + + LockSync lockInfo = null; + try + { + lockInfo = new LockSync(lockTokenFileStream, logger); + } + catch (Exception ex) + { + // Something went wrong, cleanup. + try + { + lockInfo.Dispose(); + File.Delete(lockFilePath); + } + catch { } + throw; + } + + return lockInfo; + + } + */ + /* + /// + /// Returns true if the lock was created and contains no data. + /// Returns false if the lock contains information about the lock. + /// + internal bool IsNew() + { + return lockTokenFileStream.Length == 0; + } + + internal async Task SetLockInfoAsync(ServerLockInfo lockInfo) + { + lockTokenFileStream.Seek(0, SeekOrigin.Begin); + await JsonSerializer.SerializeAsync(lockTokenFileStream, lockInfo); + lockTokenFileStream.SetLength(lockTokenFileStream.Position); + } + + internal async Task GetLockInfoAsync() + { + lockTokenFileStream.Seek(0, SeekOrigin.Begin); + return await JsonSerializer.DeserializeAsync(lockTokenFileStream); + } + */ + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects). + + // Just close the stream, do not delete lock-mode and lock-token files. + if (lockTokenFileStream != null) + { + lockTokenFileStream.Close(); + File.Delete(lockTokenFileStream.Name); + logger.LogMessage("Lock-sync deleted succesefully", userFileSystemPath); + } + } + + // 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. + // ~Lock2() + // { + // // 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/ITHit.FileSystem.Samples.Common/Logger.cs b/ITHit.FileSystem.Samples.Common.Windows/Logger.cs similarity index 97% rename from ITHit.FileSystem.Samples.Common/Logger.cs rename to ITHit.FileSystem.Samples.Common.Windows/Logger.cs index 286f214..c928af8 100644 --- a/ITHit.FileSystem.Samples.Common/Logger.cs +++ b/ITHit.FileSystem.Samples.Common.Windows/Logger.cs @@ -6,7 +6,7 @@ using ITHit.FileSystem; -namespace ITHit.FileSystem.Samples.Common +namespace ITHit.FileSystem.Samples.Common.Windows { /// /// Implements unified logging. diff --git a/ITHit.FileSystem.Samples.Common/Registrar.cs b/ITHit.FileSystem.Samples.Common.Windows/Registrar.cs similarity index 91% rename from ITHit.FileSystem.Samples.Common/Registrar.cs rename to ITHit.FileSystem.Samples.Common.Windows/Registrar.cs index c0c0511..07c0f72 100644 --- a/ITHit.FileSystem.Samples.Common/Registrar.cs +++ b/ITHit.FileSystem.Samples.Common.Windows/Registrar.cs @@ -8,7 +8,7 @@ using Windows.Storage; using Windows.Storage.Provider; -namespace ITHit.FileSystem.Samples.Common +namespace ITHit.FileSystem.Samples.Common.Windows { /// /// Registers and unregisters sync root. @@ -35,13 +35,13 @@ public static async Task RegisterAsync(string syncRootId, string path, string di storageInfo.Context = CryptographicBuffer.ConvertStringToBinary(path, BinaryStringEncoding.Utf8); storageInfo.HydrationPolicy = StorageProviderHydrationPolicy.Progressive; - storageInfo.HydrationPolicyModifier = StorageProviderHydrationPolicyModifier.AutoDehydrationAllowed | StorageProviderHydrationPolicyModifier.ValidationRequired; + storageInfo.HydrationPolicyModifier = StorageProviderHydrationPolicyModifier.AutoDehydrationAllowed; //| StorageProviderHydrationPolicyModifier.ValidationRequired; // To implement folders on-demand placeholders population set the // StorageProviderSyncRootInfo.PopulationPolicy to StorageProviderPopulationPolicy.Full. storageInfo.PopulationPolicy = StorageProviderPopulationPolicy.Full; // Set to Full to list folder content immediately on program start. - // The read-only attribute is used to indicate that the item is being locked by another user. Do not include it into InSyncPolicy. + // The read-only attribute is used to indicate that the item is being locked by another user. Do NOT include it into InSyncPolicy. storageInfo.InSyncPolicy = StorageProviderInSyncPolicy.FileCreationTime | StorageProviderInSyncPolicy.DirectoryCreationTime | StorageProviderInSyncPolicy.FileLastWriteTime | StorageProviderInSyncPolicy.DirectoryLastWriteTime | @@ -56,10 +56,10 @@ 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. var proDefinitions = storageInfo.StorageProviderItemPropertyDefinitions; - proDefinitions.Add(new StorageProviderItemPropertyDefinition { DisplayNameResource = "Lock Owner" , Id = 2 }); - proDefinitions.Add(new StorageProviderItemPropertyDefinition { DisplayNameResource = "Lock Scope" , Id = 3 }); - proDefinitions.Add(new StorageProviderItemPropertyDefinition { DisplayNameResource = "Lock Expires" , Id = 4 }); - proDefinitions.Add(new StorageProviderItemPropertyDefinition { DisplayNameResource = "ETag" , Id = 5 }); + proDefinitions.Add(new StorageProviderItemPropertyDefinition { DisplayNameResource = "Lock Owner" , Id = (int)CustomColumnIds.LockOwnerIcon }); + proDefinitions.Add(new StorageProviderItemPropertyDefinition { DisplayNameResource = "Lock Scope" , Id = (int)CustomColumnIds.LockScope }); + proDefinitions.Add(new StorageProviderItemPropertyDefinition { DisplayNameResource = "Lock Expires" , Id = (int)CustomColumnIds.LockExpirationDate }); + proDefinitions.Add(new StorageProviderItemPropertyDefinition { DisplayNameResource = "ETag" , Id = (int)CustomColumnIds.ETag }); ValidateStorageProviderSyncRootInfo(storageInfo); diff --git a/ITHit.FileSystem.Samples.Common/Syncronyzation/ClientToServerSync.cs b/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/ClientToServerSync.cs similarity index 84% rename from ITHit.FileSystem.Samples.Common/Syncronyzation/ClientToServerSync.cs rename to ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/ClientToServerSync.cs index acb5763..87a3469 100644 --- a/ITHit.FileSystem.Samples.Common/Syncronyzation/ClientToServerSync.cs +++ b/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/ClientToServerSync.cs @@ -12,7 +12,7 @@ using Windows.Storage; using Windows.Storage.Provider; -namespace ITHit.FileSystem.Samples.Common.Syncronyzation +namespace ITHit.FileSystem.Samples.Common.Windows.Syncronyzation { /// /// User File System to Remote Storage synchronization. @@ -76,23 +76,23 @@ internal async Task SyncronizeMovedAsync(string userFileSystemFolderPath) { // Process items moved in user file system. userFileSystemOldPath = userFileSystemItem.GetOriginalPath(); - await new RemoteStorageRawItem(userFileSystemOldPath, virtualDrive, this).MoveToAsync(userFileSystemPath); + FileSystemItemTypeEnum itemType = FsPath.GetItemType(userFileSystemPath); + await virtualDrive.GetRemoteStorageRawItem(userFileSystemOldPath, itemType, this).MoveToAsync(userFileSystemPath); } else { // Restore Original Path and 'locked' icon that are lost during MS Office transactional save. // We keep Original Path to process moved files when app was not running. userFileSystemOldPath = userFileSystemItem.GetOriginalPath(); - if (!userFileSystemItem.IsNew() && string.IsNullOrEmpty(userFileSystemOldPath)) + if (!userFileSystemItem.IsNew(virtualDrive) && string.IsNullOrEmpty(userFileSystemOldPath)) { // Restore Original Path. - LogMessage("Saving Original Path", userFileSystemItem.Path); + LogMessage("Setting Original Path", userFileSystemItem.Path); userFileSystemItem.SetOriginalPath(userFileSystemItem.Path); // Restore the 'locked' icon. - bool isLocked = await Lock.IsLockedAsync(userFileSystemPath); - ServerLockInfo existingLock = isLocked ? await (await Lock.LockAsync(userFileSystemPath, FileMode.Open, LockMode.None, this)).GetLockInfoAsync() : null; - await new UserFileSystemRawItem(userFileSystemPath).SetLockInfoAsync(existingLock); + ServerLockInfo existingLock = await virtualDrive.LockManager(userFileSystemPath, this).GetLockInfoAsync(); + await virtualDrive.GetUserFileSystemRawItem(userFileSystemPath, this).SetLockInfoAsync(existingLock); } } } @@ -146,15 +146,17 @@ internal async Task SyncronizeFolderAsync(string userFileSystemFolderPath) { if (!FsPath.AvoidSync(userFileSystemPath)) { - if (PlaceholderItem.GetItem(userFileSystemPath).IsNew()) + if (PlaceholderItem.GetItem(userFileSystemPath).IsNew(virtualDrive)) { // Create a file/folder in the remote storage. - await RemoteStorageRawItem.CreateAsync(userFileSystemPath, virtualDrive, this); + FileSystemItemTypeEnum itemType = FsPath.GetItemType(userFileSystemPath); + await virtualDrive.GetRemoteStorageRawItem(userFileSystemPath, itemType, this).CreateAsync(); } - else + else if (!PlaceholderItem.GetItem(userFileSystemPath).IsMoved()) { // Update file/folder in the remote storage. Unlock if auto-locked. - await new RemoteStorageRawItem(userFileSystemPath, virtualDrive, this).UpdateAsync(); + FileSystemItemTypeEnum itemType = FsPath.GetItemType(userFileSystemPath); + await virtualDrive.GetRemoteStorageRawItem(userFileSystemPath, itemType, this).UpdateAsync(); } } } diff --git a/ITHit.FileSystem.Samples.Common/Syncronyzation/FileAttributesExt.cs b/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/FileAttributesExt.cs similarity index 91% rename from ITHit.FileSystem.Samples.Common/Syncronyzation/FileAttributesExt.cs rename to ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/FileAttributesExt.cs index 9ad6afe..bd13f70 100644 --- a/ITHit.FileSystem.Samples.Common/Syncronyzation/FileAttributesExt.cs +++ b/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/FileAttributesExt.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace ITHit.FileSystem.Samples.Common.Syncronyzation +namespace ITHit.FileSystem.Samples.Common.Windows.Syncronyzation { /// @@ -23,7 +23,7 @@ namespace ITHit.FileSystem.Samples.Common.Syncronyzation /// 512 (0x200) FILE_ATTRIBUTE_SPARSE_FILE /// [Flags] - public enum FileAttributesExt + internal enum FileAttributesExt { Pinned = 0x00080000, Unpinned = 0x00100000, diff --git a/ITHit.FileSystem.Samples.Common/Syncronyzation/FullSyncService.cs b/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/FullSyncService.cs similarity index 79% rename from ITHit.FileSystem.Samples.Common/Syncronyzation/FullSyncService.cs rename to ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/FullSyncService.cs index 49996c7..45edfa5 100644 --- a/ITHit.FileSystem.Samples.Common/Syncronyzation/FullSyncService.cs +++ b/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/FullSyncService.cs @@ -12,7 +12,7 @@ using Windows.Storage; using Windows.Storage.Provider; -namespace ITHit.FileSystem.Samples.Common.Syncronyzation +namespace ITHit.FileSystem.Samples.Common.Windows.Syncronyzation { /// /// Performs a full synchronization between the user file system and the remote storage, recursively processing all folders. @@ -25,16 +25,17 @@ namespace ITHit.FileSystem.Samples.Common.Syncronyzation public class FullSyncService : Logger, IDisposable { /// - /// FullSyncService states. + /// Current synchronization state. /// - public enum SynchronizationState - { - Started, - Stopped, - Idle - } + public virtual SynchronizationState SyncState { get; private set; } = SynchronizationState.Disabled; + + /// + /// Event, fired when synchronization state changes. + /// + public event SyncronizationEvent SyncEvent; + /// - /// Virtual drive. + /// Virtual drive instance to which this synchronization service belongs. /// private VirtualDriveBase virtualDrive; @@ -43,20 +44,6 @@ public enum SynchronizationState /// private System.Timers.Timer timer = null; - - public class SynchEventArgs : EventArgs - { - public SynchronizationState state; - - public SynchEventArgs(SynchronizationState state) - { - this.state = state; - } - } - - public delegate void SyncEvent(object sender, SynchEventArgs synchEventArgs); - public event SyncEvent syncEvent; - /// /// User file system path. /// @@ -81,9 +68,15 @@ internal FullSyncService(double syncIntervalMs, string userFileSystemRootPath, V /// public async Task StartAsync() { + if (SyncState != SynchronizationState.Disabled) + { + return; + } + // Do not start next synchronyzation automatically, wait until previous synchronyzation completed. timer.AutoReset = false; timer.Start(); + InvokeSyncEvent(SynchronizationState.Enabled); LogMessage($"Started"); } @@ -92,15 +85,27 @@ public async Task StartAsync() /// public async Task StopAsync() { + if (SyncState == SynchronizationState.Disabled) + { + return; + } + timer.Stop(); + InvokeSyncEvent(SynchronizationState.Disabled); LogMessage($"Stopped"); } - private void InvokeSyncEvent(SynchronizationState state) + private void InvokeSyncEvent(SynchronizationState newState) { - if (syncEvent != null) + if ( (SyncState == SynchronizationState.Disabled) && (newState == SynchronizationState.Idle)) + { + return; + } + + SyncState = newState; + if (SyncEvent != null) { - syncEvent.Invoke(this, new SynchEventArgs(state)); + SyncEvent.Invoke(this, new SynchEventArgs(newState)); } } @@ -108,7 +113,7 @@ private async void Timer_ElapsedAsync(object sender, System.Timers.ElapsedEventA { try { - InvokeSyncEvent(SynchronizationState.Started); + InvokeSyncEvent(SynchronizationState.Synchronizing); // UFS -> RS. Recursivery synchronize all moved files and folders present in the user file system. Restore OriginalPath. await new ClientToServerSync(virtualDrive, Log).SyncronizeMovedAsync(userFileSystemRootPath); @@ -117,7 +122,8 @@ private async void Timer_ElapsedAsync(object sender, System.Timers.ElapsedEventA // UFS <- RS. Recursivery synchronize all updated/created/deleted file and folders present in the user file system. await new ServerToClientSync(virtualDrive, Log).SyncronizeFolderAsync(userFileSystemRootPath); - InvokeSyncEvent(SynchronizationState.Stopped); + + InvokeSyncEvent(SynchronizationState.Idle); } catch(Exception ex) { diff --git a/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/IRemoteStorageRawItem.cs b/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/IRemoteStorageRawItem.cs new file mode 100644 index 0000000..25eb6d4 --- /dev/null +++ b/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/IRemoteStorageRawItem.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace ITHit.FileSystem.Samples.Common.Windows +{ + /// + /// Provides methods for synching the user file system to the remote storage. + /// + internal interface IRemoteStorageRawItem : IClientNotifications + { + /// + /// Creates the file or folder in the remote storage. + /// + Task CreateAsync(); + + /// + /// Sends content to the remote storage if the item is modified. + /// If Auto-locking is enabled, automatically locks the file if not locked. + /// Unlocks the file after the update if auto-locked. + /// + Task UpdateAsync(); + + /// + /// Moves the item in the remote storage. + /// Calls method and than . + /// + /// Target path in the user file system. + Task MoveToAsync(string userFileSystemNewPath); + + /// + /// Deletes file or folder in the remote storage. + /// + Task DeleteAsync(); + } +} diff --git a/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/RemoteStorageRawItem.cs b/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/RemoteStorageRawItem.cs new file mode 100644 index 0000000..10f65d3 --- /dev/null +++ b/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/RemoteStorageRawItem.cs @@ -0,0 +1,663 @@ +using ITHit.FileSystem; +using ITHit.FileSystem.Windows; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Windows.ApplicationModel.Activation; + +namespace ITHit.FileSystem.Samples.Common.Windows.Syncronyzation +{ + /// + /// Provides methods for synching the user file system to the remote storage. + /// Creates, updates, deletes, moves, locks and unloacks files and folders based on the info from user file system. + /// This class also sets status icons in file manager. + /// + /// In most cases you can use this class in your project without any changes. + public class RemoteStorageRawItem : IRemoteStorageRawItem where TItemType : IVirtualFileSystemItem + { + /// + /// Path to the file or folder in the user file system. + /// + private readonly string userFileSystemPath; + + /// + /// Virtual drive. + /// + private readonly VirtualDriveBase virtualDrive; + + /// + /// Logger. + /// + private readonly ILogger logger; + + /// + /// Manages loc-sync, lock-info and lock-mode files. + /// + private readonly LockManager lockManager; + + /// + /// File system item that corresponds to . + /// + private TItemType item; + + private readonly UserFileSystemRawItem userFileSystemRawItem; + + /// + /// Creates instance of this class. + /// + /// File or folder path in the user file system. + /// Logger. + internal RemoteStorageRawItem(string userFileSystemPath, VirtualDriveBase virtualDrive, ILogger logger) + { + if (string.IsNullOrEmpty(userFileSystemPath)) + { + throw new ArgumentNullException(nameof(userFileSystemPath)); + } + if (virtualDrive == null) + { + throw new ArgumentNullException(nameof(virtualDrive)); + } + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } + + this.userFileSystemPath = userFileSystemPath; + this.virtualDrive = virtualDrive; + this.logger = logger; + this.lockManager = virtualDrive.LockManager(userFileSystemPath, logger); + this.userFileSystemRawItem = virtualDrive.GetUserFileSystemRawItem(userFileSystemPath, logger); + } + + /* + /// + /// Creates a new file or folder in the remote storage. + /// + /// Path to the file or folder in the user file system to be created in the remote storage. + /// Logger + internal static async Task CreateAsync(string userFileSystemNewItemPath, VirtualDriveBase userEngine, ILogger logger) where IItemTypeCreate : IUserFileSystemItem + { + await new RemoteStorageRawItem(userFileSystemNewItemPath, userEngine, logger).CreateAsync(); + } + */ + + /// + /// This method reduces the number of + /// calls. + /// + /// Path of the item in the user file system. + /// File or folder that corresponds to path. + private async Task GetItemAsync(string userFileSystemPath) + { + if (item == null) + { + item = await virtualDrive.GetItemAsync(userFileSystemPath, logger); + if (item == null) + { + throw new FileNotFoundException(userFileSystemPath); + } + } + return item; + } + + /// + /// Returns true if the item implements , false - otherwise. + /// + internal async Task IsLockSupportedAsync() + { + return await GetItemAsync(userFileSystemPath) is IVirtualLock; + } + + /// + public async Task CreateAsync() + { + try + { + logger.LogMessage("Creating item in the remote storage", userFileSystemPath); + await CreateOrUpdateAsync(FileMode.CreateNew); + await userFileSystemRawItem.ClearStateAsync(); + logger.LogMessage("Created in the remote storage succesefully", userFileSystemPath); + } + catch (Exception ex) + { + await userFileSystemRawItem.SetUploadErrorStateAsync(ex); + + // Rethrow the exception preserving stack trace of the original exception. + System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw(); + } + } + + /// + public async Task UpdateAsync() + { + // This protects from ocationally calling update on moved items. + if (PlaceholderItem.GetItem(userFileSystemPath).IsMoved()) + { + string originalPath = PlaceholderItem.GetItem(userFileSystemPath).GetOriginalPath(); + throw new ConflictException(Modified.Client, $"The item was moved. Original path: {originalPath}"); + } + + LockSync lockSync = null; + try + { + if (!PlaceholderItem.GetItem(userFileSystemPath).GetInSync()) + { + // This will make sure only one thread can do the lock-update-unlock sequence. + lockSync = await lockManager.LockAsync(); + + IVirtualFileSystemItem userFileSystemItem = await GetItemAsync(userFileSystemPath); + + ServerLockInfo lockInfo = null; + if (userFileSystemItem is IVirtualLock) + { + // Get lock info in case of manual lock or failed lock/update/unlock + if (await lockManager.IsLockedAsync()) + { + lockInfo = await lockManager.GetLockInfoAsync(); + } + + // Lock file if auto-locking is enabled. + else if (virtualDrive.Settings.AutoLock) + { + // Lock file in remote storage. + lockInfo = await LockAsync(lockSync, LockMode.Auto); + } + } + + // Update item in remote storage. + logger.LogMessage("Sending to remote storage", userFileSystemPath); + await CreateOrUpdateAsync(FileMode.Open, lockInfo); + //await new UserFileSystemRawItem(userFileSystemPath).ClearStateAsync(); + logger.LogMessage("Sent to remote storage succesefully", userFileSystemPath); + } + + // Unlock if auto-locked. + if (await lockManager.GetLockModeAsync() == LockMode.Auto) + { + // Required in case the file failed to unlock during previous update. + lockSync ??= await lockManager.LockAsync(); + + // Send unlock request to remote storage. + await UnlockAsync(lockSync); + } + } + catch (ClientLockFailedException ex) + { + // Failed to lock file. Possibly blocked from another thread. This is a normal behaviour. + logger.LogMessage(ex.Message, userFileSystemPath); + } + catch (Exception ex) + { + // Some error when locking, updating or unlocking occured. + await userFileSystemRawItem.SetUploadErrorStateAsync(ex); + + // Rethrow the exception preserving stack trace of the original exception. + System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw(); + } + finally + { + if (lockSync != null) + { + lockSync.Dispose(); + } + // Do not delete lock-token and lock-mode files here, it is required if IUserLock + // interfce is implemented and the file was locked manually. + } + } + + /// + /// Creates or updates the item in the remote storage. + /// + /// + /// Indicates if the file should created or updated. + /// Supported modes are and + /// + /// + /// Information about the lock. Pass null if the item is not locked. + /// + private async Task CreateOrUpdateAsync(FileMode mode, ServerLockInfo lockInfo = null) + { + if ((mode != FileMode.CreateNew) && (mode != FileMode.Open)) + { + throw new ArgumentOutOfRangeException("mode", $"Must be {FileMode.CreateNew} or {FileMode.Open}"); + } + + FileSystemInfo userFileSystemItem = FsPath.GetFileSystemItem(userFileSystemPath); + + using (WindowsFileSystemItem userFileSystemWinItem = WindowsFileSystemItem.OpenReadAttributes(userFileSystemPath, FileMode.Open, FileShare.Read)) + //await using (FileStream userFileSystemStream = userFileSystemFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) + { + // Create the new file/folder in the remote storage only if the file/folder in the user file system was not moved. + // If the file is moved in user file system, move must first be syched to remote storage. + if ((mode == FileMode.CreateNew) && PlaceholderItem.GetItem(userFileSystemPath).IsMoved()) + { + string originalPath = PlaceholderItem.GetItem(userFileSystemPath).GetOriginalPath(); + throw new ConflictException(Modified.Client, $"The item was moved. Original path: {originalPath}"); + } + + // Ensures LastWriteTimeUtc is in sync with file content after Open() was called. + userFileSystemItem.Refresh(); + + IFileSystemItemMetadata info = GetMetadata(userFileSystemItem); + + // Update remote storage file. + FileStream userFileSystemStream = null; + try + { + string eTag = null; + if (FsPath.IsFile(userFileSystemPath)) + { + // File is marked as not in-sync when updated OR moved. + // Opening a file for reading triggers hydration, make sure to open only if content is modified. + if (PlaceholderFile.GetFileDataSizeInfo(userFileSystemWinItem.SafeHandle).ModifiedDataSize > 0) + { + //userFileSystemStream = new FileStream(userFileSystemWinItem.SafeHandle, FileAccess.Read); + userFileSystemStream = ((FileInfo)userFileSystemItem).Open(FileMode.Open, FileAccess.Read, FileShare.Read); + } + if (mode == FileMode.CreateNew) + { + string userFileSystemParentPath = Path.GetDirectoryName(userFileSystemPath); + IVirtualFolder userFolder = await virtualDrive.GetItemAsync(userFileSystemParentPath, logger); + eTag = await userFolder.CreateFileAsync((IFileMetadata)info, userFileSystemStream); + } + else + { + IVirtualFile userFile = await GetItemAsync(userFileSystemPath) as IVirtualFile; + eTag = await virtualDrive.GetETagManager(userFileSystemPath).GetETagAsync(); + eTag = await userFile.UpdateAsync((IFileMetadata)info, userFileSystemStream, eTag, lockInfo); + } + } + else + { + if (mode == FileMode.CreateNew) + { + string userFileSystemParentPath = Path.GetDirectoryName(userFileSystemPath); + IVirtualFolder userFolder = await virtualDrive.GetItemAsync(userFileSystemParentPath, logger); + eTag = await userFolder.CreateFolderAsync((IFolderMetadata)info); + } + else + { + IVirtualFolder userFolder = await GetItemAsync(userFileSystemPath) as IVirtualFolder; + eTag = await virtualDrive.GetETagManager(userFileSystemPath).GetETagAsync(); + eTag = await userFolder.UpdateAsync((IFolderMetadata)info, eTag, lockInfo); + } + } + await virtualDrive.GetETagManager(userFileSystemPath).SetETagAsync(eTag); + if (mode == FileMode.CreateNew) + { + PlaceholderItem.GetItem(userFileSystemPath).SetOriginalPath(userFileSystemPath); + } + } + finally + { + if (userFileSystemStream != null) + { + userFileSystemStream.Close(); + } + } + + PlaceholderItem.SetInSync(userFileSystemWinItem.SafeHandle, true); + } + } + + /// + /// Gets file system item info from the user file system item. + /// Removes Pinned, Unpinned and Offline flags from attributes. + /// + /// User file system item info. + /// Object implementing interface. + private static IFileSystemItemMetadata GetMetadata(FileSystemInfo userFileSystemItem) + { + FileSystemItemMetadata itemInfo; + + if (userFileSystemItem is FileInfo) + { + itemInfo = new FileMetadata(); + } + else + { + itemInfo = new FolderMetadata(); + } + + // Remove Pinned, unpinned and offline flags, + // so they are not sent to the remote storage by mistake. + FileAttributes flags = userFileSystemItem.Attributes + & (FileAttributes)~FileAttributesExt.Pinned + & (FileAttributes)~FileAttributesExt.Unpinned + & (FileAttributes)~FileAttributesExt.Offline; + + itemInfo.Name = userFileSystemItem.Name; + itemInfo.Attributes = flags; + itemInfo.CreationTime = userFileSystemItem.CreationTime; + itemInfo.LastWriteTime = userFileSystemItem.LastWriteTime; + itemInfo.LastAccessTime = userFileSystemItem.LastAccessTime; + itemInfo.ChangeTime = userFileSystemItem.LastWriteTime; + + itemInfo.CustomData = PlaceholderItem.GetItem(userFileSystemItem.FullName).GetCustomData(); + + if (userFileSystemItem is FileInfo) + { + ((FileMetadata)itemInfo).Length = ((FileInfo)userFileSystemItem).Length; + }; + + return itemInfo; + } + + /// + public async Task MoveToAsync(string userFileSystemNewPath) + { + try + { + await MoveToAsync(userFileSystemNewPath, null); + } + finally + { + await new RemoteStorageRawItem(userFileSystemNewPath, virtualDrive, logger).MoveToCompletionAsync(); + } + } + + /// + /// Moves the item in the remote storage. This method is called by the platform. + /// To move item manually use the method instead. + /// + /// Target path in user file system. + /// Confirms move competeion. Passed by the platform only. + internal async Task MoveToAsync(string userFileSystemNewPath, IConfirmationResultContext resultContext) + { + string userFileSystemOldPath = userFileSystemPath; + + try + { + //bool? inSync = null; + //bool updateTargetOnSuccess = false; + //string eTag = null; + try + { + if (!FsPath.IsRecycleBin(userFileSystemNewPath) // When a file is deleted, it is moved to a Recycle Bin. + && !FsPath.AvoidSync(userFileSystemOldPath) && !FsPath.AvoidSync(userFileSystemNewPath)) + { + logger.LogMessage("Moving item in remote storage", userFileSystemOldPath, userFileSystemNewPath); + /* + // Read In-Sync state before move and set after move. + if (FsPath.Exists(userFileSystemOldPath)) + { + inSync = PlaceholderItem.GetItem(userFileSystemOldPath).GetInSync(); + } + */ + + IVirtualFileSystemItem userFileSystemItemOld = await GetItemAsync(userFileSystemOldPath); + await userFileSystemItemOld.MoveToAsync(userFileSystemNewPath); + //updateTargetOnSuccess = true; + logger.LogMessage("Moved succesefully in remote storage", userFileSystemOldPath, userFileSystemNewPath); + + ETagManager eTagManager = virtualDrive.GetETagManager(userFileSystemOldPath); + if (FsPath.Exists(eTagManager.ETagFilePath)) + { + await eTagManager.MoveToAsync(userFileSystemNewPath); + logger.LogMessage("Moved ETag succesefully", userFileSystemOldPath, userFileSystemNewPath); + } + } + } + finally + { + if (resultContext != null) + { + // Calling ReturnConfirmationResult() moves file in the user file system. + logger.LogMessage("Confirming move in user file system", userFileSystemOldPath, userFileSystemNewPath); + resultContext.ReturnConfirmationResult(); + // After this the MoveToCompletionAsync() method is called. + // After that, in case of a file, the file handle is closed, triggering IFile.CloseAsync() call + // and Windows File Manager move progress window is closed. + // In case the target is the offline folder, the IFolder.GetChildrenAsync() method is called. + } + } + } + catch (Exception ex) + { + string userFileSystemExPath = FsPath.Exists(userFileSystemNewPath) ? userFileSystemNewPath : userFileSystemOldPath; + await virtualDrive.GetUserFileSystemRawItem(userFileSystemExPath, logger).SetUploadErrorStateAsync(ex); + + // Rethrow the exception preserving stack trace of the original exception. + System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw(); + } + } + + /// + /// Completes move. This method is called by the platform. + /// To move item manually use the method instead. + /// Sets In-Sync state based on file content changes, + /// Updates OriginalPath so the file does not appear as moved. + /// + /// + internal async Task MoveToCompletionAsync() + { + string userFileSystemNewPath = userFileSystemPath; + + if (FsPath.Exists(userFileSystemNewPath) // This check is just to avoid extra error in the log. + && !FsPath.IsRecycleBin(userFileSystemNewPath) // When a file with content is deleted, it is moved to a Recycle Bin. + && !FsPath.AvoidAutoLock(userFileSystemNewPath))// No need to update temp MS Office docs. + { + // Open file to prevent reads and changes between GetFileDataSizeInfo() call and SetInSync() call. + //using (WindowsFileSystemItem userFileSystemWinItem = WindowsFileSystemItem.OpenReadAttributes(userFileSystemNewPath, FileMode.Open, FileShare.None)) + { + // If a file with content is deleted it is moved to a recycle bin and converted + // to a regular file, so placeholder features are not available on it, checking if a file is a placeholder. + if (/*updateTargetOnSuccess &&*/ PlaceholderItem.IsPlaceholder(userFileSystemNewPath)) + { + PlaceholderItem placeholderNew = PlaceholderItem.GetItem(userFileSystemNewPath); + // Restore In-sync state. + /* + if (inSync != null) + { + placeholderNew.SetInSync(inSync.Value); + } + else + */ + if (((placeholderNew is PlaceholderFile) && ((PlaceholderFile)placeholderNew).GetFileDataSizeInfo().ModifiedDataSize == 0) + || (placeholderNew is PlaceholderFolder)) + { + logger.LogMessage("Setting In-Sync state", userFileSystemNewPath); + placeholderNew.SetInSync(true); + } + } + + } + + // Recursively restore OriginalPath and the 'locked' icon. + await MoveToCompletionRecursiveAsync(userFileSystemNewPath); + } + } + + /// + /// Recursively restores OriginalPath and the 'locked' icon. + /// + /// Path in the user file system to start recursive processing. + private async Task MoveToCompletionRecursiveAsync(string userFileSystemNewPath) + { + if (FsPath.IsFolder(userFileSystemNewPath)) + { + if (!new DirectoryInfo(userFileSystemNewPath).Attributes.HasFlag(System.IO.FileAttributes.Offline)) + { + //LogMessage("Folder offline, skipping:", userFileSystemFolderPath); + + IEnumerable userFileSystemChildren = Directory.EnumerateFileSystemEntries(userFileSystemNewPath, "*"); + + foreach (string userFileSystemChildPath in userFileSystemChildren) + { + try + { + await MoveToCompletionRecursiveAsync(userFileSystemChildPath); + } + catch (Exception ex) + { + logger.LogError("Failed to complete move", userFileSystemChildPath, null, ex); + } + } + } + } + + if (FsPath.Exists(userFileSystemNewPath) // This check is just to avoid extra error in the log. + && !FsPath.IsRecycleBin(userFileSystemNewPath) // When a file with content is deleted, it is moved to a Recycle Bin. + && !FsPath.AvoidAutoLock(userFileSystemNewPath))// No need to update temp MS Office docs. + { + // Open file to prevent reads and changes between GetFileDataSizeInfo() call and SetInSync() call. + using (WindowsFileSystemItem userFileSystemWinItem = WindowsFileSystemItem.OpenReadAttributes(userFileSystemNewPath, FileMode.Open, FileShare.None)) + { + // If a file with content is deleted it is moved to a recycle bin and converted + // to a regular file, so placeholder features are not available on it, checking if a file is a placeholder. + if (/*updateTargetOnSuccess &&*/ PlaceholderItem.IsPlaceholder(userFileSystemNewPath)) + { + PlaceholderItem placeholderNew = PlaceholderItem.GetItem(userFileSystemNewPath); + + await virtualDrive.GetUserFileSystemRawItem(userFileSystemNewPath, logger).ClearStateAsync(); + + // Update OriginalPath if the item is not new. + // Required for pptx and xlsx files Save As operation. + if (!placeholderNew.IsNew(virtualDrive)) + { + // Update OriginalPath, so the item does not appear as moved. + logger.LogMessage("Setting Original Path", userFileSystemNewPath); + placeholderNew.SetOriginalPath(userFileSystemNewPath); + } + + // Restore the 'locked' icon. + ServerLockInfo existingLock = await virtualDrive.LockManager(userFileSystemNewPath, logger).GetLockInfoAsync(); + await virtualDrive.GetUserFileSystemRawItem(userFileSystemNewPath, logger).SetLockInfoAsync(existingLock); + } + } + } + } + + /// + public async Task DeleteAsync() + { + if (!FsPath.AvoidSync(userFileSystemPath)) + { + IVirtualFileSystemItem userFileSystemItem = await GetItemAsync(userFileSystemPath); + await userFileSystemItem.DeleteAsync(); + + virtualDrive.GetETagManager(userFileSystemPath).DeleteETag(); + + return true; + } + + return false; + } + + /// + /// Locks the item in the remote storage. The item must implement interface. + /// + /// Indicates automatic or manual lock. + /// + /// Thrown when a file can not be locked. For example when a lock-token file is blocked + /// from another thread, during update, lock and unlock operations. + /// + public async Task LockAsync(LockMode lockMode = LockMode.Manual) + { + // Verify that the item supports locking. + if(!(await GetItemAsync(userFileSystemPath) is IVirtualLock)) + { + throw new NotSupportedException(nameof(IVirtualLock)); + } + + using (LockSync lockSync = await lockManager.LockAsync()) + { + await LockAsync(lockSync, lockMode); + } + } + + /// + /// Locks the item in the remote storage. The item must implement interface. + /// + /// Sync lock. + /// Indicates automatic or manual lock. + private async Task LockAsync(LockSync lockSync, LockMode lockMode) + { + ServerLockInfo lockInfo = null; + try + { + logger.LogMessage("Locking in remote storage", userFileSystemPath); + + // Set lock-pending icon. + await userFileSystemRawItem.SetLockPendingIconAsync(true); + + // Lock file in remote storage. + IVirtualLock userLock = await GetItemAsync(userFileSystemPath) as IVirtualLock; + lockInfo = await userLock.LockAsync(); + + // Save lock-token and lock-mode. + await lockManager.SetLockInfoAsync(lockInfo); + await lockManager.SetLockModeAsync(lockMode); + + logger.LogMessage("Locked in remote storage succesefully.", userFileSystemPath); + } + catch (Exception ex) + { + logger.LogError("Locking in remote storage failed.", userFileSystemPath, null, ex); + + // Delete lock-token and lock-mode files. + await lockManager.DeleteLockAsync(); + + // Clear lock icon. + await userFileSystemRawItem.SetLockInfoAsync(null); + + // Rethrow the exception preserving stack trace of the original exception. + System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw(); + } + + // Set locked icon and all lock properties. + await userFileSystemRawItem.SetLockInfoAsync(lockInfo); + return lockInfo; + } + + /// + /// Unlocks the item in the remote storage. The item must implement interface. + /// + /// + /// Thrown when a file can not be locked. For example when a lock-token file is blocked + /// from another thread, during update, lock and unlock operations. + /// + public async Task UnlockAsync() + { + // Verify that the item supports locking. + if (!(await GetItemAsync(userFileSystemPath) is IVirtualLock)) + { + throw new NotSupportedException(nameof(IVirtualLock)); + } + + using (LockSync lockSync = await lockManager.LockAsync()) + { + await UnlockAsync(lockSync); + } + } + + /// + /// Unlocks the file in the remote storage using existing . + /// + /// Sync lock. + private async Task UnlockAsync(LockSync lockSync) + { + logger.LogMessage("Unlocking in remote storage", userFileSystemPath); + + // Set pending icon. + await userFileSystemRawItem.SetLockPendingIconAsync(true); + + // Read lock-token from lock-info file. + string lockToken = (await lockManager.GetLockInfoAsync()).LockToken; + + // Unlock file in remote storage. + IVirtualLock userLock = await GetItemAsync(userFileSystemPath) as IVirtualLock; + await userLock.UnlockAsync(lockToken); + + // Delete lock-mode and lock-token files. + await lockManager.DeleteLockAsync(); + + // Remove lock icon. + await userFileSystemRawItem.SetLockInfoAsync(null); + + logger.LogMessage("Unlocked in remote storage succesefully", userFileSystemPath); + } + } +} \ No newline at end of file diff --git a/ITHit.FileSystem.Samples.Common/Syncronyzation/ServerToClientSync.cs b/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/ServerToClientSync.cs similarity index 69% rename from ITHit.FileSystem.Samples.Common/Syncronyzation/ServerToClientSync.cs rename to ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/ServerToClientSync.cs index 30c96e2..eee0341 100644 --- a/ITHit.FileSystem.Samples.Common/Syncronyzation/ServerToClientSync.cs +++ b/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/ServerToClientSync.cs @@ -12,7 +12,7 @@ using Windows.Storage; using Windows.Storage.Provider; -namespace ITHit.FileSystem.Samples.Common.Syncronyzation +namespace ITHit.FileSystem.Samples.Common.Windows.Syncronyzation { /// /// Synchronizes files and folders from remote storage to user file system. @@ -54,11 +54,11 @@ internal async Task SyncronizeFolderAsync(string userFileSystemFolderPath) IEnumerable userFileSystemChildren = Directory.EnumerateFileSystemEntries(userFileSystemFolderPath, "*"); //LogMessage("Synchronizing:", userFileSystemFolderPath); - IUserFolder userFolder = await virtualDrive.GetItemAsync(userFileSystemFolderPath); - IEnumerable remoteStorageChildrenItems = await userFolder.EnumerateChildrenAsync("*"); + IVirtualFolder userFolder = await virtualDrive.GetItemAsync(userFileSystemFolderPath, this); + IEnumerable remoteStorageChildrenItems = await userFolder.EnumerateChildrenAsync("*"); // Create new files/folders in the user file system. - foreach (FileSystemItemBasicInfo remoteStorageItem in remoteStorageChildrenItems) + foreach (FileSystemItemMetadata remoteStorageItem in remoteStorageChildrenItems) { string userFileSystemPath = Path.Combine(userFileSystemFolderPath, remoteStorageItem.Name); try @@ -71,14 +71,7 @@ internal async Task SyncronizeFolderAsync(string userFileSystemFolderPath) { LogMessage($"Creating", userFileSystemPath); - // If the file is moved/renamed and the app is not running this will help us - // to sync the file/folder to remote storage after app starts. - remoteStorageItem.CustomData = new CustomData - { - OriginalPath = userFileSystemPath - }.Serialize(); - - await UserFileSystemRawItem.CreateAsync(userFileSystemFolderPath, new[] { remoteStorageItem }); + await virtualDrive.GetUserFileSystemRawItem(userFileSystemFolderPath, this).CreateAsync(new[] { remoteStorageItem }); LogMessage($"Created succesefully", userFileSystemPath); } } @@ -96,47 +89,58 @@ internal async Task SyncronizeFolderAsync(string userFileSystemFolderPath) try { string itemName = Path.GetFileName(userFileSystemPath); - FileSystemItemBasicInfo remoteStorageItem = remoteStorageChildrenItems.FirstOrDefault(x => x.Name.Equals(itemName, StringComparison.InvariantCultureIgnoreCase)); + FileSystemItemMetadata remoteStorageItem = remoteStorageChildrenItems.FirstOrDefault(x => x.Name.Equals(itemName, StringComparison.InvariantCultureIgnoreCase)); if (!FsPath.AvoidSync(userFileSystemPath)) { + UserFileSystemRawItem userFileSystemRawItem = virtualDrive.GetUserFileSystemRawItem(userFileSystemPath, this); if (remoteStorageItem == null) { - if (PlaceholderItem.GetItem(userFileSystemPath).GetInSync()) + if (PlaceholderItem.GetItem(userFileSystemPath).GetInSync() + //&& !PlaceholderItem.GetItem(userFileSystemPath).IsNew(virtualDrive) // Extra check to protect from deletion files that were deleted becuse of incorrect folder merging operation. + ) { // Delete the file/folder in user file system. LogMessage("Deleting item", userFileSystemPath); - await new UserFileSystemRawItem(userFileSystemPath).DeleteAsync(); + await userFileSystemRawItem.DeleteAsync(); LogMessage("Deleted succesefully", userFileSystemPath); } } else { if (PlaceholderItem.GetItem(userFileSystemPath).GetInSync() - && !await ETag.ETagEqualsAsync(userFileSystemPath, remoteStorageItem)) + && !await virtualDrive.GetETagManager(userFileSystemPath).ETagEqualsAsync(remoteStorageItem)) { // User file system <- remote storage update. LogMessage("Remote item modified", userFileSystemPath); - await new UserFileSystemRawItem(userFileSystemPath).UpdateAsync(remoteStorageItem); + await userFileSystemRawItem.UpdateAsync(remoteStorageItem); LogMessage("Updated succesefully", userFileSystemPath); } - // Set the "locked by another user" icon and all custom columns data. - if(PlaceholderItem.GetItem(userFileSystemPath).GetInSync()) + // Set the read-only attribute and all custom columns data. + if (PlaceholderItem.GetItem(userFileSystemPath).GetInSync()) { - await new UserFileSystemRawItem(userFileSystemPath).SetLockedByAnotherUserAsync(remoteStorageItem.LockedByAnotherUser); - await new UserFileSystemRawItem(userFileSystemPath).SetCustomColumnsDataAsync(remoteStorageItem.CustomProperties); + await userFileSystemRawItem.SetLockedByAnotherUserAsync(remoteStorageItem.LockedByAnotherUser); + await userFileSystemRawItem.SetCustomColumnsDataAsync(remoteStorageItem.CustomProperties); } // Hydrate / dehydrate the file. - if (new UserFileSystemRawItem(userFileSystemPath).HydrationRequired()) + if (userFileSystemRawItem.HydrationRequired()) { LogMessage("Hydrating", userFileSystemPath); - new PlaceholderFile(userFileSystemPath).Hydrate(0, -1); - LogMessage("Hydrated succesefully", userFileSystemPath); + try + { + new PlaceholderFile(userFileSystemPath).Hydrate(0, -1); + LogMessage("Hydrated succesefully", userFileSystemPath); + } + catch(FileLoadException) + { + // Typically this happens if another thread already hydrating the file. + LogMessage("Failed to hydrate. The file is blocked.", userFileSystemPath); + } } - else if (new UserFileSystemRawItem(userFileSystemPath).DehydrationRequired()) + else if (userFileSystemRawItem.DehydrationRequired()) { LogMessage("Dehydrating", userFileSystemPath); new PlaceholderFile(userFileSystemPath).Dehydrate(0, -1); diff --git a/ITHit.FileSystem.Samples.Common/Syncronyzation/UserFileSystemMonitor.cs b/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/UserFileSystemMonitor.cs similarity index 83% rename from ITHit.FileSystem.Samples.Common/Syncronyzation/UserFileSystemMonitor.cs rename to ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/UserFileSystemMonitor.cs index a7f5dc2..d8e66f1 100644 --- a/ITHit.FileSystem.Samples.Common/Syncronyzation/UserFileSystemMonitor.cs +++ b/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/UserFileSystemMonitor.cs @@ -12,7 +12,7 @@ using Windows.Storage.Provider; using Windows.System.Update; -namespace ITHit.FileSystem.Samples.Common.Syncronyzation +namespace ITHit.FileSystem.Samples.Common.Windows.Syncronyzation { /// /// Monitors files and folders creation as well as attributes change in the user file system. @@ -24,22 +24,17 @@ namespace ITHit.FileSystem.Samples.Common.Syncronyzation /// /// In most cases you can use this class in your project without any changes. /// - internal class UserFileSystemMonitor : Logger, IDisposable + public class UserFileSystemMonitor : Logger, IDisposable { /// /// User file system watcher. /// - private FileSystemWatcher watcher = new FileSystemWatcher(); - - /// - /// User file system root path. Folder to monitor for changes. - /// - private string userFileSystemRootPath; + private readonly FileSystemWatcher watcher = new FileSystemWatcher(); /// /// Virtual drive. /// - private VirtualDriveBase vilrtualDrive; + private readonly VirtualDriveBase virtualDrive; /// /// Creates instance of this class. @@ -47,10 +42,8 @@ internal class UserFileSystemMonitor : Logger, IDisposable /// User file system root path. Attributes are monitored in this folder. /// Logger. internal UserFileSystemMonitor(string userFileSystemRootPath, VirtualDriveBase virtualDrive, ILog log) : base("User File System Monitor", log) - { - - this.userFileSystemRootPath = userFileSystemRootPath; - this.vilrtualDrive = virtualDrive; + { + this.virtualDrive = virtualDrive; watcher.IncludeSubdirectories = true; watcher.Path = userFileSystemRootPath; @@ -66,13 +59,20 @@ internal UserFileSystemMonitor(string userFileSystemRootPath, VirtualDriveBase v /// /// Starts monitoring attributes changes in user file system. /// - internal async Task StartAsync() + public async Task StartAsync() { watcher.EnableRaisingEvents = true; LogMessage("Started"); } + public async Task StopAsync() + { + watcher.EnableRaisingEvents = false; + + LogMessage("Stopped"); + } + /// /// Called when a file or folder is created in the user file system. /// @@ -81,14 +81,16 @@ internal async Task StartAsync() /// private async void CreatedAsync(object sender, FileSystemEventArgs e) { - LogMessage($"{e.ChangeType}", e.FullPath); + //LogMessage($"{e.ChangeType}", e.FullPath); + string userFileSystemPath = e.FullPath; try { - - // When a file/folder is moved this method is also called. The file/folder move is already processed in IFileSystem.MoveToAsync(). + // When a file/folder is moved this method is also called. The file/folder move is already processed in IFileSystemItem.MoveToAsync(). if (FsPath.Exists(userFileSystemPath) && !PlaceholderItem.IsPlaceholder(userFileSystemPath)) { + LogMessage("Creating new item", userFileSystemPath); + // When a new file or folder is created under sync root it is // created as a regular file or folder. Converting to placeholder. PlaceholderItem.ConvertToPlaceholder(userFileSystemPath, false); @@ -98,9 +100,10 @@ private async void CreatedAsync(object sender, FileSystemEventArgs e) if (!FsPath.AvoidSync(userFileSystemPath)) { // Create the file/folder in the remote storage. + FileSystemItemTypeEnum itemType = FsPath.GetItemType(userFileSystemPath); try { - await RemoteStorageRawItem.CreateAsync(userFileSystemPath, vilrtualDrive, this); + await virtualDrive.GetRemoteStorageRawItem(userFileSystemPath, itemType, this).CreateAsync(); } catch (IOException ex) { @@ -123,20 +126,21 @@ private async void CreatedAsync(object sender, FileSystemEventArgs e) /// private async void ChangedAsync(object sender, FileSystemEventArgs e) { - LogMessage($"{e.ChangeType}", e.FullPath); + //LogMessage($"{e.ChangeType}", e.FullPath); try { string userFileSystemPath = e.FullPath; if (FsPath.Exists(userFileSystemPath) && !FsPath.AvoidSync(userFileSystemPath)) { // Hydrate / dehydrate. - if (new UserFileSystemRawItem(userFileSystemPath).HydrationRequired()) + if (virtualDrive.GetUserFileSystemRawItem(userFileSystemPath, this).HydrationRequired()) { LogMessage("Hydrating", userFileSystemPath); + DateTimeOffset start = DateTimeOffset.Now; new PlaceholderFile(userFileSystemPath).Hydrate(0, -1); - LogMessage("Hydrated succesefully", userFileSystemPath); + LogMessage($"Hydrated succesefully {DateTimeOffset.Now-start}", userFileSystemPath); } - else if (new UserFileSystemRawItem(userFileSystemPath).DehydrationRequired()) + else if (virtualDrive.GetUserFileSystemRawItem(userFileSystemPath, this).DehydrationRequired()) { LogMessage("Dehydrating", userFileSystemPath); new PlaceholderFile(userFileSystemPath).Dehydrate(0, -1); @@ -156,7 +160,7 @@ private async void ChangedAsync(object sender, FileSystemEventArgs e) /// We monitor this event for logging purposes only. private async void DeletedAsync(object sender, FileSystemEventArgs e) { - LogMessage(e.ChangeType.ToString(), e.FullPath); + //LogMessage(e.ChangeType.ToString(), e.FullPath); } /// @@ -165,7 +169,7 @@ private async void DeletedAsync(object sender, FileSystemEventArgs e) /// We monitor this event for logging purposes only. private async void RenamedAsync(object sender, RenamedEventArgs e) { - LogMessage("Renamed", e.OldFullPath, e.FullPath); + //LogMessage("Renamed", e.OldFullPath, e.FullPath); } diff --git a/ITHit.FileSystem.Samples.Common/Syncronyzation/UserFileSystemRawItem.cs b/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/UserFileSystemRawItem.cs similarity index 72% rename from ITHit.FileSystem.Samples.Common/Syncronyzation/UserFileSystemRawItem.cs rename to ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/UserFileSystemRawItem.cs index 085ac0f..f224d7e 100644 --- a/ITHit.FileSystem.Samples.Common/Syncronyzation/UserFileSystemRawItem.cs +++ b/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/UserFileSystemRawItem.cs @@ -11,44 +11,75 @@ using Windows.Storage; using Windows.Storage.Provider; -namespace ITHit.FileSystem.Samples.Common.Syncronyzation +namespace ITHit.FileSystem.Samples.Common.Windows.Syncronyzation { /// /// Provides methods for synching the from remote storage to the user file system. /// Creates, updates and deletes placeholder files and folders based on the info from the remote storage. /// /// In most cases you can use this class in your project without any changes. - public class UserFileSystemRawItem + internal class UserFileSystemRawItem : IServerNotifications { /// /// Path to the file or folder placeholder in user file system. /// - private string userFileSystemPath; + private readonly string userFileSystemPath; + + /// + /// Virtual drive instance. + /// + private readonly VirtualDriveBase virtualDrive; + + /// + /// Logger. + /// + private readonly ILogger logger; + + /// + /// ETag manager. + /// + private readonly ETagManager eTagManager; /// /// Creates instance of this class. /// /// File or folder path in user file system. /// Logger. - public UserFileSystemRawItem(string userFileSystemPath) + public UserFileSystemRawItem(string userFileSystemPath, VirtualDriveBase virtualDrive, ILogger logger) { - if(string.IsNullOrEmpty(userFileSystemPath)) + if (string.IsNullOrEmpty(userFileSystemPath)) + { + throw new ArgumentNullException(nameof(userFileSystemPath)); + } + + if (virtualDrive == null) { - throw new ArgumentNullException("userFileSystemPath"); + throw new ArgumentNullException(nameof(virtualDrive)); + } + + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); } this.userFileSystemPath = userFileSystemPath; + this.virtualDrive = virtualDrive; + this.logger = logger; + + this.eTagManager = virtualDrive.GetETagManager(userFileSystemPath); } //$ - /// Creates a new file or folder placeholder in user file system. + /// Creates new file and folder placeholders in the user file system in this folder. /// - /// User file system folder path in which the new item will be created. + /// User file system folder path in which the new items will be created. /// Array of new files and folders. /// Number of items created. - public static async Task CreateAsync(string userFileSystemParentPath, FileSystemItemBasicInfo[] newItemsInfo) + public async Task CreateAsync(FileSystemItemMetadata[] newItemsInfo) { + string userFileSystemParentPath = userFileSystemPath; + try { // Because of the on-demand population the file or folder placeholder may not exist in the user file system. @@ -56,35 +87,48 @@ public static async Task CreateAsync(string userFileSystemParentPath, File if (Directory.Exists(userFileSystemParentPath) && !new DirectoryInfo(userFileSystemParentPath).Attributes.HasFlag(System.IO.FileAttributes.Offline)) { + // Here we save current user file system path inside file/folder. + // If the file/folder is moved or renamed while this app is not running, + // we extract the saved path when this app starts and sync the file/folder to the remote storage. + foreach (var newItemInfo in newItemsInfo) + { + string userFileSystemPath = Path.Combine(userFileSystemParentPath, newItemInfo.Name); + newItemInfo.CustomData = new CustomData + { + OriginalPath = userFileSystemPath + }.Serialize(); + } + // Create placeholders. uint created = new PlaceholderFolder(userFileSystemParentPath).CreatePlaceholders(newItemsInfo); // Create ETags. - foreach (FileSystemItemBasicInfo child in newItemsInfo) + foreach (FileSystemItemMetadata child in newItemsInfo) { - string userFileSystemItemPath = Path.Combine(userFileSystemParentPath, child.Name); - await ETag.SetETagAsync(userFileSystemItemPath, child.ETag); + string userFileSystemNewItemPath = Path.Combine(userFileSystemParentPath, child.Name); + await virtualDrive.GetETagManager(userFileSystemNewItemPath).SetETagAsync(child.ETag); - // Set the "locked by another user" icon and all custom columns data. - await new UserFileSystemRawItem(userFileSystemItemPath).SetLockedByAnotherUserAsync(child.LockedByAnotherUser); - await new UserFileSystemRawItem(userFileSystemItemPath).SetCustomColumnsDataAsync(child.CustomProperties); + // Set the read-only attribute and all custom columns data. + UserFileSystemRawItem newUserFileSystemRawItem = virtualDrive.GetUserFileSystemRawItem(userFileSystemNewItemPath, logger); + await newUserFileSystemRawItem.SetLockedByAnotherUserAsync(child.LockedByAnotherUser); + await newUserFileSystemRawItem.SetCustomColumnsDataAsync(child.CustomProperties); } return created; } } catch (ExistsException ex) - { - // "Cannot create a file when that file already exists." - //await new UserFileSystemItem(userFileSystemParentPath).ShowDownloadErrorAsync(ex); + { + // "Cannot create a file when that file already exists." + //await new UserFileSystemItem(userFileSystemParentPath).ShowDownloadErrorAsync(ex); + + // Rethrow the exception preserving stack trace of the original exception. + System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw(); + } - // Rethrow the exception preserving stack trace of the original exception. - System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw(); - } - return 0; } - //$> + //$> //$ @@ -94,7 +138,7 @@ public static async Task CreateAsync(string userFileSystemParentPath, File /// This method failes if the file or folder in user file system is modified (not in-sync with the remote storage). /// New file or folder info. /// True if the file was updated. False - otherwise. - public async Task UpdateAsync(FileSystemItemBasicInfo itemInfo) + public async Task UpdateAsync(FileSystemItemMetadata itemInfo) { try { @@ -104,7 +148,7 @@ public async Task UpdateAsync(FileSystemItemBasicInfo itemInfo) PlaceholderItem placeholderItem = PlaceholderItem.GetItem(userFileSystemPath); // To be able to update the item we need to remove the read-only attribute. - if((FsPath.GetFileSystemItem(userFileSystemPath).Attributes | System.IO.FileAttributes.ReadOnly) != 0) + if ((FsPath.GetFileSystemItem(userFileSystemPath).Attributes | System.IO.FileAttributes.ReadOnly) != 0) { FsPath.GetFileSystemItem(userFileSystemPath).Attributes &= ~System.IO.FileAttributes.ReadOnly; } @@ -113,12 +157,12 @@ public async Task UpdateAsync(FileSystemItemBasicInfo itemInfo) placeholderItem.SetItemInfo(itemInfo); // Set ETag. - await ETag.SetETagAsync(userFileSystemPath, itemInfo.ETag); + await eTagManager.SetETagAsync(itemInfo.ETag); // Clear icon. //await ClearStateAsync(); - // Set the "locked by another user" icon and all custom columns data. + // Set the read-only attribute and all custom columns data. await SetLockedByAnotherUserAsync(itemInfo.LockedByAnotherUser); await SetCustomColumnsDataAsync(itemInfo.CustomProperties); @@ -169,7 +213,8 @@ public async Task DeleteAsync() } // Delete ETag - ETag.DeleteETag(userFileSystemPath); + logger.LogMessage("Deleting ETag", userFileSystemPath); + eTagManager.DeleteETag(); return true; } @@ -199,11 +244,12 @@ public async Task DeleteAsync() /// This method failes if the file or folder in user file system is modified (not in sync with the remote storage) /// or if the target file exists. /// - public async Task MoveToAsync(string userFileSystemNewPath) + /// True if the item was moved. False - otherwise. + public async Task MoveToAsync(string userFileSystemNewPath) { // Cloud Filter API does not provide a function to move a placeholder file only if it is not modified. // The file may be modified between InSync call, Move() call and SetInSync() in this method. - + bool itemMoved = false; try { // Because of the on-demand population the file or folder placeholder may not exist in the user file system. @@ -212,30 +258,22 @@ public async Task MoveToAsync(string userFileSystemNewPath) bool inSync = PlaceholderItem.GetItem(userFileSystemPath).GetInSync(); if (inSync) { - string eTag = await ETag.GetETagAsync(userFileSystemPath); - - ETag.DeleteETag(userFileSystemPath); - try - { - Directory.Move(userFileSystemPath, userFileSystemNewPath); - } - catch - { - await ETag.SetETagAsync(userFileSystemPath, eTag); - throw; - } + logger.LogMessage("Moving ETag", userFileSystemPath, userFileSystemNewPath); + await eTagManager.MoveToAsync(userFileSystemNewPath); - await ETag.SetETagAsync(userFileSystemNewPath, eTag); + logger.LogMessage("Moving item", userFileSystemPath, userFileSystemNewPath); + Directory.Move(userFileSystemPath, userFileSystemNewPath); // The file is marked as not in sync after move/rename. Marking it as in-sync. PlaceholderItem placeholderItem = PlaceholderItem.GetItem(userFileSystemNewPath); placeholderItem.SetInSync(true); placeholderItem.SetOriginalPath(userFileSystemNewPath); - await new UserFileSystemRawItem(userFileSystemNewPath).ClearStateAsync(); + await virtualDrive.GetUserFileSystemRawItem(userFileSystemNewPath, logger).ClearStateAsync(); + itemMoved = true; } - if(!inSync) + if (!inSync) { throw new ConflictException(Modified.Client, "The item is not in-sync with the cloud."); } @@ -243,12 +281,13 @@ public async Task MoveToAsync(string userFileSystemNewPath) } catch (Exception ex) { - string path = FsPath.Exists(userFileSystemNewPath) ? userFileSystemNewPath : userFileSystemPath; - await new UserFileSystemRawItem(userFileSystemPath).SetDownloadErrorStateAsync(ex); + string userFileSystemExPath = FsPath.Exists(userFileSystemNewPath) ? userFileSystemNewPath : userFileSystemPath; + await virtualDrive.GetUserFileSystemRawItem(userFileSystemExPath, logger).SetDownloadErrorStateAsync(ex); // Rethrow the exception preserving stack trace of the original exception. System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw(); } + return itemMoved; } /// @@ -338,7 +377,7 @@ private async Task SetDownloadErrorStateAsync(Exception ex) /// True to display the icon. False - to remove the icon. private async Task SetConflictIconAsync(bool set) { - await SetIconAsync(set, 2, "Error.ico", "Conflict. File is modified both on the server and on the client."); + await SetIconAsync(set, (int)CustomColumnIds.ConflictIcon, "Error.ico", "Conflict. File is modified both on the server and on the client."); } /// @@ -347,7 +386,7 @@ private async Task SetConflictIconAsync(bool set) /// True to display the icon. False - to remove the icon. private async Task SetDownloadPendingIconAsync(bool set) { -// await SetIconAsync(set, 2, "Down.ico", "Download from server pending"); + // await SetIconAsync(set, 2, "Down.ico", "Download from server pending"); } /// @@ -356,7 +395,7 @@ private async Task SetDownloadPendingIconAsync(bool set) /// True to display the icon. False - to remove the icon. private async Task SetUploadPendingIconAsync(bool set) { -// await SetIconAsync(set, 2, "Up.ico", "Upload to server pending"); + // await SetIconAsync(set, 2, "Up.ico", "Upload to server pending"); } /// @@ -368,7 +407,7 @@ internal async Task SetLockInfoAsync(ServerLockInfo lockInfo) IEnumerable lockProps = null; if (lockInfo != null) { - lockProps = lockInfo.GetLockProperties(Path.Combine(Config.Settings.IconsFolderPath, "Locked.ico")); + lockProps = lockInfo.GetLockProperties(Path.Combine(virtualDrive.Settings.IconsFolderPath, "Locked.ico")); } await SetCustomColumnsDataAsync(lockProps); } @@ -379,7 +418,7 @@ internal async Task SetLockInfoAsync(ServerLockInfo lockInfo) /// True to display the icon. False - to remove the icon. internal async Task SetLockPendingIconAsync(bool set) { - await SetIconAsync(set, 2, "LockedPending.ico", "Updating lock..."); + await SetIconAsync(set, (int)CustomColumnIds.LockOwnerIcon, "LockedPending.ico", "Updating lock..."); } /// @@ -392,7 +431,10 @@ private async Task SetIconAsync(bool set, int? id = null, string iconFile = null if (storageItem == null) { - return; // Item does not exists. + // This method may be called on temp files, typically created by MS Office, that exist for a short period of time. + // StorageProviderItemProperties.SetAsync(null,) causes AccessViolationException + // which is not handled by .NET (or handled by HandleProcessCorruptedStateExceptions) and causes a fatal crush. + return; } try @@ -403,7 +445,7 @@ private async Task SetIconAsync(bool set, int? id = null, string iconFile = null { Id = id.Value, Value = description, - IconResource = Path.Combine(Config.Settings.IconsFolderPath, iconFile) + IconResource = Path.Combine(virtualDrive.Settings.IconsFolderPath, iconFile) }; await StorageProviderItemProperties.SetAsync(storageItem, new StorageProviderItemProperty[] { propState }); } @@ -414,7 +456,7 @@ private async Task SetIconAsync(bool set, int? id = null, string iconFile = null } // Setting status icon failes for blocked files. - catch(FileNotFoundException) + catch (FileNotFoundException) { } @@ -440,14 +482,15 @@ private async Task SetIconAsync(bool set, int? id = null, string iconFile = null /// Sets or removes read-only attribute on files. /// /// True to set the read-only attribute. False - to remove the read-only attribute. - public async Task SetLockedByAnotherUserAsync(bool set) + public async Task SetLockedByAnotherUserAsync(bool set) { + bool resultSet = false; // Changing read-only attribute on folders triggers folders listing. Changing it on files only. - if(FsPath.IsFile(userFileSystemPath)) + if (FsPath.IsFile(userFileSystemPath)) { FileInfo file = new FileInfo(userFileSystemPath); - if(set != file.IsReadOnly) + if (set != file.IsReadOnly) { // Set/Remove read-only attribute. if (set) @@ -458,8 +501,12 @@ public async Task SetLockedByAnotherUserAsync(bool set) { new FileInfo(userFileSystemPath).Attributes &= ~System.IO.FileAttributes.ReadOnly; } + + resultSet = true; } } + + return resultSet; } internal async Task SetCustomColumnsDataAsync(IEnumerable customColumnsData) @@ -476,14 +523,25 @@ internal async Task SetCustomColumnsDataAsync(IEnumerable @@ -29,9 +29,9 @@ internal class VfsEngine : EngineWindows /// Creates a Windows user file system. /// /// A license string. - /// A root folder of your user file system. Your file system tree will be located under this folder. + /// A root folder of your user file system. Your file system tree will be located under this folder. /// Logger. - internal VfsEngine(string license, string path, VirtualDriveBase virtualDrive, ILog log) : base(license, path) + internal VfsEngine(string license, string userFileSystemRootPath, VirtualDriveBase virtualDrive, ILog log) : base(license, userFileSystemRootPath) { logger = new Logger("File System Engine", log); @@ -59,7 +59,9 @@ public override async Task GetFileSystemItemAsync(string path) return new VfsFolder(path, this, this, virtualDrive); } - // When a file handle is being closed during delete, the file does not exist, return null. + // Note that this method may be called for items that foes not exists in file system, + // for example when a a file handle is being closed during delete operation. + // The Engine calls IFile.CloseAsync() and IFileSystemItem.DeleteCompletionAsync() methods in this case. return null; } //$> diff --git a/ITHit.FileSystem.Samples.Common/VfsFile.cs b/ITHit.FileSystem.Samples.Common.Windows/VfsFile.cs similarity index 66% rename from ITHit.FileSystem.Samples.Common/VfsFile.cs rename to ITHit.FileSystem.Samples.Common.Windows/VfsFile.cs index a52865f..eea3a1e 100644 --- a/ITHit.FileSystem.Samples.Common/VfsFile.cs +++ b/ITHit.FileSystem.Samples.Common.Windows/VfsFile.cs @@ -1,18 +1,20 @@ -using ITHit.FileSystem; -using ITHit.FileSystem.Windows; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; -using ITHit.FileSystem.Samples.Common.Syncronyzation; -namespace ITHit.FileSystem.Samples.Common +using ITHit.FileSystem; +using ITHit.FileSystem.Windows; +using ITHit.FileSystem.Samples.Common.Windows.Syncronyzation; + + +namespace ITHit.FileSystem.Samples.Common.Windows { // In most cases you can use this class in your project without any changes. /// - internal class VfsFile : VfsFileSystemItem, IFile + internal class VfsFile : VfsFileSystemItem, IFile { /// @@ -28,26 +30,30 @@ public VfsFile(string path, ILogger logger, VfsEngine engine, VirtualDriveBase u /// public async Task OpenAsync(IOperationContext operationContext, IResultContext context) { - Logger.LogMessage("IFile.OpenAsync()", UserFileSystemPath); + Logger.LogMessage($"{nameof(IFile)}.{nameof(OpenAsync)}()", UserFileSystemPath); // Auto-lock the file. string userFileSystemFilePath = UserFileSystemPath; if (Engine.ChangesProcessingEnabled && FsPath.Exists(userFileSystemFilePath)) { - if (Config.Settings.AutoLock - && !FsPath.AvoidAutoLock(userFileSystemFilePath) - && ! await Lock.IsLockedAsync(userFileSystemFilePath) + if (VirtualDrive.Settings.AutoLock + && !FsPath.AvoidAutoLock(userFileSystemFilePath) + && !await VirtualDrive.LockManager(userFileSystemFilePath, Logger).IsLockedAsync() && FsPath.IsWriteLocked(userFileSystemFilePath) - && !new PlaceholderFile(userFileSystemFilePath).IsNew()) + && !new PlaceholderFile(userFileSystemFilePath).IsNew(VirtualDrive)) { - try - { - await new RemoteStorageRawItem(userFileSystemFilePath, VirtualDrive, Logger).LockAsync(LockMode.Auto); - } - catch(ClientLockFailedException ex) + RemoteStorageRawItem remoteStorageRawItem = new RemoteStorageRawItem(userFileSystemFilePath, VirtualDrive, Logger); + if (await remoteStorageRawItem.IsLockSupportedAsync()) { - // Lock file is blocked by the concurrent thread. This is a normal behaviour. - Logger.LogMessage(ex.Message, userFileSystemFilePath); + try + { + await remoteStorageRawItem.LockAsync(LockMode.Auto); + } + catch (ClientLockFailedException ex) + { + // Lock file is blocked by a concurrent thread. This is a normal behaviour. + Logger.LogMessage(ex.Message, userFileSystemFilePath); + } } } } @@ -81,15 +87,15 @@ public async Task CloseAsync(IOperationContext operationContext, IResultContext try { - if (PlaceholderItem.GetItem(userFileSystemFilePath).IsNew()) + if (PlaceholderItem.GetItem(userFileSystemFilePath).IsNew(VirtualDrive)) { // Create new file in the remote storage. - await RemoteStorageRawItem.CreateAsync(userFileSystemFilePath, VirtualDrive, Logger); + await new RemoteStorageRawItem(userFileSystemFilePath, VirtualDrive, Logger).CreateAsync(); } - else + else if(!PlaceholderItem.GetItem(userFileSystemFilePath).IsMoved()) { // Send content to remote storage. Unlock if auto-locked. - await new RemoteStorageRawItem(userFileSystemFilePath, VirtualDrive, Logger).UpdateAsync(); + await new RemoteStorageRawItem(userFileSystemFilePath, VirtualDrive, Logger).UpdateAsync(); } } catch (IOException ex) @@ -102,24 +108,27 @@ public async Task CloseAsync(IOperationContext operationContext, IResultContext } } //$> - + //$ public async Task TransferDataAsync(long offset, long length, ITransferDataOperationContext operationContext, ITransferDataResultContext resultContext) { - // This method has a 60 sec timeout. - // To process longer requests and reset the timout timer call resultContext.ReportProgress() method. + // On Windows this method has a 60 sec timeout. + // To process longer requests and reset the timout timer call the resultContext.ReportProgress() or resultContext.ReturnData() method. + + // For files > 4Gb we have to use the OptionalLength. + if (operationContext.FileSize > 0x100000000) + { + length += operationContext.OptionalLength; + } - Logger.LogMessage($"IFile.TransferDataAsync({offset}, {length})", UserFileSystemPath); + Logger.LogMessage($"{nameof(IFile)}.{nameof(TransferDataAsync)}({offset}, {length})", UserFileSystemPath); SimulateNetworkDelay(length, resultContext); - - long optionalLength = length + operationContext.OptionalLength; - IUserFile userFile = await VirtualDrive.GetItemAsync(UserFileSystemPath); - byte[] buffer = await userFile.ReadAsync(offset, optionalLength); + IVirtualFile userFile = await VirtualDrive.GetItemAsync(UserFileSystemPath, Logger); - resultContext.ReturnData(buffer, offset, optionalLength); + await userFile.ReadAsync(offset, length, operationContext.FileSize, resultContext); } //$> @@ -133,7 +142,7 @@ public async Task ValidateDataAsync(long offset, long length, IValidateDataOpera //SimulateNetworkDelay(length, resultContext); - IUserFile userFile = await VirtualDrive.GetItemAsync(UserFileSystemPath); + IVirtualFile userFile = await VirtualDrive.GetItemAsync(UserFileSystemPath, Logger); bool isValid = await userFile.ValidateDataAsync(offset, length); resultContext.ReturnValidationResult(offset, length, isValid); diff --git a/ITHit.FileSystem.Samples.Common/VfsFileSystemItem.cs b/ITHit.FileSystem.Samples.Common.Windows/VfsFileSystemItem.cs similarity index 53% rename from ITHit.FileSystem.Samples.Common/VfsFileSystemItem.cs rename to ITHit.FileSystem.Samples.Common.Windows/VfsFileSystemItem.cs index ad47417..d997138 100644 --- a/ITHit.FileSystem.Samples.Common/VfsFileSystemItem.cs +++ b/ITHit.FileSystem.Samples.Common.Windows/VfsFileSystemItem.cs @@ -6,15 +6,15 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using ITHit.FileSystem.Samples.Common.Syncronyzation; +using ITHit.FileSystem.Samples.Common.Windows.Syncronyzation; using Windows.Storage; using Windows.Storage.Provider; -namespace ITHit.FileSystem.Samples.Common +namespace ITHit.FileSystem.Samples.Common.Windows { // In most cases you can use this class in your project without any changes. /// - internal abstract class VfsFileSystemItem : IFileSystemItem + internal abstract class VfsFileSystemItem : IFileSystemItem where TItemType : IVirtualFileSystemItem { /// /// File or folder path in the user file system. @@ -39,18 +39,18 @@ internal abstract class VfsFileSystemItem : IFileSystemItem /// /// Creates instance of this class. /// - /// File or folder path in user file system. + /// File or folder path in the user file system. /// Logger. public VfsFileSystemItem(string userFileSystemPath, ILogger logger, VfsEngine engine, VirtualDriveBase virtualDrive) { if (string.IsNullOrEmpty(userFileSystemPath)) { - throw new ArgumentNullException("userFileSystemPath"); + throw new ArgumentNullException(nameof(userFileSystemPath)); } if(logger == null) { - throw new ArgumentNullException("logger"); + throw new ArgumentNullException(nameof(logger)); } UserFileSystemPath = userFileSystemPath; @@ -64,44 +64,51 @@ public VfsFileSystemItem(string userFileSystemPath, ILogger logger, VfsEngine en public async Task MoveToAsync(string userFileSystemNewPath, IOperationContext operationContext, IConfirmationResultContext resultContext) { string userFileSystemOldPath = this.UserFileSystemPath; - Logger.LogMessage("IFileSystemItem.MoveToAsync()", userFileSystemOldPath, userFileSystemNewPath); + Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(MoveToAsync)}()", userFileSystemOldPath, userFileSystemNewPath); // Process move. if (Engine.ChangesProcessingEnabled) { if (FsPath.Exists(userFileSystemOldPath)) { - await new RemoteStorageRawItem(userFileSystemOldPath, VirtualDrive, Logger).MoveToAsync(userFileSystemNewPath, resultContext); + await new RemoteStorageRawItem(userFileSystemOldPath, VirtualDrive, Logger).MoveToAsync(userFileSystemNewPath, resultContext); } } else { resultContext.ReturnConfirmationResult(); } + } + //$> + + /// + public async Task MoveToCompletionAsync(IMoveCompletionContext moveCompletionContext, IResultContext resultContext) + { + string userFileSystemNewPath = this.UserFileSystemPath; + string userFileSystemOldPath = moveCompletionContext.SourcePath; + Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(MoveToCompletionAsync)}()", userFileSystemOldPath, userFileSystemNewPath); - // Restore Original Path and locked icon, lost during MS Office transactional save. - if (FsPath.Exists(userFileSystemNewPath) && PlaceholderItem.IsPlaceholder(userFileSystemNewPath)) + if (Engine.ChangesProcessingEnabled) { - PlaceholderItem userFileSystemNewItem = PlaceholderItem.GetItem(userFileSystemNewPath); - if (!userFileSystemNewItem.IsNew() && string.IsNullOrEmpty(userFileSystemNewItem.GetOriginalPath())) + if (FsPath.Exists(userFileSystemNewPath)) { - // Restore Original Path. - Logger.LogMessage("Saving Original Path", userFileSystemNewPath); - userFileSystemNewItem.SetOriginalPath(userFileSystemNewPath); - - // Restore the 'locked' icon. - bool isLocked = await Lock.IsLockedAsync(userFileSystemNewPath); - ServerLockInfo existingLock = isLocked ? await (await Lock.LockAsync(userFileSystemNewPath, FileMode.Open, LockMode.None, Logger)).GetLockInfoAsync() : null; - await new UserFileSystemRawItem(userFileSystemNewPath).SetLockInfoAsync(existingLock); + FileSystemItemTypeEnum itemType = FsPath.GetItemType(userFileSystemNewPath); + await new RemoteStorageRawItem(userFileSystemNewPath, VirtualDrive, Logger).MoveToCompletionAsync(); } } } - //$> //$ public async Task DeleteAsync(IOperationContext operationContext, IConfirmationResultContext resultContext) { + /* + Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(DeleteAsync)}()", this.UserFileSystemPath); + string userFileSystemPath = this.UserFileSystemPath; + Logger.LogMessage("Confirming delete in user file system", userFileSystemPath); + resultContext.ReturnConfirmationResult(); + */ + Logger.LogMessage("IFileSystemItem.DeleteAsync()", this.UserFileSystemPath); string userFileSystemPath = this.UserFileSystemPath; @@ -111,7 +118,7 @@ public async Task DeleteAsync(IOperationContext operationContext, IConfirmationR if (Engine.ChangesProcessingEnabled && !FsPath.AvoidSync(userFileSystemPath)) { - await new RemoteStorageRawItem(userFileSystemPath, VirtualDrive, Logger).DeleteAsync(); + await new RemoteStorageRawItem(userFileSystemPath, VirtualDrive, Logger).DeleteAsync(); Logger.LogMessage("Deleted item in remote storage succesefully", userFileSystemPath); } } @@ -124,12 +131,38 @@ public async Task DeleteAsync(IOperationContext operationContext, IConfirmationR resultContext.ReturnConfirmationResult(); } } + + /// + public async Task DeleteCompletionAsync(IOperationContext operationContext, IResultContext resultContext) + { + Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(DeleteCompletionAsync)}()", this.UserFileSystemPath); + /* + string userFileSystemPath = this.UserFileSystemPath; + string remoteStoragePath = null; + try + { + if (Engine.ChangesProcessingEnabled + && !FsPath.AvoidSync(userFileSystemPath)) + { + await new RemoteStorageRawItem(userFileSystemPath, VirtualDrive, Logger).DeleteAsync(); + Logger.LogMessage("Deleted item in remote storage succesefully", userFileSystemPath); + } + } + catch (Exception ex) + { + Logger.LogError("Delete in remote storage failed", remoteStoragePath, null, ex); + + // Rethrow the exception preserving stack trace of the original exception. + System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw(); + } + */ + } //$> /// - public Task GetBasicInfoAsync() + public Task GetMetadataAsync() { - // Return IFileBasicInfo for a file, IFolderBasicInfo for a folder. + // Return IFileMetadata for a file, IFolderMetadata for a folder. throw new NotImplementedException(); } @@ -140,13 +173,13 @@ public Task GetBasicInfoAsync() /// Context to report progress to. protected void SimulateNetworkDelay(long fileLength, IResultContext resultContext) { - if (Config.Settings.NetworkSimulationDelayMs > 0) + if (VirtualDrive.Settings.NetworkSimulationDelayMs > 0) { int numProgressResults = 5; for (int i = 0; i < numProgressResults; i++) { resultContext.ReportProgress(fileLength, i * fileLength / numProgressResults); - Thread.Sleep(Config.Settings.NetworkSimulationDelayMs); + Thread.Sleep(VirtualDrive.Settings.NetworkSimulationDelayMs); } } } diff --git a/ITHit.FileSystem.Samples.Common/VfsFolder.cs b/ITHit.FileSystem.Samples.Common.Windows/VfsFolder.cs similarity index 65% rename from ITHit.FileSystem.Samples.Common/VfsFolder.cs rename to ITHit.FileSystem.Samples.Common.Windows/VfsFolder.cs index 1e7f8d2..e727254 100644 --- a/ITHit.FileSystem.Samples.Common/VfsFolder.cs +++ b/ITHit.FileSystem.Samples.Common.Windows/VfsFolder.cs @@ -1,6 +1,4 @@ -using ITHit.FileSystem; -using ITHit.FileSystem.Windows; -using System; +using System; using System.Collections.Generic; using System.IO; using System.IO.Enumeration; @@ -8,18 +6,22 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using ITHit.FileSystem.Samples.Common.Syncronyzation; using Windows.Storage; using Windows.Storage.Provider; -namespace ITHit.FileSystem.Samples.Common +using ITHit.FileSystem; +using ITHit.FileSystem.Windows; +using ITHit.FileSystem.Samples.Common.Windows.Syncronyzation; + + +namespace ITHit.FileSystem.Samples.Common.Windows { // In most cases you can use this class in your project without any changes. //$ - internal class VfsFolder : VfsFileSystemItem, IFolder + internal class VfsFolder : VfsFileSystemItem, IFolder { - public VfsFolder(string path, ILogger logger, VfsEngine engine, VirtualDriveBase userEngine) : base(path, logger, engine, userEngine) + public VfsFolder(string path, ILogger logger, VfsEngine engine, VirtualDriveBase virtualDrive) : base(path, logger, engine, virtualDrive) { } @@ -34,12 +36,12 @@ public async Task GetChildrenAsync(string pattern, IOperationContext operationCo Logger.LogMessage($"IFolder.GetChildrenAsync({pattern})", UserFileSystemPath); - IUserFolder userFolder = await VirtualDrive.GetItemAsync(UserFileSystemPath); - IEnumerable children = await userFolder.EnumerateChildrenAsync(pattern); + IVirtualFolder userFolder = await VirtualDrive.GetItemAsync(UserFileSystemPath, Logger); + IEnumerable children = await userFolder.EnumerateChildrenAsync(pattern); // Filtering existing files/folders. This is only required to avoid extra errors in the log. - List newChildren = new List(); - foreach (FileSystemItemBasicInfo child in children) + List newChildren = new List(); + foreach (FileSystemItemMetadata child in children) { string userFileSystemItemPath = Path.Combine(UserFileSystemPath, child.Name); if (!FsPath.Exists(userFileSystemItemPath)) @@ -61,9 +63,9 @@ public async Task GetChildrenAsync(string pattern, IOperationContext operationCo // always call ReturnChildren(), even if the folder is empty. resultContext.ReturnChildren(newChildren.ToArray(), newChildren.Count()); - - // Save ETags and set "locked by another user" icon. - foreach (FileSystemItemBasicInfo child in children) + + // Save ETags, the read-only attribute and all custom columns data. + foreach (FileSystemItemMetadata child in children) { string userFileSystemItemPath = Path.Combine(UserFileSystemPath, child.Name); @@ -71,11 +73,12 @@ public async Task GetChildrenAsync(string pattern, IOperationContext operationCo // ETags must correspond with a server file/folder, NOT with a client placeholder. // It should NOT be moved/deleted/updated when a placeholder in the user file system is moved/deleted/updated. // It should be moved/deleted when a file/folder in the remote storage is moved/deleted. - await ETag.SetETagAsync(userFileSystemItemPath, child.ETag); + await VirtualDrive.GetETagManager(userFileSystemItemPath, Logger).SetETagAsync(child.ETag); - // Set the "locked by another user" icon and all custom columns data. - await new UserFileSystemRawItem(userFileSystemItemPath).SetLockedByAnotherUserAsync(child.LockedByAnotherUser); - await new UserFileSystemRawItem(userFileSystemItemPath).SetCustomColumnsDataAsync(child.CustomProperties); + // Set the read-only attribute and all custom columns data. + UserFileSystemRawItem userFileSystemRawItem = VirtualDrive.GetUserFileSystemRawItem(userFileSystemItemPath, Logger); + await userFileSystemRawItem.SetLockedByAnotherUserAsync(child.LockedByAnotherUser); + await userFileSystemRawItem.SetCustomColumnsDataAsync(child.CustomProperties); } } } diff --git a/ITHit.FileSystem.Samples.Common.Windows/VirtualDriveBase.cs b/ITHit.FileSystem.Samples.Common.Windows/VirtualDriveBase.cs new file mode 100644 index 0000000..37885c1 --- /dev/null +++ b/ITHit.FileSystem.Samples.Common.Windows/VirtualDriveBase.cs @@ -0,0 +1,228 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using log4net; + +using ITHit.FileSystem; +using ITHit.FileSystem.Windows; +using ITHit.FileSystem.Samples.Common.Windows.Syncronyzation; + + +namespace ITHit.FileSystem.Samples.Common.Windows +{ + /// + public abstract class VirtualDriveBase : IVirtualDrive, IDisposable + { + /// + /// Virtual drive settings. + /// + public readonly Settings Settings; + + /// + /// Current synchronization state of this virtual drive. + /// + public virtual SynchronizationState SyncState { get; private set; } = SynchronizationState.Disabled; + + /// + /// Event, fired when synchronization state changes. + /// + public event SyncronizationEvent SyncEvent; + + /// + /// Processes file system calls, implements on-demand folders listing + /// and initial on-demand file content transfer from remote storage to client. + /// + private readonly VfsEngine engine; + + /// + /// Monitors pinned and unpinned attributes in user file system. + /// + private readonly UserFileSystemMonitor userFileSystemMonitor; + + /// + /// Performs complete synchronyzation of the folders and files that are already synched to user file system. + /// + public readonly FullSyncService SyncService; + + /// + /// Log4Net logger. + /// + private readonly ILog log; + + /// + /// Creates instance of this class. + /// + /// A license string. + /// + /// A root folder of your user file system. Your file system tree will be located under this folder. + /// + /// Log4net logger. + /// Virtual drive settings. + public VirtualDriveBase(string license, string userFileSystemRootPath, Settings settings, ILog log) + { + this.Settings = settings; + this.log = log; + engine = new VfsEngine(license, userFileSystemRootPath, this, log); + SyncService = new FullSyncService(settings.SyncIntervalMs, userFileSystemRootPath, this, log); + SyncService.SyncEvent += SyncService_SyncEvent; + userFileSystemMonitor = new UserFileSystemMonitor(userFileSystemRootPath, this, log); + } + + private void SyncService_SyncEvent(object sender, SynchEventArgs synchEventArgs) + { + InvokeSyncEvent(synchEventArgs.NewState); + } + + /// + public abstract Task GetVirtualFileSystemItemAsync(string userFileSystemPath, FileSystemItemTypeEnum itemType, ILogger logger); + + /// + /// This is a strongly typed variant of GetVirtualFileSystemItemAsync() method for internal use. + /// + /// This is either or . + /// Path in user file system for which your will return a file or a folder. + /// File or folder object that corresponds to the path in user file system. + internal async Task GetItemAsync(string userFileSystemPath, ILogger logger) where T : IVirtualFileSystemItem + { + FileSystemItemTypeEnum itemType = typeof(T).Name == nameof(IVirtualFile) ? FileSystemItemTypeEnum.File : FileSystemItemTypeEnum.Folder; + + IVirtualFileSystemItem userItem = await GetVirtualFileSystemItemAsync(userFileSystemPath, itemType, logger); + if (userItem == null) + { + throw new System.IO.FileNotFoundException($"{itemType} not found.", userFileSystemPath); + } + + if ((T)userItem == null) + { + throw new NotImplementedException($"{typeof(T).Name}"); + } + + return (T)userItem; + } + + + + /// + /// Starts processing OS file system calls, starts user file system to remote storage synchronization + /// and remote storage to user file system synchronization + /// as well as starts monitoring for pinned/unpinned files. + /// + public virtual async Task StartAsync() + { + // Start processing OS file system calls. + engine.ChangesProcessingEnabled = true; + await engine.StartAsync(); + + await SetEnabledAsync(true); + } + + /// + public virtual async Task SetEnabledAsync(bool enabled) + { + if (enabled) + { + // Start periodical synchronyzation between client and server, + // in case any changes are lost because the client or the server were unavailable. + await SyncService.StartAsync(); + + // Start monitoring pinned/unpinned attributes and files/folders creation in user file system. + await userFileSystemMonitor.StartAsync(); + } + else + { + await SyncService.StopAsync(); + await userFileSystemMonitor.StopAsync(); + } + } + + private bool disposedValue; + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + engine.Dispose(); + SyncService.Dispose(); + userFileSystemMonitor.Dispose(); + } + + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + disposedValue = true; + } + } + + // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources + // ~UserEngine() + // { + // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + // Dispose(disposing: false); + // } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + private void InvokeSyncEvent(SynchronizationState state) + { + SyncState = state; + if (SyncEvent != null) + { + SyncEvent.Invoke(this, new SynchEventArgs(state)); + } + } + + /// + public IServerNotifications ServerNotifications(string userFileSystemPath, ILogger logger) + { + return GetUserFileSystemRawItem(userFileSystemPath, logger); + } + + /// + public IClientNotifications ClientNotifications(string userFileSystemPath, ILogger logger) + { + FileSystemItemTypeEnum itemType = FsPath.GetItemType(userFileSystemPath); + return GetRemoteStorageRawItem(userFileSystemPath, itemType, logger); + } + + internal LockManager LockManager(string userFileSystemPath, ILogger logger) + { + return new LockManager(userFileSystemPath, Settings.ServerDataFolderPath, engine.Path, logger); + } + + /// + /// Provides methods for reading and writing eTags. + /// + /// User file system item path. + /// Logger. + /// + public ETagManager GetETagManager(string userFileSystemPath, ILogger logger = null) + { + logger ??= new Logger("Virtual Drive", log); + return new ETagManager(userFileSystemPath, Settings.ServerDataFolderPath, engine.Path, logger); + } + + internal IRemoteStorageRawItem GetRemoteStorageRawItem(string userFileSystemPath, FileSystemItemTypeEnum itemType, ILogger logger) + { + if (itemType == FileSystemItemTypeEnum.File) + { + return new RemoteStorageRawItem(userFileSystemPath, this, logger); + } + else + { + return new RemoteStorageRawItem(userFileSystemPath, this, logger); + } + } + + internal UserFileSystemRawItem GetUserFileSystemRawItem(string userFileSystemPath, ILogger logger) + { + return new UserFileSystemRawItem(userFileSystemPath, this, logger); + } + } +} diff --git a/ITHit.FileSystem.Samples.Common/CustomColumnIds.cs b/ITHit.FileSystem.Samples.Common/CustomColumnIds.cs new file mode 100644 index 0000000..4ad8c2b --- /dev/null +++ b/ITHit.FileSystem.Samples.Common/CustomColumnIds.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ITHit.FileSystem.Samples.Common +{ + /// + /// Represents custom columns IDs that are displayed in Windows File manager, + /// such as Lock Owner, Lock Scope, etc. + /// + public enum CustomColumnIds + { + /// + /// Lock Owner column ID. The lock icon is being displayed in the Windows File Manager Status column. + /// + LockOwnerIcon = 2, + + /// + /// Conflict icon column ID. The conflict icon is being displayed in the Windows File Manager Status column. + /// + ConflictIcon = 3, + + /// + /// Lock Scope column ID. Shows if the lock is Exclusive or Shared. + /// + LockScope = 4, + + /// + /// Lock Expires column ID. + /// + LockExpirationDate = 5, + + /// + /// ETag column ID. + /// + ETag = 6 + } +} diff --git a/ITHit.FileSystem.Samples.Common/ETag.cs b/ITHit.FileSystem.Samples.Common/ETag.cs deleted file mode 100644 index 9d91de6..0000000 --- a/ITHit.FileSystem.Samples.Common/ETag.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading.Tasks; - -namespace ITHit.FileSystem.Samples.Common -{ - /// - /// Provides method for reading and writing ETags. - /// - public static class ETag - { - /// - /// Creates or updates ETag associated with the file. - /// - /// Path in the user file system. - /// ETag. - /// - public static async Task SetETagAsync(string userFileSystemPath, string eTag) - { - string eTagFilePath = GetETagFilePath(userFileSystemPath); - Directory.CreateDirectory(Path.GetDirectoryName(eTagFilePath)); - await File.WriteAllTextAsync(eTagFilePath, eTag); - } - - /// - /// Gets ETag associated with a file. - /// - /// Path in the user file system. - /// ETag. - public static async Task GetETagAsync(string userFileSystemPath) - { - string eTagFilePath = GetETagFilePath(userFileSystemPath); - if (!File.Exists(eTagFilePath)) - { - return null; - } - return await File.ReadAllTextAsync(eTagFilePath); - } - - /// - /// Deletes ETag associated with a file. - /// - /// Path in the user file system. - public static void DeleteETag(string userFileSystemPath) - { - File.Delete(GetETagFilePath(userFileSystemPath)); - } - - /// - /// Gets path to the file in which ETag is stored based on the provided user file system path. - /// - /// Path to the file or folder to get the ETag file path. - /// Path to the file that contains ETag. - public static string GetETagFilePath(string userFileSystemPath) - { - // Get path relative to the virtual root. - string relativePath = userFileSystemPath.TrimEnd(Path.DirectorySeparatorChar).Substring( - Config.Settings.UserFileSystemRootPath.TrimEnd(Path.DirectorySeparatorChar).Length); - - string path = $"{Config.Settings.ServerDataFolderPath.TrimEnd(Path.DirectorySeparatorChar)}{relativePath}.etag"; - return path; - } - - /// - /// Returns true if the remote storage ETag and user file system ETags are equal. False - otherwise. - /// - /// User file system item. - /// Remote storage item info. - /// - /// ETag is updated on the server during every document update and is sent to client with a file. - /// During client->server update it is sent back to the remote storage together with a modified content. - /// This ensures the changes on the server are not overwritten if the document on the server is modified. - /// - public static async Task ETagEqualsAsync(string userFileSystemPath, FileSystemItemBasicInfo remoteStorageItem) - { - string remoteStorageETag = remoteStorageItem.ETag; - string userFileSystemETag = await ETag.GetETagAsync(userFileSystemPath); - - 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; - } - } -} diff --git a/ITHit.FileSystem.Samples.Common/FileBasicInfo.cs b/ITHit.FileSystem.Samples.Common/FileMetadata.cs similarity index 65% rename from ITHit.FileSystem.Samples.Common/FileBasicInfo.cs rename to ITHit.FileSystem.Samples.Common/FileMetadata.cs index 4c78f80..6931993 100644 --- a/ITHit.FileSystem.Samples.Common/FileBasicInfo.cs +++ b/ITHit.FileSystem.Samples.Common/FileMetadata.cs @@ -5,8 +5,8 @@ namespace ITHit.FileSystem.Samples.Common { - /// - public class FileBasicInfo : FileSystemItemBasicInfo, IFileBasicInfo + /// + public class FileMetadata : FileSystemItemMetadata, IFileMetadata { /// public long Length { get; set; } diff --git a/ITHit.FileSystem.Samples.Common/FileSystemItemBasicInfo.cs b/ITHit.FileSystem.Samples.Common/FileSystemItemMetadata.cs similarity index 75% rename from ITHit.FileSystem.Samples.Common/FileSystemItemBasicInfo.cs rename to ITHit.FileSystem.Samples.Common/FileSystemItemMetadata.cs index ee8ad23..f2583b1 100644 --- a/ITHit.FileSystem.Samples.Common/FileSystemItemBasicInfo.cs +++ b/ITHit.FileSystem.Samples.Common/FileSystemItemMetadata.cs @@ -10,7 +10,7 @@ namespace ITHit.FileSystem.Samples.Common /// Represents a basic information about the file or the folder in the user file system. /// In addition to properties provided by this class contains Etag property. /// - public class FileSystemItemBasicInfo : IFileSystemItemBasicInfo + public class FileSystemItemMetadata : IFileSystemItemMetadata { /// public string Name { get; set; } @@ -40,7 +40,14 @@ public class FileSystemItemBasicInfo : IFileSystemItemBasicInfo /// /// Indicates if the item is locked by another user in the remote storage. + /// This will set a read-only flag on this item. /// + /// + /// Note that the read-only flag is a convenience-only feature. + /// Typically the user will be notified by the application that the item can not be saved if + /// she/he tries to update this item . + /// Read-only flag does not protect this item from modifications. + /// public bool LockedByAnotherUser { get; set; } /// diff --git a/ITHit.FileSystem.Samples.Common/FolderBasicInfo.cs b/ITHit.FileSystem.Samples.Common/FolderMetadata.cs similarity index 55% rename from ITHit.FileSystem.Samples.Common/FolderBasicInfo.cs rename to ITHit.FileSystem.Samples.Common/FolderMetadata.cs index 1436720..9d59630 100644 --- a/ITHit.FileSystem.Samples.Common/FolderBasicInfo.cs +++ b/ITHit.FileSystem.Samples.Common/FolderMetadata.cs @@ -5,8 +5,8 @@ namespace ITHit.FileSystem.Samples.Common { - /// - public class FolderBasicInfo : FileSystemItemBasicInfo, IFolderBasicInfo + /// + public class FolderMetadata : FileSystemItemMetadata, IFolderMetadata { } diff --git a/ITHit.FileSystem.Samples.Common/IClientNotifications.cs b/ITHit.FileSystem.Samples.Common/IClientNotifications.cs new file mode 100644 index 0000000..62ee1e6 --- /dev/null +++ b/ITHit.FileSystem.Samples.Common/IClientNotifications.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace ITHit.FileSystem.Samples.Common +{ + /// + /// Represents client messages that must be to be sent to the remote storage, such as lock and unlock commands. + /// + /// + /// + /// Call methods of this class when the client needs to notify the remote storage + /// about the changes made in the user file system. + /// + /// + /// Methods of this interface call the method + /// and than the and methods. + /// + /// + public interface IClientNotifications + { + /// + /// Locks the file in the remote storage. + /// When this method is called the calls the method. + /// + /// Indicates automatic or manual lock. + /// + /// Thrown when a file can not be locked. For example when a lock-token file is blocked + /// from another thread, during update, lock and unlock operations. + /// + Task LockAsync(LockMode lockMode = LockMode.Manual); + + /// + /// Unlocks the file in the remote storage. + /// + Task UnlockAsync(); + } +} diff --git a/ITHit.FileSystem.Samples.Common/IServerNotifications.cs b/ITHit.FileSystem.Samples.Common/IServerNotifications.cs new file mode 100644 index 0000000..e83d970 --- /dev/null +++ b/ITHit.FileSystem.Samples.Common/IServerNotifications.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace ITHit.FileSystem.Samples.Common +{ + + /// + /// Represents messages sent from the remote storage to the virtual drive. + /// + /// + /// + /// Call methods of this class when the client receives messages from the remote storage + /// (for example via we sockets) about changes in the remote storage, such as files and folders creation, update, + /// deletion, move/rename, etc. + /// + /// + public interface IServerNotifications + { + /// + /// Creates a new file or folder on this virtual drive. + /// + /// Array of new files and folders. + /// + /// Call this method from your remote storage monitor when new items are created in the remote storage. + /// + /// Because of the on-demand loading, the folder specified in may not exist in the user file system. + /// In this case the new items will not be created and this method method will return 0. + /// + /// + /// Number of items created. + Task CreateAsync(FileSystemItemMetadata[] newItemsInfo); + + /// + /// Updates a file or folder on this virtual drive. + /// This method automatically hydrates and dehydrate files. + /// + /// + /// Call this method from your remote storage monitor when a file or folder is updated in the remote storage. + /// This method failes if the file or folder in user file system is modified (not in-sync with the remote storage). + /// + /// Because of the on-demand loading, the item specified in may not exist in the user file system. + /// In this case the item will not be updated and this method will return false. + /// + /// + /// New file or folder info. + /// True if the file was updated. False - otherwise, for example if the item does not exist in the user file system. + Task UpdateAsync(FileSystemItemMetadata itemInfo); + + /// + /// Deletes a file or folder from this virtual drive. + /// + /// + /// Call this method from your remote storage monitor when a file or folder is deleted in the remote storage. + /// + /// This method throws if the file or folder or any file or folder + /// in the folder hierarchy being deleted in user file system is modified (not in sync with the remote storage). + /// + /// + /// Because of the on-demand loading, the item specified in may not exist in the user file system. + /// In this case the item will not be deleted and this method will return false. + /// + /// + /// True if the file was deleted. False - otherwise, for example if the item does not exist in the user file system. + Task DeleteAsync(); + + /// + /// Moves a file or folder on this virtual drive. + /// + /// New path in user file system. + /// + /// Call this method from your remote storage monitor when a file or folder is moved in the remote storage. + /// + /// This method failes if the file or folder in user file system is modified (not in sync with the remote storage) + /// or if the target file exists. + /// + /// + /// Because of the on-demand loading, the item that is being moved may not exist in the user file system. + /// In this case the item will not be moved and this method will return false. + /// + /// + /// True if the file was moved. False - otherwise, for example if the item does not exist in the user file system. + Task MoveToAsync(string userFileSystemNewPath); + + /// + /// Sets or removes read-only attribute on files on this virtual drive. + /// + /// True to set the read-only attribute. False - to remove the read-only attribute. + //Task SetLockedByAnotherUserAsync(string userFileSystemPath, bool set); + } +} diff --git a/ITHit.FileSystem.Samples.Common/ITHit.FileSystem.Samples.Common.csproj b/ITHit.FileSystem.Samples.Common/ITHit.FileSystem.Samples.Common.csproj index 05f66f1..cf066fe 100644 --- a/ITHit.FileSystem.Samples.Common/ITHit.FileSystem.Samples.Common.csproj +++ b/ITHit.FileSystem.Samples.Common/ITHit.FileSystem.Samples.Common.csproj @@ -1,11 +1,13 @@ - + netstandard2.1 + IT Hit LTD. + IT Hit LTD. + IT Hit User File System + IT Hit User File System + Contains functionality common for all Virtual Drive samples, both for Windows and macOS. You will inherit your file and folder classes from IUserFile and iUserFolder defined in this module. - - - - + \ No newline at end of file diff --git a/ITHit.FileSystem.Samples.Common/IUserFile.cs b/ITHit.FileSystem.Samples.Common/IUserFile.cs deleted file mode 100644 index 954a484..0000000 --- a/ITHit.FileSystem.Samples.Common/IUserFile.cs +++ /dev/null @@ -1,13 +0,0 @@ -using ITHit.FileSystem; -using System.IO; -using System.Threading.Tasks; - -namespace ITHit.FileSystem.Samples.Common -{ - public interface IUserFile : IUserFileSystemItem - { - Task ReadAsync(long offset, long length); - Task UpdateAsync(IFileBasicInfo fileInfo, Stream content = null, ServerLockInfo lockInfo = null); - Task ValidateDataAsync(long offset, long length); - } -} \ No newline at end of file diff --git a/ITHit.FileSystem.Samples.Common/IUserFileSystemItem.cs b/ITHit.FileSystem.Samples.Common/IUserFileSystemItem.cs deleted file mode 100644 index bd73404..0000000 --- a/ITHit.FileSystem.Samples.Common/IUserFileSystemItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Threading.Tasks; - -namespace ITHit.FileSystem.Samples.Common -{ - public interface IUserFileSystemItem - { - Task DeleteAsync(); - Task LockAsync(); - Task MoveToAsync(string userFileSystemNewPath); - Task UnlockAsync(string lockToken); - } -} \ No newline at end of file diff --git a/ITHit.FileSystem.Samples.Common/IUserFolder.cs b/ITHit.FileSystem.Samples.Common/IUserFolder.cs deleted file mode 100644 index 299b0b9..0000000 --- a/ITHit.FileSystem.Samples.Common/IUserFolder.cs +++ /dev/null @@ -1,15 +0,0 @@ -using ITHit.FileSystem; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; - -namespace ITHit.FileSystem.Samples.Common -{ - public interface IUserFolder : IUserFileSystemItem - { - Task CreateFileAsync(IFileBasicInfo fileInfo, Stream content); - Task CreateFolderAsync(IFolderBasicInfo folderInfo); - Task> EnumerateChildrenAsync(string pattern); - Task UpdateAsync(IFolderBasicInfo folderInfo, ServerLockInfo lockInfo = null); - } -} \ No newline at end of file diff --git a/ITHit.FileSystem.Samples.Common/IVirtualDrive.cs b/ITHit.FileSystem.Samples.Common/IVirtualDrive.cs new file mode 100644 index 0000000..5ef3c45 --- /dev/null +++ b/ITHit.FileSystem.Samples.Common/IVirtualDrive.cs @@ -0,0 +1,83 @@ +using System.Threading.Tasks; + +namespace ITHit.FileSystem.Samples.Common +{ + /// + /// Represents a virtual drive. Processes OS file system calls, + /// synchronizes user file system to remote storage and back, + /// monitors files pinning and unpinning. + /// + /// + /// This class calls and interfaces returned from + /// the fectory method. It also provides methods for updating + /// the user file system in response to notifications sent by the remote storage. + /// + public interface IVirtualDrive + { + /// + /// Gets a file or a folder item corresponding to path in the user file system. + /// + /// + /// Path in user file system for which your will return a file or a folder. + /// + /// Type of the item to return. + /// Logger. + /// + /// + /// This is a factory method that returns files and folders in your remote storage. + /// From this method implementation you will return a file or a folder item that corresponds + /// to provided parameter and type of item - . + /// Your files must implement interface. Your folders must implement interface. + /// + /// + /// The Engine will then call and methods to get the + /// required information and pass it to the platform. + /// + /// + /// Note that this method may be called for files that does not exist in the user file system, + /// for example for files that were moved in user file system when the application was not running. + /// + /// + /// A file or a folder item that corresponds to the path in the user file system. + Task GetVirtualFileSystemItemAsync(string userFileSystemPath, FileSystemItemTypeEnum itemType, ILogger logger); + + /// + /// Current synchronization state of this virtual drive. + /// + SynchronizationState SyncState { get; } + + /// + /// Event, fired when synchronization state changes. + /// + event SyncronizationEvent SyncEvent; + + /// + /// Enables or disables full synchronization service and user file sytem monitor. + /// + /// Pass true to start synchronization. Pass false - to stop. + Task SetEnabledAsync(bool enabled); + + /// + /// Use object returned by this method to process messages sent by the remote storage, + /// such as files / folders creations, updates, deletes, etc. + /// + /// Path in the user file system to send a notification about. + /// Logger. + /// + /// Call methods of the object returned by this method when the remote storage is notitying the client + /// (via web sockets or other channel) about the changes made in the remote storage. + /// + IServerNotifications ServerNotifications(string userFileSystemPath, ILogger logger); + + /// + /// Use object returned by this method to send messages to the remote storage, + /// such as lock and unlock commands. + /// + /// Path in the user file system to send a notification about. + /// + /// Call methods of the object returned by this method when the client needs + /// to notify the remote storage about the changes made in user file system. + /// + IClientNotifications ClientNotifications(string userFileSystemPath, ILogger logger); + } +} \ No newline at end of file diff --git a/ITHit.FileSystem.Samples.Common/IVirtualFile.cs b/ITHit.FileSystem.Samples.Common/IVirtualFile.cs new file mode 100644 index 0000000..e06a6a1 --- /dev/null +++ b/ITHit.FileSystem.Samples.Common/IVirtualFile.cs @@ -0,0 +1,39 @@ +using ITHit.FileSystem; +using System.IO; +using System.Threading.Tasks; + +namespace ITHit.FileSystem.Samples.Common +{ + /// + /// Represents a file on a virtual drive. + /// + /// + /// You will implement this interface on file items. + /// + public interface IVirtualFile : IVirtualFileSystemItem + { + /// + /// Reads this file content from the remote storage. + /// + /// File content offset, in bytes, to start reading from. + /// Data length to read, in bytes. + /// Total file size, in bytes. + /// + /// You will use this parameter to return file content by + /// calling + /// + Task ReadAsync(long offset, long length, long fileSize, ITransferDataResultContext resultContext); + + /// + /// Updates this file in the remote storage. + /// + /// New information about the file, such as creation date, modification date, attributes, etc. + /// New file content or null if the file content is not modified. + /// The ETag to be sent to the remote storage as part of the update request to make sure the content is not overwritten. + /// Information about the lock. Null is passed if the item is not locked. + /// The new ETag returned from the remote storage. + Task UpdateAsync(IFileMetadata fileInfo, Stream content = null, string eTag = null, ServerLockInfo lockInfo = null); + + Task ValidateDataAsync(long offset, long length); + } +} \ No newline at end of file diff --git a/ITHit.FileSystem.Samples.Common/IVirtualFileSystemItem.cs b/ITHit.FileSystem.Samples.Common/IVirtualFileSystemItem.cs new file mode 100644 index 0000000..7949126 --- /dev/null +++ b/ITHit.FileSystem.Samples.Common/IVirtualFileSystemItem.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; + +namespace ITHit.FileSystem.Samples.Common +{ + /// + /// Represents a file or a folder on a virtul drive. + /// + public interface IVirtualFileSystemItem + { + /// + /// Renames or moves this file or folder to a new location in the remote storage. + /// + /// Target path of this file or folder in the user file system. + Task MoveToAsync(string userFileSystemNewPath); + + /// + /// Deletes this file or folder in the remote storage. + /// + Task DeleteAsync(); + } +} \ No newline at end of file diff --git a/ITHit.FileSystem.Samples.Common/IVirtualFolder.cs b/ITHit.FileSystem.Samples.Common/IVirtualFolder.cs new file mode 100644 index 0000000..d3de8c6 --- /dev/null +++ b/ITHit.FileSystem.Samples.Common/IVirtualFolder.cs @@ -0,0 +1,50 @@ +using ITHit.FileSystem; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace ITHit.FileSystem.Samples.Common +{ + /// + /// Represents a folder on a virtual drive. + /// + /// + /// You will implement this interface on folder items. + /// + public interface IVirtualFolder : IVirtualFileSystemItem + { + /// + /// Gets list of files and folders in this folder. + /// + /// Search pattern. + /// + /// List of files and folders located in this folder in the remote + /// storage that correstonds with the provided search pattern. + /// + Task> EnumerateChildrenAsync(string pattern); + + /// + /// Creates a new file in this folder in the remote storage. + /// + /// Information about the new file. + /// New file content. + /// The new ETag returned from the remote storage. + Task CreateFileAsync(IFileMetadata fileInfo, Stream content); + + /// + /// Creates a new folder in the remote storage. + /// + /// Information about the new folder. + /// The new ETag returned from the remote storage. + Task CreateFolderAsync(IFolderMetadata folderInfo); + + /// + /// Updates this folder info in the remote storage. + /// + /// New folder information. + /// The ETag to be sent to the remote storage as part of the update request to make sure the content is not overwritten. + /// Information about the lock. Null is passed if the item is not locked. + /// The new ETag returned from the remote storage. + Task UpdateAsync(IFolderMetadata folderInfo, string eTag = null, ServerLockInfo lockInfo = null); + } +} \ No newline at end of file diff --git a/ITHit.FileSystem.Samples.Common/IVirtualLock.cs b/ITHit.FileSystem.Samples.Common/IVirtualLock.cs new file mode 100644 index 0000000..254b16d --- /dev/null +++ b/ITHit.FileSystem.Samples.Common/IVirtualLock.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace ITHit.FileSystem.Samples.Common +{ + /// + /// Represents file of folder that can be locked in the remote storage. + /// Implementing this interface on an item will make the lock icon to appear in file manager. + /// + public interface IVirtualLock + { + /// + /// Locks this item in the remote storage. + /// + /// Lock info that conains lock-token returned by the remote storage. + /// + /// Lock your item in the remote storage in this method and receive the lock-token. + /// Return a new object with the being set from this function. + /// The will become available via and + /// methods lockInfo parameter when the item in the remote storage should be updated. + /// Supply the lock-token as part of your server update request. + /// + Task LockAsync(); + + /// + /// Unlocks this item in the remote storage. + /// + /// Lock token to unlock the item in the remote storage. + /// + /// Unlock your item in the remote storage in this method using the + /// parameter. + /// + Task UnlockAsync(string lockToken); + } +} diff --git a/ITHit.FileSystem.Samples.Common/Locks/LockMode.cs b/ITHit.FileSystem.Samples.Common/LockMode.cs similarity index 88% rename from ITHit.FileSystem.Samples.Common/Locks/LockMode.cs rename to ITHit.FileSystem.Samples.Common/LockMode.cs index 8e104e6..e60fa9f 100644 --- a/ITHit.FileSystem.Samples.Common/Locks/LockMode.cs +++ b/ITHit.FileSystem.Samples.Common/LockMode.cs @@ -7,7 +7,7 @@ namespace ITHit.FileSystem.Samples.Common /// /// Indicates how the file was locked and how to unlock the file. /// - internal enum LockMode + public enum LockMode { /// /// The file is not locked. @@ -15,7 +15,7 @@ internal enum LockMode None = 0, /// - /// The file is automatically locked on file handle open and automatically unlocked on file handle close. + /// The file is automatically locked on file handle open and should be automatically unlocked on file handle close. /// Auto = 1, diff --git a/ITHit.FileSystem.Samples.Common/Locks/Lock.cs b/ITHit.FileSystem.Samples.Common/Locks/Lock.cs deleted file mode 100644 index 2a1694c..0000000 --- a/ITHit.FileSystem.Samples.Common/Locks/Lock.cs +++ /dev/null @@ -1,294 +0,0 @@ -using ITHit.FileSystem; -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.CompilerServices; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; -using Windows.System; - -namespace ITHit.FileSystem.Samples.Common -{ - /// - /// Represents file lock. - /// - /// - /// The lock must be stored outside of CustomData, 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-mode file is stored separately from lock-token file because we must be able to read/write - /// the lock-mode while the lock-token file is blocked during file update, lock and unlock operations. - /// - internal class Lock : IDisposable - { - /// - /// Open lock-token file stream. Corresponds with . - /// - private FileStream lockTokenFileStream = null; - - /// - /// Path in user file system to which the corresponds. - /// - private string userFileSystemPath = null; - - /// - /// Logger. - /// - private ILogger logger = null; - - /// - /// Creates instance of this class. - /// - /// Path to the item in user file system to which this lock is applied. - /// Open stream to lock-token file. - /// Logger. - private Lock(string userFileSystemPath, FileStream lockTokenFileStream, ILogger logger) - { - this.userFileSystemPath = userFileSystemPath; - this.lockTokenFileStream = lockTokenFileStream; - this.logger = logger; - } - - /// - /// Sets lock mode associated with the file or folder. - /// - /// Path in the user file system. - /// Lock mode. - internal static async Task SetLockModeAsync(string userFileSystemPath, LockMode lockMode) - { - if (lockMode == LockMode.None) - { - File.Delete(GetLockModeFilePath(userFileSystemPath)); - return; - } - - string lockModeFilePath = GetLockModeFilePath(userFileSystemPath); - 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. - /// - /// Path in the user file system. - /// Lock mode or if the file is not locked. - internal static async Task GetLockModeAsync(string userFileSystemPath) - { - string lockModeFilePath = GetLockModeFilePath(userFileSystemPath); - try - { - await using (FileStream fileStream = File.Open(lockModeFilePath, FileMode.Open, FileAccess.Read, FileShare.Delete)) - { - return (LockMode)fileStream.ReadByte(); - } - } - catch (FileNotFoundException) - { - return LockMode.None; - } - } - - /// - /// Returns true if the file or folder is locked. Returns true if the lock, update or unlock - /// operation started but have not completed yet. - /// - /// Path in the user file system. - /// True if the file is locked, false otherwise. - internal static async Task IsLockedAsync(string userFileSystemPath) - { - string lockModeFilePath = GetLockModeFilePath(userFileSystemPath); - return File.Exists(lockModeFilePath); - } - - /// - /// Gets existing file lock or creates a new lock. - /// - /// Path to the item in user file system to be locked. - /// - /// Indicates if a new lock should be created or existing lock file to be opened. - /// Allowed options are , and . - /// - /// - /// Indicates automatic or manual lock. Saved only for new files, ignored when existing lock is opened. - /// - /// - /// Thrown when a file can not be locked. For example when a lock-token file is blocked - /// from another thread, during update, lock and unlock operations. - /// - /// File lock. - internal static async Task LockAsync(string userFileSystemPath, FileMode lockFileOpenMode, LockMode lockMode, ILogger logger) - { - if ((lockFileOpenMode != FileMode.OpenOrCreate) - && (lockFileOpenMode != FileMode.Open) - && (lockFileOpenMode != FileMode.CreateNew)) - { - throw new ArgumentOutOfRangeException("openMode", $"Must be {FileMode.OpenOrCreate} or {FileMode.Open} or {FileMode.CreateNew}"); - } - - string lockFilePath = GetLockTokenFilePath(userFileSystemPath); - - // Create lock-token file or open existing lock-token file. - FileStream lockTokenFileStream = null; - try - { - // Blocks lock-token file for reading and writing, - // so other threads can not start file update, lock and unlock file until this lock request is completed - // and lock-toked is saved in a lock-token file. - // Lock-mode file can still be updated directly, we do not want to block from changes. - lockTokenFileStream = File.Open(lockFilePath, lockFileOpenMode, FileAccess.ReadWrite, FileShare.Delete); - } - catch (IOException ex) - { - // Possibly blocked in another thread. This is a normal behaviour. - throw new ClientLockFailedException("Can't open or create lock-token file.", ex); - } - - // Save lock mode for new locks. - Lock lockInfo = null; - try - { - lockInfo = new Lock(userFileSystemPath, lockTokenFileStream, logger); - if (lockInfo.IsNew()) - { - await SetLockModeAsync(userFileSystemPath, lockMode); - } - } - catch(Exception ex) - { - // Something went wrong, cleanup. - try - { - lockInfo.Unlock(); - lockInfo.Dispose(); - } - catch { } - throw new ClientLockFailedException("Can't set lock-mode.", ex); - } - - return lockInfo; - } - - /// - /// Returns true if the lock was created and contains no data. - /// Returns false if the lock contains information about the lock. - /// - internal bool IsNew() - { - return lockTokenFileStream.Length == 0; - } - - internal async Task SetLockInfoAsync(ServerLockInfo lockInfo) - { - lockTokenFileStream.Seek(0, SeekOrigin.Begin); - await JsonSerializer.SerializeAsync(lockTokenFileStream, lockInfo); - lockTokenFileStream.SetLength(lockTokenFileStream.Position); - } - - internal async Task GetLockInfoAsync() - { - lockTokenFileStream.Seek(0, SeekOrigin.Begin); - return await JsonSerializer.DeserializeAsync(lockTokenFileStream); - } - - /// - /// Gets path to the file in which lock mode is stored based on the provided user file system path. - /// - /// Path to the file or folder to get the lock mode file path. - /// Path to the file that contains the lock mode. - private static string GetLockModeFilePath(string userFileSystemPath) - { - - // Get path relative to the virtual root. - string relativePath = userFileSystemPath.TrimEnd(Path.DirectorySeparatorChar).Substring( - Config.Settings.UserFileSystemRootPath.TrimEnd(Path.DirectorySeparatorChar).Length); - string path = $"{Config.Settings.ServerDataFolderPath.TrimEnd(Path.DirectorySeparatorChar)}{relativePath}.lockmode"; - return path; - } - - /// - /// Gets path to the file in which lock token is stored based on the provided user file system path. - /// - /// Path to the file or folder to get the lock token file path. - /// Path to the file that contains the lock token. - private static string GetLockTokenFilePath(string userFileSystemPath) - { - // Get path relative to the virtual root. - string relativePath = userFileSystemPath.TrimEnd(Path.DirectorySeparatorChar).Substring( - Config.Settings.UserFileSystemRootPath.TrimEnd(Path.DirectorySeparatorChar).Length); - - string path = $"{Config.Settings.ServerDataFolderPath.TrimEnd(Path.DirectorySeparatorChar)}{relativePath}.locktoken"; - return path; - } - - /// - /// deletes lock-token and lock-mode files. - /// - internal void Unlock() - { - try - { - string lockModeFilePath = GetLockModeFilePath(userFileSystemPath); - File.Delete(lockModeFilePath); - } - catch(Exception ex) - { - logger.LogError("Failed to delete lock-mode file.", userFileSystemPath, null, ex); - } - - try - { - File.Delete(lockTokenFileStream.Name); - lockTokenFileStream = null; - } - catch (Exception ex) - { - logger.LogError("Failed to delete lock-token file.", userFileSystemPath, null, ex); - } - } - - private bool disposedValue = false; // To detect redundant calls - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - // TODO: dispose managed state (managed objects). - - // Just close the stream, do not delete lock-mode and lock-token files. - if (lockTokenFileStream != null) - { - lockTokenFileStream.Close(); - } - } - - // 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. - // ~Lock2() - // { - // // 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/ITHit.FileSystem.Samples.Common/Locks/ServerLockInfo.cs b/ITHit.FileSystem.Samples.Common/ServerLockInfo.cs similarity index 62% rename from ITHit.FileSystem.Samples.Common/Locks/ServerLockInfo.cs rename to ITHit.FileSystem.Samples.Common/ServerLockInfo.cs index f12d195..c6f7f31 100644 --- a/ITHit.FileSystem.Samples.Common/Locks/ServerLockInfo.cs +++ b/ITHit.FileSystem.Samples.Common/ServerLockInfo.cs @@ -29,12 +29,17 @@ public class ServerLockInfo /// public bool Exclusive { get; set; } + /// + /// Gets this lock info as a set of properties that can be visually displayed in file manager. + /// + /// Lock icon path that will be displayed in file manager. + /// List of properties. public IEnumerable GetLockProperties(string lockIconPath) { List lockProps = new List(); - lockProps.Add(new FileSystemItemPropertyData(2, Owner, lockIconPath)); - lockProps.Add(new FileSystemItemPropertyData(3, Exclusive ? "Exclusive" : "Shared")); - lockProps.Add(new FileSystemItemPropertyData(4, LockExpirationDateUtc.ToString())); + 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.ToString())); return lockProps; } } diff --git a/ITHit.FileSystem.Samples.Common/Settings.cs b/ITHit.FileSystem.Samples.Common/Settings.cs index 744114c..a775e76 100644 --- a/ITHit.FileSystem.Samples.Common/Settings.cs +++ b/ITHit.FileSystem.Samples.Common/Settings.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Configuration; -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -9,17 +8,6 @@ namespace ITHit.FileSystem.Samples.Common { - /// - /// Glogal object for accessing configuration. - /// - public static class Config - { - /// - /// Application settings. - /// - public static Settings Settings { get; set; } - } - /// /// Strongly binded project settings. /// @@ -29,7 +17,7 @@ public class Settings /// Unique ID of this application. /// /// - /// If you must to run more than one instance of this application side-by-side on same client machine + /// If you must to run more than one instance of this application side-by-side on the same client machine /// (aka Corporate Drive and Personal Drive) set unique ID for each instance. /// public string AppID { get; set; } diff --git a/ITHit.FileSystem.Samples.Common/SynchronizationState.cs b/ITHit.FileSystem.Samples.Common/SynchronizationState.cs new file mode 100644 index 0000000..41a7ad0 --- /dev/null +++ b/ITHit.FileSystem.Samples.Common/SynchronizationState.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ITHit.FileSystem.Samples.Common +{ + /// + /// Synchronization state. + /// + public enum SynchronizationState + { + /// + /// Synchronization is enabled. + /// + Enabled, + + /// + /// Synchronizing data between user file system and remote storage. + /// + Synchronizing, + + /// + /// Waiting for the next synchronization. + /// + Idle, + + /// + /// Synchronization disabled. + /// + Disabled + } + + /// + /// Synchronization state change event argument. + /// + public class SynchEventArgs : EventArgs + { + /// + /// New state of the service. + /// + public SynchronizationState NewState; + + /// + /// Creates instance of this class. + /// + /// New synchronization state. + public SynchEventArgs(SynchronizationState newState) + { + this.NewState = newState; + } + } + + /// + /// Synchronization state change event delegate. + /// + /// instance firing this event. + /// New synchronization state. + public delegate void SyncronizationEvent(object sender, SynchEventArgs synchEventArgs); +} diff --git a/ITHit.FileSystem.Samples.Common/Syncronyzation/RemoteStorageRawItem.cs b/ITHit.FileSystem.Samples.Common/Syncronyzation/RemoteStorageRawItem.cs deleted file mode 100644 index 4ef9f01..0000000 --- a/ITHit.FileSystem.Samples.Common/Syncronyzation/RemoteStorageRawItem.cs +++ /dev/null @@ -1,472 +0,0 @@ -using ITHit.FileSystem; -using ITHit.FileSystem.Windows; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using Windows.ApplicationModel.Activation; - -namespace ITHit.FileSystem.Samples.Common.Syncronyzation -{ - /// - /// Provides methods for synching the user file system to the remote storage. - /// Creates, updates, deletes, moves, locks and unloacks files and folders based on the info from user file system. - /// This class also іets status icons in file manager. - /// - /// In most cases you can use this class in your project without any changes. - public class RemoteStorageRawItem - { - /// - /// Path to the file or folder in the user file system. - /// - private string userFileSystemPath; - - /// - /// Virtual drive. - /// - private VirtualDriveBase virtualDrive; - - /// - /// Logger. - /// - private ILogger logger; - - /// - /// Creates instance of this class. - /// - /// File or folder path in the user file system. - /// Logger. - internal RemoteStorageRawItem(string userFileSystemPath, VirtualDriveBase virtualDrive, ILogger logger) - { - if (string.IsNullOrEmpty(userFileSystemPath)) - { - throw new ArgumentNullException("userFileSystemPath"); - } - if (logger == null) - { - throw new ArgumentNullException("logger"); - } - - this.userFileSystemPath = userFileSystemPath; - this.virtualDrive = virtualDrive; - this.logger = logger; - } - - /// - /// Creates a new file or folder in the remote storage. - /// - /// Path to the file or folder in the user file system to be created in the remote storage. - /// Logger - public static async Task CreateAsync(string userFileSystemNewItemPath, VirtualDriveBase userEngine, ILogger logger) - { - try - { - logger.LogMessage("Creating item in the remote storage", userFileSystemNewItemPath); - await new RemoteStorageRawItem(userFileSystemNewItemPath, userEngine, logger).CreateOrUpdateAsync(FileMode.CreateNew); - await new UserFileSystemRawItem(userFileSystemNewItemPath).ClearStateAsync(); - logger.LogMessage("Created in the remote storage succesefully", userFileSystemNewItemPath); - } - catch (Exception ex) - { - await new UserFileSystemRawItem(userFileSystemNewItemPath).SetUploadErrorStateAsync(ex); - - // Rethrow the exception preserving stack trace of the original exception. - System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw(); - } - } - - /// - /// Sends contending to the remote storage if the item is modified. - /// If Auto-locking is enabled, automatically locks the file if not locked. Unlocks the file after the update if auto-locked. - /// - public async Task UpdateAsync() - { - Lock fileLock = null; - try - { - if (!PlaceholderItem.GetItem(userFileSystemPath).GetInSync()) - { - - // Lock file if auto-locking is enabled. - if (Config.Settings.AutoLock) - { - // Get existing lock or create a new lock. - fileLock = await LockAsync(FileMode.OpenOrCreate, LockMode.Auto); - } - - ServerLockInfo lockInfo = fileLock!=null ? await fileLock.GetLockInfoAsync() : null; - - // Update item in remote storage. - logger.LogMessage("Sending to remote storage", userFileSystemPath); - await CreateOrUpdateAsync(FileMode.Open, lockInfo); - //await new UserFileSystemRawItem(userFileSystemPath).ClearStateAsync(); - logger.LogMessage("Sent to remote storage succesefully", userFileSystemPath); - } - - // Unlock if auto-locked. - if (await Lock.GetLockModeAsync(userFileSystemPath) == LockMode.Auto) - { - if (fileLock == null) - { - // Get existing lock. - fileLock = await Lock.LockAsync(userFileSystemPath, FileMode.Open, LockMode.None, logger); - } - await UnlockAsync(fileLock); - } - } - catch(ClientLockFailedException ex) - { - // Failed to lock file. Possibly blocked from another thread. This is a normal behaviour. - logger.LogMessage(ex.Message, userFileSystemPath); - } - catch (Exception ex) - { - // Some error when locking, updating or unlocking occured. - await new UserFileSystemRawItem(userFileSystemPath).SetUploadErrorStateAsync(ex); - - // Rethrow the exception preserving stack trace of the original exception. - System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw(); - } - finally - { - if (fileLock != null) - { - fileLock.Dispose(); - } - } - } - - /// - /// Creates or updates the item in the remote storage. - /// - /// - /// Indicates if the file should created or updated. - /// Supported modes are and - /// - /// Information about the lock. Pass null if the item is not locked. - private async Task CreateOrUpdateAsync(FileMode mode, ServerLockInfo lockInfo = null) - { - if ((mode != FileMode.CreateNew) && (mode != FileMode.Open)) - { - throw new ArgumentOutOfRangeException("mode", $"Must be {FileMode.CreateNew} or {FileMode.Open}"); - } - - FileSystemInfo userFileSystemItem = FsPath.GetFileSystemItem(userFileSystemPath); - - using (WindowsFileSystemItem userFileSystemWinItem = WindowsFileSystemItem.OpenReadAttributes(userFileSystemPath, FileMode.Open, FileShare.Read)) - //await using (FileStream userFileSystemStream = userFileSystemFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) - { - // Create the new file/folder in the remote storage only if the file/folder in the user file system was not moved. - // If the file is moved in user file system, move must first be syched to remote storage. - if ((mode == FileMode.CreateNew) && PlaceholderItem.GetItem(userFileSystemPath).IsMoved()) - { - string originalPath = PlaceholderItem.GetItem(userFileSystemPath).GetOriginalPath(); - throw new ConflictException(Modified.Client, $"The item was moved. Original path: {originalPath}"); - } - - // Ensures LastWriteTimeUtc is in sync with file content after Open() was called. - userFileSystemItem.Refresh(); - - IFileSystemItemBasicInfo info = GetBasicInfo(userFileSystemItem); - - // Update remote storage file. - FileStream userFileSystemStream = null; - try - { - string eTag = null; - if (FsPath.IsFile(userFileSystemPath)) - { - // File is marked as not in-sync when updated OR moved. - // Opening a file for reading triggers hydration, make sure to open only if content is modified. - if (PlaceholderFile.GetFileDataSizeInfo(userFileSystemWinItem.SafeHandle).ModifiedDataSize > 0) - { - //userFileSystemStream = new FileStream(userFileSystemWinItem.SafeHandle, FileAccess.Read); - userFileSystemStream = ((FileInfo)userFileSystemItem).Open(FileMode.Open, FileAccess.Read, FileShare.Read); - } - if (mode == FileMode.CreateNew) - { - string userFileSystemParentPath = Path.GetDirectoryName(userFileSystemPath); - IUserFolder userFolder = await virtualDrive.GetItemAsync(userFileSystemParentPath); - eTag = await userFolder.CreateFileAsync((IFileBasicInfo)info, userFileSystemStream); - } - else - { - IUserFile userFile = await virtualDrive.GetItemAsync(userFileSystemPath); - eTag = await userFile.UpdateAsync((IFileBasicInfo)info, userFileSystemStream, lockInfo); - } - } - else - { - if (mode == FileMode.CreateNew) - { - string userFileSystemParentPath = Path.GetDirectoryName(userFileSystemPath); - IUserFolder userFolder = await virtualDrive.GetItemAsync(userFileSystemParentPath); - eTag = await userFolder.CreateFolderAsync((IFolderBasicInfo)info); - } - else - { - IUserFolder userFolder = await virtualDrive.GetItemAsync(userFileSystemPath); - eTag = await userFolder.UpdateAsync((IFolderBasicInfo)info, lockInfo); - } - } - await ETag.SetETagAsync(userFileSystemPath, eTag); - if (mode == FileMode.CreateNew) - { - PlaceholderItem.GetItem(userFileSystemPath).SetOriginalPath(userFileSystemPath); - } - } - finally - { - if (userFileSystemStream != null) - { - userFileSystemStream.Close(); - } - } - - PlaceholderItem.SetInSync(userFileSystemWinItem.SafeHandle, true); - } - } - - /// - /// Gets file system item info from the user file system item. - /// Removes Pinned, Unpinned and Offline flags from attributes. - /// - /// User file system item info. - /// object. - private static IFileSystemItemBasicInfo GetBasicInfo(FileSystemInfo userFileSystemItem) - { - FileSystemItemBasicInfo itemInfo; - - if (userFileSystemItem is FileInfo) - { - itemInfo = new FileBasicInfo(); - } - else - { - itemInfo = new FolderBasicInfo(); - } - - // Remove Pinned, unpinned and offline flags, - // so they do not occasionally go into the remote storage. - FileAttributes flags = userFileSystemItem.Attributes - & (FileAttributes)~FileAttributesExt.Pinned - & (FileAttributes)~FileAttributesExt.Unpinned - & (FileAttributes)~FileAttributesExt.Offline; - - itemInfo.Name = userFileSystemItem.Name; - itemInfo.Attributes = flags; - itemInfo.CreationTime = userFileSystemItem.CreationTime; - itemInfo.LastWriteTime = userFileSystemItem.LastWriteTime; - itemInfo.LastAccessTime = userFileSystemItem.LastAccessTime; - itemInfo.ChangeTime = userFileSystemItem.LastWriteTime; - - itemInfo.CustomData = PlaceholderItem.GetItem(userFileSystemItem.FullName).GetCustomData(); - - if (userFileSystemItem is FileInfo) - { - ((FileBasicInfo)itemInfo).Length = ((FileInfo)userFileSystemItem).Length; - }; - - return itemInfo; - } - - internal async Task MoveToAsync(string userFileSystemNewPath, IConfirmationResultContext resultContext = null) - { - string userFileSystemOldPath = userFileSystemPath; - - try - { - bool? inSync = null; - bool updateTargetOnSuccess = false; - string eTag = null; - try - { - if (!FsPath.IsRecycleBin(userFileSystemNewPath) // When a file is deleted, it is moved to a Recycle Bin. - && !FsPath.AvoidSync(userFileSystemOldPath) && !FsPath.AvoidSync(userFileSystemNewPath)) - { - logger.LogMessage("Moving item in remote storage", userFileSystemOldPath, userFileSystemNewPath); - - // Read In-Sync state before move and set after move - if (FsPath.Exists(userFileSystemOldPath)) - { - inSync = PlaceholderItem.GetItem(userFileSystemOldPath).GetInSync(); - } - - eTag = await ETag.GetETagAsync(userFileSystemOldPath); - ETag.DeleteETag(userFileSystemOldPath); - - IUserFileSystemItem userFileSystemItemOld = await virtualDrive.GetItemAsync(userFileSystemOldPath); - await userFileSystemItemOld.MoveToAsync(userFileSystemNewPath); - updateTargetOnSuccess = true; - logger.LogMessage("Moved succesefully in remote storage", userFileSystemOldPath, userFileSystemNewPath); - } - } - finally - { - if (resultContext != null) - { - resultContext.ReturnConfirmationResult(); - } - - // This check is just to avoid extra error in the log. - if (FsPath.Exists(userFileSystemNewPath)) - { - // Open file to preven reads and changes between GetFileDataSizeInfo() call and SetInSync() call. - using (WindowsFileSystemItem userFileSystemWinItem = WindowsFileSystemItem.OpenReadAttributes(userFileSystemNewPath, FileMode.Open, FileShare.None)) - { - if ((eTag != null) && PlaceholderItem.IsPlaceholder(userFileSystemNewPath)) - { - await ETag.SetETagAsync(userFileSystemNewPath, eTag); - } - - // If a file with content is deleted it is moved to a recycle bin and converted - // to a regular file, so placeholder features are not available on it, checking if a file is a placeholder. - if (updateTargetOnSuccess && PlaceholderItem.IsPlaceholder(userFileSystemNewPath)) - { - PlaceholderItem placeholderNew = PlaceholderItem.GetItem(userFileSystemNewPath); - - // Update OriginalPath, so the item does not appear as moved. - placeholderNew.SetOriginalPath(userFileSystemNewPath); - - if (inSync != null) - { - placeholderNew.SetInSync(inSync.Value); - } - else if ((placeholderNew is PlaceholderFile) && ((PlaceholderFile)placeholderNew).GetFileDataSizeInfo().ModifiedDataSize == 0) - { - placeholderNew.SetInSync(true); - } - await new UserFileSystemRawItem(userFileSystemNewPath).ClearStateAsync(); - } - } - } - } - } - catch (Exception ex) - { - string userFileSystemPath = FsPath.Exists(userFileSystemNewPath) ? userFileSystemNewPath : userFileSystemOldPath; - await new UserFileSystemRawItem(userFileSystemPath).SetUploadErrorStateAsync(ex); - - // Rethrow the exception preserving stack trace of the original exception. - System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw(); - } - } - - /// - /// Deletes file or folder in the remote storage. - /// - internal async Task DeleteAsync() - { - if (!FsPath.AvoidSync(userFileSystemPath)) - { - IUserFileSystemItem userFileSystemItem = await virtualDrive.GetItemAsync(userFileSystemPath); - await userFileSystemItem.DeleteAsync(); - - ETag.DeleteETag(userFileSystemPath); - - return true; - } - - return false; - } - - /// - /// Locks the file in the remote storage. - /// - /// - /// Indicates automatic or manual lock. - /// - /// - /// Thrown when a file can not be locked. For example when a lock-token file is blocked - /// from another thread, during update, lock and unlock operations. - /// - internal async Task LockAsync(LockMode lockMode = LockMode.Manual) - { - using (await LockAsync(FileMode.CreateNew, lockMode)) - { - } - } - - /// - /// Locks the file in the remote storage or gets existing lock. - /// - /// - /// Indicates if a new lock should be created or existing lock file to be opened. - /// Allowed options are , and . - /// - /// - /// Indicates automatic or manual lock. Saved only for new files, ignored when existing lock is opened. - /// - /// - /// Thrown when a file can not be locked. For example when a lock-token file is blocked - /// from another thread, during update, lock and unlock operations. - /// - /// File lock. - private async Task LockAsync(FileMode lockFileOpenMode, LockMode lockMode = LockMode.Manual) - { - // Get existing lock or create a new lock. - Lock fileLock = await Lock.LockAsync(userFileSystemPath, lockFileOpenMode, lockMode, logger); - - if (fileLock.IsNew()) - { - logger.LogMessage("Locking", userFileSystemPath); - - // Set pending icon. - await new UserFileSystemRawItem(userFileSystemPath).SetLockPendingIconAsync(true); - - // Lock file in remote storage. - IUserFileSystemItem userFileSystemItem = await virtualDrive.GetItemAsync(userFileSystemPath); - ServerLockInfo lockInfo = await userFileSystemItem.LockAsync(); - - // Save lock-token in lock-file. - await fileLock.SetLockInfoAsync(lockInfo); - - // Set locked icon and all lock properties. - await new UserFileSystemRawItem(userFileSystemPath).SetLockInfoAsync(lockInfo); - - logger.LogMessage($"Locked succesefully. Mode: {lockMode}", userFileSystemPath); - } - - return fileLock; - } - - /// - /// Unlocks the file in the remote storage. - /// - internal async Task UnlockAsync() - { - using (Lock fileLock = await LockAsync(FileMode.Open)) - { - await UnlockAsync(fileLock); - } - } - - /// - /// Unlocks the file in the remote storage using existing . - /// - /// File lock. - private async Task UnlockAsync(Lock fileLock) - { - logger.LogMessage("Unlocking", userFileSystemPath); - - // Set pending icon. - await new UserFileSystemRawItem(userFileSystemPath).SetLockPendingIconAsync(true); - - // Read lock-token from lock-file. - string lockToken = (await fileLock.GetLockInfoAsync()).LockToken; - - // Ulock file in remote storage. - IUserFileSystemItem userFileSystemItem = await virtualDrive.GetItemAsync(userFileSystemPath); - await userFileSystemItem.UnlockAsync(lockToken); - - // Remove lock icon. - await new UserFileSystemRawItem(userFileSystemPath).SetLockInfoAsync(null); - - // Operation completed succesefully, delete lock files. - fileLock.Unlock(); - - logger.LogMessage("Unlocked succesefully", userFileSystemPath); - } - } -} \ No newline at end of file diff --git a/ITHit.FileSystem.Samples.Common/VirtualDriveBase.cs b/ITHit.FileSystem.Samples.Common/VirtualDriveBase.cs deleted file mode 100644 index 7689f0b..0000000 --- a/ITHit.FileSystem.Samples.Common/VirtualDriveBase.cs +++ /dev/null @@ -1,146 +0,0 @@ -using ITHit.FileSystem; -using log4net; -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; -using ITHit.FileSystem.Samples.Common.Syncronyzation; - -namespace ITHit.FileSystem.Samples.Common -{ - /// - /// Represents a virtual drive. Processes OS file system calls, - /// synchronizes user file system to remote storage and back, - /// monitors files pinning and unpinning. - /// - /// - /// This class calls and interfaces. - /// - public abstract class VirtualDriveBase : IDisposable - { - /// - /// Processes file system calls, implements on-demand folders listing - /// and initial on-demand file content transfer from remote storage to client. - /// - private VfsEngine engine; - - /// - /// Monitors pinned and unpinned attributes in user file system. - /// - private UserFileSystemMonitor userFileSystemMonitor; - - /// - /// Performs complete synchronyzation of the folders and files that are already synched to user file system. - /// - public FullSyncService SyncService; - - /// - /// Creates instance of this class. - /// - /// A license string. - /// - /// A root folder of your user file system. Your file system tree will be located under this folder. - /// - /// Log4net logger. - /// Full synchronization interval in milliseconds. - public VirtualDriveBase(string license, string userFileSystemRootPath, ILog log, double syncIntervalMs) - { - engine = new VfsEngine(license, userFileSystemRootPath, this, log); - SyncService = new FullSyncService(syncIntervalMs, userFileSystemRootPath, this, log); - userFileSystemMonitor = new UserFileSystemMonitor(userFileSystemRootPath, this, log); - } - - /// - /// Gets file or folder object corresponding to path in user file system. - /// - /// - /// Path in user file system for which your will return a file or a folder. - /// - /// - /// - /// This is a factory method that returns files and folders in your user file system. - /// In your implementation you will return file or folder object that corresponds to provided parameter. - /// Your file must implement interface. Your folder must implement interface. - /// - /// - /// The will then call and methods to get the - /// required information and pass it to the platform. - /// - /// - /// Note that this method may be called for files that does not exist in the user file system, - /// for example when a file handle is closed after the file has been deleted. - /// - /// - /// File or folder object that corresponds to the path in user file system. - public abstract Task GetUserFileSystemItemAsync(string userFileSystemPath); - - /// - /// This is a strongly typed variant of GetUserFileSystemItemAsync() method for internal use. - /// - /// This is either or or . - /// Path in user file system for which your will return a file or a folder. - /// File or folder object that corresponds to the path in user file system. - internal async Task GetItemAsync(string userFileSystemPath) where T : class, IUserFileSystemItem - { - IUserFileSystemItem userItem = await GetUserFileSystemItemAsync(userFileSystemPath); - if (userItem as T == null) - { - throw new NotImplementedException($"{typeof(T).Name}"); - } - - return userItem as T; - } - - /// - /// Starts processing OS file system calls, starts user file system to remote storage synchronization - /// and remote storage to user file system synchronization - /// as well as starts monitoring for pinned/unpinned files. - /// - public async Task StartAsync() - { - // Start processing OS file system calls. - engine.ChangesProcessingEnabled = true; - await engine.StartAsync(); - - // Start periodical synchronyzation between client and server, - // in case any changes are lost because the client or the server were unavailable. - await SyncService.StartAsync(); - - // Start monitoring pinned/unpinned attributes and files/folders creation in user file system. - await userFileSystemMonitor.StartAsync(); - } - - private bool disposedValue; - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - engine.Dispose(); - SyncService.Dispose(); - userFileSystemMonitor.Dispose(); - } - - // TODO: free unmanaged resources (unmanaged objects) and override finalizer - // TODO: set large fields to null - disposedValue = true; - } - } - - // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources - // ~UserEngine() - // { - // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - // Dispose(disposing: false); - // } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - } -} diff --git a/UserFileSystemSamples.sln b/UserFileSystemSamples.sln index cdc2b32..6c8caac 100644 --- a/UserFileSystemSamples.sln +++ b/UserFileSystemSamples.sln @@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebDAVDrive.UI", "WebDAVDri EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VirtualFileSystem", "VirtualFileSystem\VirtualFileSystem.csproj", "{F83D05C4-EF6D-4BD9-99C4-B404443437F0}" EndProject +Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "WebDAVDrive.Setup", "WebDAVDrive.Setup\WebDAVDrive.Setup.wixproj", "{2EDB1552-04DD-4974-8199-A8A453916204}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/VirtualFileSystem/AppSettings.cs b/VirtualFileSystem/AppSettings.cs index 9268baf..acc96ea 100644 --- a/VirtualFileSystem/AppSettings.cs +++ b/VirtualFileSystem/AppSettings.cs @@ -87,7 +87,7 @@ public static AppSettings ReadSettings(this IConfiguration configuration) // Folder where eTags and file locks are stored. string localApplicationDataFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - settings.ServerDataFolderPath = Path.Combine(localApplicationDataFolderPath, settings.ProductName, settings.UserFileSystemRootPath.Replace(":", ""), "ServerData"); + settings.ServerDataFolderPath = Path.Combine(localApplicationDataFolderPath, settings.AppID, settings.UserFileSystemRootPath.Replace(":", ""), "ServerData"); return settings; } diff --git a/VirtualFileSystem/Mapping.cs b/VirtualFileSystem/Mapping.cs index a536a9d..887fc11 100644 --- a/VirtualFileSystem/Mapping.cs +++ b/VirtualFileSystem/Mapping.cs @@ -51,17 +51,17 @@ public static string ReverseMapPath(string remoteStorageUri) /// /// Remote storage item info. /// User file system item info. - public static FileSystemItemBasicInfo GetUserFileSysteItemBasicInfo(FileSystemInfo remoteStorageItem) + public static FileSystemItemMetadata GetUserFileSysteItemMetadata(FileSystemInfo remoteStorageItem) { - FileSystemItemBasicInfo userFileSystemItem; + FileSystemItemMetadata userFileSystemItem; if (remoteStorageItem is FileInfo) { - userFileSystemItem = new FileBasicInfo(); + userFileSystemItem = new FileMetadata(); } else { - userFileSystemItem = new FolderBasicInfo(); + userFileSystemItem = new FolderMetadata(); } userFileSystemItem.Name = remoteStorageItem.Name; @@ -76,7 +76,7 @@ public static FileSystemItemBasicInfo GetUserFileSysteItemBasicInfo(FileSystemIn // This will make sure the changes on the server is not overwritten. // // In this sample, for the sake of simplicity, we use file last write time instead of ETag. - userFileSystemItem.ETag = remoteStorageItem.LastWriteTime.ToBinary().ToString(); + userFileSystemItem.ETag = userFileSystemItem.LastWriteTime.ToUniversalTime().ToString("o"); // If the item is locked by another user, set the LockedByAnotherUser to true. // Here we just use the read-only attribute from remote storage item for demo purposes. @@ -84,7 +84,7 @@ public static FileSystemItemBasicInfo GetUserFileSysteItemBasicInfo(FileSystemIn if (remoteStorageItem is FileInfo) { - ((FileBasicInfo)userFileSystemItem).Length = ((FileInfo)remoteStorageItem).Length; + ((FileMetadata)userFileSystemItem).Length = ((FileInfo)remoteStorageItem).Length; }; // Set custom columns to be displayed in file manager. @@ -99,7 +99,7 @@ public static FileSystemItemBasicInfo GetUserFileSysteItemBasicInfo(FileSystemIn Owner = "User Name", Exclusive = true, LockExpirationDateUtc = DateTimeOffset.Now.AddMinutes(30) - }.GetLockProperties(Path.Combine(Config.Settings.IconsFolderPath, "LockedByAnotherUser.ico")) + }.GetLockProperties(Path.Combine(Program.Settings.IconsFolderPath, "LockedByAnotherUser.ico")) ); } userFileSystemItem.CustomProperties = customProps; diff --git a/VirtualFileSystem/Program.cs b/VirtualFileSystem/Program.cs index d1a634a..90793c8 100644 --- a/VirtualFileSystem/Program.cs +++ b/VirtualFileSystem/Program.cs @@ -8,11 +8,11 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; -using VirtualFileSystem.Syncronyzation; using Windows.Storage; using Windows.Storage.Provider; using ITHit.FileSystem.Samples.Common; +using ITHit.FileSystem.Samples.Common.Windows; namespace VirtualFileSystem { @@ -33,19 +33,13 @@ class Program /// synchronizes user file system to remote storage and back, /// monitors files pinning and unpinning. /// - private static VirtualDrive virtualDrive; - - /// - /// Monitores changes in the remote file system. - /// - internal static RemoteStorageMonitor RemoteStorageMonitorInstance; + internal static VirtualDrive VirtualDrive; static async Task Main(string[] args) { // Load Settings. IConfiguration configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json", false, true).Build(); Settings = configuration.ReadSettings(); - Config.Settings = Settings; // Load Log4Net for net configuration. var logRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); @@ -69,7 +63,7 @@ static async Task Main(string[] args) Directory.CreateDirectory(Settings.ServerDataFolderPath); await Registrar.RegisterAsync(SyncRootId, Settings.UserFileSystemRootPath, Settings.ProductName, - Path.Combine(Config.Settings.IconsFolderPath, "Drive.ico")); + Path.Combine(Settings.IconsFolderPath, "Drive.ico")); } else { @@ -84,15 +78,12 @@ await Registrar.RegisterAsync(SyncRootId, Settings.UserFileSystemRootPath, Setti try { - virtualDrive = new VirtualDrive(Settings.UserFileSystemLicense, Settings.UserFileSystemRootPath, log, Settings.SyncIntervalMs); - RemoteStorageMonitorInstance = new RemoteStorageMonitor(Settings.RemoteStorageRootPath, log); + VirtualDrive = new VirtualDrive(Settings.UserFileSystemLicense, Settings.UserFileSystemRootPath, Settings, log); - // Start processing OS file system calls. + // Start processing OS file system calls, monitoring changes in user file system and remote storge. //engine.ChangesProcessingEnabled = false; - await virtualDrive.StartAsync(); - - // Start monitoring changes in remote file system. - await RemoteStorageMonitorInstance.StartAsync(); + await VirtualDrive.StartAsync(); + //await VirtualDrive.SyncService.StopAsync(); #if DEBUG // Opens Windows File Manager with user file system folder and remote storage folder. @@ -103,8 +94,7 @@ await Registrar.RegisterAsync(SyncRootId, Settings.UserFileSystemRootPath, Setti } finally { - virtualDrive.Dispose(); - RemoteStorageMonitorInstance.Dispose(); + VirtualDrive.Dispose(); } if (exitKey.KeyChar == 'q') @@ -132,7 +122,7 @@ await Registrar.RegisterAsync(SyncRootId, Settings.UserFileSystemRootPath, Setti try { - Directory.Delete(Config.Settings.ServerDataFolderPath, true); + Directory.Delete(Settings.ServerDataFolderPath, true); } catch (Exception ex) { @@ -170,7 +160,6 @@ private static void ShowTestEnvironment() } - // Open Windows File Manager with ETags and locks storage. //ProcessStartInfo serverDataInfo = new ProcessStartInfo(Program.Settings.ServerDataFolderPath); //serverDataInfo.UseShellExecute = true; // Open window only if not opened already. diff --git a/VirtualFileSystem/RemoteStorageMonitor.cs b/VirtualFileSystem/RemoteStorageMonitor.cs index 1891bc1..8a7a68f 100644 --- a/VirtualFileSystem/RemoteStorageMonitor.cs +++ b/VirtualFileSystem/RemoteStorageMonitor.cs @@ -13,9 +13,9 @@ using Windows.System.Update; using ITHit.FileSystem.Samples.Common; -using ITHit.FileSystem.Samples.Common.Syncronyzation; +using ITHit.FileSystem.Samples.Common.Windows; -namespace VirtualFileSystem.Syncronyzation +namespace VirtualFileSystem { /// /// Monitors changes in the remote storage, notifies the client and updates the user file system. @@ -30,23 +30,33 @@ namespace VirtualFileSystem.Syncronyzation internal class RemoteStorageMonitor : Logger, IDisposable { /// - /// Watches for changes in the remote storage file system. + /// Remote storage path. Folder to monitor for changes. /// - private FileSystemWatcher watcher = new FileSystemWatcher(); + private readonly string remoteStorageRootPath; /// - /// Remote storage path. Folder to monitor for changes. + /// Virtul drive instance. This class will call methods + /// to update user file system when any data is changed in the remote storage: + /// , + /// , etc. + /// + private readonly VirtualDrive virtualDrive; + + /// + /// Watches for changes in the remote storage file system. /// - private string remoteStorageRootPath; + private readonly FileSystemWatcher watcher = new FileSystemWatcher(); /// /// Creates instance of this class. /// /// Remote storage path. Folder that contains source files to monitor changes. + /// Virtual drive to send notifications about changes in the remote storage. /// Logger. - internal RemoteStorageMonitor(string remoteStorageRootPath, ILog log) : base("Remote Storage Monitor", log) + internal RemoteStorageMonitor(string remoteStorageRootPath, VirtualDrive virtualDrive, ILog log) : base("Remote Storage Monitor", log) { this.remoteStorageRootPath = remoteStorageRootPath; + this.virtualDrive = virtualDrive; } /// @@ -68,6 +78,14 @@ internal async Task StartAsync() LogMessage($"Started"); } + /// + /// Stops monitoring changes in the remote storage. + /// + internal async Task StopAsync() + { + Enabled = false; + } + private bool Started() { return !string.IsNullOrEmpty(watcher.Path); @@ -112,12 +130,10 @@ private async void CreatedAsync(object sender, FileSystemEventArgs e) // Because of the on-demand population the file or folder placeholder may not exist in the user file system // or the folder may be offline. - if (Directory.Exists(userFileSystemParentPath) - && !new DirectoryInfo(userFileSystemParentPath).Attributes.HasFlag(System.IO.FileAttributes.Offline)) + FileSystemInfo remoteStorageItem = FsPath.GetFileSystemItem(remoteStoragePath); + FileSystemItemMetadata newItemInfo = Mapping.GetUserFileSysteItemMetadata(remoteStorageItem); + if (await virtualDrive.ServerNotifications(userFileSystemParentPath, this).CreateAsync(new[] { newItemInfo }) > 0) { - FileSystemInfo remoteStorageItem = FsPath.GetFileSystemItem(remoteStoragePath); - FileSystemItemBasicInfo newItemInfo = Mapping.GetUserFileSysteItemBasicInfo(remoteStorageItem); - await UserFileSystemRawItem.CreateAsync(userFileSystemParentPath, new[] { newItemInfo }); LogMessage($"Created succesefully", userFileSystemPath); } } @@ -153,15 +169,12 @@ private async void ChangedAsync(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 sent updates from server back to client that issued the update. - FileSystemItemBasicInfo itemInfo = Mapping.GetUserFileSysteItemBasicInfo(remoteStorageItem); - if (!await ETag.ETagEqualsAsync(userFileSystemPath, itemInfo)) + FileSystemItemMetadata itemInfo = Mapping.GetUserFileSysteItemMetadata(remoteStorageItem); + if (!await virtualDrive.GetETagManager(userFileSystemPath, this).ETagEqualsAsync(itemInfo)) { - await new UserFileSystemRawItem(userFileSystemPath).UpdateAsync(itemInfo); + await virtualDrive.ServerNotifications(userFileSystemPath, this).UpdateAsync(itemInfo); LogMessage("Updated succesefully", userFileSystemPath); } - - // Update "locked by another user" icon. - await new UserFileSystemRawItem(userFileSystemPath).SetLockedByAnotherUserAsync(itemInfo.LockedByAnotherUser); } } } @@ -186,9 +199,8 @@ private async void DeletedAsync(object sender, FileSystemEventArgs e) string userFileSystemPath = Mapping.ReverseMapPath(remoteStoragePath); // Because of the on-demand population the file or folder placeholder may not exist in the user file system. - if (FsPath.Exists(userFileSystemPath)) + if (await virtualDrive.ServerNotifications(userFileSystemPath, this).DeleteAsync()) { - await new UserFileSystemRawItem(userFileSystemPath).DeleteAsync(); LogMessage("Deleted succesefully", userFileSystemPath); } } @@ -214,11 +226,11 @@ private async void RenamedAsync(object sender, RenamedEventArgs e) { string userFileSystemOldPath = Mapping.ReverseMapPath(remoteStorageOldPath); + string userFileSystemNewPath = Mapping.ReverseMapPath(remoteStorageNewPath); + // Because of the on-demand population the file or folder placeholder may not exist in the user file system. - if (FsPath.Exists(userFileSystemOldPath)) + if (await virtualDrive.ServerNotifications(userFileSystemOldPath, this).MoveToAsync(userFileSystemNewPath)) { - string userFileSystemNewPath = Mapping.ReverseMapPath(remoteStorageNewPath); - await new UserFileSystemRawItem(userFileSystemOldPath).MoveToAsync(userFileSystemNewPath); LogMessage("Renamed succesefully:", userFileSystemOldPath, userFileSystemNewPath); } } diff --git a/VirtualFileSystem/UserFile.cs b/VirtualFileSystem/UserFile.cs deleted file mode 100644 index 06bd2e1..0000000 --- a/VirtualFileSystem/UserFile.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading.Tasks; - -using ITHit.FileSystem; -using ITHit.FileSystem.Samples.Common; - -namespace VirtualFileSystem -{ - /// - /// Represents a file in the remote storage. - /// - /// You will change methods of this class to read/write data from/to your remote storage. - internal class UserFile : UserFileSystemItem, IUserFile - { - /// - /// Creates instance of this class. - /// - /// Path of this file in the user file system. - /// Information about file lock. Pass null if the item is not locked. - public UserFile(string userFileSystemFilePath) : base(userFileSystemFilePath) - { - - } - - /// - /// Reads file content from the remote storage. - /// - /// Offset in bytes in file content to start reading from. - /// Lenth in bytes of the file content to read. - /// File content that corresponds to the provided offset and length. - public async Task ReadAsync(long offset, long length) - { - // This method has a 60 sec timeout. - // To process longer requests modify the IFolder.TransferDataAsync() implementation. - - await using (FileStream stream = File.OpenRead(RemoteStoragePath)) - { - stream.Seek(offset, SeekOrigin.Begin); - byte[] buffer = new byte[length]; - int bytesRead = await stream.ReadAsync(buffer, 0, (int)length); - return buffer; - } - } - - public async Task ValidateDataAsync(long offset, long length) - { - return true; - } - - /// - /// Updates file in the remote storage. - /// - /// New information about the file, such as creation date, modification date, attributes, etc. - /// New file content or null if the file content is not modified. - /// Information about the lock. Caller passes null if the item is not locked. - /// New ETag returned from the remote storage. - public async Task UpdateAsync(IFileBasicInfo fileInfo, Stream content = null, ServerLockInfo lockInfo = null) - { - return await CreateOrUpdateFileAsync(RemoteStoragePath, fileInfo, FileMode.Open, content); - } - } -} diff --git a/VirtualFileSystem/VirtualDrive.cs b/VirtualFileSystem/VirtualDrive.cs index 95575ec..633ba92 100644 --- a/VirtualFileSystem/VirtualDrive.cs +++ b/VirtualFileSystem/VirtualDrive.cs @@ -2,37 +2,84 @@ using System.Collections.Generic; using System.Text; using System.Threading.Tasks; +using System.IO; using log4net; using ITHit.FileSystem.Samples.Common; -using System.IO; +using ITHit.FileSystem.Samples.Common.Windows; +using ITHit.FileSystem; namespace VirtualFileSystem { - /// + /// + /// Processes OS file system calls, + /// synchronizes the user file system to the remote storage and back, + /// monitors files pinning and unpinning in the local file system, + /// monitores changes in the remote storage. + /// internal class VirtualDrive : VirtualDriveBase { + /// + /// Monitors changes in the remote storage, notifies the client and updates the user file system. + /// + internal RemoteStorageMonitor RemoteStorageMonitor; + /// - public VirtualDrive(string license, string userFileSystemRootPath, ILog log, double syncIntervalMs) - : base(license, userFileSystemRootPath, log, syncIntervalMs) + public VirtualDrive(string license, string userFileSystemRootPath, Settings settings, ILog log) + : base(license, userFileSystemRootPath, settings, log) { - + string remoteStorageRootPath = Mapping.MapPath(userFileSystemRootPath); + RemoteStorageMonitor = new RemoteStorageMonitor(remoteStorageRootPath, this, log); } /// - public override async Task GetUserFileSystemItemAsync(string userFileSystemPath) + public override async Task GetVirtualFileSystemItemAsync(string userFileSystemPath, FileSystemItemTypeEnum itemType, ILogger logger) { - if (File.Exists(userFileSystemPath)) + if (itemType == FileSystemItemTypeEnum.File) { - return new UserFile(userFileSystemPath); + return new VirtualFile(userFileSystemPath, this, logger); } - if (Directory.Exists(userFileSystemPath)) + else { - return new UserFolder(userFileSystemPath); + return new VirtualFolder(userFileSystemPath, this, logger); } + } - // When a file handle is being closed during delete, the file does not exist, return null. - return null; + /// + /// Enables or disables full synchronization service, user file sytem monitor and remote storage monitor. + /// + /// Pass true to start synchronization. Pass false - to stop. + public override async Task SetEnabledAsync(bool enabled) + { + await base.SetEnabledAsync(enabled); + + if (enabled) + { + await RemoteStorageMonitor.StartAsync(); + } + else + { + await RemoteStorageMonitor.StopAsync(); + } + } + + + private bool disposedValue; + + protected override void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + RemoteStorageMonitor.Dispose(); + } + + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + disposedValue = true; + } + base.Dispose(disposing); } } } diff --git a/VirtualFileSystem/VirtualFile.cs b/VirtualFileSystem/VirtualFile.cs new file mode 100644 index 0000000..b4e5014 --- /dev/null +++ b/VirtualFileSystem/VirtualFile.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; + +using ITHit.FileSystem; +using ITHit.FileSystem.Samples.Common; + +namespace VirtualFileSystem +{ + /// + /// Represents a file in the remote storage. + /// + /// You will change methods of this class to read/write data from/to your remote storage. + internal class VirtualFile : VirtualFileSystemItem, IVirtualFile + { + /// + /// Creates instance of this class. + /// + /// Path of this file in the user file system. + /// Virtual Drive instance that created this item. + /// Logger. + public VirtualFile(string userFileSystemFilePath, VirtualDrive virtualDrive, ILogger logger) + : base(userFileSystemFilePath, virtualDrive, logger) + { + + } + + /// + /// Reads this file content from the remote storage. + /// + /// File content offset, in bytes, to start reading from. + /// Data length to read, in bytes. + /// Total file size, in bytes. + /// + /// You will use this parameter to return file content by + /// calling + /// + public async Task ReadAsync(long offset, long length, long fileSize, ITransferDataResultContext resultContext) + { + // On Windows this method has a 60 sec timeout. + // To process longer requests and reset the timout timer call the resultContext.ReportProgress() or resultContext.ReturnData() method. + + await using (FileStream stream = File.OpenRead(RemoteStoragePath)) + { + const long MAX_CHUNK_SIZE = 0x500000; //5Mb + + long chunkSize = Math.Min(MAX_CHUNK_SIZE, length); + + stream.Seek(offset, SeekOrigin.Begin); + + long total = offset + length; + byte[] buffer = new byte[chunkSize]; + long bytesRead; + while ( (bytesRead = await stream.ReadAsync(buffer, 0, (int)chunkSize) ) > 0) + { + resultContext.ReturnData(buffer, offset, bytesRead); + offset += bytesRead; + length -= bytesRead; + chunkSize = Math.Min(MAX_CHUNK_SIZE, length); + if (offset >= total) + { + return; + } + } + } + } + + + public async Task ValidateDataAsync(long offset, long length) + { + return true; + } + + /// + /// Updates file in the remote storage. + /// + /// New information about the file, such as creation date, modification date, attributes, etc. + /// New file content or null if the file content is not modified. + /// The ETag to be sent to the remote storage as part of the update request to make sure the content is not overwritten. + /// Information about the lock. Caller passes null if the item is not locked. + /// The new ETag returned from the remote storage. + public async Task UpdateAsync(IFileMetadata fileInfo, Stream content = null, string eTagOld = null, ServerLockInfo lockInfo = null) + { + return await CreateOrUpdateFileAsync(RemoteStoragePath, fileInfo, FileMode.Open, content, eTagOld, lockInfo); + } + } +} diff --git a/VirtualFileSystem/VirtualFileSystem.csproj b/VirtualFileSystem/VirtualFileSystem.csproj index 8faff0e..341c64d 100644 --- a/VirtualFileSystem/VirtualFileSystem.csproj +++ b/VirtualFileSystem/VirtualFileSystem.csproj @@ -27,11 +27,10 @@ To simulate the remote storage, this sample is using a folder in the local file - - + diff --git a/VirtualFileSystem/UserFileSystemItem.cs b/VirtualFileSystem/VirtualFileSystemItem.cs similarity index 72% rename from VirtualFileSystem/UserFileSystemItem.cs rename to VirtualFileSystem/VirtualFileSystemItem.cs index c33783c..9c47bdc 100644 --- a/VirtualFileSystem/UserFileSystemItem.cs +++ b/VirtualFileSystem/VirtualFileSystemItem.cs @@ -8,6 +8,7 @@ using ITHit.FileSystem; using ITHit.FileSystem.Windows; using ITHit.FileSystem.Samples.Common; +using ITHit.FileSystem.Samples.Common.Windows; namespace VirtualFileSystem @@ -16,27 +17,40 @@ namespace VirtualFileSystem /// Represents a file or a folder in the remote storage. Contains methods common for both files and folders. /// /// You will change methods of this class to read/write data from/to your remote storage. - internal class UserFileSystemItem : IUserFileSystemItem + internal class VirtualFileSystemItem : IVirtualFileSystemItem, IVirtualLock { /// /// Path of this file of folder in the user file system. /// - protected string UserFileSystemPath; + protected readonly string UserFileSystemPath; /// /// Path of this file or folder in the remote storage. /// - protected string RemoteStoragePath; + protected readonly string RemoteStoragePath; + + /// + /// Virtual Drive instance that created this item. + /// + protected readonly VirtualDrive VirtualDrive; + + /// + /// Logger. + /// + protected readonly ILogger Logger; /// /// Creates instance of this class. /// /// Path of this file of folder in the user file system. - /// Information about the lock. Pass null if the item is not locked. - public UserFileSystemItem(string userFileSystemPath) + /// Virtual Drive instance that created this item. + /// Logger. + public VirtualFileSystemItem(string userFileSystemPath, VirtualDrive virtualDrive, ILogger logger) { this.UserFileSystemPath = userFileSystemPath; + this.VirtualDrive = virtualDrive; this.RemoteStoragePath = Mapping.MapPath(userFileSystemPath); + this.Logger = logger; } /// @@ -55,7 +69,7 @@ public async Task MoveToAsync(string userFileSystemNewPath) { // Disable RemoteStorageMonitor to avoid circular calls. // This is only required because of the specifics of the simplicity of this example. - Program.RemoteStorageMonitorInstance.Enabled = false; + Program.VirtualDrive.RemoteStorageMonitor.Enabled = false; if (remoteStorageOldItem is FileInfo) { @@ -68,7 +82,7 @@ public async Task MoveToAsync(string userFileSystemNewPath) } finally { - Program.RemoteStorageMonitorInstance.Enabled = true; + Program.VirtualDrive.RemoteStorageMonitor.Enabled = true; } } } @@ -85,7 +99,7 @@ public async Task DeleteAsync() { // Disable RemoteStorageMonitor to avoid circular calls. // This is only required because of the specifics of the simplicity of this example. - Program.RemoteStorageMonitorInstance.Enabled = false; + Program.VirtualDrive.RemoteStorageMonitor.Enabled = false; if (remoteStorageItem is FileInfo) { @@ -98,7 +112,7 @@ public async Task DeleteAsync() } finally { - Program.RemoteStorageMonitorInstance.Enabled = true; + Program.VirtualDrive.RemoteStorageMonitor.Enabled = true; } } } @@ -110,26 +124,17 @@ public async Task DeleteAsync() /// New information about the file, such as modification date, attributes, custom data, etc. /// Specifies if a new file should be created or existing file should be updated. /// New file content or null if the file content is not modified. - /// Information about the lock. Caller passes null if the item is not locked. - /// New ETag returned from the remote storage. - protected async Task CreateOrUpdateFileAsync(string remoteStoragePath, IFileBasicInfo newInfo, FileMode mode, Stream newContentStream = null, ServerLockInfo lockInfo = null) + /// The ETag to be sent to the remote storage as part of the update request to make sure the content is not overwritten. + /// Information about the lock. Null if the item is not locked. + /// The new ETag returned from the remote storage. + protected async Task CreateOrUpdateFileAsync( + string remoteStoragePath, IFileMetadata newInfo, FileMode mode, Stream newContentStream = null, string eTagOld = null, ServerLockInfo lockInfo = null) { - // Get ETag and lock-token here and send it to the remote storage with the new item content/info if needed. - if (mode == FileMode.Open) - { - // Get ETag. - string eTag = await ETag.GetETagAsync(UserFileSystemPath); - - // Get lock-token. - string lockToken = lockInfo?.LockToken; - } - FileInfo remoteStorageItem = new FileInfo(remoteStoragePath); try { - - Program.RemoteStorageMonitorInstance.Enabled = false; // Disable RemoteStorageMonitor to avoid circular calls. + Program.VirtualDrive.RemoteStorageMonitor.Enabled = false; // Disable RemoteStorageMonitor to avoid circular calls. // If another thread is trying to sync the same file, this call will fail in other threads. // In your implementation you must lock your remote storage file, or block it for reading and writing by other means. @@ -140,8 +145,8 @@ protected async Task CreateOrUpdateFileAsync(string remoteStoragePath, I { // Verify that the item in the remote storage is not modified since it was downloaded to the user file system. // In your real-life application you will send the ETag to the server as part of the update request. - FileSystemItemBasicInfo itemInfo = Mapping.GetUserFileSysteItemBasicInfo(remoteStorageItem); - if (!(await ETag.ETagEqualsAsync(userFileSystemPath, itemInfo))) + FileSystemItemMetadata itemInfo = Mapping.GetUserFileSysteItemMetadata(remoteStorageItem); + if (!(await VirtualDrive.GetETagManager(userFileSystemPath).ETagEqualsAsync(itemInfo))) { throw new ConflictException(Modified.Server, "Item is modified in the remote storage, ETags not equal."); } @@ -151,8 +156,8 @@ protected async Task CreateOrUpdateFileAsync(string remoteStoragePath, I // This is only required to avoid circular updates because of the simplicity of this sample. // In your real-life application you will receive a new ETag from the server in the update response // and return it from this method. - string eTag = newInfo.LastWriteTime.DateTime.ToBinary().ToString(); - await ETag.SetETagAsync(userFileSystemPath, eTag); + string eTagNew = newInfo.LastWriteTime.ToUniversalTime().ToString("o"); + await VirtualDrive.GetETagManager(userFileSystemPath).SetETagAsync(eTagNew); // Update remote storage file content. if (newContentStream != null) @@ -170,12 +175,12 @@ protected async Task CreateOrUpdateFileAsync(string remoteStoragePath, I newInfo.LastAccessTime, newInfo.LastWriteTime); - return eTag; + return eTagNew; } } finally { - Program.RemoteStorageMonitorInstance.Enabled = true; + Program.VirtualDrive.RemoteStorageMonitor.Enabled = true; } } @@ -186,25 +191,17 @@ protected async Task CreateOrUpdateFileAsync(string remoteStoragePath, I /// Path of the folder to be created or updated in the remote storage. /// New information about the folder, such as modification date, attributes, custom data, etc. /// Specifies if a new folder should be created or existing folder should be updated. - /// Information about the lock. Caller passes null if the item is not locked. - /// New ETag returned from the remote storage. - protected async Task CreateOrUpdateFolderAsync(string remoteStoragePath, IFolderBasicInfo newInfo, FileMode mode, ServerLockInfo lockInfo = null) + /// The ETag to be sent to the remote storage as part of the update request to make sure the content is not overwritten. + /// Information about the lock. Null if the item is not locked. + /// The new ETag returned from the remote storage. + protected async Task CreateOrUpdateFolderAsync( + string remoteStoragePath, IFolderMetadata newInfo, FileMode mode, string eTagOld = null, ServerLockInfo lockInfo = null) { - // Get ETag and lock-token here and send it to the remote storage with the new item content/info if needed. - if (mode == FileMode.Open) - { - // Get ETag. - string eTag = await ETag.GetETagAsync(UserFileSystemPath); - - // Get lock-token. - string lockToken = lockInfo?.LockToken; - } - DirectoryInfo remoteStorageItem = new DirectoryInfo(remoteStoragePath); try { - Program.RemoteStorageMonitorInstance.Enabled = false; // Disable RemoteStorageMonitor to avoid circular calls. + Program.VirtualDrive.RemoteStorageMonitor.Enabled = false; // Disable RemoteStorageMonitor to avoid circular calls. remoteStorageItem.Create(); @@ -213,8 +210,8 @@ protected async Task CreateOrUpdateFolderAsync(string remoteStoragePath, { // Verify that the item in the remote storage is not modified since it was downloaded to the user file system. // In your real-life application you will send the ETag to the server as part of the update request. - FileSystemItemBasicInfo itemInfo = Mapping.GetUserFileSysteItemBasicInfo(remoteStorageItem); - if (!(await ETag.ETagEqualsAsync(userFileSystemPath, itemInfo))) + FileSystemItemMetadata itemInfo = Mapping.GetUserFileSysteItemMetadata(remoteStorageItem); + if (!(await VirtualDrive.GetETagManager(userFileSystemPath).ETagEqualsAsync(itemInfo))) { throw new ConflictException(Modified.Server, "Item is modified in the remote storage, ETags not equal."); } @@ -223,20 +220,21 @@ protected async Task CreateOrUpdateFolderAsync(string remoteStoragePath, // Update ETag/LastWriteTime in user file system, so the synchronyzation or remote storage monitor would not start the new update. // This is only required to avoid circular updates because of the simplicity of this sample. // In your real-life application you will receive a new ETag from server in the update response. - string eTag = newInfo.LastWriteTime.DateTime.ToBinary().ToString(); - await ETag.SetETagAsync(userFileSystemPath, eTag); + string eTagNew = newInfo.LastWriteTime.ToUniversalTime().ToString("o"); + + await VirtualDrive.GetETagManager(userFileSystemPath).SetETagAsync(eTagNew); remoteStorageItem.Attributes = newInfo.Attributes; - remoteStorageItem.CreationTimeUtc = newInfo.CreationTime.DateTime; - remoteStorageItem.LastWriteTimeUtc = newInfo.LastWriteTime.DateTime; - remoteStorageItem.LastAccessTimeUtc = newInfo.LastAccessTime.DateTime; - remoteStorageItem.LastWriteTimeUtc = newInfo.LastWriteTime.DateTime; + remoteStorageItem.CreationTimeUtc = newInfo.CreationTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = newInfo.LastWriteTime.UtcDateTime; + remoteStorageItem.LastAccessTimeUtc = newInfo.LastAccessTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = newInfo.LastWriteTime.UtcDateTime; - return eTag; + return eTagNew; } finally { - Program.RemoteStorageMonitorInstance.Enabled = true; + Program.VirtualDrive.RemoteStorageMonitor.Enabled = true; } } @@ -249,7 +247,7 @@ protected async Task CreateOrUpdateFolderAsync(string remoteStoragePath, /// Return a new object with the being set from this function. /// The will become available via the property when the /// item in the remote storage should be updated. Supply the lock-token during the update request in - /// and method calls. + /// and method calls. /// public async Task LockAsync() { diff --git a/VirtualFileSystem/UserFolder.cs b/VirtualFileSystem/VirtualFolder.cs similarity index 67% rename from VirtualFileSystem/UserFolder.cs rename to VirtualFileSystem/VirtualFolder.cs index ba84600..2dd0b57 100644 --- a/VirtualFileSystem/UserFolder.cs +++ b/VirtualFileSystem/VirtualFolder.cs @@ -15,14 +15,16 @@ namespace VirtualFileSystem /// creating files and folders and updating this folder information (creatin date, modification date, attributes, etc.). /// /// You will change methods of this class to read/write data from/to your remote storage. - internal class UserFolder : UserFileSystemItem, IUserFolder + internal class VirtualFolder : VirtualFileSystemItem, IVirtualFolder { /// /// Creates instance of this class. /// /// Path of this folder in the user file system. - /// Information about file lock. Pass null if the item is not locked. - public UserFolder(string userfileSystemFolderPath) : base(userfileSystemFolderPath) + /// Virtual Drive instance that created this item. + /// Logger. + public VirtualFolder(string userfileSystemFolderPath, VirtualDrive virtualDrive, ILogger logger) + : base(userfileSystemFolderPath, virtualDrive, logger) { } @@ -35,17 +37,17 @@ public UserFolder(string userfileSystemFolderPath) : base(userfileSystemFolderPa /// List of files and folders located in this folder in the remote /// storage that correstonds with the provided search pattern. /// - public async Task> EnumerateChildrenAsync(string pattern) + public async Task> EnumerateChildrenAsync(string pattern) { // This method has a 60 sec timeout. // To process longer requests modify the IFolder.GetChildrenAsync() implementation. IEnumerable remoteStorageChildren = new DirectoryInfo(RemoteStoragePath).EnumerateFileSystemInfos(pattern); - List userFileSystemChildren = new List(); + List userFileSystemChildren = new List(); foreach (FileSystemInfo remoteStorageItem in remoteStorageChildren) { - FileSystemItemBasicInfo itemInfo = Mapping.GetUserFileSysteItemBasicInfo(remoteStorageItem); + FileSystemItemMetadata itemInfo = Mapping.GetUserFileSysteItemMetadata(remoteStorageItem); userFileSystemChildren.Add(itemInfo); } @@ -58,7 +60,7 @@ public async Task> EnumerateChildrenAsync(s /// Information about the new file. /// New file content. /// New ETag returned from the remote storage. - public async Task CreateFileAsync(IFileBasicInfo fileInfo, Stream content) + public async Task CreateFileAsync(IFileMetadata fileInfo, Stream content) { string itemPath = Path.Combine(RemoteStoragePath, fileInfo.Name); return await CreateOrUpdateFileAsync(itemPath, fileInfo, FileMode.CreateNew, content); @@ -69,7 +71,7 @@ public async Task CreateFileAsync(IFileBasicInfo fileInfo, Stream conten /// /// Information about the new folder. /// New ETag returned from the remote storage. - public async Task CreateFolderAsync(IFolderBasicInfo folderInfo) + public async Task CreateFolderAsync(IFolderMetadata folderInfo) { string itemPath = Path.Combine(RemoteStoragePath, folderInfo.Name); return await CreateOrUpdateFolderAsync(itemPath, folderInfo, FileMode.CreateNew); @@ -79,11 +81,12 @@ public async Task CreateFolderAsync(IFolderBasicInfo folderInfo) /// Updates folder in the remote storage. /// /// New folder information. - /// Information about the lock. Caller passes null if the item is not locked. - /// New ETag returned from the remote storage. - public async Task UpdateAsync(IFolderBasicInfo folderInfo, ServerLockInfo lockInfo = null) + /// The ETag to be sent to the remote storage as part of the update request to make sure the content is not overwritten. + /// Information about the lock. Null if the item is not locked. + /// The new ETag returned from the remote storage. + public async Task UpdateAsync(IFolderMetadata folderInfo, string eTagOld = null, ServerLockInfo lockInfo = null) { - return await CreateOrUpdateFolderAsync(RemoteStoragePath, folderInfo, FileMode.Open); + return await CreateOrUpdateFolderAsync(RemoteStoragePath, folderInfo, FileMode.Open, eTagOld, lockInfo); } } diff --git a/VirtualFileSystemMac/README.md b/VirtualFileSystemMac/README.md index 9b68f50..3b9a050 100644 --- a/VirtualFileSystemMac/README.md +++ b/VirtualFileSystemMac/README.md @@ -13,9 +13,9 @@
  • Visual Studio Community 2019 for Mac v8.8.10+, Stable Channel.
  • Solution Structure

    -

    The macOS sample solution consists of 3 projects: container application, a system extension project, and a common code.

    -

    The container application provides a Menu Bar icon to install/uninstall the system extension. Inside the container application, you should change the hardcoded directory to replicate in your Virtual Disk manually. Consider that the system extension can only access sandbox folders (including Downloads, Pictures, Music, Movies). It means that it won't be able to show the contents of the folder outside of the sandbox.

    -

    The system extension project runs in the background and implements a virtual file system on macOS (File Provider). It processes requests from macOS applications sent via macOS file system API and lists folders content. The macOS extension can be installed only as part of a container application, you can not install the extension application by itself.

    +

    The macOS sample solution consists of 3 projects: container application, an extension project, and a common code.

    +

    The container application provides a Menu Bar icon to install/uninstall the file system extension. Inside the container application, you should change the hardcoded directory to replicate in your Virtual Disk manually. Consider that the extension can only access sandbox folders (including Downloads, Pictures, Music, Movies). It means that it won't be able to show the contents of the folder outside of the sandbox.

    +

    The extension project runs in the background and implements a virtual file system on macOS (File Provider). It processes requests from macOS applications sent via macOS file system API and lists folders content. The macOS extension can be installed only as part of a container application, you can not install the extension application by itself.

    Configuring the Sample

    In the following steps, we will describe how to configure and run this sample in the development environment. You will create an Apple group ID, Apple app identifies, and Apple provisioning profiles. Then you will update the sample container application project and extension project to use the created IDs and profiles.

    Log-in to the Apple developer account here: https://developer.apple.com/. To complete the steps below you must have an App Manager role.

    @@ -24,7 +24,7 @@

    Create App Group. Navigate to Certificates, IDs, Profiles -> Identifiers -> App Groups and create a new group.

  • -

    Create Apple macOS App IDs. Navigate to Certificates, IDs, Profile -> Identifiers -> App IDs. Create 2 identifiers that will be unique for your project. One will be used for container application another – for the system extension.

    +

    Create Apple macOS App IDs. Navigate to Certificates, IDs, Profile -> Identifiers -> App IDs. Create 2 identifiers that will be unique for your project. One will be used for container application another – for the extension.

  • Add app identifiers to the group. Add both identifiers created in Step 2 to the group created in Step 1. Select identifier and click on Edit. Then check the App Groups checkbox, select the Edit button and select the group created in Step 1.

    @@ -36,10 +36,10 @@

    Download profiles and certificates in XCode. Run XCode and go to Xcode Menu > Preferences -> Accounts tab. Select team and click on “Download Manual Profiles”. You can find more detailed instructions: here

  • -

    Set bundle identifier name in Container project. The bundle identifier is located in VirtualFilesystemMacApp/Info.plist file. You can edit it either in Visual Studio or directly in Info.plist file in the CFBundleIdentifier field (by default it is set to com.ithit.virtualfilesystem.app). You must set this identifier to the value specified in Step 1.

    +

    Set bundle identifier name in Container project. The bundle identifier is located in VirtualFilesystemMacApp/Info.plist file. You can edit it either in Visual Studio or directly in Info.plist file in the CFBundleIdentifier field (by default it is set to com.userfilesystem.vfs.app). You must set this identifier to the value specified in Step 1.

  • -

    Set bundle identifier name in the Extension project. The bundle identifier is located in FileProviderExtension/Info.plist file. You can edit it either in Visual Studio or directly in Info.plist file in the CFBundleIdentifier field (by default it is set to com.ithit.virtualfilesystem.app.extension). You must set this identifier to the value specified in Step 1.

    +

    Set bundle identifier name in the Extension project. The bundle identifier is located in FileProviderExtension/Info.plist file. You can edit it either in Visual Studio or directly in Info.plist file in the CFBundleIdentifier field (by default it is set to com.userfilesystem.vfs.app.extension). You must set this identifier to the value specified in Step 1.

  • Configure macOS bundle signing in Container and Extension projects. For each project in Visual Studio go to the project Options. Select Mac Signing and check 'Sign the application bundle'. Select Identity and Provisioning profile.

    diff --git a/WebDAVDrive.Setup/Product.wxs b/WebDAVDrive.Setup/Product.wxs new file mode 100644 index 0000000..d4701b6 --- /dev/null +++ b/WebDAVDrive.Setup/Product.wxs @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebDAVDrive.Setup/WebDAVDrive.Setup.wixproj b/WebDAVDrive.Setup/WebDAVDrive.Setup.wixproj new file mode 100644 index 0000000..4bb4f03 --- /dev/null +++ b/WebDAVDrive.Setup/WebDAVDrive.Setup.wixproj @@ -0,0 +1,55 @@ + + + + Debug + x86 + 3.10 + 2edb1552-04dd-4974-8199-a8a453916204 + 2.0 + WebDAVDrive.Setup + Package + + + bin\$(Configuration)\ + obj\$(Configuration)\ + Debug + + + bin\$(Configuration)\ + obj\$(Configuration)\ + + + + + + + + $(WixExtDir)\WixUIExtension.dll + WixUIExtension + + + + + WebDAVDrive + {e23287b1-c11f-45f4-a0e0-f86f0de9a719} + True + + + Content + TESTFOLDER + + + + + + + + + \ No newline at end of file diff --git a/WebDAVDrive.Setup/WixUI_Install.wxs b/WebDAVDrive.Setup/WixUI_Install.wxs new file mode 100644 index 0000000..74da204 --- /dev/null +++ b/WebDAVDrive.Setup/WixUI_Install.wxs @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + 1 + + NOT Installed + Installed AND PATCH + + + 1 + LicenseAccepted = "1" + + NOT Installed + Installed AND NOT PATCH + Installed AND PATCH + + 1 + + 1 + 1 + 1 + + + + + + + diff --git a/WebDAVDrive.Setup/background.png b/WebDAVDrive.Setup/background.png new file mode 100644 index 0000000000000000000000000000000000000000..4f349f19f5595fdbdae8d23bbd0a67234e33c32f GIT binary patch literal 16355 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 J4Cro6v~5ZD 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 literal 0 HcmV?d00001 diff --git a/WebDAVDrive.Setup/banner.png b/WebDAVDrive.Setup/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..d93e264e86c481330b603868a506cfb8d5bd058d GIT binary patch literal 1625 zcmY+FdpJ~S9LHZXXRy{S(o?A9vToZj=ggQWQZqKgxP%#V4hdz5MlmVNmP@%LLUbjH zE-p>lZk7@$HIGnoE1{ChhN3N7l4j4r{?U2f=lA@+pZEK|-{14Rf1HEvt{YU8w3Pq= zDjY|)2LN)NcyFUfCMqM<2nTQ7xn2&qoc&|~#Dn;Fz~cdXdte_gtF;G+5Sa6TAqN9!z+{gYz!C#=Ukc()fiVhAKw)@O=s2fSU>}HvLUbPpK_MCjF;ED>D+bX~ zGVDX93&?bg3}a+Eikk_E;5{J;2%~cbSrZ~_hRLviL`O*oMnX`W;FW~nL@+O50umg? za5D)OKy(Zb#R-uJ<1+{#L_kP7J_9aiD;|g7DYKPmP%fiV2P zHD{hbH9y$gFvR_{i?|Ip%-8u8e1=(wb7IZOA0hUmoOjNdWilCCqoD?W=`*`LTs?uD z9En7RAc!KbprH7ZlG4x0DynMg3p6w|7iukDqOGI5bh)113VnSeV;G?`tgKd9uU=!f zb{*S+>+0s=>FLE2pgz8v{5Jas1#b%t6@^EL#Zh~rV`Ads6B70xNJ>pj%gD^iK9YMX zub{B#{Kb;;OMhOwSy^@K_JfDgM@`KwkJ~!Cx?lA4y?ot2Ffcqa`hI+3;={*JlarsP zzD|GpexlQ(4xd#n(0K#9=t}R)bO6Ya9QHb|=zkH(cJbz8fRC`(j ztq}86R22>F8*81mL7J=>?&h%DeY9BZ$$}?lY)$xHC@I3Ag~2MfGybBwg0{6?DOE?* z7jwcWQS6iB)B4PyTq)>G4s4%f^FrVPHI`{+Q}Z~Bl%CF%1|n_&cKGYFAu+rJUsLl^Yifl9J7Z-ZA;i!AX^6x!o-1uvbo%kd}9~ zFY{q&Zzf=a@+*lnxS7BwgAeN%El zGw4#)j$WzX=rZfobpvNwHrDdD`SFL>~Fl6Tan9I$SCuMbJ=qZ(o>6EjKPW$9ba_2$%vxjcWt z%MDljr+)F}J*=>(-AvB?EWP8+ytcPlSnr6+qPq*L{6);Pb%A`=vQ1a=VXkYX; zKa+P(*;He6ccjbOL!BeGi_YpLL(lt#$8fyI|zqcmfajfJ*xVjrSw#MD164RZBqVD)%24TIeu+b z701w%d)vt_%BtQ2Q$5Gh3}y@#hs;!5uQ#*6Jg?r0@yOO3S|4UcJrTRK7`}K+4_*^8 xshcG27|PwKsH}j^WcCP5J-EIh4eZ7xGT + /// ConsoleExitEvent, works as ManualResetEvent. Contain field KeyInfo with ConsoleKeyInfo, which contains info about key, which was pressed to exit from application. + ///
  • + public class ConsoleExitEvent : EventWaitHandle + { + /// + /// Key, used to select exit method. + /// + public ConsoleKeyInfo KeyInfo { get; set; } + + public ConsoleExitEvent():base(false, EventResetMode.ManualReset) + { + KeyInfo = new ConsoleKeyInfo(); + } + } + [DllImport("user32.dll")] public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); + [DllImport("user32.dll")] static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + /// - /// Hide/Show console + /// Hide/Show console. /// - /// Console visibility + /// Console visibility. public static void SetConsoleWindowVisibility(bool visible) { IntPtr hWnd = FindWindow(null, Console.Title); if (hWnd != IntPtr.Zero) { - if (visible) ShowWindow(hWnd, 1); //1 = SW_SHOWNORMAL - else ShowWindow(hWnd, 0); //0 = SW_HIDE + if (visible) ShowWindow(hWnd, 1); + else ShowWindow(hWnd, 0); } } - #endregion /// /// Starts new thread and waits while any key will be pressed in console. /// - /// ManualResetEvent, invokes when any key will be pressed + /// ManualResetEvent, invokes when any key will be pressed. /// - public static ConsoleKeyInfo WaitConsoleReadKey(ManualResetEvent exitEvent) + public static void WaitConsoleReadKey(ConsoleExitEvent exitEvent) { ConsoleKeyInfo exitKey = new ConsoleKeyInfo(); Thread readKeyThread = new Thread(() => { exitKey = Console.ReadKey(); + exitEvent.KeyInfo = (ConsoleKeyInfo)exitKey; exitEvent.Set(); }); readKeyThread.IsBackground = true; readKeyThread.Start(); - return exitKey; } } } diff --git a/WebDAVDrive/CredentialManager.cs b/WebDAVDrive.UI/CredentialManager.cs similarity index 100% rename from WebDAVDrive/CredentialManager.cs rename to WebDAVDrive.UI/CredentialManager.cs diff --git a/WebDAVDrive.UI/RegistryManager.cs b/WebDAVDrive.UI/RegistryManager.cs index 3d76366..a50e7d9 100644 --- a/WebDAVDrive.UI/RegistryManager.cs +++ b/WebDAVDrive.UI/RegistryManager.cs @@ -1,10 +1,11 @@ -using ITHit.FileSystem.Samples.Common; -using Microsoft.Win32; +using Microsoft.Win32; using System; using System.Collections.Generic; using System.Text; using System.Threading; +using ITHit.FileSystem.Samples.Common; + namespace WebDAVDrive.UI { public class RegistryManager diff --git a/WebDAVDrive.UI/WebDAVDrive.UI.csproj b/WebDAVDrive.UI/WebDAVDrive.UI.csproj index 6cb91b5..eefc034 100644 --- a/WebDAVDrive.UI/WebDAVDrive.UI.csproj +++ b/WebDAVDrive.UI/WebDAVDrive.UI.csproj @@ -13,11 +13,11 @@ - + - + diff --git a/WebDAVDrive.UI/WindowsTrayInterface.cs b/WebDAVDrive.UI/WindowsTrayInterface.cs index fa2796d..093ffa2 100644 --- a/WebDAVDrive.UI/WindowsTrayInterface.cs +++ b/WebDAVDrive.UI/WindowsTrayInterface.cs @@ -6,8 +6,9 @@ using System.Windows.Forms; using ITHit.FileSystem.Samples.Common; -using ITHit.FileSystem.Samples.Common.Syncronyzation; -using static ITHit.FileSystem.Samples.Common.Syncronyzation.FullSyncService; +using ITHit.FileSystem.Samples.Common.Windows; +using ITHit.FileSystem.Samples.Common.Windows.Syncronyzation; +using static ITHit.FileSystem.Samples.Common.Windows.Syncronyzation.FullSyncService; namespace WebDAVDrive.UI { @@ -18,17 +19,16 @@ public class WindowsTrayInterface { /// /// Create new tray icon. - /// + ///
    s /// Product name. - /// Sync service - /// ManualResetEvent, used to stop application + /// VirtualDriveBase, need to get syncService and fileSystemMonitor. + /// ManualResetEvent, used to stop application. /// - public static Thread CreateTrayInterface(string productName, FullSyncService syncService, ManualResetEvent exitEvent) + public static Thread CreateTrayInterface(string productName, IVirtualDrive virtualDrive, ConsoleManager.ConsoleExitEvent exitEvent) { // Start tray application. Thread thread = new Thread(() => { - WindowsTrayInterface windowsTrayInterface = new WindowsTrayInterface($"{productName}", syncService); - syncService.syncEvent += windowsTrayInterface.HandleStatusChange; + WindowsTrayInterface windowsTrayInterface = new WindowsTrayInterface($"{productName}", virtualDrive); Application.Run(); exitEvent.Set(); }); @@ -39,27 +39,30 @@ public static Thread CreateTrayInterface(string productName, FullSyncService syn } /// - /// Changes button status to Idle + /// Changes button status to Idle. /// private void StatusToIdle() { notifyIcon.Text = Title + $"\n{Localization.Resources.Idle}"; notifyIcon.ContextMenuStrip.Items[0].Text = Localization.Resources.StopSync; + notifyIcon.Icon = new System.Drawing.Icon("Images\\Drive.ico"); ; } /// - /// Changes button status to Synching + /// Changes button status to Synching. /// private void StatusToSynching() { notifyIcon.Text = Title + $"\n{Localization.Resources.StatusSync}"; notifyIcon.ContextMenuStrip.Items[0].Text = Localization.Resources.StopSync; + notifyIcon.Icon = new System.Drawing.Icon("Images\\DriveSync.ico"); ; } private void StatusToSyncStopped() { - notifyIcon.Text = Title + $"\n{Localization.Resources.StopSync}"; + notifyIcon.Text = Title + $"\n{Localization.Resources.StatusSyncStopped}"; notifyIcon.ContextMenuStrip.Items[0].Text = Localization.Resources.StartSync; + notifyIcon.Icon = new System.Drawing.Icon("Images\\DrivePause.ico"); ; } /// /// Icon in the status bar notification area. @@ -72,7 +75,7 @@ private void StatusToSyncStopped() public static bool Visible = true; /// - /// Notify icon title + /// Notify icon title. /// public string Title { get; set; } @@ -88,7 +91,7 @@ private void StatusToSyncStopped() /// /// Synchronization service instance. The tray application will enable/disable this application and show its status. /// - public WindowsTrayInterface(string title, FullSyncService syncService) + public WindowsTrayInterface(string title, IVirtualDrive virtualDrive) { Title = title; notifyIcon = new NotifyIcon(); @@ -98,11 +101,11 @@ public WindowsTrayInterface(string title, FullSyncService syncService) notifyIcon.Text = title; ContextMenuStrip contextMenu = new ContextMenuStrip(); - contextMenu.Items.Add(Localization.Resources.StopSync, null, (s, e) => { StartStopSync(syncService); }); + contextMenu.Items.Add(Localization.Resources.StopSync, null, (s, e) => { StartStopSync(virtualDrive); }); #if !DEBUG // Hide console on app start. Visible = false; - SetConsoleWindowVisibility(false); + ConsoleManager.SetConsoleWindowVisibility(false); contextMenu.Items.Add(Localization.Resources.ShowLog, null, (s, e) => { #else contextMenu.Items.Add(Localization.Resources.HideLog, null, (s, e) => { @@ -115,11 +118,6 @@ public WindowsTrayInterface(string title, FullSyncService syncService) contextMenu.Items.Add($"{Localization.Resources.Exit} {title}",null, (s,e) => { Application.Exit(); }); notifyIcon.ContextMenuStrip = contextMenu; - - notifyIcon.MouseClick += (object sender, MouseEventArgs e) => - { - //MessageBox.Show("Clicked"); - }; } /// @@ -130,17 +128,17 @@ public WindowsTrayInterface(string title, FullSyncService syncService) /// /// This method handles StartStop Sycn button in tray menu. /// - private async void StartStopSync(FullSyncService syncService) + private async void StartStopSync(IVirtualDrive virtualDrive) { if (!sycnStopped) { - await syncService.StopAsync(); + await virtualDrive.SetEnabledAsync(false); sycnStopped = true; StatusToSyncStopped(); } else { - await syncService.StartAsync(); + await virtualDrive.SetEnabledAsync(true); sycnStopped = false; StatusToIdle(); } @@ -151,7 +149,7 @@ private async void StartStopSync(FullSyncService syncService) /// public void HandleStatusChange(object sender, SynchEventArgs synchEventArgs) { - if (synchEventArgs.state == SynchronizationState.Started) + if (synchEventArgs.NewState == SynchronizationState.Synchronizing) { StatusToSynching(); } diff --git a/WebDAVDrive/AppSettings.cs b/WebDAVDrive/AppSettings.cs index ac8c41c..e1ad3b4 100644 --- a/WebDAVDrive/AppSettings.cs +++ b/WebDAVDrive/AppSettings.cs @@ -85,7 +85,7 @@ public static AppSettings ReadSettings(this IConfiguration configuration) // Folder where eTags and file locks are stored. string localApplicationDataFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - settings.ServerDataFolderPath = Path.Combine(localApplicationDataFolderPath, settings.ProductName, settings.UserFileSystemRootPath.Replace(":", ""), "ServerData"); + settings.ServerDataFolderPath = Path.Combine(localApplicationDataFolderPath, settings.AppID, settings.UserFileSystemRootPath.Replace(":", ""), "ServerData"); return settings; } } diff --git a/WebDAVDrive/Mapping.cs b/WebDAVDrive/Mapping.cs index 3895f0d..e130578 100644 --- a/WebDAVDrive/Mapping.cs +++ b/WebDAVDrive/Mapping.cs @@ -6,7 +6,6 @@ using System.Linq; using ITHit.FileSystem; -using ITHit.FileSystem.Windows; using ITHit.FileSystem.Samples.Common; using ITHit.WebDAV.Client; @@ -76,17 +75,17 @@ public static string ReverseMapPath(string remoteStorageUri) /// /// Remote storage item info. /// User file system item info. - public static FileSystemItemBasicInfo GetUserFileSystemItemBasicInfo(IHierarchyItemAsync remoteStorageItem) + public static FileSystemItemMetadata GetUserFileSystemItemMetadata(IHierarchyItemAsync remoteStorageItem) { - FileSystemItemBasicInfo userFileSystemItem; + FileSystemItemMetadata userFileSystemItem; if (remoteStorageItem is IFileAsync) { - userFileSystemItem = new FileBasicInfo(); + userFileSystemItem = new FileMetadata(); } else { - userFileSystemItem = new FolderBasicInfo(); + userFileSystemItem = new FolderMetadata(); } userFileSystemItem.Name = remoteStorageItem.DisplayName; @@ -98,7 +97,7 @@ public static FileSystemItemBasicInfo GetUserFileSystemItemBasicInfo(IHierarchyI // If the item is locked by another user, set the LockedByAnotherUser to true. // This will set read-only arrtibute on files. - // Note that read-only attribute is a convenience feture, it does not protect files from modification. + // Note that read-only attribute is a convenience feature, it does not protect files from modification. userFileSystemItem.LockedByAnotherUser = remoteStorageItem.ActiveLocks.Length > 0; if (remoteStorageItem is IFileAsync) @@ -106,9 +105,9 @@ public static FileSystemItemBasicInfo GetUserFileSystemItemBasicInfo(IHierarchyI // 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. - ((FileBasicInfo)userFileSystemItem).ETag = ((IFileAsync)remoteStorageItem).Etag; + ((FileMetadata)userFileSystemItem).ETag = ((IFileAsync)remoteStorageItem).Etag; - ((FileBasicInfo)userFileSystemItem).Length = ((IFileAsync)remoteStorageItem).ContentLength; + ((FileMetadata)userFileSystemItem).Length = ((IFileAsync)remoteStorageItem).ContentLength; }; // Set custom columns to be displayed in file manager. @@ -124,12 +123,12 @@ public static FileSystemItemBasicInfo GetUserFileSystemItemBasicInfo(IHierarchyI Owner = lockInfo.Owner, Exclusive = lockInfo.LockScope == LockScope.Exclusive, LockExpirationDateUtc = DateTimeOffset.Now.Add(lockInfo.TimeOut) - }.GetLockProperties(Path.Combine(Config.Settings.IconsFolderPath, "LockedByAnotherUser.ico")) + }.GetLockProperties(Path.Combine(Program.Settings.IconsFolderPath, "LockedByAnotherUser.ico")) ); } if (remoteStorageItem is IFileAsync) { - customProps.Add(new FileSystemItemPropertyData(5, ((IFileAsync)remoteStorageItem).Etag)); + customProps.Add(new FileSystemItemPropertyData((int)CustomColumnIds.ETag, ((IFileAsync)remoteStorageItem).Etag)); }; userFileSystemItem.CustomProperties = customProps; diff --git a/WebDAVDrive/Program.cs b/WebDAVDrive/Program.cs index 91eb1ca..8f2d49c 100644 --- a/WebDAVDrive/Program.cs +++ b/WebDAVDrive/Program.cs @@ -20,6 +20,7 @@ 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 WebDAVDrive.UI; @@ -46,22 +47,17 @@ class Program /// /// Processes OS file system calls, /// synchronizes user file system to remote storage and back, - /// monitors files pinning and unpinning. + /// monitors files pinning and unpinning in the local file system, + /// monitores changes in the remote storage. /// private static VirtualDrive virtualDrive; - /// - /// Monitores changes in the remote file system. - /// - internal static RemoteStorageMonitor RemoteStorageMonitorInstance; - //[STAThread] static async Task Main(string[] args) { // Load Settings. IConfiguration configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json", false, true).Build(); Settings = configuration.ReadSettings(); - Config.Settings = Settings; // Load Log4Net for net configuration. var logRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); @@ -85,7 +81,7 @@ static async Task Main(string[] args) Directory.CreateDirectory(Settings.ServerDataFolderPath); await Registrar.RegisterAsync(SyncRootId, Settings.UserFileSystemRootPath, Settings.ProductName, - Path.Combine(Config.Settings.IconsFolderPath, "Drive.ico")); + Path.Combine(Settings.IconsFolderPath, "Drive.ico")); } else { @@ -101,37 +97,33 @@ await Registrar.RegisterAsync(SyncRootId, Settings.UserFileSystemRootPath, Setti ConsoleKeyInfo exitKey = new ConsoleKeyInfo(); // Event to be fired when any key will be pressed in the console or when the tray application exits. - ManualResetEvent exitEvent = new ManualResetEvent(false); + ConsoleManager.ConsoleExitEvent exitEvent = new ConsoleManager.ConsoleExitEvent(); try { - virtualDrive = new VirtualDrive(Settings.UserFileSystemLicense, Settings.UserFileSystemRootPath, log, Settings.SyncIntervalMs); - RemoteStorageMonitorInstance = new RemoteStorageMonitor(Settings.WebDAVServerUrl, log); + virtualDrive = new VirtualDrive(Settings.UserFileSystemLicense, Settings.UserFileSystemRootPath, Settings, log); // Start tray application. - Thread tryIconThread = WindowsTrayInterface.CreateTrayInterface(Settings.ProductName, virtualDrive.SyncService, exitEvent); + Thread tryIconThread = WindowsTrayInterface.CreateTrayInterface(Settings.ProductName, virtualDrive, exitEvent); - // Start processing OS file system calls. + // Start processing OS file system calls, monitoring changes in user file system and remote storge. //engine.ChangesProcessingEnabled = false; await virtualDrive.StartAsync(); - - // Start monitoring changes in remote file system. - //await RemoteStorageMonitorInstance.StartAsync(); #if DEBUG // Opens Windows File Manager with user file system folder and remote storage folder. ShowTestEnvironment(); #endif // Keep this application running until user input. - exitKey = WebDAVDrive.UI.ConsoleManager.WaitConsoleReadKey(exitEvent); + ConsoleManager.WaitConsoleReadKey(exitEvent); //wait until the button "Exit" is pressed or any key in console is peressed to stop application exitEvent.WaitOne(); + exitKey = exitEvent.KeyInfo; } finally { virtualDrive.Dispose(); - RemoteStorageMonitorInstance.Dispose(); } if (exitKey.KeyChar == 'q') @@ -159,7 +151,7 @@ await Registrar.RegisterAsync(SyncRootId, Settings.UserFileSystemRootPath, Setti try { - Directory.Delete(Config.Settings.ServerDataFolderPath, true); + Directory.Delete(Settings.ServerDataFolderPath, true); } catch (Exception ex) { diff --git a/WebDAVDrive/RemoteStorageMonitor.cs b/WebDAVDrive/RemoteStorageMonitor.cs index f87eab9..4b0df1a 100644 --- a/WebDAVDrive/RemoteStorageMonitor.cs +++ b/WebDAVDrive/RemoteStorageMonitor.cs @@ -13,6 +13,7 @@ using ITHit.FileSystem; using ITHit.FileSystem.Windows; using ITHit.FileSystem.Samples.Common; +using ITHit.FileSystem.Samples.Common.Windows; namespace WebDAVDrive { @@ -23,31 +24,47 @@ namespace WebDAVDrive ///
    internal class RemoteStorageMonitor : Logger, IDisposable { - /// /// Remote storage path. Folder to monitor changes in. /// private string remoteStorageRootPath; + /// + /// Virtual drive instance. This class will call methods + /// to update user file system when any data is changed in the remote storage: + /// , + /// , etc. + /// + private IVirtualDrive virtualDrive; + /// /// Creates instance of this class. /// /// Remote storage path. Folder that contains source files to monitor changes. + /// Virtual drive to send notifications about changes in the remote storage. /// Logger. - internal RemoteStorageMonitor(string remoteStorageRootPath, ILog log) : base("Remote Storage Monitor", log) + internal RemoteStorageMonitor(string remoteStorageRootPath, IVirtualDrive virtualDrive, ILog log) : base("Remote Storage Monitor", log) { this.remoteStorageRootPath = remoteStorageRootPath; + this.virtualDrive = virtualDrive; } /// - /// Starts monitoring changes on the server. + /// Starts monitoring changes in the remote storage. /// internal async Task StartAsync() { } - - + + /// + /// Stops monitoring changes in the remote storage. + /// + internal async Task StopAsync() + { + + } + private void Error(object sender, ErrorEventArgs e) { LogError(null, null, null, e.GetException()); diff --git a/WebDAVDrive/UserFile.cs b/WebDAVDrive/UserFile.cs deleted file mode 100644 index 7a361ae..0000000 --- a/WebDAVDrive/UserFile.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading.Tasks; - -using ITHit.FileSystem; -using ITHit.FileSystem.Samples.Common; -using ITHit.WebDAV.Client; - -namespace WebDAVDrive -{ - /// - /// Represents a file in the remote storage. - /// - /// You will change methods of this class to read/write data from/to your remote storage. - internal class UserFile : UserFileSystemItem, IUserFile - { - /// - /// Creates instance of this class. - /// - /// Path of this file in the user file system. - /// Information about file lock. Pass null if the item is not locked. - public UserFile(string userFileSystemFilePath) : base(userFileSystemFilePath) - { - - } - - /// - /// Reads file content from the remote storage. - /// - /// Offset in bytes in file content to start reading from. - /// Lenth in bytes of the file content to read. - /// File content that corresponds to the provided offset and length. - public async Task ReadAsync(long offset, long length) - { - // This method has a 60 sec timeout. - // To process longer requests modify the IFolder.TransferDataAsync() implementation. - - IFileAsync file = await Program.DavClient.OpenFileAsync(RemoteStorageUri); - using (Stream stream = await file.GetReadStreamAsync(offset, length)) - { - byte[] buffer = new byte[length]; - int bufferPos = 0; - int bytesRead = 0; - while ((bytesRead = await stream.ReadAsync(buffer, bufferPos, (int)length)) > 0) - { - bufferPos += bytesRead; - length -= bytesRead; - } - return buffer; - } - } - - public async Task ValidateDataAsync(long offset, long length) - { - return true; - } - - /// - /// Updates file in the remote storage. - /// - /// New information about the file, such as creation date, modification date, attributes, etc. - /// New file content or null if the file content is not modified. - /// Information about the lock. Caller passes null if the item is not locked. - /// New ETag returned from the remote storage. - public async Task UpdateAsync(IFileBasicInfo fileInfo, Stream content = null, ServerLockInfo lockInfo = null) - { - return await CreateOrUpdateFileAsync(new Uri(RemoteStorageUri), fileInfo, FileMode.Open, content, lockInfo); - } - } -} diff --git a/WebDAVDrive/VirtualDrive.cs b/WebDAVDrive/VirtualDrive.cs index 2a20280..514be51 100644 --- a/WebDAVDrive/VirtualDrive.cs +++ b/WebDAVDrive/VirtualDrive.cs @@ -2,37 +2,84 @@ using System.Collections.Generic; using System.Text; using System.Threading.Tasks; +using System.IO; using log4net; using ITHit.FileSystem.Samples.Common; -using System.IO; +using ITHit.FileSystem.Samples.Common.Windows; +using ITHit.FileSystem; namespace WebDAVDrive { - /// + /// + /// Processes OS file system calls, + /// synchronizes the user file system to the remote storage and back, + /// monitors files pinning and unpinning in the local file system, + /// monitores changes in the remote storage. + /// internal class VirtualDrive : VirtualDriveBase { + /// + /// Monitors changes in the remote storage, notifies the client and updates the user file system. + /// + private readonly RemoteStorageMonitor remoteStorageMonitor; + /// - public VirtualDrive(string license, string userFileSystemRootPath, ILog log, double syncIntervalMs) - : base(license, userFileSystemRootPath, log, syncIntervalMs) + public VirtualDrive(string license, string userFileSystemRootPath, Settings settings, ILog log) + : base(license, userFileSystemRootPath, settings, log) { - + string remoteStorageRootPath = Mapping.MapPath(userFileSystemRootPath); + remoteStorageMonitor = new RemoteStorageMonitor(remoteStorageRootPath, this, log); } /// - public override async Task GetUserFileSystemItemAsync(string userFileSystemPath) + public override async Task GetVirtualFileSystemItemAsync(string userFileSystemPath, FileSystemItemTypeEnum itemType, ILogger logger) { - if (File.Exists(userFileSystemPath)) + if (itemType == FileSystemItemTypeEnum.File) { - return new UserFile(userFileSystemPath); + return new VirtualFile(userFileSystemPath, logger); } - if (Directory.Exists(userFileSystemPath)) + else { - return new UserFolder(userFileSystemPath); + return new VirtualFolder(userFileSystemPath, logger); } + } - // When a file handle is being closed during delete, the file does not exist, return null. - return null; + /// + /// Enables or disables full synchronization service, user file sytem monitor and remote storage monitor. + /// + /// Pass true to start synchronization. Pass false - to stop. + public override async Task SetEnabledAsync(bool enabled) + { + await base.SetEnabledAsync(enabled); + + if(enabled) + { + await remoteStorageMonitor.StartAsync(); + } + else + { + await remoteStorageMonitor.StopAsync(); + } + } + + + private bool disposedValue; + + protected override void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + remoteStorageMonitor.Dispose(); + } + + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + disposedValue = true; + } + base.Dispose(disposing); } } } diff --git a/WebDAVDrive/VirtualFile.cs b/WebDAVDrive/VirtualFile.cs new file mode 100644 index 0000000..d009c34 --- /dev/null +++ b/WebDAVDrive/VirtualFile.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; + +using ITHit.FileSystem; +using ITHit.FileSystem.Samples.Common; +using ITHit.WebDAV.Client; + +namespace WebDAVDrive +{ + /// + /// Represents a file on a virtual drive. + /// + /// You will change methods of this class to read/write data from/to your remote storage. + internal class VirtualFile : VirtualFileSystemItem, IVirtualFile + { + /// + /// Creates instance of this class. + /// + /// Path of this file in the user file system. + /// Logger. + public VirtualFile(string userFileSystemFilePath, ILogger logger) + : base(userFileSystemFilePath, logger) + { + + } + + /// + /// Reads this file content from the remote storage. + /// + /// File content offset, in bytes, to start reading from. + /// Data length to read, in bytes. + /// Total file size, in bytes. + /// + /// You will use this parameter to return file content by + /// calling + /// + public async Task ReadAsync(long offset, long length, long fileSize, ITransferDataResultContext resultContext) + { + // On Windows this method has a 60 sec timeout. + // To process longer requests and reset the timout timer call the resultContext.ReportProgress() or resultContext.ReturnData() method. + + IFileAsync file = await Program.DavClient.OpenFileAsync(RemoteStorageUri); + using (Stream stream = await file.GetReadStreamAsync(offset, fileSize)) + { + const long MAX_CHUNK_SIZE = 0x500000; //5Mb + + long chunkSize = Math.Min(MAX_CHUNK_SIZE, length); + + stream.Seek(offset, SeekOrigin.Begin); + + long total = offset + length; + byte[] buffer = new byte[chunkSize]; + long bytesRead; + while ((bytesRead = await stream.ReadAsync(buffer, 0, (int)chunkSize)) > 0) + { + resultContext.ReturnData(buffer, offset, bytesRead); + offset += bytesRead; + length -= bytesRead; + chunkSize = Math.Min(MAX_CHUNK_SIZE, length); + if (offset >= total) + { + return; + } + } + } + } + + public async Task ValidateDataAsync(long offset, long length) + { + return true; + } + + /// + /// Updates file in the remote storage. + /// + /// New information about the file, such as creation date, modification date, attributes, etc. + /// New file content or null if the file content is not modified. + /// The ETag to be sent to the remote storage as part of the update request to make sure the content is not overwritten. + /// Information about the lock. Caller passes null if the item is not locked. + /// The new ETag returned from the remote storage. + public async Task UpdateAsync(IFileMetadata fileInfo, Stream content = null, string eTagOld = null, ServerLockInfo lockInfo = null) + { + return await CreateOrUpdateFileAsync(new Uri(RemoteStorageUri), fileInfo, FileMode.Open, content, eTagOld, lockInfo); + } + } +} diff --git a/WebDAVDrive/UserFileSystemItem.cs b/WebDAVDrive/VirtualFileSystemItem.cs similarity index 71% rename from WebDAVDrive/UserFileSystemItem.cs rename to WebDAVDrive/VirtualFileSystemItem.cs index 686d8d8..5a097ce 100644 --- a/WebDAVDrive/UserFileSystemItem.cs +++ b/WebDAVDrive/VirtualFileSystemItem.cs @@ -12,10 +12,10 @@ namespace WebDAVDrive { /// - /// Represents a file or a folder in the remote storage. Contains methods common for both files and folders. + /// Represents a file or a folder on a virtual drive. Contains methods common for both files and folders. /// /// You will change methods of this class to read/write data from/to your remote storage. - internal class UserFileSystemItem : IUserFileSystemItem + internal class VirtualFileSystemItem : IVirtualFileSystemItem, IVirtualLock { /// /// Path of this file of folder in the user file system. @@ -27,18 +27,25 @@ internal class UserFileSystemItem : IUserFileSystemItem /// protected string RemoteStorageUri; + /// + /// Logger. + /// + protected readonly ILogger Logger; + /// /// Creates instance of this class. /// /// Path of this file of folder in the user file system. - public UserFileSystemItem(string userFileSystemPath) + /// Logger. + public VirtualFileSystemItem(string userFileSystemPath, ILogger logger) { this.UserFileSystemPath = userFileSystemPath; this.RemoteStorageUri = Mapping.MapPath(userFileSystemPath); + this.Logger = logger; } /// - /// Renames or moves file or folder to a new location in the remote storage. + /// Renames or moves this file or folder to a new location in the remote storage. /// /// Target path of this file or folder in the user file system. public async Task MoveToAsync(string userFileSystemNewPath) @@ -58,33 +65,25 @@ public async Task DeleteAsync() } /// - /// Creates or updates file in the remote storage. + /// Creates or updates this item in the remote storage. /// /// Uri of the file to be created or updated in the remote storage. - /// New information about the file, such as modification date, attributes, custom data, etc. + /// New information about the file or folder, such as modification date, attributes, custom data, etc. /// Specifies if a new file should be created or existing file should be updated. /// New file content or null if the file content is not modified. + /// The ETag to be sent to the remote storage as part of the update request to make sure the content is not overwritten. /// Information about the lock. Caller passes null if the item is not locked. - /// New ETag returned from the remote storage. - protected async Task CreateOrUpdateFileAsync(Uri remoteStorageUri, IFileBasicInfo newInfo, FileMode mode, Stream content = null, ServerLockInfo lockInfo = null) + /// The new ETag returned from the remote storage. + protected static async Task CreateOrUpdateFileAsync( + Uri remoteStorageUri, IFileSystemItemMetadata newInfo, FileMode mode, Stream content = null, string eTagOld = null, ServerLockInfo lockInfo = null) { - string eTag = null; - string lockToken = null; - // Get ETag and lock-token here and send it to the remote storage with the new item content/info. - if (mode == FileMode.Open) - { - // Get ETag. - eTag = await ETag.GetETagAsync(UserFileSystemPath); - - // Get lock-token. - lockToken = lockInfo?.LockToken; - } - + string eTagNew = null; if (content != null || mode == FileMode.CreateNew) { long contentLength = content != null ? content.Length : 0; - IWebRequestAsync request = await Program.DavClient.GetFileWriteRequestAsync(remoteStorageUri, null, contentLength, 0, -1, lockToken, eTag); + IWebRequestAsync request = await Program.DavClient.GetFileWriteRequestAsync( + remoteStorageUri, null, contentLength, 0, -1, lockInfo?.LockToken, eTagOld); // Update remote storage file content. using (Stream davContentStream = await request.GetRequestStreamAsync()) @@ -94,26 +93,27 @@ protected async Task CreateOrUpdateFileAsync(Uri remoteStorageUri, IFile await content.CopyToAsync(davContentStream); } - // Get ETag returned by the server, if any. + // Get the new ETag returned by the server (if any). + // We return the new ETag to the Engine to be stored with the file unlil the next update. IWebResponseAsync response = await request.GetResponseAsync(); - eTag = response.Headers["ETag"]; + eTagNew = response.Headers["ETag"]; response.Close(); } } - return eTag; + return eTagNew; } /// - /// Locks the item in the remote storage. + /// Locks this item in the remote storage. /// /// Lock info that conains lock-token returned by the remote storage. /// /// Lock your item in the remote storage in this method and receive the lock-token. /// Return a new object with the being set from this function. - /// The will become available via methods parameter when the - /// item in the remote storage should be updated. Supply the lock-token during the update request in - /// and method calls. + /// The will become available via and + /// methods lockInfo parameter when the item in the remote storage should be updated. + /// Supply the lock-token as part of your server update request. /// public async Task LockAsync() { @@ -127,7 +127,7 @@ public async Task LockAsync() } /// - /// Unlocks the item in the remote storage. + /// Unlocks this item in the remote storage. /// /// Lock token to unlock the item in the remote storage. /// diff --git a/WebDAVDrive/UserFolder.cs b/WebDAVDrive/VirtualFolder.cs similarity index 62% rename from WebDAVDrive/UserFolder.cs rename to WebDAVDrive/VirtualFolder.cs index 6b29afb..061b7fd 100644 --- a/WebDAVDrive/UserFolder.cs +++ b/WebDAVDrive/VirtualFolder.cs @@ -12,31 +12,32 @@ namespace WebDAVDrive { /// - /// Represents a folder in the remote storage. Provides methods for enumerating this folder children, + /// Represents a folder on a virtual drive. Provides methods for enumerating this folder children, /// creating files and folders and updating this folder information (creatin date, modification date, attributes, etc.). /// /// You will change methods of this class to read/write data from/to your remote storage. - internal class UserFolder : UserFileSystemItem, IUserFolder + internal class VirtualFolder : VirtualFileSystemItem, IVirtualFolder { /// /// Creates instance of this class. /// /// Path of this folder in the user file system. - /// Information about file lock. Pass null if the item is not locked. - public UserFolder(string userfileSystemFolderPath) : base(userfileSystemFolderPath) + /// Logger. + public VirtualFolder(string userfileSystemFolderPath, ILogger logger) + : base(userfileSystemFolderPath, logger) { } /// - /// Gets list of files and folders in this folder in the remote storage. + /// Gets list of files and folders in this folder. /// /// Search pattern. /// /// List of files and folders located in this folder in the remote /// storage that correstonds with the provided search pattern. /// - public async Task> EnumerateChildrenAsync(string pattern) + public async Task> EnumerateChildrenAsync(string pattern) { // This method has a 60 sec timeout. // To process longer requests modify the IFolder.GetChildrenAsync() implementation. @@ -52,10 +53,10 @@ public async Task> EnumerateChildrenAsync(s remoteStorageChildren = await Program.DavClient.GetChildrenAsync(new Uri(RemoteStorageUri), false); } - List userFileSystemChildren = new List(); + List userFileSystemChildren = new List(); foreach (IHierarchyItemAsync remoteStorageItem in remoteStorageChildren) { - FileSystemItemBasicInfo itemInfo = Mapping.GetUserFileSystemItemBasicInfo(remoteStorageItem); + FileSystemItemMetadata itemInfo = Mapping.GetUserFileSystemItemMetadata(remoteStorageItem); userFileSystemChildren.Add(itemInfo); } @@ -63,23 +64,23 @@ public async Task> EnumerateChildrenAsync(s } /// - /// Creates a file in the remote storage. + /// Creates a new file in this folder in the remote storage. /// /// Information about the new file. /// New file content. - /// New ETag returned from the remote storage. - public async Task CreateFileAsync(IFileBasicInfo fileInfo, Stream content) + /// The new ETag returned from the remote storage. + public async Task CreateFileAsync(IFileMetadata fileInfo, Stream content) { Uri newFileUri = new Uri(new Uri(RemoteStorageUri), fileInfo.Name); return await CreateOrUpdateFileAsync(newFileUri, fileInfo, FileMode.CreateNew, content); } /// - /// Creates a folder in the remote storage. + /// Creates a new folder in the remote storage. /// /// Information about the new folder. - /// New ETag returned from the remote storage. - public async Task CreateFolderAsync(IFolderBasicInfo folderInfo) + /// The new ETag returned from the remote storage. + public async Task CreateFolderAsync(IFolderMetadata folderInfo) { Uri newFolderUri = new Uri(new Uri(RemoteStorageUri), folderInfo.Name); await Program.DavClient.CreateFolderAsync(newFolderUri); @@ -87,15 +88,15 @@ public async Task CreateFolderAsync(IFolderBasicInfo folderInfo) } /// - /// Updates folder in the remote storage. + /// Updates this folder info in the remote storage. /// /// New folder information. - /// Information about the lock. Caller passes null if the item is not locked. - /// New ETag returned from the remote storage. - public async Task UpdateAsync(IFolderBasicInfo folderInfo, ServerLockInfo lockInfo = null) + /// The ETag to be sent to the remote storage as part of the update request to make sure the content is not overwritten. + /// Information about the lock. Null is passed if the item is not locked. + /// The new ETag returned from the remote storage. + public async Task UpdateAsync(IFolderMetadata folderInfo, string eTagOld = null, ServerLockInfo lockInfo = null) { return null; } - } } diff --git a/WebDAVDrive/WebDAVDrive.csproj b/WebDAVDrive/WebDAVDrive.csproj index c855a02..7a73a28 100644 --- a/WebDAVDrive/WebDAVDrive.csproj +++ b/WebDAVDrive/WebDAVDrive.csproj @@ -1,4 +1,4 @@ - + Exe netcoreapp3.1 @@ -33,7 +33,7 @@ - + diff --git a/WebDAVDrive/appsettings.json b/WebDAVDrive/appsettings.json index d9eaff7..a29c5d7 100644 --- a/WebDAVDrive/appsettings.json +++ b/WebDAVDrive/appsettings.json @@ -15,6 +15,8 @@ "WebDAVClientLicense": "", // Your WebDAV server URL. + // In case this parameter is empty, the dialog to specify the server URL will be displayed during first start. + // In this case the URL is saved to registry under HKEY_CURRENT_USER\SOFTWARE\ "WebDAVServerUrl": "https://server/", //Your virtual file system will be mounted under this path.