/// <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; 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); if (await engine.ServerNotifications(userFileSystemPath).UpdateAsync(itemInfo)) { // Because of the on-demand population the file or folder placeholder may not exist in the user file system. // In this case the IServerNotifications.UpdateAsync() call is ignored. 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); } }
/// <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); FileSystemInfo remoteStorageItem = FsPath.GetFileSystemItem(remoteStoragePath); if (remoteStorageItem != null) { IFileSystemItemMetadata newItemInfo = Mapping.GetUserFileSysteItemMetadata(remoteStorageItem); if (await engine.ServerNotifications(userFileSystemParentPath).CreateAsync(new[] { newItemInfo }) > 0) { // Because of the on-demand population, the parent folder placeholder may not exist in the user file system // or the folder may be offline. In this case the IServerNotifications.CreateAsync() call is ignored. LogMessage($"Created succesefully", userFileSystemPath); } } } } catch (Exception ex) { LogError($"{e.ChangeType} failed", remoteStoragePath, null, 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); 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); } } // To signal that the children enumeration is completed // always call ReturnChildren(), even if the folder is empty. resultContext.ReturnChildren(userFileSystemChildren.ToArray(), userFileSystemChildren.Count()); }
/// <summary> /// Creates or updates this item in the remote storage. /// </summary> /// <param name="remoteStorageUri">Uri of the file to be created or updated in the remote storage.</param> /// <param name="newInfo">New information about the file or folder, such as modification date, attributes, custom data, etc.</param> /// <param name="mode">Specifies if a new file should be created or existing file should be updated.</param> /// <param name="content">New file content or null if the file content is not modified.</param> /// <param name="eTagOld">The ETag to be sent to the remote storage as part of the update request to make sure the content is not overwritten.</param> /// <param name="lockInfo">Information about the lock. Caller passes null if the item is not locked.</param> /// <returns>The new ETag returned from the remote storage.</returns> protected static async Task <string> 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); }
/// <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 }); } }
/// <inheritdoc/> public async Task GetChildrenAsync(string pattern, IOperationContext operationContext, IFolderListingResultContext resultContext) { 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); 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); }
/// <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> /// Creates or updates the item in the remote storage. /// </summary> /// <param name="mode"> /// Indicates if the file should created or updated. /// Supported modes are <see cref="FileMode.CreateNew"/> and <see cref="FileMode.Open"/> /// </param> /// <param name="lockInfo"> /// Information about the lock. Pass null if the item is not locked. /// </param> 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 <IVirtualFolder>(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 <IVirtualFolder>(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); } }