diff --git a/ITHit.FileSystem.Samples.Common/ITHit.FileSystem.Samples.Common.csproj b/Common/Common.csproj similarity index 63% rename from ITHit.FileSystem.Samples.Common/ITHit.FileSystem.Samples.Common.csproj rename to Common/Common.csproj index f0ceb68..e61a187 100644 --- a/ITHit.FileSystem.Samples.Common/ITHit.FileSystem.Samples.Common.csproj +++ b/Common/Common.csproj @@ -5,9 +5,9 @@ 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. + Contains functionality common for all Virtual Drive samples, both for Windows and macOS. - + \ No newline at end of file diff --git a/ITHit.FileSystem.Samples.Common/CustomColumnIds.cs b/Common/CustomColumnIds.cs similarity index 100% rename from ITHit.FileSystem.Samples.Common/CustomColumnIds.cs rename to Common/CustomColumnIds.cs diff --git a/ITHit.FileSystem.Samples.Common/FileMetadataExt.cs b/Common/FileMetadataExt.cs similarity index 100% rename from ITHit.FileSystem.Samples.Common/FileMetadataExt.cs rename to Common/FileMetadataExt.cs diff --git a/ITHit.FileSystem.Samples.Common/FileSystemItemMetadataExt.cs b/Common/FileSystemItemMetadataExt.cs similarity index 90% rename from ITHit.FileSystem.Samples.Common/FileSystemItemMetadataExt.cs rename to Common/FileSystemItemMetadataExt.cs index a763319..e27ca31 100644 --- a/ITHit.FileSystem.Samples.Common/FileSystemItemMetadataExt.cs +++ b/Common/FileSystemItemMetadataExt.cs @@ -19,7 +19,7 @@ public class FileSystemItemMetadataExt : IFileSystemItemMetadata public FileAttributes Attributes { get; set; } /// - public byte[] CustomData { get; set; } + public byte[] CustomData { get; set; } = new byte[] { }; /// public DateTimeOffset CreationTime { get; set; } @@ -48,11 +48,11 @@ public class FileSystemItemMetadataExt : IFileSystemItemMetadata /// she/he tries to update this item . /// Read-only flag does not protect this item from modifications. /// - public bool LockedByAnotherUser { get; set; } + public bool LockedByAnotherUser { get; set; } = false; /// /// Custom columns data to be displayed in the file manager. /// - public IEnumerable CustomProperties { get; set; } + public IEnumerable CustomProperties { get; set; } = new FileSystemItemPropertyData[] { }; } } diff --git a/ITHit.FileSystem.Samples.Common/FolderMetadataExt.cs b/Common/FolderMetadataExt.cs similarity index 100% rename from ITHit.FileSystem.Samples.Common/FolderMetadataExt.cs rename to Common/FolderMetadataExt.cs diff --git a/ITHit.FileSystem.Samples.Common/Settings.cs b/Common/Settings.cs similarity index 90% rename from ITHit.FileSystem.Samples.Common/Settings.cs rename to Common/Settings.cs index 4f52821..b692f42 100644 --- a/ITHit.FileSystem.Samples.Common/Settings.cs +++ b/Common/Settings.cs @@ -54,11 +54,6 @@ public class Settings /// public string ProductName { get; set; } - /// - /// Path to the folder that stores ETags, locks and other data associated with files and folders. - /// - public string ServerDataFolderPath { get; set; } - /// /// Automatically lock the file in remote storage when a file handle is being opened for writing, unlock on close. /// diff --git a/Common/StreamCopy.cs b/Common/StreamCopy.cs new file mode 100644 index 0000000..cf07e7c --- /dev/null +++ b/Common/StreamCopy.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; + +namespace ITHit.FileSystem.Samples.Common +{ + /// + /// Stream helper functions. + /// + public static class StreamCopy + { + + /// + /// Asynchronously copies specified number of bytes from current stream to destination stream, using a specified buffer size. + /// + /// Source stream. + /// The stream to which the contents of the current file stream will be copied. + /// The size, in bytes, of the buffer. This value must be greater than zero. + /// Number of bytes to copy. + public static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, long count) + { + byte[] buffer = new byte[bufferSize]; + int read; + while (count > 0 + && (read = await source.ReadAsync(buffer, 0, (int)Math.Min(buffer.LongLength, count))) > 0) + { + await destination.WriteAsync(buffer, 0, read); + count -= read; + } + } + } +} diff --git a/ITHit.FileSystem.Samples.Common/SynchronizationState.cs b/Common/SynchronizationState.cs similarity index 100% rename from ITHit.FileSystem.Samples.Common/SynchronizationState.cs rename to Common/SynchronizationState.cs diff --git a/ITHit.FileSystem.Samples.Common.Windows/ClientLockFailedException.cs b/ITHit.FileSystem.Samples.Common.Windows/ClientLockFailedException.cs deleted file mode 100644 index 632e8c4..0000000 --- a/ITHit.FileSystem.Samples.Common.Windows/ClientLockFailedException.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace ITHit.FileSystem.Samples.Common.Windows -{ - /// - /// 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 class ClientLockFailedException : IOException - { - /// - /// Creates instance of this class. - /// - /// The error message that explains the reason for the exception. - public ClientLockFailedException(string message) : base(message) - { - } - - /// - /// Creates instance of this class with a specified - /// error message and a reference to the inner exception that is the cause of this - /// exception. - /// - /// The error message that explains the reason for the exception. - /// - /// The exception that is the cause of the current exception. If the innerException - /// parameter is not null, the current exception is raised in a catch block that - /// handles the inner exception. - /// - public ClientLockFailedException(string message, Exception innerException) : base(message, innerException) - { - } - - /// - /// Creates instance of this class with a message and HRESULT code. - /// - /// The error message that explains the reason for the exception. - /// An integer identifying the error that has occurred. - public ClientLockFailedException(string message, int hresult) : base(message, hresult) - { - } - } -} diff --git a/ITHit.FileSystem.Samples.Common.Windows/CustomData.cs b/ITHit.FileSystem.Samples.Common.Windows/CustomData.cs deleted file mode 100644 index 747ab26..0000000 --- a/ITHit.FileSystem.Samples.Common.Windows/CustomData.cs +++ /dev/null @@ -1,138 +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; - -namespace ITHit.FileSystem.Samples.Common.Windows -{ - /// - /// Custom data stored with a file or folder placeholder, such original file/folder path. Max 4KB. - /// - /// To avoid storing metatadata and keep footprit small, this class is is using custom serialization. - public class CustomData - { - /// - /// Keeps the original file/folder path. Used to sync file/folder from user file system to remote storage - /// if this app was not running when the file/folder was moved or renamed. This field allows to avoid - /// delete-create sequence during client to server synchronization after app failure. - /// - public string OriginalPath = ""; - - /// - /// Serializes all custom data fields into the byte array. - /// - /// Byte array representing custom data. - public byte[] Serialize() - { - using (MemoryStream m = new MemoryStream()) - { - using (BinaryWriter writer = new BinaryWriter(m)) - { - writer.Write(OriginalPath); - } - return m.ToArray(); - } - } - - /// - /// Deserializes custom data from byte array into object. - /// - /// Byte array representing custom data. - /// - public static CustomData Deserialize(byte[] data) - { - if(data == null) - { - throw new ArgumentNullException("data"); - } - - CustomData obj = new CustomData(); - using (MemoryStream m = new MemoryStream(data)) - { - using (BinaryReader reader = new BinaryReader(m)) - { - obj.OriginalPath = reader.ReadString(); - } - } - return obj; - } - } - - /// - /// Placeholder methods to get and set custom data associated with a placeholder, such as OriginalPath. - /// - internal static class PlaceholderItemExtensions - { - public static void SetCustomData(this PlaceholderItem placeholder, string originalPath) - { - CustomData customData = new CustomData { OriginalPath = originalPath }; - placeholder.SetCustomData(customData.Serialize()); - } - - public static void SetCustomData(Microsoft.Win32.SafeHandles.SafeFileHandle safeHandle, string originalPath) - { - CustomData customData = new CustomData { OriginalPath = originalPath }; - PlaceholderItem.SetCustomData(safeHandle, customData.Serialize()); - } - - public static void SetOriginalPath(this PlaceholderItem placeholder, string originalPath) - { - byte[] customDataRaw = placeholder.GetCustomData(); - CustomData customData = (customDataRaw.Length > 0) ? CustomData.Deserialize(customDataRaw) : new CustomData(); - - customData.OriginalPath = originalPath; - placeholder.SetCustomData(customData.Serialize()); - } - - public static string GetOriginalPath(this PlaceholderItem placeholder) - { - byte[] customDataRaw = placeholder.GetCustomData(); - CustomData customData = (customDataRaw.Length > 0) ? CustomData.Deserialize(customDataRaw) : new CustomData(); - return customData.OriginalPath; - } - - /// - /// Returns true if the file was moved in the user file system and - /// changes not yet synched to the remote storage. - /// - public static bool IsMoved(this PlaceholderItem placeholder) - { - // If original path was never set, the file was just created, not moved. - string originalPath = placeholder.GetOriginalPath(); - if (string.IsNullOrEmpty(originalPath)) - { - return false; - } - - // Otherwise verify that current file path and original file path are equal. - return !originalPath.TrimEnd(Path.DirectorySeparatorChar).Equals(placeholder.Path.TrimEnd(Path.DirectorySeparatorChar), StringComparison.InvariantCultureIgnoreCase); - } - - /// - /// 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, VirtualDriveBase virtualDrive) - { - // ETag absence signals that the item is new. - // However, ETag file may not exists during move operation, - // additionally checking OriginalPath presence. - // Can not rely on OriginalPath only, - // because MS Office files are being deleted and re-created during transactional save. - - string originalPath = placeholder.GetOriginalPath(); - - bool eTagFileExists = File.Exists(virtualDrive.GetETagManager(placeholder.Path).ETagFilePath); - - return !eTagFileExists && string.IsNullOrEmpty(originalPath); - } - - } -} diff --git a/ITHit.FileSystem.Samples.Common.Windows/FsPath.cs b/ITHit.FileSystem.Samples.Common.Windows/FsPath.cs deleted file mode 100644 index f4dd638..0000000 --- a/ITHit.FileSystem.Samples.Common.Windows/FsPath.cs +++ /dev/null @@ -1,304 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using Windows.Storage; -using FileAttributes = System.IO.FileAttributes; - -namespace ITHit.FileSystem.Samples.Common.Windows -{ - /// - /// Provides file system operations. Helps determining file and folder existence and creating file and folder items. - /// - public static class FsPath - { - /// - /// Returns true if the path points to a file. False - otherwise. - /// - /// Throws exception if the file/folder does not exists. - /// Path to the file or folder. - /// True if the path is file. False - otherwise. - public static bool IsFile(string path) - { - return !IsFolder(path); - } - - /// - /// Returns true if the path points to a folder. False - otherwise. - /// - /// Throws exception if the file/folder does not exists. - /// Path to the file or folder. - /// True if the path is folder. False - otherwise. - public static bool IsFolder(string path) - { - FileAttributes attributes = File.GetAttributes(path); - return (attributes & FileAttributes.Directory) == FileAttributes.Directory; - } - - /// - /// Returns true if a file or folder exists under the specified path. False - otherwise. - /// - /// Does not throw exceptions. - /// Path to the file or folder. - /// True if the file or folder exists under specified path. False - otherwise. - 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. - /// - /// Path to the file or folder. - public static bool IsRecycleBin(string path) - { - return path.IndexOf("\\$Recycle.Bin", StringComparison.InvariantCultureIgnoreCase) != -1; - } - - /// - /// Returns storage item or null if item does not eists. - /// - /// Path to the file or folder. - /// - /// Instance of or - /// that corresponds to path or null if item does not exists. - /// - public static async Task GetStorageItemAsync(string path) - { - if (File.Exists(path)) - { - return await StorageFile.GetFileFromPathAsync(path); - } - if (Directory.Exists(path)) - { - return await StorageFolder.GetFolderFromPathAsync(path); - } - return null; - } - - /// - /// Returns folder or file item or null if the item does not exists. - /// - /// Path to the file or folder. - /// - /// Instance of or - /// that corresponds to path or null if item does not exists. - /// - public static FileSystemInfo GetFileSystemItem(string path) - { - if (File.Exists(path)) - { - return new FileInfo(path); - } - if (Directory.Exists(path)) - { - return new DirectoryInfo(path); - } - return null; - } - - /// - /// Gets file or folder attributes in a human-readable form. - /// - /// Path to the file or folder. - /// String that represents file or folder attributes or null if the file/folder is not found. - public static string GetAttString(string path) - { - try - { - return File.GetAttributes(path).ToString(); - } - catch - { - return null; - } - } - - /// - /// Returns true if the file is in ~XXXXX.tmp, pptXXXX.tmp format, false - otherwise. - /// - /// Path to a file. - 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.GetFileNameWithoutExtension(path).Length == 7)) && Path.GetExtension(path).Equals(".tmp", StringComparison.InvariantCultureIgnoreCase)); // Excel temp files - } - - /// - /// Returns true if file system contains MS Office lock file (~$file.ext) in file - /// system that corresponds to the provided path to MS Office file. - /// - /// Path to the MS Office file. - internal static bool IsMsOfficeLocked(string path) - { - string lockPath = GetLockPathFromMsOfficePath(path); - return lockPath != null; - } - - /// - /// Returns true if the provided path points to MS Office lock file (~$file.ext). - /// - /// Path to the MS Office lock file. - internal static bool IsMsOfficeLockFile(string path) - { - return Path.GetFileName(path).StartsWith("~$"); - } - - /// - /// Returns MS Office lock file path if such file exists. - /// - /// MS Office file path. - /// Lock file path. - /// - /// mydoc.docx -> ~$mydoc.docx - /// mydocfi.docx -> ~$ydocfi.docx - /// mydocfile.docx -> ~$docfile.docx - /// mydocfile.pptx -> ~$mydocfile.pptx - /// mydocfile.ppt -> ~$mydocfile.ppt - /// mydocfile.xlsx -> ~$mydocfile.xlsx - /// mydocfile.xls -> null - /// - private static string GetLockPathFromMsOfficePath(string msOfficeFilePath) - { - string msOfficeLockFilePath = null; - int separatorIndex = msOfficeFilePath.LastIndexOf(Path.DirectorySeparatorChar); - if ((separatorIndex != -1) && !string.IsNullOrEmpty(Path.GetExtension(msOfficeFilePath))) - { - msOfficeLockFilePath = msOfficeFilePath.Insert(separatorIndex + 1, "~$"); - if(FsPath.Exists(msOfficeLockFilePath)) - { - return msOfficeLockFilePath; - } - int fileNameLength = Path.GetFileNameWithoutExtension(msOfficeFilePath).Length; - if (fileNameLength > 6) - { - int removeChars = fileNameLength == 7 ? 1 : 2; - msOfficeLockFilePath = msOfficeLockFilePath.Remove(separatorIndex + 1 + "~$".Length, removeChars); - if (FsPath.Exists(msOfficeLockFilePath)) - { - return msOfficeLockFilePath; - } - } - } - return null; - } - - /// - /// Returns true if the file or folder is marked with Hidden or Temporaty attributes. - /// - /// Path to a file or folder. - /// - /// True if the file or folder is marked with Hidden or Temporaty attributes. - /// Returns false if no Hidden or Temporaty attributes found or file/folder does not exists. - /// - private static bool IsHiddenOrTemp(string path) - { - if(!FsPath.Exists(path)) - { - return false; - } - - FileAttributes att = File.GetAttributes(path); - return ( (att & System.IO.FileAttributes.Hidden) != 0) - || ((att & System.IO.FileAttributes.Temporary) != 0); - } - - /// - /// Returns true if the file or folder should not be syched between the user file - /// system and the remote storage. False - otherwise. - /// - /// Path to a file or folder. - public static bool AvoidSync(string path) - { - return IsMsOfficeLockFile(path) || IsMsOfficeLocked(path) || IsMsOfficeTemp(path) || IsHiddenOrTemp(path); - } - - /// - /// Returns true if the file or folder should not be automatically locked. False - otherwise. - /// - /// Path to a file or folder. - public static bool AvoidAutoLock(string path) - { - return IsMsOfficeLockFile(path) || IsMsOfficeTemp(path) || IsHiddenOrTemp(path); - } - - /// - /// Gets formatted file size or null for folders or if the file is not found. - /// - /// Path to a file or folder. - public static string Size(string path) - { - if (!File.Exists(path)) - { - return null; - } - - long length; - try - { - length = new FileInfo(path).Length; - } - catch - { - 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) - { - return "0" + suf[0]; - } - long bytes = Math.Abs(length); - int place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024))); - double num = Math.Round(bytes / Math.Pow(1024, place), 1); - return (Math.Sign(length) * num).ToString() + suf[place]; - } - - /// - /// Returns true if the file is locked for writing, false otherwise. - /// - /// Path ot a file. - /// True if the file is locked for writing. False otherwise. - public static bool IsWriteLocked(string path) - { - // To avoid hydration (typically during the file deletion) we check the Offline attribute. - // If file is marked offline, it is not open for writing. - if (((int)File.GetAttributes(path) & (int)Syncronyzation.FileAttributesExt.Offline) != 0) - { - return false; - } - - try - { - using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)) - { - stream.Close(); - } - } - catch (IOException) - { - return true; - } - - return false; - } - } -} diff --git a/ITHit.FileSystem.Samples.Common.Windows/Locks/LockManager.cs b/ITHit.FileSystem.Samples.Common.Windows/Locks/LockManager.cs deleted file mode 100644 index 1389007..0000000 --- a/ITHit.FileSystem.Samples.Common.Windows/Locks/LockManager.cs +++ /dev/null @@ -1,185 +0,0 @@ -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 deleted file mode 100644 index b421549..0000000 --- a/ITHit.FileSystem.Samples.Common.Windows/Locks/LockSync.cs +++ /dev/null @@ -1,185 +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.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.Windows/Syncronyzation/ClientToServerSync.cs b/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/ClientToServerSync.cs deleted file mode 100644 index 7c124a5..0000000 --- a/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/ClientToServerSync.cs +++ /dev/null @@ -1,183 +0,0 @@ -using ITHit.FileSystem; -using ITHit.FileSystem.Windows; -using log4net; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Windows.Storage; -using Windows.Storage.Provider; - -namespace ITHit.FileSystem.Samples.Common.Windows.Syncronyzation -{ - /// - /// User File System to Remote Storage synchronization. - /// - /// In most cases you can use this class in your project without any changes. - internal class ClientToServerSync : Logger - { - /// - /// Virtual drive. - /// - private VirtualDriveBase virtualDrive; - - /// - /// Creates instance of this class. - /// - /// Logger. - internal ClientToServerSync(VirtualDriveBase virtualDrive, ILog log) : base("UFS -> RS Sync", log) - { - this.virtualDrive = virtualDrive; - } - - /// - /// Recursively synchronizes all files and folders moved in user file system with the remote storage. - /// Restores Original Path and 'locked' icon that are lost during MS Office transactional save. - /// Synchronizes only folders already loaded into the user file system. - /// - /// Folder path in user file system. - internal async Task SyncronizeMovedAsync(string userFileSystemFolderPath) - { - // In case of on-demand loading the user file system contains only a subset of the server files and folders. - // Here we sync folder only if its content already loaded into user file system (folder is not offline). - // The folder content is loaded inside IFolder.GetChildrenAsync() method. - if (new DirectoryInfo(userFileSystemFolderPath).Attributes.HasFlag(System.IO.FileAttributes.Offline)) - { - //LogMessage("Folder offline, skipping:", userFileSystemFolderPath); - return; - } - - IEnumerable userFileSystemChildren = Directory.EnumerateFileSystemEntries(userFileSystemFolderPath, "*"); - //LogMessage("Synchronizing:", userFileSystemFolderPath); - - foreach (string userFileSystemPath in userFileSystemChildren) - { - string userFileSystemOldPath = null; - try - { - - if (!PlaceholderItem.IsPlaceholder(userFileSystemPath)) - { - // Convert regular file/folder to placeholder. - // The file/folder was created or overwritten. - PlaceholderItem.ConvertToPlaceholder(userFileSystemPath, false); - LogMessage("Converted to placeholder", userFileSystemPath); - } - - - if (!FsPath.AvoidSync(userFileSystemPath)) - { - PlaceholderItem userFileSystemItem = PlaceholderItem.GetItem(userFileSystemPath); - if (userFileSystemItem.IsMoved()) - { - // Process items moved in user file system. - userFileSystemOldPath = userFileSystemItem.GetOriginalPath(); - 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(virtualDrive) && string.IsNullOrEmpty(userFileSystemOldPath)) - { - // Restore Original Path. - LogMessage("Setting Original Path", userFileSystemItem.Path); - userFileSystemItem.SetOriginalPath(userFileSystemItem.Path); - - // Restore the 'locked' icon. - ServerLockInfo existingLock = await virtualDrive.LockManager(userFileSystemPath, this).GetLockInfoAsync(); - await virtualDrive.GetUserFileSystemRawItem(userFileSystemPath, this).SetLockInfoAsync(existingLock); - } - } - } - } - catch (Exception ex) - { - LogError("Move in remote storage failed", userFileSystemOldPath, userFileSystemPath, ex); - } - - // Synchronize subfolders. - try - { - if (FsPath.IsFolder(userFileSystemPath)) - { - await SyncronizeMovedAsync(userFileSystemPath); - } - } - catch (Exception ex) - { - LogError("Folder move sync failed", userFileSystemPath, null, ex); - } - } - } - - /// - /// Recursively updates and creates files and folders in remote storage. - /// Synchronizes only folders already loaded into the user file system. - /// - /// Folder path in user file system. - internal async Task SyncronizeFolderAsync(string userFileSystemFolderPath) - { - // In case of on-demand loading the user file system contains only a subset of the server files and folders. - // Here we sync folder only if its content already loaded into user file system (folder is not offline). - // The folder content is loaded inside IFolder.GetChildrenAsync() method. - if (new DirectoryInfo(userFileSystemFolderPath).Attributes.HasFlag(System.IO.FileAttributes.Offline)) - { -// LogMessage("Folder offline, skipping:", userFileSystemFolderPath); - return; - } - - - IEnumerable userFileSystemChildren = Directory.EnumerateFileSystemEntries(userFileSystemFolderPath, "*"); -// LogMessage("Synchronizing:", userFileSystemFolderPath); - - - // Update and create files/folders in remote storage. - userFileSystemChildren = Directory.EnumerateFileSystemEntries(userFileSystemFolderPath, "*"); - foreach (string userFileSystemPath in userFileSystemChildren) - { - try - { - if (!FsPath.AvoidSync(userFileSystemPath)) - { - if (PlaceholderItem.GetItem(userFileSystemPath).IsNew(virtualDrive)) - { - // Create a file/folder in the remote storage. - FileSystemItemTypeEnum itemType = FsPath.GetItemType(userFileSystemPath); - await virtualDrive.GetRemoteStorageRawItem(userFileSystemPath, itemType, this).CreateAsync(); - } - else if (!PlaceholderItem.GetItem(userFileSystemPath).IsMoved()) - { - // Update file/folder in the remote storage. Unlock if auto-locked. - FileSystemItemTypeEnum itemType = FsPath.GetItemType(userFileSystemPath); - await virtualDrive.GetRemoteStorageRawItem(userFileSystemPath, itemType, this).UpdateAsync(); - } - } - } - catch (Exception ex) - { - LogError("Update failed", userFileSystemPath, null, ex); - } - - // Synchronize subfolders. - try - { - if (Directory.Exists(userFileSystemPath)) - { - await SyncronizeFolderAsync(userFileSystemPath); - } - } - catch (Exception ex) - { - LogError("Folder sync failed:", userFileSystemPath, null, ex); - } - } - } - } -} diff --git a/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/FileAttributesExt.cs b/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/FileAttributesExt.cs deleted file mode 100644 index adb90dc..0000000 --- a/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/FileAttributesExt.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace ITHit.FileSystem.Samples.Common.Windows.Syncronyzation -{ - - /// - /// File attributes that are not provided by .NET, but required for - /// detecting pinned/unpinned files and synchronization. - /// - /// - /// You can enable Attributes column in Windows File Manager to see them. - /// Some usefull file attributes reference: - /// - /// 4194304 (0x400000) FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS (M) - /// 4096 (0x1000) FILE_ATTRIBUTE_OFFLINE (O) - /// 1024 (0x400) FILE_ATTRIBUTE_REPARSE_POINT (L) - /// 16 (0x10) FILE_ATTRIBUTE_DIRECTORY (D) - /// (0x00080000) FILE_ATTRIBUTE_PINNED (P) - /// (0x00100000) FILE_ATTRIBUTE_UNPINNED (U) - /// 32 (0x20) FILE_ATTRIBUTE_ARCHIVE (A) - /// 512 (0x200) FILE_ATTRIBUTE_SPARSE_FILE - /// - [Flags] - internal enum FileAttributesExt - { - Pinned = 0x00080000, - Unpinned = 0x00100000, - Offline = 0x1000 - } -} diff --git a/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/FullSyncService.cs b/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/FullSyncService.cs deleted file mode 100644 index 326be16..0000000 --- a/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/FullSyncService.cs +++ /dev/null @@ -1,181 +0,0 @@ -using ITHit.FileSystem; -using ITHit.FileSystem.Windows; -using log4net; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Windows.Storage; -using Windows.Storage.Provider; - -namespace ITHit.FileSystem.Samples.Common.Windows.Syncronyzation -{ - /// - /// Performs a full synchronization between the user file system and the remote storage, recursively processing all folders. - /// - /// - /// This is a simple full synchronyzation example. - /// - /// You can use this class in your project out of the box or replace it with a more advanced algorithm. - /// - public class FullSyncService : Logger, IDisposable - { - /// - /// Current synchronization state. - /// - public virtual SynchronizationState SyncState { get; private set; } = SynchronizationState.Disabled; - - /// - /// Event, fired when synchronization state changes. - /// - public event SyncronizationEvent SyncEvent; - - /// - /// Virtual drive instance to which this synchronization service belongs. - /// - private VirtualDriveBase virtualDrive; - - /// - /// Timer to start synchronization. - /// - private System.Timers.Timer timer = null; - - /// - /// User file system path. - /// - private string userFileSystemRootPath; - - /// - /// Creates instance of this class. - /// - /// Synchronization interval in milliseconds. - /// User file system root path. - /// Logger. - internal FullSyncService(double syncIntervalMs, string userFileSystemRootPath, VirtualDriveBase virtualDrive, ILog log) : base("Sync Service", log) - { - timer = new System.Timers.Timer(syncIntervalMs); - timer.Elapsed += Timer_ElapsedAsync; - this.userFileSystemRootPath = userFileSystemRootPath; - this.virtualDrive = virtualDrive; - } - - /// - /// Starts synchronization. - /// - 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"); - } - - /// - /// Stops synchronization. - /// - public async Task StopAsync() - { - if (SyncState == SynchronizationState.Disabled) - { - return; - } - - timer.Stop(); - InvokeSyncEvent(SynchronizationState.Disabled); - LogMessage($"Stopped"); - } - - private void InvokeSyncEvent(SynchronizationState newState) - { - if ( (SyncState == SynchronizationState.Disabled) && (newState == SynchronizationState.Idle)) - { - return; - } - - SyncState = newState; - if (SyncEvent != null) - { - SyncEvent.Invoke(this, new SynchEventArgs(newState)); - } - } - - private async void Timer_ElapsedAsync(object sender, System.Timers.ElapsedEventArgs e) - { - try - { - 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); - - // UFS -> RS. Recursivery synchronize all updated/created/deleted file and folders present in the user file system. - await new ClientToServerSync(virtualDrive, Log).SyncronizeFolderAsync(userFileSystemRootPath); - - // 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.Idle); - } - catch(Exception ex) - { - LogError(null, null, null, ex); - } - finally - { - // Wait and than start synchronyzation again. - timer.Start(); - } - } - - private void Error(object sender, ErrorEventArgs e) - { - LogError(null, null, null, e.GetException()); - } - - private bool disposedValue = false; // To detect redundant calls - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - timer.Stop(); - timer.Dispose(); - LogMessage($"Disposed"); - } - - // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. - // TODO: set large fields to null. - - disposedValue = true; - } - } - - // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. - // ~SyncService() - // { - // // 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.Windows/Syncronyzation/IRemoteStorageRawItem.cs b/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/IRemoteStorageRawItem.cs deleted file mode 100644 index 1cd87f7..0000000 --- a/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/IRemoteStorageRawItem.cs +++ /dev/null @@ -1,37 +0,0 @@ -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 deleted file mode 100644 index ca27a76..0000000 --- a/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/RemoteStorageRawItem.cs +++ /dev/null @@ -1,641 +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.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)); - } - - this.virtualDrive = virtualDrive ?? throw new ArgumentNullException(nameof(virtualDrive)); - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - - this.userFileSystemPath = userFileSystemPath; - this.lockManager = virtualDrive.LockManager(userFileSystemPath, logger); - this.userFileSystemRawItem = virtualDrive.GetUserFileSystemRawItem(userFileSystemPath, logger); - } - - /// - /// 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) - { - FileSystemItemMetadataExt itemInfo; - - if (userFileSystemItem is FileInfo) - { - itemInfo = new FileMetadataExt(); - } - else - { - itemInfo = new FolderMetadataExt(); - } - - // 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) - { - ((FileMetadataExt)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) - { - // In this method you must either confirm or reject the move by calling call either - // IConfirmationResultContext.ReturnConfirmationResult() or IConfirmationResultContext.ReturnErrorResult(). - - string userFileSystemOldPath = userFileSystemPath; - - 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); - } - } - - // Confirm move. - 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 ReturnConfirmationResult() call 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 addition to thos, in case the target is the offline folder, the IFolder.GetChildrenAsync() method is called. - } - } - catch(Exception ex) - { - logger.LogError("Failed to move item", userFileSystemOldPath, userFileSystemNewPath, ex); - resultContext.ReturnErrorResult(); - - //string userFileSystemExPath = FsPath.Exists(userFileSystemNewPath) ? userFileSystemNewPath : userFileSystemOldPath; - //await virtualDrive.GetUserFileSystemRawItem(userFileSystemExPath, logger).SetUploadErrorStateAsync(ex); - } - } - - /// - /// 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); - } - } -} diff --git a/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/ServerToClientSync.cs b/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/ServerToClientSync.cs deleted file mode 100644 index 5524b73..0000000 --- a/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/ServerToClientSync.cs +++ /dev/null @@ -1,172 +0,0 @@ -using ITHit.FileSystem; -using ITHit.FileSystem.Windows; -using log4net; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Windows.Storage; -using Windows.Storage.Provider; - -namespace ITHit.FileSystem.Samples.Common.Windows.Syncronyzation -{ - /// - /// Synchronizes files and folders from remote storage to user file system. - /// - /// In most cases you can use this class in your project without any changes. - internal class ServerToClientSync : Logger - { - /// - /// Virtual drive. - /// - private VirtualDriveBase virtualDrive; - - /// - /// Creates instance of this class. - /// - /// instance. - /// Logger. - internal ServerToClientSync(VirtualDriveBase virtualDrive, ILog log) : base("UFS <- RS Sync", log) - { - this.virtualDrive = virtualDrive; - } - - /// - /// Recursively synchronizes all files and folders from server to client. - /// Synchronizes only folders already loaded into the user file system. - /// - /// Folder path in user file system. - internal async Task SyncronizeFolderAsync(string userFileSystemFolderPath) - { - // In case of on-demand loading the user file system contains only a subset of the server files and folders. - // Here we sync folder only if its content already loaded into user file system (folder is not offline). - // The folder content is loaded inside IFolder.GetChildrenAsync() method. - if (new DirectoryInfo(userFileSystemFolderPath).Attributes.HasFlag(System.IO.FileAttributes.Offline)) - { - // LogMessage("Folder offline, skipping:", userFileSystemFolderPath); - return; - } - - IEnumerable userFileSystemChildren = Directory.EnumerateFileSystemEntries(userFileSystemFolderPath, "*"); - //LogMessage("Synchronizing:", userFileSystemFolderPath); - - IVirtualFolder userFolder = await virtualDrive.GetItemAsync(userFileSystemFolderPath, this); - IEnumerable remoteStorageChildrenItems = await userFolder.EnumerateChildrenAsync("*"); - - // Create new files/folders in the user file system. - foreach (FileSystemItemMetadataExt remoteStorageItem in remoteStorageChildrenItems) - { - string userFileSystemPath = Path.Combine(userFileSystemFolderPath, remoteStorageItem.Name); - try - { - // We do not want to sync MS Office temp files, etc. from remote storage. - // We also do not want to create MS Office files during transactional save in user file system. - if (!FsPath.AvoidSync(remoteStorageItem.Name) && !FsPath.AvoidSync(userFileSystemPath)) - { - if (!FsPath.Exists(userFileSystemPath)) - { - LogMessage($"Creating", userFileSystemPath); - - await virtualDrive.GetUserFileSystemRawItem(userFileSystemFolderPath, this).CreateAsync(new[] { remoteStorageItem }); - LogMessage($"Created succesefully", userFileSystemPath); - } - } - } - catch (Exception ex) - { - LogError("Creation failed", userFileSystemPath, null, ex); - } - } - - // Update files/folders in user file system and sync subfolders. - userFileSystemChildren = Directory.EnumerateFileSystemEntries(userFileSystemFolderPath, "*"); - foreach (string userFileSystemPath in userFileSystemChildren) - { - try - { - string itemName = Path.GetFileName(userFileSystemPath); - FileSystemItemMetadataExt 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() - //&& !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 userFileSystemRawItem.DeleteAsync(); - LogMessage("Deleted succesefully", userFileSystemPath); - } - } - else - { - if (PlaceholderItem.GetItem(userFileSystemPath).GetInSync() - && !await virtualDrive.GetETagManager(userFileSystemPath).ETagEqualsAsync(remoteStorageItem)) - { - // User file system <- remote storage update. - LogMessage("Remote item modified", userFileSystemPath); - await userFileSystemRawItem.UpdateAsync(remoteStorageItem); - LogMessage("Updated succesefully", userFileSystemPath); - } - - // Set the read-only attribute and all custom columns data. - if (PlaceholderItem.GetItem(userFileSystemPath).GetInSync()) - { - await userFileSystemRawItem.SetLockedByAnotherUserAsync(remoteStorageItem.LockedByAnotherUser); - await userFileSystemRawItem.SetCustomColumnsDataAsync(remoteStorageItem.CustomProperties); - } - - // Hydrate / dehydrate the file. - if (userFileSystemRawItem.HydrationRequired()) - { - LogMessage("Hydrating", 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 (userFileSystemRawItem.DehydrationRequired()) - { - LogMessage("Dehydrating", userFileSystemPath); - new PlaceholderFile(userFileSystemPath).Dehydrate(0, -1); - LogMessage("Dehydrated succesefully", userFileSystemPath); - } - } - } - } - catch (Exception ex) - { - LogError("Update failed", userFileSystemPath, null, ex); - } - - // Synchronize subfolders. - try - { - if (Directory.Exists(userFileSystemPath)) - { - await SyncronizeFolderAsync(userFileSystemPath); - } - } - catch (Exception ex) - { - LogError("Folder sync failed:", userFileSystemPath, null, ex); - } - } - } - } -} diff --git a/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/UserFileSystemMonitor.cs b/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/UserFileSystemMonitor.cs deleted file mode 100644 index 2da50ca..0000000 --- a/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/UserFileSystemMonitor.cs +++ /dev/null @@ -1,217 +0,0 @@ -using ITHit.FileSystem; -using ITHit.FileSystem.Windows; -using log4net; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Windows.Storage; -using Windows.Storage.FileProperties; -using Windows.Storage.Provider; -using Windows.System.Update; - -namespace ITHit.FileSystem.Samples.Common.Windows.Syncronyzation -{ - /// - /// Monitors files and folders creation as well as attributes change in the user file system. - /// If any file or folder pinned or unpinned, triggers hydration or dehydration. - /// - /// - /// Windows does not provide any notifications for pinned/unpinned attributes change as well as for files/folders creation. - /// We need to monitor them using regular FileSystemWatcher. - /// - /// In most cases you can use this class in your project without any changes. - /// - public class UserFileSystemMonitor : Logger, IDisposable - { - /// - /// User file system watcher. - /// - private readonly FileSystemWatcher watcher = new FileSystemWatcher(); - - /// - /// Virtual drive. - /// - private readonly VirtualDriveBase virtualDrive; - - /// - /// Creates instance of this class. - /// - /// 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.virtualDrive = virtualDrive; - - watcher.IncludeSubdirectories = true; - watcher.Path = userFileSystemRootPath; - //watcher.Filter = "*.*"; - watcher.NotifyFilter = NotifyFilters.Attributes | NotifyFilters.FileName | NotifyFilters.DirectoryName; - watcher.Error += Error; - watcher.Created += CreatedAsync; - watcher.Changed += ChangedAsync; - watcher.Deleted += DeletedAsync; - watcher.Renamed += RenamedAsync; - } - - /// - /// Starts monitoring attributes changes in user file system. - /// - 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. - /// - /// - /// This method is also called when a file is being moved in user file system, after the IFileSystem.MoveToAsync() call. - /// - private async void CreatedAsync(object sender, FileSystemEventArgs e) - { - //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 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); - LogMessage("Converted to placeholder", userFileSystemPath); - - // Do not create temp MS Office, temp and hidden files in remote storage. - if (!FsPath.AvoidSync(userFileSystemPath)) - { - // Create the file/folder in the remote storage. - FileSystemItemTypeEnum itemType = FsPath.GetItemType(userFileSystemPath); - try - { - await virtualDrive.GetRemoteStorageRawItem(userFileSystemPath, itemType, this).CreateAsync(); - } - catch (IOException ex) - { - LogError("Creation in remote storage failed. Possibly in use by an application", userFileSystemPath, null, ex); - } - } - } - } - catch (Exception ex) - { - LogError($"{e.ChangeType} failed", userFileSystemPath, null, ex); - } - } - - /// - /// Called when a file or folder attributes changed in the user file system. - /// - /// - /// Here we monitor pinned and unpinned attributes and hydrate/dehydrate files. - /// - private async void ChangedAsync(object sender, FileSystemEventArgs e) - { - //LogMessage($"{e.ChangeType}", e.FullPath); - try - { - string userFileSystemPath = e.FullPath; - if (FsPath.Exists(userFileSystemPath) && !FsPath.AvoidSync(userFileSystemPath)) - { - // Hydrate / dehydrate. - if (virtualDrive.GetUserFileSystemRawItem(userFileSystemPath, this).HydrationRequired()) - { - LogMessage("Hydrating", userFileSystemPath); - DateTimeOffset start = DateTimeOffset.Now; - new PlaceholderFile(userFileSystemPath).Hydrate(0, -1); - LogMessage($"Hydrated succesefully {DateTimeOffset.Now-start}", userFileSystemPath); - } - else if (virtualDrive.GetUserFileSystemRawItem(userFileSystemPath, this).DehydrationRequired()) - { - LogMessage("Dehydrating", userFileSystemPath); - new PlaceholderFile(userFileSystemPath).Dehydrate(0, -1); - LogMessage("Dehydrated succesefully", userFileSystemPath); - } - } - } - catch (Exception ex) - { - LogError("Hydration/dehydration failed", e.FullPath, null, ex); - } - } - - /// - /// Called when a file or folder is deleted in the user file system. - /// - /// We monitor this event for logging purposes only. - private async void DeletedAsync(object sender, FileSystemEventArgs e) - { - //LogMessage(e.ChangeType.ToString(), e.FullPath); - } - - /// - /// Called when a file or folder is renamed in the user file system. - /// - /// We monitor this event for logging purposes only. - private async void RenamedAsync(object sender, RenamedEventArgs e) - { - //LogMessage("Renamed", e.OldFullPath, e.FullPath); - } - - - private void Error(object sender, ErrorEventArgs e) - { - LogError(null, null, null, e.GetException()); - } - - - private bool disposedValue = false; // To detect redundant calls - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - watcher.Dispose(); - LogMessage($"Disposed"); - } - - // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. - // TODO: set large fields to null. - - disposedValue = true; - } - } - - // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. - // ~ServerChangesMonitor() - // { - // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - // Dispose(false); - // } - - // This code added to correctly implement the disposable pattern. - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - // TODO: uncomment the following line if the finalizer is overridden above. - // GC.SuppressFinalize(this); - } - } -} diff --git a/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/UserFileSystemRawItem.cs b/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/UserFileSystemRawItem.cs deleted file mode 100644 index 80c02a0..0000000 --- a/ITHit.FileSystem.Samples.Common.Windows/Syncronyzation/UserFileSystemRawItem.cs +++ /dev/null @@ -1,549 +0,0 @@ -using ITHit.FileSystem; -using ITHit.FileSystem.Windows; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; -using Windows.Storage; -using Windows.Storage.Provider; - -namespace ITHit.FileSystem.Samples.Common.Windows.Syncronyzation -{ - /// - /// Provides methods for synching items from the 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. - internal class UserFileSystemRawItem : IServerNotifications - { - /// - /// Path to the file or folder placeholder in user file system. - /// - 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, VirtualDriveBase virtualDrive, ILogger logger) - { - if (string.IsNullOrEmpty(userFileSystemPath)) - { - throw new ArgumentNullException(nameof(userFileSystemPath)); - } - this.virtualDrive = virtualDrive ?? throw new ArgumentNullException(nameof(virtualDrive)); - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - - this.userFileSystemPath = userFileSystemPath; - this.eTagManager = virtualDrive.GetETagManager(userFileSystemPath); - } - - - /// - /// Creates new file and folder placeholders in the user file system in this folder. - /// - /// Array of new files and folders. - /// Number of items created. - public async Task CreateAsync(IFileSystemItemMetadata[] newItemsInfo) - { - string userFileSystemParentPath = userFileSystemPath; - - try - { - // Because of the on-demand population the file or folder placeholder may not exist in the user file system. - // Here we also check that the folder content was loaded into user file system (the folder is not offline). - 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 = await virtualDrive.Engine.ServerNotifications(userFileSystemParentPath).CreateAsync(newItemsInfo); - - // Create ETags. - foreach (IFileSystemItemMetadata newItemInfo in newItemsInfo) - { - FileSystemItemMetadataExt newItemInfoExt = newItemInfo as FileSystemItemMetadataExt ?? throw new NotImplementedException($"{nameof(FileSystemItemMetadataExt)}"); - - string userFileSystemNewItemPath = Path.Combine(userFileSystemParentPath, newItemInfoExt.Name); - await virtualDrive.GetETagManager(userFileSystemNewItemPath).SetETagAsync(newItemInfoExt.ETag); - - // Set the read-only attribute and all custom columns data. - UserFileSystemRawItem newUserFileSystemRawItem = virtualDrive.GetUserFileSystemRawItem(userFileSystemNewItemPath, logger); - await newUserFileSystemRawItem.SetLockedByAnotherUserAsync(newItemInfoExt.LockedByAnotherUser); - await newUserFileSystemRawItem.SetCustomColumnsDataAsync(newItemInfoExt.CustomProperties); - } - - return created; - } - } - catch (ExistsException 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(); - } - - return 0; - } - - - - /// - /// Updates information about the file or folder placeholder in the user file system. - /// This method automatically hydrates and dehydrate files. - /// - /// 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(IFileSystemItemMetadata itemInfo) - { - FileSystemItemMetadataExt itemInfoExt = itemInfo as FileSystemItemMetadataExt ?? throw new NotImplementedException($"{nameof(FileSystemItemMetadataExt)}"); - try - { - // Because of the on-demand population the file or folder placeholder may not exist in the user file system. - if (FsPath.Exists(userFileSystemPath)) - { - 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) - { - FsPath.GetFileSystemItem(userFileSystemPath).Attributes &= ~System.IO.FileAttributes.ReadOnly; - } - - // Dehydrate/hydrate the file, update file size, custom data, creation date, modification date, attributes. - await virtualDrive.Engine.ServerNotifications(userFileSystemPath).UpdateAsync(itemInfoExt); - - // Set ETag. - await eTagManager.SetETagAsync(itemInfoExt.ETag); - - // Clear icon. - //await ClearStateAsync(); - - // Set the read-only attribute and all custom columns data. - await SetLockedByAnotherUserAsync(itemInfoExt.LockedByAnotherUser); - await SetCustomColumnsDataAsync(itemInfoExt.CustomProperties); - - return true; - } - } - catch (Exception ex) - { - await SetDownloadErrorStateAsync(ex); - - // Rethrow the exception preserving stack trace of the original exception. - System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw(); - } - return false; - } - - - /// - /// Deletes a file or folder placeholder in user file system. - /// - /// - /// 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). - /// - /// True if the file was deleted. False - otherwise. - public async Task DeleteAsync() - { - // Cloud Filter API does not provide a function to delete a placeholder file only if it is not modified. - // Here we check that the file is not modified in user file system, using GetInSync() call. - // To avoid the file modification between GetInSync() call and Delete() call we - // open it without FileShare.Write flag. - try - { - // Because of the on-demand population the file or folder placeholder may not exist in the user file system. - if (FsPath.Exists(userFileSystemPath)) - { - using (WindowsFileSystemItem userFileSystemWinItem = WindowsFileSystemItem.Open(userFileSystemPath, (FileAccess)0, FileMode.Open, FileShare.Read | FileShare.Delete)) - { - if (PlaceholderItem.GetInSync(userFileSystemWinItem.SafeHandle)) - { - await virtualDrive.Engine.ServerNotifications(userFileSystemPath).DeleteAsync(); - - // Delete ETag - logger.LogMessage("Deleting ETag", userFileSystemPath); - eTagManager.DeleteETag(); - - return true; - } - else - { - throw new ConflictException(Modified.Client, "The item is not in-sync with the cloud."); - } - } - } - } - catch (Exception ex) - { - await SetDownloadErrorStateAsync(ex); - - // Rethrow the exception preserving stack trace of the original exception. - System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw(); - } - - return false; - } - - /// - /// Moves a file or folder placeholder in user file system. - /// - /// New path in user file system. - /// - /// 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. - /// - /// 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. - if (FsPath.Exists(userFileSystemPath)) - { - bool inSync = PlaceholderItem.GetItem(userFileSystemPath).GetInSync(); - if (inSync) - { - logger.LogMessage("Moving ETag", userFileSystemPath, userFileSystemNewPath); - await eTagManager.MoveToAsync(userFileSystemNewPath); - - logger.LogMessage("Moving item", userFileSystemPath, userFileSystemNewPath); - await virtualDrive.Engine.ServerNotifications(userFileSystemPath).MoveToAsync(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 virtualDrive.GetUserFileSystemRawItem(userFileSystemNewPath, logger).ClearStateAsync(); - itemMoved = true; - } - - if (!inSync) - { - throw new ConflictException(Modified.Client, "The item is not in-sync with the cloud."); - } - } - } - catch (Exception 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; - } - - /// - /// Returns true if hydration is required. False - otherwise. - /// - internal bool HydrationRequired() - { - if (FsPath.IsFile(userFileSystemPath)) - { - int attributes = (int)new FileInfo(userFileSystemPath).Attributes; - - // Download content (hydrate) if file is pinned and no file content is present locally (offline). - if (((attributes & (int)FileAttributesExt.Pinned) != 0) - && ((attributes & (int)FileAttributesExt.Offline) != 0)) - { - return true; - } - } - return false; - } - - /// - /// Returns true if dehydration is required. False - otherwise. - /// - internal bool DehydrationRequired() - { - if (FsPath.IsFile(userFileSystemPath)) - { - int attributes = (int)new FileInfo(userFileSystemPath).Attributes; - - // Remove content (dehydrate) if file is unpinned and file content is present locally (not offline). - if (((attributes & (int)FileAttributesExt.Unpinned) != 0) - && ((attributes & (int)FileAttributesExt.Offline) == 0)) - { - return true; - } - } - return false; - } - - internal async Task ClearStateAsync() - { - if (FsPath.Exists(userFileSystemPath)) - { - await SetIconAsync(false); - } - } - - internal async Task SetUploadErrorStateAsync(Exception ex) - { - if (FsPath.Exists(userFileSystemPath)) - { - if (ex is ConflictException) - { - await SetConflictIconAsync(true); - } - else - { - await SetUploadPendingIconAsync(true); - } - } - } - - private async Task SetDownloadErrorStateAsync(Exception ex) - { - if (FsPath.Exists(userFileSystemPath)) - { - if (ex is ConflictException) - { - await SetConflictIconAsync(true); - } - if (ex is ExistsException) - { - await SetConflictIconAsync(true); - } - else - { - // Could be BlockedException or other exception. - await SetDownloadPendingIconAsync(true); - } - } - } - - /// - /// Sets or removes "Conflict" icon. - /// - /// True to display the icon. False - to remove the icon. - private async Task SetConflictIconAsync(bool set) - { - await SetIconAsync(set, (int)CustomColumnIds.ConflictIcon, "Error.ico", "Conflict. File is modified both on the server and on the client."); - } - - /// - /// Sets or removes "Download pending" icon. - /// - /// 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"); - } - - /// - /// Sets or removes "Upload pending" icon. - /// - /// 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"); - } - - /// - /// Sets or removes "Lock" icon and all lock properties. - /// - /// True to display the icon. False - to remove the icon. - internal async Task SetLockInfoAsync(ServerLockInfo lockInfo) - { - IEnumerable lockProps = null; - if (lockInfo != null) - { - lockProps = lockInfo.GetLockProperties(Path.Combine(virtualDrive.Settings.IconsFolderPath, "Locked.ico")); - } - await SetCustomColumnsDataAsync(lockProps); - } - - /// - /// Sets or removes "Lock pending" icon. - /// - /// True to display the icon. False - to remove the icon. - internal async Task SetLockPendingIconAsync(bool set) - { - await SetIconAsync(set, (int)CustomColumnIds.LockOwnerIcon, "LockedPending.ico", "Updating lock..."); - } - - /// - /// Sets or removes icon. - /// - /// True to display the icon. False - to remove the icon. - private async Task SetIconAsync(bool set, int? id = null, string iconFile = null, string description = null) - { - IStorageItem storageItem = await FsPath.GetStorageItemAsync(userFileSystemPath); - - if (storageItem == null) - { - // 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 - { - if (set) - { - StorageProviderItemProperty propState = new StorageProviderItemProperty() - { - Id = id.Value, - Value = description, - IconResource = Path.Combine(virtualDrive.Settings.IconsFolderPath, iconFile) - }; - await StorageProviderItemProperties.SetAsync(storageItem, new StorageProviderItemProperty[] { propState }); - } - else - { - await StorageProviderItemProperties.SetAsync(storageItem, new StorageProviderItemProperty[] { }); - } - } - - // Setting status icon failes for blocked files. - catch (FileNotFoundException) - { - - } - catch (COMException) - { - // "Error HRESULT E_FAIL has been returned from a call to a COM component." - } - catch (Exception ex) - { - if (ex.HResult == -2147024499) - { - // "The operation failed due to a conflicting cloud file property lock. (0x8007018D)" - } - else - { - // Rethrow the exception preserving stack trace of the original exception. - System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw(); - } - } - } - - /// - /// 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) - { - bool resultSet = false; - // Changing read-only attribute on folders triggers folders listing. Changing it on files only. - - if (FsPath.IsFile(userFileSystemPath)) - { - FileInfo file = new FileInfo(userFileSystemPath); - if (set != file.IsReadOnly) - { - // Set/Remove read-only attribute. - if (set) - { - new FileInfo(userFileSystemPath).Attributes |= System.IO.FileAttributes.ReadOnly; - } - else - { - new FileInfo(userFileSystemPath).Attributes &= ~System.IO.FileAttributes.ReadOnly; - } - - resultSet = true; - } - } - - return resultSet; - } - - internal async Task SetCustomColumnsDataAsync(IEnumerable customColumnsData) - { - List customColumns = new List(); - - if (customColumnsData != null) - { - foreach (FileSystemItemPropertyData column in customColumnsData) - { - customColumns.Add(new StorageProviderItemProperty() - { - Id = column.Id, - // If value is empty Windows File Manager crushes. - Value = string.IsNullOrEmpty(column.Value) ? "-" : column.Value, - // If icon is not set Windows File Manager crushes. - IconResource = column.IconResource ?? Path.Combine(virtualDrive.Settings.IconsFolderPath, "Blank.ico") - }); - } - } - - // This method may be called on temp files, typically created by MS Office, that exist for a short period of time. - IStorageItem storageItem = await FsPath.GetStorageItemAsync(userFileSystemPath); - if (storageItem == null) - { - // 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; - } - - FileInfo file = new FileInfo(userFileSystemPath); - - // Can not set provider properties on read-only files. - // Changing read-only attribute on folders triggers folders listing. Changing it on files only. - bool readOnly = file.IsReadOnly; - - // Remove read-only attribute. - if (readOnly && ((file.Attributes & System.IO.FileAttributes.Directory) == 0)) - { - file.IsReadOnly = false; - //new FileInfo(userFileSystemPath).Attributes &= ~System.IO.FileAttributes.ReadOnly; - } - - // Update columns data. - await StorageProviderItemProperties.SetAsync(storageItem, customColumns); - - // Set read-only attribute. - if (readOnly && ((file.Attributes & System.IO.FileAttributes.Directory) == 0)) - { - file.IsReadOnly = true; - //new FileInfo(userFileSystemPath).Attributes |= System.IO.FileAttributes.ReadOnly; - } - } - } -} diff --git a/ITHit.FileSystem.Samples.Common.Windows/VfsEngine.cs b/ITHit.FileSystem.Samples.Common.Windows/VfsEngine.cs deleted file mode 100644 index 6ab4488..0000000 --- a/ITHit.FileSystem.Samples.Common.Windows/VfsEngine.cs +++ /dev/null @@ -1,89 +0,0 @@ -using ITHit.FileSystem.Windows; -using log4net; -using System.IO; -using System.Threading.Tasks; - -namespace ITHit.FileSystem.Samples.Common.Windows -{ - // In most cases you can use this class in your project without any changes. - /// - internal class VfsEngine : EngineWindows - { - /// - /// Virtual drive. - /// - private VirtualDriveBase virtualDrive; - - /// - /// Logger. - /// - private ILogger logger; - - /// - /// Enables or disables processing of changes made in IFile.CoseAsync(), IFileSystemItem.MoveToAsync(), IFileSystemItem.DeteteAsync(). - /// You will disable processing to debug synchronization service. - /// - internal bool ChangesProcessingEnabled = true; - - /// - /// 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. - /// Logger. - internal VfsEngine(string license, string userFileSystemRootPath, VirtualDriveBase virtualDrive, ILog log) : base(license, userFileSystemRootPath) - { - logger = new Logger("File System Engine", log); - - this.virtualDrive = virtualDrive; - - // We want our file system to run regardless of any errors. - // If any request to file system fails in user code or in Engine itself we continue processing. - ThrowExceptions = false; - - StateChanged += Engine_StateChanged; - Error += Engine_Error; - Message += Engine_Message; - } - - - /// - public override async Task GetFileSystemItemAsync(string path) - { - if(File.Exists(path)) - { - return new VfsFile(path, this, this, virtualDrive); - } - if(Directory.Exists(path)) - { - return new VfsFolder(path, this, this, virtualDrive); - } - - // Note that this method may be called for items that does 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; - } - - - private void Engine_Message(IEngine sender, EngineMessageEventArgs e) - { - logger.LogMessage(e.Message, e.SourcePath, e.TargetPath); - } - - private void Engine_Error(IEngine sender, EngineErrorEventArgs e) - { - logger.LogError(e.Message, e.SourcePath, e.TargetPath, e.Exception); - } - - /// - /// Show status change. - /// - /// Engine - /// Contains new and old Engine state. - private void Engine_StateChanged(Engine engine, EngineWindows.StateChangeEventArgs e) - { - engine.LogMessage($"{e.NewState}"); - } - } -} diff --git a/ITHit.FileSystem.Samples.Common.Windows/VfsFile.cs b/ITHit.FileSystem.Samples.Common.Windows/VfsFile.cs deleted file mode 100644 index aa1119e..0000000 --- a/ITHit.FileSystem.Samples.Common.Windows/VfsFile.cs +++ /dev/null @@ -1,151 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -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 - { - - /// - /// Creates instance of this class. - /// - /// File path in user file system. - /// Logger. - public VfsFile(string path, ILogger logger, VfsEngine engine, VirtualDriveBase userEngine) : base(path, logger, engine, userEngine) - { - - } - - /// - public async Task OpenAsync(IOperationContext operationContext, IResultContext context) - { - Logger.LogMessage($"{nameof(IFile)}.{nameof(OpenAsync)}()", UserFileSystemPath); - - // Auto-lock the file. - string userFileSystemFilePath = UserFileSystemPath; - if (Engine.ChangesProcessingEnabled && FsPath.Exists(userFileSystemFilePath)) - { - if (VirtualDrive.Settings.AutoLock - && !FsPath.AvoidAutoLock(userFileSystemFilePath) - && !await VirtualDrive.LockManager(userFileSystemFilePath, Logger).IsLockedAsync() - && FsPath.IsWriteLocked(userFileSystemFilePath) - && !new PlaceholderFile(userFileSystemFilePath).IsNew(VirtualDrive)) - { - RemoteStorageRawItem remoteStorageRawItem = new RemoteStorageRawItem(userFileSystemFilePath, VirtualDrive, Logger); - if (await remoteStorageRawItem.IsLockSupportedAsync()) - { - 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); - } - } - } - } - } - - - /// - public async Task CloseAsync(IOperationContext operationContext, IResultContext context) - { - // Here, if the file in the user file system is modified (not in-sync), you will send the file content, - // creation time, modification time and attributes to the remote storage. - // We also send ETag, to make sure the changes on the server, if any, are not overwritten. - - Logger.LogMessage("IFile.CloseAsync()", UserFileSystemPath); - - string userFileSystemFilePath = UserFileSystemPath; - - // In case the file is moved it does not exist in user file system when CloseAsync() is called. - if (Engine.ChangesProcessingEnabled - && FsPath.Exists(userFileSystemFilePath) - && !FsPath.AvoidSync(userFileSystemFilePath)) - { - - // In case the file is overwritten it is converted to a regular file prior to CloseAsync(). - // we need to convert it back into file/folder placeholder. - if (!PlaceholderItem.IsPlaceholder(userFileSystemFilePath)) - { - PlaceholderItem.ConvertToPlaceholder(userFileSystemFilePath, false); - Logger.LogMessage("Converted to placeholder", userFileSystemFilePath); - } - - try - { - if (PlaceholderItem.GetItem(userFileSystemFilePath).IsNew(VirtualDrive)) - { - // Create new file in the remote storage. - await new RemoteStorageRawItem(userFileSystemFilePath, VirtualDrive, Logger).CreateAsync(); - } - else if(!PlaceholderItem.GetItem(userFileSystemFilePath).IsMoved()) - { - // Send content to remote storage. Unlock if auto-locked. - await new RemoteStorageRawItem(userFileSystemFilePath, VirtualDrive, Logger).UpdateAsync(); - } - } - catch (IOException ex) - { - // Either the file is already being synced in another thread or client or server file is blocked by concurrent process. - // This is a normal behaviour. - // The file must be synched by your synchronyzation service at a later time, when the file becomes available. - Logger.LogMessage("Failed to upload file. Possibly in use by an application or blocked for synchronization in another thread:", ex.Message); - } - } - } - - - - /// - public async Task TransferDataAsync(long offset, long length, ITransferDataOperationContext operationContext, 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. - - // For files > 4Gb we have to use the OptionalLength. - if (operationContext.FileSize > 0x100000000) - { - length += operationContext.OptionalLength; - } - - Logger.LogMessage($"{nameof(IFile)}.{nameof(TransferDataAsync)}({offset}, {length})", UserFileSystemPath); - - SimulateNetworkDelay(length, resultContext); - - IVirtualFile userFile = await VirtualDrive.GetItemAsync(UserFileSystemPath, Logger); - - await userFile.ReadAsync(offset, length, operationContext.FileSize, resultContext); - } - - - /// - public async Task ValidateDataAsync(long offset, long length, IValidateDataOperationContext operationContext, IValidateDataResultContext resultContext) - { - // This method has a 60 sec timeout. - // To process longer requests and reset the timout timer call IContextWindows.ReportProgress() method. - - Logger.LogMessage($"IFile.ValidateDataAsync({offset}, {length})", UserFileSystemPath); - - //SimulateNetworkDelay(length, resultContext); - - 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.Windows/VfsFileSystemItem.cs b/ITHit.FileSystem.Samples.Common.Windows/VfsFileSystemItem.cs deleted file mode 100644 index fdbd38b..0000000 --- a/ITHit.FileSystem.Samples.Common.Windows/VfsFileSystemItem.cs +++ /dev/null @@ -1,187 +0,0 @@ -using ITHit.FileSystem; -using ITHit.FileSystem.Windows; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using ITHit.FileSystem.Samples.Common.Windows.Syncronyzation; -using Windows.Storage; -using Windows.Storage.Provider; - -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 where TItemType : IVirtualFileSystemItem - { - /// - /// File or folder path in the user file system. - /// - protected readonly string UserFileSystemPath; - - /// - /// Logger. - /// - protected readonly ILogger Logger; - - /// - /// User file system Engine. - /// - protected readonly VfsEngine Engine; - - /// - /// Virtual drive. - /// - protected VirtualDriveBase VirtualDrive; - - /// - /// Creates instance of this class. - /// - /// 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(nameof(userFileSystemPath)); - } - - if(logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } - - UserFileSystemPath = userFileSystemPath; - Logger = logger; - Engine = engine; - VirtualDrive = virtualDrive; - } - - - /// - public async Task MoveToAsync(string userFileSystemNewPath, IOperationContext operationContext, IConfirmationResultContext resultContext) - { - string userFileSystemOldPath = this.UserFileSystemPath; - 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); - } - } - 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); - - if (Engine.ChangesProcessingEnabled) - { - if (FsPath.Exists(userFileSystemNewPath)) - { - 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; - 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 failed", remoteStoragePath, null, ex); - } - finally - { - 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 GetMetadataAsync() - { - // Return IFileMetadata for a file, IFolderMetadata for a folder. - throw new NotImplementedException(); - } - - /// - /// Simulates network delays and reports file transfer progress for demo purposes. - /// - /// Length of file. - /// Context to report progress to. - protected void SimulateNetworkDelay(long fileLength, IResultContext resultContext) - { - if (VirtualDrive.Settings.NetworkSimulationDelayMs > 0) - { - int numProgressResults = 5; - for (int i = 0; i < numProgressResults; i++) - { - resultContext.ReportProgress(fileLength, i * fileLength / numProgressResults); - Thread.Sleep(VirtualDrive.Settings.NetworkSimulationDelayMs); - } - } - } - } -} diff --git a/ITHit.FileSystem.Samples.Common.Windows/VfsFolder.cs b/ITHit.FileSystem.Samples.Common.Windows/VfsFolder.cs deleted file mode 100644 index 27a7e26..0000000 --- a/ITHit.FileSystem.Samples.Common.Windows/VfsFolder.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Enumeration; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Windows.Storage; -using Windows.Storage.Provider; - -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 - { - public VfsFolder(string path, ILogger logger, VfsEngine engine, VirtualDriveBase virtualDrive) : base(path, logger, engine, virtualDrive) - { - - } - - /// - public async Task GetChildrenAsync(string pattern, IOperationContext operationContext, IFolderListingResultContext resultContext) - { - // This method has a 60 sec timeout. - // To process longer requests and reset the timout timer call one of the following: - // - resultContext.ReturnChildren() method. - // - resultContext.ReportProgress() method. - - Logger.LogMessage($"IFolder.GetChildrenAsync({pattern})", UserFileSystemPath); - - 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 (FileSystemItemMetadataExt child in children) - { - string userFileSystemItemPath = Path.Combine(UserFileSystemPath, child.Name); - if (!FsPath.Exists(userFileSystemItemPath)) - { - Logger.LogMessage("Creating", child.Name); - - // 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. - child.CustomData = new CustomData - { - OriginalPath = userFileSystemItemPath - }.Serialize(); - - newChildren.Add(child); - } - } - - // To signal that the children enumeration is completed - // always call ReturnChildren(), even if the folder is empty. - resultContext.ReturnChildren(newChildren.ToArray(), newChildren.Count()); - - - // Save ETags, the read-only attribute and all custom columns data. - foreach (FileSystemItemMetadataExt child in children) - { - string userFileSystemItemPath = Path.Combine(UserFileSystemPath, child.Name); - - // Create ETags. - // 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 VirtualDrive.GetETagManager(userFileSystemItemPath, Logger).SetETagAsync(child.ETag); - - // 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 deleted file mode 100644 index e9b9a9e..0000000 --- a/ITHit.FileSystem.Samples.Common.Windows/VirtualDriveBase.cs +++ /dev/null @@ -1,228 +0,0 @@ -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. - /// - internal 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/IClientNotifications.cs b/ITHit.FileSystem.Samples.Common/IClientNotifications.cs deleted file mode 100644 index 7a13772..0000000 --- a/ITHit.FileSystem.Samples.Common/IClientNotifications.cs +++ /dev/null @@ -1,39 +0,0 @@ -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/IVirtualDrive.cs b/ITHit.FileSystem.Samples.Common/IVirtualDrive.cs deleted file mode 100644 index 184ac64..0000000 --- a/ITHit.FileSystem.Samples.Common/IVirtualDrive.cs +++ /dev/null @@ -1,71 +0,0 @@ -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 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); - } -} diff --git a/ITHit.FileSystem.Samples.Common/IVirtualFile.cs b/ITHit.FileSystem.Samples.Common/IVirtualFile.cs deleted file mode 100644 index d0b7257..0000000 --- a/ITHit.FileSystem.Samples.Common/IVirtualFile.cs +++ /dev/null @@ -1,39 +0,0 @@ -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); - } -} diff --git a/ITHit.FileSystem.Samples.Common/IVirtualFileSystemItem.cs b/ITHit.FileSystem.Samples.Common/IVirtualFileSystemItem.cs deleted file mode 100644 index d255c0f..0000000 --- a/ITHit.FileSystem.Samples.Common/IVirtualFileSystemItem.cs +++ /dev/null @@ -1,21 +0,0 @@ -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(); - } -} diff --git a/ITHit.FileSystem.Samples.Common/IVirtualFolder.cs b/ITHit.FileSystem.Samples.Common/IVirtualFolder.cs deleted file mode 100644 index 01b8a13..0000000 --- a/ITHit.FileSystem.Samples.Common/IVirtualFolder.cs +++ /dev/null @@ -1,50 +0,0 @@ -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); - } -} diff --git a/ITHit.FileSystem.Samples.Common/IVirtualLock.cs b/ITHit.FileSystem.Samples.Common/IVirtualLock.cs deleted file mode 100644 index 8108ae6..0000000 --- a/ITHit.FileSystem.Samples.Common/IVirtualLock.cs +++ /dev/null @@ -1,37 +0,0 @@ -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/LockMode.cs b/ITHit.FileSystem.Samples.Common/LockMode.cs deleted file mode 100644 index efb9a89..0000000 --- a/ITHit.FileSystem.Samples.Common/LockMode.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace ITHit.FileSystem.Samples.Common -{ - /// - /// Indicates how the file was locked and how to unlock the file. - /// - public enum LockMode - { - /// - /// The file is not locked. - /// - None = 0, - - /// - /// The file is automatically locked on file handle open and should be automatically unlocked on file handle close. - /// - Auto = 1, - - /// - /// The file is manually locked by the user and should be manually unlocked by the user. - /// - Manual = 2 - } -} diff --git a/UserFileSystemSamples.sln b/UserFileSystemSamples.sln deleted file mode 100644 index f44c94f..0000000 --- a/UserFileSystemSamples.sln +++ /dev/null @@ -1,51 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31019.35 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebDAVDrive", "WebDAVDrive\WebDAVDrive.csproj", "{E23287B1-C11F-45F4-A0E0-F86F0DE9A719}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ITHit.FileSystem.Samples.Common", "ITHit.FileSystem.Samples.Common\ITHit.FileSystem.Samples.Common.csproj", "{63C11274-D693-4C19-A83C-390B26D8B65B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebDAVDrive.UI", "WebDAVDrive.UI\WebDAVDrive.UI.csproj", "{9729AEEC-2642-4011-9849-1C957141C6FF}" -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 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ITHit.FileSystem.Samples.Common.Windows", "ITHit.FileSystem.Samples.Common.Windows\ITHit.FileSystem.Samples.Common.Windows.csproj", "{0EEFC851-55FF-4A22-8512-B589702B8486}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E23287B1-C11F-45F4-A0E0-F86F0DE9A719}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E23287B1-C11F-45F4-A0E0-F86F0DE9A719}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E23287B1-C11F-45F4-A0E0-F86F0DE9A719}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E23287B1-C11F-45F4-A0E0-F86F0DE9A719}.Release|Any CPU.Build.0 = Release|Any CPU - {63C11274-D693-4C19-A83C-390B26D8B65B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {63C11274-D693-4C19-A83C-390B26D8B65B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {63C11274-D693-4C19-A83C-390B26D8B65B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {63C11274-D693-4C19-A83C-390B26D8B65B}.Release|Any CPU.Build.0 = Release|Any CPU - {9729AEEC-2642-4011-9849-1C957141C6FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9729AEEC-2642-4011-9849-1C957141C6FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9729AEEC-2642-4011-9849-1C957141C6FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9729AEEC-2642-4011-9849-1C957141C6FF}.Release|Any CPU.Build.0 = Release|Any CPU - {F83D05C4-EF6D-4BD9-99C4-B404443437F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F83D05C4-EF6D-4BD9-99C4-B404443437F0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F83D05C4-EF6D-4BD9-99C4-B404443437F0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F83D05C4-EF6D-4BD9-99C4-B404443437F0}.Release|Any CPU.Build.0 = Release|Any CPU - {0EEFC851-55FF-4A22-8512-B589702B8486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0EEFC851-55FF-4A22-8512-B589702B8486}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0EEFC851-55FF-4A22-8512-B589702B8486}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0EEFC851-55FF-4A22-8512-B589702B8486}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {740A716A-38A7-46BC-A21F-18336D0023B7} - EndGlobalSection -EndGlobal diff --git a/VirtualFileSystem/Images/Down.ico b/VirtualFileSystem/Images/Down.ico deleted file mode 100644 index 820848b..0000000 Binary files a/VirtualFileSystem/Images/Down.ico and /dev/null differ diff --git a/VirtualFileSystem/Images/Up.ico b/VirtualFileSystem/Images/Up.ico deleted file mode 100644 index 0bcb6b7..0000000 Binary files a/VirtualFileSystem/Images/Up.ico and /dev/null differ diff --git a/VirtualFileSystem/Images/Warning.ico b/VirtualFileSystem/Images/Warning.ico deleted file mode 100644 index 173c60d..0000000 Binary files a/VirtualFileSystem/Images/Warning.ico and /dev/null differ diff --git a/VirtualFileSystem/VirtualDrive.cs b/VirtualFileSystem/VirtualDrive.cs deleted file mode 100644 index c77c7e9..0000000 --- a/VirtualFileSystem/VirtualDrive.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; -using System.IO; -using log4net; - -using ITHit.FileSystem.Samples.Common; -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, Settings settings, ILog log) - : base(license, userFileSystemRootPath, settings, log) - { - string remoteStorageRootPath = Mapping.MapPath(userFileSystemRootPath); - RemoteStorageMonitor = new RemoteStorageMonitor(remoteStorageRootPath, this, log); - } - - /// - public override async Task GetVirtualFileSystemItemAsync(string userFileSystemPath, FileSystemItemTypeEnum itemType, ILogger logger) - { - if (itemType == FileSystemItemTypeEnum.File) - { - return new VirtualFile(userFileSystemPath, this, logger); - } - else - { - return new VirtualFolder(userFileSystemPath, this, logger); - } - } - - /// - /// 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 deleted file mode 100644 index 01d594c..0000000 --- a/VirtualFileSystem/VirtualFile.cs +++ /dev/null @@ -1,89 +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 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/VirtualFileSystemItem.cs b/VirtualFileSystem/VirtualFileSystemItem.cs deleted file mode 100644 index 34b67b4..0000000 --- a/VirtualFileSystem/VirtualFileSystemItem.cs +++ /dev/null @@ -1,270 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -using ITHit.FileSystem; -using ITHit.FileSystem.Windows; -using ITHit.FileSystem.Samples.Common; -using ITHit.FileSystem.Samples.Common.Windows; - - -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 VirtualFileSystemItem : IVirtualFileSystemItem, IVirtualLock - { - /// - /// Path of this file of folder in the user file system. - /// - protected readonly string UserFileSystemPath; - - /// - /// Path of this file or folder in the remote storage. - /// - 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. - /// 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; - } - - /// - /// Renames or moves 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) - { - string remoteStorageOldPath = RemoteStoragePath; - string remoteStorageNewPath = Mapping.MapPath(userFileSystemNewPath); - - FileSystemInfo remoteStorageOldItem = FsPath.GetFileSystemItem(remoteStorageOldPath); - if (remoteStorageOldItem != null) - { - try - { - // Disable RemoteStorageMonitor to avoid circular calls. - // This is only required because of the specifics of the simplicity of this example. - Program.VirtualDrive.RemoteStorageMonitor.Enabled = false; - - if (remoteStorageOldItem is FileInfo) - { - (remoteStorageOldItem as FileInfo).MoveTo(remoteStorageNewPath, true); - } - else - { - (remoteStorageOldItem as DirectoryInfo).MoveTo(remoteStorageNewPath); - } - } - finally - { - Program.VirtualDrive.RemoteStorageMonitor.Enabled = true; - } - } - } - - /// - /// Deletes this file or folder in the remote storage. - /// - public async Task DeleteAsync() - { - FileSystemInfo remoteStorageItem = FsPath.GetFileSystemItem(RemoteStoragePath); - if (remoteStorageItem!=null) - { - try - { - // Disable RemoteStorageMonitor to avoid circular calls. - // This is only required because of the specifics of the simplicity of this example. - Program.VirtualDrive.RemoteStorageMonitor.Enabled = false; - - if (remoteStorageItem is FileInfo) - { - remoteStorageItem.Delete(); - } - else - { - (remoteStorageItem as DirectoryInfo).Delete(true); - } - } - finally - { - Program.VirtualDrive.RemoteStorageMonitor.Enabled = true; - } - } - } - - /// - /// Creates or updates file in the remote storage. - /// - /// Path 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. - /// 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. 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) - { - FileInfo remoteStorageItem = new FileInfo(remoteStoragePath); - - try - { - 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. - await using (FileStream remoteStorageStream = remoteStorageItem.Open(mode, FileAccess.Write, FileShare.None)) - { - string userFileSystemPath = Mapping.ReverseMapPath(remoteStoragePath); - if (mode == FileMode.Open) - { - // 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. - FileSystemItemMetadataExt 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."); - } - } - - // 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 the server in the update response - // and return it from this method. - string eTagNew = newInfo.LastWriteTime.ToUniversalTime().ToString("o"); - await VirtualDrive.GetETagManager(userFileSystemPath).SetETagAsync(eTagNew); - - // Update remote storage file content. - if (newContentStream != null) - { - await newContentStream.CopyToAsync(remoteStorageStream); - remoteStorageStream.SetLength(newContentStream.Length); - } - - // Update remote storage file basic info. - WindowsFileSystemItem.SetFileInformation( - remoteStorageStream.SafeFileHandle, - newInfo.Attributes, - newInfo.CreationTime, - newInfo.LastWriteTime, - newInfo.LastAccessTime, - newInfo.LastWriteTime); - - return eTagNew; - } - } - finally - { - Program.VirtualDrive.RemoteStorageMonitor.Enabled = true; - } - } - - - /// - /// Creates or updates folder in the remote storage. - /// - /// 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. - /// 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) - { - DirectoryInfo remoteStorageItem = new DirectoryInfo(remoteStoragePath); - - try - { - Program.VirtualDrive.RemoteStorageMonitor.Enabled = false; // Disable RemoteStorageMonitor to avoid circular calls. - - remoteStorageItem.Create(); - - string userFileSystemPath = Mapping.ReverseMapPath(remoteStoragePath); - if (mode == FileMode.Open) - { - // 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. - FileSystemItemMetadataExt 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."); - } - } - - // 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 eTagNew = newInfo.LastWriteTime.ToUniversalTime().ToString("o"); - - await VirtualDrive.GetETagManager(userFileSystemPath).SetETagAsync(eTagNew); - - remoteStorageItem.Attributes = newInfo.Attributes; - remoteStorageItem.CreationTimeUtc = newInfo.CreationTime.UtcDateTime; - remoteStorageItem.LastWriteTimeUtc = newInfo.LastWriteTime.UtcDateTime; - remoteStorageItem.LastAccessTimeUtc = newInfo.LastAccessTime.UtcDateTime; - remoteStorageItem.LastWriteTimeUtc = newInfo.LastWriteTime.UtcDateTime; - - return eTagNew; - } - finally - { - Program.VirtualDrive.RemoteStorageMonitor.Enabled = true; - } - } - - /// - /// Locks the 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 the property when the - /// item in the remote storage should be updated. Supply the lock-token during the update request in - /// and method calls. - /// - public async Task LockAsync() - { - return new ServerLockInfo { LockToken = "token", Exclusive = true, LockExpirationDateUtc = DateTimeOffset.Now.AddMinutes(30), Owner = "You" }; - } - - /// - /// Unlocks the 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. - /// - public async Task UnlockAsync(string lockToken) - { - - } - } -} diff --git a/VirtualFileSystem/VirtualFolder.cs b/VirtualFileSystem/VirtualFolder.cs deleted file mode 100644 index 707793d..0000000 --- a/VirtualFileSystem/VirtualFolder.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using ITHit.FileSystem; -using ITHit.FileSystem.Samples.Common; - -namespace VirtualFileSystem -{ - /// - /// Represents a folder in the remote storage. 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 VirtualFolder : VirtualFileSystemItem, IVirtualFolder - { - /// - /// Creates instance of this class. - /// - /// Path of this folder in the user file system. - /// Virtual Drive instance that created this item. - /// Logger. - public VirtualFolder(string userfileSystemFolderPath, VirtualDrive virtualDrive, ILogger logger) - : base(userfileSystemFolderPath, virtualDrive, logger) - { - - } - - /// - /// Gets list of files and folders in this folder in the remote storage. - /// - /// 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) - { - // 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(); - foreach (FileSystemInfo remoteStorageItem in remoteStorageChildren) - { - FileSystemItemMetadataExt itemInfo = Mapping.GetUserFileSysteItemMetadata(remoteStorageItem); - userFileSystemChildren.Add(itemInfo); - } - - return userFileSystemChildren; - } - - /// - /// Creates a file in the remote storage. - /// - /// Information about the new file. - /// New file content. - /// New ETag returned from the remote storage. - public async Task CreateFileAsync(IFileMetadata fileInfo, Stream content) - { - string itemPath = Path.Combine(RemoteStoragePath, fileInfo.Name); - return await CreateOrUpdateFileAsync(itemPath, fileInfo, FileMode.CreateNew, content); - } - - /// - /// Creates a folder in the remote storage. - /// - /// Information about the new folder. - /// New ETag returned from the remote storage. - public async Task CreateFolderAsync(IFolderMetadata folderInfo) - { - string itemPath = Path.Combine(RemoteStoragePath, folderInfo.Name); - return await CreateOrUpdateFolderAsync(itemPath, folderInfo, FileMode.CreateNew); - } - - /// - /// Updates folder 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 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, eTagOld, lockInfo); - } - - } -} diff --git a/VirtualFileSystemMac/FileProviderExtension/VfsFile.cs b/VirtualFileSystemMac/FileProviderExtension/VfsFile.cs deleted file mode 100644 index 5753dd0..0000000 --- a/VirtualFileSystemMac/FileProviderExtension/VfsFile.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using ITHit.FileSystem; -using ITHit.FileSystem.Mac; -using VirtualFilesystemCommon; - -namespace FileProviderExtension -{ - public class VfsFile : VfsFileSystemItem, IFileMac, IFileMetadata - { - public long Length { get; set; } - - public VfsFile(string name, FileAttributes attributes, - DateTimeOffset creationTime, DateTimeOffset lastWriteTime, DateTimeOffset lastAccessTime, long length, ILogger logger) - : base(name, logger) - { - Name = name; - Attributes = attributes; - CreationTime = creationTime; - LastWriteTime = lastWriteTime; - LastAccessTime = lastAccessTime; - Length = length; - } - - - public async Task OpenAsync(IOperationContext operationContext, IResultContext context) - { - throw new NotImplementedException(); - } - - public async Task TransferDataAsync(long offset, long length, ITransferDataOperationContext operationContext, ITransferDataResultContext resultContext) - { - Logger.LogMessage($"IFile.TransferDataAsync({offset}, {length})", UserFileSystemPath); - - byte[] buffer = new byte[length]; - - await using (FileStream stream = File.OpenRead(RemoteStoragePath)) - { - stream.Seek(offset, SeekOrigin.Begin); - - int bytesRead = await stream.ReadAsync(buffer, 0, (int)length); - } - - resultContext.ReturnData(buffer, offset, length); - } - - public async Task ValidateDataAsync(long offset, long length, IValidateDataOperationContext operationContext, IValidateDataResultContext resultContext) - { - throw new NotImplementedException(); - } - - public async Task UpdateAsync(Stream stream, IResultContext context) - { - Logger.LogMessage($"IFile.UpdateAsync", UserFileSystemPath); - - Logger.LogMessage("Sending to remote storage", UserFileSystemPath); - FileInfo remoteStorageItem = new FileInfo(RemoteStoragePath); - await using (FileStream remoteStorageStream = remoteStorageItem.Open(FileMode.Open, FileAccess.Write, FileShare.None)) - { - string userFileSystemPath = Mapping.ReverseMapPath(RemoteStoragePath); - - // update remote storage file content. - if (stream != null) - { - await stream.CopyToAsync(remoteStorageStream); - remoteStorageStream.SetLength(stream.Length); - } - } - Logger.LogMessage("Sent to remote storage succesefully", UserFileSystemPath); - } - - public Task CloseAsync(IOperationContext operationContext, IResultContext context) - { - throw new NotImplementedException(); - } - } -} diff --git a/VirtualFileSystemMac/FileProviderExtension/VfsFolder.cs b/VirtualFileSystemMac/FileProviderExtension/VfsFolder.cs deleted file mode 100644 index 695a7c6..0000000 --- a/VirtualFileSystemMac/FileProviderExtension/VfsFolder.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using System.Collections.Generic; -using ITHit.FileSystem; -using VirtualFilesystemCommon; - -namespace FileProviderExtension -{ - public class VfsFolder : VfsFileSystemItem, IFolder, IFolderMetadata - { - public VfsFolder(string name, FileAttributes attributes, - DateTimeOffset creationTime, DateTimeOffset lastWriteTime, - DateTimeOffset lastAccessTime, ILogger logger) - : base(name, logger) - { - Name = name; - Attributes = attributes; - CreationTime = creationTime; - LastWriteTime = lastWriteTime; - LastAccessTime = lastAccessTime; - } - - public async Task GetChildrenAsync(string pattern, IOperationContext operationContext, IFolderListingResultContext resultContext) - { - Logger.LogMessage($"IFolder.GetChildrenAsync({pattern})", UserFileSystemPath); - - IEnumerable remoteStorageChildren = new DirectoryInfo(Mapping.MapPath(Name)).EnumerateFileSystemInfos(pattern); - List infos = new List(); - - foreach (FileSystemInfo remoteStorageItem in remoteStorageChildren) - { - VfsFileSystemItem info = (VfsFileSystemItem)Mapping.GetUserFileSysteItemBasicInfo(remoteStorageItem, Logger); - info.Name = Mapping.ReverseMapPath(info.Name); - - infos.Add(info); - } - - resultContext.ReturnChildren(infos.ToArray(), infos.Count); - } - } -} diff --git a/VirtualFileSystemMac/FileProviderExtension/packages.config b/VirtualFileSystemMac/FileProviderExtension/packages.config deleted file mode 100644 index 543e81d..0000000 --- a/VirtualFileSystemMac/FileProviderExtension/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/VirtualFileSystemMac/RemoteStorage/General.docx b/VirtualFileSystemMac/RemoteStorage/General.docx deleted file mode 100644 index fd8d5ff..0000000 Binary files a/VirtualFileSystemMac/RemoteStorage/General.docx and /dev/null differ diff --git a/VirtualFileSystemMac/RemoteStorage/Introduction.pptx b/VirtualFileSystemMac/RemoteStorage/Introduction.pptx deleted file mode 100644 index 8260da6..0000000 Binary files a/VirtualFileSystemMac/RemoteStorage/Introduction.pptx and /dev/null differ diff --git a/VirtualFileSystemMac/VirtualFilesystemCommon/packages.config b/VirtualFileSystemMac/VirtualFilesystemCommon/packages.config deleted file mode 100644 index 7ede3a3..0000000 --- a/VirtualFileSystemMac/VirtualFilesystemCommon/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/WebDAVDrive/Images/Down.ico b/WebDAVDrive/Images/Down.ico deleted file mode 100644 index 820848b..0000000 Binary files a/WebDAVDrive/Images/Down.ico and /dev/null differ diff --git a/WebDAVDrive/Images/Drive.ico b/WebDAVDrive/Images/Drive.ico deleted file mode 100644 index 0f2b936..0000000 Binary files a/WebDAVDrive/Images/Drive.ico and /dev/null differ diff --git a/WebDAVDrive/Images/DrivePause.ico b/WebDAVDrive/Images/DrivePause.ico deleted file mode 100644 index 1b3e2fd..0000000 Binary files a/WebDAVDrive/Images/DrivePause.ico and /dev/null differ diff --git a/WebDAVDrive/Images/DriveSync.ico b/WebDAVDrive/Images/DriveSync.ico deleted file mode 100644 index 049c4e3..0000000 Binary files a/WebDAVDrive/Images/DriveSync.ico and /dev/null differ diff --git a/WebDAVDrive/Images/Up.ico b/WebDAVDrive/Images/Up.ico deleted file mode 100644 index 0bcb6b7..0000000 Binary files a/WebDAVDrive/Images/Up.ico and /dev/null differ diff --git a/WebDAVDrive/Images/Warning.ico b/WebDAVDrive/Images/Warning.ico deleted file mode 100644 index 173c60d..0000000 Binary files a/WebDAVDrive/Images/Warning.ico and /dev/null differ diff --git a/WebDAVDrive/VirtualDrive.cs b/WebDAVDrive/VirtualDrive.cs deleted file mode 100644 index fe93ad5..0000000 --- a/WebDAVDrive/VirtualDrive.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; -using System.IO; -using log4net; - -using ITHit.FileSystem.Samples.Common; -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, Settings settings, ILog log) - : base(license, userFileSystemRootPath, settings, log) - { - string remoteStorageRootPath = Mapping.MapPath(userFileSystemRootPath); - remoteStorageMonitor = new RemoteStorageMonitor(remoteStorageRootPath, this, log); - } - - /// - public override async Task GetVirtualFileSystemItemAsync(string userFileSystemPath, FileSystemItemTypeEnum itemType, ILogger logger) - { - if (itemType == FileSystemItemTypeEnum.File) - { - return new VirtualFile(userFileSystemPath, logger); - } - else - { - return new VirtualFolder(userFileSystemPath, logger); - } - } - - /// - /// 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 deleted file mode 100644 index d3fb9dd..0000000 --- a/WebDAVDrive/VirtualFile.cs +++ /dev/null @@ -1,89 +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 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/VirtualFileSystemItem.cs b/WebDAVDrive/VirtualFileSystemItem.cs deleted file mode 100644 index 5dbaf61..0000000 --- a/WebDAVDrive/VirtualFileSystemItem.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -using ITHit.FileSystem; -using ITHit.FileSystem.Samples.Common; -using ITHit.WebDAV.Client; - -namespace WebDAVDrive -{ - /// - /// 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 VirtualFileSystemItem : IVirtualFileSystemItem, IVirtualLock - { - /// - /// Path of this file of folder in the user file system. - /// - protected string UserFileSystemPath; - - /// - /// Path of this file or folder in the remote storage. - /// - protected string RemoteStorageUri; - - /// - /// Logger. - /// - protected readonly ILogger Logger; - - /// - /// Creates instance of this class. - /// - /// Path of this file of folder in the user file system. - /// Logger. - public VirtualFileSystemItem(string userFileSystemPath, ILogger logger) - { - this.UserFileSystemPath = userFileSystemPath; - this.RemoteStorageUri = Mapping.MapPath(userFileSystemPath); - this.Logger = logger; - } - - /// - /// 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) - { - string remoteStorageOldPath = RemoteStorageUri; - string remoteStorageNewPath = Mapping.MapPath(userFileSystemNewPath); - - await Program.DavClient.MoveToAsync(new Uri(remoteStorageOldPath), new Uri(remoteStorageNewPath), true); - } - - /// - /// Deletes this file or folder in the remote storage. - /// - public async Task DeleteAsync() - { - await Program.DavClient.DeleteAsync(new Uri(RemoteStorageUri)); - } - - /// - /// 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 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. - /// 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 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, lockInfo?.LockToken, eTagOld); - - // Update remote storage file content. - using (Stream davContentStream = await request.GetRequestStreamAsync()) - { - if (content != null) - { - await content.CopyToAsync(davContentStream); - } - - // 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(); - eTagNew = response.Headers["ETag"]; - response.Close(); - } - - } - return eTagNew; - } - - /// - /// 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. - /// - public async Task LockAsync() - { - LockInfo lockInfo = await Program.DavClient.LockAsync(new Uri(RemoteStorageUri), LockScope.Exclusive, false, null, TimeSpan.MaxValue); - return new ServerLockInfo { - LockToken = lockInfo.LockToken.LockToken, - Exclusive = lockInfo.LockScope == LockScope.Exclusive, - Owner = lockInfo.Owner, - LockExpirationDateUtc = DateTimeOffset.Now.Add(lockInfo.TimeOut) - }; - } - - /// - /// 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. - /// - public async Task UnlockAsync(string lockToken) - { - try - { - await Program.DavClient.UnlockAsync(new Uri(RemoteStorageUri), lockToken); - } - catch(ITHit.WebDAV.Client.Exceptions.ConflictException) - { - // The item is already unlocked. - } - } - } -} diff --git a/WebDAVDrive/VirtualFolder.cs b/WebDAVDrive/VirtualFolder.cs deleted file mode 100644 index e8e8160..0000000 --- a/WebDAVDrive/VirtualFolder.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using ITHit.FileSystem; -using ITHit.FileSystem.Samples.Common; -using ITHit.WebDAV.Client; - -namespace WebDAVDrive -{ - /// - /// 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 VirtualFolder : VirtualFileSystemItem, IVirtualFolder - { - /// - /// Creates instance of this class. - /// - /// Path of this folder in the user file system. - /// Logger. - public VirtualFolder(string userfileSystemFolderPath, ILogger logger) - : base(userfileSystemFolderPath, logger) - { - - } - - /// - /// 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) - { - // This method has a 60 sec timeout. - // To process longer requests modify the IFolder.GetChildrenAsync() implementation. - - IHierarchyItemAsync[] remoteStorageChildren = null; - // Retry the request in case the log-in dialog is shown. - try - { - remoteStorageChildren = await Program.DavClient.GetChildrenAsync(new Uri(RemoteStorageUri), false); - } - catch (ITHit.WebDAV.Client.Exceptions.Redirect302Exception) - { - remoteStorageChildren = await Program.DavClient.GetChildrenAsync(new Uri(RemoteStorageUri), false); - } - - List userFileSystemChildren = new List(); - foreach (IHierarchyItemAsync remoteStorageItem in remoteStorageChildren) - { - FileSystemItemMetadataExt itemInfo = Mapping.GetUserFileSystemItemMetadata(remoteStorageItem); - userFileSystemChildren.Add(itemInfo); - } - - return userFileSystemChildren; - } - - /// - /// 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. - 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 new folder in the remote storage. - /// - /// Information about the new folder. - /// 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); - return null; // This implementation does not support ETags on folders. - } - - /// - /// 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. - public async Task UpdateAsync(IFolderMetadata folderInfo, string eTagOld = null, ServerLockInfo lockInfo = null) - { - return null; - } - } -} diff --git a/ITHit.FileSystem.Samples.Common.Windows/ITHit.FileSystem.Samples.Common.Windows.csproj b/Windows/Common/Common.Windows.csproj similarity index 85% rename from ITHit.FileSystem.Samples.Common.Windows/ITHit.FileSystem.Samples.Common.Windows.csproj rename to Windows/Common/Common.Windows.csproj index 93e2264..93b4ab5 100644 --- a/ITHit.FileSystem.Samples.Common.Windows/ITHit.FileSystem.Samples.Common.Windows.csproj +++ b/Windows/Common/Common.Windows.csproj @@ -12,7 +12,7 @@ - - + + \ No newline at end of file diff --git a/Windows/Common/CustomDataManager.cs b/Windows/Common/CustomDataManager.cs new file mode 100644 index 0000000..65bca76 --- /dev/null +++ b/Windows/Common/CustomDataManager.cs @@ -0,0 +1,568 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Windows.Storage; +using Windows.Storage.Provider; +using System.Linq; + +namespace ITHit.FileSystem.Samples.Common.Windows +{ + /// + /// Manages custom data associated with the item. + /// + /// + /// We can not store custom data inside placeholder because of the MS Office transactional save, + /// which renames and deletes the file, so all custom data is lost with it. + /// + public class CustomDataManager + { + /// + /// Path in user file system with which this custom data corresponds. + /// + private readonly string userFileSystemPath; + + /// + /// Path to the folder that stores custom data associated with files and folders. + /// + private readonly string serverDataFolderPath; + + /// + /// Virtual file system root path. + /// + private readonly string userFileSystemRootPath; + + /// + /// Path to the icons folder. + /// + private readonly string iconsFolderPath; + + /// + /// Logger. + /// + private readonly ILogger logger; + + /// + /// Path to the file that stores custom columns data. + /// + private readonly string customColumnsFilePath; + + /// + /// Custom columns file extension. + /// + private const string customColumnsExt = ".columns"; + + /// + /// Creates instance of this class. + /// + public CustomDataManager( + string userFileSystemPath, + string serverDataFolderPath, + string userFileSystemRootPath, + string iconsFolderPath, + ILogger logger) + { + this.userFileSystemPath = userFileSystemPath ?? throw new NullReferenceException(nameof(userFileSystemPath)); + this.serverDataFolderPath = serverDataFolderPath ?? throw new NullReferenceException(nameof(serverDataFolderPath)); + this.userFileSystemRootPath = userFileSystemRootPath ?? throw new NullReferenceException(nameof(userFileSystemRootPath)); + this.iconsFolderPath = iconsFolderPath ?? throw new NullReferenceException(nameof(iconsFolderPath)); + this.logger = logger ?? throw new NullReferenceException(nameof(logger)); + + customColumnsFilePath = $"{GetColumnsFilePath(userFileSystemPath)}{customColumnsExt}"; + } + + /// + /// Indicates if the item was saved to the remote storage. + /// + /// + /// To detect if this is a new file we must store some marker ouside of the file, + /// for the marker to survive MS Office transactional save, which deletes and recreates the file. + /// + public bool IsNew + { + get + { + // If ETag file exists, this means the data was succcesefully saved to the server. + ETagManager eTagManager = new ETagManager(userFileSystemPath, serverDataFolderPath, userFileSystemRootPath, logger); + return !eTagManager.ETagExists(); + } + set + { + ETagManager eTagManager = new ETagManager(userFileSystemPath, serverDataFolderPath, userFileSystemRootPath, logger); + if(value) + { + eTagManager.DeleteETag(); + } + else + { + eTagManager.EnsureETagExists(); + } + } + } + + /* + internal async Task ClearStateAsync() + { + if (FsPath.Exists(userFileSystemPath)) + { + await SetIconAsync(false); + } + } + + internal async Task SetUploadErrorStateAsync(Exception ex) + { + if (FsPath.Exists(userFileSystemPath)) + { + if (ex is ConflictException) + { + await SetConflictIconAsync(true); + } + else + { + await SetUploadPendingIconAsync(true); + } + } + } + + private async Task SetDownloadErrorStateAsync(Exception ex) + { + if (FsPath.Exists(userFileSystemPath)) + { + if (ex is ConflictException) + { + await SetConflictIconAsync(true); + } + if (ex is ExistsException) + { + await SetConflictIconAsync(true); + } + else + { + // Could be BlockedException or other exception. + await SetDownloadPendingIconAsync(true); + } + } + } + + /// + /// Sets or removes "Download pending" icon. + /// + /// 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"); + } + + /// + /// Sets or removes "Upload pending" icon. + /// + /// 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"); + } + */ + + /// + /// Sets or removes "Conflict" icon. + /// + /// True to display the icon. False - to remove the icon. + private async Task SetConflictIconAsync(bool set) + { + await SetIconAsync(set, (int)CustomColumnIds.ConflictIcon, "Error.ico", "Conflict. File is modified both on the server and on the client."); + } + + /// + /// Sets or removes "Lock" icon and all lock properties. + /// + /// Information about the lock. Pass null to remove lock info. + public async Task SetLockInfoAsync(ServerLockInfo lockInfo) + { + if (lockInfo != null) + { + // Add lock info columns. + IEnumerable lockProps = lockInfo.GetLockProperties(Path.Combine(iconsFolderPath, "Locked.ico")); + await SetCustomColumnsAsync(lockProps); + } + else + { + // Remove lock info columns. + IEnumerable lockProps = new ServerLockInfo().GetLockProperties(null); + await RemoveCustomColumnsAsync(lockProps); + } + } + + /// + /// Sets or removes "Lock pending" icon. + /// + /// True to display the icon. False - to remove the icon. + public async Task SetLockPendingIconAsync(bool set) + { + await SetIconAsync(set, (int)CustomColumnIds.LockOwnerIcon, "LockedPending.ico", "Updating lock..."); + } + + /// + /// Sets or removes icon. + /// + /// True to display the icon. False - to remove the icon. + private async Task SetIconAsync(bool set, int id, string iconFile = null, string description = null) + { + if(set) + { + string iconFilePath = Path.Combine(iconsFolderPath, iconFile); + FileSystemItemPropertyData propData = new FileSystemItemPropertyData((int)id, description, iconFilePath); + await SetCustomColumnsAsync(new []{ propData }); + } + else + { + FileSystemItemPropertyData propData = new FileSystemItemPropertyData((int)id, null); + await RemoveCustomColumnsAsync(new []{ propData }); + } + } + + /* + /// + /// Sets or removes icon. + /// + /// True to display the icon. False - to remove the icon. + private async Task SetIconAsync(bool set, int? id = null, string iconFile = null, string description = null) + { + IStorageItem storageItem = await FsPath.GetStorageItemAsync(userFileSystemPath); + + if (storageItem == null) + { + // 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 + { + if (set) + { + StorageProviderItemProperty propState = new StorageProviderItemProperty() + { + Id = id.Value, + Value = description, + IconResource = Path.Combine(virtualDrive.Settings.IconsFolderPath, iconFile) + }; + await StorageProviderItemProperties.SetAsync(storageItem, new StorageProviderItemProperty[] { propState }); + } + else + { + await StorageProviderItemProperties.SetAsync(storageItem, new StorageProviderItemProperty[] { }); + } + } + + // Setting status icon failes for blocked files. + catch (FileNotFoundException) + { + + } + catch (COMException) + { + // "Error HRESULT E_FAIL has been returned from a call to a COM component." + } + catch (Exception ex) + { + if (ex.HResult == -2147024499) + { + // "The operation failed due to a conflicting cloud file property lock. (0x8007018D)" + } + else + { + // Rethrow the exception preserving stack trace of the original exception. + System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw(); + } + } + } + */ + + /// + /// 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) + { + bool resultSet = false; + // Changing read-only attribute on folders triggers folders listing. Changing it on files only. + + if (FsPath.IsFile(userFileSystemPath)) + { + FileInfo file = new FileInfo(userFileSystemPath); + if (set != file.IsReadOnly) + { + // Set/Remove read-only attribute. + if (set) + { + new FileInfo(userFileSystemPath).Attributes |= System.IO.FileAttributes.ReadOnly; + } + else + { + new FileInfo(userFileSystemPath).Attributes &= ~System.IO.FileAttributes.ReadOnly; + } + + resultSet = true; + } + } + + return resultSet; + } + + /// + /// Removes columns. + /// + /// List of columns to remove. + public async Task RemoveCustomColumnsAsync(IEnumerable customColumnsData) + { + // All cutom columns must be set togather, otherwise columns data will be wiped. + + // Read custom columns. + List allColumns = (await ReadCustomColumnsAsync()).ToList(); + + // Remove columns. + foreach (var removeColumn in customColumnsData) + { + var column = allColumns.FirstOrDefault(x => x.Id == removeColumn.Id); + if (column != null) + { + allColumns.Remove(column); + } + } + + // Save custom columns + await WriteCustomColumnsAsync(allColumns); + + // Display in Windows File Manager. + await ShowCustomColumnsAsync(allColumns); + + } + + /// + /// Adds or updates custom columns data, preserving existing columns. + /// + /// List of columns to add or update. + public async Task SetCustomColumnsAsync(IEnumerable customColumnsData) + { + // All cutom columns must be set togather, otherwise columns data will be wiped. + + // Read custom columns. + IEnumerable allColumns = await ReadCustomColumnsAsync(); + + // Merge existing colums with columns that must be set. + IList newColumns = new List(customColumnsData); + foreach (var curColumn in allColumns) + { + var column = customColumnsData.FirstOrDefault(x => x.Id == curColumn.Id); + if(column == null) + { + newColumns.Add(curColumn); + } + } + + // Save custom columns + await WriteCustomColumnsAsync(newColumns); + + // Display in Windows File Manager. + await ShowCustomColumnsAsync(newColumns); + } + + /// + /// Reads custom columns data from persistent media. + /// + private async Task> ReadCustomColumnsAsync() + { + if (!File.Exists(customColumnsFilePath)) + { + return new List(); + } + await using (FileStream stream = File.Open(customColumnsFilePath, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.Read)) + { + stream.Seek(0, SeekOrigin.Begin); + return await JsonSerializer.DeserializeAsync>(stream); + } + } + + /// + /// Saves custom columns data to persistent media. + /// + private async Task WriteCustomColumnsAsync(IEnumerable customColumnsData) + { + if (customColumnsData.Any()) + { + await using (FileStream stream = File.Open(customColumnsFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Delete)) + { + stream.Seek(0, SeekOrigin.Begin); + await JsonSerializer.SerializeAsync(stream, customColumnsData); + stream.SetLength(stream.Position); + } + } + else + { + File.Delete(customColumnsFilePath); + } + } + + /// + /// Loads data saved for custom columns and shows it in Windows File Manager. + /// + /// + public async Task RefreshCustomColumnsAsync() + { + await ShowCustomColumnsAsync(await ReadCustomColumnsAsync()); + } + + /// + /// Displays custom columns in Windows file mnager. + /// + /// list of columns to display. + private async Task ShowCustomColumnsAsync(IEnumerable customColumnsData) + { + List customColumns = new List(); + + if (customColumnsData != null) + { + foreach (FileSystemItemPropertyData column in customColumnsData) + { + customColumns.Add(new StorageProviderItemProperty() + { + Id = column.Id, + // If value is empty Windows File Manager crushes. + Value = string.IsNullOrEmpty(column.Value) ? "-" : column.Value, + // If icon is not set Windows File Manager crushes. + IconResource = column.IconResource ?? Path.Combine(iconsFolderPath, "Blank.ico") + }); + } + } + + // This method may be called on temp files, typically created by MS Office, that exist for a short period of time. + IStorageItem storageItem = await FsPath.GetStorageItemAsync(userFileSystemPath); + if (storageItem == null) + { + // 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 (nor handled by HandleProcessCorruptedStateExceptions) and causes a fatal crush. + return; + } + + FileInfo file = new FileInfo(userFileSystemPath); + + // Can not set provider properties on read-only files. + // Changing read-only attribute on folders triggers folders listing. Changing on files only. + bool readOnly = file.IsReadOnly; + + // Remove read-only attribute. + if (readOnly && ((file.Attributes & System.IO.FileAttributes.Directory) == 0)) + { + file.IsReadOnly = false; + //new FileInfo(userFileSystemPath).Attributes &= ~System.IO.FileAttributes.ReadOnly; + } + + // Update columns data. + await StorageProviderItemProperties.SetAsync(storageItem, customColumns); + + // Set read-only attribute. + if (readOnly && ((file.Attributes & System.IO.FileAttributes.Directory) == 0)) + { + file.IsReadOnly = true; + //new FileInfo(userFileSystemPath).Attributes |= System.IO.FileAttributes.ReadOnly; + } + } + + + /// + /// Provides methods for reading and writing ETags. + /// + public ETagManager ETagManager + { + get { return new ETagManager(userFileSystemPath, serverDataFolderPath, userFileSystemRootPath, logger); } + } + + /// + /// Manages lock-info and lock-mode files that correspond with the file in the user file system. + /// + public LockManager LockManager + { + get { return new LockManager(userFileSystemPath, serverDataFolderPath, userFileSystemRootPath, logger); } + } + + /// + /// Moves all custom data to a new location. + /// + /// Path of the file in the user file system to move this data to. + public async Task MoveToAsync(string userFileSystemNewPath) + { + await CustomColumnsMoveToAsync(userFileSystemNewPath); + await ETagManager.MoveToAsync(userFileSystemNewPath); + await LockManager.MoveToAsync(userFileSystemNewPath); + } + + /// + /// Gets custom columns file path (without extension). + /// + /// Path of the file in user file system to get the path for. + private string GetColumnsFilePath(string userFileSystemPath) + { + // Get path relative to the virtual root. + string relativePath = userFileSystemPath.TrimEnd(Path.DirectorySeparatorChar).Substring( + userFileSystemRootPath.TrimEnd(Path.DirectorySeparatorChar).Length); + + return $"{serverDataFolderPath.TrimEnd(Path.DirectorySeparatorChar)}{relativePath}"; + } + + + /// + /// Moves custom columns to a new location. + /// + /// Path of the file in the user file system to move custom columns to. + private async Task CustomColumnsMoveToAsync(string userFileSystemNewPath) + { + // Move custom columns file. + string customColumnsTargetPath = GetColumnsFilePath(userFileSystemNewPath); + string customColumnsFileTargetPath = $"{customColumnsTargetPath}{customColumnsExt}"; + + // Ensure the target directory exisit, in case we are moving into empty folder or which is offline. + new FileInfo(customColumnsFileTargetPath).Directory.Create(); + + if (File.Exists(customColumnsFilePath)) + { + File.Move(customColumnsFilePath, customColumnsFileTargetPath); + } + + // If this is a folder, move all data in this folder. + string customColumnsSourceFolderPath = GetColumnsFilePath(userFileSystemPath); + if (Directory.Exists(customColumnsSourceFolderPath)) + { + Directory.Move(customColumnsSourceFolderPath, customColumnsTargetPath); + } + } + + /// + /// Deletes all custom data associated with the item. + /// + public void Delete() + { + DeleteCustomColumns(); + ETagManager.DeleteETag(); + LockManager.DeleteLock(); + } + + /// + /// Deletes custom columns associated with a file. + /// + private void DeleteCustomColumns() + { + File.Delete(customColumnsFilePath); + + // If this is a folder, delete all custom columns in this folder. + string customColumnsFolderPath = GetColumnsFilePath(userFileSystemPath); + if (Directory.Exists(customColumnsFolderPath)) + { + Directory.Delete(customColumnsFolderPath, true); + } + } + } +} diff --git a/ITHit.FileSystem.Samples.Common.Windows/ETagManager.cs b/Windows/Common/ETagManager.cs similarity index 74% rename from ITHit.FileSystem.Samples.Common.Windows/ETagManager.cs rename to Windows/Common/ETagManager.cs index c034a5f..d426732 100644 --- a/ITHit.FileSystem.Samples.Common.Windows/ETagManager.cs +++ b/Windows/Common/ETagManager.cs @@ -7,7 +7,7 @@ namespace ITHit.FileSystem.Samples.Common.Windows { /// - /// Provides method for reading and writing ETags. + /// Provides methods for reading and writing ETags. /// public class ETagManager { @@ -15,7 +15,7 @@ public class ETagManager private readonly string userFileSystemRootPath; private readonly string serverDataFolderPath; private readonly ILogger logger; - internal readonly string ETagFilePath; + private readonly string eTagFilePath; private const string eTagExt = ".etag"; @@ -25,13 +25,13 @@ public class ETagManager /// User file system root path. /// Folder where ETags are stored. /// Logger. - internal ETagManager(string userFileSystemPath, string serverDataFolderPath, string userFileSystemRootPath, ILogger logger) + public ETagManager(string userFileSystemPath, string serverDataFolderPath, string userFileSystemRootPath, ILogger logger) { this.userFileSystemPath = userFileSystemPath; this.userFileSystemRootPath = userFileSystemRootPath; this.serverDataFolderPath = serverDataFolderPath; this.logger = logger; - this.ETagFilePath = $"{GetEtagFilePath(userFileSystemPath)}{eTagExt}"; + this.eTagFilePath = $"{GetETagFilePath(userFileSystemPath)}{eTagExt}"; } /// @@ -41,14 +41,14 @@ internal ETagManager(string userFileSystemPath, string serverDataFolderPath, str /// public async Task SetETagAsync(string eTag) { - // Delete ETag file if null or empty string value is passed. - if (string.IsNullOrEmpty(eTag) && File.Exists(ETagFilePath)) + // Delete ETag file if null string value is passed. + if ( (eTag==null) && File.Exists(eTagFilePath)) { DeleteETag(); } - Directory.CreateDirectory(Path.GetDirectoryName(ETagFilePath)); - await File.WriteAllTextAsync(ETagFilePath, eTag); + Directory.CreateDirectory(Path.GetDirectoryName(eTagFilePath)); + await File.WriteAllTextAsync(eTagFilePath, eTag); } /// @@ -57,11 +57,28 @@ public async Task SetETagAsync(string eTag) /// ETag. public async Task GetETagAsync() { - if (!File.Exists(ETagFilePath)) + if (!File.Exists(eTagFilePath)) { return null; } - return await File.ReadAllTextAsync(ETagFilePath); + return await File.ReadAllTextAsync(eTagFilePath); + } + + /// + /// Returns true if the ETag file exists. False - otherwise. + /// + public bool ETagExists() + { + return File.Exists(eTagFilePath); + } + + public void EnsureETagExists() + { + Directory.CreateDirectory(Path.GetDirectoryName(eTagFilePath)); + using (FileStream stream = File.Open(eTagFilePath, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete)) + { + + } } /// @@ -71,15 +88,18 @@ public async Task GetETagAsync() internal async Task MoveToAsync(string userFileSystemNewPath) { // Move ETag file. - string eTagTargetPath = GetEtagFilePath(userFileSystemNewPath); + 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 (File.Exists(eTagFilePath)) + { + File.Move(eTagFilePath, eTagFileTargetPath); + } // If this is a folder, move all eTags in this folder. - string eTagSourceFolderPath = GetEtagFilePath(userFileSystemPath); + string eTagSourceFolderPath = GetETagFilePath(userFileSystemPath); if (Directory.Exists(eTagSourceFolderPath)) { Directory.Move(eTagSourceFolderPath, eTagTargetPath); @@ -91,10 +111,10 @@ internal async Task MoveToAsync(string userFileSystemNewPath) /// internal void DeleteETag() { - File.Delete(ETagFilePath); + File.Delete(eTagFilePath); // If this is a folder, delete all eTags in this folder. - string eTagFolderPath = GetEtagFilePath(userFileSystemPath); + string eTagFolderPath = GetETagFilePath(userFileSystemPath); if (Directory.Exists(eTagFolderPath)) { Directory.Delete(eTagFolderPath, true); @@ -128,7 +148,7 @@ public async Task ETagEqualsAsync(FileSystemItemMetadataExt remoteStorageI /// Gets ETag file path (without extension). /// /// Path of the file in user file system to get ETag path for. - private string GetEtagFilePath(string userFileSystemPath) + private string GetETagFilePath(string userFileSystemPath) { // Get path relative to the virtual root. string relativePath = userFileSystemPath.TrimEnd(Path.DirectorySeparatorChar).Substring( diff --git a/Windows/Common/FsPath.cs b/Windows/Common/FsPath.cs new file mode 100644 index 0000000..befc3b5 --- /dev/null +++ b/Windows/Common/FsPath.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Windows.Storage; +using FileAttributes = System.IO.FileAttributes; +using ITHit.FileSystem.Windows; + +namespace ITHit.FileSystem.Samples.Common.Windows +{ + /// + /// Provides file system operations. Helps determining file and folder existence and creating file and folder items. + /// + public static class FsPath + { + /// + /// Returns true if the path points to a file. False - otherwise. + /// + /// Throws exception if the file/folder does not exists. + /// Path to the file or folder. + /// True if the path is file. False - otherwise. + public static bool IsFile(string path) + { + return !IsFolder(path); + } + + /// + /// Returns true if the path points to a folder. False - otherwise. + /// + /// Throws exception if the file/folder does not exists. + /// Path to the file or folder. + /// True if the path is folder. False - otherwise. + public static bool IsFolder(string path) + { + FileAttributes attributes = File.GetAttributes(path); + return (attributes & FileAttributes.Directory) == FileAttributes.Directory; + } + + /// + /// Returns true if a file or folder exists under the specified path. False - otherwise. + /// + /// Does not throw exceptions. + /// Path to the file or folder. + /// True if the file or folder exists under specified path. False - otherwise. + public static bool Exists(string path) + { + return File.Exists(path) || Directory.Exists(path); + } + + public static FileSystemItemType GetItemType(string path) + { + return FsPath.IsFile(path) ? FileSystemItemType.File : FileSystemItemType.Folder; + } + + /// + /// Returns storage item or null if item does not eists. + /// + /// Path to the file or folder. + /// + /// Instance of or + /// that corresponds to path or null if item does not exists. + /// + public static async Task GetStorageItemAsync(string path) + { + if (File.Exists(path)) + { + return await StorageFile.GetFileFromPathAsync(path); + } + if (Directory.Exists(path)) + { + return await StorageFolder.GetFolderFromPathAsync(path); + } + return null; + } + + /// + /// Returns folder or file item or null if the item does not exists. + /// + /// Path to the file or folder. + /// + /// Instance of or + /// that corresponds to path or null if item does not exists. + /// + public static FileSystemInfo GetFileSystemItem(string path) + { + if (File.Exists(path)) + { + return new FileInfo(path); + } + if (Directory.Exists(path)) + { + return new DirectoryInfo(path); + } + return null; + } + + /// + /// Gets file or folder attributes in a human-readable form. + /// + /// Path to the file or folder. + /// String that represents file or folder attributes or null if the file/folder is not found. + public static string GetAttString(string path) + { + try + { + return File.GetAttributes(path).ToString(); + } + catch + { + return null; + } + } + + /// + /// Gets formatted file size or null for folders or if the file is not found. + /// + /// Path to a file or folder. + public static string Size(string path) + { + if (!File.Exists(path)) + { + return null; + } + + long length; + try + { + length = new FileInfo(path).Length; + } + catch + { + 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) + { + return "0" + suf[0]; + } + long bytes = Math.Abs(length); + int place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024))); + double num = Math.Round(bytes / Math.Pow(1024, place), 1); + return (Math.Sign(length) * num).ToString() + suf[place]; + } + } +} diff --git a/Windows/Common/LockManager.cs b/Windows/Common/LockManager.cs new file mode 100644 index 0000000..a7ef00e --- /dev/null +++ b/Windows/Common/LockManager.cs @@ -0,0 +1,217 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +using ITHit.FileSystem.Windows; + + +namespace ITHit.FileSystem.Samples.Common.Windows +{ + /// + /// Manages lock-info and lock mode files that correspond with the file in the user file system. + /// + /// + /// + /// The lock info must be stored outside of , because the file in user file system + /// is renamed and deleted during MS Office transactional save operation. + /// The lock must remain regardless of the transactional save. + /// + /// + public class LockManager + { + /// + /// Path in user file system with which this lock corresponds. + /// + private readonly string userFileSystemPath; + + /// + /// Path to the folder that stores custom data associated with files and folders. + /// + private readonly string serverDataFolderPath; + + /// + /// Virtual file system root path. + /// + private readonly string userFileSystemRootPath; + + /// + /// Path to the file that contains the lock mode. + /// + private readonly string lockModeFilePath; + + /// + /// Path to the file that contains the lock-token and other lock info. + /// + private readonly string lockInfoFilePath; + + /// + /// Logger. + /// + private readonly ILogger logger; + + private const string lockModeExt = ".lockmode"; + + private const string lockInfoExt = ".lockinfo"; + + /// + /// Creates instance of this class. + /// + public LockManager(string userFileSystemPath, string serverDataFolderPath, string userFileSystemRootPath, ILogger logger) + { + this.userFileSystemPath = userFileSystemPath ?? throw new NullReferenceException(nameof(userFileSystemPath)); + this.serverDataFolderPath = serverDataFolderPath ?? throw new NullReferenceException(nameof(serverDataFolderPath)); + this.userFileSystemRootPath = userFileSystemRootPath ?? throw new NullReferenceException(nameof(userFileSystemRootPath)); + this.logger = logger ?? throw new NullReferenceException(nameof(logger)); + + // Get path relative to the virtual root. + string dataFile = GetLockFilePath(userFileSystemPath); + lockModeFilePath = $"{dataFile}{lockModeExt}"; + lockInfoFilePath = $"{dataFile}{lockInfoExt}"; + } + + /// + /// Sets lock mode associated with the file or folder. + /// + /// Lock mode. + public async Task SetLockModeAsync(LockMode lockMode) + { + if (lockMode == LockMode.None) + { + File.Delete(lockModeFilePath); + return; + } + + Directory.CreateDirectory(Path.GetDirectoryName(lockModeFilePath)); + await using (FileStream fileStream = File.Open(lockModeFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Delete)) + { + fileStream.WriteByte((byte)lockMode); + } + } + + /// + /// Gets lock mode associated with a file or folder. + /// + /// Lock mode or if the file is not locked. + public async Task GetLockModeAsync() + { + if(!File.Exists(lockModeFilePath)) + { + 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. + /// + public async Task IsLockedAsync() + { + return File.Exists(lockInfoFilePath); + } + + /// + /// Gets lock info or null if the item is not locked. + /// + public async Task GetLockInfoAsync() + { + if(!File.Exists(lockInfoFilePath)) + { + return null; + } + await using (FileStream stream = File.Open(lockInfoFilePath, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.Read)) + { + stream.Seek(0, SeekOrigin.Begin); + return await JsonSerializer.DeserializeAsync(stream); + } + } + + /// + /// Sets lock info. + /// + /// Lock info. + public async Task SetLockInfoAsync(ServerLockInfo lockInfo) + { + await using (FileStream stream = File.Open(lockInfoFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Delete)) + { + stream.Seek(0, SeekOrigin.Begin); + await JsonSerializer.SerializeAsync(stream, lockInfo); + stream.SetLength(stream.Position); + } + } + + /// + /// Deletes lock-token and lock-mode files. + /// + public void DeleteLock() + { + 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); + } + } + + /// + /// Gets lock file and lock mode files path (without extension). + /// + /// Path of the file in user file system to get the path for. + private string GetLockFilePath(string userFileSystemPath) + { + // Get path relative to the virtual root. + string relativePath = userFileSystemPath.TrimEnd(Path.DirectorySeparatorChar).Substring( + userFileSystemRootPath.TrimEnd(Path.DirectorySeparatorChar).Length); + + return $"{serverDataFolderPath.TrimEnd(Path.DirectorySeparatorChar)}{relativePath}"; + } + + + /// + /// Moves custom columns to a new location. + /// + /// Path of the file in the user file system to move custom columns to. + internal async Task MoveToAsync(string userFileSystemNewPath) + { + // Move custom columns file. + string lockTargetPath = GetLockFilePath(userFileSystemNewPath); + string lockInfoFileTargetPath = $"{lockTargetPath}{lockModeExt}"; + string lockModeFileTargetPath = $"{lockTargetPath}{lockInfoExt}"; + + // Ensure the target directory exisit, in case we are moving into empty folder or which is offline. + new FileInfo(lockInfoFileTargetPath).Directory.Create(); + if (File.Exists(lockInfoFilePath)) + { + File.Move(lockInfoFilePath, lockInfoFileTargetPath); + } + if (File.Exists(lockModeFilePath)) + { + File.Move(lockModeFilePath, lockModeFileTargetPath); + } + + // If this is a folder, move all data in this folder. + string lockSourceFolderPath = GetLockFilePath(userFileSystemPath); + if (Directory.Exists(lockSourceFolderPath)) + { + Directory.Move(lockSourceFolderPath, lockTargetPath); + } + } + } +} diff --git a/ITHit.FileSystem.Samples.Common.Windows/Logger.cs b/Windows/Common/Logger.cs similarity index 100% rename from ITHit.FileSystem.Samples.Common.Windows/Logger.cs rename to Windows/Common/Logger.cs diff --git a/ITHit.FileSystem.Samples.Common.Windows/Registrar.cs b/Windows/Common/Registrar.cs similarity index 90% rename from ITHit.FileSystem.Samples.Common.Windows/Registrar.cs rename to Windows/Common/Registrar.cs index f21cffe..f8ddc4b 100644 --- a/ITHit.FileSystem.Samples.Common.Windows/Registrar.cs +++ b/Windows/Common/Registrar.cs @@ -42,17 +42,18 @@ public static async Task RegisterAsync(string syncRootId, string path, string di 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. - storageInfo.InSyncPolicy = - StorageProviderInSyncPolicy.FileCreationTime | StorageProviderInSyncPolicy.DirectoryCreationTime | - StorageProviderInSyncPolicy.FileLastWriteTime | StorageProviderInSyncPolicy.DirectoryLastWriteTime | - StorageProviderInSyncPolicy.FileHiddenAttribute | StorageProviderInSyncPolicy.DirectoryHiddenAttribute | - StorageProviderInSyncPolicy.FileSystemAttribute | StorageProviderInSyncPolicy.DirectorySystemAttribute; - //StorageProviderInSyncPolicy.FileReadOnlyAttribute | StorageProviderInSyncPolicy.DirectoryReadOnlyAttribute; + storageInfo.InSyncPolicy = + //StorageProviderInSyncPolicy.FileCreationTime | StorageProviderInSyncPolicy.DirectoryCreationTime | + //StorageProviderInSyncPolicy.FileLastWriteTime | StorageProviderInSyncPolicy.DirectoryLastWriteTime | + //StorageProviderInSyncPolicy.FileHiddenAttribute | StorageProviderInSyncPolicy.DirectoryHiddenAttribute | + //StorageProviderInSyncPolicy.FileSystemAttribute | StorageProviderInSyncPolicy.DirectorySystemAttribute | + //StorageProviderInSyncPolicy.FileReadOnlyAttribute | StorageProviderInSyncPolicy.DirectoryReadOnlyAttribute | + StorageProviderInSyncPolicy.Default; //storageInfo.ShowSiblingsAsGroup = false; //storageInfo.HardlinkPolicy = StorageProviderHardlinkPolicy.None; - + // Adds columns to Windows File Manager. // Show/hide columns in the "More..." context menu on the columns header. var proDefinitions = storageInfo.StorageProviderItemPropertyDefinitions; diff --git a/ITHit.FileSystem.Samples.Common/ServerLockInfo.cs b/Windows/Common/ServerLockInfo.cs similarity index 83% rename from ITHit.FileSystem.Samples.Common/ServerLockInfo.cs rename to Windows/Common/ServerLockInfo.cs index 9bcd80f..01576bd 100644 --- a/ITHit.FileSystem.Samples.Common/ServerLockInfo.cs +++ b/Windows/Common/ServerLockInfo.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace ITHit.FileSystem.Samples.Common +namespace ITHit.FileSystem.Samples.Common.Windows { /// /// Information about the lock returned from the remote storage as a result of the lock operation. @@ -27,19 +27,19 @@ public class ServerLockInfo /// /// True if the item is locked exclusively. False in case the item has a shared lock. /// - public bool Exclusive { get; set; } + public bool Exclusive { get; set; } = true; /// - /// Gets this lock info as a set of properties that can be visually displayed in file manager. + /// Gets this lock info as a set of properties that can be visually displayed in the file manager. /// /// Lock icon path that will be displayed in file manager. - /// List of properties. + /// List of properties that represent this lock info. public IEnumerable GetLockProperties(string lockIconPath) { List lockProps = new List(); lockProps.Add(new FileSystemItemPropertyData((int)CustomColumnIds.LockOwnerIcon, Owner, lockIconPath)); lockProps.Add(new FileSystemItemPropertyData((int)CustomColumnIds.LockScope, Exclusive ? "Exclusive" : "Shared")); - lockProps.Add(new FileSystemItemPropertyData((int)CustomColumnIds.LockExpirationDate, LockExpirationDateUtc.ToString())); + lockProps.Add(new FileSystemItemPropertyData((int)CustomColumnIds.LockExpirationDate, LockExpirationDateUtc != null ? LockExpirationDateUtc.ToString() : "")); return lockProps; } } diff --git a/Windows/UserFileSystemSamples.sln b/Windows/UserFileSystemSamples.sln new file mode 100644 index 0000000..cab5470 --- /dev/null +++ b/Windows/UserFileSystemSamples.sln @@ -0,0 +1,61 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31019.35 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common", "..\Common\Common.csproj", "{BDBB70BB-6343-4703-872B-C2C619C39EE4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common.Windows", "Common\Common.Windows.csproj", "{F7EF0045-9B22-444A-991B-AC08CF77D3F6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VirtualFileSystem", "VirtualFileSystem\VirtualFileSystem.csproj", "{3A7779D2-FE00-418F-BF31-BB2EF4D6637D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VirtualDrive", "VirtualDrive\VirtualDrive.csproj", "{8391F42F-245B-4DD9-A171-1F6A9F3FD108}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebDAVDrive", "WebDAVDrive", "{264745B0-DF86-41E1-B400-3CAA1B403830}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebDAVDrive", "WebDAVDrive\WebDAVDrive\WebDAVDrive.csproj", "{C624F9B5-3EA1-416C-8592-37E6064C8247}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebDAVDrive.UI", "WebDAVDrive\WebDAVDrive.UI\WebDAVDrive.UI.csproj", "{51F6CFCC-AB57-40DD-AADA-6299A2C6B941}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BDBB70BB-6343-4703-872B-C2C619C39EE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BDBB70BB-6343-4703-872B-C2C619C39EE4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BDBB70BB-6343-4703-872B-C2C619C39EE4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BDBB70BB-6343-4703-872B-C2C619C39EE4}.Release|Any CPU.Build.0 = Release|Any CPU + {F7EF0045-9B22-444A-991B-AC08CF77D3F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F7EF0045-9B22-444A-991B-AC08CF77D3F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7EF0045-9B22-444A-991B-AC08CF77D3F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7EF0045-9B22-444A-991B-AC08CF77D3F6}.Release|Any CPU.Build.0 = Release|Any CPU + {3A7779D2-FE00-418F-BF31-BB2EF4D6637D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3A7779D2-FE00-418F-BF31-BB2EF4D6637D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3A7779D2-FE00-418F-BF31-BB2EF4D6637D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3A7779D2-FE00-418F-BF31-BB2EF4D6637D}.Release|Any CPU.Build.0 = Release|Any CPU + {8391F42F-245B-4DD9-A171-1F6A9F3FD108}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8391F42F-245B-4DD9-A171-1F6A9F3FD108}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8391F42F-245B-4DD9-A171-1F6A9F3FD108}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8391F42F-245B-4DD9-A171-1F6A9F3FD108}.Release|Any CPU.Build.0 = Release|Any CPU + {C624F9B5-3EA1-416C-8592-37E6064C8247}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C624F9B5-3EA1-416C-8592-37E6064C8247}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C624F9B5-3EA1-416C-8592-37E6064C8247}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C624F9B5-3EA1-416C-8592-37E6064C8247}.Release|Any CPU.Build.0 = Release|Any CPU + {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51F6CFCC-AB57-40DD-AADA-6299A2C6B941}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {C624F9B5-3EA1-416C-8592-37E6064C8247} = {264745B0-DF86-41E1-B400-3CAA1B403830} + {51F6CFCC-AB57-40DD-AADA-6299A2C6B941} = {264745B0-DF86-41E1-B400-3CAA1B403830} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {740A716A-38A7-46BC-A21F-18336D0023B7} + EndGlobalSection +EndGlobal diff --git a/VirtualFileSystem/AppSettings.cs b/Windows/VirtualDrive/AppSettings.cs similarity index 93% rename from VirtualFileSystem/AppSettings.cs rename to Windows/VirtualDrive/AppSettings.cs index 6bb1108..06e4937 100644 --- a/VirtualFileSystem/AppSettings.cs +++ b/Windows/VirtualDrive/AppSettings.cs @@ -9,7 +9,7 @@ using ITHit.FileSystem.Samples.Common; -namespace VirtualFileSystem +namespace VirtualDrive { /// /// Strongly binded project settings. @@ -23,6 +23,11 @@ public class AppSettings : Settings /// In your real-life application you will read data from your cloud storage, database or any other location, instead of this folder. /// public string RemoteStorageRootPath { get; set; } + + /// + /// Path to the folder that stores custom data associated with files and folders. + /// + public string ServerDataFolderPath { get; set; } } /// @@ -85,7 +90,7 @@ public static AppSettings ReadSettings(this IConfiguration configuration) // Load product name from entry exe file. settings.ProductName = FileVersionInfo.GetVersionInfo(assemblyLocation).ProductName; - // Folder where eTags and file locks are stored. + // Folder where custom data is stored. string localApplicationDataFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); settings.ServerDataFolderPath = Path.Combine(localApplicationDataFolderPath, settings.AppID, settings.UserFileSystemRootPath.Replace(":", ""), "ServerData"); diff --git a/VirtualFileSystem/Images/Blank.ico b/Windows/VirtualDrive/Images/Blank.ico similarity index 100% rename from VirtualFileSystem/Images/Blank.ico rename to Windows/VirtualDrive/Images/Blank.ico diff --git a/VirtualFileSystem/Images/Drive.ico b/Windows/VirtualDrive/Images/Drive.ico similarity index 100% rename from VirtualFileSystem/Images/Drive.ico rename to Windows/VirtualDrive/Images/Drive.ico diff --git a/VirtualFileSystem/Images/Error.ico b/Windows/VirtualDrive/Images/Error.ico similarity index 100% rename from VirtualFileSystem/Images/Error.ico rename to Windows/VirtualDrive/Images/Error.ico diff --git a/VirtualFileSystem/Images/Locked.ico b/Windows/VirtualDrive/Images/Locked.ico similarity index 100% rename from VirtualFileSystem/Images/Locked.ico rename to Windows/VirtualDrive/Images/Locked.ico diff --git a/VirtualFileSystem/Images/LockedByAnotherUser.ico b/Windows/VirtualDrive/Images/LockedByAnotherUser.ico similarity index 100% rename from VirtualFileSystem/Images/LockedByAnotherUser.ico rename to Windows/VirtualDrive/Images/LockedByAnotherUser.ico diff --git a/VirtualFileSystem/Images/LockedPending.ico b/Windows/VirtualDrive/Images/LockedPending.ico similarity index 100% rename from VirtualFileSystem/Images/LockedPending.ico rename to Windows/VirtualDrive/Images/LockedPending.ico diff --git a/VirtualFileSystem/Images/Unlocked.ico b/Windows/VirtualDrive/Images/Unlocked.ico similarity index 100% rename from VirtualFileSystem/Images/Unlocked.ico rename to Windows/VirtualDrive/Images/Unlocked.ico diff --git a/Windows/VirtualDrive/Mapping.cs b/Windows/VirtualDrive/Mapping.cs new file mode 100644 index 0000000..d86f8c3 --- /dev/null +++ b/Windows/VirtualDrive/Mapping.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; + +using ITHit.FileSystem; + + +namespace VirtualDrive +{ + /// + /// Maps a user file system path to the remote storage path and back. + /// + /// + /// You will change methods of this class to map the user file system path to your remote storage path. + /// + public static class Mapping + { + /// + /// Returns a remote storage URI that corresponds to the user file system path. + /// + /// Full path in the user file system. + /// Remote storage URI that corresponds to the . + public static string MapPath(string userFileSystemPath) + { + // Get path relative to the virtual root. + string relativePath = Path.TrimEndingDirectorySeparator(userFileSystemPath).Substring( + Path.TrimEndingDirectorySeparator(Program.Settings.UserFileSystemRootPath).Length); + + string path = $"{Path.TrimEndingDirectorySeparator(Program.Settings.RemoteStorageRootPath)}{relativePath}"; + return path; + } + + /// + /// Returns a user file system path that corresponds to the remote storage URI. + /// + /// Remote storage URI. + /// Path in the user file system that corresponds to the . + public static string ReverseMapPath(string remoteStorageUri) + { + // Get path relative to the virtual root. + string relativePath = Path.TrimEndingDirectorySeparator(remoteStorageUri).Substring( + Path.TrimEndingDirectorySeparator(Program.Settings.RemoteStorageRootPath).Length); + + string path = $"{Path.TrimEndingDirectorySeparator(Program.Settings.UserFileSystemRootPath)}{relativePath}"; + return path; + } + + /// + /// Gets a user file system item info from the remote storage data. + /// + /// Remote storage item info. + /// User file system item info. + public static IFileSystemItemMetadata GetUserFileSysteItemMetadata(FileSystemInfo remoteStorageItem) + { + IFileSystemItemMetadata userFileSystemItem; + + if (remoteStorageItem is FileInfo) + { + userFileSystemItem = new FileMetadata(); + ((FileMetadata)userFileSystemItem).Length = ((FileInfo)remoteStorageItem).Length; + } + else + { + userFileSystemItem = new FolderMetadata(); + } + + userFileSystemItem.Name = remoteStorageItem.Name; + userFileSystemItem.Attributes = remoteStorageItem.Attributes; + userFileSystemItem.CreationTime = remoteStorageItem.CreationTime; + userFileSystemItem.LastWriteTime = remoteStorageItem.LastWriteTime; + userFileSystemItem.LastAccessTime = remoteStorageItem.LastAccessTime; + userFileSystemItem.ChangeTime = remoteStorageItem.LastWriteTime; + + return userFileSystemItem; + } + } +} diff --git a/Windows/VirtualDrive/MsOfficeDocsMonitor.cs b/Windows/VirtualDrive/MsOfficeDocsMonitor.cs new file mode 100644 index 0000000..7ad1053 --- /dev/null +++ b/Windows/VirtualDrive/MsOfficeDocsMonitor.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using log4net; + +using ITHit.FileSystem; +using ITHit.FileSystem.Windows; +using ITHit.FileSystem.Samples.Common.Windows; + +namespace VirtualDrive +{ + /// + /// Monitors MS Office files renames in the user file system and sends changes to the remote storage. + /// + internal class MsOfficeDocsMonitor : Logger, IDisposable + { + /// + /// User file system watcher. + /// + private readonly FileSystemWatcherQueued watcher = new FileSystemWatcherQueued(); + + /// + /// Engine. + /// + private readonly VirtualEngine engine; + + + /// + /// Creates instance of this class. + /// + /// User file system root path. + /// Engine. + /// Logger. + internal MsOfficeDocsMonitor(string userFileSystemRootPath, VirtualEngine engine, ILog log) + : base("MS Office docs Monitor", log) + { + if(string.IsNullOrEmpty(userFileSystemRootPath)) + { + throw new ArgumentNullException(nameof(userFileSystemRootPath)); + } + this.engine = engine ?? throw new ArgumentNullException(nameof(engine)); + + watcher.IncludeSubdirectories = true; + watcher.Path = userFileSystemRootPath; + //watcher.Filter = "*.*"; + watcher.NotifyFilter = NotifyFilters.FileName; + watcher.Error += Error; + watcher.Created += CreatedAsync; + watcher.Changed += ChangedAsync; + watcher.Deleted += DeletedAsync; + watcher.Renamed += RenamedAsync; + } + + /// + /// Starts monitoring the user file system. + /// + public void Start() + { + watcher.EnableRaisingEvents = true; + LogMessage("Started"); + } + + /// + /// Stops monitoring the user file system. + /// + public void Stop() + { + watcher.EnableRaisingEvents = false; + LogMessage("Stopped"); + } + + + /// + /// Called when a file or folder is created in the user file system. + /// + private async void CreatedAsync(object sender, FileSystemEventArgs e) + { + LogMessage("Creating", e.FullPath); + } + + /// + /// Called when an item is updated in the user file system. + /// + private async void ChangedAsync(object sender, FileSystemEventArgs e) + { + LogMessage($"{e.ChangeType}", e.FullPath); + } + + /// + /// Called when a file or folder is deleted in the user file system. + /// + private async void DeletedAsync(object sender, FileSystemEventArgs e) + { + LogMessage(e.ChangeType.ToString(), e.FullPath); + } + + /// + /// Called when a file or folder is renamed in the user file system. + /// + private async void RenamedAsync(object sender, RenamedEventArgs e) + { + // If the item and was previusly filtered by EngineWindows.FilterAsync(), + // for example temp MS Office file was renamed SGE4274H->file.xlsx, + // we need to convert the file to a pleaceholder and upload it to the remote storage. + // We must also + + LogMessage("Renamed", e.OldFullPath, e.FullPath); + + string userFileSystemOldPath = e.OldFullPath; + string userFileSystemNewPath = e.FullPath; + try + { + if (System.IO.File.Exists(userFileSystemNewPath) + && !MsOfficeHelper.AvoidMsOfficeSync(userFileSystemNewPath)) + { + if (!PlaceholderItem.IsPlaceholder(userFileSystemNewPath)) + { + if (engine.CustomDataManager(userFileSystemNewPath).IsNew) + { + await engine.ClientNotifications(userFileSystemNewPath, this).CreateAsync(); + } + else + { + LogMessage("Converting to placeholder", userFileSystemNewPath); + PlaceholderItem.ConvertToPlaceholder(userFileSystemNewPath, null, false); + await engine.ClientNotifications(userFileSystemNewPath, this).UpdateAsync(); + await engine.CustomDataManager(userFileSystemNewPath).RefreshCustomColumnsAsync(); + } + } + } + } + catch (Exception ex) + { + LogError($"{e.ChangeType} failed", userFileSystemOldPath, userFileSystemNewPath, ex); + } + + } + + private void Error(object sender, ErrorEventArgs e) + { + LogError(null, null, null, e.GetException()); + } + + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + watcher.Dispose(); + LogMessage($"Disposed"); + } + + // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. + // TODO: set large fields to null. + + disposedValue = true; + } + } + + // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. + // ~ServerChangesMonitor() + // { + // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + // Dispose(false); + // } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + // TODO: uncomment the following line if the finalizer is overridden above. + // GC.SuppressFinalize(this); + } + } +} diff --git a/Windows/VirtualDrive/MsOfficeHelper.cs b/Windows/VirtualDrive/MsOfficeHelper.cs new file mode 100644 index 0000000..bad785c --- /dev/null +++ b/Windows/VirtualDrive/MsOfficeHelper.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; + + +namespace VirtualDrive +{ + /// + /// Microsoft Office helper methods. + /// + internal class MsOfficeHelper + { + /// + /// Returns true if the path points to a recycle bin folder. + /// + /// Path to the file or folder. + public static bool IsRecycleBin(string path) + { + return path.IndexOf("\\$Recycle.Bin", StringComparison.InvariantCultureIgnoreCase) != -1; + } + + /// + /// Returns true if the file is in ~XXXXX.tmp, pptXXXX.tmp format, false - otherwise. + /// + /// Path to a file. + 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) && System.IO.File.Exists(path)) // Excel temp files + || (((Path.GetFileNameWithoutExtension(path).Length == 8) || (Path.GetFileNameWithoutExtension(path).Length == 7)) && Path.GetExtension(path).Equals(".tmp", StringComparison.InvariantCultureIgnoreCase)); // Excel temp files + } + + /// + /// Returns true if file system contains MS Office lock file (~$file.ext) in file + /// system that corresponds to the provided path to MS Office file. + /// + /// Path to the MS Office file. + public static bool IsMsOfficeLocked(string path) + { + string lockPath = GetLockPathFromMsOfficePath(path); + return lockPath != null; + } + + /// + /// Returns true if the provided path points to MS Office lock file (~$file.ext). + /// + /// Path to the MS Office lock file. + internal static bool IsMsOfficeLockFile(string path) + { + return Path.GetFileName(path).StartsWith("~$"); + } + + /// + /// Returns MS Office lock file path if such file exists. + /// + /// MS Office file path. + /// Lock file path. + /// + /// mydoc.docx -> ~$mydoc.docx + /// mydocfi.docx -> ~$ydocfi.docx + /// mydocfile.docx -> ~$docfile.docx + /// mydocfile.pptx -> ~$mydocfile.pptx + /// mydocfile.ppt -> ~$mydocfile.ppt + /// mydocfile.xlsx -> ~$mydocfile.xlsx + /// mydocfile.xls -> null + /// + private static string GetLockPathFromMsOfficePath(string msOfficeFilePath) + { + string msOfficeLockFilePath = null; + int separatorIndex = msOfficeFilePath.LastIndexOf(Path.DirectorySeparatorChar); + if ((separatorIndex != -1) && !string.IsNullOrEmpty(Path.GetExtension(msOfficeFilePath))) + { + msOfficeLockFilePath = msOfficeFilePath.Insert(separatorIndex + 1, "~$"); + if (System.IO.File.Exists(msOfficeLockFilePath)) + { + return msOfficeLockFilePath; + } + int fileNameLength = Path.GetFileNameWithoutExtension(msOfficeFilePath).Length; + if (fileNameLength > 6) + { + int removeChars = fileNameLength == 7 ? 1 : 2; + msOfficeLockFilePath = msOfficeLockFilePath.Remove(separatorIndex + 1 + "~$".Length, removeChars); + if (System.IO.File.Exists(msOfficeLockFilePath)) + { + return msOfficeLockFilePath; + } + } + } + return null; + } + + /// + /// Returns true if the file or folder is marked with Hidden or Temporaty attributes. + /// + /// Path to a file or folder. + /// + /// True if the file or folder is marked with Hidden or Temporaty attributes. + /// Returns false if no Hidden or Temporaty attributes found or file/folder does not exists. + /// + private static bool IsHiddenOrTemp(string path) + { + if (!System.IO.File.Exists(path) && !System.IO.Directory.Exists(path)) + { + return false; + } + + FileAttributes att = System.IO.File.GetAttributes(path); + return ((att & System.IO.FileAttributes.Hidden) != 0) + || ((att & System.IO.FileAttributes.Temporary) != 0); + } + + /// + /// Returns true if the file or folder should not be automatically locked. False - otherwise. + /// + /// Path to a file or folder. + public static bool AvoidMsOfficeSync(string path) + { + return IsMsOfficeLockFile(path) || IsMsOfficeTemp(path) || IsHiddenOrTemp(path); + } + } +} diff --git a/VirtualFileSystem/Program.cs b/Windows/VirtualDrive/Program.cs similarity index 85% rename from VirtualFileSystem/Program.cs rename to Windows/VirtualDrive/Program.cs index d67ae51..b4e7b17 100644 --- a/VirtualFileSystem/Program.cs +++ b/Windows/VirtualDrive/Program.cs @@ -1,6 +1,3 @@ -using log4net; -using log4net.Config; -using Microsoft.Extensions.Configuration; using System; using System.Diagnostics; using System.IO; @@ -10,11 +7,14 @@ using System.Threading.Tasks; using Windows.Storage; using Windows.Storage.Provider; +using Microsoft.Extensions.Configuration; +using log4net; +using log4net.Config; -using ITHit.FileSystem.Samples.Common; using ITHit.FileSystem.Samples.Common.Windows; -namespace VirtualFileSystem + +namespace VirtualDrive { class Program { @@ -30,10 +30,9 @@ class Program /// /// Processes OS file system calls, - /// synchronizes user file system to remote storage and back, - /// monitors files pinning and unpinning. + /// synchronizes user file system to remote storage. /// - internal static VirtualDrive VirtualDrive; + public static VirtualEngine Engine; static async Task Main(string[] args) { @@ -78,12 +77,16 @@ await Registrar.RegisterAsync(SyncRootId, Settings.UserFileSystemRootPath, Setti try { - VirtualDrive = new VirtualDrive(Settings.UserFileSystemLicense, Settings.UserFileSystemRootPath, Settings, log); + Engine = new VirtualEngine( + Settings.UserFileSystemLicense, + Settings.UserFileSystemRootPath, + Settings.ServerDataFolderPath, + Settings.IconsFolderPath, + log); + Engine.AutoLock = Settings.AutoLock; - // Start processing OS file system calls, monitoring changes in user file system and remote storge. - //engine.ChangesProcessingEnabled = false; - await VirtualDrive.StartAsync(); - await VirtualDrive.SyncService.StopAsync(); + // Start processing OS file system calls. + await Engine.StartAsync(); #if DEBUG // Opens Windows File Manager with user file system folder and remote storage folder. @@ -94,7 +97,7 @@ await Registrar.RegisterAsync(SyncRootId, Settings.UserFileSystemRootPath, Setti } finally { - VirtualDrive.Dispose(); + Engine.Dispose(); } if (exitKey.KeyChar == 'q') @@ -111,6 +114,7 @@ await Registrar.RegisterAsync(SyncRootId, Settings.UserFileSystemRootPath, Setti // Unregister during programm uninstall and delete all files/folder. await Registrar.UnregisterAsync(SyncRootId); + try { Directory.Delete(Settings.UserFileSystemRootPath, true); @@ -160,13 +164,13 @@ 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. - //using (Process serverDataWinFileManager = Process.Start(serverDataInfo)) - //{ + // Open Windows File Manager with custom data storage. Uncomment this to debug custom data storage management. + ProcessStartInfo serverDataInfo = new ProcessStartInfo(Program.Settings.ServerDataFolderPath); + serverDataInfo.UseShellExecute = true; // Open window only if not opened already. + using (Process serverDataWinFileManager = Process.Start(serverDataInfo)) + { - //} + } } #endif diff --git a/Windows/VirtualDrive/RemoteStorage/General.docx b/Windows/VirtualDrive/RemoteStorage/General.docx new file mode 100644 index 0000000..0502395 Binary files /dev/null and b/Windows/VirtualDrive/RemoteStorage/General.docx differ diff --git a/Windows/VirtualDrive/RemoteStorage/Introduction.pptx b/Windows/VirtualDrive/RemoteStorage/Introduction.pptx new file mode 100644 index 0000000..4212a12 Binary files /dev/null and b/Windows/VirtualDrive/RemoteStorage/Introduction.pptx differ diff --git a/VirtualFileSystem/RemoteStorage/Library/Content.txt b/Windows/VirtualDrive/RemoteStorage/Library/Content.txt similarity index 100% rename from VirtualFileSystem/RemoteStorage/Library/Content.txt rename to Windows/VirtualDrive/RemoteStorage/Library/Content.txt diff --git a/VirtualFileSystem/RemoteStorage/Library/Overview.doc b/Windows/VirtualDrive/RemoteStorage/Library/Overview.doc similarity index 100% rename from VirtualFileSystem/RemoteStorage/Library/Overview.doc rename to Windows/VirtualDrive/RemoteStorage/Library/Overview.doc diff --git a/VirtualFileSystem/RemoteStorage/Library/Product.vsd b/Windows/VirtualDrive/RemoteStorage/Library/Product.vsd similarity index 100% rename from VirtualFileSystem/RemoteStorage/Library/Product.vsd rename to Windows/VirtualDrive/RemoteStorage/Library/Product.vsd diff --git a/VirtualFileSystem/RemoteStorage/Library/Project.doc b/Windows/VirtualDrive/RemoteStorage/Library/Project.doc similarity index 100% rename from VirtualFileSystem/RemoteStorage/Library/Project.doc rename to Windows/VirtualDrive/RemoteStorage/Library/Project.doc diff --git a/VirtualFileSystem/RemoteStorage/Library/Vision.doc b/Windows/VirtualDrive/RemoteStorage/Library/Vision.doc similarity index 100% rename from VirtualFileSystem/RemoteStorage/Library/Vision.doc rename to Windows/VirtualDrive/RemoteStorage/Library/Vision.doc diff --git a/VirtualFileSystem/RemoteStorage/Library/Vision.mpp b/Windows/VirtualDrive/RemoteStorage/Library/Vision.mpp similarity index 100% rename from VirtualFileSystem/RemoteStorage/Library/Vision.mpp rename to Windows/VirtualDrive/RemoteStorage/Library/Vision.mpp diff --git a/VirtualFileSystem/RemoteStorage/My Directory/Notes.txt b/Windows/VirtualDrive/RemoteStorage/My Directory/Notes.txt similarity index 100% rename from VirtualFileSystem/RemoteStorage/My Directory/Notes.txt rename to Windows/VirtualDrive/RemoteStorage/My Directory/Notes.txt diff --git a/Windows/VirtualDrive/RemoteStorage/Notes.txt b/Windows/VirtualDrive/RemoteStorage/Notes.txt new file mode 100644 index 0000000..dc73c34 --- /dev/null +++ b/Windows/VirtualDrive/RemoteStorage/Notes.txt @@ -0,0 +1 @@ +My notes file texthhgasdasdasd \ No newline at end of file diff --git a/VirtualFileSystem/RemoteStorage/Pictures/Arctic Ice.jpeg b/Windows/VirtualDrive/RemoteStorage/Pictures/Arctic Ice.jpeg similarity index 100% rename from VirtualFileSystem/RemoteStorage/Pictures/Arctic Ice.jpeg rename to Windows/VirtualDrive/RemoteStorage/Pictures/Arctic Ice.jpeg diff --git a/VirtualFileSystem/RemoteStorage/Pictures/Arctic Sun.jpeg b/Windows/VirtualDrive/RemoteStorage/Pictures/Arctic Sun.jpeg similarity index 100% rename from VirtualFileSystem/RemoteStorage/Pictures/Arctic Sun.jpeg rename to Windows/VirtualDrive/RemoteStorage/Pictures/Arctic Sun.jpeg diff --git a/VirtualFileSystem/RemoteStorage/Pictures/Autumn.jpeg b/Windows/VirtualDrive/RemoteStorage/Pictures/Autumn.jpeg similarity index 100% rename from VirtualFileSystem/RemoteStorage/Pictures/Autumn.jpeg rename to Windows/VirtualDrive/RemoteStorage/Pictures/Autumn.jpeg diff --git a/VirtualFileSystem/RemoteStorage/Pictures/Beach.jpeg b/Windows/VirtualDrive/RemoteStorage/Pictures/Beach.jpeg similarity index 100% rename from VirtualFileSystem/RemoteStorage/Pictures/Beach.jpeg rename to Windows/VirtualDrive/RemoteStorage/Pictures/Beach.jpeg diff --git a/VirtualFileSystem/RemoteStorage/Pictures/Boats.jpeg b/Windows/VirtualDrive/RemoteStorage/Pictures/Boats.jpeg similarity index 100% rename from VirtualFileSystem/RemoteStorage/Pictures/Boats.jpeg rename to Windows/VirtualDrive/RemoteStorage/Pictures/Boats.jpeg diff --git a/VirtualFileSystem/RemoteStorage/Pictures/Cruise Ship.jpeg b/Windows/VirtualDrive/RemoteStorage/Pictures/Cruise Ship.jpeg similarity index 100% rename from VirtualFileSystem/RemoteStorage/Pictures/Cruise Ship.jpeg rename to Windows/VirtualDrive/RemoteStorage/Pictures/Cruise Ship.jpeg diff --git a/VirtualFileSystem/RemoteStorage/Pictures/Glacier.jpeg b/Windows/VirtualDrive/RemoteStorage/Pictures/Glacier.jpeg similarity index 100% rename from VirtualFileSystem/RemoteStorage/Pictures/Glacier.jpeg rename to Windows/VirtualDrive/RemoteStorage/Pictures/Glacier.jpeg diff --git a/VirtualFileSystem/RemoteStorage/Pictures/Hotel.jpeg b/Windows/VirtualDrive/RemoteStorage/Pictures/Hotel.jpeg similarity index 100% rename from VirtualFileSystem/RemoteStorage/Pictures/Hotel.jpeg rename to Windows/VirtualDrive/RemoteStorage/Pictures/Hotel.jpeg diff --git a/VirtualFileSystem/RemoteStorage/Pictures/Landing Pier.jpeg b/Windows/VirtualDrive/RemoteStorage/Pictures/Landing Pier.jpeg similarity index 100% rename from VirtualFileSystem/RemoteStorage/Pictures/Landing Pier.jpeg rename to Windows/VirtualDrive/RemoteStorage/Pictures/Landing Pier.jpeg diff --git a/VirtualFileSystem/RemoteStorage/Pictures/Orange in the Mountains.jpeg b/Windows/VirtualDrive/RemoteStorage/Pictures/Orange in the Mountains.jpeg similarity index 100% rename from VirtualFileSystem/RemoteStorage/Pictures/Orange in the Mountains.jpeg rename to Windows/VirtualDrive/RemoteStorage/Pictures/Orange in the Mountains.jpeg diff --git a/VirtualFileSystem/RemoteStorage/Pictures/River.jpeg b/Windows/VirtualDrive/RemoteStorage/Pictures/River.jpeg similarity index 100% rename from VirtualFileSystem/RemoteStorage/Pictures/River.jpeg rename to Windows/VirtualDrive/RemoteStorage/Pictures/River.jpeg diff --git a/VirtualFileSystem/RemoteStorage/Pictures/Sunset.jpeg b/Windows/VirtualDrive/RemoteStorage/Pictures/Sunset.jpeg similarity index 100% rename from VirtualFileSystem/RemoteStorage/Pictures/Sunset.jpeg rename to Windows/VirtualDrive/RemoteStorage/Pictures/Sunset.jpeg diff --git a/VirtualFileSystem/RemoteStorage/Pictures/Windsurfing.jpeg b/Windows/VirtualDrive/RemoteStorage/Pictures/Windsurfing.jpeg similarity index 100% rename from VirtualFileSystem/RemoteStorage/Pictures/Windsurfing.jpeg rename to Windows/VirtualDrive/RemoteStorage/Pictures/Windsurfing.jpeg diff --git a/VirtualFileSystem/RemoteStorage/Pictures/Yachts.jpeg b/Windows/VirtualDrive/RemoteStorage/Pictures/Yachts.jpeg similarity index 100% rename from VirtualFileSystem/RemoteStorage/Pictures/Yachts.jpeg rename to Windows/VirtualDrive/RemoteStorage/Pictures/Yachts.jpeg diff --git a/VirtualFileSystem/RemoteStorage/Pictures/Yellow Tree.jpeg b/Windows/VirtualDrive/RemoteStorage/Pictures/Yellow Tree.jpeg similarity index 100% rename from VirtualFileSystem/RemoteStorage/Pictures/Yellow Tree.jpeg rename to Windows/VirtualDrive/RemoteStorage/Pictures/Yellow Tree.jpeg diff --git a/VirtualFileSystem/RemoteStorage/Products/General.doc b/Windows/VirtualDrive/RemoteStorage/Products/General.doc similarity index 100% rename from VirtualFileSystem/RemoteStorage/Products/General.doc rename to Windows/VirtualDrive/RemoteStorage/Products/General.doc diff --git a/VirtualFileSystem/RemoteStorage/Products/General.vsd b/Windows/VirtualDrive/RemoteStorage/Products/General.vsd similarity index 100% rename from VirtualFileSystem/RemoteStorage/Products/General.vsd rename to Windows/VirtualDrive/RemoteStorage/Products/General.vsd diff --git a/VirtualFileSystem/RemoteStorage/Products/Product.mpp b/Windows/VirtualDrive/RemoteStorage/Products/Product.mpp similarity index 100% rename from VirtualFileSystem/RemoteStorage/Products/Product.mpp rename to Windows/VirtualDrive/RemoteStorage/Products/Product.mpp diff --git a/VirtualFileSystem/RemoteStorage/Project.mpp b/Windows/VirtualDrive/RemoteStorage/Project.mpp similarity index 100% rename from VirtualFileSystem/RemoteStorage/Project.mpp rename to Windows/VirtualDrive/RemoteStorage/Project.mpp diff --git a/VirtualFileSystem/RemoteStorage/Project.pdf b/Windows/VirtualDrive/RemoteStorage/Project.pdf similarity index 100% rename from VirtualFileSystem/RemoteStorage/Project.pdf rename to Windows/VirtualDrive/RemoteStorage/Project.pdf diff --git a/VirtualFileSystem/RemoteStorage/Readme.txt b/Windows/VirtualDrive/RemoteStorage/Readme.txt similarity index 100% rename from VirtualFileSystem/RemoteStorage/Readme.txt rename to Windows/VirtualDrive/RemoteStorage/Readme.txt diff --git a/VirtualFileSystem/RemoteStorage/Sales/Australia/Plan.doc b/Windows/VirtualDrive/RemoteStorage/Sales/Australia/Plan.doc similarity index 100% rename from VirtualFileSystem/RemoteStorage/Sales/Australia/Plan.doc rename to Windows/VirtualDrive/RemoteStorage/Sales/Australia/Plan.doc diff --git a/VirtualFileSystem/RemoteStorage/Sales/Australia/Prices.xls b/Windows/VirtualDrive/RemoteStorage/Sales/Australia/Prices.xls similarity index 100% rename from VirtualFileSystem/RemoteStorage/Sales/Australia/Prices.xls rename to Windows/VirtualDrive/RemoteStorage/Sales/Australia/Prices.xls diff --git a/VirtualFileSystem/RemoteStorage/Sales/Canada/Introduction.ppt b/Windows/VirtualDrive/RemoteStorage/Sales/Canada/Introduction.ppt similarity index 100% rename from VirtualFileSystem/RemoteStorage/Sales/Canada/Introduction.ppt rename to Windows/VirtualDrive/RemoteStorage/Sales/Canada/Introduction.ppt diff --git a/VirtualFileSystem/RemoteStorage/Sales/Canada/Prices.xls b/Windows/VirtualDrive/RemoteStorage/Sales/Canada/Prices.xls similarity index 100% rename from VirtualFileSystem/RemoteStorage/Sales/Canada/Prices.xls rename to Windows/VirtualDrive/RemoteStorage/Sales/Canada/Prices.xls diff --git a/VirtualFileSystem/RemoteStorage/Sales/Canada/Product.mpp b/Windows/VirtualDrive/RemoteStorage/Sales/Canada/Product.mpp similarity index 100% rename from VirtualFileSystem/RemoteStorage/Sales/Canada/Product.mpp rename to Windows/VirtualDrive/RemoteStorage/Sales/Canada/Product.mpp diff --git a/VirtualFileSystem/RemoteStorage/Sales/Canada/Stat.xls b/Windows/VirtualDrive/RemoteStorage/Sales/Canada/Stat.xls similarity index 100% rename from VirtualFileSystem/RemoteStorage/Sales/Canada/Stat.xls rename to Windows/VirtualDrive/RemoteStorage/Sales/Canada/Stat.xls diff --git a/VirtualFileSystem/RemoteStorage/Sales/Europe/Stat.xls b/Windows/VirtualDrive/RemoteStorage/Sales/Europe/Stat.xls similarity index 100% rename from VirtualFileSystem/RemoteStorage/Sales/Europe/Stat.xls rename to Windows/VirtualDrive/RemoteStorage/Sales/Europe/Stat.xls diff --git a/VirtualFileSystem/RemoteStorage/Sales/USA/Vision.mpp b/Windows/VirtualDrive/RemoteStorage/Sales/USA/Vision.mpp similarity index 100% rename from VirtualFileSystem/RemoteStorage/Sales/USA/Vision.mpp rename to Windows/VirtualDrive/RemoteStorage/Sales/USA/Vision.mpp diff --git a/VirtualFileSystem/RemoteStorage/Scheme.vsdx b/Windows/VirtualDrive/RemoteStorage/Scheme.vsdx similarity index 100% rename from VirtualFileSystem/RemoteStorage/Scheme.vsdx rename to Windows/VirtualDrive/RemoteStorage/Scheme.vsdx diff --git a/VirtualFileSystem/RemoteStorage/Stat.xlsx b/Windows/VirtualDrive/RemoteStorage/Stat.xlsx similarity index 100% rename from VirtualFileSystem/RemoteStorage/Stat.xlsx rename to Windows/VirtualDrive/RemoteStorage/Stat.xlsx diff --git a/Windows/VirtualDrive/RemoteStorageMonitor.cs b/Windows/VirtualDrive/RemoteStorageMonitor.cs new file mode 100644 index 0000000..18fa6d8 --- /dev/null +++ b/Windows/VirtualDrive/RemoteStorageMonitor.cs @@ -0,0 +1,311 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using log4net; + +using ITHit.FileSystem; +using ITHit.FileSystem.Samples.Common.Windows; +using System.Linq; + +namespace VirtualDrive +{ + /// + /// Monitors changes in the remote storage, notifies the client and updates the user file system. + /// If any file or folder is is modified, created, delated, renamed or attributes changed in the remote storage, + /// triggers an event with information about changes. + /// + /// + /// Here, for demo purposes we simulate server by monitoring source file path using FileWatchWrapper. + /// In your application, instead of using FileWatchWrapper, you will connect to your remote storage using web sockets + /// or use any other technology to get notifications about changes in your remote storage. + /// + internal class RemoteStorageMonitor : Logger, IDisposable + { + /// + /// Remote storage path. Folder to monitor for changes. + /// + private readonly string remoteStorageRootPath; + + /// + /// Virtul drive instance. This class will call methods + /// to update user file system when any data is changed in the remote storage. + /// + private readonly VirtualEngine engine; + + /// + /// Watches for changes in the remote storage file system. + /// + private readonly FileSystemWatcherQueued watcher = new FileSystemWatcherQueued(); + + /// + /// Creates instance of this class. + /// + /// Remote storage path. Folder that contains source files to monitor changes. + /// Virtual drive to send notifications about changes in the remote storage. + /// Logger. + internal RemoteStorageMonitor(string remoteStorageRootPath, VirtualEngine engine, ILog log) : base("Remote Storage Monitor", log) + { + this.remoteStorageRootPath = remoteStorageRootPath; + this.engine = engine; + + watcher.IncludeSubdirectories = true; + watcher.Path = remoteStorageRootPath; + //watcher.Filter = "*.*"; + watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Attributes | NotifyFilters.DirectoryName | NotifyFilters.FileName; + watcher.Error += Error; + watcher.Created += CreatedAsync; + watcher.Changed += ChangedAsync; + watcher.Deleted += DeletedAsync; + watcher.Renamed += RenamedAsync; + watcher.EnableRaisingEvents = false; + } + + /// + /// Starts monitoring changes on the server. + /// + internal void Start() + { + watcher.EnableRaisingEvents = true; + LogMessage($"Started"); + } + + /// + /// Stops monitoring changes in the remote storage. + /// + internal void Stop() + { + watcher.EnableRaisingEvents = false; + LogMessage($"Stopped"); + } + + /// + /// Called when a file or folder is created in the remote storage. + /// + /// In this method we create a new file/folder in the user file system. + private async void CreatedAsync(object sender, FileSystemEventArgs e) + { + LogMessage(e.ChangeType.ToString(), e.FullPath); + string remoteStoragePath = e.FullPath; + try + { + string userFileSystemPath = Mapping.ReverseMapPath(remoteStoragePath); + + // This check is only required because we can not prevent circular calls because of the simplicity of this example. + // In your real-life application you will not sent updates from server back to client that issued the update. + if (!FsPath.Exists(userFileSystemPath)) + { + string userFileSystemParentPath = Path.GetDirectoryName(userFileSystemPath); + + // 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. + FileSystemInfo remoteStorageItem = FsPath.GetFileSystemItem(remoteStoragePath); + if (remoteStorageItem != null) + { + IFileSystemItemMetadata newItemInfo = Mapping.GetUserFileSysteItemMetadata(remoteStorageItem); + if (await engine.ServerNotifications(userFileSystemParentPath).CreateAsync(new[] { newItemInfo }) > 0) + { + LogMessage($"Created succesefully", userFileSystemPath); + engine.CustomDataManager(userFileSystemPath, this).IsNew = false; + } + } + } + } + catch (Exception ex) + { + LogError($"{e.ChangeType} failed", remoteStoragePath, null, ex); + } + } + + /// + /// Called when a file content changed or file/folder attributes changed in the remote storage. + /// + /// + /// In this method we update corresponding file/folder information in user file system. + /// We also dehydrate the file if it is not blocked. + /// + private async void ChangedAsync(object sender, FileSystemEventArgs e) + { + LogMessage(e.ChangeType.ToString(), e.FullPath); + string remoteStoragePath = e.FullPath; + string userFileSystemPath = null; + try + { + userFileSystemPath = Mapping.ReverseMapPath(remoteStoragePath); + + // This check is only required because we can not prevent circular calls because of the simplicity of this example. + // In your real-life application you will not sent updates from server back to client that issued the update. + if (IsModified(userFileSystemPath, remoteStoragePath)) + { + FileSystemInfo remoteStorageItem = FsPath.GetFileSystemItem(remoteStoragePath); + IFileSystemItemMetadata itemInfo = Mapping.GetUserFileSysteItemMetadata(remoteStorageItem); + + // Because of the on-demand population the file or folder placeholder may not exist in the user file system. + if (await engine.ServerNotifications(userFileSystemPath).UpdateAsync(itemInfo)) + { + LogMessage("Updated succesefully", userFileSystemPath); + } + } + } + catch (IOException ex) + { + // The file is blocked in the user file system. This is a normal behaviour. + LogMessage(ex.Message); + } + catch (Exception ex) + { + LogError($"{e.ChangeType} failed", remoteStoragePath, null, ex); + } + } + + /// + /// Called when a file or folder is deleted in the remote storage. + /// + /// In this method we delete corresponding file/folder in user file system. + private async void DeletedAsync(object sender, FileSystemEventArgs e) + { + LogMessage(e.ChangeType.ToString(), e.FullPath); + string remoteStoragePath = e.FullPath; + try + { + string userFileSystemPath = Mapping.ReverseMapPath(remoteStoragePath); + + // This check is only required because we can not prevent circular calls because of the simplicity of this example. + // In your real-life application you will not sent updates from server back to client that issued the update. + Thread.Sleep(2000); // This can be removed in a real-life application. + if (FsPath.Exists(userFileSystemPath)) + { + // Because of the on-demand population the file or folder placeholder may not exist in the user file system. + if (await engine.ServerNotifications(userFileSystemPath).DeleteAsync()) + { + LogMessage("Deleted succesefully", userFileSystemPath); + } + engine.CustomDataManager(userFileSystemPath, this).Delete(); + } + } + catch (Exception ex) + { + LogError($"{e.ChangeType} failed", remoteStoragePath, null, ex); + } + } + + /// + /// Called when a file or folder is renamed in the remote storage. + /// + /// In this method we rename corresponding file/folder in user file system. + private async void RenamedAsync(object sender, RenamedEventArgs e) + { + LogMessage("Renamed:", e.OldFullPath, e.FullPath); + string remoteStorageOldPath = e.OldFullPath; + string remoteStorageNewPath = e.FullPath; + try + { + string userFileSystemOldPath = Mapping.ReverseMapPath(remoteStorageOldPath); + string userFileSystemNewPath = Mapping.ReverseMapPath(remoteStorageNewPath); + + // This check is only required because we can not prevent circular calls because of the simplicity of this example. + // In your real-life application you will not sent updates from server back to client that issued the update. + Thread.Sleep(2000); // This can be removed in a real-life application. + if (FsPath.Exists(userFileSystemOldPath)) + { + // Because of the on-demand population the file or folder placeholder may not exist in the user file system. + if (await engine.ServerNotifications(userFileSystemOldPath).MoveToAsync(userFileSystemNewPath)) + { + LogMessage("Renamed succesefully:", userFileSystemOldPath, userFileSystemNewPath); + } + } + await engine.CustomDataManager(userFileSystemOldPath, this).MoveToAsync(userFileSystemNewPath); + } + catch (Exception ex) + { + LogError($"{e.ChangeType} failed", remoteStorageOldPath, remoteStorageNewPath, ex); + } + } + + private void Error(object sender, ErrorEventArgs e) + { + LogError(null, null, null, e.GetException()); + } + + + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + watcher.Dispose(); + LogMessage($"Disposed"); + } + + // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. + // TODO: set large fields to null. + + disposedValue = true; + } + } + + // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. + // ~ServerChangesMonitor() + // { + // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + // Dispose(false); + // } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + // TODO: uncomment the following line if the finalizer is overridden above. + // GC.SuppressFinalize(this); + } + + /// + /// Compares two files contents. + /// + /// File or folder 1 to compare. + /// File or folder 2 to compare. + /// True if file is modified. False - otherwise. + private static bool IsModified(string filePath1, string filePath2) + { + if(FsPath.IsFolder(filePath1) && FsPath.IsFolder(filePath2)) + { + return false; + } + + try + { + if (new FileInfo(filePath1).Length == new FileInfo(filePath2).Length) + { + byte[] hash1; + byte[] hash2; + using (var alg = System.Security.Cryptography.MD5.Create()) + { + using (FileStream stream = new FileStream(filePath1, FileMode.Open, FileAccess.Read, FileShare.None)) + { + hash1 = alg.ComputeHash(stream); + } + using (FileStream stream = new FileStream(filePath2, FileMode.Open, FileAccess.Read, FileShare.None)) + { + hash2 = alg.ComputeHash(stream); + } + } + + return !hash1.SequenceEqual(hash2); + } + } + catch(IOException) + { + // One of the files is blocked. Can not compare files and start sychronization. + return false; + } + + return true; + } + } +} diff --git a/VirtualFileSystem/VirtualFileSystem.csproj b/Windows/VirtualDrive/VirtualDrive.csproj similarity index 71% rename from VirtualFileSystem/VirtualFileSystem.csproj rename to Windows/VirtualDrive/VirtualDrive.csproj index 8d60293..0db2475 100644 --- a/VirtualFileSystem/VirtualFileSystem.csproj +++ b/Windows/VirtualDrive/VirtualDrive.csproj @@ -5,12 +5,14 @@ 10.0.19041.0 IT Hit LTD. IT Hit LTD. - Virtual File System + Virtual Drive IT Hit LTD. AnyCPU - A virtual file system with synchronization support, on-demand loading, selective offline files support, upload and download progress, and error reporting. It synchronizes files and folders both from remote storage to the user file system and from the user file system to remote storage. + A virtual drive project in .NET/C# with synchronization support, on-demand loading, selective offline files support, upload and download progress. It synchronizes files and folders both from remote storage to the user file system and from the user file system to remote storage. -To simulate the remote storage, this sample is using a folder in the local file system on the same machine. +To simulate the remote storage, this sample is using a folder in the local file system on the same machine. You can use this project as a strting point for creating a Virtual Drive with advanced features. + +This is an advanced project with ETags support, Microsoft Office documents editing, automatic Microsoft Office documents locking, and custom columns in Windows File Manager. false @@ -30,8 +32,8 @@ To simulate the remote storage, this sample is using a folder in the local file - - + + @@ -40,9 +42,6 @@ To simulate the remote storage, this sample is using a folder in the local file PreserveNewest - - PreserveNewest - PreserveNewest @@ -61,12 +60,6 @@ To simulate the remote storage, this sample is using a folder in the local file PreserveNewest - - PreserveNewest - - - PreserveNewest - Always diff --git a/Windows/VirtualDrive/VirtualEngine.cs b/Windows/VirtualDrive/VirtualEngine.cs new file mode 100644 index 0000000..0b3cdd9 --- /dev/null +++ b/Windows/VirtualDrive/VirtualEngine.cs @@ -0,0 +1,174 @@ +using System.IO; +using System.Threading.Tasks; +using log4net; + +using ITHit.FileSystem; +using ITHit.FileSystem.Windows; + +using ITHit.FileSystem.Samples.Common.Windows; +using System; + +namespace VirtualDrive +{ + /// + public class VirtualEngine : EngineWindows + { + /// + /// Logger. + /// + private readonly ILogger logger; + + /// + /// Monitors changes in the remote storage, notifies the client and updates the user file system. + /// + internal readonly RemoteStorageMonitor RemoteStorageMonitor; + + /// + /// Monitors MS Office documents renames in the user file system. + /// + private readonly MsOfficeDocsMonitor msOfficeDocsMonitor; + + /// + /// Path to the folder that custom data associated with files and folders. + /// + private readonly string serverDataFolderPath; + + /// + /// Path to the icons folder. + /// + private readonly string iconsFolderPath; + + /// + /// Creates a vitual file system Engine. + /// + /// A license string. + /// + /// A root folder of your user file system. + /// Your file system tree will be located under this folder. + /// + /// Path to the folder that stores custom data associated with files and folders. + /// Path to the icons folder. + /// Logger. + public VirtualEngine(string license, string userFileSystemRootPath, string serverDataFolderPath, string iconsFolderPath, ILog log) + : base(license, userFileSystemRootPath) + { + logger = new Logger("File System Engine", log) ?? throw new NullReferenceException(nameof(log)); + this.serverDataFolderPath = serverDataFolderPath ?? throw new NullReferenceException(nameof(serverDataFolderPath)); + this.iconsFolderPath = iconsFolderPath ?? throw new NullReferenceException(nameof(iconsFolderPath)); + + // We want our file system to run regardless of any errors. + // If any request to file system fails in user code or in Engine itself we continue processing. + ThrowExceptions = false; + + StateChanged += Engine_StateChanged; + Error += Engine_Error; + Message += Engine_Message; + + string remoteStorageRootPath = Mapping.MapPath(userFileSystemRootPath); + RemoteStorageMonitor = new RemoteStorageMonitor(remoteStorageRootPath, this, log); + msOfficeDocsMonitor = new MsOfficeDocsMonitor(userFileSystemRootPath, this, log); + } + + /// + public override async Task GetFileSystemItemAsync(string path, FileSystemItemType itemType) + { + // When a file or folder is deleted, the item may be already + // deleted in user file system when this method is called + // The Engine calls IFile.CloseAsync() and IFileSystemItem.DeleteCompletionAsync() methods in this case. + + // On macOS there is no access to the local file system. + // You should NOT try to determine item type or read local files/folders on macOS. + + if (itemType == FileSystemItemType.File) + { + return new VirtualFile(path, this, this); + } + else + { + return new VirtualFolder(path, this, this); + } + } + + /// + public override async Task FilterAsync(string userFileSystemPath, string userFileSystemNewPath = null) + { + // IsMsOfficeLocked() check is required for MS Office PowerPoint. + // PowerPoint does not block the file for reading when the file is opened for editing. + // As a result the file will be sent to the remote storage during each file save operation. + + if (userFileSystemNewPath == null) + { + // Executed during create, update, delete, open, close. + return MsOfficeHelper.AvoidMsOfficeSync(userFileSystemPath); + } + else + { + // Executed during rename/move operation. + return + MsOfficeHelper.IsRecycleBin(userFileSystemNewPath) // When a hydrated file is deleted, it is moved to a Recycle Bin. + || MsOfficeHelper.AvoidMsOfficeSync(userFileSystemNewPath); + } + } + + /// + public override async Task StartAsync() + { + await base.StartAsync(); + RemoteStorageMonitor.Start(); + msOfficeDocsMonitor.Start(); + } + + public override async Task StopAsync() + { + await base.StopAsync(); + RemoteStorageMonitor.Stop(); + msOfficeDocsMonitor.Stop(); + } + + private void Engine_Message(IEngine sender, EngineMessageEventArgs e) + { + logger.LogMessage(e.Message, e.SourcePath, e.TargetPath); + } + + private void Engine_Error(IEngine sender, EngineErrorEventArgs e) + { + logger.LogError(e.Message, e.SourcePath, e.TargetPath, e.Exception); + } + + /// + /// Show status change. + /// + /// Engine + /// Contains new and old Engine state. + private void Engine_StateChanged(Engine engine, EngineWindows.StateChangeEventArgs e) + { + engine.LogMessage($"{e.NewState}"); + } + + /// + /// Manages custom data associated with the item. + /// + internal CustomDataManager CustomDataManager(string userFileSystemPath, ILogger logger = null) + { + return new CustomDataManager(userFileSystemPath, serverDataFolderPath, Path, iconsFolderPath, logger ?? this.logger); + } + + 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/Windows/VirtualDrive/VirtualFile.cs b/Windows/VirtualDrive/VirtualFile.cs new file mode 100644 index 0000000..9659251 --- /dev/null +++ b/Windows/VirtualDrive/VirtualFile.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using ITHit.FileSystem; +using ITHit.FileSystem.Samples.Common; +using ITHit.FileSystem.Samples.Common.Windows; +using ITHit.FileSystem.Windows; + +namespace VirtualDrive +{ + /// + public class VirtualFile : VirtualFileSystemItem, IFile + { + + /// + /// Creates instance of this class. + /// + /// File path in the user file system. + /// Engine instance. + /// Logger. + public VirtualFile(string path, VirtualEngine engine, ILogger logger) : base(path, engine, logger) + { + + } + + /// + public async Task OpenAsync(IOperationContext operationContext, IResultContext context) + { + Logger.LogMessage($"{nameof(IFile)}.{nameof(OpenAsync)}()", UserFileSystemPath); + } + + /// + public async Task CloseAsync(IOperationContext operationContext, IResultContext context) + { + Logger.LogMessage($"{nameof(IFile)}.{nameof(CloseAsync)}()", UserFileSystemPath); + } + + /// + public async Task ReadAsync(Stream output, long offset, long length, ITransferDataOperationContext operationContext, 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. + + Logger.LogMessage($"{nameof(IFile)}.{nameof(ReadAsync)}({offset}, {length})", UserFileSystemPath); + + SimulateNetworkDelay(length, resultContext); + + await using (FileStream stream = System.IO.File.OpenRead(RemoteStoragePath)) + { + stream.Seek(offset, SeekOrigin.Begin); + const int bufferSize = 0x500000; // 5Mb. Buffer size must be multiple of 4096 bytes for optimal performance. + await stream.CopyToAsync(output, bufferSize, length); + } + } + + /// + public async Task ValidateDataAsync(long offset, long length, IValidateDataOperationContext operationContext, IValidateDataResultContext resultContext) + { + // This method has a 60 sec timeout. + // To process longer requests and reset the timout timer call the ReturnValidationResult() method or IContextWindows.ReportProgress() method. + + Logger.LogMessage($"{nameof(IFile)}.{nameof(ValidateDataAsync)}({offset}, {length})", UserFileSystemPath); + + //SimulateNetworkDelay(length, resultContext); + + bool isValid = true; + + resultContext.ReturnValidationResult(offset, length, isValid); + } + + /// + public async Task WriteAsync(IFileMetadata fileMetadata, Stream content = null) + { + if(MsOfficeHelper.IsMsOfficeLocked(UserFileSystemPath)) // Required for PowerPoint. It does not block the for writing. + { + throw new ClientLockFailedException("The file is blocked for writing."); + } + + Logger.LogMessage($"{nameof(IFile)}.{nameof(WriteAsync)}()", UserFileSystemPath); + + CustomDataManager customDataManager = Engine.CustomDataManager(UserFileSystemPath); + // Send the ETag to the server as part of the update to ensure the file in the remote storge is not modified since last read. + string oldEtag = await customDataManager.ETagManager.GetETagAsync(); + + // Send the lock-token to the server as part of the update. + string lockToken = (await customDataManager.LockManager.GetLockInfoAsync())?.LockToken; + + FileInfo remoteStorageItem = new FileInfo(RemoteStoragePath); + + if (content != null) + { + // Upload remote storage file content. + await using (FileStream remoteStorageStream = remoteStorageItem.Open(FileMode.Open, FileAccess.Write, FileShare.Delete)) + { + await content.CopyToAsync(remoteStorageStream); + remoteStorageStream.SetLength(content.Length); + } + } + + // Update remote storage file metadata. + remoteStorageItem.Attributes = fileMetadata.Attributes; + remoteStorageItem.CreationTimeUtc = fileMetadata.CreationTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = fileMetadata.LastWriteTime.UtcDateTime; + remoteStorageItem.LastAccessTimeUtc = fileMetadata.LastAccessTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = fileMetadata.LastWriteTime.UtcDateTime; + + // Get the new ETag from server here as part of the update and save it on the client. + string newEtag = "1234567890"; + await customDataManager.ETagManager.SetETagAsync(newEtag); + await customDataManager.SetCustomColumnsAsync(new[] { new FileSystemItemPropertyData((int)CustomColumnIds.ETag, newEtag) }); + } + } +} diff --git a/Windows/VirtualDrive/VirtualFileSystemItem.cs b/Windows/VirtualDrive/VirtualFileSystemItem.cs new file mode 100644 index 0000000..9a60d38 --- /dev/null +++ b/Windows/VirtualDrive/VirtualFileSystemItem.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using ITHit.FileSystem; +using ITHit.FileSystem.Samples.Common.Windows; +using ITHit.FileSystem.Windows; + +namespace VirtualDrive +{ + /// + public abstract class VirtualFileSystemItem : IFileSystemItem, ILock + { + /// + /// File or folder path in the user file system. + /// + protected readonly string UserFileSystemPath; + + /// + /// Path of this file or folder in the remote storage. + /// + protected readonly string RemoteStoragePath; + + /// + /// Logger. + /// + protected readonly ILogger Logger; + + /// + /// Engine. + /// + protected readonly VirtualEngine Engine; + + /// + /// Creates instance of this class. + /// + /// File or folder path in the user file system. + /// Engine instance. + /// Logger. + public VirtualFileSystemItem(string userFileSystemPath, VirtualEngine engine, ILogger logger) + { + if (string.IsNullOrEmpty(userFileSystemPath)) + { + throw new ArgumentNullException(nameof(userFileSystemPath)); + } + Logger = logger ?? throw new ArgumentNullException(nameof(logger)); + Engine = engine ?? throw new ArgumentNullException(nameof(engine)); + + UserFileSystemPath = userFileSystemPath; + RemoteStoragePath = Mapping.MapPath(userFileSystemPath); + } + + /// + public async Task MoveToAsync(string userFileSystemNewPath, IOperationContext operationContext, IConfirmationResultContext resultContext) + { + string userFileSystemOldPath = this.UserFileSystemPath; + Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(MoveToAsync)}()", userFileSystemOldPath, userFileSystemNewPath); + + string remoteStorageOldPath = RemoteStoragePath; + string remoteStorageNewPath = Mapping.MapPath(userFileSystemNewPath); + + FileSystemInfo remoteStorageOldItem = FsPath.GetFileSystemItem(remoteStorageOldPath); + if (remoteStorageOldItem != null) + { + if (remoteStorageOldItem is FileInfo) + { + (remoteStorageOldItem as FileInfo).MoveTo(remoteStorageNewPath, true); + } + else + { + (remoteStorageOldItem as DirectoryInfo).MoveTo(remoteStorageNewPath); + } + Logger.LogMessage("Moved item in remote storage succesefully", userFileSystemOldPath, userFileSystemNewPath); + } + + await Engine.CustomDataManager(userFileSystemOldPath, Logger).MoveToAsync(userFileSystemNewPath); + } + + /// + public async Task MoveToCompletionAsync(IMoveCompletionContext moveCompletionContext, IResultContext resultContext) + { + string userFileSystemNewPath = this.UserFileSystemPath; + string userFileSystemOldPath = moveCompletionContext.SourcePath; + Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(MoveToCompletionAsync)}()", userFileSystemOldPath, userFileSystemNewPath); + } + + /// + public async Task DeleteAsync(IOperationContext operationContext, IConfirmationResultContext resultContext) + { + Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(DeleteAsync)}()", UserFileSystemPath); + } + + /// + public async Task DeleteCompletionAsync(IOperationContext operationContext, IResultContext resultContext) + { + // On Windows, for move with overwrite on folders to function correctly, + // the deletion of the folder in the remote storage must be done in DeleteCompletionAsync() + // Otherwise the folder will be deleted before files in it can be moved. + + Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(DeleteCompletionAsync)}()", UserFileSystemPath); + + FileSystemInfo remoteStorageItem = FsPath.GetFileSystemItem(RemoteStoragePath); + if (remoteStorageItem != null) + { + if (remoteStorageItem is FileInfo) + { + remoteStorageItem.Delete(); + } + else + { + (remoteStorageItem as DirectoryInfo).Delete(true); + } + Logger.LogMessage("Deleted item in remote storage succesefully", UserFileSystemPath); + } + + Engine.CustomDataManager(UserFileSystemPath, Logger).Delete(); + } + + /// + public Task GetMetadataAsync() + { + // Return IFileMetadata for a file, IFolderMetadata for a folder. + throw new NotImplementedException(); + } + + /// + /// Simulates network delays and reports file transfer progress for demo purposes. + /// + /// Length of file. + /// Context to report progress to. + protected void SimulateNetworkDelay(long fileLength, IResultContext resultContext) + { + if (Program.Settings.NetworkSimulationDelayMs > 0) + { + int numProgressResults = 5; + for (int i = 0; i < numProgressResults; i++) + { + resultContext.ReportProgress(fileLength, i * fileLength / numProgressResults); + Thread.Sleep(Program.Settings.NetworkSimulationDelayMs); + } + } + } + + /// + public async Task LockAsync(LockMode lockMode) + { + Logger.LogMessage($"{nameof(ILock)}.{nameof(LockAsync)}()", UserFileSystemPath); + + CustomDataManager customDataManager = Engine.CustomDataManager(UserFileSystemPath, Logger); + LockManager lockManager = customDataManager.LockManager; + if (!await lockManager.IsLockedAsync() + && !Engine.CustomDataManager(UserFileSystemPath).IsNew) + { + // Set pending icon, so the user has a feedback as lock operation may take some time. + await customDataManager.SetLockPendingIconAsync(true); + + // Call your remote storage here to lock the item. + // Save the lock token and other lock info received from the remote storage on the client. + // Supply the lock-token as part of each remote storage update in File.WriteAsync() method. + // For demo purposes we just fill some generic data. + ServerLockInfo lockInfo = new ServerLockInfo() { LockToken = "ServerToken", Owner = "You", Exclusive = true, LockExpirationDateUtc = DateTimeOffset.Now.AddMinutes(30) }; + + // Save lock-token and lock-mode. + await lockManager.SetLockInfoAsync(lockInfo); + await lockManager.SetLockModeAsync(lockMode); + + // Set lock icon and lock info in custom columns. + await customDataManager.SetLockInfoAsync(lockInfo); + + Logger.LogMessage("Locked in remote storage succesefully.", UserFileSystemPath); + } + } + + /// + public async Task GetLockModeAsync() + { + LockManager lockManager = Engine.CustomDataManager(UserFileSystemPath, Logger).LockManager; + return await lockManager.GetLockModeAsync(); + } + + /// + public async Task UnlockAsync() + { + if (MsOfficeHelper.IsMsOfficeLocked(UserFileSystemPath)) // Required for PowerPoint. It does not block the for writing. + { + throw new ClientLockFailedException("The file is blocked for writing."); + } + + CustomDataManager customDataManager = Engine.CustomDataManager(UserFileSystemPath, Logger); + LockManager lockManager = customDataManager.LockManager; + + // Set pending icon, so the user has a feedback as unlock operation may take some time. + await customDataManager.SetLockPendingIconAsync(true); + + // Read lock-token from lock-info file. + string lockToken = (await lockManager.GetLockInfoAsync()).LockToken; + + // Unlock the item in the remote storage here. + + // Delete lock-mode and lock-token info. + lockManager.DeleteLock(); + + // Remove lock icon and lock info in custom columns. + await customDataManager.SetLockInfoAsync(null); + + Logger.LogMessage("Unlocked in the remote storage succesefully", UserFileSystemPath); + } + + } +} diff --git a/Windows/VirtualDrive/VirtualFolder.cs b/Windows/VirtualDrive/VirtualFolder.cs new file mode 100644 index 0000000..efddaf4 --- /dev/null +++ b/Windows/VirtualDrive/VirtualFolder.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Enumeration; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using ITHit.FileSystem; +using ITHit.FileSystem.Windows; +using ITHit.FileSystem.Samples.Common.Windows; +using ITHit.FileSystem.Samples.Common; + +namespace VirtualDrive +{ + /// + public class VirtualFolder : VirtualFileSystemItem, IFolder + { + /// + /// Creates instance of this class. + /// + /// Folder path in the user file system. + /// Engine instance. + /// Logger. + public VirtualFolder(string path, VirtualEngine engine, ILogger logger) : base(path, engine, logger) + { + + } + + /// + public async Task CreateFileAsync(IFileMetadata fileMetadata, Stream content = null) + { + string userFileSystemNewItemPath = Path.Combine(UserFileSystemPath, fileMetadata.Name); + Logger.LogMessage($"{nameof(IFolder)}.{nameof(CreateFileAsync)}()", userFileSystemNewItemPath); + + FileInfo remoteStorageItem = new FileInfo(Path.Combine(RemoteStoragePath, fileMetadata.Name)); + + // Upload remote storage file content. + await using (FileStream remoteStorageStream = remoteStorageItem.Open(FileMode.CreateNew, FileAccess.Write, FileShare.Delete)) + { + if (content != null) + { + await content.CopyToAsync(remoteStorageStream); + remoteStorageStream.SetLength(content.Length); + } + } + + // Update remote storage file metadata. + remoteStorageItem.Attributes = fileMetadata.Attributes; + remoteStorageItem.CreationTimeUtc = fileMetadata.CreationTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = fileMetadata.LastWriteTime.UtcDateTime; + remoteStorageItem.LastAccessTimeUtc = fileMetadata.LastAccessTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = fileMetadata.LastWriteTime.UtcDateTime; + + // Get ETag from server here and save it on the client. + string newEtag = "1234567890"; + + CustomDataManager customDataManager = Engine.CustomDataManager(userFileSystemNewItemPath); + + // Mark this item as not new, which is required for correct MS Office saving opertions. + customDataManager.IsNew = false; + + await customDataManager.ETagManager.SetETagAsync(newEtag); + + // Update ETag in custom column displayed in file manager. + await customDataManager.SetCustomColumnsAsync(new[] { new FileSystemItemPropertyData((int)CustomColumnIds.ETag, newEtag) }); + } + + /// + public async Task CreateFolderAsync(IFolderMetadata folderMetadata) + { + string userFileSystemNewItemPath = Path.Combine(UserFileSystemPath, folderMetadata.Name); + Logger.LogMessage($"{nameof(IFolder)}.{nameof(CreateFolderAsync)}()", userFileSystemNewItemPath); + + DirectoryInfo remoteStorageItem = new DirectoryInfo(Path.Combine(RemoteStoragePath, folderMetadata.Name)); + remoteStorageItem.Create(); + + // Update remote storage folder metadata. + remoteStorageItem.Attributes = folderMetadata.Attributes; + remoteStorageItem.CreationTimeUtc = folderMetadata.CreationTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = folderMetadata.LastWriteTime.UtcDateTime; + remoteStorageItem.LastAccessTimeUtc = folderMetadata.LastAccessTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = folderMetadata.LastWriteTime.UtcDateTime; + + // Get ETag from server here and save it on the client. + string newEtag = "1234567890"; + + CustomDataManager customDataManager = Engine.CustomDataManager(userFileSystemNewItemPath); + + customDataManager.IsNew = false; + await customDataManager.ETagManager.SetETagAsync(newEtag); + await customDataManager.SetCustomColumnsAsync(new[] { new FileSystemItemPropertyData((int)CustomColumnIds.ETag, newEtag) }); + } + + /// + public async Task GetChildrenAsync(string pattern, IOperationContext operationContext, IFolderListingResultContext resultContext) + { + // This method has a 60 sec timeout. + // To process longer requests and reset the timout timer call one of the following: + // - resultContext.ReturnChildren() method. + // - resultContext.ReportProgress() method. + + Logger.LogMessage($"{nameof(IFolder)}.{nameof(GetChildrenAsync)}({pattern})", UserFileSystemPath); + + IEnumerable remoteStorageChildren = new DirectoryInfo(RemoteStoragePath).EnumerateFileSystemInfos(pattern); + + List userFileSystemChildren = new List(); + foreach (FileSystemInfo remoteStorageItem in remoteStorageChildren) + { + IFileSystemItemMetadata itemInfo = Mapping.GetUserFileSysteItemMetadata(remoteStorageItem); + userFileSystemChildren.Add(itemInfo); + + string userFileSystemItemPath = Path.Combine(UserFileSystemPath, itemInfo.Name); + + // Filtering existing files/folders. This is only required to avoid extra errors in the log. + if (!FsPath.Exists(userFileSystemItemPath)) + { + Logger.LogMessage("Creating", userFileSystemItemPath); + userFileSystemChildren.Add(itemInfo); + } + + CustomDataManager customDataManager = Engine.CustomDataManager(userFileSystemItemPath); + + // Mark this item as not new, which is required for correct MS Office saving opertions. + customDataManager.IsNew = false; + + // Save ETag on the client side, to be sent to the remote storage as part of the update. + await customDataManager.ETagManager.SetETagAsync("1234567890"); + } + + // To signal that the children enumeration is completed + // always call ReturnChildren(), even if the folder is empty. + resultContext.ReturnChildren(userFileSystemChildren.ToArray(), userFileSystemChildren.Count()); + + // Show some custom column in file manager for demo purposes. + foreach (IFileSystemItemMetadata itemInfo in userFileSystemChildren) + { + string userFileSystemItemPath = Path.Combine(UserFileSystemPath, itemInfo.Name); + + FileSystemItemPropertyData eTagColumn = new FileSystemItemPropertyData((int)CustomColumnIds.ETag, "1234567890"); + await Engine.CustomDataManager(userFileSystemItemPath).SetCustomColumnsAsync(new []{ eTagColumn }); + } + } + + /// + public async Task WriteAsync(IFolderMetadata folderMetadata) + { + Logger.LogMessage($"{nameof(IFolder)}.{nameof(WriteAsync)}()", UserFileSystemPath); + + DirectoryInfo remoteStorageItem = new DirectoryInfo(RemoteStoragePath); + + // Update remote storage folder metadata. + remoteStorageItem.Attributes = folderMetadata.Attributes; + remoteStorageItem.CreationTimeUtc = folderMetadata.CreationTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = folderMetadata.LastWriteTime.UtcDateTime; + remoteStorageItem.LastAccessTimeUtc = folderMetadata.LastAccessTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = folderMetadata.LastWriteTime.UtcDateTime; + } + } +} diff --git a/Windows/VirtualDrive/appsettings.json b/Windows/VirtualDrive/appsettings.json new file mode 100644 index 0000000..aecb915 --- /dev/null +++ b/Windows/VirtualDrive/appsettings.json @@ -0,0 +1,29 @@ +{ + // Unique ID of this application. + // If you must to run more than one instance of this application side-by-side on same client machine + // (aka Corporate Drive and Personal Drive) set unique ID for each instance. + "AppID": "VirtualDrive", + + // License to activate the IT Hit User File System Engine. If no license is specified the Engine will be activated + // automatically via internet and will function for 5 days. The Engine will stop working after that. + // To enable a 1-month trial period, download a trial license here: https://userfilesystem.com/download/ + "UserFileSystemLicense": "", + + // Folder that contains file structure to simulate data for your remote storage. + // In your real-life application you will read data from your cloud storage, database or any other location, instead of this folder. + // You can specify here both absolute path and path relative to application folder. + "RemoteStorageRootPath": ".\\RemoteStorage\\", + + //Your virtual file system will be mounted under this path. + "UserFileSystemRootPath": "%USERPROFILE%\\VirtualDrive\\", + + // Full synchronization interval in milliseconds. + "SyncIntervalMs": 10000, + + // Network delay in milliseconds. When this parameter is > 0 the file download is delayd to demonstrate file transfer progress. + // Set this parameter to 0 to avoid any network simulation delays. + "NetworkSimulationDelayMs": 0, + + // Automatically lock the file in remote storage when a file handle is being opened for writing, unlock on close. + "AutoLock": true +} \ No newline at end of file diff --git a/VirtualFileSystem/log4net.config b/Windows/VirtualDrive/log4net.config similarity index 100% rename from VirtualFileSystem/log4net.config rename to Windows/VirtualDrive/log4net.config diff --git a/Windows/VirtualFileSystem/AppSettings.cs b/Windows/VirtualFileSystem/AppSettings.cs new file mode 100644 index 0000000..3c8c569 --- /dev/null +++ b/Windows/VirtualFileSystem/AppSettings.cs @@ -0,0 +1,92 @@ +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +using ITHit.FileSystem.Samples.Common; + +namespace VirtualFileSystem +{ + /// + /// Strongly binded project settings. + /// + public class AppSettings : Settings + { + /// + /// Folder that contains file structure to simulate data for your virtual file system. + /// + /// + /// In your real-life application you will read data from your cloud storage, database or any other location, instead of this folder. + /// + public string RemoteStorageRootPath { get; set; } + } + + /// + /// Binds, validates and normalizes Settings configuration. + /// + public static class SettingsConfigValidator + { + /// + /// Binds, validates and normalizes WebDAV Context configuration. + /// + /// Instance of . + /// Virtual File System Settings. + public static AppSettings ReadSettings(this IConfiguration configuration) + { + AppSettings settings = new AppSettings(); + + if (configuration == null) + { + throw new ArgumentNullException("configurationSection"); + } + + configuration.Bind(settings); + + if (string.IsNullOrEmpty(settings.RemoteStorageRootPath)) + { + throw new ArgumentNullException("Settings.RemoteStorageRootPath"); + } + + if (string.IsNullOrEmpty(settings.UserFileSystemRootPath)) + { + throw new ArgumentNullException("Settings.UserFileSystemRootPath"); + } + + if (!Path.IsPathRooted(settings.RemoteStorageRootPath)) + { + string execPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); + settings.RemoteStorageRootPath = Path.GetFullPath(Path.Combine(execPath, "..", "..", "..", settings.RemoteStorageRootPath)); + } + + if (!Directory.Exists(settings.RemoteStorageRootPath)) + { + throw new DirectoryNotFoundException(string.Format("Settings.RemoteStorageRootPath specified in appsettings.json is invalid: '{0}'.", settings.RemoteStorageRootPath)); + } + + if (!Directory.Exists(settings.UserFileSystemRootPath)) + { + settings.UserFileSystemRootPath = Environment.ExpandEnvironmentVariables(settings.UserFileSystemRootPath); + } + + if (!Directory.Exists(settings.UserFileSystemRootPath)) + { + Directory.CreateDirectory(settings.UserFileSystemRootPath); + } + + string assemblyLocation = Assembly.GetEntryAssembly().Location; + + // Icons folder. + settings.IconsFolderPath = Path.Combine(Path.GetDirectoryName(assemblyLocation), @"Images"); + + // Load product name from entry exe file. + settings.ProductName = FileVersionInfo.GetVersionInfo(assemblyLocation).ProductName; + + + return settings; + } + } +} diff --git a/WebDAVDrive.UI/Drive.ico b/Windows/VirtualFileSystem/Images/Drive.ico similarity index 100% rename from WebDAVDrive.UI/Drive.ico rename to Windows/VirtualFileSystem/Images/Drive.ico diff --git a/VirtualFileSystem/Mapping.cs b/Windows/VirtualFileSystem/Mapping.cs similarity index 54% rename from VirtualFileSystem/Mapping.cs rename to Windows/VirtualFileSystem/Mapping.cs index 35ab701..e7e12a2 100644 --- a/VirtualFileSystem/Mapping.cs +++ b/Windows/VirtualFileSystem/Mapping.cs @@ -1,20 +1,21 @@ -using ITHit.FileSystem; -using ITHit.FileSystem.Windows; using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading.Tasks; -using ITHit.FileSystem.Samples.Common; +using ITHit.FileSystem; + namespace VirtualFileSystem { /// /// Maps a user file system path to the remote storage path and back. /// - /// You will change methods of this class to map the user file system path to your remote storage path. - internal static class Mapping + /// + /// You will change methods of this class to map the user file system path to your remote storage path. + /// + public static class Mapping { /// /// Returns a remote storage URI that corresponds to the user file system path. @@ -51,17 +52,18 @@ public static string ReverseMapPath(string remoteStorageUri) /// /// Remote storage item info. /// User file system item info. - public static FileSystemItemMetadataExt GetUserFileSysteItemMetadata(FileSystemInfo remoteStorageItem) + public static IFileSystemItemMetadata GetUserFileSysteItemMetadata(FileSystemInfo remoteStorageItem) { - FileSystemItemMetadataExt userFileSystemItem; + IFileSystemItemMetadata userFileSystemItem; if (remoteStorageItem is FileInfo) { - userFileSystemItem = new FileMetadataExt(); + userFileSystemItem = new FileMetadata(); + ((FileMetadata)userFileSystemItem).Length = ((FileInfo)remoteStorageItem).Length; } else { - userFileSystemItem = new FolderMetadataExt(); + userFileSystemItem = new FolderMetadata(); } userFileSystemItem.Name = remoteStorageItem.Name; @@ -71,39 +73,6 @@ public static FileSystemItemMetadataExt GetUserFileSysteItemMetadata(FileSystemI userFileSystemItem.LastAccessTime = remoteStorageItem.LastAccessTime; userFileSystemItem.ChangeTime = remoteStorageItem.LastWriteTime; - // You will 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. - // - // In this sample, for the sake of simplicity, we use file last write time instead of ETag. - 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. - userFileSystemItem.LockedByAnotherUser = (remoteStorageItem.Attributes & System.IO.FileAttributes.ReadOnly) != 0; - - if (remoteStorageItem is FileInfo) - { - ((FileMetadataExt)userFileSystemItem).Length = ((FileInfo)remoteStorageItem).Length; - }; - - // Set custom columns to be displayed in file manager. - // We create property definitions when registering the sync root with corresponding IDs. - List customProps = new List(); - if (userFileSystemItem.LockedByAnotherUser) - { - customProps.AddRange( - new ServerLockInfo() - { - LockToken = "token", - Owner = "User Name", - Exclusive = true, - LockExpirationDateUtc = DateTimeOffset.Now.AddMinutes(30) - }.GetLockProperties(Path.Combine(Program.Settings.IconsFolderPath, "LockedByAnotherUser.ico")) - ); - } - userFileSystemItem.CustomProperties = customProps; - return userFileSystemItem; } } diff --git a/Windows/VirtualFileSystem/Program.cs b/Windows/VirtualFileSystem/Program.cs new file mode 100644 index 0000000..bfc7b21 --- /dev/null +++ b/Windows/VirtualFileSystem/Program.cs @@ -0,0 +1,164 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Windows.Storage; +using Windows.Storage.Provider; +using Microsoft.Extensions.Configuration; +using log4net; +using log4net.Config; + +using ITHit.FileSystem.Samples.Common.Windows; + + +namespace VirtualFileSystem +{ + class Program + { + /// + /// Application settings. + /// + internal static AppSettings Settings; + + /// + /// Log4Net logger. + /// + private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// Processes OS file system calls, + /// synchronizes user file system to remote storage. + /// + public static VirtualEngine Engine; + + static async Task Main(string[] args) + { + // Load Settings. + IConfiguration configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json", false, true).Build(); + Settings = configuration.ReadSettings(); + + // Load Log4Net for net configuration. + var logRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); + XmlConfigurator.Configure(logRepository, new FileInfo("log4net.config")); + + // Enable UTF8 for Console Window + Console.OutputEncoding = System.Text.Encoding.UTF8; + + log.Info($"\n{System.Diagnostics.Process.GetCurrentProcess().ProcessName} {Settings.AppID}"); + log.Info("\nPress 'Q' to unregister file system, delete all files/folders and exit (simulate uninstall with full cleanup)."); + log.Info("\nPress 'q' to unregister file system and exit (simulate uninstall)."); + log.Info("\nPress any other key to exit without unregistering (simulate reboot)."); + log.Info("\n----------------------\n"); + + // Typically you will register sync root during your application installation. + // Here we register it during first program start for the sake of the development convenience. + if (!await Registrar.IsRegisteredAsync(Settings.UserFileSystemRootPath)) + { + log.Info($"\nRegistering {Settings.UserFileSystemRootPath} sync root."); + Directory.CreateDirectory(Settings.UserFileSystemRootPath); + + await Registrar.RegisterAsync(SyncRootId, Settings.UserFileSystemRootPath, Settings.ProductName, + Path.Combine(Settings.IconsFolderPath, "Drive.ico")); + } + else + { + log.Info($"\n{Settings.UserFileSystemRootPath} sync root already registered."); + } + + // Log indexed state. + StorageFolder userFileSystemRootFolder = await StorageFolder.GetFolderFromPathAsync(Settings.UserFileSystemRootPath); + log.Info($"\nIndexed state: {(await userFileSystemRootFolder.GetIndexedStateAsync())}\n"); + + ConsoleKeyInfo exitKey; + + try + { + Engine = new VirtualEngine(Settings.UserFileSystemLicense, Settings.UserFileSystemRootPath, log); + + // Start processing OS file system calls. + await Engine.StartAsync(); + +#if DEBUG + // Opens Windows File Manager with user file system folder and remote storage folder. + ShowTestEnvironment(); +#endif + // Keep this application running until user input. + exitKey = Console.ReadKey(); + } + finally + { + Engine.Dispose(); + } + + if (exitKey.KeyChar == 'q') + { + // Unregister during programm uninstall. + await Registrar.UnregisterAsync(SyncRootId); + log.Info($"\n\nUnregistering {Settings.UserFileSystemRootPath} sync root."); + log.Info("\nAll empty file and folder placeholders are deleted. Hydrated placeholders are converted to regular files / folders.\n"); + } + else if (exitKey.KeyChar == 'Q') + { + log.Info($"\n\nUnregistering {Settings.UserFileSystemRootPath} sync root."); + log.Info("\nAll files and folders placeholders are deleted.\n"); + + // Unregister during programm uninstall and delete all files/folder. + await Registrar.UnregisterAsync(SyncRootId); + try + { + Directory.Delete(Settings.UserFileSystemRootPath, true); + } + catch (Exception ex) + { + log.Error($"\n{ex}"); + } + } + else + { + log.Info("\n\nAll downloaded file / folder placeholders remain in file system. Restart the application to continue managing files.\n"); + } + + return 1; + } + +#if DEBUG + /// + /// Opens Windows File Manager with both remote storage and user file system for testing. + /// + /// This method is provided solely for the development and testing convenience. + private static void ShowTestEnvironment() + { + // Open Windows File Manager with user file system. + ProcessStartInfo ufsInfo = new ProcessStartInfo(Program.Settings.UserFileSystemRootPath); + ufsInfo.UseShellExecute = true; // Open window only if not opened already. + using (Process ufsWinFileManager = Process.Start(ufsInfo)) + { + + } + + // Open Windows File Manager with remote storage. + ProcessStartInfo rsInfo = new ProcessStartInfo(Program.Settings.RemoteStorageRootPath); + rsInfo.UseShellExecute = true; // Open window only if not opened already. + using (Process rsWinFileManager = Process.Start(rsInfo)) + { + + } + } +#endif + + /// + /// Gets automatically generated Sync Root ID. + /// + /// An identifier in the form: [Storage Provider ID]![Windows SID]![Account ID] + private static string SyncRootId + { + get + { + return $"{Settings.AppID}!{System.Security.Principal.WindowsIdentity.GetCurrent().User}!User"; + } + } + } +} diff --git a/VirtualFileSystem/README.md b/Windows/VirtualFileSystem/README.md similarity index 100% rename from VirtualFileSystem/README.md rename to Windows/VirtualFileSystem/README.md diff --git a/VirtualFileSystemMac/RemoteStorage/Project.pdf b/Windows/VirtualFileSystem/RemoteStorage/General.pdf similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Project.pdf rename to Windows/VirtualFileSystem/RemoteStorage/General.pdf diff --git a/Windows/VirtualFileSystem/RemoteStorage/Introduction.pdf b/Windows/VirtualFileSystem/RemoteStorage/Introduction.pdf new file mode 100644 index 0000000..c82e775 Binary files /dev/null and b/Windows/VirtualFileSystem/RemoteStorage/Introduction.pdf differ diff --git a/Windows/VirtualFileSystem/RemoteStorage/Library/Content.pdf b/Windows/VirtualFileSystem/RemoteStorage/Library/Content.pdf new file mode 100644 index 0000000..c82e775 Binary files /dev/null and b/Windows/VirtualFileSystem/RemoteStorage/Library/Content.pdf differ diff --git a/VirtualFileSystemMac/RemoteStorage/Products/General.vsd b/Windows/VirtualFileSystem/RemoteStorage/Library/Overview.txt similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Products/General.vsd rename to Windows/VirtualFileSystem/RemoteStorage/Library/Overview.txt diff --git a/VirtualFileSystemMac/RemoteStorage/Scheme.vsdx b/Windows/VirtualFileSystem/RemoteStorage/Library/Product.txt similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Scheme.vsdx rename to Windows/VirtualFileSystem/RemoteStorage/Library/Product.txt diff --git a/Windows/VirtualFileSystem/RemoteStorage/Library/Project.pdf b/Windows/VirtualFileSystem/RemoteStorage/Library/Project.pdf new file mode 100644 index 0000000..c82e775 Binary files /dev/null and b/Windows/VirtualFileSystem/RemoteStorage/Library/Project.pdf differ diff --git a/Windows/VirtualFileSystem/RemoteStorage/Library/Vision.txt b/Windows/VirtualFileSystem/RemoteStorage/Library/Vision.txt new file mode 100644 index 0000000..e69de29 diff --git a/Windows/VirtualFileSystem/RemoteStorage/My Directory/Content.pdf b/Windows/VirtualFileSystem/RemoteStorage/My Directory/Content.pdf new file mode 100644 index 0000000..c82e775 Binary files /dev/null and b/Windows/VirtualFileSystem/RemoteStorage/My Directory/Content.pdf differ diff --git a/VirtualFileSystem/RemoteStorage/Notes.txt b/Windows/VirtualFileSystem/RemoteStorage/My Directory/Notes.txt similarity index 100% rename from VirtualFileSystem/RemoteStorage/Notes.txt rename to Windows/VirtualFileSystem/RemoteStorage/My Directory/Notes.txt diff --git a/VirtualFileSystemMac/RemoteStorage/My Directory/Notes.txt b/Windows/VirtualFileSystem/RemoteStorage/Notes.txt similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/My Directory/Notes.txt rename to Windows/VirtualFileSystem/RemoteStorage/Notes.txt diff --git a/VirtualFileSystemMac/RemoteStorage/Pictures/Arctic Ice.jpeg b/Windows/VirtualFileSystem/RemoteStorage/Pictures/Arctic Ice.jpeg similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Pictures/Arctic Ice.jpeg rename to Windows/VirtualFileSystem/RemoteStorage/Pictures/Arctic Ice.jpeg diff --git a/VirtualFileSystemMac/RemoteStorage/Pictures/Arctic Sun.jpeg b/Windows/VirtualFileSystem/RemoteStorage/Pictures/Arctic Sun.jpeg similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Pictures/Arctic Sun.jpeg rename to Windows/VirtualFileSystem/RemoteStorage/Pictures/Arctic Sun.jpeg diff --git a/VirtualFileSystemMac/RemoteStorage/Pictures/Autumn.jpeg b/Windows/VirtualFileSystem/RemoteStorage/Pictures/Autumn.jpeg similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Pictures/Autumn.jpeg rename to Windows/VirtualFileSystem/RemoteStorage/Pictures/Autumn.jpeg diff --git a/VirtualFileSystemMac/RemoteStorage/Pictures/Beach.jpeg b/Windows/VirtualFileSystem/RemoteStorage/Pictures/Beach.jpeg similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Pictures/Beach.jpeg rename to Windows/VirtualFileSystem/RemoteStorage/Pictures/Beach.jpeg diff --git a/VirtualFileSystemMac/RemoteStorage/Pictures/Boats.jpeg b/Windows/VirtualFileSystem/RemoteStorage/Pictures/Boats.jpeg similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Pictures/Boats.jpeg rename to Windows/VirtualFileSystem/RemoteStorage/Pictures/Boats.jpeg diff --git a/VirtualFileSystemMac/RemoteStorage/Pictures/Cruise Ship.jpeg b/Windows/VirtualFileSystem/RemoteStorage/Pictures/Cruise Ship.jpeg similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Pictures/Cruise Ship.jpeg rename to Windows/VirtualFileSystem/RemoteStorage/Pictures/Cruise Ship.jpeg diff --git a/VirtualFileSystemMac/RemoteStorage/Pictures/Glacier.jpeg b/Windows/VirtualFileSystem/RemoteStorage/Pictures/Glacier.jpeg similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Pictures/Glacier.jpeg rename to Windows/VirtualFileSystem/RemoteStorage/Pictures/Glacier.jpeg diff --git a/VirtualFileSystemMac/RemoteStorage/Pictures/Hotel.jpeg b/Windows/VirtualFileSystem/RemoteStorage/Pictures/Hotel.jpeg similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Pictures/Hotel.jpeg rename to Windows/VirtualFileSystem/RemoteStorage/Pictures/Hotel.jpeg diff --git a/VirtualFileSystemMac/RemoteStorage/Pictures/Landing Pier.jpeg b/Windows/VirtualFileSystem/RemoteStorage/Pictures/Landing Pier.jpeg similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Pictures/Landing Pier.jpeg rename to Windows/VirtualFileSystem/RemoteStorage/Pictures/Landing Pier.jpeg diff --git a/VirtualFileSystemMac/RemoteStorage/Pictures/Orange in the Mountains.jpeg b/Windows/VirtualFileSystem/RemoteStorage/Pictures/Orange in the Mountains.jpeg similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Pictures/Orange in the Mountains.jpeg rename to Windows/VirtualFileSystem/RemoteStorage/Pictures/Orange in the Mountains.jpeg diff --git a/VirtualFileSystemMac/RemoteStorage/Pictures/River.jpeg b/Windows/VirtualFileSystem/RemoteStorage/Pictures/River.jpeg similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Pictures/River.jpeg rename to Windows/VirtualFileSystem/RemoteStorage/Pictures/River.jpeg diff --git a/VirtualFileSystemMac/RemoteStorage/Pictures/Sunset.jpeg b/Windows/VirtualFileSystem/RemoteStorage/Pictures/Sunset.jpeg similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Pictures/Sunset.jpeg rename to Windows/VirtualFileSystem/RemoteStorage/Pictures/Sunset.jpeg diff --git a/VirtualFileSystemMac/RemoteStorage/Pictures/Windsurfing.jpeg b/Windows/VirtualFileSystem/RemoteStorage/Pictures/Windsurfing.jpeg similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Pictures/Windsurfing.jpeg rename to Windows/VirtualFileSystem/RemoteStorage/Pictures/Windsurfing.jpeg diff --git a/VirtualFileSystemMac/RemoteStorage/Pictures/Yachts.jpeg b/Windows/VirtualFileSystem/RemoteStorage/Pictures/Yachts.jpeg similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Pictures/Yachts.jpeg rename to Windows/VirtualFileSystem/RemoteStorage/Pictures/Yachts.jpeg diff --git a/VirtualFileSystemMac/RemoteStorage/Pictures/Yellow Tree.jpeg b/Windows/VirtualFileSystem/RemoteStorage/Pictures/Yellow Tree.jpeg similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Pictures/Yellow Tree.jpeg rename to Windows/VirtualFileSystem/RemoteStorage/Pictures/Yellow Tree.jpeg diff --git a/Windows/VirtualFileSystem/RemoteStorage/Products/General.pdf b/Windows/VirtualFileSystem/RemoteStorage/Products/General.pdf new file mode 100644 index 0000000..c82e775 Binary files /dev/null and b/Windows/VirtualFileSystem/RemoteStorage/Products/General.pdf differ diff --git a/Windows/VirtualFileSystem/RemoteStorage/Products/Product.txt b/Windows/VirtualFileSystem/RemoteStorage/Products/Product.txt new file mode 100644 index 0000000..e69de29 diff --git a/Windows/VirtualFileSystem/RemoteStorage/Project.pdf b/Windows/VirtualFileSystem/RemoteStorage/Project.pdf new file mode 100644 index 0000000..c82e775 Binary files /dev/null and b/Windows/VirtualFileSystem/RemoteStorage/Project.pdf differ diff --git a/VirtualFileSystemMac/RemoteStorage/Readme.txt b/Windows/VirtualFileSystem/RemoteStorage/Readme.txt similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Readme.txt rename to Windows/VirtualFileSystem/RemoteStorage/Readme.txt diff --git a/Windows/VirtualFileSystem/RemoteStorage/Sales/Australia/Plan.pdf b/Windows/VirtualFileSystem/RemoteStorage/Sales/Australia/Plan.pdf new file mode 100644 index 0000000..c82e775 Binary files /dev/null and b/Windows/VirtualFileSystem/RemoteStorage/Sales/Australia/Plan.pdf differ diff --git a/Windows/VirtualFileSystem/RemoteStorage/Sales/Australia/Prices.txt b/Windows/VirtualFileSystem/RemoteStorage/Sales/Australia/Prices.txt new file mode 100644 index 0000000..e69de29 diff --git a/Windows/VirtualFileSystem/RemoteStorage/Sales/Canada/Introduction.txt b/Windows/VirtualFileSystem/RemoteStorage/Sales/Canada/Introduction.txt new file mode 100644 index 0000000..e69de29 diff --git a/Windows/VirtualFileSystem/RemoteStorage/Sales/Canada/Prices.pdf b/Windows/VirtualFileSystem/RemoteStorage/Sales/Canada/Prices.pdf new file mode 100644 index 0000000..c82e775 Binary files /dev/null and b/Windows/VirtualFileSystem/RemoteStorage/Sales/Canada/Prices.pdf differ diff --git a/Windows/VirtualFileSystem/RemoteStorage/Sales/Canada/Product.txt b/Windows/VirtualFileSystem/RemoteStorage/Sales/Canada/Product.txt new file mode 100644 index 0000000..e69de29 diff --git a/Windows/VirtualFileSystem/RemoteStorage/Sales/Canada/Stat.pdf b/Windows/VirtualFileSystem/RemoteStorage/Sales/Canada/Stat.pdf new file mode 100644 index 0000000..c82e775 Binary files /dev/null and b/Windows/VirtualFileSystem/RemoteStorage/Sales/Canada/Stat.pdf differ diff --git a/Windows/VirtualFileSystem/RemoteStorage/Sales/Europe/Stat.txt b/Windows/VirtualFileSystem/RemoteStorage/Sales/Europe/Stat.txt new file mode 100644 index 0000000..e69de29 diff --git a/Windows/VirtualFileSystem/RemoteStorage/Sales/USA/Vision.pdf b/Windows/VirtualFileSystem/RemoteStorage/Sales/USA/Vision.pdf new file mode 100644 index 0000000..c82e775 Binary files /dev/null and b/Windows/VirtualFileSystem/RemoteStorage/Sales/USA/Vision.pdf differ diff --git a/Windows/VirtualFileSystem/RemoteStorage/Scheme.txt b/Windows/VirtualFileSystem/RemoteStorage/Scheme.txt new file mode 100644 index 0000000..e69de29 diff --git a/Windows/VirtualFileSystem/RemoteStorage/Stat.pdf b/Windows/VirtualFileSystem/RemoteStorage/Stat.pdf new file mode 100644 index 0000000..c82e775 Binary files /dev/null and b/Windows/VirtualFileSystem/RemoteStorage/Stat.pdf differ diff --git a/VirtualFileSystem/RemoteStorageMonitor.cs b/Windows/VirtualFileSystem/RemoteStorageMonitor.cs similarity index 58% rename from VirtualFileSystem/RemoteStorageMonitor.cs rename to Windows/VirtualFileSystem/RemoteStorageMonitor.cs index 736307f..a925a30 100644 --- a/VirtualFileSystem/RemoteStorageMonitor.cs +++ b/Windows/VirtualFileSystem/RemoteStorageMonitor.cs @@ -1,30 +1,25 @@ -using ITHit.FileSystem; -using ITHit.FileSystem.Windows; -using log4net; using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; -using Windows.Storage; -using Windows.Storage.FileProperties; -using Windows.Storage.Provider; -using Windows.System.Update; +using log4net; -using ITHit.FileSystem.Samples.Common; +using ITHit.FileSystem; using ITHit.FileSystem.Samples.Common.Windows; +using System.Linq; namespace VirtualFileSystem { /// /// Monitors changes in the remote storage, notifies the client and updates the user file system. /// If any file or folder is is modified, created, delated, renamed or attributes changed in the remote storage, - /// triggers an event with information about changes being made. + /// triggers an event with information about changes. /// /// - /// Here, for demo purposes we simulate server by monitoring source file path using FileSystemWatcher. - /// In your application, instead of using FileSystemWatcher, you will connect to your remote storage using web sockets + /// Here, for demo purposes we simulate server by monitoring source file path using FileWatchWrapper. + /// In your application, instead of using FileWatchWrapper, you will connect to your remote storage using web sockets /// or use any other technology to get notifications about changes in your remote storage. /// internal class RemoteStorageMonitor : Logger, IDisposable @@ -35,35 +30,27 @@ internal class RemoteStorageMonitor : Logger, IDisposable private readonly string remoteStorageRootPath; /// - /// Virtul drive instance. This class will call methods - /// to update user file system when any data is changed in the remote storage: - /// , - /// , etc. + /// Engine instance. We will call methods + /// to update user file system when any data is changed in the remote storage. /// - private readonly VirtualDrive virtualDrive; + private readonly Engine engine; /// /// Watches for changes in the remote storage file system. /// - private readonly FileSystemWatcher watcher = new FileSystemWatcher(); + private readonly FileSystemWatcherQueued watcher = new FileSystemWatcherQueued(); /// /// 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. + /// Virtual drive to send notifications about changes in the remote storage. /// Logger. - internal RemoteStorageMonitor(string remoteStorageRootPath, VirtualDrive virtualDrive, ILog log) : base("Remote Storage Monitor", log) + internal RemoteStorageMonitor(string remoteStorageRootPath, Engine engine, ILog log) : base("Remote Storage Monitor", log) { this.remoteStorageRootPath = remoteStorageRootPath; - this.virtualDrive = virtualDrive; - } + this.engine = engine; - /// - /// Starts monitoring changes on the server. - /// - internal async Task StartAsync() - { watcher.IncludeSubdirectories = true; watcher.Path = remoteStorageRootPath; //watcher.Filter = "*.*"; @@ -73,68 +60,55 @@ internal async Task StartAsync() watcher.Changed += ChangedAsync; watcher.Deleted += DeletedAsync; watcher.Renamed += RenamedAsync; - watcher.EnableRaisingEvents = true; - - LogMessage($"Started"); + watcher.EnableRaisingEvents = false; } /// - /// Stops monitoring changes in the remote storage. + /// Starts monitoring changes on the server. /// - internal async Task StopAsync() - { - Enabled = false; - } - - private bool Started() + internal void Start() { - return !string.IsNullOrEmpty(watcher.Path); + watcher.EnableRaisingEvents = true; + LogMessage($"Started"); } /// - /// Disables or enables monitoring. Used to avoid circular calls. + /// Stops monitoring changes in the remote storage. /// - /// - /// In this sample we can not detect which client have made an update in the remote storage - /// folder and have to disable remote storage monitoring when the update is made. In your - /// real-life system you do not send requests back to the client that initiated the change - /// and will just delete this property. - /// - internal bool Enabled + internal void Stop() { - get { return watcher.EnableRaisingEvents; } - set - { - if (Started()) - { - watcher.EnableRaisingEvents = value; - } - } + watcher.EnableRaisingEvents = false; + LogMessage($"Stopped"); } /// /// Called when a file or folder is created in the remote storage. /// - /// In this method we create a new file/folder in user file system. + /// In this method we create a new file/folder in the user file system. private async void CreatedAsync(object sender, FileSystemEventArgs e) { LogMessage(e.ChangeType.ToString(), e.FullPath); string remoteStoragePath = e.FullPath; try { - // We do not want to sync MS Office temp files from remote storage. - if (!FsPath.AvoidSync(remoteStoragePath)) + string userFileSystemPath = Mapping.ReverseMapPath(remoteStoragePath); + + // This check is only required because we can not prevent circular calls because of the simplicity of this example. + // In your real-life application you will not sent updates from server back to client that issued the update. + if (!FsPath.Exists(userFileSystemPath)) { - string userFileSystemPath = Mapping.ReverseMapPath(remoteStoragePath); string userFileSystemParentPath = Path.GetDirectoryName(userFileSystemPath); // 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. FileSystemInfo remoteStorageItem = FsPath.GetFileSystemItem(remoteStoragePath); - FileSystemItemMetadataExt newItemInfo = Mapping.GetUserFileSysteItemMetadata(remoteStorageItem); - if (await virtualDrive.ServerNotifications(userFileSystemParentPath, this).CreateAsync(new[] { newItemInfo }) > 0) + if (remoteStorageItem != null) { - LogMessage($"Created succesefully", userFileSystemPath); + IFileSystemItemMetadata newItemInfo = Mapping.GetUserFileSysteItemMetadata(remoteStorageItem); + if (await engine.ServerNotifications(userFileSystemParentPath).CreateAsync(new[] { newItemInfo }) > 0) + { + LogMessage($"Created succesefully", userFileSystemPath); + } } } } @@ -155,29 +129,30 @@ private async void ChangedAsync(object sender, FileSystemEventArgs e) { LogMessage(e.ChangeType.ToString(), e.FullPath); string remoteStoragePath = e.FullPath; + string userFileSystemPath = null; try { - // We do not want to sync MS Office temp files, etc. from remote storage. - if (!FsPath.AvoidSync(remoteStoragePath)) + userFileSystemPath = Mapping.ReverseMapPath(remoteStoragePath); + + // This check is only required because we can not prevent circular calls because of the simplicity of this example. + // In your real-life application you will not sent updates from server back to client that issued the update. + if (IsModified(userFileSystemPath, remoteStoragePath)) { - string userFileSystemPath = Mapping.ReverseMapPath(remoteStoragePath); + FileSystemInfo remoteStorageItem = FsPath.GetFileSystemItem(remoteStoragePath); + IFileSystemItemMetadata itemInfo = Mapping.GetUserFileSysteItemMetadata(remoteStorageItem); // 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 engine.ServerNotifications(userFileSystemPath).UpdateAsync(itemInfo)) { - FileSystemInfo remoteStorageItem = FsPath.GetFileSystemItem(remoteStoragePath); - - // This check is only required because we can not prevent circular calls because of the simplicity of this example. - // In your real-life application you will not sent updates from server back to client that issued the update. - FileSystemItemMetadataExt itemInfo = Mapping.GetUserFileSysteItemMetadata(remoteStorageItem); - if (!await virtualDrive.GetETagManager(userFileSystemPath, this).ETagEqualsAsync(itemInfo)) - { - await virtualDrive.ServerNotifications(userFileSystemPath, this).UpdateAsync(itemInfo); - LogMessage("Updated succesefully", userFileSystemPath); - } + LogMessage("Updated succesefully", userFileSystemPath); } } } + catch (IOException ex) + { + // The file is blocked in the user file system. This is a normal behaviour. + LogMessage(ex.Message); + } catch (Exception ex) { LogError($"{e.ChangeType} failed", remoteStoragePath, null, ex); @@ -194,12 +169,15 @@ private async void DeletedAsync(object sender, FileSystemEventArgs e) string remoteStoragePath = e.FullPath; try { - if (!FsPath.AvoidSync(remoteStoragePath)) - { - string userFileSystemPath = Mapping.ReverseMapPath(remoteStoragePath); + string userFileSystemPath = Mapping.ReverseMapPath(remoteStoragePath); + // This check is only required because we can not prevent circular calls because of the simplicity of this example. + // In your real-life application you will not sent updates from server back to client that issued the update. + Thread.Sleep(2000); // This can be removed in a real-life application. + if (FsPath.Exists(userFileSystemPath)) + { // Because of the on-demand population the file or folder placeholder may not exist in the user file system. - if (await virtualDrive.ServerNotifications(userFileSystemPath, this).DeleteAsync()) + if (await engine.ServerNotifications(userFileSystemPath).DeleteAsync()) { LogMessage("Deleted succesefully", userFileSystemPath); } @@ -222,14 +200,16 @@ private async void RenamedAsync(object sender, RenamedEventArgs e) string remoteStorageNewPath = e.FullPath; try { - if (!FsPath.AvoidSync(remoteStorageOldPath) && !FsPath.AvoidSync(remoteStorageNewPath)) - { - string userFileSystemOldPath = Mapping.ReverseMapPath(remoteStorageOldPath); - - string userFileSystemNewPath = Mapping.ReverseMapPath(remoteStorageNewPath); + string userFileSystemOldPath = Mapping.ReverseMapPath(remoteStorageOldPath); + string userFileSystemNewPath = Mapping.ReverseMapPath(remoteStorageNewPath); + // This check is only required because we can not prevent circular calls because of the simplicity of this example. + // In your real-life application you will not sent updates from server back to client that issued the update. + Thread.Sleep(2000); // This can be removed in a real-life application. + if (FsPath.Exists(userFileSystemOldPath)) + { // Because of the on-demand population the file or folder placeholder may not exist in the user file system. - if (await virtualDrive.ServerNotifications(userFileSystemOldPath, this).MoveToAsync(userFileSystemNewPath)) + if (await engine.ServerNotifications(userFileSystemOldPath).MoveToAsync(userFileSystemNewPath)) { LogMessage("Renamed succesefully:", userFileSystemOldPath, userFileSystemNewPath); } @@ -281,5 +261,48 @@ public void Dispose() // TODO: uncomment the following line if the finalizer is overridden above. // GC.SuppressFinalize(this); } + + /// + /// Compares two files contents. + /// + /// File or folder 1 to compare. + /// File or folder 2 to compare. + /// True if file is modified. False - otherwise. + private static bool IsModified(string filePath1, string filePath2) + { + if(FsPath.IsFolder(filePath1) && FsPath.IsFolder(filePath2)) + { + return false; + } + + try + { + if (new FileInfo(filePath1).Length == new FileInfo(filePath2).Length) + { + byte[] hash1; + byte[] hash2; + using (var alg = System.Security.Cryptography.MD5.Create()) + { + using (FileStream stream = new FileStream(filePath1, FileMode.Open, FileAccess.Read, FileShare.None)) + { + hash1 = alg.ComputeHash(stream); + } + using (FileStream stream = new FileStream(filePath2, FileMode.Open, FileAccess.Read, FileShare.None)) + { + hash2 = alg.ComputeHash(stream); + } + } + + return !hash1.SequenceEqual(hash2); + } + } + catch(IOException) + { + // One of the files is blocked. Can not compare files and start sychronization. + return false; + } + + return true; + } } } diff --git a/Windows/VirtualFileSystem/VirtualEngine.cs b/Windows/VirtualFileSystem/VirtualEngine.cs new file mode 100644 index 0000000..c45ca9b --- /dev/null +++ b/Windows/VirtualFileSystem/VirtualEngine.cs @@ -0,0 +1,126 @@ +using System.IO; +using System.Threading.Tasks; +using log4net; + +using ITHit.FileSystem; +using ITHit.FileSystem.Windows; + +using ITHit.FileSystem.Samples.Common.Windows; + + +namespace VirtualFileSystem +{ + + /// + public class VirtualEngine : EngineWindows + { + /// + /// Logger. + /// + private ILogger logger; + + /// + /// Monitors changes in the remote storage, notifies the client and updates the user file system. + /// + internal RemoteStorageMonitor RemoteStorageMonitor; + + /// + /// Creates a vitual file system Engine. + /// + /// A license string. + /// + /// A root folder of your user file system. + /// Your file system tree will be located under this folder. + /// + /// Logger. + public VirtualEngine(string license, string userFileSystemRootPath, ILog log) : base(license, userFileSystemRootPath) + { + logger = new Logger("File System Engine", log); + + // We want our file system to run regardless of any errors. + // If any request to file system fails in user code or in Engine itself we continue processing. + ThrowExceptions = false; + + StateChanged += Engine_StateChanged; + Error += Engine_Error; + Message += Engine_Message; + + string remoteStorageRootPath = Mapping.MapPath(userFileSystemRootPath); + RemoteStorageMonitor = new RemoteStorageMonitor(remoteStorageRootPath, this, log); + } + + /// + public override async Task GetFileSystemItemAsync(string path, FileSystemItemType itemType) + { + // When a file or folder is deleted, the item may be already + // deleted in user file system when this method is called + // The Engine calls IFile.CloseAsync() and IFileSystemItem.DeleteCompletionAsync() methods in this case. + + // On macOS there is no access to the local file system. + // You should NOT try to determine item type or read local files/folders on macOS. + + + if (itemType == FileSystemItemType.File) + { + return new VirtualFile(path, this); + } + else + { + return new VirtualFolder(path, this); + } + } + + /// + public override async Task StartAsync() + { + await base.StartAsync(); + RemoteStorageMonitor.Start(); + } + + public override async Task StopAsync() + { + await base.StopAsync(); + RemoteStorageMonitor.Stop(); + } + + private void Engine_Message(IEngine sender, EngineMessageEventArgs e) + { + logger.LogMessage(e.Message, e.SourcePath, e.TargetPath); + } + + private void Engine_Error(IEngine sender, EngineErrorEventArgs e) + { + logger.LogError(e.Message, e.SourcePath, e.TargetPath, e.Exception); + } + + /// + /// Show status change. + /// + /// Engine + /// Contains new and old Engine state. + private void Engine_StateChanged(Engine engine, EngineWindows.StateChangeEventArgs e) + { + engine.LogMessage($"{e.NewState}"); + } + + + 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/Windows/VirtualFileSystem/VirtualFile.cs b/Windows/VirtualFileSystem/VirtualFile.cs new file mode 100644 index 0000000..46fd80c --- /dev/null +++ b/Windows/VirtualFileSystem/VirtualFile.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using ITHit.FileSystem; +using ITHit.FileSystem.Samples.Common; +using ITHit.FileSystem.Samples.Common.Windows; + + +namespace VirtualFileSystem +{ + /// + public class VirtualFile : VirtualFileSystemItem, IFile + { + + /// + /// Creates instance of this class. + /// + /// File path in the user file system. + /// Logger. + public VirtualFile(string path, ILogger logger) : base(path, logger) + { + + } + + /// + public async Task OpenAsync(IOperationContext operationContext, IResultContext context) + { + Logger.LogMessage($"{nameof(IFile)}.{nameof(OpenAsync)}()", UserFileSystemPath); + } + + + /// + public async Task CloseAsync(IOperationContext operationContext, IResultContext context) + { + // Here, if the file in the user file system is modified (not in-sync), you will send the file content, + // creation time, modification time and attributes to the remote storage. + // Typically you will also send the ETag, to make sure the changes on the server, if any, are not overwritten. + + Logger.LogMessage($"{nameof(IFile)}.{nameof(CloseAsync)}()", UserFileSystemPath); + } + + + + /// + public async Task ReadAsync(Stream output, long offset, long length, ITransferDataOperationContext operationContext, 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. + + Logger.LogMessage($"{nameof(IFile)}.{nameof(ReadAsync)}({offset}, {length})", UserFileSystemPath); + + SimulateNetworkDelay(length, resultContext); + + await using (FileStream stream = System.IO.File.OpenRead(RemoteStoragePath)) + { + stream.Seek(offset, SeekOrigin.Begin); + const int bufferSize = 0x500000; // 5Mb. Buffer size must be multiple of 4096 bytes for optimal performance. + await stream.CopyToAsync(output, bufferSize, length); + } + } + + + /// + public async Task ValidateDataAsync(long offset, long length, IValidateDataOperationContext operationContext, IValidateDataResultContext resultContext) + { + // This method has a 60 sec timeout. + // To process longer requests and reset the timout timer call IContextWindows.ReportProgress() method. + + Logger.LogMessage($"{nameof(IFile)}.{nameof(ValidateDataAsync)}({offset}, {length})", UserFileSystemPath); + + //SimulateNetworkDelay(length, resultContext); + + bool isValid = true; + + resultContext.ReturnValidationResult(offset, length, isValid); + } + + /// + public async Task WriteAsync(IFileMetadata fileMetadata, Stream content = null) + { + Logger.LogMessage($"{nameof(IFile)}.{nameof(WriteAsync)}()", UserFileSystemPath); + + FileInfo remoteStorageItem = new FileInfo(RemoteStoragePath); + + if (content != null) + { + // Upload remote storage file content. + await using (FileStream remoteStorageStream = remoteStorageItem.Open(FileMode.Open, FileAccess.Write, FileShare.Delete)) + { + await content.CopyToAsync(remoteStorageStream); + remoteStorageStream.SetLength(content.Length); + } + } + + // Update remote storage file metadata. + remoteStorageItem.Attributes = fileMetadata.Attributes; + remoteStorageItem.CreationTimeUtc = fileMetadata.CreationTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = fileMetadata.LastWriteTime.UtcDateTime; + remoteStorageItem.LastAccessTimeUtc = fileMetadata.LastAccessTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = fileMetadata.LastWriteTime.UtcDateTime; + + } + } +} diff --git a/Windows/VirtualFileSystem/VirtualFileSystem.csproj b/Windows/VirtualFileSystem/VirtualFileSystem.csproj new file mode 100644 index 0000000..6b26fde --- /dev/null +++ b/Windows/VirtualFileSystem/VirtualFileSystem.csproj @@ -0,0 +1,53 @@ + + + Exe + netcoreapp3.1 + 10.0.19041.0 + IT Hit LTD. + IT Hit LTD. + Virtual File System + IT Hit LTD. + AnyCPU + A simple virtual file system project in .NET with synchronization support, on-demand loading, upload and download progress. It synchronizes files and folders both from remote storage to the user file system and from the user file system to remote storage. + +To simulate the remote storage, this sample is using a folder in the local file system on the same machine. You can use this project as a quick starting point for creating a simple virtual file system. + + +This project does not support ETags, locking, Microsoft Office documents editing and custom columns in Windows File Manager. For the above features see the Virtual Drive sample project. + + + false + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + PreserveNewest + + + Always + + + \ No newline at end of file diff --git a/Windows/VirtualFileSystem/VirtualFileSystemItem.cs b/Windows/VirtualFileSystem/VirtualFileSystemItem.cs new file mode 100644 index 0000000..58d0865 --- /dev/null +++ b/Windows/VirtualFileSystem/VirtualFileSystemItem.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using ITHit.FileSystem; +using ITHit.FileSystem.Samples.Common.Windows; + + +namespace VirtualFileSystem +{ + /// + public abstract class VirtualFileSystemItem : IFileSystemItem + { + /// + /// File or folder path in the user file system. + /// + protected readonly string UserFileSystemPath; + + /// + /// Path of this file or folder in the remote storage. + /// + protected readonly string RemoteStoragePath; + + /// + /// Logger. + /// + protected readonly ILogger Logger; + + /// + /// Creates instance of this class. + /// + /// File or folder path in the user file system. + /// Logger. + public VirtualFileSystemItem(string userFileSystemPath, ILogger logger) + { + if (string.IsNullOrEmpty(userFileSystemPath)) + { + throw new ArgumentNullException(nameof(userFileSystemPath)); + } + Logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + UserFileSystemPath = userFileSystemPath; + RemoteStoragePath = Mapping.MapPath(userFileSystemPath); + } + + + /// + public async Task MoveToAsync(string userFileSystemNewPath, IOperationContext operationContext, IConfirmationResultContext resultContext) + { + string userFileSystemOldPath = this.UserFileSystemPath; + Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(MoveToAsync)}()", userFileSystemOldPath, userFileSystemNewPath); + + string remoteStorageOldPath = RemoteStoragePath; + string remoteStorageNewPath = Mapping.MapPath(userFileSystemNewPath); + + FileSystemInfo remoteStorageOldItem = FsPath.GetFileSystemItem(remoteStorageOldPath); + if (remoteStorageOldItem != null) + { + if (remoteStorageOldItem is FileInfo) + { + (remoteStorageOldItem as FileInfo).MoveTo(remoteStorageNewPath, true); + } + else + { + (remoteStorageOldItem as DirectoryInfo).MoveTo(remoteStorageNewPath); + } + Logger.LogMessage("Moved item in remote storage succesefully", userFileSystemOldPath, userFileSystemNewPath); + } + } + + + /// + public async Task MoveToCompletionAsync(IMoveCompletionContext moveCompletionContext, IResultContext resultContext) + { + string userFileSystemNewPath = this.UserFileSystemPath; + string userFileSystemOldPath = moveCompletionContext.SourcePath; + Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(MoveToCompletionAsync)}()", userFileSystemOldPath, userFileSystemNewPath); + } + + + /// + public async Task DeleteAsync(IOperationContext operationContext, IConfirmationResultContext resultContext) + { + Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(DeleteAsync)}()", this.UserFileSystemPath); + } + + /// + public async Task DeleteCompletionAsync(IOperationContext operationContext, IResultContext resultContext) + { + // On Windows, for move with overwrite to function properly for folders, + // the deletion of the folder in the remote storage must be done in DeleteCompletionAsync() + // Otherwise the folder will be deleted before files in it can be moved. + + Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(DeleteCompletionAsync)}()", this.UserFileSystemPath); + + FileSystemInfo remoteStorageItem = FsPath.GetFileSystemItem(RemoteStoragePath); + if (remoteStorageItem != null) + { + if (remoteStorageItem is FileInfo) + { + remoteStorageItem.Delete(); + } + else + { + (remoteStorageItem as DirectoryInfo).Delete(true); + } + Logger.LogMessage("Deleted item in remote storage succesefully", UserFileSystemPath); + } + } + + + /// + public Task GetMetadataAsync() + { + // Return IFileMetadata for a file, IFolderMetadata for a folder. + throw new NotImplementedException(); + } + + /// + /// Simulates network delays and reports file transfer progress for demo purposes. + /// + /// Length of file. + /// Context to report progress to. + protected void SimulateNetworkDelay(long fileLength, IResultContext resultContext) + { + if (Program.Settings.NetworkSimulationDelayMs > 0) + { + int numProgressResults = 5; + for (int i = 0; i < numProgressResults; i++) + { + resultContext.ReportProgress(fileLength, i * fileLength / numProgressResults); + Thread.Sleep(Program.Settings.NetworkSimulationDelayMs); + } + } + } + } +} diff --git a/Windows/VirtualFileSystem/VirtualFolder.cs b/Windows/VirtualFileSystem/VirtualFolder.cs new file mode 100644 index 0000000..9676bb9 --- /dev/null +++ b/Windows/VirtualFileSystem/VirtualFolder.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Enumeration; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using ITHit.FileSystem; +using ITHit.FileSystem.Windows; +using ITHit.FileSystem.Samples.Common.Windows; + +namespace VirtualFileSystem +{ + + /// + public class VirtualFolder : VirtualFileSystemItem, IFolder + { + /// + /// Creates instance of this class. + /// + /// Folder path in the user file system. + /// Logger. + public VirtualFolder(string path, ILogger logger) : base(path, logger) + { + + } + + /// + public async Task CreateFileAsync(IFileMetadata fileMetadata, Stream content = null) + { + Logger.LogMessage($"{nameof(IFolder)}.{nameof(CreateFileAsync)}()", Path.Combine(UserFileSystemPath, fileMetadata.Name)); + + FileInfo remoteStorageItem = new FileInfo(Path.Combine(RemoteStoragePath, fileMetadata.Name)); + + // Upload remote storage file content. + await using (FileStream remoteStorageStream = remoteStorageItem.Open(FileMode.CreateNew, FileAccess.Write, FileShare.Delete)) + { + if (content != null) + { + await content.CopyToAsync(remoteStorageStream); + remoteStorageStream.SetLength(content.Length); + } + } + + // Update remote storage file metadata. + remoteStorageItem.Attributes = fileMetadata.Attributes; + remoteStorageItem.CreationTimeUtc = fileMetadata.CreationTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = fileMetadata.LastWriteTime.UtcDateTime; + remoteStorageItem.LastAccessTimeUtc = fileMetadata.LastAccessTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = fileMetadata.LastWriteTime.UtcDateTime; + } + + /// + public async Task CreateFolderAsync(IFolderMetadata folderMetadata) + { + Logger.LogMessage($"{nameof(IFolder)}.{nameof(CreateFolderAsync)}()", Path.Combine(UserFileSystemPath, folderMetadata.Name)); + + DirectoryInfo remoteStorageItem = new DirectoryInfo(Path.Combine(RemoteStoragePath, folderMetadata.Name)); + remoteStorageItem.Create(); + + // Update remote storage folder metadata. + remoteStorageItem.Attributes = folderMetadata.Attributes; + remoteStorageItem.CreationTimeUtc = folderMetadata.CreationTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = folderMetadata.LastWriteTime.UtcDateTime; + remoteStorageItem.LastAccessTimeUtc = folderMetadata.LastAccessTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = folderMetadata.LastWriteTime.UtcDateTime; + } + + /// + public async Task GetChildrenAsync(string pattern, IOperationContext operationContext, IFolderListingResultContext resultContext) + { + // This method has a 60 sec timeout. + // To process longer requests and reset the timout timer call one of the following: + // - resultContext.ReturnChildren() method. + // - resultContext.ReportProgress() method. + + Logger.LogMessage($"{nameof(IFolder)}.{nameof(GetChildrenAsync)}({pattern})", UserFileSystemPath); + + IEnumerable remoteStorageChildren = new DirectoryInfo(RemoteStoragePath).EnumerateFileSystemInfos(pattern); + + List userFileSystemChildren = new List(); + foreach (FileSystemInfo remoteStorageItem in remoteStorageChildren) + { + IFileSystemItemMetadata itemInfo = Mapping.GetUserFileSysteItemMetadata(remoteStorageItem); + userFileSystemChildren.Add(itemInfo); + + string userFileSystemItemPath = Path.Combine(UserFileSystemPath, itemInfo.Name); + + // Filtering existing files/folders. This is only required to avoid extra errors in the log. + if (!FsPath.Exists(userFileSystemItemPath)) + { + Logger.LogMessage("Creating", userFileSystemItemPath); + userFileSystemChildren.Add(itemInfo); + } + } + + // To signal that the children enumeration is completed + // always call ReturnChildren(), even if the folder is empty. + resultContext.ReturnChildren(userFileSystemChildren.ToArray(), userFileSystemChildren.Count()); + } + + /// + public async Task WriteAsync(IFolderMetadata folderMetadata) + { + Logger.LogMessage($"{nameof(IFolder)}.{nameof(WriteAsync)}()", UserFileSystemPath); + + DirectoryInfo remoteStorageItem = new DirectoryInfo(RemoteStoragePath); + + // Update remote storage folder metadata. + remoteStorageItem.Attributes = folderMetadata.Attributes; + remoteStorageItem.CreationTimeUtc = folderMetadata.CreationTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = folderMetadata.LastWriteTime.UtcDateTime; + remoteStorageItem.LastAccessTimeUtc = folderMetadata.LastAccessTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = folderMetadata.LastWriteTime.UtcDateTime; + } + } + +} diff --git a/VirtualFileSystem/appsettings.json b/Windows/VirtualFileSystem/appsettings.json similarity index 97% rename from VirtualFileSystem/appsettings.json rename to Windows/VirtualFileSystem/appsettings.json index 9be6042..9203271 100644 --- a/VirtualFileSystem/appsettings.json +++ b/Windows/VirtualFileSystem/appsettings.json @@ -2,7 +2,7 @@ // Unique ID of this application. // If you must to run more than one instance of this application side-by-side on same client machine // (aka Corporate Drive and Personal Drive) set unique ID for each instance. - "AppID": "VFSDrive", + "AppID": "VirtualFileSystem", // License to activate the IT Hit User File System Engine. If no license is specified the Engine will be activated // automatically via internet and will function for 5 days. The Engine will stop working after that. diff --git a/WebDAVDrive/log4net.config b/Windows/VirtualFileSystem/log4net.config similarity index 97% rename from WebDAVDrive/log4net.config rename to Windows/VirtualFileSystem/log4net.config index 51400f4..69c0064 100644 --- a/WebDAVDrive/log4net.config +++ b/Windows/VirtualFileSystem/log4net.config @@ -6,6 +6,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/VirtualFileSystemMac/FileProviderExtension/ConsoleLogger.cs b/macOS/FileProviderExtension/ConsoleLogger.cs similarity index 100% rename from VirtualFileSystemMac/FileProviderExtension/ConsoleLogger.cs rename to macOS/FileProviderExtension/ConsoleLogger.cs diff --git a/VirtualFileSystemMac/FileProviderExtension/Entitlements.plist b/macOS/FileProviderExtension/Entitlements.plist similarity index 100% rename from VirtualFileSystemMac/FileProviderExtension/Entitlements.plist rename to macOS/FileProviderExtension/Entitlements.plist diff --git a/macOS/FileProviderExtension/Extensions/StreamExtensions.cs b/macOS/FileProviderExtension/Extensions/StreamExtensions.cs new file mode 100644 index 0000000..2dfad04 --- /dev/null +++ b/macOS/FileProviderExtension/Extensions/StreamExtensions.cs @@ -0,0 +1,28 @@ +using System; +using System.IO; +using System.Threading.Tasks; + +namespace FileProviderExtension.Extensions +{ + public static class StreamExtensions + { + /// + /// Asynchronously copies specified number of bytes from current stream to destination stream, using a specified buffer size. + /// + /// Source stream. + /// The stream to which the contents of the current file stream will be copied. + /// The size, in bytes, of the buffer. This value must be greater than zero. + /// Number of bytes to copy. + public static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, long count) + { + byte[] buffer = new byte[bufferSize]; + int read; + while (count > 0 + && (read = await source.ReadAsync(buffer, 0, (int)Math.Min(buffer.LongLength, count))) > 0) + { + await destination.WriteAsync(buffer, 0, read); + count -= read; + } + } + } +} diff --git a/VirtualFileSystemMac/FileProviderExtension/FileProviderExtension.csproj b/macOS/FileProviderExtension/FileProviderExtension.csproj similarity index 87% rename from VirtualFileSystemMac/FileProviderExtension/FileProviderExtension.csproj rename to macOS/FileProviderExtension/FileProviderExtension.csproj index ad13ed9..a631caf 100644 --- a/VirtualFileSystemMac/FileProviderExtension/FileProviderExtension.csproj +++ b/macOS/FileProviderExtension/FileProviderExtension.csproj @@ -10,7 +10,7 @@ FileProviderExtension v4.8 Resources - 2.5.5091.0 + 3.0.6973.0 true @@ -63,14 +63,16 @@ - ..\packages\ITHit.FileSystem.2.5.5091\lib\netstandard2.0\ITHit.FileSystem.dll + ..\packages\ITHit.FileSystem.3.0.6973-Beta\lib\netstandard2.0\ITHit.FileSystem.dll + - ..\packages\ITHit.FileSystem.Mac.2.5.5091-Alpha\lib\xamarinmac20\ITHit.FileSystem.Mac.dll + ..\packages\ITHit.FileSystem.Mac.3.0.6973-Beta\lib\xamarinmac20\ITHit.FileSystem.Mac.dll + @@ -78,13 +80,14 @@ - - - - + + + + + diff --git a/VirtualFileSystemMac/FileProviderExtension/Info.plist b/macOS/FileProviderExtension/Info.plist similarity index 97% rename from VirtualFileSystemMac/FileProviderExtension/Info.plist rename to macOS/FileProviderExtension/Info.plist index aa85175..a04400c 100644 --- a/VirtualFileSystemMac/FileProviderExtension/Info.plist +++ b/macOS/FileProviderExtension/Info.plist @@ -31,7 +31,7 @@ NSExtensionPointIdentifier com.apple.fileprovider-nonui NSExtensionPrincipalClass - VfsEngine + VirtualEngine NSExtensionFileProviderSupportsEnumeration diff --git a/VirtualFileSystemMac/FileProviderExtension/Mapping.cs b/macOS/FileProviderExtension/Mapping.cs similarity index 56% rename from VirtualFileSystemMac/FileProviderExtension/Mapping.cs rename to macOS/FileProviderExtension/Mapping.cs index ec2c690..7b411ea 100644 --- a/VirtualFileSystemMac/FileProviderExtension/Mapping.cs +++ b/macOS/FileProviderExtension/Mapping.cs @@ -21,8 +21,7 @@ public static string MapPath(string userFileSystemPath) string relativePath = userFileSystemPath.TrimEnd(Path.DirectorySeparatorChar).Substring( AppGroupSettings.GetUserRootPath().TrimEnd(Path.DirectorySeparatorChar).Length); - string path = $"{AppGroupSettings.GetRemoteRootPath().TrimEnd(Path.DirectorySeparatorChar)}{relativePath}"; - return path; + return $"{AppGroupSettings.GetRemoteRootPath().TrimEnd(Path.DirectorySeparatorChar)}{relativePath}"; } /// @@ -36,8 +35,7 @@ public static string ReverseMapPath(string remoteStorageUri) string relativePath = remoteStorageUri.TrimEnd(Path.DirectorySeparatorChar).Substring( AppGroupSettings.GetRemoteRootPath().TrimEnd(Path.DirectorySeparatorChar).Length); - string path = $"{AppGroupSettings.GetUserRootPath().TrimEnd(Path.DirectorySeparatorChar)}{relativePath}"; - return path; + return $"{AppGroupSettings.GetUserRootPath().TrimEnd(Path.DirectorySeparatorChar)}{relativePath}"; } /// @@ -45,45 +43,27 @@ public static string ReverseMapPath(string remoteStorageUri) /// /// Remote storage item info. /// User file system item info. - public static IFileSystemItemMetadata GetUserFileSysteItemBasicInfo(FileSystemInfo remoteStorageItem, ILogger logger) + public static IFileSystemItemMetadata GetUserFileSysteItemMetadata(FileSystemInfo remoteStorageItem) { - VfsFileSystemItem userFileSystemItem; + IFileSystemItemMetadata userFileSystemItem; if (remoteStorageItem is FileInfo) { - FileInfo remoteStorageFile = (FileInfo)remoteStorageItem; - userFileSystemItem = new VfsFile(remoteStorageFile.FullName, remoteStorageFile.Attributes, remoteStorageFile.CreationTime, - remoteStorageFile.LastWriteTime, remoteStorageFile.LastAccessTime, remoteStorageFile.Length, logger); + userFileSystemItem = new FileMetadata(); + ((FileMetadata)userFileSystemItem).Length = ((FileInfo)remoteStorageItem).Length; } else { - userFileSystemItem = new VfsFolder(remoteStorageItem.FullName, remoteStorageItem.Attributes, remoteStorageItem.CreationTime, - remoteStorageItem.LastWriteTime, remoteStorageItem.LastAccessTime, logger); + userFileSystemItem = new FolderMetadata(); } - userFileSystemItem.Name = remoteStorageItem.FullName; + userFileSystemItem.Name = remoteStorageItem.Name; userFileSystemItem.Attributes = remoteStorageItem.Attributes; userFileSystemItem.CreationTime = remoteStorageItem.CreationTime; userFileSystemItem.LastWriteTime = remoteStorageItem.LastWriteTime; userFileSystemItem.LastAccessTime = remoteStorageItem.LastAccessTime; userFileSystemItem.ChangeTime = remoteStorageItem.LastWriteTime; - // You will 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. - // - // In this sample, for the sake of simplicity, we use file last write time instead of ETag. - userFileSystemItem.ETag = remoteStorageItem.LastWriteTime.ToBinary().ToString(); - - // 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. - userFileSystemItem.LockedByAnotherUser = (remoteStorageItem.Attributes & FileAttributes.ReadOnly) != 0; - - if (remoteStorageItem is FileInfo) - { - ((VfsFile)userFileSystemItem).Length = ((FileInfo)remoteStorageItem).Length; - }; - return userFileSystemItem; } } diff --git a/VirtualFileSystemMac/FileProviderExtension/RemoteStorageMonitor.cs b/macOS/FileProviderExtension/RemoteStorageMonitor.cs similarity index 81% rename from VirtualFileSystemMac/FileProviderExtension/RemoteStorageMonitor.cs rename to macOS/FileProviderExtension/RemoteStorageMonitor.cs index 56ae147..8676752 100644 --- a/VirtualFileSystemMac/FileProviderExtension/RemoteStorageMonitor.cs +++ b/macOS/FileProviderExtension/RemoteStorageMonitor.cs @@ -75,11 +75,31 @@ public void Stop() private void RenamedAsync(object sender, RenamedEventArgs e) { logger.LogMessage(e.ChangeType.ToString(), e.FullPath); + + string userFileSystemParentPath = Path.GetDirectoryName(e.FullPath); + logger.LogMessage(e.ChangeType.ToString(), Mapping.ReverseMapPath(userFileSystemParentPath)); + + fileProviderManager.SignalEnumerator(Mapping.ReverseMapPath(userFileSystemParentPath), error => { + if (error != null) + { + logger.LogError(error.Description); + } + }); + } private void DeletedAsync(object sender, FileSystemEventArgs e) { logger.LogMessage(e.ChangeType.ToString(), e.FullPath); + string userFileSystemParentPath = Path.GetDirectoryName(e.FullPath); + logger.LogMessage(e.ChangeType.ToString(), Mapping.ReverseMapPath(userFileSystemParentPath)); + + fileProviderManager.SignalEnumerator(Mapping.ReverseMapPath(userFileSystemParentPath), error => { + if (error != null) + { + logger.LogError(error.Description); + } + }); } private void CreatedAsync(object sender, FileSystemEventArgs e) @@ -87,8 +107,8 @@ private void CreatedAsync(object sender, FileSystemEventArgs e) logger.LogMessage(e.ChangeType.ToString(), e.FullPath); string userFileSystemParentPath = Path.GetDirectoryName(e.FullPath); logger.LogMessage(e.ChangeType.ToString(), Mapping.ReverseMapPath(userFileSystemParentPath)); - - fileProviderManager.SignalEnumerator(/*NSFileProviderItemIdentifier.RootContainer*/Mapping.ReverseMapPath(userFileSystemParentPath), error => { + + fileProviderManager.SignalEnumerator(Mapping.ReverseMapPath(userFileSystemParentPath), error => { if (error != null) { logger.LogError(error.Description); diff --git a/VirtualFileSystemMac/FileProviderExtension/VfsEngine.cs b/macOS/FileProviderExtension/VirtualEngine.cs similarity index 74% rename from VirtualFileSystemMac/FileProviderExtension/VfsEngine.cs rename to macOS/FileProviderExtension/VirtualEngine.cs index 4a3cc3e..30252ca 100644 --- a/VirtualFileSystemMac/FileProviderExtension/VfsEngine.cs +++ b/macOS/FileProviderExtension/VirtualEngine.cs @@ -9,8 +9,8 @@ namespace FileProviderExtension { - [Register(nameof(VfsEngine))] - public class VfsEngine : EngineMac + [Register(nameof(VirtualEngine))] + public class VirtualEngine : EngineMac { /// /// instance. @@ -19,7 +19,7 @@ public class VfsEngine : EngineMac private RemoteStorageMonitor remoteStorageMonitor; [Export("initWithDomain:")] - public VfsEngine(NSFileProviderDomain domain) + public VirtualEngine(NSFileProviderDomain domain) : base(domain) { License = AppGroupSettings.GetLicense(); @@ -29,19 +29,18 @@ public VfsEngine(NSFileProviderDomain domain) remoteStorageMonitor.Start(); } - public override async Task GetFileSystemItemAsync(string path) + public override async Task GetFileSystemItemAsync(string path, FileSystemItemType itemType) { string remotePath = Mapping.MapPath(path); + logger.LogMessage($"{nameof(IEngine)}.{nameof(GetFileSystemItemAsync)}()", path, remotePath); if (File.Exists(remotePath)) { - FileInfo fileInfo = new FileInfo(remotePath); - return new VfsFile(Mapping.ReverseMapPath(fileInfo.FullName), fileInfo.Attributes, fileInfo.CreationTime, fileInfo.LastWriteTime, fileInfo.LastAccessTime, fileInfo.Length, logger); + return new VirtualFile(path, this); } else if (Directory.Exists(remotePath)) { - DirectoryInfo dirInfo = new DirectoryInfo(remotePath); - return new VfsFolder(Mapping.ReverseMapPath(dirInfo.FullName), dirInfo.Attributes, dirInfo.CreationTime, dirInfo.LastWriteTime, dirInfo.LastAccessTime, logger); + return new VirtualFolder(path, this); } return null; diff --git a/macOS/FileProviderExtension/VirtualFile.cs b/macOS/FileProviderExtension/VirtualFile.cs new file mode 100644 index 0000000..d82bc64 --- /dev/null +++ b/macOS/FileProviderExtension/VirtualFile.cs @@ -0,0 +1,88 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using FileProviderExtension.Extensions; +using ITHit.FileSystem; +using ITHit.FileSystem.Mac; +using VirtualFilesystemCommon; + +namespace FileProviderExtension +{ + /// + public class VirtualFile : VirtualFileSystemItem, IFile + { + /// + /// Creates instance of this class. + /// + /// File path in the user file system. + /// Logger. + public VirtualFile(string path, ILogger logger) : base(path, logger) + { + + } + + /// + public async Task OpenAsync(IOperationContext operationContext, IResultContext context) + { + Logger.LogMessage($"{nameof(IFile)}.{nameof(OpenAsync)}()", UserFileSystemPath); + } + + + /// + public async Task CloseAsync(IOperationContext operationContext, IResultContext context) + { + Logger.LogMessage($"{nameof(IFile)}.{nameof(CloseAsync)}()", UserFileSystemPath); + } + + + + /// + public async Task ReadAsync(Stream output, long offset, long length, ITransferDataOperationContext operationContext, ITransferDataResultContext resultContext) + { + + Logger.LogMessage($"{nameof(IFile)}.{nameof(ReadAsync)}({offset}, {length})", UserFileSystemPath); + + SimulateNetworkDelay(length, resultContext); + + await using (FileStream stream = System.IO.File.OpenRead(RemoteStoragePath)) + { + stream.Seek(offset, SeekOrigin.Begin); + const int bufferSize = 0x500000; // 5Mb. Buffer size must be multiple of 4096 bytes for optimal performance. + await stream.CopyToAsync(output, bufferSize, length); + } + } + + + /// + public async Task ValidateDataAsync(long offset, long length, IValidateDataOperationContext operationContext, IValidateDataResultContext resultContext) + { + + Logger.LogMessage($"{nameof(IFile)}.{nameof(ValidateDataAsync)}({offset}, {length})", UserFileSystemPath); + } + + /// + public async Task WriteAsync(IFileMetadata fileMetadata, Stream content = null) + { + Logger.LogMessage($"{nameof(IFile)}.{nameof(WriteAsync)}()", UserFileSystemPath); + + FileInfo remoteStorageItem = new FileInfo(RemoteStoragePath); + + if (content != null) + { + // Upload remote storage file content. + await using (FileStream remoteStorageStream = remoteStorageItem.Open(FileMode.Open, FileAccess.Write, FileShare.Delete)) + { + await content.CopyToAsync(remoteStorageStream); + remoteStorageStream.SetLength(content.Length); + } + } + + // Update remote storage file metadata. + remoteStorageItem.Attributes = fileMetadata.Attributes; + remoteStorageItem.CreationTimeUtc = fileMetadata.CreationTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = fileMetadata.LastWriteTime.UtcDateTime; + remoteStorageItem.LastAccessTimeUtc = fileMetadata.LastAccessTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = fileMetadata.LastWriteTime.UtcDateTime; + } + } +} diff --git a/VirtualFileSystemMac/FileProviderExtension/VfsFileSystemItem.cs b/macOS/FileProviderExtension/VirtualFileSystemItem.cs similarity index 55% rename from VirtualFileSystemMac/FileProviderExtension/VfsFileSystemItem.cs rename to macOS/FileProviderExtension/VirtualFileSystemItem.cs index d7c3671..5027b6d 100644 --- a/VirtualFileSystemMac/FileProviderExtension/VfsFileSystemItem.cs +++ b/macOS/FileProviderExtension/VirtualFileSystemItem.cs @@ -8,9 +8,7 @@ namespace FileProviderExtension { - // In most cases you can use this class in your project without any changes. - /// - public abstract class VfsFileSystemItem : IFileSystemItem, IFileSystemItemMetadata + public abstract class VirtualFileSystemItem : IFileSystemItem { /// /// File or folder path in the user file system. @@ -20,119 +18,105 @@ public abstract class VfsFileSystemItem : IFileSystemItem, IFileSystemItemMetada /// /// Path of this file or folder in the remote storage. /// - protected string RemoteStoragePath; + protected readonly string RemoteStoragePath; /// /// Logger. /// protected readonly ILogger Logger; - /// - public string Name { get; set; } - - /// - public FileAttributes Attributes { get; set; } - - /// - public byte[] CustomData { get; set; } - - /// - public DateTimeOffset CreationTime { get; set; } - - /// - public DateTimeOffset LastWriteTime { get; set; } - - /// - public DateTimeOffset LastAccessTime { get; set; } - - /// - public DateTimeOffset ChangeTime { get; set; } - - /// - /// Server ETag. - /// - public string ETag { get; set; } - - /// - /// Indicates if the item is locked by another user in the remote storage. - /// - public bool LockedByAnotherUser { get; set; } - /// /// 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) + public VirtualFileSystemItem(string userFileSystemPath, ILogger logger) { if (string.IsNullOrEmpty(userFileSystemPath)) { - throw new ArgumentNullException("userFileSystemPath"); + throw new ArgumentNullException(nameof(userFileSystemPath)); } + Logger = logger ?? throw new ArgumentNullException(nameof(logger)); UserFileSystemPath = userFileSystemPath; RemoteStoragePath = Mapping.MapPath(userFileSystemPath); - Logger = logger; } /// public async Task MoveToAsync(string userFileSystemNewPath, IOperationContext operationContext, IConfirmationResultContext resultContext) { - Logger.LogMessage($"IFileSystemItem.MoveToAsync", UserFileSystemPath, userFileSystemNewPath); + string userFileSystemOldPath = this.UserFileSystemPath; + Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(MoveToAsync)}()", UserFileSystemPath, userFileSystemNewPath); string remoteStorageNewPath = Mapping.MapPath(userFileSystemNewPath); + string remoteStorageOldPath = RemoteStoragePath; if (File.Exists(RemoteStoragePath)) { new FileInfo(RemoteStoragePath).MoveTo(remoteStorageNewPath); } - else if(Directory.Exists(RemoteStoragePath)) + else if (Directory.Exists(RemoteStoragePath)) { new DirectoryInfo(RemoteStoragePath).MoveTo(remoteStorageNewPath); } + Logger.LogMessage("Moved item in remote storage succesefully", userFileSystemOldPath, userFileSystemNewPath); } + /// + public async Task MoveToCompletionAsync(IMoveCompletionContext moveCompletionContext, IResultContext resultContext) + { + throw new NotImplementedException(); + } + /// public async Task DeleteAsync(IOperationContext operationContext, IConfirmationResultContext resultContext) { - Logger.LogMessage($"IFileSystemItem.DeleteAsync", UserFileSystemPath); + Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(DeleteAsync)}()", UserFileSystemPath); if (File.Exists(RemoteStoragePath)) { File.Delete(RemoteStoragePath); } - else if(Directory.Exists(RemoteStoragePath)) + else if (Directory.Exists(RemoteStoragePath)) { Directory.Delete(RemoteStoragePath, true); } } - - /// - /// Simulates network delays and reports file transfer progress for demo purposes. - /// - /// Length of file. - /// Context to report progress to. - protected void SimulateNetworkDelay(long fileLength, IResultContext resultContext) + /// + public async Task DeleteCompletionAsync(IOperationContext operationContext, IResultContext resultContext) { throw new NotImplementedException(); } + - public Task MoveToCompletionAsync(IMoveCompletionContext moveCompletionContext, IResultContext resultContext) + /// + public async Task GetMetadataAsync() { - throw new NotImplementedException(); - } + // Return IFileMetadata for a file, IFolderMetadata for a folder. + + if (File.Exists(RemoteStoragePath)) + { + return Mapping.GetUserFileSysteItemMetadata(new FileInfo(RemoteStoragePath)); + } + else if (Directory.Exists(RemoteStoragePath)) + { + return Mapping.GetUserFileSysteItemMetadata(new DirectoryInfo(RemoteStoragePath)); + } - public Task DeleteCompletionAsync(IOperationContext operationContext, IResultContext resultContext) - { - throw new NotImplementedException(); + return null; } - public Task GetMetadataAsync() + /// + /// Simulates network delays and reports file transfer progress for demo purposes. + /// + /// Length of file. + /// Context to report progress to. + protected void SimulateNetworkDelay(long fileLength, IResultContext resultContext) { - throw new NotImplementedException(); + //Thread.Sleep(10000); } } } diff --git a/macOS/FileProviderExtension/VirtualFolder.cs b/macOS/FileProviderExtension/VirtualFolder.cs new file mode 100644 index 0000000..3e680fd --- /dev/null +++ b/macOS/FileProviderExtension/VirtualFolder.cs @@ -0,0 +1,122 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using System.Collections.Generic; +using ITHit.FileSystem; +using VirtualFilesystemCommon; +using ITHit.FileSystem.Mac; + +namespace FileProviderExtension +{ + + + + /// + public class VirtualFolder : VirtualFileSystemItem, IFolder + { + + + + + + + + + + + + /// + /// Creates instance of this class. + /// + /// Folder path in the user file system. + /// Logger. + public VirtualFolder(string path, ILogger logger) : base(path, logger) + { + + } + + + + /// + public async Task CreateFileAsync(IFileMetadata fileMetadata, Stream content = null) + { + Logger.LogMessage($"{nameof(IFolder)}.{nameof(CreateFileAsync)}()", Path.Combine(UserFileSystemPath, fileMetadata.Name)); + + FileInfo remoteStorageItem = new FileInfo(Path.Combine(RemoteStoragePath, fileMetadata.Name)); + + // Upload remote storage file content. + await using (FileStream remoteStorageStream = remoteStorageItem.Open(FileMode.CreateNew, FileAccess.Write, FileShare.Delete)) + { + if (content != null) + { + await content.CopyToAsync(remoteStorageStream); + remoteStorageStream.SetLength(content.Length); + } + } + + // Update remote storage file metadata. + remoteStorageItem.Attributes = fileMetadata.Attributes; + remoteStorageItem.CreationTimeUtc = fileMetadata.CreationTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = fileMetadata.LastWriteTime.UtcDateTime; + remoteStorageItem.LastAccessTimeUtc = fileMetadata.LastAccessTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = fileMetadata.LastWriteTime.UtcDateTime; + } + + + + /// + public async Task CreateFolderAsync(IFolderMetadata folderMetadata) + { + Logger.LogMessage($"{nameof(IFolder)}.{nameof(CreateFolderAsync)}()", Path.Combine(UserFileSystemPath, folderMetadata.Name)); + + DirectoryInfo remoteStorageItem = new DirectoryInfo(Path.Combine(RemoteStoragePath, folderMetadata.Name)); + remoteStorageItem.Create(); + + // Update remote storage folder metadata. + remoteStorageItem.Attributes = folderMetadata.Attributes; + remoteStorageItem.CreationTimeUtc = folderMetadata.CreationTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = folderMetadata.LastWriteTime.UtcDateTime; + remoteStorageItem.LastAccessTimeUtc = folderMetadata.LastAccessTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = folderMetadata.LastWriteTime.UtcDateTime; + } + + + + /// + public async Task GetChildrenAsync(string pattern, IOperationContext operationContext, IFolderListingResultContext resultContext) + { + Logger.LogMessage($"{nameof(IFolder)}.{nameof(GetChildrenAsync)}({pattern})", UserFileSystemPath); + + IEnumerable remoteStorageChildren = new DirectoryInfo(RemoteStoragePath).EnumerateFileSystemInfos(pattern); + + List userFileSystemChildren = new List(); + foreach (FileSystemInfo remoteStorageItem in remoteStorageChildren) + { + IFileSystemItemMetadata itemInfo = Mapping.GetUserFileSysteItemMetadata(remoteStorageItem); + userFileSystemChildren.Add(itemInfo); + } + + // To signal that the children enumeration is completed + // always call ReturnChildren(), even if the folder is empty. + resultContext.ReturnChildren(userFileSystemChildren.ToArray(), userFileSystemChildren.Count); + } + + + + /// + public async Task WriteAsync(IFolderMetadata folderMetadata) + { + Logger.LogMessage($"{nameof(IFolder)}.{nameof(WriteAsync)}()", UserFileSystemPath); + + DirectoryInfo remoteStorageItem = new DirectoryInfo(RemoteStoragePath); + + // Update remote storage folder metadata. + remoteStorageItem.Attributes = folderMetadata.Attributes; + remoteStorageItem.CreationTimeUtc = folderMetadata.CreationTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = folderMetadata.LastWriteTime.UtcDateTime; + remoteStorageItem.LastAccessTimeUtc = folderMetadata.LastAccessTime.UtcDateTime; + remoteStorageItem.LastWriteTimeUtc = folderMetadata.LastWriteTime.UtcDateTime; + } + } + +} diff --git a/macOS/FileProviderExtension/packages.config b/macOS/FileProviderExtension/packages.config new file mode 100644 index 0000000..4e162e6 --- /dev/null +++ b/macOS/FileProviderExtension/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/VirtualFileSystemMac/README.md b/macOS/README.md similarity index 97% rename from VirtualFileSystemMac/README.md rename to macOS/README.md index 3b9a050..a1e35a4 100644 --- a/VirtualFileSystemMac/README.md +++ b/macOS/README.md @@ -65,5 +65,5 @@

Virtual File System Mac in .NET/C#

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

Next Article:

-WebDAV Drive Sample in .NET, C# +Virtual Drive Sample in .NET, C# diff --git a/VirtualFileSystem/RemoteStorage/General.docx b/macOS/RemoteStorage/General.docx similarity index 100% rename from VirtualFileSystem/RemoteStorage/General.docx rename to macOS/RemoteStorage/General.docx diff --git a/VirtualFileSystem/RemoteStorage/Introduction.pptx b/macOS/RemoteStorage/Introduction.pptx similarity index 100% rename from VirtualFileSystem/RemoteStorage/Introduction.pptx rename to macOS/RemoteStorage/Introduction.pptx diff --git a/VirtualFileSystemMac/RemoteStorage/Library/Content.txt b/macOS/RemoteStorage/Library/Content.txt similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Library/Content.txt rename to macOS/RemoteStorage/Library/Content.txt diff --git a/VirtualFileSystemMac/RemoteStorage/Library/Overview.doc b/macOS/RemoteStorage/Library/Overview.doc similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Library/Overview.doc rename to macOS/RemoteStorage/Library/Overview.doc diff --git a/VirtualFileSystemMac/RemoteStorage/Library/Product.vsd b/macOS/RemoteStorage/Library/Product.vsd similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Library/Product.vsd rename to macOS/RemoteStorage/Library/Product.vsd diff --git a/VirtualFileSystemMac/RemoteStorage/Library/Project.doc b/macOS/RemoteStorage/Library/Project.doc similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Library/Project.doc rename to macOS/RemoteStorage/Library/Project.doc diff --git a/VirtualFileSystemMac/RemoteStorage/Library/Vision.doc b/macOS/RemoteStorage/Library/Vision.doc similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Library/Vision.doc rename to macOS/RemoteStorage/Library/Vision.doc diff --git a/VirtualFileSystemMac/RemoteStorage/Library/Vision.mpp b/macOS/RemoteStorage/Library/Vision.mpp similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Library/Vision.mpp rename to macOS/RemoteStorage/Library/Vision.mpp diff --git a/VirtualFileSystemMac/RemoteStorage/Notes.txt b/macOS/RemoteStorage/My Directory/Notes.txt similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Notes.txt rename to macOS/RemoteStorage/My Directory/Notes.txt diff --git a/macOS/RemoteStorage/Notes.txt b/macOS/RemoteStorage/Notes.txt new file mode 100644 index 0000000..45a4503 --- /dev/null +++ b/macOS/RemoteStorage/Notes.txt @@ -0,0 +1 @@ +My notes file text \ No newline at end of file diff --git a/macOS/RemoteStorage/Pictures/Arctic Ice.jpeg b/macOS/RemoteStorage/Pictures/Arctic Ice.jpeg new file mode 100644 index 0000000..aa7066e Binary files /dev/null and b/macOS/RemoteStorage/Pictures/Arctic Ice.jpeg differ diff --git a/macOS/RemoteStorage/Pictures/Arctic Sun.jpeg b/macOS/RemoteStorage/Pictures/Arctic Sun.jpeg new file mode 100644 index 0000000..19951f9 Binary files /dev/null and b/macOS/RemoteStorage/Pictures/Arctic Sun.jpeg differ diff --git a/macOS/RemoteStorage/Pictures/Autumn.jpeg b/macOS/RemoteStorage/Pictures/Autumn.jpeg new file mode 100644 index 0000000..64a19de Binary files /dev/null and b/macOS/RemoteStorage/Pictures/Autumn.jpeg differ diff --git a/macOS/RemoteStorage/Pictures/Beach.jpeg b/macOS/RemoteStorage/Pictures/Beach.jpeg new file mode 100644 index 0000000..6a52177 Binary files /dev/null and b/macOS/RemoteStorage/Pictures/Beach.jpeg differ diff --git a/macOS/RemoteStorage/Pictures/Boats.jpeg b/macOS/RemoteStorage/Pictures/Boats.jpeg new file mode 100644 index 0000000..da0d252 Binary files /dev/null and b/macOS/RemoteStorage/Pictures/Boats.jpeg differ diff --git a/macOS/RemoteStorage/Pictures/Cruise Ship.jpeg b/macOS/RemoteStorage/Pictures/Cruise Ship.jpeg new file mode 100644 index 0000000..75123bc Binary files /dev/null and b/macOS/RemoteStorage/Pictures/Cruise Ship.jpeg differ diff --git a/macOS/RemoteStorage/Pictures/Glacier.jpeg b/macOS/RemoteStorage/Pictures/Glacier.jpeg new file mode 100644 index 0000000..2302981 Binary files /dev/null and b/macOS/RemoteStorage/Pictures/Glacier.jpeg differ diff --git a/macOS/RemoteStorage/Pictures/Hotel.jpeg b/macOS/RemoteStorage/Pictures/Hotel.jpeg new file mode 100644 index 0000000..15f060f Binary files /dev/null and b/macOS/RemoteStorage/Pictures/Hotel.jpeg differ diff --git a/macOS/RemoteStorage/Pictures/Landing Pier.jpeg b/macOS/RemoteStorage/Pictures/Landing Pier.jpeg new file mode 100644 index 0000000..48218d5 Binary files /dev/null and b/macOS/RemoteStorage/Pictures/Landing Pier.jpeg differ diff --git a/macOS/RemoteStorage/Pictures/Orange in the Mountains.jpeg b/macOS/RemoteStorage/Pictures/Orange in the Mountains.jpeg new file mode 100644 index 0000000..295fb58 Binary files /dev/null and b/macOS/RemoteStorage/Pictures/Orange in the Mountains.jpeg differ diff --git a/macOS/RemoteStorage/Pictures/River.jpeg b/macOS/RemoteStorage/Pictures/River.jpeg new file mode 100644 index 0000000..b9846e0 Binary files /dev/null and b/macOS/RemoteStorage/Pictures/River.jpeg differ diff --git a/macOS/RemoteStorage/Pictures/Sunset.jpeg b/macOS/RemoteStorage/Pictures/Sunset.jpeg new file mode 100644 index 0000000..8d4f89b Binary files /dev/null and b/macOS/RemoteStorage/Pictures/Sunset.jpeg differ diff --git a/macOS/RemoteStorage/Pictures/Windsurfing.jpeg b/macOS/RemoteStorage/Pictures/Windsurfing.jpeg new file mode 100644 index 0000000..3bb0be1 Binary files /dev/null and b/macOS/RemoteStorage/Pictures/Windsurfing.jpeg differ diff --git a/macOS/RemoteStorage/Pictures/Yachts.jpeg b/macOS/RemoteStorage/Pictures/Yachts.jpeg new file mode 100644 index 0000000..9c68db3 Binary files /dev/null and b/macOS/RemoteStorage/Pictures/Yachts.jpeg differ diff --git a/macOS/RemoteStorage/Pictures/Yellow Tree.jpeg b/macOS/RemoteStorage/Pictures/Yellow Tree.jpeg new file mode 100644 index 0000000..4ce2882 Binary files /dev/null and b/macOS/RemoteStorage/Pictures/Yellow Tree.jpeg differ diff --git a/VirtualFileSystemMac/RemoteStorage/Products/General.doc b/macOS/RemoteStorage/Products/General.doc similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Products/General.doc rename to macOS/RemoteStorage/Products/General.doc diff --git a/macOS/RemoteStorage/Products/General.vsd b/macOS/RemoteStorage/Products/General.vsd new file mode 100644 index 0000000..e69de29 diff --git a/VirtualFileSystemMac/RemoteStorage/Products/Product.mpp b/macOS/RemoteStorage/Products/Product.mpp similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Products/Product.mpp rename to macOS/RemoteStorage/Products/Product.mpp diff --git a/VirtualFileSystemMac/RemoteStorage/Project.mpp b/macOS/RemoteStorage/Project.mpp similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Project.mpp rename to macOS/RemoteStorage/Project.mpp diff --git a/macOS/RemoteStorage/Project.pdf b/macOS/RemoteStorage/Project.pdf new file mode 100644 index 0000000..c82e775 Binary files /dev/null and b/macOS/RemoteStorage/Project.pdf differ diff --git a/macOS/RemoteStorage/Readme.txt b/macOS/RemoteStorage/Readme.txt new file mode 100644 index 0000000..a9492fa --- /dev/null +++ b/macOS/RemoteStorage/Readme.txt @@ -0,0 +1,2 @@ +This is a test file structure that simulates your virtual file system. +In your real-life application you will load all folder structure and documents from your remote server or storage. \ No newline at end of file diff --git a/VirtualFileSystemMac/RemoteStorage/Sales/Australia/Plan.doc b/macOS/RemoteStorage/Sales/Australia/Plan.doc similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Sales/Australia/Plan.doc rename to macOS/RemoteStorage/Sales/Australia/Plan.doc diff --git a/VirtualFileSystemMac/RemoteStorage/Sales/Australia/Prices.xls b/macOS/RemoteStorage/Sales/Australia/Prices.xls similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Sales/Australia/Prices.xls rename to macOS/RemoteStorage/Sales/Australia/Prices.xls diff --git a/VirtualFileSystemMac/RemoteStorage/Sales/Canada/Introduction.ppt b/macOS/RemoteStorage/Sales/Canada/Introduction.ppt similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Sales/Canada/Introduction.ppt rename to macOS/RemoteStorage/Sales/Canada/Introduction.ppt diff --git a/VirtualFileSystemMac/RemoteStorage/Sales/Canada/Prices.xls b/macOS/RemoteStorage/Sales/Canada/Prices.xls similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Sales/Canada/Prices.xls rename to macOS/RemoteStorage/Sales/Canada/Prices.xls diff --git a/VirtualFileSystemMac/RemoteStorage/Sales/Canada/Product.mpp b/macOS/RemoteStorage/Sales/Canada/Product.mpp similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Sales/Canada/Product.mpp rename to macOS/RemoteStorage/Sales/Canada/Product.mpp diff --git a/VirtualFileSystemMac/RemoteStorage/Sales/Canada/Stat.xls b/macOS/RemoteStorage/Sales/Canada/Stat.xls similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Sales/Canada/Stat.xls rename to macOS/RemoteStorage/Sales/Canada/Stat.xls diff --git a/VirtualFileSystemMac/RemoteStorage/Sales/Europe/Stat.xls b/macOS/RemoteStorage/Sales/Europe/Stat.xls similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Sales/Europe/Stat.xls rename to macOS/RemoteStorage/Sales/Europe/Stat.xls diff --git a/VirtualFileSystemMac/RemoteStorage/Sales/USA/Vision.mpp b/macOS/RemoteStorage/Sales/USA/Vision.mpp similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Sales/USA/Vision.mpp rename to macOS/RemoteStorage/Sales/USA/Vision.mpp diff --git a/macOS/RemoteStorage/Scheme.vsdx b/macOS/RemoteStorage/Scheme.vsdx new file mode 100644 index 0000000..e69de29 diff --git a/VirtualFileSystemMac/RemoteStorage/Stat.xlsx b/macOS/RemoteStorage/Stat.xlsx similarity index 100% rename from VirtualFileSystemMac/RemoteStorage/Stat.xlsx rename to macOS/RemoteStorage/Stat.xlsx diff --git a/VirtualFileSystemMac/VirtualFilesystemCommon/AppGroupSettings.cs b/macOS/VirtualFilesystemCommon/AppGroupSettings.cs similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemCommon/AppGroupSettings.cs rename to macOS/VirtualFilesystemCommon/AppGroupSettings.cs diff --git a/VirtualFileSystemMac/VirtualFilesystemCommon/Properties/AssemblyInfo.cs b/macOS/VirtualFilesystemCommon/Properties/AssemblyInfo.cs similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemCommon/Properties/AssemblyInfo.cs rename to macOS/VirtualFilesystemCommon/Properties/AssemblyInfo.cs diff --git a/VirtualFileSystemMac/VirtualFilesystemCommon/VirtualFilesystemCommon.csproj b/macOS/VirtualFilesystemCommon/VirtualFilesystemCommon.csproj similarity index 94% rename from VirtualFileSystemMac/VirtualFilesystemCommon/VirtualFilesystemCommon.csproj rename to macOS/VirtualFilesystemCommon/VirtualFilesystemCommon.csproj index 95ddfb6..cc51ce1 100644 --- a/VirtualFileSystemMac/VirtualFilesystemCommon/VirtualFilesystemCommon.csproj +++ b/macOS/VirtualFilesystemCommon/VirtualFilesystemCommon.csproj @@ -11,7 +11,7 @@ v2.0 Xamarin.Mac Resources - 2.5.5091.0 + 3.0.6973.0 true @@ -52,7 +52,7 @@ - ..\packages\ITHit.FileSystem.2.5.5091\lib\netstandard2.1\ITHit.FileSystem.dll + ..\packages\ITHit.FileSystem.3.0.6973-Beta\lib\netstandard2.0\ITHit.FileSystem.dll diff --git a/macOS/VirtualFilesystemCommon/packages.config b/macOS/VirtualFilesystemCommon/packages.config new file mode 100644 index 0000000..cccb588 --- /dev/null +++ b/macOS/VirtualFilesystemCommon/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/VirtualFileSystemMac/VirtualFilesystemMac.sln b/macOS/VirtualFilesystemMac.sln similarity index 98% rename from VirtualFileSystemMac/VirtualFilesystemMac.sln rename to macOS/VirtualFilesystemMac.sln index 29f4707..a4e6744 100644 --- a/VirtualFileSystemMac/VirtualFilesystemMac.sln +++ b/macOS/VirtualFilesystemMac.sln @@ -35,6 +35,6 @@ Global SolutionGuid = {EC725A74-F3CF-4F79-BEC5-4F6234C3687D} EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution - version = 2.5.5091.0 + version = 3.0.6973.0 EndGlobalSection EndGlobal diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/AppDelegate.cs b/macOS/VirtualFilesystemMacApp/AppDelegate.cs similarity index 98% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/AppDelegate.cs rename to macOS/VirtualFilesystemMacApp/AppDelegate.cs index 7624178..2efde6b 100644 --- a/VirtualFileSystemMac/VirtualFilesystemMacApp/AppDelegate.cs +++ b/macOS/VirtualFilesystemMacApp/AppDelegate.cs @@ -17,7 +17,7 @@ public AppDelegate() public override void DidFinishLaunching(NSNotification notification) { - LocalExtensionManager = new ExtensionManager("com.userfilesystem.vfs.app", "ITHitFS6"); + LocalExtensionManager = new ExtensionManager("com.userfilesystem.vfs.app", "ITHitFS"); NSMenu menu = new NSMenu(); diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-128.png b/macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-128.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-128.png rename to macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-128.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-128@2x.png b/macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-128@2x.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-128@2x.png rename to macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-128@2x.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-16.png b/macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-16.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-16.png rename to macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-16.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-16@2x.png b/macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-16@2x.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-16@2x.png rename to macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-16@2x.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-256.png b/macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-256.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-256.png rename to macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-256.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-256@2x.png b/macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-256@2x.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-256@2x.png rename to macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-256@2x.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-32.png b/macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-32.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-32.png rename to macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-32.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-32@2x.png b/macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-32@2x.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-32@2x.png rename to macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-32@2x.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-512.png b/macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-512.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-512.png rename to macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-512.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png b/macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png rename to macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/Contents.json rename to macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_128x128.png b/macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_128x128.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_128x128.png rename to macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_128x128.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png b/macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png rename to macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_16x16.png b/macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_16x16.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_16x16.png rename to macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_16x16.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png b/macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png rename to macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_256x256.png b/macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_256x256.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_256x256.png rename to macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_256x256.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png b/macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png rename to macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_32x32.png b/macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_32x32.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_32x32.png rename to macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_32x32.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png b/macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png rename to macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_512x512.png b/macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_512x512.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_512x512.png rename to macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_512x512.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png b/macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png rename to macOS/VirtualFilesystemMacApp/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/Contents.json b/macOS/VirtualFilesystemMacApp/Assets.xcassets/Contents.json similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Assets.xcassets/Contents.json rename to macOS/VirtualFilesystemMacApp/Assets.xcassets/Contents.json diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Entitlements.plist b/macOS/VirtualFilesystemMacApp/Entitlements.plist similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Entitlements.plist rename to macOS/VirtualFilesystemMacApp/Entitlements.plist diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/ExtensionManager.cs b/macOS/VirtualFilesystemMacApp/ExtensionManager.cs similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/ExtensionManager.cs rename to macOS/VirtualFilesystemMacApp/ExtensionManager.cs diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Info.plist b/macOS/VirtualFilesystemMacApp/Info.plist similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Info.plist rename to macOS/VirtualFilesystemMacApp/Info.plist diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Main.cs b/macOS/VirtualFilesystemMacApp/Main.cs similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Main.cs rename to macOS/VirtualFilesystemMacApp/Main.cs diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Main.storyboard b/macOS/VirtualFilesystemMacApp/Main.storyboard similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Main.storyboard rename to macOS/VirtualFilesystemMacApp/Main.storyboard diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_128x128.png b/macOS/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_128x128.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_128x128.png rename to macOS/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_128x128.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png b/macOS/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png rename to macOS/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_16x16.png b/macOS/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_16x16.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_16x16.png rename to macOS/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_16x16.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png b/macOS/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png rename to macOS/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_256x256.png b/macOS/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_256x256.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_256x256.png rename to macOS/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_256x256.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png b/macOS/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png rename to macOS/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_32x32.png b/macOS/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_32x32.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_32x32.png rename to macOS/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_32x32.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png b/macOS/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png rename to macOS/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_512x512.png b/macOS/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_512x512.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_512x512.png rename to macOS/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_512x512.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png b/macOS/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png rename to macOS/VirtualFilesystemMacApp/Resources/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/TrayIcon.png b/macOS/VirtualFilesystemMacApp/Resources/TrayIcon.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/TrayIcon.png rename to macOS/VirtualFilesystemMacApp/Resources/TrayIcon.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/TrayIcon@2x.png b/macOS/VirtualFilesystemMacApp/Resources/TrayIcon@2x.png similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/TrayIcon@2x.png rename to macOS/VirtualFilesystemMacApp/Resources/TrayIcon@2x.png diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/appsettings.json b/macOS/VirtualFilesystemMacApp/Resources/appsettings.json similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/Resources/appsettings.json rename to macOS/VirtualFilesystemMacApp/Resources/appsettings.json diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/ViewController.cs b/macOS/VirtualFilesystemMacApp/ViewController.cs similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/ViewController.cs rename to macOS/VirtualFilesystemMacApp/ViewController.cs diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/ViewController.designer.cs b/macOS/VirtualFilesystemMacApp/ViewController.designer.cs similarity index 100% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/ViewController.designer.cs rename to macOS/VirtualFilesystemMacApp/ViewController.designer.cs diff --git a/VirtualFileSystemMac/VirtualFilesystemMacApp/VirtualFilesystemMacApp.csproj b/macOS/VirtualFilesystemMacApp/VirtualFilesystemMacApp.csproj similarity index 99% rename from VirtualFileSystemMac/VirtualFilesystemMacApp/VirtualFilesystemMacApp.csproj rename to macOS/VirtualFilesystemMacApp/VirtualFilesystemMacApp.csproj index ab7867f..5dac65d 100644 --- a/VirtualFileSystemMac/VirtualFilesystemMacApp/VirtualFilesystemMacApp.csproj +++ b/macOS/VirtualFilesystemMacApp/VirtualFilesystemMacApp.csproj @@ -10,7 +10,7 @@ Virtual Filesystem v4.8 Resources - 2.5.5091.0 + 3.0.6973.0 true