/// <summary> /// Returns MS Office lock file path if such file exists. /// </summary> /// <param name="msOfficeFilePath">MS Office file path.</param> /// <returns>Lock file path.</returns> /// <remarks> /// 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 /// </remarks> 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); }
/// <inheritdoc/> public 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{DateTimeOffset.Now} [{Thread.CurrentThread.ManagedThreadId,2}] {componentName,-26}{message,-45} {sourcePath,-80} {size,7} {att} {targetPath}"); }
/// <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)) { if (await engine.ServerNotifications(userFileSystemOldPath).MoveToAsync(userFileSystemNewPath)) { // Because of the on-demand population the file or folder placeholder may not exist in the user file system. // In this case the IServerNotifications.MoveToAsync() call is ignored. LogMessage("Renamed succesefully:", userFileSystemOldPath, 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); 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: // - IGetChildrenContext.ReturnChildren() method. // - IGetChildrenContext.ReportProgress() method. LogMessage($"IFolder.GetChildrenAsync({pattern})", this.FullPath); string remoreStoragePath = Mapping.MapPath(this.FullPath); IEnumerable <FileSystemInfo> remoteStorageChildren = new DirectoryInfo(remoreStoragePath).EnumerateFileSystemInfos(pattern); List <IFileSystemItemBasicInfo> userFileSystemChildren = new List <IFileSystemItemBasicInfo>(); foreach (FileSystemInfo remoteStorageItem in remoteStorageChildren) { string userFileSystemPath = Path.Combine(this.FullPath, Path.GetFileName(remoteStorageItem.Name)); if (!FsPath.Exists(userFileSystemPath)) { // Uncomment to simulate slow network access. //Thread.Sleep(10000); //resultContext.ReportProgress(remoteStorageChildren.Count(), userFileSystemChildren.Count()); LogMessage("Creating:", Path.GetFileName(remoteStorageItem.Name)); FileSystemItemBasicInfo userFileSystemItemInfo = Mapping.GetUserFileSysteItemInfo(remoteStorageItem); userFileSystemChildren.Add(userFileSystemItemInfo); } } // To signal that the children enumeration is completed // always call this method in GetChildrenAsync(), even if the folder is empty. resultContext.ReturnChildren(userFileSystemChildren.ToArray(), userFileSystemChildren.Count()); }
/// <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()); }
///<inheritdoc> public async Task MoveToAsync(string userFileSystemNewPath, IOperationContext operationContext, IConfirmationResultContext resultContext) { // Here we will simply move the file in remote storage and confirm the operation. // In your implementation you may implement a more complex scenario with offline operations support. LogMessage("IFileSystemItem.MoveToAsync()", this.FullPath, userFileSystemNewPath); string userFileSystemOldPath = this.FullPath; try { bool?inSync = null; try { Program.RemoteStorageMonitorInstance.Enabled = false; // Disable RemoteStorageMonitor to avoid circular calls. // When a file is deleted, it is moved to a Recycle Bin, that is why we check for recycle bin here. if (FsPath.Exists(userFileSystemOldPath) && !FsPath.IsRecycleBin(userFileSystemNewPath) && !FsPath.AvoidSync(userFileSystemOldPath) && !FsPath.AvoidSync(userFileSystemNewPath)) { inSync = PlaceholderItem.GetItem(userFileSystemOldPath).GetInSync(); string remoteStorageOldPath = Mapping.MapPath(userFileSystemOldPath); string remoteStorageNewPath = Mapping.MapPath(userFileSystemNewPath); FileSystemInfo remoteStorageOldItem = FsPath.GetFileSystemItem(remoteStorageOldPath); if (remoteStorageOldItem is FileInfo) { (remoteStorageOldItem as FileInfo).MoveTo(remoteStorageNewPath); } else { (remoteStorageOldItem as DirectoryInfo).MoveTo(remoteStorageNewPath); } LogMessage("Moved succesefully:", remoteStorageOldPath, remoteStorageNewPath); } } finally { resultContext.ReturnConfirmationResult(); // 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. if ((inSync != null) && PlaceholderItem.IsPlaceholder(userFileSystemNewPath)) { PlaceholderItem.GetItem(userFileSystemNewPath).SetInSync(inSync.Value); } } } catch (Exception ex) { // remove try-catch when error processing inside CloudProvider is fixed. LogError("Move failed:", $"From: {this.FullPath} to:{userFileSystemNewPath}", ex); } finally { Program.RemoteStorageMonitorInstance.Enabled = true; } }
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); Logger.LogMessage($"{message,-45} {sourcePath,-80} {size,7} {att} {targetPath}"); }
/// <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. FileSystemItemMetadataExt itemInfo = Mapping.GetUserFileSysteItemMetadata(remoteStorageItem); if (!await virtualDrive.GetETagManager(userFileSystemPath, this).ETagEqualsAsync(itemInfo)) { await virtualDrive.ServerNotifications(userFileSystemPath, this).UpdateAsync(itemInfo); LogMessage("Updated succesefully", userFileSystemPath); } } } } catch (Exception ex) { LogError($"{e.ChangeType} failed", remoteStoragePath, null, ex); } }
/// <inheritdoc/> public async Task OpenAsync(IOperationContext operationContext, IResultContext context) { Logger.LogMessage("IFile.OpenAsync()", UserFileSystemPath); // Auto-lock the file. string userFileSystemFilePath = UserFileSystemPath; if (Engine.ChangesProcessingEnabled && FsPath.Exists(userFileSystemFilePath)) { if (Program.Settings.AutoLock && !FsPath.AvoidAutoLock(userFileSystemFilePath) && !await Lock.IsLockedAsync(userFileSystemFilePath) && FsPath.IsWriteLocked(userFileSystemFilePath) && !new PlaceholderFile(userFileSystemFilePath).IsNew()) { try { await new RemoteStorageRawItem(userFileSystemFilePath, Logger).LockAsync(LockMode.Auto); } catch (ClientLockFailedException ex) { // Lock file is blocked by the concurrent thread. This is a normal behaviour. Logger.LogMessage(ex.Message, userFileSystemFilePath); } } } }
/// <summary> /// Returns true if the file or folder is marked with Hidden or Temporaty attributes. /// </summary> /// <param name="path">Path to a file or folder.</param> /// <returns> /// 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. /// </returns> 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)); }
//$<IFolder.CloseAsync /// <inheritdoc/> 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()) { // Create new file in the remote storage. await RemoteStorageRawItem.CreateAsync(userFileSystemFilePath, Logger); } else { // Send content to remote storage. Unlock if auto-locked. await new RemoteStorageRawItem(userFileSystemFilePath, 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); } } }
/// <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($"IFolder.GetChildrenAsync({pattern})", UserFileSystemPath); IEnumerable <FileSystemItemBasicInfo> children = await new UserFolder(UserFileSystemPath).EnumerateChildrenAsync(pattern); // Filtering existing files/folders. This is only required to avoid extra errors in the log. List <IFileSystemItemBasicInfo> newChildren = new List <IFileSystemItemBasicInfo>(); foreach (IFileSystemItemBasicInfo child in children) { string userFileSystemItemPath = Path.Combine(UserFileSystemPath, child.Name); if (!FsPath.Exists(userFileSystemItemPath)) { Logger.LogMessage("Creating", child.Name); 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 and set "locked by another user" icon. foreach (FileSystemItemBasicInfo 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. ETag.SetETagAsync(userFileSystemItemPath, child.ETag); // Set the lock icon and read-only attribute, to indicate that the item is locked by another user. new UserFileSystemRawItem(userFileSystemItemPath).SetLockedByAnotherUserAsync(child.LockedByAnotherUser); } }
/// <inheritdoc/> public async Task CloseAsync(IOperationContext operationContext, IResultContext context) { // Here, if the file in user file system is modified (not in-sync), we send file content, // creation time, modification time and attributes to remote storage. // We also create new ETag, associate it with a file in user file system and send it to the server. LogMessage("IFile.CloseAsync()", this.FullPath); string userFileSystemFilePath = this.FullPath; // In case the file is moved it does not exist in user file system when Close() is called. if (!FsPath.Exists(userFileSystemFilePath) || FsPath.AvoidSync(userFileSystemFilePath)) { return; } // In case the file is overwritten it is converted to a regular file prior to Close(). // we need to convert it back into file/folder placeholder. if (!PlaceholderItem.IsPlaceholder(userFileSystemFilePath)) { PlaceholderItem.ConvertToPlaceholder(userFileSystemFilePath, false); LogMessage("Converted to placeholder:", userFileSystemFilePath); } if (!PlaceholderItem.GetItem(userFileSystemFilePath).GetInSync()) { LogMessage("Changed:", userFileSystemFilePath); string remoteStorageFilePath = Mapping.MapPath(userFileSystemFilePath); try { await new RemoteStorageItem(remoteStorageFilePath).UpdateAsync(userFileSystemFilePath); LogMessage("Updated succesefully:", remoteStorageFilePath); } 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. LogMessage("Failed to upload file. Possibly in use by an application or blocked for synchronization in another thread:", ex.Message); } } }
///<inheritdoc> public async Task DeleteAsync(IOperationContext operationContext, IConfirmationResultContext resultContext) { // Here we will simply delete the file in remote storage and confirm the operation. // In your implementation you may implement a more complex scenario with offline operations support. LogMessage("IFileSystemItem.DeleteAsync()", this.FullPath); string userFileSystemPath = this.FullPath; string remoteStoragePath = null; try { try { Program.RemoteStorageMonitorInstance.Enabled = false; // Disable RemoteStorageMonitor to avoid circular calls. remoteStoragePath = Mapping.MapPath(userFileSystemPath); if (FsPath.Exists(remoteStoragePath) && !FsPath.AvoidSync(userFileSystemPath)) { FileSystemInfo remoteStorageItem = FsPath.GetFileSystemItem(remoteStoragePath); remoteStorageItem.Delete(); LogMessage("Deleted succesefully:", remoteStoragePath); } } finally { resultContext.ReturnConfirmationResult(); } } catch (Exception ex) { // remove try-catch when error processing inside CloudProvider is fixed. LogError("Delete failed:", remoteStoragePath, ex); } finally { Program.RemoteStorageMonitorInstance.Enabled = true; } }
//$<IFileSystemItem.MoveToAsync ///<inheritdoc> public async Task MoveToAsync(string userFileSystemNewPath, IOperationContext operationContext, IConfirmationResultContext resultContext) { string userFileSystemOldPath = this.UserFileSystemPath; Logger.LogMessage("IFileSystemItem.MoveToAsync()", userFileSystemOldPath, userFileSystemNewPath); // Process move. if (Engine.ChangesProcessingEnabled) { if (FsPath.Exists(userFileSystemOldPath)) { await new RemoteStorageRawItem(userFileSystemOldPath, Logger).MoveToAsync(userFileSystemNewPath, resultContext); } } else { resultContext.ReturnConfirmationResult(); } // Restore Original Path and locked icon, lost during MS Office transactional save. if (FsPath.Exists(userFileSystemNewPath) && PlaceholderItem.IsPlaceholder(userFileSystemNewPath)) { PlaceholderItem userFileSystemNewItem = PlaceholderItem.GetItem(userFileSystemNewPath); if (!userFileSystemNewItem.IsNew() && string.IsNullOrEmpty(userFileSystemNewItem.GetOriginalPath())) { // Restore Original Path. Logger.LogMessage("Saving Original Path", userFileSystemNewPath); userFileSystemNewItem.SetOriginalPath(userFileSystemNewPath); // Restore the 'locked' icon. bool isLocked = await Lock.IsLockedAsync(userFileSystemNewPath); await new UserFileSystemRawItem(userFileSystemNewPath).SetLockIconAsync(isLocked); } } }
protected void LogError(string message, string sourcePath = null, Exception ex = null) { string att = FsPath.Exists(sourcePath) ? FsPath.GetAttString(sourcePath) : null; Logger.LogError($"{message,-45} {sourcePath,-80} {att} ", ex); }
/// <inheritdoc/> public void LogError(string message, string sourcePath = null, string targetPath = null, Exception ex = null) { string att = FsPath.Exists(sourcePath) ? FsPath.GetAttString(sourcePath) : null; Log.Error($"\n{DateTimeOffset.Now} [{Thread.CurrentThread.ManagedThreadId,2}] {componentName,-26}{message,-45} {sourcePath,-80} {att} ", ex); }