Creating Virtual File System in .NET

In this article, we will provide you with step-by-step instructions on creating a virtual file system for Windows in .NET with basic functionality such as on-demand folder content listing, on-demand file content hydration, offline files support, client-to-server, and server-to-client synchronization. In this article, we will use a folder in a local file system as a remote storage simulation. 

This article covers basic virtual file system functionality. For advanced topics such as locking refer to the Creating Virtual Drive in .NET article.

The Core Engine Interfaces

There are 2 types of items in a virtual file system: folders, represented by IFolder interface and files represented by IFile interface. Both IFolder and IFile interfaces are derived from IFileSystemItem interface. Files and folders are generated by the factory method called GetFileSystemItemAsync() that you must override in your class derived from EngineWindows or EngineMacBelow is a typical structure of classes in your project:

  Typical Virtual File System project class diagram

Creating a .NET Project 

Create a .NET console application. In this article, we will use the .NET 5 as a target framework.

Add an ITHit.FileSystem.Windows module reference to your project. In Visual Studio you can do this via the Project->Manage NuGet Packages menu:

 Adding ITHit.FileSystem.Windows Nuget reference.

The IT Hit User File System on Windows requires Windows Runtime API support. To be able to use it we will set the target framework moniker in the project file. Open the project file and change the TargetFramework element:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0-windows10.0.19041.0</TargetFramework>
  </PropertyGroup>
  ...
</Project>

You can find more framework monikers for other Windows versions (as well as how to target .NET Core 3.1 if needed) in the Call Windows Runtime APIs in desktop apps article.

To inform the Windows platform about a new virtual file system and to receive file system API calls you must register a new file system (register a sync root in terms of Windows Cloud Files API). Typically you will do this during your application's first start. You can also do this during the installation process if you are using a regular .msi installer. To register you will use the StorageProviderSyncRootManager class. Create a Registrar class and add it to your project:

public static class Registrar
{
    /// <summary>
    /// Registers sync root.
    /// </summary>
    /// <param name="syncRootId">ID of the sync root.</param>
    /// <param name="path">A root folder of your user file system.</param>
    /// <param name="displayName">Human readable display name.</param>
    /// <remarks>Call this method during application installation.</remarks>
    public async Task RegisterAsync(string syncRootId, string path, string displayName)
    {
        StorageProviderSyncRootInfo storageInfo = new StorageProviderSyncRootInfo();
        storageInfo.Path = await StorageFolder.GetFolderFromPathAsync(path);
        storageInfo.Id = syncRootId;
        storageInfo.DisplayNameResource = displayName;
        storageInfo.IconResource = @"C:\Proj\Icons\Drive.ico";
        storageInfo.Version = "1.0";
        storageInfo.RecycleBinUri = new Uri("https://userfilesystem.com/recyclebin");
        storageInfo.Context = CryptographicBuffer.ConvertStringToBinary(
        path, BinaryStringEncoding.Utf8);

        storageInfo.HydrationPolicy = StorageProviderHydrationPolicy.Progressive;
        storageInfo.HydrationPolicyModifier = 
                StorageProviderHydrationPolicyModifier.AutoDehydrationAllowed;
 
        // To support on-demand population set this property to 'Full'.
        storageInfo.PopulationPolicy = StorageProviderPopulationPolicy.Full;
 
        // This configures when PlaceholderItem.GetInSync() should return true/false.
        storageInfo.InSyncPolicy = StorageProviderInSyncPolicy.Default;
 
        StorageProviderSyncRootManager.Register(storageInfo);
    }

    public static async Task UnregisterAsync(string syncRootId)
    {
        StorageProviderSyncRootManager.Unregister(syncRootId);
    }
}

Note that if you have any regular files and folders under your virtual file system root during registration, by default, they will be considered 'New'. When you start the Engine such files will be automatically converted into placeholders and synced to your remote storage. 

Unregistering the Virtual File System

To unregister the virtual file system you will call the StorageProviderSyncRootManager.Unregister() method passing the sync root ID specified during registration.

Note that you need to unregister the virtual file system only in case you create a regular .msi installer. In case you are creating a packaged installer, you do NOT need to unregister the sync root (and you can not do it because the package does not provide any uninstall events/hooks). The package will automatically delete the sync root registration on uninstall. The package will also automatically delete all folders created by your application, such as your sync root folder, you do not need to delete them manually.

On Windows, in addition to unregistering the virtual file system, you will also delete any data stored by the Engine. To do this call the EngineWindows.UninstallCleanupAsync() method. Again, calling this method is required only in case you are creating a regular .msi installer. In the case of a packaged installer, you do NOT need to call this method, all temporary data will be deleted automatically.  

Implementing Files and Folders Factory Method

When the platform receives a call via its file system API, such as folder listing or reading file content, the Engine will first request a folder or file item by calling the IEngine.GetFileSystemItemAsync() virtual factory method. The Engine will pass the user file system path, an item type (file or folder), and the remote storage item ID as parameters. Then, the Engine will call IFile or IFolder interface methods.

Create a VirtualEngine class in your project and derive it from EngineWindows class. Then override the GetFileSystemItemAsync() method, which will return your files and folders:

public class VirtualEngine : EngineWindows
{
    public VirtualEngine(string license, string userFileSystemRootPath) 
            : base(license, userFileSystemRootPath)
    {
    }

    public override async Task<IFileSystemItem> GetFileSystemItemAsync(
        string userFileSystemPath, 
        FileSystemItemType itemType, 
        byte[] remoteStorageItemId = null)
    {
        if (itemType == FileSystemItemType.File)
        {
            return new VirtualFile(remoteStorageItemId);
        }
        else
        {
            return new VirtualFolder(remoteStorageItemId);
        }
    }
}

Note that typically, for performance reasons, you should NOT make any server calls inside your GetFileSystemItemAsync() method implementation. Instead, you will just create an item and return it to the Engine. You will make server calls inside your IFile and IFolder method implementations.

Remote Storage Item ID

Each file and folder in the User File System can store an item identifier that helps to link the user file system item to your remote storage item. The maximum size of the item identifier on Windows is 4KB.

Note that starting with the User File System v4 Beta 2, the remote storage item ID is managed by the Engine and survives Microsoft Office, AutoCAD, and other applications file save lifecycle.

You can read and write item ID using PlaceholderItem class. To set the item ID call the PlaceholderItem.SetRemoteStorageItemId() method:

byte[] itemId = ...
PlaceholderItem item = engine.Placeholders.GetItem(@"C:\Users\User1\VFS\file.ext");
item.SetRemoteStorageItemId(itemId);

To read the item ID call the PlaceholderItem.GetRemoteStorageItemId() method:

PlaceholderItem item = engine.Placeholders.GetItem(@"C:\Users\User1\VFS\file.ext");
byte[] itemId = item.GetRemoteStorageItemId();

Note that the item ID can only be stored with a placeholder item. It can not be stored with regular files and folders. If a placeholder item is converted into a regular file/folder, the item ID is deleted. 

The remoteStorageItemId parameter of the IEngine.GetFileSystemItemAsync() method is passed to GetFileSystemItemAsync() method only if you return it from  IFolder.GetChildrenAsync() method when creating an item, or if you set it by other means, such as by calling PlaceholderItem.SetRemoteStorageItemId() method. If the remote storage item ID was never set, the null is passed to your IFolder.GetFileSystemItemAsync() implementation.

For existing items, you will set the item ID by filling the IFileSystemItemMetadata.RemoteStorageItemId property for each item that you return from IFolder.GetChildrenAsync() method. See the Implementing Folder Content Listing section below for an example. For new items created in the user file system, you must return the remote storage item ID from the IFolder.CreateFileAsync() and IFolder.CreateFolderAsync() methods. See the Creating Files and Folders in the Remote Storage section in this article.

You may also want to set the remote storage item ID for your root folder, before the first Engine run. If you do not set the ID, the remoteStorageItemId parameter passed to the IEngine.GetFileSystemItemAsync() method will be null for your file system root folder.

// Set root item ID. It will be passed to IEngine.GetFileSystemItemAsync() method 
// as a remoteStorageItemId parameter when a root folder is requested. 
// In this example we just read the ID of the remote storage root folder 
// used as a remote storage simulation.
byte[] itemId = WindowsFileSystemItem.GetItemIdByPath(Settings.RemoteStorageRootPath);
engine.Placeholders.GetRootItem().SetRemoteStorageItemId(itemId);

Implementing Folder Content Listing

When the platform makes a folder listing request, the Engine calls the IFolder.GetChildrenAsync() method. In your implementation, you will call your remote storage, create a list with information about each file and folder, and will return it to the Engine by calling the IFolderListingResultContext.ReturnChildrenAsync() method. Below is a sample GetChildrenAsync() method implementation which emulates the remote storage by listing the content of another folder in the file system:

public class VirtualFolder: IFolder
{
    protected readonly string RemoteStoragePath;

    public VirtualFolder(byte[] itemId)
    {
        RemoteStoragePath = WindowsFileSystemItem.GetPathByItemId(itemId);
    }

    public async Task GetChildrenAsync(
        string pattern, 
        IOperationContext operationContext, 
        IFolderListingResultContext resultContext,
        CancellationToken cancellationToken = default)
    {
        var remoteStorageChildren = 
            new DirectoryInfo(RemoteStoragePath).EnumerateFileSystemInfos(pattern);

        var userFileSystemChildren = new List<IFileSystemItemMetadata>();
        foreach (FileSystemInfo remoteStorageItem in remoteStorageChildren)
        {
            var itemInfo = Mapping.GetUserFileSysteItemMetadata(remoteStorageItem);
            userFileSystemChildren.Add(itemInfo);
        }

        resultContext.ReturnChildren(
            userFileSystemChildren.ToArray(), 
            userFileSystemChildren.Count());
    }
    ...
}

You can call the IFolderListingResultContext.ReturnChildren() method multiple times, returning the list of files and folders in several turns. To specify the total amount of children, the ReturnChildren() method provides a second parameter, so the platform knows when children's enumeration is completed.

Note that on Windows, unless you implement streaming mode, the GetChildrenAsync() method is called only one time during the initial on-demand population. After the initial call, you will update the folder content using a pull or push approach described below in this article in the Remote Storage to User File System Synchronization section.

Each item in the list returned to the Engine must be of the type IFileMetadata or IFolderMetadata. The User File System library provides FileMetadata and FolderMetadata classes that you can use out of the box in many cases. Below is the GetUserFileSysteItemMetadata() method implementation used in the the above example: 

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();
    }

    // Set your remote storage item ID here. 
    // It will be passed to the IEngine.GetFileSystemItemAsync() during every operation.
    userFileSystemItem.RemoteStorageItemId = 
        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;
}

Starting the Engine

Now, when you have a file system registered and folder listing implemented you can start processing file system calls. To do this you will create a VirtualEngine class instance and call the IEngine.StartAsync() method. Below is an example of your Main() application method. For the sake of simplicity, in this example, we will register the virtual file system on application start and unregister on exit.

static async Task Main(string[] args)
{
    // Note that your root file system path must be indexed.
    string userFileSystemRootPath = @"C:\Users\User1\VFS\";

    // Register file system.
    Directory.CreateDirectory(userFileSystemRootPath);
    await Registrar.RegisterAsync(SyncRootId, userFileSystemRootPath, "VFS");

    using (var engine = new VirtualEngine(license, userFileSystemRootPath))
    {
        // Set remote storage root item ID.
        byte[] itemId = WindowsFileSystemItem.GetItemIdByPath(@"C:\RemoteStorage\");
        engine.Placeholders.GetRootItem().SetRemoteStorageItemId(itemId);

        // Start processing OS file system calls.
        await engine.StartAsync();

        // Keep this application running until user input.
        Console.ReadKey();
    }

    // Unregister during programm uninstall and delete all files/folder.
    await Registrar.UnregisterAsync(SyncRootId);
    Directory.Delete(userFileSystemRootPath, true);
}

// SyncRoot ID must be in the form: [Storage Provider ID]![Windows SID]![Account ID]
private static string SyncRootId
{
    get { return $"VirtualFileSystem!{WindowsIdentity.GetCurrent().User}!User"; }
}

When the Engine starts, it finds all files that were created, updated, moved, and deleted in the virtual file system when the engine was not running and calls IFile and IFolder intarface methods. It also hydrates all pinned files and dehydrates unpinned files. For example, if you are starting the engine for the first time and there were any files or folders they will be considered new and will be synced to your remote storage by calling IFolder.CreateFileAsync() and IFolder.CreateFolderAsync()

If you do not want to process changes, pass false as a parameter to the StartAsync() method. See the User File System to Remote Storage Synchronization section for more details.

Reading File Content (Hydration)

The process of downloading the file content from the remote storage to the user file system is called Hydration. When files are initially created in the user file system during the IFolder.GetChildrenAsync() method call they do not have any content on disk. Even though they report a correct file size, they are dehydrated. Such files are marked with an offline attribute and have a cloud icon Offline file in the Windows File Manager in the Status column:

Virtual File System in Windows Explorer with dehydrated files

When the platform detects that the file hydration is required, for example when an application opens a file handle for reading, the Engine calls the IFile.ReadAsync() method passing offset and a length of the block of the file content requested by the platform. It will also pass the output stream to which you will write the data. Below we provide an example of the ReadAsync() method implementation with a file system being used as a remote storage simulation:

public class VirtualFile: IFile
{
    protected readonly string RemoteStoragePath;

    public VirtualFile(byte[] itemId)
    {
        RemoteStoragePath = WindowsFileSystemItem.GetPathByItemId(itemId);
    }

    public async Task ReadAsync(
        Stream output, 
        long offset, 
        long length, 
        ITransferDataOperationContext operationContext, 
        ITransferDataResultContext resultContext,
        CancellationToken cancellationToken = default)
    {
        await using (FileStream stream = System.IO.File.OpenRead(RemoteStoragePath))
        {
            stream.Seek(offset, SeekOrigin.Begin);
            // Buffer size must be multiple of 4096 bytes for optimal performance.
            const int bufferSize = 0x500000; // 5Mb. 
            await stream.CopyToAsync(output, bufferSize, length);
        }
    }
    ...
}

As soon as the regular Stream.CopyToAsync() does not support the length of the data to be copied, here is the CopyToAsync() extension method with count parameter:

public static async Task CopyToAsync(
    this Stream source, 
    Stream destination, 
    int bufferSize, 
    long count)
{
    byte[] buffer = new byte[bufferSize];
    int read;
    while (count > 0 
        && (read = await source.ReadAsync(
            buffer, 
            0, 
            (int)Math.Min(buffer.LongLength, count))) > 0)
    {
        await destination.WriteAsync(buffer, 0, read);
        count -= read;
    }
}

During the download process, the platform automatically calculates and displays the download progress:  

File download progress in Windows Explorer 

Optionally you can also call the IResultContext.ReportProgress() method to report the progress to the platform.

When the download is completed the file icon turns into a checkbox on a white background , meaning the file is on disk and is in the in-sync state:

Hydrated files are marked with green checkbox in Windows Explorer

Note that hydrated files, marked with  icons, can be purged from the file system in case there is not enough space on the disk. To avoid this, the user can "pin" the file by calling the "Always keep on this device" menu in Windows File Manager. In this case, the file will be marked with Pinned attribute and will remain in the file system regardless of the remaining disk space.

Writing File Content

When the file content or file metadata is modified and needs to be uploaded to the remote storage the Engine calls IFile.WriteAsync() method. The Engine passes updated metadata and a file content stream as parameters.

In some cases, however, if the file content is not modified, or if the file is blocked, the content parameter will be null. The content parameter is also null after the move operation (if the file content was not modified). See the "Synchronization After the Move Operation" for more details about processing files after the move.

An example below is using a file system as a remote storage simulation: 

public class VirtualFile: IFile
{
    ...
    public async Task WriteAsync(
        IFileMetadata fileMetadata, 
        Stream content = null, 
        IOperationContext operationContext = null, 
        IInSyncResultContext inSyncResultContext = null,
        CancellationToken cancellationToken = default)
    {
        FileInfo remoteStorageItem = new FileInfo(RemoteStoragePath);

        if (content != null)
        {
            // Upload remote storage file content.
            await using (FileStream remoteStorageStream = remoteStorageItem.Open(
            FileMode.Open, FileAccess.Write, FileShare.Delete))
            {
                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;
    }
}

If the WriteAsync() method completes without exceptions the file is marked as in-sync. Otherwise, the file is left in the not in-sync state. 

Creating Files and Folders in the Remote Storage

When a file or a folder is being created in the user file system the Engine calls IFolder.CreateFileAsync() or IFolder.CreateFolderAsync() methods. The Engine passes a new item metadata and, in the case of a file, the file content stream as parameters. The file content stream may be null if the file is blocked. Below is an example of CreateFileAsync() and CreateFolderAsync() methods implementation with a file system being used as a remote storage simulation:

public class VirtualFolder: IFolder
{
    ...
    public async Task<byte[]> CreateFileAsync(
        IFileMetadata fileMetadata, 
        Stream content = null, 
        IInSyncResultContext inSyncResultContext = null,
        CancellationToken cancellationToken = default)
    {
        FileInfo remoteStorageItem = 
            new FileInfo(Path.Combine(RemoteStoragePath, fileMetadata.Name));

        // Create file in the remote storage.
        await using (FileStream remoteStorageStream = remoteStorageItem.Open(
        FileMode.CreateNew, FileAccess.Write, FileShare.Delete))
        {
            if (content != null)
            {
                await content.CopyToAsync(remoteStorageStream);
                remoteStorageStream.SetLength(content.Length);
            }
        }

        // Set 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 your remote storage item ID. 
        // It will be passed later into IEngine.GetFileSystemItemAsync() method.
        return WindowsFileSystemItem.GetItemIdByPath(remoteStorageItem.FullName);
    }
}

CreateFolderAsync() method implementation example:

public class VirtualFolder: IFolder
{
    ...
    public async Task<byte[]> CreateFolderAsync( 
        IFolderMetadata folderMetadata, 
        IInSyncResultContext inSyncResultContext = null, 
        CancellationToken cancellationToken = default)
    {
        DirectoryInfo remoteStorageItem = 
            new DirectoryInfo(Path.Combine(RemoteStoragePath, folderMetadata.Name));
        remoteStorageItem.Create();

        // Set 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);
    }
}

You will return the new item ID from IFolder.CreateFileAsync() and IFolder.CreateFolderAsync() methods. This ID will then be passed to the IEngine.GetFileSystemItemAsync() method as a remoteStorageItemId paramater during every file system operation.

If the CreateFileAsync() method completes without exceptions and the content parameter is NOT null, the file is converted into a placeholder. Otherwise, the file remains a regular file. If the content parameter is null, you will typically create a 0-length file in your remote storage and will upload content at a later time, when the file handle is released. 

If the CreateFolderAsync() method completes without exceptions the folder is converted into a placeholder, otherwise, the folder remains a regular folder.

Setting the Result of the Operation

In most cases, if the create, write, move or delete operation completes successfully you would want the item to be marked as in-sync. You can also explicitly set the result of the operation, without throwing any exception. To explicitly set the result, each method that modifies the remote storage provides the IInSyncResultContext interface passed as a parameter. To set the result of the operation you can set the IInSyncResultContext.SetInSync property:

public async Task<byte[]> CreateFileAsync(
IFileMetadata fileMetadata, 
Stream content = null, 
IInSyncResultContext inSyncResultContext = null,
CancellationToken cancellationToken = default)
{
    if( /* server unavailable */ )
    {
        inSyncResultContext.SetInSync = false;
        return null;
    }
    ...
}

The SetInSync property is set to true by default before calling each method. Setting the IInSyncResultContext.SetInSync property is identical to throwing an exception inside your method implementation.

The result context parameter that implements the IInSyncResultContext interface is passed to the following methods: Folder.CreateFileAsync(), IFolder.CreateFolderAsync(), IFile.WriteAsync(), IFolder.WriteAsync(),  IFileSystemItem.MoveToCompletionAsync(), and IFileSystemItem.DeleteCompletion().

To process all items that failed to create, write, move or delete you can call the EngineWindows.ProcessAsync() method. See the User File System to Remote Storage Synchronization section below.

User File System to Remote Storage Synchronization

To sync all changes from your user file system to the remote storage the Engine provides EngineWindows.ProcessAsync() method. This method finds all files that were created, updated, moved, and deleted in the virtual file system and synchs them to the remote storage. It also hydrates all pinned files (marked with the "Keep on this device" menu) and dehydrates unpinned files (marked with the "Free up space." menu).

Typically you will call the ProcessAsync() method after restoring connection to server or authentication events, to process all changes that we were made while your client was offline.

The ProcessAsync() method is also automatically called when you start the Engine if you pass true to the Engine.StartAsync() method.

The IClientNotifications interface methods that were used for updating and creating items that were used for synchronization in v3 and earlier are removed in Engine v4 Beta 2 and later versions. 

Remote Storage to User File System Synchronization

To apply changes made in your remote storage in the user file system, you will either periodically pull your server for changes or implement push notifications from your remote storage to the client machines (via web sockets or similar technologies). In many cases, you will implement both approaches. In both cases, you will use the IServerNotifications interface to apply changes received from your remote storage into the user file system.

To get an object implementing IServerNotifications interface you will call the IEngine.ServerNotifications() method, passing the user file system path. 

Because of the on-demand population, the folder placeholder, in which new items are created, or the placeholder that is being updated, deleted, or moved, may not exist in the user file system or maybe offline. In this case, the changes made via IServerNotifications interface are ignored and the method call returns a value indicating that no changes were applied. Typically this return value is useful for logging and testing purposes.

Below we will show how to do this for new items, updated, deleted, and moved items.

To create files and folders in the user file system you will call the IServerNotifications.CreateAsync() method passing the list of items metadata to be created:

IFileSystemItemMetadata item = Mapping.GetUserFileSysteItemMetadata("/Folder/file.ext");
IServerNotifications sn = engine.ServerNotifications(@"C:\Users\User1\VFS\Folder\");
if (await sn.CreateAsync(new[] { item }) > 0)
{
    LogMessage($"Created succesefully");
}

In case the parent folder placeholder does not exist or is offline the IServerNotifications.CreateAsync() call will not create any items and will return 0.

To update a file or folder, call the IServerNotifications.UpdateAsync() method:

IFileSystemItemMetadata item = Mapping.GetUserFileSysteItemMetadata("/file.ext");
IServerNotifications sn = engine.ServerNotifications(@"C:\Users\User1\VFS\file.ext");
if (await sn.UpdateAsync(item))
{
    LogMessage("Updated succesefully");
}

To delete a file or folder call the IServerNotifications.DeleteAsync() method:

IServerNotifications sn = engine.ServerNotifications(@"C:\Users\User1\VFS\file.ext");
if (await sn.DeleteAsync())
{
    LogMessage("Deleted succesefully");
}

To move or rename a file or folder call IServerNotifications.MoveToAsync() method:

IServerNotifications sn = engine.ServerNotifications(@"C:\Users\User1\VFS\file.ext");
if (await sn.MoveToAsync(@"C:\Users\User1\VFS\Folder\file.ext"))
{
    LogMessage("Renamed or moved succesefully");
}

 

Managing Timeout on Windows

On Windows, the following methods have a 60 sec timeout: IFolder.GetChildrenAsync()IFile.ReadAsync()IFileSystemItem.DeleteAsync()IFileSystemItem.DeleteCompletedAsync(), IFileSystemItem.MoveToAsync() and IFileSystemItem.MoveToCompletionAsync(). To reset the timput you can call the IResultContext.ReportProgress() method. However, in case of the IFolder.GetChildrenAsync() method it will only reset the timer, the ReportProgress() call has no impact on progress display in Windows File Manager.

Inside the IFolder.GetChildrenAsync() method the IFolderListingResultContext.ReturnChildren() call will also reset the timeout.

In the IFile.ReadAsync() method the timeout is reset each time you write data to the output stream. However, the timeout is reset only if the stream is advanced by 4K or more. Otherwise, the content is buffered and the stream waits for more data to be written.

 

Next Article:

Creating Thumbnails Provider Shell Extension for Virtual Drive