From 581423a87cebc172f15a3f33086cb9e12528f6dc Mon Sep 17 00:00:00 2001 From: qianlifeng Date: Wed, 22 Oct 2014 18:36:49 +0800 Subject: [PATCH] Add MFT Searcher --- .../MFTSearch/MFTSearchRecord.cs | 27 ++ Wox.Infrastructure/MFTSearch/MFTSearcher.cs | 262 ++++++++++++++++++ .../MFTSearch/MFTSearcherCache.cs | 136 +++++++++ Wox.Infrastructure/MFTSearch/PInvokeWin32.cs | 165 +++++++++++ .../MFTSearch/USNChangeReason.cs | 47 ++++ Wox.Infrastructure/MFTSearch/USNRecord.cs | 34 +++ Wox.Infrastructure/MFTSearch/USNRecordType.cs | 8 + Wox.Infrastructure/MFTSearch/VolumeMonitor.cs | 158 +++++++++++ Wox.Infrastructure/Wox.Infrastructure.csproj | 9 + 9 files changed, 846 insertions(+) create mode 100644 Wox.Infrastructure/MFTSearch/MFTSearchRecord.cs create mode 100644 Wox.Infrastructure/MFTSearch/MFTSearcher.cs create mode 100644 Wox.Infrastructure/MFTSearch/MFTSearcherCache.cs create mode 100644 Wox.Infrastructure/MFTSearch/PInvokeWin32.cs create mode 100644 Wox.Infrastructure/MFTSearch/USNChangeReason.cs create mode 100644 Wox.Infrastructure/MFTSearch/USNRecord.cs create mode 100644 Wox.Infrastructure/MFTSearch/USNRecordType.cs create mode 100644 Wox.Infrastructure/MFTSearch/VolumeMonitor.cs diff --git a/Wox.Infrastructure/MFTSearch/MFTSearchRecord.cs b/Wox.Infrastructure/MFTSearch/MFTSearchRecord.cs new file mode 100644 index 0000000000..3186ec97f6 --- /dev/null +++ b/Wox.Infrastructure/MFTSearch/MFTSearchRecord.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Wox.Infrastructure.MFTSearch +{ + public class MFTSearchRecord + { + private USNRecord usn; + + public MFTSearchRecord(USNRecord usn) + { + this.usn = usn; + } + + public string FullPath + { + get { return usn.FullPath; } + } + + public bool IsFolder + { + get { return usn.IsFolder; } + } + } +} diff --git a/Wox.Infrastructure/MFTSearch/MFTSearcher.cs b/Wox.Infrastructure/MFTSearch/MFTSearcher.cs new file mode 100644 index 0000000000..044969b6d8 --- /dev/null +++ b/Wox.Infrastructure/MFTSearch/MFTSearcher.cs @@ -0,0 +1,262 @@ +/* + * Thanks to the https://github.com/yiwenshengmei/MyEverything, we can bring MFT search to Wox + * + */ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using MyEverything; + +namespace Wox.Infrastructure.MFTSearch +{ + + public class MFTSearcher + { + private static MFTSearcherCache cache = new MFTSearcherCache(); + + public static void IndexVolume(string volume) + { + List files; + List folders; + EnumerateVolume(volume, out files, out folders); + cache.AddRecord(volume, files, USNRecordType.File); + cache.AddRecord(volume, folders, USNRecordType.Folder); + } + + public static void IndexAllVolumes() + { + foreach (DriveInfo drive in DriveInfo.GetDrives()) + { + IndexVolume(drive.VolumeLabel); + } + } + + public static List Search(string item) + { + return null; + } + + private static void AddVolumeRootRecord(string volumeName, ref List folders) + { + string rightVolumeName = string.Concat("\\\\.\\", volumeName); + rightVolumeName = string.Concat(rightVolumeName, Path.DirectorySeparatorChar); + IntPtr hRoot = PInvokeWin32.CreateFile(rightVolumeName, + 0, + PInvokeWin32.FILE_SHARE_READ | PInvokeWin32.FILE_SHARE_WRITE, + IntPtr.Zero, + PInvokeWin32.OPEN_EXISTING, + PInvokeWin32.FILE_FLAG_BACKUP_SEMANTICS, + IntPtr.Zero); + + if (hRoot.ToInt32() != PInvokeWin32.INVALID_HANDLE_VALUE) + { + PInvokeWin32.BY_HANDLE_FILE_INFORMATION fi = new PInvokeWin32.BY_HANDLE_FILE_INFORMATION(); + bool bRtn = PInvokeWin32.GetFileInformationByHandle(hRoot, out fi); + if (bRtn) + { + UInt64 fileIndexHigh = (UInt64)fi.FileIndexHigh; + UInt64 indexRoot = (fileIndexHigh << 32) | fi.FileIndexLow; + + folders.Add(new USNRecord + { + FRN = indexRoot, + Name = volumeName, + ParentFrn = 0, + IsVolumeRoot = true, + IsFolder = true, + VolumeName = volumeName + }); + } + else + { + throw new IOException("GetFileInformationbyHandle() returned invalid handle", + new Win32Exception(Marshal.GetLastWin32Error())); + } + PInvokeWin32.CloseHandle(hRoot); + } + else + { + throw new IOException("Unable to get root frn entry", new Win32Exception(Marshal.GetLastWin32Error())); + } + } + private static void EnumerateVolume(string volumeName, out List files, out List flds) + { + files = new List(); + flds = new List(); + IntPtr medBuffer = IntPtr.Zero; + IntPtr pVolume = IntPtr.Zero; + try + { + AddVolumeRootRecord(volumeName, ref flds); + pVolume = GetVolumeJournalHandle(volumeName); + EnableVomuleJournal(pVolume); + + SetupMFTEnumInBuffer(ref medBuffer, pVolume); + EnumerateFiles(volumeName, pVolume, medBuffer, ref files, ref flds); + } + catch (Exception e) + { + Console.WriteLine(e.Message, e); + Exception innerException = e.InnerException; + while (innerException != null) + { + Console.WriteLine(innerException.Message, innerException); + innerException = innerException.InnerException; + } + throw new ApplicationException("Error in EnumerateVolume()", e); + } + finally + { + if (pVolume.ToInt32() != PInvokeWin32.INVALID_HANDLE_VALUE) + { + PInvokeWin32.CloseHandle(pVolume); + if (medBuffer != IntPtr.Zero) + { + Marshal.FreeHGlobal(medBuffer); + } + } + } + } + internal static IntPtr GetVolumeJournalHandle(string volumeName) + { + string vol = string.Concat("\\\\.\\", volumeName); + IntPtr pVolume = PInvokeWin32.CreateFile(vol, + PInvokeWin32.GENERIC_READ | PInvokeWin32.GENERIC_WRITE, + PInvokeWin32.FILE_SHARE_READ | PInvokeWin32.FILE_SHARE_WRITE, + IntPtr.Zero, + PInvokeWin32.OPEN_EXISTING, + 0, + IntPtr.Zero); + if (pVolume.ToInt32() == PInvokeWin32.INVALID_HANDLE_VALUE) + { + throw new IOException(string.Format("CreateFile(\"{0}\") returned invalid handle", volumeName), + new Win32Exception(Marshal.GetLastWin32Error())); + } + else + { + return pVolume; + } + } + unsafe private static void EnableVomuleJournal(IntPtr pVolume) + { + UInt64 MaximumSize = 0x800000; + UInt64 AllocationDelta = 0x100000; + UInt32 cb; + PInvokeWin32.CREATE_USN_JOURNAL_DATA cujd; + cujd.MaximumSize = MaximumSize; + cujd.AllocationDelta = AllocationDelta; + + int sizeCujd = Marshal.SizeOf(cujd); + IntPtr cujdBuffer = Marshal.AllocHGlobal(sizeCujd); + PInvokeWin32.ZeroMemory(cujdBuffer, sizeCujd); + Marshal.StructureToPtr(cujd, cujdBuffer, true); + + bool fOk = PInvokeWin32.DeviceIoControl(pVolume, PInvokeWin32.FSCTL_CREATE_USN_JOURNAL, + cujdBuffer, sizeCujd, IntPtr.Zero, 0, out cb, IntPtr.Zero); + if (!fOk) + { + throw new IOException("DeviceIoControl() returned false", new Win32Exception(Marshal.GetLastWin32Error())); + } + } + unsafe internal static bool QueryUSNJournal(IntPtr pVolume, out PInvokeWin32.USN_JOURNAL_DATA ujd, out uint bytesReturned) + { + bool bOK = PInvokeWin32.DeviceIoControl( + pVolume, PInvokeWin32.FSCTL_QUERY_USN_JOURNAL, + IntPtr.Zero, + 0, + out ujd, + sizeof(PInvokeWin32.USN_JOURNAL_DATA), + out bytesReturned, + IntPtr.Zero + ); + return bOK; + } + unsafe private static void SetupMFTEnumInBuffer(ref IntPtr medBuffer, IntPtr pVolume) + { + uint bytesReturned = 0; + PInvokeWin32.USN_JOURNAL_DATA ujd = new PInvokeWin32.USN_JOURNAL_DATA(); + + bool bOk = QueryUSNJournal(pVolume, out ujd, out bytesReturned); + if (bOk) + { + PInvokeWin32.MFT_ENUM_DATA med; + med.StartFileReferenceNumber = 0; + med.LowUsn = 0; + med.HighUsn = ujd.NextUsn; + int sizeMftEnumData = Marshal.SizeOf(med); + medBuffer = Marshal.AllocHGlobal(sizeMftEnumData); + PInvokeWin32.ZeroMemory(medBuffer, sizeMftEnumData); + Marshal.StructureToPtr(med, medBuffer, true); + } + else + { + throw new IOException("DeviceIoControl() returned false", new Win32Exception(Marshal.GetLastWin32Error())); + } + } + unsafe private static void EnumerateFiles(string volumeName, IntPtr pVolume, IntPtr medBuffer, ref List files, ref List folders) + { + IntPtr pData = Marshal.AllocHGlobal(sizeof(UInt64) + 0x10000); + PInvokeWin32.ZeroMemory(pData, sizeof(UInt64) + 0x10000); + uint outBytesReturned = 0; + + while (false != PInvokeWin32.DeviceIoControl(pVolume, PInvokeWin32.FSCTL_ENUM_USN_DATA, medBuffer, + sizeof(PInvokeWin32.MFT_ENUM_DATA), pData, sizeof(UInt64) + 0x10000, out outBytesReturned, + IntPtr.Zero)) + { + IntPtr pUsnRecord = new IntPtr(pData.ToInt32() + sizeof(Int64)); + while (outBytesReturned > 60) + { + PInvokeWin32.USN_RECORD usn = new PInvokeWin32.USN_RECORD(pUsnRecord); + + if (usn.IsFolder) + { + folders.Add(new USNRecord + { + Name = usn.FileName, + ParentFrn = usn.ParentFRN, + FRN = usn.FRN, + IsFolder = true, + VolumeName = volumeName + }); + } + else + { + files.Add(new USNRecord + { + Name = usn.FileName, + ParentFrn = usn.ParentFRN, + FRN = usn.FRN, + IsFolder = false, + VolumeName = volumeName + }); + } + + pUsnRecord = new IntPtr(pUsnRecord.ToInt32() + usn.RecordLength); + outBytesReturned -= usn.RecordLength; + } + Marshal.WriteInt64(medBuffer, Marshal.ReadInt64(pData, 0)); + } + Marshal.FreeHGlobal(pData); + } + internal static void FillPath(string volume, USNRecord record, MFTSearcherCache db) + { + if (record == null) return; + var fdSource = db.GetFolderSource(volume); + string fullpath = record.Name; + FindRecordPath(record, ref fullpath, fdSource); + record.FullPath = fullpath; + } + private static void FindRecordPath(USNRecord curRecord, ref string fullpath, Dictionary fdSource) + { + if (curRecord.IsVolumeRoot) return; + USNRecord nextRecord = null; + if (!fdSource.TryGetValue(curRecord.ParentFrn, out nextRecord)) + return; + fullpath = string.Format("{0}{1}{2}", nextRecord.Name, Path.DirectorySeparatorChar, fullpath); + FindRecordPath(nextRecord, ref fullpath, fdSource); + } + } +} diff --git a/Wox.Infrastructure/MFTSearch/MFTSearcherCache.cs b/Wox.Infrastructure/MFTSearch/MFTSearcherCache.cs new file mode 100644 index 0000000000..d6f19bb5bf --- /dev/null +++ b/Wox.Infrastructure/MFTSearch/MFTSearcherCache.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MyEverything; + +namespace Wox.Infrastructure.MFTSearch +{ + internal class MFTSearcherCache + { + private Dictionary> _volumes_files = new Dictionary>(); + private Dictionary> _volumes_folders = new Dictionary>(); + + public MFTSearcherCache() { } + + public bool ContainsVolume(string volume) + { + return _volumes_files.ContainsKey(volume) && _volumes_folders.ContainsKey(volume); + } + public void AddRecord(string volume, List r, USNRecordType type) + { + if (type == USNRecordType.File) + { + CheckHashTableKey(_volumes_files, volume); + r.ForEach(x => _volumes_files[volume].Add(x.FRN, x)); + } + else + { + CheckHashTableKey(_volumes_folders, volume); + r.ForEach(x => _volumes_folders[volume].Add(x.FRN, x)); + } + } + public void AddRecord(string volume, USNRecord record, USNRecordType type) + { + if (type == USNRecordType.File) + { + CheckHashTableKey(_volumes_files, volume); + _volumes_files[volume].Add(record.FRN, record); + } + else + { + CheckHashTableKey(_volumes_folders, volume); + _volumes_folders[volume].Add(record.FRN, record); + } + } + private void CheckHashTableKey(Dictionary> hashtable, string key) + { + if (!hashtable.ContainsKey(key)) + hashtable.Add(key, new Dictionary()); + } + public bool DeleteRecord(string volume, ulong frn) + { + bool result = false; + result = DeleteRecordHashTableItem(_volumes_files, volume, frn); + if (!result) result = DeleteRecordHashTableItem(_volumes_folders, volume, frn); + return result; + } + private bool DeleteRecordHashTableItem(Dictionary> hashtable, string volume, ulong frn) + { + if (hashtable.ContainsKey(volume) && hashtable[volume].ContainsKey(frn)) + { + hashtable[volume].Remove(frn); + return true; + } + else + { + return false; + } + } + public void UpdateRecord(string volume, USNRecord record, USNRecordType type) + { + if (type == USNRecordType.File) + RealUpdateRecord(volume, _volumes_files, record); + else + RealUpdateRecord(volume, _volumes_folders, record); + } + private bool RealUpdateRecord(string volume, Dictionary> source, USNRecord record) + { + if (source.ContainsKey(volume) && source[volume].ContainsKey(record.FRN)) + { + source[volume][record.FRN] = record; + return true; + } + else + { + return false; + } + } + public List FindByName(string filename, out long foundFileCnt, out long fountFolderCnt) + { + + var fileQuery = from filesInVolumeDic in _volumes_files.Values + from eachFilePair in filesInVolumeDic + where eachFilePair.Value.Name.Contains(filename) + select eachFilePair.Value; + + var folderQuery = from fldsInVolumeDic in _volumes_folders.Values + from eachFldPair in fldsInVolumeDic + where eachFldPair.Value.Name.Contains(filename) + select eachFldPair.Value; + + foundFileCnt = fileQuery.Count(); + fountFolderCnt = folderQuery.Count(); + + List result = new List(); + + result.AddRange(fileQuery); + result.AddRange(folderQuery); + + return result; + } + public USNRecord FindByFrn(string volume, ulong frn) + { + if ((!_volumes_files.ContainsKey(volume)) || (!_volumes_folders.ContainsKey(volume))) + throw new Exception(string.Format("DB not contain the volume: {0}", volume)); + USNRecord result = null; + _volumes_files[volume].TryGetValue(frn, out result); + if (result != null) return result; + _volumes_folders[volume].TryGetValue(frn, out result); + return result; + } + public long FileCount + { + get { return _volumes_files.Sum(x => x.Value.Count); } + } + public long FolderCount + { + get { return _volumes_folders.Sum(x => x.Value.Count); } + } + public Dictionary GetFolderSource(string volume) + { + Dictionary result = null; + _volumes_folders.TryGetValue(volume, out result); + return result; + } + } +} diff --git a/Wox.Infrastructure/MFTSearch/PInvokeWin32.cs b/Wox.Infrastructure/MFTSearch/PInvokeWin32.cs new file mode 100644 index 0000000000..6d536c2455 --- /dev/null +++ b/Wox.Infrastructure/MFTSearch/PInvokeWin32.cs @@ -0,0 +1,165 @@ +using System; +using System.Runtime.InteropServices; + +namespace Wox.Infrastructure.MFTSearch { + public class PInvokeWin32 + { + + public const UInt32 GENERIC_READ = 0x80000000; + public const UInt32 GENERIC_WRITE = 0x40000000; + public const UInt32 FILE_SHARE_READ = 0x00000001; + public const UInt32 FILE_SHARE_WRITE = 0x00000002; + public const UInt32 FILE_ATTRIBUTE_DIRECTORY = 0x00000010; + public const UInt32 OPEN_EXISTING = 3; + public const UInt32 FILE_FLAG_BACKUP_SEMANTICS = 0x02000000; + public const Int32 INVALID_HANDLE_VALUE = -1; + public const UInt32 FSCTL_QUERY_USN_JOURNAL = 0x000900f4; + public const UInt32 FSCTL_ENUM_USN_DATA = 0x000900b3; + public const UInt32 FSCTL_CREATE_USN_JOURNAL = 0x000900e7; + public const UInt32 FSCTL_READ_USN_JOURNAL = 0x000900bb; + + + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, + uint dwShareMode, IntPtr lpSecurityAttributes, + uint dwCreationDisposition, uint dwFlagsAndAttributes, + IntPtr hTemplateFile); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetFileInformationByHandle(IntPtr hFile, + out BY_HANDLE_FILE_INFORMATION lpFileInformation); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CloseHandle(IntPtr hObject); + + [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool DeviceIoControl(IntPtr hDevice, + UInt32 dwIoControlCode, + IntPtr lpInBuffer, Int32 nInBufferSize, + out USN_JOURNAL_DATA lpOutBuffer, Int32 nOutBufferSize, + out uint lpBytesReturned, IntPtr lpOverlapped); + + [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool DeviceIoControl(IntPtr hDevice, + UInt32 dwIoControlCode, + IntPtr lpInBuffer, Int32 nInBufferSize, + IntPtr lpOutBuffer, Int32 nOutBufferSize, + out uint lpBytesReturned, IntPtr lpOverlapped); + + [DllImport("kernel32.dll")] + public static extern void ZeroMemory(IntPtr ptr, Int32 size); + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BY_HANDLE_FILE_INFORMATION { + public uint FileAttributes; + public FILETIME CreationTime; + public FILETIME LastAccessTime; + public FILETIME LastWriteTime; + public uint VolumeSerialNumber; + public uint FileSizeHigh; + public uint FileSizeLow; + public uint NumberOfLinks; + public uint FileIndexHigh; + public uint FileIndexLow; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FILETIME { + public uint DateTimeLow; + public uint DateTimeHigh; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct USN_JOURNAL_DATA { + public UInt64 UsnJournalID; + public Int64 FirstUsn; + public Int64 NextUsn; + public Int64 LowestValidUsn; + public Int64 MaxUsn; + public UInt64 MaximumSize; + public UInt64 AllocationDelta; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MFT_ENUM_DATA { + public UInt64 StartFileReferenceNumber; + public Int64 LowUsn; + public Int64 HighUsn; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CREATE_USN_JOURNAL_DATA { + public UInt64 MaximumSize; + public UInt64 AllocationDelta; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct READ_USN_JOURNAL_DATA { + public Int64 StartUsn; + public UInt32 ReasonMask; + public UInt32 ReturnOnlyOnClose; + public UInt64 Timeout; + public UInt64 BytesToWaitFor; + public UInt64 UsnJournalID; + } + + public class USN_RECORD { + public UInt32 RecordLength; + public UInt16 MajorVersion; + public UInt16 MinorVersion; + public UInt64 FRN; // 8 + public UInt64 ParentFRN; // 16 + public Int64 Usn; // Need be care + public UInt64 TimeStamp; // Need Be care + public UInt32 Reason; + public UInt32 SourceInfo; + public UInt32 SecurityId; + public UInt32 FileAttributes; // 52 + public UInt16 FileNameLength; + public UInt16 FileNameOffset; + public string FileName = string.Empty; + + private const int RecordLength_OFFSET = 0; + private const int MajorVersion_OFFSET = 4; + private const int MinorVersion_OFFSET = 6; + private const int FileReferenceNumber_OFFSET = 8; + private const int ParentFileReferenceNumber_OFFSET = 16; + private const int Usn_OFFSET = 24; + private const int TimeStamp_OFFSET = 32; + private const int Reason_OFFSET = 40; + private const int SourceInfo_OFFSET = 44; + private const int SecurityId_OFFSET = 48; + private const int FileAttributes_OFFSET = 52; + private const int FileNameLength_OFFSET = 56; + private const int FileNameOffset_OFFSET = 58; + private const int FileName_OFFSET = 60; + + public USN_RECORD(IntPtr p) { + this.RecordLength = (UInt32)Marshal.ReadInt32(p, RecordLength_OFFSET); + this.MajorVersion = (UInt16)Marshal.ReadInt16(p, MajorVersion_OFFSET); + this.MinorVersion = (UInt16)Marshal.ReadInt16(p, MinorVersion_OFFSET); + this.FRN = (UInt64)Marshal.ReadInt64(p, FileReferenceNumber_OFFSET); + this.ParentFRN = (UInt64)Marshal.ReadInt64(p, ParentFileReferenceNumber_OFFSET); + this.Usn = Marshal.ReadInt64(p, Usn_OFFSET); + this.TimeStamp = (UInt64)Marshal.ReadInt64(p, TimeStamp_OFFSET); + this.Reason = (UInt32)Marshal.ReadInt32(p, Reason_OFFSET); + this.SourceInfo = (UInt32)Marshal.ReadInt32(p, SourceInfo_OFFSET); + this.SecurityId = (UInt32)Marshal.ReadInt32(p, SecurityId_OFFSET); + this.FileAttributes = (UInt32)Marshal.ReadInt32(p, FileAttributes_OFFSET); + this.FileNameLength = (UInt16)Marshal.ReadInt16(p, FileNameLength_OFFSET); + this.FileNameOffset = (UInt16)Marshal.ReadInt16(p, FileNameOffset_OFFSET); + + this.FileName = Marshal.PtrToStringUni(new IntPtr(p.ToInt32() + this.FileNameOffset), this.FileNameLength / sizeof(char)); + } + + public bool IsFolder { + get { return 0 != (FileAttributes & PInvokeWin32.FILE_ATTRIBUTE_DIRECTORY); } + } + } + } +} diff --git a/Wox.Infrastructure/MFTSearch/USNChangeReason.cs b/Wox.Infrastructure/MFTSearch/USNChangeReason.cs new file mode 100644 index 0000000000..61095a6a57 --- /dev/null +++ b/Wox.Infrastructure/MFTSearch/USNChangeReason.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Wox.Infrastructure.MFTSearch +{ + internal class USNChangeReason + { + public static Dictionary USN_REASONS = new Dictionary { + {"USN_REASON_DATA_OVERWRITE", 0x00000001}, + {"USN_REASON_DATA_EXTEND", 0x00000002}, + {"USN_REASON_DATA_TRUNCATION", 0x00000004}, + {"USN_REASON_NAMED_DATA_OVERWRITE", 0x00000010}, + {"USN_REASON_NAMED_DATA_EXTEND", 0x00000020}, + {"USN_REASON_NAMED_DATA_TRUNCATION", 0x00000040}, + {"USN_REASON_FILE_CREATE", 0x00000100}, + {"USN_REASON_FILE_DELETE", 0x00000200}, + {"USN_REASON_EA_CHANGE", 0x00000400}, + {"USN_REASON_SECURITY_CHANGE", 0x00000800}, + {"USN_REASON_RENAME_OLD_NAME", 0x00001000}, + {"USN_REASON_RENAME_NEW_NAME", 0x00002000}, + {"USN_REASON_INDEXABLE_CHANGE", 0x00004000}, + {"USN_REASON_BASIC_INFO_CHANGE", 0x00008000}, + {"USN_REASON_HARD_LINK_CHANGE", 0x00010000}, + {"USN_REASON_COMPRESSION_CHANGE", 0x00020000}, + {"USN_REASON_ENCRYPTION_CHANGE", 0x00040000}, + {"USN_REASON_OBJECT_ID_CHANGE", 0x00080000}, + {"USN_REASON_REPARSE_POINT_CHANGE", 0x00100000}, + {"USN_REASON_STREAM_CHANGE", 0x00200000}, + {"USN_REASON_TRANSACTED_CHANGE", 0x00400000}, + {"USN_REASON_CLOSE", 0x80000000} + }; + + public static string ReasonPrettyFormat(UInt32 rsn) + { + StringBuilder sb = new StringBuilder(); + sb.Append("["); + foreach (var rsnPair in USN_REASONS) + { + if ((rsnPair.Value & rsn) != 0) + sb.Append(rsnPair.Key + " "); + } + sb.Append("]"); + return sb.ToString(); + } + } +} diff --git a/Wox.Infrastructure/MFTSearch/USNRecord.cs b/Wox.Infrastructure/MFTSearch/USNRecord.cs new file mode 100644 index 0000000000..64c0ee05e7 --- /dev/null +++ b/Wox.Infrastructure/MFTSearch/USNRecord.cs @@ -0,0 +1,34 @@ +using System; +using MyEverything; + +namespace Wox.Infrastructure.MFTSearch +{ + public class USNRecord + { + + public string Name { get; set; } + public ulong FRN { get; set; } + public UInt64 ParentFrn { get; set; } + public string FullPath { get; set; } + public bool IsVolumeRoot { get; set; } + public bool IsFolder { get; set; } + public string VolumeName { get; set; } + + public override string ToString() + { + return string.IsNullOrEmpty(FullPath) ? Name : FullPath; + } + + public static USNRecord ParseUSN(string volume, PInvokeWin32.USN_RECORD usn) + { + return new USNRecord + { + FRN = usn.FRN, + Name = usn.FileName, + ParentFrn = usn.ParentFRN, + IsFolder = usn.IsFolder, + VolumeName = volume + }; + } + } +} diff --git a/Wox.Infrastructure/MFTSearch/USNRecordType.cs b/Wox.Infrastructure/MFTSearch/USNRecordType.cs new file mode 100644 index 0000000000..41c68076cc --- /dev/null +++ b/Wox.Infrastructure/MFTSearch/USNRecordType.cs @@ -0,0 +1,8 @@ +namespace Wox.Infrastructure.MFTSearch +{ + internal enum USNRecordType + { + File, + Folder + } +} \ No newline at end of file diff --git a/Wox.Infrastructure/MFTSearch/VolumeMonitor.cs b/Wox.Infrastructure/MFTSearch/VolumeMonitor.cs new file mode 100644 index 0000000000..c65cf866d9 --- /dev/null +++ b/Wox.Infrastructure/MFTSearch/VolumeMonitor.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; +using System.Threading; +using System.Diagnostics; +using System.IO; +using Wox.Infrastructure.MFTSearch; + +namespace MyEverything +{ + internal class VolumeMonitor + { + + public Action RecordAddedEvent; + public Action RecordDeletedEvent; + public Action RecordRenameEvent; + + public void Monitor(List volumes, MFTSearcherCache db) + { + foreach (var volume in volumes) + { + if (string.IsNullOrEmpty(volume)) throw new InvalidOperationException("Volume cant't be null or empty string."); + if (!db.ContainsVolume(volume)) throw new InvalidOperationException(string.Format("Volume {0} must be scaned first.")); + Thread th = new Thread(new ParameterizedThreadStart(MonitorThread)); + th.Start(new Dictionary { { "Volume", volume }, { "MFTSearcherCache", db } }); + } + } + private PInvokeWin32.READ_USN_JOURNAL_DATA SetupInputData4JournalRead(string volume, uint reason) + { + IntPtr pMonitorVolume = MFTSearcher.GetVolumeJournalHandle(volume); + uint bytesReturned = 0; + PInvokeWin32.USN_JOURNAL_DATA ujd = new PInvokeWin32.USN_JOURNAL_DATA(); + Wox.Infrastructure.MFTSearch.MFTSearcher.QueryUSNJournal(pMonitorVolume, out ujd, out bytesReturned); + + // 构建输入参数 + PInvokeWin32.READ_USN_JOURNAL_DATA rujd = new PInvokeWin32.READ_USN_JOURNAL_DATA(); + rujd.StartUsn = ujd.NextUsn; + rujd.ReasonMask = reason; + rujd.ReturnOnlyOnClose = 1; + rujd.Timeout = 0; + rujd.BytesToWaitFor = 1; + rujd.UsnJournalID = ujd.UsnJournalID; + + return rujd; + } + private void MonitorThread(object param) + { + + MFTSearcherCache db = (param as Dictionary)["MFTSearcherCache"] as MFTSearcherCache; + string volume = (param as Dictionary)["Volume"] as string; + IntPtr pbuffer = Marshal.AllocHGlobal(0x1000); + PInvokeWin32.READ_USN_JOURNAL_DATA rujd = SetupInputData4JournalRead(volume, 0xFFFFFFFF); + UInt32 cbRead; + IntPtr prujd; + + while (true) + { + prujd = Marshal.AllocHGlobal(Marshal.SizeOf(rujd)); + PInvokeWin32.ZeroMemory(prujd, Marshal.SizeOf(rujd)); + Marshal.StructureToPtr(rujd, prujd, true); + + Debug.WriteLine(string.Format("\nMoniting on {0}......", volume)); + IntPtr pVolume = Wox.Infrastructure.MFTSearch.MFTSearcher.GetVolumeJournalHandle(volume); + + bool fok = PInvokeWin32.DeviceIoControl(pVolume, + PInvokeWin32.FSCTL_READ_USN_JOURNAL, + prujd, Marshal.SizeOf(typeof(PInvokeWin32.READ_USN_JOURNAL_DATA)), + pbuffer, 0x1000, out cbRead, IntPtr.Zero); + + IntPtr pRealData = new IntPtr(pbuffer.ToInt32() + Marshal.SizeOf(typeof(Int64))); + uint offset = 0; + + if (fok) + { + while (offset + Marshal.SizeOf(typeof(Int64)) < cbRead) + { + PInvokeWin32.USN_RECORD usn = new PInvokeWin32.USN_RECORD(new IntPtr(pRealData.ToInt32() + (int)offset)); + ProcessUSN(usn, volume, db); + offset += usn.RecordLength; + } + } + + Marshal.FreeHGlobal(prujd); + rujd.StartUsn = Marshal.ReadInt64(pbuffer); + } + } + private void ProcessUSN(PInvokeWin32.USN_RECORD usn, string volume, MFTSearcherCache db) + { + var dbCached = db.FindByFrn(volume, usn.FRN); + Wox.Infrastructure.MFTSearch.MFTSearcher.FillPath(volume, dbCached, db); + Debug.WriteLine(string.Format("------USN[frn={0}]------", usn.FRN)); + Debug.WriteLine(string.Format("FileName={0}, USNChangeReason={1}", usn.FileName, USNChangeReason.ReasonPrettyFormat(usn.Reason))); + Debug.WriteLine(string.Format("FileName[Cached]={0}", dbCached == null ? "NoCache" : dbCached.FullPath)); + Debug.WriteLine("--------------------------------------"); + + if (MaskEqual(usn.Reason, USNChangeReason.USN_REASONS["USN_REASON_RENAME_NEW_NAME"])) + ProcessRenameNewName(usn, volume, db); + if ((usn.Reason & USNChangeReason.USN_REASONS["USN_REASON_FILE_CREATE"]) != 0) + ProcessFileCreate(usn, volume, db); + if (MaskEqual(usn.Reason, USNChangeReason.USN_REASONS["USN_REASON_FILE_DELETE"])) + ProcessFileDelete(usn, volume, db); + } + private void ProcessFileDelete(PInvokeWin32.USN_RECORD usn, string volume, MFTSearcherCache db) + { + var cached = db.FindByFrn(volume, usn.FRN); + if (cached == null) + { + return; + } + else + { + Wox.Infrastructure.MFTSearch.MFTSearcher.FillPath(volume, cached, db); + var deleteok = db.DeleteRecord(volume, usn.FRN); + Debug.WriteLine(string.Format(">>>> File {0} deleted {1}.", cached.FullPath, deleteok ? "successful" : "fail")); + if (RecordDeletedEvent != null) + RecordDeletedEvent(cached); + } + } + private void ProcessRenameNewName(PInvokeWin32.USN_RECORD usn, string volume, MFTSearcherCache db) + { + USNRecord newRecord = USNRecord.ParseUSN(volume, usn); + //string fullpath = newRecord.Name; + //db.FindRecordPath(newRecord, ref fullpath, db.GetFolderSource(volume)); + //newRecord.FullPath = fullpath; + var oldRecord = db.FindByFrn(volume, usn.FRN); + Wox.Infrastructure.MFTSearch.MFTSearcher.FillPath(volume, oldRecord, db); + Wox.Infrastructure.MFTSearch.MFTSearcher.FillPath(volume, newRecord, db); + Debug.WriteLine(string.Format(">>>> RenameFile {0} to {1}", oldRecord.FullPath, newRecord.FullPath)); + db.UpdateRecord(volume, newRecord, + usn.IsFolder ? USNRecordType.Folder : USNRecordType.File); + if (RecordRenameEvent != null) RecordRenameEvent(oldRecord, newRecord); + if (newRecord.FullPath.Contains("$RECYCLE.BIN")) + { + Debug.WriteLine(string.Format(">>>> Means {0} moved to recycle.", oldRecord.FullPath)); + } + } + private void ProcessFileCreate(PInvokeWin32.USN_RECORD usn, string volume, MFTSearcherCache db) + { + USNRecord record = USNRecord.ParseUSN(volume, usn); + //string fullpath = record.Name; + //db.FindRecordPath(record, ref fullpath, db.GetFolderSource(volume)); + //record.FullPath = fullpath; + db.AddRecord(volume, record, usn.IsFolder ? USNRecordType.Folder : USNRecordType.File); + Wox.Infrastructure.MFTSearch.MFTSearcher.FillPath(volume, record, db); + Debug.WriteLine(string.Format(">>>> NewFile: {0}", record.FullPath)); + if (RecordAddedEvent != null) + RecordAddedEvent(record); + } + + + private bool MaskEqual(uint target, uint compare) + { + return (target & compare) != 0; + } + } +} diff --git a/Wox.Infrastructure/Wox.Infrastructure.csproj b/Wox.Infrastructure/Wox.Infrastructure.csproj index 040b9d2be4..ae41dfffaa 100644 --- a/Wox.Infrastructure/Wox.Infrastructure.csproj +++ b/Wox.Infrastructure/Wox.Infrastructure.csproj @@ -22,6 +22,7 @@ DEBUG;TRACE prompt 4 + true pdbonly @@ -55,6 +56,14 @@ + + + + + + + +