/// <inheritdoc/> public async Task <byte[]> CreateFileAsync(IFileMetadata fileMetadata, Stream content = null) { Logger.LogMessage($"{nameof(IFolder)}.{nameof(CreateFileAsync)}()", Path.Combine(UserFileSystemPath, fileMetadata.Name)); FileInfo remoteStorageItem = new FileInfo(Path.Combine(RemoteStoragePath, fileMetadata.Name)); // Upload remote storage file content. await using (FileStream remoteStorageStream = remoteStorageItem.Open(FileMode.CreateNew, FileAccess.Write, FileShare.Delete)) { if (content != null) { await content.CopyToAsync(remoteStorageStream); remoteStorageStream.SetLength(content.Length); } } // Update remote storage file metadata. remoteStorageItem.Attributes = fileMetadata.Attributes; remoteStorageItem.CreationTimeUtc = fileMetadata.CreationTime.UtcDateTime; remoteStorageItem.LastWriteTimeUtc = fileMetadata.LastWriteTime.UtcDateTime; remoteStorageItem.LastAccessTimeUtc = fileMetadata.LastAccessTime.UtcDateTime; remoteStorageItem.LastWriteTimeUtc = fileMetadata.LastWriteTime.UtcDateTime; // Return remote storage item ID. It will be passed later into IEngine.GetFileSystemItemAsync() method. return(WindowsFileSystemItem.GetItemIdByPath(remoteStorageItem.FullName)); }
/// <summary> /// Gets a user file system item info from the remote storage data. /// </summary> /// <param name="remoteStorageItem">Remote storage item info.</param> /// <returns>User file system item info.</returns> public static IFileSystemItemMetadata GetUserFileSysteItemMetadata(FileSystemInfo remoteStorageItem) { IFileSystemItemMetadata userFileSystemItem; if (remoteStorageItem is FileInfo) { userFileSystemItem = new FileMetadata(); ((FileMetadata)userFileSystemItem).Length = ((FileInfo)remoteStorageItem).Length; } else { userFileSystemItem = new FolderMetadata(); } // Store you item ID here. It will be passed to IEngine.GetFileSystemItemAsync() during every operation. // Note that the file is deleted during MS Office transactional save and iten ID will be deleted with it. // See Virtual Drive sample for MS Office documents editing. userFileSystemItem.ItemId = WindowsFileSystemItem.GetItemIdByPath(remoteStorageItem.FullName); userFileSystemItem.Name = remoteStorageItem.Name; userFileSystemItem.Attributes = remoteStorageItem.Attributes; userFileSystemItem.CreationTime = remoteStorageItem.CreationTime; userFileSystemItem.LastWriteTime = remoteStorageItem.LastWriteTime; userFileSystemItem.LastAccessTime = remoteStorageItem.LastAccessTime; userFileSystemItem.ChangeTime = remoteStorageItem.LastWriteTime; return(userFileSystemItem); }
/// <summary> /// Deletes a file or folder placeholder in user file system. /// </summary> /// <remarks>This method failes if the file or folder in user file system is modified (not in sync with the remote storage).</remarks> public async Task DeleteAsync() { // Windows 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 in this method we // open it without FileShare.Write flag. 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); } } else { throw new IOException("File not in-sync."); } } }
///<inheritdoc> public async Task MoveToAsync(string userFileSystemNewPath, byte[] newParentItemId, IOperationContext operationContext, IConfirmationResultContext resultContext) { string userFileSystemOldPath = this.UserFileSystemPath; Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(MoveToAsync)}()", userFileSystemOldPath, userFileSystemNewPath); string remoteStorageOldPath = RemoteStoragePath; string remoteStorageNewParentPath = WindowsFileSystemItem.GetPathByItemId(newParentItemId); string remoteStorageNewPath = Path.Combine(remoteStorageNewParentPath, Path.GetFileName(userFileSystemNewPath)); FileSystemInfo remoteStorageOldItem = FsPath.GetFileSystemItem(remoteStorageOldPath); if (remoteStorageOldItem != null) { if (remoteStorageOldItem is FileInfo) { (remoteStorageOldItem as FileInfo).MoveTo(remoteStorageNewPath, true); } else { (remoteStorageOldItem as DirectoryInfo).MoveTo(remoteStorageNewPath); } Logger.LogMessage("Moved item in remote storage succesefully", userFileSystemOldPath, userFileSystemNewPath); } }
/// <summary> /// Creates or updates file in the remote storage. /// </summary> /// <param name="remoteStoragePath">Path of the file to be created or updated in the remote storage.</param> /// <param name="newInfo">New information about the file, 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="newContentStream">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. Null if the item is not locked.</param> /// <returns>The new ETag returned from the remote storage.</returns> protected async Task <string> CreateOrUpdateFileAsync( string remoteStoragePath, IFileMetadata newInfo, FileMode mode, Stream newContentStream = null, string eTagOld = null, ServerLockInfo lockInfo = null) { FileInfo remoteStorageItem = new FileInfo(remoteStoragePath); try { Program.VirtualDrive.RemoteStorageMonitor.Enabled = false; // Disable RemoteStorageMonitor to avoid circular calls. // If another thread is trying to sync the same file, this call will fail in other threads. // In your implementation you must lock your remote storage file, or block it for reading and writing by other means. await using (FileStream remoteStorageStream = remoteStorageItem.Open(mode, FileAccess.Write, FileShare.None)) { string userFileSystemPath = Mapping.ReverseMapPath(remoteStoragePath); if (mode == FileMode.Open) { // Verify that the item in the remote storage is not modified since it was downloaded to the user file system. // In your real-life application you will send the ETag to the server as part of the update request. FileSystemItemMetadataExt itemInfo = Mapping.GetUserFileSysteItemMetadata(remoteStorageItem); if (!(await VirtualDrive.GetETagManager(userFileSystemPath).ETagEqualsAsync(itemInfo))) { throw new ConflictException(Modified.Server, "Item is modified in the remote storage, ETags not equal."); } } // Update ETag/LastWriteTime in user file system, so the synchronyzation or remote storage monitor would not start the new update. // This is only required to avoid circular updates because of the simplicity of this sample. // In your real-life application you will receive a new ETag from the server in the update response // and return it from this method. string eTagNew = newInfo.LastWriteTime.ToUniversalTime().ToString("o"); await VirtualDrive.GetETagManager(userFileSystemPath).SetETagAsync(eTagNew); // Update remote storage file content. if (newContentStream != null) { await newContentStream.CopyToAsync(remoteStorageStream); remoteStorageStream.SetLength(newContentStream.Length); } // Update remote storage file basic info. WindowsFileSystemItem.SetFileInformation( remoteStorageStream.SafeFileHandle, newInfo.Attributes, newInfo.CreationTime, newInfo.LastWriteTime, newInfo.LastAccessTime, newInfo.LastWriteTime); return(eTagNew); } } finally { Program.VirtualDrive.RemoteStorageMonitor.Enabled = true; } }
/// <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); } } } }
private async Task CreateOrUpdateFileAsync(string userFileSystemPath, bool create) { try { Program.RemoteStorageMonitorInstance.Enabled = false; // Disable RemoteStorageMonitor to avoid circular calls. FileInfo userFileSystemFile = new FileInfo(userFileSystemPath); FileInfo remoteStorageFile = new FileInfo(remoteStoragePath); FileMode fileMode = create ? FileMode.CreateNew : FileMode.Open; await using (FileStream userFileSystemStream = userFileSystemFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) { // If another thread is trying to sync the same file, this call will fail in other threads. // In your implemntation you must lock your remote storage file, or block it for reading and writing by other means. await using (FileStream remoteStorageStream = remoteStorageFile.Open(fileMode, FileAccess.Write, FileShare.None)) { userFileSystemFile.Refresh(); // Ensures LastWriteTimeUtc is in sync with file content after Open() was called. // Update ETag/LastWriteTime in user file system, so the synchronyzation or remote storage monitor would not start the new update. byte[] customData = BitConverter.GetBytes(userFileSystemFile.LastWriteTime.ToBinary()); PlaceholderItem.SetCustomData(userFileSystemStream.SafeFileHandle, customData); // Update remote storage file content. await userFileSystemStream.CopyToAsync(remoteStorageStream); remoteStorageStream.SetLength(userFileSystemStream.Length); // Update remote storage file basic info. WindowsFileSystemItem.SetFileInformation(remoteStorageStream.SafeFileHandle, userFileSystemFile.Attributes & (FileAttributes) ~FileAttributesExt.Pinned, // Remove Pinned flag. userFileSystemFile.CreationTimeUtc, userFileSystemFile.LastWriteTimeUtc, userFileSystemFile.LastAccessTimeUtc, userFileSystemFile.LastWriteTimeUtc); // If you are using ETags, here you will also send to the remote storage a new file ETag. PlaceholderItem.SetInSync(userFileSystemStream.SafeFileHandle, true); } } } finally { Program.RemoteStorageMonitorInstance.Enabled = true; } }
//$> /// <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); }
/// <inheritdoc/> public async Task <byte[]> CreateFolderAsync(IFolderMetadata folderMetadata) { Logger.LogMessage($"{nameof(IFolder)}.{nameof(CreateFolderAsync)}()", Path.Combine(UserFileSystemPath, folderMetadata.Name)); DirectoryInfo remoteStorageItem = new DirectoryInfo(Path.Combine(RemoteStoragePath, folderMetadata.Name)); remoteStorageItem.Create(); // Update remote storage folder metadata. remoteStorageItem.Attributes = folderMetadata.Attributes; remoteStorageItem.CreationTimeUtc = folderMetadata.CreationTime.UtcDateTime; remoteStorageItem.LastWriteTimeUtc = folderMetadata.LastWriteTime.UtcDateTime; remoteStorageItem.LastAccessTimeUtc = folderMetadata.LastAccessTime.UtcDateTime; remoteStorageItem.LastWriteTimeUtc = folderMetadata.LastWriteTime.UtcDateTime; // Return remote storage item ID. It will be passed later into IEngine.GetFileSystemItemAsync() method. return(WindowsFileSystemItem.GetItemIdByPath(remoteStorageItem.FullName)); }
/// <summary> /// Creates instance of this class. /// </summary> /// <param name="userFileSystemPath">File or folder path in the user file system.</param> /// <param name="itemId">Remote storage item ID.</param> /// <param name="logger">Logger.</param> public VirtualFileSystemItem(string userFileSystemPath, byte[] itemId, ILogger logger) { if (string.IsNullOrEmpty(userFileSystemPath)) { throw new ArgumentNullException(nameof(userFileSystemPath)); } ItemId = itemId ?? throw new ArgumentNullException(nameof(itemId)); Logger = logger ?? throw new ArgumentNullException(nameof(logger)); UserFileSystemPath = userFileSystemPath; try { RemoteStoragePath = WindowsFileSystemItem.GetPathByItemId(ItemId); } catch (ArgumentException) { // When a file is deleted, the IFile.CloseAsync() is called for the deleted file. } }
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> /// 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(); IFileSystemItemBasicInfo info = GetBasicInfo(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); IUserFolder userFolder = await virtualDrive.GetItemAsync <IUserFolder>(userFileSystemParentPath); eTag = await userFolder.CreateFileAsync((IFileBasicInfo)info, userFileSystemStream); } else { IUserFile userFile = await virtualDrive.GetItemAsync <IUserFile>(userFileSystemPath); eTag = await userFile.UpdateAsync((IFileBasicInfo)info, userFileSystemStream, lockInfo); } } else { if (mode == FileMode.CreateNew) { string userFileSystemParentPath = Path.GetDirectoryName(userFileSystemPath); IUserFolder userFolder = await virtualDrive.GetItemAsync <IUserFolder>(userFileSystemParentPath); eTag = await userFolder.CreateFolderAsync((IFolderBasicInfo)info); } else { IUserFolder userFolder = await virtualDrive.GetItemAsync <IUserFolder>(userFileSystemPath); eTag = await userFolder.UpdateAsync((IFolderBasicInfo)info, lockInfo); } } await ETag.SetETagAsync(userFileSystemPath, eTag); if (mode == FileMode.CreateNew) { PlaceholderItem.GetItem(userFileSystemPath).SetOriginalPath(userFileSystemPath); } } finally { if (userFileSystemStream != null) { userFileSystemStream.Close(); } } PlaceholderItem.SetInSync(userFileSystemWinItem.SafeHandle, true); } }
static async Task Main(string[] args) { // Load Settings. IConfiguration configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json", false, true).Build(); Settings = configuration.ReadSettings(); // Load Log4Net for net configuration. var logRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); XmlConfigurator.Configure(logRepository, new FileInfo(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "log4net.config"))); // Enable UTF8 for Console Window Console.OutputEncoding = System.Text.Encoding.UTF8; log.Info($"\n{System.Diagnostics.Process.GetCurrentProcess().ProcessName} {Settings.AppID}"); log.Info("\nPress 'Q' to unregister file system, delete all files/folders and exit (simulate uninstall with full cleanup)."); log.Info("\nPress 'q' to unregister file system and exit (simulate uninstall)."); log.Info("\nPress any other key to exit without unregistering (simulate reboot)."); log.Info("\n----------------------\n"); // Typically you will register sync root during your application installation. // Here we register it during first program start for the sake of the development convenience. if (!await Registrar.IsRegisteredAsync(Settings.UserFileSystemRootPath)) { log.Info($"\nRegistering {Settings.UserFileSystemRootPath} sync root."); Directory.CreateDirectory(Settings.UserFileSystemRootPath); await Registrar.RegisterAsync(SyncRootId, Settings.UserFileSystemRootPath, Settings.ProductName, Path.Combine(Settings.IconsFolderPath, "Drive.ico")); // Set root item ID. It will be passed to IEngine.GetFileSystemItemAsync() method // as itemId parameter when a root folder is requested. byte[] itemId = WindowsFileSystemItem.GetItemIdByPath(Settings.RemoteStorageRootPath); PlaceholderFolder.GetItem(Settings.UserFileSystemRootPath).SetItemId(itemId); } else { log.Info($"\n{Settings.UserFileSystemRootPath} sync root already registered."); } // Log indexed state. StorageFolder userFileSystemRootFolder = await StorageFolder.GetFolderFromPathAsync(Settings.UserFileSystemRootPath); log.Info($"\nIndexed state: {(await userFileSystemRootFolder.GetIndexedStateAsync())}\n"); ConsoleKeyInfo exitKey; try { Engine = new VirtualEngine(Settings.UserFileSystemLicense, Settings.UserFileSystemRootPath, Settings.RemoteStorageRootPath, log); // Start processing OS file system calls. await Engine.StartAsync(); #if DEBUG // Opens Windows File Manager with user file system folder and remote storage folder. ShowTestEnvironment(); #endif // Keep this application running until user input. exitKey = Console.ReadKey(); } finally { Engine.Dispose(); } if (exitKey.KeyChar == 'q') { // Unregister during programm uninstall. await Registrar.UnregisterAsync(SyncRootId); log.Info($"\n\nUnregistering {Settings.UserFileSystemRootPath} sync root."); log.Info("\nAll empty file and folder placeholders are deleted. Hydrated placeholders are converted to regular files / folders.\n"); } else if (exitKey.KeyChar == 'Q') { log.Info($"\n\nUnregistering {Settings.UserFileSystemRootPath} sync root."); log.Info("\nAll files and folders placeholders are deleted.\n"); // Unregister during programm uninstall and delete all files/folder. await Registrar.UnregisterAsync(SyncRootId); try { Directory.Delete(Settings.UserFileSystemRootPath, true); } catch (Exception ex) { log.Error($"\n{ex}"); } } else { log.Info("\n\nAll downloaded file / folder placeholders remain in file system. Restart the application to continue managing files.\n"); } }