0

We are utilising the StorageLibraryChangeTracker class to monitor a folder on a USB attached disk drive for changes. Our implementation uses a timer that checks every 500ms for changes.

This works fine, unless the laptop goes to sleep. In the case where the laptop goes to sleep, even after restarting the app, we receive an exception with a 80080222 error from within the CheckForFolderChangesAsync() method.

I have searched but cannot find any information on the 80080222 error.

The only way to get everything working again is to restart the machine but this is obviously unacceptable.

public class ImageFolderMonitoringService
{
    private ILogger _log = LogManagerFactory.DefaultLogManager.GetLogger<ImageFolderMonitoringService>();
    private StorageFolder _currentFolder;
    private PdgDiskId _currentDiskId;
    private Timer _timer;
    private HashSet<string> _foundRawFiles = new HashSet<string>();
    private HashSet<string> _foundJpgFiles = new HashSet<string>();

    private int _errorCount = 0;

    public ImageFolderMonitoringService()
    {
    }

    public event EventHandler<FileReceivedEvent> FileReceived;

    public async Task<StartTrackingResult> StartTracking(StorageFolder folder)
    {
        _log.Trace("StartTracking()");

        _currentFolder = folder;

        var diskResult = await TryGetDiskId(folder);

        if (!diskResult.WasSuccessful)
        {
            _log.Trace($"Folder tracking startup unsuccessful due to disk id: {diskResult.Error}");
            return new StartTrackingResult { WasSuccessful = false, Error = diskResult.Error };
        }

        _currentDiskId = diskResult.DiskId;
        var changeTracker = _currentFolder.TryGetChangeTracker();
        changeTracker.Enable();
        _timer = new Timer(CheckForFolderChanges);
        _timer.Change(TimeSpan.Zero, Timeout.InfiniteTimeSpan);

        _log.Trace($"Folder tracking startup successful");
        return new StartTrackingResult { WasSuccessful = true, TrackingDisk = diskResult.DiskId };
    }

    public void EndTracking()
    {
        _log.Trace("EndTracking()");

        var changeTracker = _currentFolder.TryGetChangeTracker();

        if (changeTracker != null)
        {
            changeTracker.Reset();
            _timer = null;
            changeTracker = null;
        }
    }

    protected virtual void OnFileReceived(FileReceivedEvent e)
    {
        FileReceived?.Invoke(this, e);
    }

    private void CheckForFolderChanges(object state)
    {
        var ignored = CheckForFolderChangesAsync();
    }

    private async Task CheckForFolderChangesAsync()
    {
        _log.Trace("CheckForFolderChangesAsync()");

        try
        {
            var changeTracker = _currentFolder.TryGetChangeTracker();
            changeTracker.Enable();

            var changeReader = changeTracker.GetChangeReader();

            var changes = await changeReader.ReadBatchAsync();

            foreach (var change in changes)
            {
                _log.Trace($"File changed ({change.ChangeType}): {change.Path}");

                if (change.ChangeType == StorageLibraryChangeType.ChangeTrackingLost)
                {
                    // We are in trouble. Nothing else is going to be valid.
                    _log.Trace("Change tracker indicates lost files. Resetting the change tracker");
                    changeTracker.Reset();
                    break;
                }

                if (change.ChangeType == StorageLibraryChangeType.Created)
                {
                    string extension = Path.GetExtension(change.Path);
                    string filename = Path.GetFileNameWithoutExtension(change.Path);

                    switch (extension.ToLower())
                    {
                        case ".arw":
                        case ".cr2":
                            if (_foundJpgFiles.Contains(filename))
                            {
                                EmitFoundFile(filename + ".jpg");
                                _foundJpgFiles.Remove(filename);
                            }
                            else
                            {
                                _foundRawFiles.Add(filename);
                            }

                            break;
                        case ".jpg":
                            if (_foundRawFiles.Contains(filename))
                            {
                                EmitFoundFile(filename + ".jpg");
                                _foundRawFiles.Remove(filename);
                            }
                            else
                            {
                                _foundJpgFiles.Add(filename);
                            }

                            break;
                    }
                }
            }

            await changeReader.AcceptChangesAsync();
            _errorCount = 0;
            _timer.Change(TimeSpan.FromMilliseconds(500), Timeout.InfiniteTimeSpan);
        }
        catch (Exception ex)
        {
            if (_errorCount < 20)
            {
                _errorCount++;
            }

            _log.Error("Error receiving folder changes. Slowing down change checking frequency to avoid filling logs.", ex);

            _timer.Change(TimeSpan.FromMilliseconds(500 * _errorCount), Timeout.InfiniteTimeSpan);
        }
    }

    private void EmitFoundFile(string filename)
    {
        _log.Trace($"Emitting file found event: {filename}");
        OnFileReceived(new FileReceivedEvent() { FileName = filename, Folder = _currentFolder.Path, DiskId = _currentDiskId.DiskId, FileReceivedDate = DateTime.UtcNow });
    }

    private async Task<GetDiskIdResult> TryGetDiskId(StorageFolder folder)
    {
        _log.Trace("TryGetDiskId()");
        GetDiskIdResult result = new GetDiskIdResult();
        result.WasSuccessful = false;

        try
        {
            var file = await folder.TryGetItemAsync("PdgDiskId.txt");

            if (file == null)
            {
                result.Error = "Disk was not formatted using PDG tools and is missing PdgDiskId file";
                return result;
            }

            using (Stream stream = await folder.OpenStreamForReadAsync("PdgDiskId.txt"))
            using (StreamReader sr = new StreamReader(stream))
            {
                string fileText = await sr.ReadToEndAsync();

                Stream diskKeyPairStream = null;

                try
                {
                    var jsonFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///DiskSigningKey.json"));
                    diskKeyPairStream = (await jsonFile.OpenReadAsync()).AsStream();
                    var keyPair = await DiskSigningKeyPair.Load(diskKeyPairStream);

                    PdgDiskId diskLoader = new PdgDiskId(keyPair);
                    var diskIdRecord = diskLoader.Load(fileText);

                    result.WasSuccessful = true;
                    result.DiskId = diskIdRecord;
                    return result;
                }
                catch (FormatException ex)
                {
                    result.Error = ex.Message;
                    return result;
                }
                finally
                {
                    if (diskKeyPairStream != null)
                    {
                        diskKeyPairStream.Dispose();
                        diskKeyPairStream = null;
                    }
                }
            }
        }
        catch (Exception ex)
        {
            _log.Error("Unexpected error inspecting disk", ex);
            result.Error = "Unexpected error inspecting disk";
            return result;
        }
    }
}

Wyck
  • 10,311
  • 6
  • 39
  • 60
Ross
  • 88
  • 9
  • You probably should stop tracking before laptop goes to sleep and start after it resumes – Pavel Anikhouski Aug 12 '19 at 20:18
  • @PavelAnikhouski - I agree that would make sense. The change tracker only has a few methods on the API. None as far as I can tell stop tracking. Docs here: https://learn.microsoft.com/en-gb/uwp/api/Windows.Storage.StorageLibraryChangeTracker – Ross Aug 12 '19 at 20:21
  • Hello, have you tried testing with a folder on your hard drive? In my tests, the folders on the hard drive were tracked normally. So the reason for this error may be that the USB device is disconnected from the computer during sleep, causing the trace to fail. – Richard Zhang Aug 13 '19 at 08:10
  • @Richasy - Thanks for your input. Your intuition is correct. We only seem to intermittently receive the error with USB disks. However, to satisfy project requirements we must be able to reliably monitor files received on a USB attached drive. Monitoring for changes in UWP apps is not as straight forward as a standard .NET app. This API is the best method I have found and is OK for our purpose apart from this error with regard to USB drives and suspension. I either need to find a way to cleanly deal with suspension or find another API that will allow me to detect files copied on to the disk. – Ross Aug 13 '19 at 09:46
  • A bit more useful information. This only seems to happen on USB disks. Once we start monitoring even after closing the app we cannot safely disconnect the USB drive. It looks like the monitoring gets a lock on the drive and stops it from being removed. However, I cannot find anything in the docs about stopping monitoring or cleaning up. The docs clearly state: "StorageLibraryChangeTracker works for user libraries, or for any folder on the local machine. This includes secondary drives or removable drives but does not include NAS drives or network drives." – Ross Aug 13 '19 at 09:52
  • Further info: The exception is thrown on the changeReader.ReadBatchAsync() call. – Ross Aug 13 '19 at 16:08
  • Further information. We have updated some drivers on the machine. Now we still receive the error but if we just catch it and log it the service continues to work. We would still like to know what the error is referring to and cannot find a reference to an error with the code 80080222. – Ross Aug 15 '19 at 16:38

2 Answers2

0

Use the code from the following post to track when the computer goes to sleep or wakes up: How to check when the computer is going to sleep or waking up

Write code that calls Endtracking when the computer is going to sleep and StartTracking when the computer wakes up.

Edney Holder
  • 1,140
  • 8
  • 22
  • That method won't work from a UWP app. I'm also not aware of any required clean up steps for the StorageLibraryChangeTracker or indeed anyway to stop tracking which happens even when the app is closed down. If anyone can suggest clean up logic I should be performing then that would be great. The docs for the tracking is here: https://learn.microsoft.com/en-gb/windows/uwp/files/change-tracking-filesystem – Ross Aug 12 '19 at 19:57
0

@Ross - thanks for using the change tracker and reporting the issues

There isn't a "stop change tracking" function available, as internally system components will always need to change track the disk. Most of API is really just a thin wrapper around access to the system change tracker without the option to stop it, since it shouldn't be stopped.

However this all seems to be going wrong in your case, a couple things that might help:

From what we can tell locally, the error code you are getting means the change tracker doesn't know about the folder you are looking at. Obviously this is a bad thing for a number of reasons so we're looking at getting it fixed here.

Either way though, this error should have been wrapped in a StorageLibraryChangeType.ChangeTrackingLost entry in the change tracker. Can you try working around it by:

  1. Try killing and restarting the wsearch service. (net stop wsearch from an admin prompt). If change tracking starts working again, that means the issue is with the search indexer loosing its scope over sleep as we're suspecting.
  2. Trying calling changeTracker.Enable() again after getting the error code to kick the system back into shape.

Of course, you will have to treat this as change tracking lost. On indexed locations this means you can use "System.Search.GatherTime property to get the most recently indexed items for a fast catch up.

Let us know if that doesn't work, we'll try to come up with something else.

Adam
  • 982
  • 7
  • 16
  • We now no longer have a problem that breaks tracking after suspension of the laptop after updating some drivers. However, my colleague can get the error to reoccur by removing and reattaching the USB drive. When he does this stopping and restarting the wsearch tracker does then allow us to monitor for changes again without a reboot. Interestingly after removing and reattaching the drive we get a different error on calling enable in our timer loop (The RPC server is unavailable 800706BA). This goes away when we restart the whole process refreshing the _currentFolder variable. HTH – Ross Aug 16 '19 at 12:49
  • We have found some reliable steps to reproduce and resolve the error: 1) Monitor the USB drive. 2) Format the USB drive. 3) Error occurs 4) Kill wsearch service 5) wsearch automatically restarts on its own 6) Error Persists 7) Restart App and Re Request Monitoring Folder 8) Error Cleared. Seems to us after formatting a drive, we either have to restart the computer or restart the wsearch service for monitoring to work for that drive. HTH – Ross Sep 10 '19 at 10:14