Item Change Events

On Windows the Engine provides ItemsChanging and ItemsChanged events that are fired before and after item change on every item creation, update, delete and move operation, for both incoming and outgoing updates. The ItemsChanging event is also fired on download and listing operations progress. Inside these events you can obtain information about each affected item, direction of the synchronization and result status of the operation.

Both events provides a list of affected items in the ItemsChangeEventArgs.Items property. The direction of the synchronization operation is provided in the ItemsChangeEventArgs.Direction property, which can be incoming or outgoing. The source of the operation, that is if the operation was initiated by the client (for example for hydration or population), or by the server (in case of the IServerNotifications interface methods calls) can be determined using the ItemsChangeEventArgs.Source property. 

Tracking Progress

ItemsChanging and ItemsChanged events can be used to track progress, during files hydration and on folders population. You can use these events to render a custom progress UI or for logging purposes. You can capture start and completion of the operation inside the ItemsChanging and ItemsChanged events as well as you can track progress inside ItemsChanging event. The current position is provided in ItemsChangeEventArgs.Position property. The total number of items (in case of population) or bytes (in case of hydration) is provided in ItemsChangeEventArgs.Length property.

The type of the event is provided in the ItemsChangeEventArgs.NotificationTime property. It indicates if the event is fired before the operation, after the operation or this is a progress event. 

Below is an example of ItemsChanging event logging hydration progress and population progress:

Engine.ItemsChanging += Engine_ItemsChanging;

private void Engine_ItemsChanging(Engine sender, ItemsChangeEventArgs e)
{
    // Log info about the opertion.
    switch (e.OperationType)
    {
        case OperationType.Populate:
            // Log a single parent folder for folder population.
            LogItemChanging(e, e.Parent);
            break;
        default:
            // Log each item in the list for all other operations.
            foreach (ChangeEventItem item in e.Items)
            {
                LogItemChanging(e, item);
            }
            break;
    }
}

private void LogItemChanging(ItemsChangeEventArgs e, ChangeEventItem item)
{
    ILogger logger = Logger.CreateLogger(e.ComponentName);
    string msg = $"{e.Direction} {e.OperationType}:{e.NotificationTime}";

    // Log progress.
    if (e.NotificationTime.HasFlag(NotificationTime.Progress))
    {
        long progress = e.Position * 100 / (e.Length > 0 ? e.Length : 1);
        msg = $"{msg}: {progress}%";
    }
            
    logger.LogDebug(msg, item.Path, item.NewPath, e.OperationContext, item.Metadata);
}

Note that progress UI in Windows Explorer and macOS Finder, displayed in the Status column during file hydration is rendered automatically by the platform as soon as you feed data inside your IFile.Readsync() method call. You do not need to program progress in Windows Explorer and macOS Finder.

ItemsChanged Event

In addition to properties provided in both events, the ItemsChanged event provides a detailed result of the operation in  the ItemsChangeEventArgs.Result property. The OperationResult.Status describes if the item was successfully updated/created/deleted/moved or the reason for the failure, such as exception, item being not found (for example because of the on-demand population), being in-use or filtered inside the Engine.FilterAsync() method.

You can use the ItemsChanged event for multiple purposes, such as:

Below is a logging code that you can put ingo ItemsChanged event to collect information about affected items and status of the operation:

private void Engine_ItemsChanged(Engine sender, ItemsChangeEventArgs e)
{
    foreach (ChangeEventItem item in e.Items)
    {
        ILogger logger = Logger.CreateLogger(e.ComponentName);
        string msg = $"{e.Direction} {e.OperationType}: {e.Result.Status}";
        switch (e.Result.Status)
        {
            case OperationStatus.Success:
            case OperationStatus.Conflict:
                logger.LogMessage(msg, item.Path, item.NewPath, e.OperationContext);
                break;
            case OperationStatus.Exception:
                logger.LogError(msg, item.Path, item.NewPath, e.Result.Exception);
                break;
            case OperationStatus.Filtered:
                msg = $"{msg} by {e.Result.FilteredBy.GetType().Name}";
                logger.LogDebug(msg, item.Path, item.NewPath, e.OperationContext);
                break;
            default:
                msg = $"{msg}. {e.Result.Message}";
                logger.LogDebug(msg, item.Path, item.NewPath, e.OperationContext);
                break;
        }
    }
}

 

Next Article:

Managing Custom Properties