907 lines
33 KiB
C#
907 lines
33 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public class DataFolderWatcher : IDisposable
|
|
{
|
|
private readonly FileUpdater m_updater;
|
|
private ILog m_logger = new ConsoleLog();
|
|
private string m_dataFolder;
|
|
private List<string> m_excludeFolders = new List<string>();
|
|
|
|
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<UpdateOperation> m_operations = new List<UpdateOperation>();
|
|
// 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<USNOperations> m_usnOperationsQueue = new Queue<USNOperations>();
|
|
private Dictionary<Int64, USNOperations> m_usnOperationByUSN = new Dictionary<Int64, USNOperations>();
|
|
private USNOperations m_lastQueuedOperation = null;
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// The logger. Cannot be set to a null value.
|
|
/// </summary>
|
|
public ILog Logger
|
|
{
|
|
get { return m_logger; }
|
|
set { if (value != null) m_logger = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Init done?
|
|
/// </summary>
|
|
public bool Initializing
|
|
{
|
|
get
|
|
{
|
|
m_internalMutex.WaitOne();
|
|
bool result = (m_state != State.Running) || !m_lastUsnEntryTreatedValid;
|
|
m_internalMutex.ReleaseMutex();
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The data folder of the library has changed
|
|
/// </summary>
|
|
public void OnDataFolderChanged(string _folder, List<string> _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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Try to get the last USN entry treated. Returns false if no valid entry stored.
|
|
/// </summary>
|
|
private bool GetLastUsnEntryTreated(out Int64 _lastUsn)
|
|
{
|
|
m_internalMutex.WaitOne();
|
|
bool result = m_lastUsnEntryTreatedValid;
|
|
_lastUsn = m_lastUsnEntryTreated;
|
|
m_internalMutex.ReleaseMutex();
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the last USN entry treated.
|
|
/// </summary>
|
|
private void SetLastUsnEntryTreated(bool valid, Int64 _lastUsn)
|
|
{
|
|
m_internalMutex.WaitOne();
|
|
m_lastUsnEntryTreatedValid = valid;
|
|
m_lastUsnEntryTreated = _lastUsn;
|
|
m_internalMutex.ReleaseMutex();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is this a path to one of the excluded folders?
|
|
/// </summary>
|
|
private bool IsInExcludeFolder(string _path)
|
|
{
|
|
string normalizedPath = PathUtils.Normalize(_path);
|
|
foreach (string folder in m_excludeFolders)
|
|
{
|
|
if (normalizedPath.StartsWith(folder))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Does _path points to a data file that should be watched?
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start watching.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stop watching.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ask for the other thread(s) to exit.
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Open or create the USN journal of the volume containing the data folder.
|
|
/// </summary>
|
|
/// <returns>true if init done</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Close the USN journal.
|
|
/// </summary>
|
|
private void CloseVolume()
|
|
{
|
|
if (m_usnJournalRootHandle.ToInt32() != Win32Api.INVALID_HANDLE_VALUE)
|
|
{
|
|
Win32Api.CloseHandle(m_usnJournalRootHandle);
|
|
m_usnJournalRootHandle = IntPtr.Zero;
|
|
}
|
|
|
|
m_folderDictionary.SetJournalHandle(IntPtr.Zero);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Watching thread: Read the NTFS journal of the volume at regular intervals and
|
|
/// check if the entries are relevant.
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go to the CreatingJournal state
|
|
/// </summary>
|
|
private void GotoCreatingJournalState()
|
|
{
|
|
AskForLibraryReset();
|
|
m_lastUsnEntryWatchedValid = false;
|
|
SetLastUsnEntryTreated(false, 0);
|
|
|
|
m_state = State.CreatingJournal;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update state CreatingJournal
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go to the Initializing state
|
|
/// </summary>
|
|
private void GotoInitializingState()
|
|
{
|
|
AskForLibraryReset();
|
|
m_lastUsnEntryWatchedValid = false;
|
|
SetLastUsnEntryTreated(false, 0);
|
|
|
|
m_state = State.Initializing;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update state running. Parse the USN journal looking for interesting entries.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Analyze the journal entry. Update the folder dictionary and signal the data file updater if needed.
|
|
/// </summary>
|
|
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<string> 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<string> 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<string> addedFiles;
|
|
List<string> 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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This file has been changed. Ask the updater to check this path and update the file.
|
|
/// </summary>
|
|
private void FileChanged(string _path, Int64 _usn)
|
|
{
|
|
UpdateOperation updateOperation = m_updater.AskForFileUpdate(_path);
|
|
NewUpdateOperation(_usn, updateOperation);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Keep trace of the created operation so we can know when the USN entry has been
|
|
/// completely treated.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This USN has been ignored. Try to avoid reading it again.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Force the updater to recheck all the files of the data folder.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check which operations are finished to update the last treated USN entry.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cancel all the operations
|
|
/// </summary>
|
|
private void AskForLibraryReset()
|
|
{
|
|
m_updater.ClearLibrary();
|
|
m_usnOperationsQueue.Clear();
|
|
m_usnOperationByUSN.Clear();
|
|
m_lastQueuedOperation = null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Load/Save
|
|
|
|
/// <summary>
|
|
/// Load the state of the watcher that was saved in a library XML.
|
|
/// Called from the library thread before the watcher thread is running.
|
|
/// </summary>
|
|
/// <returns>true if no error occurred. If no LastTreatedUSN found, then return false.</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <returns>true if no error occurred.</returns>
|
|
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
|
|
}
|
|
}
|