using System;
using System.IO;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Threading;
using UAF;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Xml.Linq;
using UAF.Interfaces.Logging;
namespace DataFileAnalyzer
{
///
/// Keep the data file updater informed of all the changes that happen in the data folder
/// so that it can update the files accordingly.
/// Uses the NTFS journal to keep track of the changes that occurred while the
/// application was not running.
///
public class DataFolderWatcher : IDisposable
{
private readonly FileUpdater m_updater;
private ILog m_logger = new ConsoleLog();
private string m_dataFolder;
private List m_excludeFolders = new List();
private ILog m_debugLog = new NullLog(); //ConsoleLog()
// The main thread
private Thread m_thread = null;
// Mutex to lock the members of the class (bool, int, etc...)
private Mutex m_internalMutex = new Mutex(false, "DataFileAnalyzer.DataFolderWatcher.m_internalMutex");
// Event set when the main thread has been started
private readonly AutoResetEvent m_startedSyncEvent = new AutoResetEvent(false);
// Event set when the main thread has been stopped
private readonly AutoResetEvent m_stopSyncEvent = new AutoResetEvent(false);
// Stop as soon as possible
private bool m_exitAsked = false;
// The mainthread is running
private bool m_running = false;
// Have we loaded the data since the last time we started
private bool m_loaded = false;
private enum State
{
Stopped,
CreatingJournal,
Initializing,
Running
}
private State m_state = State.Stopped;
private NtfsFolderDictionary m_folderDictionary;
private IntPtr m_usnJournalRootHandle = IntPtr.Zero;
private static readonly int m_pollingInterval = 10;
private static readonly UInt64 m_journalMaxSize = 10 * 1024 * 1024;
private static readonly UInt64 m_journalAllocationDelta = 16 * 1024;
private bool m_lastUsnEntryWatchedValid = false;
private Int64 m_lastUsnEntryWatched = 0;
private bool m_lastUsnEntryTreatedValid = false;
private Int64 m_lastUsnEntryTreated = 0;
private Win32Api.READ_USN_JOURNAL_DATA m_readUsnData;
private SafeMemoryHandle m_readUsnDataBuffer;
private SafeMemoryHandle m_deviceDataBuffer;
private class USNOperations
{
public Int64 m_usn;
public List m_operations = new List();
// When this operation is finished, consider all the USN up to m_lastLinkedUSN
// as treated. This is avoids re-reading journal entries that will be ignored.
public Int64 m_lastLinkedUSN;
public USNOperations(Int64 _usn)
{
m_usn = _usn;
m_lastLinkedUSN = _usn;
}
public bool IsFinished()
{
bool finished = true;
foreach (UpdateOperation op in m_operations)
finished &= ((op.State & UpdateOperation.States.Finished) != UpdateOperation.States.None);
return finished;
}
}
private Queue m_usnOperationsQueue = new Queue();
private Dictionary m_usnOperationByUSN = new Dictionary();
private USNOperations m_lastQueuedOperation = null;
///
/// Constructor
///
public DataFolderWatcher(FileUpdater _updater)
{
m_updater = _updater;
m_logger = _updater.Logger;
m_dataFolder = null;
m_folderDictionary = new NtfsFolderDictionary(m_logger);
m_readUsnData = new Win32Api.READ_USN_JOURNAL_DATA();
m_readUsnDataBuffer = new SafeMemoryHandle(Marshal.SizeOf(m_readUsnData));
m_deviceDataBuffer = new SafeMemoryHandle(sizeof(UInt64) * 0x4000);
}
public void Dispose()
{
m_deviceDataBuffer.Dispose();
m_readUsnDataBuffer.Dispose();
}
///
/// The logger. Cannot be set to a null value.
///
public ILog Logger
{
get { return m_logger; }
set { if (value != null) m_logger = value; }
}
///
/// Init done?
///
public bool Initializing
{
get
{
m_internalMutex.WaitOne();
bool result = (m_state != State.Running) || !m_lastUsnEntryTreatedValid;
m_internalMutex.ReleaseMutex();
return result;
}
}
///
/// The data folder of the library has changed
///
public void OnDataFolderChanged(string _folder, List _excludedFolders)
{
m_dataFolder = PathUtils.Normalize(_folder);
m_excludeFolders.Clear();
foreach (string folder in _excludedFolders)
m_excludeFolders.Add(PathUtils.Normalize(Path.Combine(m_dataFolder, folder)));
m_folderDictionary.OnDataFolderChanged(m_dataFolder, m_excludeFolders);
}
///
/// Try to get the last USN entry treated. Returns false if no valid entry stored.
///
private bool GetLastUsnEntryTreated(out Int64 _lastUsn)
{
m_internalMutex.WaitOne();
bool result = m_lastUsnEntryTreatedValid;
_lastUsn = m_lastUsnEntryTreated;
m_internalMutex.ReleaseMutex();
return result;
}
///
/// Set the last USN entry treated.
///
private void SetLastUsnEntryTreated(bool valid, Int64 _lastUsn)
{
m_internalMutex.WaitOne();
m_lastUsnEntryTreatedValid = valid;
m_lastUsnEntryTreated = _lastUsn;
m_internalMutex.ReleaseMutex();
}
///
/// Is this a path to one of the excluded folders?
///
private bool IsInExcludeFolder(string _path)
{
string normalizedPath = PathUtils.Normalize(_path);
foreach (string folder in m_excludeFolders)
{
if (normalizedPath.StartsWith(folder))
return true;
}
return false;
}
///
/// Does _path points to a data file that should be watched?
///
private bool IsDataFile(string _path)
{
string normalizedPath = PathUtils.Normalize(_path);
if (normalizedPath.StartsWith(m_dataFolder))
{
foreach (string folder in m_excludeFolders)
{
if (normalizedPath.StartsWith(folder))
return false;
}
return true;
}
return false;
}
///
/// Start watching.
///
public void Start(FileLibrary _library)
{
if (m_dataFolder == null)
{
m_logger.Error("[Watcher] Couldn't start the data folder watcher: no folder set!");
return;
}
if (!m_loaded)
{
// Reset
m_lastUsnEntryTreatedValid = false;
}
m_thread = new Thread(MainThread);
m_thread.Name = "DataFileAnalyzer.DataFolderWatcher.MainThread";
m_thread.Start();
m_startedSyncEvent.WaitOne();
m_running = true;
}
///
/// Stop watching.
///
public void Stop()
{
if (m_running)
{
ExitAsked = true;
m_stopSyncEvent.WaitOne();
m_exitAsked = false;
m_thread = null;
m_dataFolder = null;
m_running = false;
m_loaded = false;
}
}
///
/// Ask for the other thread(s) to exit.
///
private bool ExitAsked
{
get
{
m_internalMutex.WaitOne();
bool result = m_exitAsked;
m_internalMutex.ReleaseMutex();
return result;
}
set
{
m_internalMutex.WaitOne();
m_exitAsked = value;
m_internalMutex.ReleaseMutex();
}
}
#region Watcher thread
///
/// Open or create the USN journal of the volume containing the data folder.
///
/// true if init done
private bool OpenVolume()
{
if (!WindowsUtils.CurrentUserIsAdmin())
{
m_logger.Error("[Watcher] The current Windows user is not an administrator on this machine.");
return false;
}
string equivalentDataPath;
IntPtr rootDriveHandle = Win32Api.GetDriveRootHandle(m_dataFolder, "ntfs", out equivalentDataPath);
if (rootDriveHandle.ToInt32() == Win32Api.INVALID_HANDLE_VALUE)
{
m_logger.Error("[Watcher] Could not get the drive root handle for path '" + m_dataFolder + "'");
return false;
}
m_usnJournalRootHandle = rootDriveHandle;
m_folderDictionary.SetJournalHandle(m_usnJournalRootHandle);
return true;
}
///
/// Close the USN journal.
///
private void CloseVolume()
{
if (m_usnJournalRootHandle.ToInt32() != Win32Api.INVALID_HANDLE_VALUE)
{
Win32Api.CloseHandle(m_usnJournalRootHandle);
m_usnJournalRootHandle = IntPtr.Zero;
}
m_folderDictionary.SetJournalHandle(IntPtr.Zero);
}
///
/// Watching thread: Read the NTFS journal of the volume at regular intervals and
/// check if the entries are relevant.
///
private void MainThread()
{
if (!OpenVolume())
{
m_exitAsked = true;
m_lastUsnEntryWatchedValid = false;
m_state = State.Stopped;
}
else
{
// Set the first USN to watch
m_lastUsnEntryWatchedValid = GetLastUsnEntryTreated(out m_lastUsnEntryWatched);
m_state = m_lastUsnEntryWatchedValid ? State.Running : State.Initializing;
if (m_lastUsnEntryTreatedValid)
m_logger.Message("[Watcher] Start running. Last USN treated: {0:X}.", m_lastUsnEntryTreated);
else
m_logger.Message("[Watcher] Start initializing. No valid USN treated.");
}
// Unlock parent thread
m_startedSyncEvent.Set();
// Main loop
while (!ExitAsked)
{
switch (m_state)
{
case State.Stopped:
ExitAsked = true;
break;
case State.CreatingJournal:
UpdateStateCreatingJournal();
break;
case State.Initializing:
UpdateStateInitializing();
break;
case State.Running:
UpdateStateRunning();
break;
}
// Sleep a while before polling again
Thread.Sleep(m_pollingInterval);
}
UpdateLastUsnEntryTreated();
m_state = State.Stopped;
m_lastUsnEntryWatchedValid = false;
m_usnOperationsQueue.Clear();
m_usnOperationByUSN.Clear();
m_lastQueuedOperation = null;
CloseVolume();
if (m_lastUsnEntryTreatedValid)
m_logger.Message("[Watcher] Stopped. Last USN treated: {0}", m_lastUsnEntryTreated);
else
m_logger.Message("[Watcher] Stopped. No last USN treated.");
// Unlock parent thread
m_stopSyncEvent.Set();
}
///
/// Go to the CreatingJournal state
///
private void GotoCreatingJournalState()
{
AskForLibraryReset();
m_lastUsnEntryWatchedValid = false;
SetLastUsnEntryTreated(false, 0);
m_state = State.CreatingJournal;
}
///
/// Update state CreatingJournal
///
private void UpdateStateCreatingJournal()
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
Win32Api.USN_JOURNAL_DATA journalData;
bool journalCreated = Win32Api.QueryUSNJournalState(m_usnJournalRootHandle, out journalData);
if (!journalCreated)
{
// Try creating a new journal
journalCreated = Win32Api.CreateUSNJournal(m_usnJournalRootHandle, m_journalMaxSize, m_journalAllocationDelta);
}
if (journalCreated)
{
stopwatch.Stop();
m_logger.Message("[Watcher] USN Journal created in {0}.", stopwatch.Elapsed.ToReadableString());
m_state = State.Initializing;
}
}
///
/// Go to the Initializing state
///
private void GotoInitializingState()
{
AskForLibraryReset();
m_lastUsnEntryWatchedValid = false;
SetLastUsnEntryTreated(false, 0);
m_state = State.Initializing;
}
///
/// Update state Initializing. The volume is parsed,
/// we collect data about all the files in the data folder,
/// we notify the updater of all the files we saw then we run.
///
private void UpdateStateInitializing()
{
Win32Api.USN_JOURNAL_DATA journalData;
bool ok = Win32Api.QueryUSNJournalState(m_usnJournalRootHandle, out journalData);
if (!ok)
{
GotoCreatingJournalState();
return;
}
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
// Init the dictionary
m_lastUsnEntryWatched = (journalData.NextUsn == 0) ? 0 : journalData.NextUsn - 1;
m_lastUsnEntryWatchedValid = Win32Api.GetHighestValidUSN(m_usnJournalRootHandle, journalData.LowestValidUsn, ref m_lastUsnEntryWatched);
if (!m_lastUsnEntryWatchedValid || !m_folderDictionary.InitFromFolder())
{
m_lastUsnEntryWatchedValid = false;
m_state = State.Stopped;
return;
}
ListAllFiles();
m_logger.Message("[Watcher] Listing all files done in {0}. Start watching at USN {1}", stopwatch.Elapsed.ToReadableString(), m_lastUsnEntryWatched);
m_state = State.Running;
}
///
/// Update state running. Parse the USN journal looking for interesting entries.
///
private void UpdateStateRunning()
{
UpdateLastUsnEntryTreated();
Win32Api.USN_JOURNAL_DATA journalData;
if (!Win32Api.QueryUSNJournalState(m_usnJournalRootHandle, out journalData))
{
m_logger.Message("[Watcher] Couldn't get journal state. Re-creating journal.");
GotoCreatingJournalState();
return;
}
if (!m_lastUsnEntryWatchedValid || m_lastUsnEntryWatched >= journalData.NextUsn)
{
m_logger.Message("[Watcher] The last watched USN is invalid. Re-initializing.");
GotoInitializingState();
return;
}
Int64 boundaryUsn = journalData.NextUsn;
// Parse the latest journal entries and get the files that has been modified
m_readUsnData.StartUsn = 0; // Will be overwritten
m_readUsnData.ReasonMask = Win32Api.USN_REASON_ALL;
m_readUsnData.ReturnOnlyOnClose = 0;
m_readUsnData.Timeout = 0;
m_readUsnData.bytesToWaitFor = 0;
m_readUsnData.UsnJournalId = journalData.UsnJournalID;
m_readUsnDataBuffer.ZeroMemory();
m_readUsnDataBuffer.FromStructure(m_readUsnData, true);
bool stopReading = false;
Int64 startUsn = m_lastUsnEntryWatched;
//m_debugLog.Message("[Read journal] Start reading from USN {0:X}", startUsn);
m_readUsnDataBuffer.BeginHandleUse();
m_deviceDataBuffer.BeginHandleUse();
while (!stopReading)
{
// Set readData.StartUsn
m_readUsnDataBuffer.WriteInt64(startUsn);
m_deviceDataBuffer.ZeroMemory();
uint bytesRead;
if (Win32Api.DeviceIoControl(
m_usnJournalRootHandle,
Win32Api.FSCTL_READ_USN_JOURNAL,
m_readUsnDataBuffer.Handle,
m_readUsnDataBuffer.Size,
m_deviceDataBuffer.Handle,
m_deviceDataBuffer.Size,
out bytesRead,
IntPtr.Zero))
{
uint totalBytes = bytesRead;
// Skip the next USN stored at the beginning of the buffer
bytesRead -= sizeof(Int64);
if (bytesRead == 0)
{
stopReading = true;
}
else
{
IntPtr usnRecordPointer = m_deviceDataBuffer.Add(sizeof(Int64));
while (bytesRead > 0)
{
Win32Api.UsnEntry usnEntry = new Win32Api.UsnEntry(usnRecordPointer);
Int64 usn = usnEntry.USN;
if (usn >= boundaryUsn)
{
stopReading = true;
break;
}
else
{
if (!m_lastUsnEntryWatchedValid || usn != m_lastUsnEntryWatched)
{
WatchUsnEntry(usnEntry);
m_lastUsnEntryWatchedValid = true;
m_lastUsnEntryWatched = usn;
}
usnRecordPointer = IntPtr.Add(usnRecordPointer, (int)usnEntry.RecordLength);
bytesRead -= usnEntry.RecordLength;
}
}
}
startUsn = m_deviceDataBuffer.ReadInt64();
}
else
{
Win32Api.GetLastErrorEnum lastWin32Error = (Win32Api.GetLastErrorEnum)Marshal.GetLastWin32Error();
if (lastWin32Error != Win32Api.GetLastErrorEnum.ERROR_HANDLE_EOF)
{
m_logger.Error("[Watcher] Error during the FSCTL_READ_USN_JOURNAL operation: 0x{0:X}", Marshal.GetLastWin32Error());
GotoInitializingState();
}
stopReading = true;
}
}
m_readUsnDataBuffer.EndHandleUse();
m_deviceDataBuffer.EndHandleUse();
//m_debugLog.Message("[Read journal] \t\tLast USN watched: {0:X}", m_lastUsnEntryWatched);
}
///
/// Analyze the journal entry. Update the folder dictionary and signal the data file updater if needed.
///
private void WatchUsnEntry(Win32Api.UsnEntry _usnEntry)
{
UInt64 frn = _usnEntry.FileReferenceNumber;
UInt64 parentFrn = _usnEntry.ParentFileReferenceNumber;
//m_debugLog.Message("WatchUsnEntry 0x{0:X}:", _usnEntry.USN);
//m_debugLog.Message("\tReason=0x{0:X}", _usnEntry.Reason);
//m_debugLog.Message("\tName={0}", _usnEntry.Name);
//m_debugLog.Message("\tFull path={0}", m_folderDictionary.GetFRNFullPath(frn));
//m_debugLog.Message("\tOld name={0}", _usnEntry.OldName);
//m_debugLog.Message("\tFRN={0:X}", frn);
//m_debugLog.Message("\tParent FRN={0:X}", parentFrn);
if ((_usnEntry.Reason & Win32Api.USN_REASON_CLOSE) == 0)
{
IgnoreUSN(_usnEntry.USN);
return;
}
bool add = false;
bool delete = false;
bool move = false;
bool modified = false;
if ((_usnEntry.Reason & Win32Api.USN_REASON_FILE_CREATE) != 0)
{
// New file/folder
add = m_folderDictionary.IsFRNInDictionary(parentFrn) && !m_folderDictionary.IsFRNInDictionary(frn);
}
else if ((_usnEntry.Reason & Win32Api.USN_REASON_FILE_DELETE) != 0)
{
// File/folder deleted
delete = m_folderDictionary.IsFRNInDictionary(frn);
}
else if ((_usnEntry.Reason & (Win32Api.USN_REASON_RENAME_NEW_NAME | Win32Api.USN_REASON_RENAME_OLD_NAME)) != 0)
{
Debug.Assert((_usnEntry.Reason & Win32Api.USN_REASON_RENAME_NEW_NAME) != 0, "Non-handled scenario in the NTFS journal parsing!");
if ((_usnEntry.Reason & Win32Api.USN_REASON_RENAME_NEW_NAME) != 0)
{
bool inFolder = m_folderDictionary.IsFRNInDictionary(frn);
bool parentInFolder = m_folderDictionary.IsFRNInDictionary(parentFrn);
if (inFolder)
{
if (!parentInFolder)
{
// File/folder moved to an outside directory. Delete.
delete = true;
}
else
{
// File/folder renamed or moved inside the data folder
move = true;
}
}
else if (parentInFolder)
{
// File/folder moved to the data folder. Add.
add = true;
}
}
else
{
m_logger.Error("[Watcher] Non-handled scenario in the NTFS journal parsing! Call a programmer.");
}
}
else if (
(_usnEntry.Reason & (
Win32Api.USN_REASON_DATA_EXTEND |
Win32Api.USN_REASON_DATA_OVERWRITE |
Win32Api.USN_REASON_DATA_TRUNCATION |
Win32Api.USN_REASON_NAMED_DATA_EXTEND |
Win32Api.USN_REASON_NAMED_DATA_OVERWRITE |
Win32Api.USN_REASON_NAMED_DATA_TRUNCATION)) != 0)
{
modified = m_folderDictionary.IsFRNInDictionary(frn);
}
if (add)
{
if (_usnEntry.IsFile)
{
m_folderDictionary.AddFile(_usnEntry.Name, frn, parentFrn);
FileChanged(m_folderDictionary.GetFRNFullPath(frn), _usnEntry.USN);
//m_debugLog.Message("\tAdded: {0}", m_folderDictionary.GetFRNFullPath(frn));
}
else
{
List addedFiles;
m_folderDictionary.AddFolder(_usnEntry.Name.ToLower(), frn, parentFrn, out addedFiles);
//m_debugLog.Message("\tAdded folder: {0}", m_folderDictionary.GetFRNFullPath(_usnEntry.FileReferenceNumber));
foreach (string addedFile in addedFiles)
{
FileChanged(addedFile, _usnEntry.USN);
//m_debugLog.Message("\t\tAdded: {0}", m_folderDictionary.GetFRNFullPath(addedFrn));
}
}
}
else if (delete)
{
if (_usnEntry.IsFile)
{
string fullPath = m_folderDictionary.GetFRNFullPath(frn);
m_folderDictionary.RemoveFile(frn);
FileChanged(fullPath, _usnEntry.USN);
//m_debugLog.Message("\tRemoved: {0}", fullPath);
}
else
{
List removedFiles;
m_folderDictionary.RemoveFolder(frn, out removedFiles);
foreach (string removedFile in removedFiles)
{
//m_debugLog.Message("\tRemoved: {0}", removedFile);
FileChanged(removedFile, _usnEntry.USN);
}
}
}
else if (move)
{
if (_usnEntry.IsFile)
{
string oldFullPath = m_folderDictionary.GetFRNFullPath(frn);
m_folderDictionary.MoveFile(frn, _usnEntry.Name, parentFrn);
FileChanged(oldFullPath, _usnEntry.USN);
FileChanged(m_folderDictionary.GetFRNFullPath(frn), _usnEntry.USN);
//m_debugLog.Message("\tMoved: {0} -> {1}", oldFullPath, m_folderDictionary.GetFRNFullPath(frn));
}
else
{
string oldFullPath = m_folderDictionary.GetFRNFullPath(frn);
List addedFiles;
List removedFiles;
m_folderDictionary.MoveFolder(frn, _usnEntry.Name, parentFrn, out addedFiles, out removedFiles);
//m_debugLog.Message("\tMoved folder: {0} -> {1}", oldFullPath, m_folderDictionary.GetFRNFullPath(frn));
foreach (string removedFile in removedFiles)
{
FileChanged(removedFile, _usnEntry.USN);
//m_debugLog.Message("\t\tRemoved: {0}", removedFile);
}
foreach (string addedFile in addedFiles)
{
FileChanged(addedFile, _usnEntry.USN);
//m_debugLog.Message("\t\tAdded: {0}", m_folderDictionary.GetFRNFullPath(addedFrn));
}
}
}
else if (modified)
{
Debug.Assert(_usnEntry.IsFile, "Non-handled scenario in the NTFS journal parsing!");
if (_usnEntry.IsFile)
{
FileChanged(m_folderDictionary.GetFRNFullPath(frn), _usnEntry.USN);
}
else
{
m_logger.Error("[Watcher] Non-handled scenario in the NTFS journal parsing! Call a programmer.");
}
}
else
{
IgnoreUSN(_usnEntry.USN);
}
}
///
/// This file has been changed. Ask the updater to check this path and update the file.
///
private void FileChanged(string _path, Int64 _usn)
{
UpdateOperation updateOperation = m_updater.AskForFileUpdate(_path);
NewUpdateOperation(_usn, updateOperation);
}
///
/// Keep trace of the created operation so we can know when the USN entry has been
/// completely treated.
///
private void NewUpdateOperation(Int64 _usn, UpdateOperation _newUpdateOperation)
{
USNOperations usnOperation;
if (!m_usnOperationByUSN.TryGetValue(_usn, out usnOperation))
{
usnOperation = new USNOperations(_usn);
m_usnOperationByUSN.Add(_usn, usnOperation);
m_usnOperationsQueue.Enqueue(usnOperation);
m_lastQueuedOperation = usnOperation;
}
usnOperation.m_operations.Add(_newUpdateOperation);
}
///
/// This USN has been ignored. Try to avoid reading it again.
///
private void IgnoreUSN(Int64 _usn)
{
if (m_lastQueuedOperation != null)
{
m_lastQueuedOperation.m_lastLinkedUSN = _usn;
}
else
{
// If no running operations, then consider this USN as
// the last USN treated so we don't read the journal entry
// again
SetLastUsnEntryTreated(true, _usn);
}
}
///
/// Force the updater to recheck all the files of the data folder.
///
private void ListAllFiles()
{
uint checkExitPeriod = 100;
uint checkExitCounter = checkExitPeriod;
foreach (string filename in m_folderDictionary.GetAllFiles())
{
FileChanged(filename, m_lastUsnEntryWatched);
// Check exit
if (checkExitCounter == 0)
{
if (ExitAsked)
break;
checkExitCounter = checkExitPeriod;
}
else
{
--checkExitCounter;
}
}
}
///
/// Check which operations are finished to update the last treated USN entry.
///
private void UpdateLastUsnEntryTreated()
{
while (m_usnOperationsQueue.Count != 0)
{
USNOperations usnOperation = m_usnOperationsQueue.Peek();
if (usnOperation.IsFinished())
{
// Update the last treated USN
SetLastUsnEntryTreated(true, usnOperation.m_lastLinkedUSN);
// Delete the operation
m_usnOperationsQueue.Dequeue();
m_usnOperationByUSN.Remove(usnOperation.m_usn);
}
else
{
break;
}
}
if (m_usnOperationsQueue.Count == 0)
m_lastQueuedOperation = null;
}
///
/// Cancel all the operations
///
private void AskForLibraryReset()
{
m_updater.ClearLibrary();
m_usnOperationsQueue.Clear();
m_usnOperationByUSN.Clear();
m_lastQueuedOperation = null;
}
#endregion
#region Load/Save
///
/// Load the state of the watcher that was saved in a library XML.
/// Called from the library thread before the watcher thread is running.
///
/// true if no error occurred. If no LastTreatedUSN found, then return false.
public bool Load(XElement _libraryElement)
{
if (m_running)
return false;
m_lastUsnEntryTreatedValid = false;
XElement watcherElement = _libraryElement.Element("DataFolderWatcher");
if (watcherElement != null)
{
XAttribute attr = watcherElement.Attribute("LastTreatedUSN");
if (attr != null && Int64.TryParse(attr.Value, out m_lastUsnEntryTreated))
{
m_lastUsnEntryTreatedValid = m_folderDictionary.Load(watcherElement);
m_loaded = m_lastUsnEntryTreatedValid;
return m_loaded;
}
}
return false;
}
///
/// Save the state of the watcher in a library XML.
/// Called by the library thread. Can be called while
/// the thread is running!
/// Don't save if the last treated USN is invalid.
///
/// true if no error occurred.
public bool Save(XElement _libraryElement)
{
Int64 lastEntryTreated;
if (GetLastUsnEntryTreated(out lastEntryTreated))
{
XElement watcherElement = new XElement("DataFolderWatcher");
_libraryElement.Add(watcherElement);
watcherElement.SetAttributeValue("LastTreatedUSN", lastEntryTreated);
return m_folderDictionary.Save(watcherElement);
}
return false;
}
#endregion
}
}