Setting and Detecting Conflict Status

User File System provides support for marking conflicting items and propagating conflict state to upper levels in Windows Explorer. In this article we will describe how to set and read an item conflict status using User File System API.

Functionality described in this article is available in User File System v6.1 Beta and later versions.

To simulate conflict:

  1. Start the WebDAV Drive sample.
  2. Hydrate the file. At this point client and remote storage eTags match.
  3. Disable storage monitor (press 'm' in the console). This will prevent any changes to propagate from remote storage to user file system.
  4. Edit and save the file in the remote storage (on your WebDAV server, press 'Edit' on web page). This will change the file eTag in remote storage, so it is different from the one saved on the client. If this is a Microsoft Office document, close the file so it is unlocked and can be edited in the next step.
  5. Edit and save the file in the user file system. The conflict icon will appear in the Windows Explorer Status column. The conflict status propagates to the upper folder levels.

User File System file conflict status in Windows Explorer

To set error status you will use the PlaceholderItem.SetErrorStatus() or PlaceholderItem.TrySetErrorStatus() methods. To read error status you will use the PlaceholderItem.GetErrorStatus() or PlaceholderItem.TryGetErrorStatus() methods.

Below is an example of the IFile.WriteAsync() method implementation with conflict detection. When the file is saved and the file content is sent to the server the update fails because eTags do not match, throwing the PreconditionFailedException

public async Task WriteAsync(
    IFileMetadata fileMetadata, 
    Stream content = null, 
    IOperationContext operationContext = null, 
    IInSyncResultContext inSyncResultContext = null, 
    CancellationToken cancellationToken = default)
{
    // Send the ETag to the server as part of the update to ensure
    // the file in the remote storge is not modified since last read.
    PlaceholderItem placeholder = Engine.Placeholders.GetItem(UserFileSystemPath);

    string oldEtag = null;

    if (placeholder.Properties.TryGetValue("ETag", out IDataItem propETag))
    {
        propETag.TryGetValue<string>(out oldEtag);
    }

    // Read the lock-token and send it to the server as part of the update.
    Client.LockUriTokenPair[] lockTokens = null;
    ...

    try
    {
        // Update remote storage file content.
        string newEtag = await Program.DavClient.UploadAsync(
            new Uri(RemoteStoragePath), async (outputStream) =>
            {
                content.Position = 0; // Required in case of retry.
                await content.CopyToAsync(outputStream);
            }, null, content.Length, 0, -1, lockTokens, oldEtag, cancellationToken);

        // Save a new ETag returned by the server, if any.
        await placeholder.Properties.AddOrUpdateAsync("ETag", newEtag);
    }
    catch (PreconditionFailedException)
    {
        // Server and client ETags do not match.
        // Set conflict status in Windows Explorer.

        Logger.LogMessage($"Conflict. The item is modified.", UserFileSystemPath);
        placeholder.SetErrorStatus(true);
        inSyncResultContext.SetInSync = false;
    }
}

 

 To prevent the Engine from syncing items in conflicting state add the ErrorStatusFilter:

public class VirtualEngine : EngineWindows
{
    ...

    public override async Task<bool> FilterAsync(
        SyncDirection direction, 
        OperationType operationType, 
        string path, 
        FileSystemItemType itemType, 
        string newPath = null, 
        IOperationContext operationContext = null)
    {
        ...

        if (await new ErrorStatusFilter().FilterAsync(
            direction, operationType, path, itemType, newPath))
        {
            LogDebug($"{nameof(ErrorStatusFilter)} filtered {operationType}", path);
            return true;
        }

        return false;
    }
}