/// <summary> /// Called when a file or folder is renamed in the remote storage. /// </summary> /// <remarks>In this method we rename corresponding file/folder in user file system.</remarks> 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); } }
/// <summary> /// Called when a file or folder is created in the remote storage. /// </summary> /// <remarks>In this method we create a new file/folder in the user file system.</remarks> 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); } }
/// <summary> /// Called when a file or folder is created in the user file system. /// </summary> /// <remarks> /// This method is also called when a file is being moved in user file system, after the IFileSystem.MoveToAsync() call. /// </remarks> 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 IFileSystem.MoveToAsync(). if (FsPath.Exists(userFileSystemPath) && !PlaceholderItem.IsPlaceholder(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. try { await RemoteStorageRawItem.CreateAsync(userFileSystemPath, this); } 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); } }
/// <summary> /// Called when a file content changed or file/folder attributes changed in the remote storage. /// </summary> /// <remarks> /// In this method we update corresponding file/folder information in user file system. /// We also dehydrate the file if it is not blocked. /// </remarks> private async void ChangedAsync(object sender, FileSystemEventArgs e) { LogMessage(e.ChangeType.ToString(), e.FullPath); string remoteStoragePath = e.FullPath; try { string userFileSystemPath = Mapping.ReverseMapPath(remoteStoragePath); // Because of the on-demand population the file or folder placeholder may not exist in the user file system. if (FsPath.Exists(userFileSystemPath)) { FileSystemInfo remoteStorageItem = FsPath.GetFileSystemItem(remoteStoragePath); if (!FsPath.AvoidSync(remoteStoragePath)) { // This check is only required because we can not prevent circular calls because of the simplicity of this example. if (!await new UserFileSystemItem(userFileSystemPath).EqualsAsync(remoteStorageItem)) { LogMessage("Item modified:", remoteStoragePath); await new UserFileSystemItem(userFileSystemPath).UpdateAsync(remoteStorageItem); LogMessage("Updated succesefully:", userFileSystemPath); } } } } catch (Exception ex) { LogError($"{e.ChangeType} failed:", remoteStoragePath, ex); } }
internal async Task ClearStateAsync() { if (FsPath.Exists(userFileSystemPath)) { await SetIconAsync(false); } }
/// <summary> /// Moves the item in the remote storage. This method is called by the platform. /// To move item manually use the <see cref="MoveToAsync(string)"/> method instead. /// </summary> /// <param name="userFileSystemNewPath">Target path in user file system.</param> /// <param name="resultContext">Confirms move competeion. Passed by the platform only.</param> 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); } }
/// <summary> /// Called when a file or folder is renamed in the remote storage. /// </summary> /// <remarks>In this method we rename corresponding file/folder in user file system.</remarks> 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); // Because of the on-demand population the file or folder placeholder may not exist in the user file system. if (FsPath.Exists(userFileSystemOldPath)) { if (!FsPath.AvoidSync(remoteStorageOldPath) && !FsPath.AvoidSync(remoteStorageNewPath)) { string userFileSystemNewPath = Mapping.ReverseMapPath(remoteStorageNewPath); await new UserFileSystemItem(userFileSystemOldPath).MoveAsync(userFileSystemNewPath); LogMessage("Renamed succesefully:", userFileSystemOldPath, userFileSystemNewPath); } } } catch (Exception ex) { LogError($"{e.ChangeType} failed:", $"From:{remoteStorageOldPath} To:{remoteStorageNewPath}", ex); } }
/// <summary> /// Called when a file or folder attributes changed in the user file system. /// </summary> /// <remarks> /// Here we monitor pinned and unpinned attributes and hydrate/dehydrate files. /// </remarks> 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 (new UserFileSystemRawItem(userFileSystemPath).HydrationRequired()) { LogMessage("Hydrating", userFileSystemPath); new PlaceholderFile(userFileSystemPath).Hydrate(0, -1); LogMessage("Hydrated succesefully", userFileSystemPath); } else if (new UserFileSystemRawItem(userFileSystemPath).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); } }
protected void LogMessage(string message, string sourcePath = null, string targetPath = null) { string att = FsPath.Exists(sourcePath) ? FsPath.GetAttString(sourcePath) : null; string size = FsPath.Size(sourcePath); log.Debug($"\n{DateTime.Now} [{Thread.CurrentThread.ManagedThreadId,2}] {"User File System Monitor: ",-26}{message,-45} {sourcePath,-80} {size,7} {att} {targetPath}"); }
/// <summary> /// Recursively restores OriginalPath and the 'locked' icon. /// </summary> /// <param name="userFileSystemNewPath">Path in the user file system to start recursive processing.</param> 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 <string> 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); } } } }
/// <summary> /// Moves a file or folder placeholder in user file system. /// </summary> /// <param name="userFileSystemNewPath">New path in user file system.</param> /// <remarks> /// 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. /// </remarks> 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. 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) { string eTag = await ETag.GetETagAsync(userFileSystemPath); ETag.DeleteETag(userFileSystemPath); try { Directory.Move(userFileSystemPath, userFileSystemNewPath); } catch { await ETag.SetETagAsync(userFileSystemPath, eTag); throw; } await ETag.SetETagAsync(userFileSystemNewPath, eTag); // The file is marked as not in sync after move/rename. Marking it as in-sync. PlaceholderItem placeholderItem = PlaceholderItem.GetItem(userFileSystemNewPath); placeholderItem.SetInSync(true); placeholderItem.SetOriginalPath(userFileSystemNewPath); await new UserFileSystemRawItem(userFileSystemNewPath).ClearStateAsync(); } if (!inSync) { throw new ConflictException(Modified.Client, "The item is not in-sync with the cloud."); } } } catch (Exception ex) { string path = FsPath.Exists(userFileSystemNewPath) ? userFileSystemNewPath : userFileSystemPath; await new UserFileSystemRawItem(userFileSystemPath).SetDownloadErrorStateAsync(ex); // Rethrow the exception preserving stack trace of the original exception. System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw(); } }
internal async Task SetUploadErrorStateAsync(Exception ex) { if (FsPath.Exists(userFileSystemPath)) { if (ex is ConflictException) { await SetConflictIconAsync(true); } else { await SetUploadPendingIconAsync(true); } } }
/// <inheritdoc/> 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 <FileSystemInfo> remoteStorageChildren = new DirectoryInfo(RemoteStoragePath).EnumerateFileSystemInfos(pattern); List <IFileSystemItemMetadata> userFileSystemChildren = new List <IFileSystemItemMetadata>(); foreach (FileSystemInfo remoteStorageItem in remoteStorageChildren) { IFileSystemItemMetadata itemInfo = Mapping.GetUserFileSysteItemMetadata(remoteStorageItem); 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); } ExternalDataManager 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 }); } }
/// <summary> /// Moves a file or folder placeholder in user file system. /// </summary> /// <param name="userFileSystemNewPath">New path in user file system.</param> /// <remarks> /// 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. /// </remarks> /// <returns>True if the item was moved. False - otherwise.</returns> public async Task <bool> 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); }
//$> /// <summary> /// Deletes a file or folder placeholder in user file system. /// </summary> /// <remarks> /// This method throws <see cref="ConflictException"/> 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). /// </remarks> /// <returns>True if the file was deleted. False - otherwise.</returns> public async Task <bool> 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)) { if (FsPath.IsFile(userFileSystemPath)) { File.Delete(userFileSystemPath); } else { Directory.Delete(userFileSystemPath, true); } // Delete ETag ETag.DeleteETag(userFileSystemPath); 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); }
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); } } }
/// <summary> /// Updates information about the file or folder placeholder in the user file system. /// This method automatically hydrates and dehydrate files. /// </summary> /// <remarks>This method failes if the file or folder in user file system is modified (not in-sync with the remote storage).</remarks> /// <param name="itemInfo">New file or folder info.</param> /// <returns>True if the file was updated. False - otherwise.</returns> public async Task <bool> 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); }
/// <summary> /// Called when a file or folder attributes changed in the user file system. /// </summary> /// <remarks> /// Here we monitor pinned and unpinned attributes and hydrate/dehydrate files. /// </remarks> private async void ChangedAsync(object sender, FileSystemEventArgs e) { LogMessage($"{e.ChangeType}", e.FullPath); try { string userFileSystemPath = e.FullPath; if (FsPath.Exists(userFileSystemPath)) { bool?hydrated = await new UserFileSystemItem(userFileSystemPath).UpdateHydrationAsync(); if (hydrated != null) { string hydrationDescrition = (bool)hydrated ? "Hydrated" : "Dehydrated"; LogMessage($"{hydrationDescrition} succesefully:", userFileSystemPath); } } } catch (Exception ex) { LogError("Hydration/dehydration failed:", e.FullPath, ex); } }
//$> //$<PlaceholderItem.SetItemInfo /// <summary> /// Updates information about the file or folder placeholder in the user file system. /// This method automatically hydrates and dehydrate files. /// </summary> /// <remarks>This method failes if the file or folder in user file system is modified (not in-sync with the remote storage).</remarks> /// <param name="itemInfo">New file or folder info.</param> /// <returns>True if the file was updated. False - otherwise.</returns> public async Task <bool> UpdateAsync(FileSystemItemBasicInfo itemInfo) { 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. placeholderItem.SetItemInfo(itemInfo); // Set ETag. await ETag.SetETagAsync(userFileSystemPath, itemInfo.ETag); // Clear icon. //await ClearStateAsync(); // Set the "locked by another user" icon and all custom columns data. await SetLockedByAnotherUserAsync(itemInfo.LockedByAnotherUser); await SetCustomColumnsDataAsync(itemInfo.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); }
/// <summary> /// Completes move. This method is called by the platform. /// To move item manually use the <see cref="MoveToAsync(string)"/> method instead. /// Sets In-Sync state based on file content changes, /// Updates OriginalPath so the file does not appear as moved. /// </summary> /// <returns></returns> 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); } }
/// <summary> /// Called when a file or folder is deleted in the remote storage. /// </summary> /// <remarks>In this method we delete corresponding file/folder in user file system.</remarks> private async void DeletedAsync(object sender, FileSystemEventArgs e) { LogMessage(e.ChangeType.ToString(), e.FullPath); string remoteStoragePath = e.FullPath; try { string userFileSystemPath = Mapping.ReverseMapPath(remoteStoragePath); // Because of the on-demand population the file or folder placeholder may not exist in the user file system. if (FsPath.Exists(userFileSystemPath)) { if (!FsPath.AvoidSync(remoteStoragePath)) { await new UserFileSystemItem(userFileSystemPath).DeleteAsync(); LogMessage("Deleted succesefully:", userFileSystemPath); } } } catch (Exception ex) { LogError($"{e.ChangeType} failed:", remoteStoragePath, ex); } }
/// <summary> /// Called when a file content changed or file/folder attributes changed in the remote storage. /// </summary> /// <remarks> /// In this method we update corresponding file/folder information in user file system. /// We also dehydrate the file if it is not blocked. /// </remarks> private async void ChangedAsync(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, etc. from remote storage. if (!FsPath.AvoidSync(remoteStoragePath)) { string userFileSystemPath = Mapping.ReverseMapPath(remoteStoragePath); // Because of the on-demand population the file or folder placeholder may not exist in the user file system. if (FsPath.Exists(userFileSystemPath)) { 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. FileSystemItemBasicInfo itemInfo = Mapping.GetUserFileSysteItemBasicInfo(remoteStorageItem); if (!await ETag.ETagEqualsAsync(userFileSystemPath, itemInfo)) { await new UserFileSystemRawItem(userFileSystemPath).UpdateAsync(itemInfo); LogMessage("Updated succesefully", userFileSystemPath); } // Update "locked by another user" icon. await new UserFileSystemRawItem(userFileSystemPath).SetLockedByAnotherUserAsync(itemInfo.LockedByAnotherUser); } } } catch (Exception ex) { LogError($"{e.ChangeType} failed", remoteStoragePath, null, ex); } }
internal async Task MoveToAsync(string userFileSystemNewPath, IConfirmationResultContext resultContext = null) { string userFileSystemOldPath = userFileSystemPath; try { bool? inSync = null; bool updateTargetOnSuccess = false; string eTag = null; try { if (!FsPath.IsRecycleBin(userFileSystemNewPath) && // When a file is deleted, it is moved to a Recycle Bin. !FsPath.AvoidSync(userFileSystemOldPath) && !FsPath.AvoidSync(userFileSystemNewPath)) { logger.LogMessage("Moving item in remote storage", userFileSystemOldPath, userFileSystemNewPath); // Read In-Sync state before move and set after move if (FsPath.Exists(userFileSystemOldPath)) { inSync = PlaceholderItem.GetItem(userFileSystemOldPath).GetInSync(); } eTag = await ETag.GetETagAsync(userFileSystemOldPath); ETag.DeleteETag(userFileSystemOldPath); IUserFileSystemItem userFileSystemItemOld = await virtualDrive.GetItemAsync <IUserFileSystemItem>(userFileSystemOldPath); await userFileSystemItemOld.MoveToAsync(userFileSystemNewPath); updateTargetOnSuccess = true; logger.LogMessage("Moved succesefully in remote storage", userFileSystemOldPath, userFileSystemNewPath); } } finally { if (resultContext != null) { resultContext.ReturnConfirmationResult(); } // This check is just to avoid extra error in the log. if (FsPath.Exists(userFileSystemNewPath)) { // Open file to preven reads and changes between GetFileDataSizeInfo() call and SetInSync() call. using (WindowsFileSystemItem userFileSystemWinItem = WindowsFileSystemItem.OpenReadAttributes(userFileSystemNewPath, FileMode.Open, FileShare.None)) { if ((eTag != null) && PlaceholderItem.IsPlaceholder(userFileSystemNewPath)) { await ETag.SetETagAsync(userFileSystemNewPath, eTag); } // If a file with content is deleted it is moved to a recycle bin and converted // to a regular file, so placeholder features are not available on it, checking if a file is a placeholder. if (updateTargetOnSuccess && PlaceholderItem.IsPlaceholder(userFileSystemNewPath)) { PlaceholderItem placeholderNew = PlaceholderItem.GetItem(userFileSystemNewPath); // Update OriginalPath, so the item does not appear as moved. placeholderNew.SetOriginalPath(userFileSystemNewPath); if (inSync != null) { placeholderNew.SetInSync(inSync.Value); } else if ((placeholderNew is PlaceholderFile) && ((PlaceholderFile)placeholderNew).GetFileDataSizeInfo().ModifiedDataSize == 0) { placeholderNew.SetInSync(true); } await new UserFileSystemRawItem(userFileSystemNewPath).ClearStateAsync(); } } } } } catch (Exception ex) { string userFileSystemPath = FsPath.Exists(userFileSystemNewPath) ? userFileSystemNewPath : userFileSystemOldPath; await new UserFileSystemRawItem(userFileSystemPath).SetUploadErrorStateAsync(ex); // Rethrow the exception preserving stack trace of the original exception. System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw(); } }
/// <summary> /// Recursively synchronizes all files and folders from server to client. /// Synchronizes only folders already loaded into the user file system. /// </summary> /// <param name="userFileSystemFolderPath">Folder path in user file system.</param> 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 <string> userFileSystemChildren = Directory.EnumerateFileSystemEntries(userFileSystemFolderPath, "*"); //LogMessage("Synchronizing:", userFileSystemFolderPath); IEnumerable <FileSystemItemBasicInfo> remoteStorageChildrenItems = await new UserFolder(userFileSystemFolderPath).EnumerateChildrenAsync("*"); // Create new files/folders in the user file system. string remoteStorageFolderPath = Mapping.MapPath(userFileSystemFolderPath); foreach (FileSystemItemBasicInfo 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. string remoteStorageItemFullPath = Path.Combine(remoteStorageFolderPath, remoteStorageItem.Name); if (!FsPath.AvoidSync(remoteStorageItemFullPath) && !FsPath.AvoidSync(userFileSystemPath)) { if (!FsPath.Exists(userFileSystemPath)) { LogMessage($"Creating", userFileSystemPath); await UserFileSystemRawItem.CreateAsync(userFileSystemFolderPath, 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); string remoteStoragePath = Mapping.MapPath(userFileSystemPath); FileSystemItemBasicInfo remoteStorageItem = remoteStorageChildrenItems.FirstOrDefault(x => x.Name.Equals(itemName, StringComparison.InvariantCultureIgnoreCase)); if (!FsPath.AvoidSync(userFileSystemPath) && !FsPath.AvoidSync(remoteStoragePath)) { if (remoteStorageItem == null) { if (PlaceholderItem.GetItem(userFileSystemPath).GetInSync()) { // Delete the file/folder in user file system. LogMessage("Deleting item", userFileSystemPath); await new UserFileSystemRawItem(userFileSystemPath).DeleteAsync(); LogMessage("Deleted succesefully", userFileSystemPath); } } else { if (PlaceholderItem.GetItem(userFileSystemPath).GetInSync() && !await ETag.ETagEqualsAsync(userFileSystemPath, remoteStorageItem)) { // User file system <- remote storage update. LogMessage("Item modified", remoteStoragePath); await new UserFileSystemRawItem(userFileSystemPath).UpdateAsync(remoteStorageItem); LogMessage("Updated succesefully", userFileSystemPath); } // Update "locked by another user" icon. if (PlaceholderItem.GetItem(userFileSystemPath).GetInSync()) { await new UserFileSystemRawItem(userFileSystemPath).SetLockedByAnotherUserAsync(remoteStorageItem.LockedByAnotherUser); } // Hydrate / dehydrate the file. if (new UserFileSystemRawItem(userFileSystemPath).HydrationRequired()) { LogMessage("Hydrating", userFileSystemPath); new PlaceholderFile(userFileSystemPath).Hydrate(0, -1); LogMessage("Hydrated succesefully", userFileSystemPath); } else if (new UserFileSystemRawItem(userFileSystemPath).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); } } }
protected void LogError(string message, string sourcePath, Exception ex) { string att = FsPath.Exists(sourcePath) ? FsPath.GetAttString(sourcePath) : null; log.Error($"\n{DateTime.Now} [{Thread.CurrentThread.ManagedThreadId,2}] {"User File System Monitor: ",-26}{message,-45} {sourcePath,-80} {att} ", ex); }
/// <inheritdoc/> 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); IHierarchyItemAsync[] remoteStorageChildren = null; // Retry the request in case the log-in dialog is shown. try { remoteStorageChildren = await Program.DavClient.GetChildrenAsync(new Uri(RemoteStoragePath), false); } catch (ITHit.WebDAV.Client.Exceptions.Redirect302Exception) { remoteStorageChildren = await Program.DavClient.GetChildrenAsync(new Uri(RemoteStoragePath), false); } List <FileSystemItemMetadataExt> userFileSystemChildren = new List <FileSystemItemMetadataExt>(); foreach (IHierarchyItemAsync remoteStorageItem in remoteStorageChildren) { FileSystemItemMetadataExt itemInfo = Mapping.GetUserFileSystemItemMetadata(remoteStorageItem); 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); } ExternalDataManager customDataManager = Engine.CustomDataManager(userFileSystemItemPath); // Mark this item as not new, which is required for correct MS Office saving opertions. customDataManager.IsNew = false; } // To signal that the children enumeration is completed // always call ReturnChildren(), even if the folder is empty. resultContext.ReturnChildren(userFileSystemChildren.ToArray(), userFileSystemChildren.Count()); // Save ETags, the read-only attribute and all custom columns data. foreach (FileSystemItemMetadataExt child in userFileSystemChildren) { string userFileSystemItemPath = Path.Combine(UserFileSystemPath, child.Name); ExternalDataManager customDataManager = Engine.CustomDataManager(userFileSystemItemPath); // Save ETag on the client side, to be sent to the remote storage as part of the update. // Setting ETag also marks an item as not new. // 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 customDataManager.ETagManager.SetETagAsync(child.ETag); // Set the read-only attribute and all custom columns data. await customDataManager.SetLockedByAnotherUserAsync(child.LockedByAnotherUser); await customDataManager.SetCustomColumnsAsync(child.CustomProperties); } }
/// <summary> /// Recursively synchronizes all files and folders from server to client. /// Synchronizes only folders already loaded into the user file system. /// </summary> /// <param name="userFileSystemFolderPath">Folder path in user file system.</param> 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 <string> userFileSystemChildren = Directory.EnumerateFileSystemEntries(userFileSystemFolderPath, "*"); //LogMessage("Synchronizing:", userFileSystemFolderPath); IVirtualFolder userFolder = await virtualDrive.GetItemAsync <IVirtualFolder>(userFileSystemFolderPath, this); IEnumerable <FileSystemItemMetadataExt> 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); } } }
/// <summary> /// Recursively synchronizes all files and folders with the server. /// Synchronizes only folders already loaded into the user file system. /// </summary> /// <param name="userFileSystemFolderPath">Folder path in user file system.</param> private 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 <string> userFileSystemChildren = Directory.EnumerateFileSystemEntries(userFileSystemFolderPath, "*"); LogMessage("Synchronizing:", userFileSystemFolderPath); string remoteStorageFolderPath = Mapping.MapPath(userFileSystemFolderPath); IEnumerable <FileSystemInfo> remoteStorageChildrenItems = new DirectoryInfo(remoteStorageFolderPath).EnumerateFileSystemInfos("*"); // Delete files/folders in user file system. foreach (string userFileSystemPath in userFileSystemChildren) { try { string remoteStoragePath = Mapping.MapPath(userFileSystemPath); FileSystemInfo remoteStorageItem = remoteStorageChildrenItems.FirstOrDefault(x => x.FullName.Equals(remoteStoragePath, StringComparison.InvariantCultureIgnoreCase)); if (remoteStorageItem == null) { LogMessage("Deleting item:", userFileSystemPath); await new UserFileSystemItem(userFileSystemPath).DeleteAsync(); LogMessage("Deleted succesefully:", userFileSystemPath); } } catch (Exception ex) { LogError("Delete failed:", userFileSystemPath, ex); } } // Create new files/folders in user file system. foreach (FileSystemInfo remoteStorageItem in remoteStorageChildrenItems) { try { string userFileSystemPath = Mapping.ReverseMapPath(remoteStorageItem.FullName); if (!FsPath.Exists(userFileSystemPath)) { LogMessage("Creating new item:", userFileSystemPath); await UserFileSystemItem.CreateAsync(userFileSystemFolderPath, remoteStorageItem); LogMessage("Created succesefully:", userFileSystemPath); } } catch (Exception ex) { LogError("Creation failed:", remoteStorageItem.FullName, ex); } } // Update files/folders in user file system and sync subfolders. userFileSystemChildren = Directory.EnumerateFileSystemEntries(userFileSystemFolderPath, "*"); foreach (string userFileSystemPath in userFileSystemChildren) { try { string remoteStoragePath = Mapping.MapPath(userFileSystemPath); FileSystemInfo remoteStorageItem = remoteStorageChildrenItems.FirstOrDefault(x => x.FullName.Equals(remoteStoragePath, StringComparison.InvariantCultureIgnoreCase)); // The item was deleted or moved/renamed on the server, but it may still exists in the user file system. // This is just to avoid extra exceptions in the log. if (remoteStorageItem == null) { continue; } // Update existing files/folders info. if (!await new UserFileSystemItem(userFileSystemPath).EqualsAsync(remoteStorageItem)) { LogMessage("Item modified:", remoteStoragePath); await new UserFileSystemItem(userFileSystemPath).UpdateAsync(remoteStorageItem); LogMessage("Updated succesefully:", userFileSystemPath); } // Hydrate / dehydrate the file. else { bool?hydrated = await new UserFileSystemItem(userFileSystemPath).UpdateHydrationAsync(); if (hydrated != null) { string hydrationDescrition = (bool)hydrated ? "Hydrated" : "Dehydrated"; LogMessage($"{hydrationDescrition} succesefully:", userFileSystemPath); } } } catch (Exception ex) { LogError("Update failed:", userFileSystemPath, ex); } // Synchronize subfolders. try { if (FsPath.IsFolder(userFileSystemPath)) { await SyncronizeFolderAsync(userFileSystemPath); } } catch (Exception ex) { LogError("Folder sync failed:", userFileSystemPath, ex); } } }