/// <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 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 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 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; } }
/// <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 { 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 (await virtualDrive.ServerNotifications(userFileSystemPath, this).DeleteAsync()) { LogMessage("Deleted succesefully", userFileSystemPath); } } } catch (Exception ex) { LogError($"{e.ChangeType} failed", remoteStoragePath, null, ex); } }
/// <inheritdoc/> 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); } }
//$<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); } } }
/// <summary> /// Compares two files contents. /// </summary> /// <param name="filePath1">File or folder 1 to compare.</param> /// <param name="filePath2">File or folder 2 to compare.</param> /// <returns>True if file is modified. False - otherwise.</returns> 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); }
/// <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); }
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); }