/// <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> /// 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); }
/// <summary> /// Recursively synchronizes all files and folders moved in user file system with the remote storage. /// Synchronizes only folders already loaded into the user file system. /// </summary> /// <param name="userFileSystemFolderPath">Folder path in user file system.</param> internal async Task SyncronizeMovedAsync(string userFileSystemFolderPath) { // In case of on-demand loading the user file system contains only a subset of the server files and folders. // Here we sync folder only if its content already loaded into user file system (folder is not offline). // The folder content is loaded inside IFolder.GetChildrenAsync() method. if (new DirectoryInfo(userFileSystemFolderPath).Attributes.HasFlag(System.IO.FileAttributes.Offline)) { //LogMessage("Folder offline, skipping:", userFileSystemFolderPath); return; } IEnumerable <string> userFileSystemChildren = Directory.EnumerateFileSystemEntries(userFileSystemFolderPath, "*"); //LogMessage("Synchronizing:", userFileSystemFolderPath); foreach (string userFileSystemPath in userFileSystemChildren) { string userFileSystemOldPath = null; try { //$<PlaceholderItem.IsPlaceholder if (!PlaceholderItem.IsPlaceholder(userFileSystemPath)) { // Convert regular file/folder to placeholder. // The file/folder was created or overwritten. PlaceholderItem.ConvertToPlaceholder(userFileSystemPath, false); LogMessage("Converted to placeholder", userFileSystemPath); } //$> if (!FsPath.AvoidSync(userFileSystemPath)) { PlaceholderItem userFileSystemItem = PlaceholderItem.GetItem(userFileSystemPath); if (userFileSystemItem.IsMoved()) { // Process items moved in user file system. userFileSystemOldPath = userFileSystemItem.GetOriginalPath(); await new RemoteStorageRawItem(userFileSystemOldPath, this).MoveToAsync(userFileSystemPath); } else { // Restore Original Path and 'locked' icon that are lost during MS Office transactional save. // We keep Original Path to process moved files when app was not running. userFileSystemOldPath = userFileSystemItem.GetOriginalPath(); if (!userFileSystemItem.IsNew() && string.IsNullOrEmpty(userFileSystemOldPath)) { // Restore Original Path. LogMessage("Saving Original Path", userFileSystemItem.Path); userFileSystemItem.SetOriginalPath(userFileSystemItem.Path); // Restore the 'locked' icon. bool isLocked = await Lock.IsLockedAsync(userFileSystemPath); await new UserFileSystemRawItem(userFileSystemPath).SetLockIconAsync(isLocked); } } } } catch (Exception ex) { LogError("Move in remote storage failed", userFileSystemOldPath, userFileSystemPath, ex); } // Synchronize subfolders. try { if (FsPath.IsFolder(userFileSystemPath)) { await SyncronizeMovedAsync(userFileSystemPath); } } catch (Exception ex) { LogError("Folder move sync failed", userFileSystemPath, null, ex); } } }
/// <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); } } }