Everything实现,快速搜索文件

最近编写NTFS文件实时搜索工具, 类似 Everything 这样, 翻阅了很多博客, 结果大致如下:

1.分析比较肤浅, 采用USN日志枚举来获取文件记录

    速度一言难尽, 因为日志枚举的是全盘所有文件的所有日志, 记录比文件记录还多, 速度当然很慢, 还有的甚至于是 使用 DeviceIoControl 函数 通过 FSCTL_GET_NTFS_FILE_RECORD 参数类型来获取全部文件记录, 我也尝试了, 其速度一言难尽, 越到后面速度越慢, 跑了几分钟我等不下去了手动强制终止了

2.虽然也有解析 MFT 记录的, 但是解析不全面

   如果按照他们写的解析文件记录, 那么你会发现: 为啥我的文件记录数总是比 Everything 少几千上万个, 而且也没有处理短文件名, 部分文件记录只有短文件名, 这个需要解析 属性列表(属性类型0x20)来获取引用记录, 然后解析引用的记录来拿到长文件名.

 

 

总体看来就是很多文章解析讲解不算全面, 直接拿来用是不可能的, 不仅速度慢, 数据还不全, 于是我就花了大量时间查找资料, 编写代码不断发现问题并分析问题, 在不断尝试于分析之下, 才得到现在的经验总结.

 

 

基本准备

数据库选择

当然是 sqlite3 了,小巧方便, 是非常好用的数据库

数据表字段

字段描述
id文件记录号,每个文件本身标识
parent_id文件(夹)所在文件夹的标识号
name文件名
path文件完整路径

 

值得一提的是, 我建表使用的是 id, parent_id,name 组成的联合主键, 这是为啥呢?很多人以为简单地用一个 id 作为主键就行了, 其实不然, 原因如下 

1.一个ID可以有多个父ID

实际对比发现扫描文件数始终比 everything 的少, 部分文件 everything 能搜出来, 我的确搜不到, 经过多次调试分析发现, 解析 文件名属性 可以得到同一个 文件ID 有不同父ID, 查看了这种文件, 发现是在不同文件夹下的同名同数据的文件, 应该是一种节约空间的做法.

2.一个id + parent_id 标识的记录项可能存在多个不同的长文件名

这是我解析时遇到的最麻烦的一个坑,因为解决了 一个ID多个父ID 记录后,  发现我的扫描记录数还是比 Everything 少一些,于是逐个文件夹比较, 找到了文件数比较少且搜索数目不一致的文件夹, 然后修改代码调试分析, 发现 id + parent_id 组合后, 文件名还能不一样(都是长文件名), 于是我解决了这个问题, 最后扫描文件数量终于和 Everything 完全一致了!

所以建表可以这么写:

CREATE TABLE IF NOT EXISTS file_list (
    id        INTEGER NOT NULL,
    parent_id INT,
    name      TEXT,
    attr      INT,
    path      TEXT,
    PRIMARY KEY(id, parent_id, name)
);

 

一. 解析NTFS 主文件表(MFT)

这一步是获取文件数据的唯一迅速且可靠的来源,只需要解析元数据文件中的$MFT(第0条文件记录)的数据属性(属性类型0x80)即可。

里面需要注意的是,这里的数据是一个dataruns,手动解析这个 dataruns 得到文件记录在驱动器上的位置和大小,读取这些数据(一次可以读取一个 dataruns 块,也可以分块读取来减少内存占用)。

读取后按照文件记录(1KB一条记录)进行解析(需要解析属性列表0x20属性和文件名属性0x30),有文件名是短文件名,可以通过从解析属性列表得到的记录参考号来获取文件记录来拿到长文件名。

此外,如果文件记录存在基本文件记录引用,那么需要把解析的文件记录的记录号改成这个基本文件记录号,不然会出现扫描的文件数比 Everything 的多。

 

以下是我根据资料编写的结构

// 文件引用 https://flatcap.github.io/linux-ntfs/ntfs/concepts/file_reference.html
typedef union _FILE_REFERENCE
{
    uint64_t        Data;
    struct {
        uint64_t    RecordNumber : 48;          // 文件记录号
        uint64_t    SequenceNumber : 16;        // 序号
    };

    bool operator < (const _FILE_REFERENCE& r) const
    {
        return this->Data < r.Data;
    }

    bool operator == (const _FILE_REFERENCE& r) const
    {
        return this->Data == r.Data;
    }

    uint64_t operator()(const _FILE_REFERENCE& key) const
    {
        return key.Data;
    }

}FILE_REFERENCE, * PFILE_REFERENCE;

// 文件记录标头 https://flatcap.github.io/linux-ntfs/ntfs/concepts/file_record.html
typedef struct _NTFS_FILE_RECORD_HEADER
{
    uint32_t        MagicNumber;                // 幻数‘FILE’
    uint16_t        UpdateSequenceOffset;       // 更新到更新序列的偏移量
    uint16_t        UpdateSequenceSize;         // 更新序号和队列的长度(总共为S个字),为表格最后两项的大小和(以字为单位),此处值为S的话,最后两项的大小总和为2S个字节。
    uint64_t        LogFileSerialNumber;        // 日志文件序列号($LogFile Sequence Number,LSN)
    uint16_t        SequenceNumber;             // 序列号
    uint16_t        HardLinkCount;              // 硬连接数
    uint16_t        FirstAttributeOffset;       // 第一个属性的偏移

    union {
        uint16_t        Data;
        struct {
            uint16_t    Use : 1;            // 记录正在使用中
            uint16_t    Directory : 1;      // 记录是目录
            uint16_t    Exension : 1;       // 一个 exension
            uint16_t    SpecialIndex : 1;   // 存在特殊索引
        };
    }Flags;                                     // 标志

    uint32_t        RealSize;                   // 文件记录的真实大小
    uint32_t        AllocatedSize;              // 文件记录的分配大小
    FILE_REFERENCE  BaseFileRecordReference;    // 对基本 FILE 记录的文件引用
    uint16_t        NextAttributeId;            // 下一属性 ID
    uint16_t        Padding;                    // 填充
    uint32_t        RecordNumber;               // 此 MFT 记录的编号
    uint16_t        UpdateSequenceNumber;       // 更新序列号
    uint16_t        UpdateSequenceArray[487];   // 更新序列数组
}NTFS_FILE_RECORD_HEADER, * PNTFS_FILE_RECORD_HEADER;

// 文件标志 https://flatcap.github.io/linux-ntfs/ntfs/attributes/file_name.html
typedef union _NTFS_FILE_FLAG
{
    uint32_t            Data;
    struct {
        uint32_t        ReadOnly : 1;           // 0x00000001 只读
        uint32_t        Hidden : 1;             // 0x00000002 隐藏
        uint32_t        System : 1;             // 0x00000004 系统
        uint32_t        Unuse : 1;              // 0x00000008 未使用
        uint32_t        Directory : 1;          // 0x00000010 目录
        uint32_t        Archive : 1;            // 0x00000020 档案
        uint32_t        Device : 1;             // 0x00000040 设备
        uint32_t        Normal : 1;             // 0x00000080 普通
        uint32_t        Temporary : 1;          // 0x00000100 临时
        uint32_t        SparseFile : 1;         // 0x00000200 稀疏
        uint32_t        ReparsePoint : 1;       // 0x00000400 重解析点
        uint32_t        Compressed : 1;         // 0x00000800 压缩
        uint32_t        Offline : 1;            // 0x00001000 脱机
        uint32_t        NotContentIndexed : 1;  // 0x00002000 无内容索引
        uint32_t        Encrypted : 1;          // 0x00004000 加密
        uint32_t        Unuse2 : 13;            // 未使用
        uint32_t        ExDirectory : 1;        // 0x10000000 目录
        uint32_t        ExIndexView : 1;        // 0x20000000 索引浏览
    };
}NTFS_FILE_FLAG, *PNTFS_FILE_FLAG;

// 0x30 文件名 https://flatcap.github.io/linux-ntfs/ntfs/attributes/file_name.html
// https://learn.microsoft.com/en-us/windows/win32/devnotes/file-name
typedef struct _NTFS_FILE_NAME
{
    FILE_REFERENCE          ParentDirectory;            // 父目录的文件引用
    uint64_t                FileCreationTime;           // 文件创建时间
    uint64_t                FileAlteredTime;            // 文件修改时间
    uint64_t                FileChangedTime;            // 文件修改时间
    uint64_t                FileReadTime;               // 文件读取时间
    uint64_t                AllocatedSize;              // 分配大小
    uint64_t                RealSize;                   // 真实大小
    NTFS_FILE_FLAG          Flags;                      // 文件标志
    uint32_t                UsedByEAsAndReparse;        // 被EAs和Reparse使用
    uint8_t                 FileNameLength;             // 文件名长度

    union {
        uint8_t             Data;       // 0x00: POSIX
        struct {
            uint8_t         Win32 : 1;  // 0x01: Win32
            uint8_t         Dos : 1;    // 0x02: DOS
        };
    }FilenameNamespace;                                 // 文件名命名空间

    wchar_t                 FileName[1];                // 文件名
}NTFS_FILE_NAME, * PNTFS_FILE_NAME;

 

 关键解析的大致逻辑如下:

uint64_t NTFS_Base::GetDataRunUint(uint8_t* pAddr, uint8_t size)
{
    uint64_t nLength = 0;

    // 计算值
    if (size > 0 && size <= 8)
    {
        uint8_t* pData = pAddr + size - 1;
        for (int i = size - 1; i >= 0; i--, pData--)
        {
            uint8_t data = *(uint8_t*)pData;
            nLength = (nLength << 8) | data;
        }
    }

    return nLength;
}

int64_t NTFS_Base::GetDataRunInt(uint8_t* pAddr, uint8_t size)
{
    uint64_t nLength = 0;
    uint64_t nMaxData = 0x01;
    int8_t nLastData = 0;

    // 计算值
    if (size > 0 && size <= 8)
    {
        uint8_t* pData = pAddr + size - 1;
        nLastData = *(uint8_t*)pData;
        for (int i = size - 1; i >= 0; i--, pData--)
        {
            uint8_t data = *(uint8_t*)pData;
            nLength = (nLength << 8) | data;
            nMaxData = nMaxData << 8;
        }
    }

    // 负数转换
    if (nLastData < 0)
    {
        nLength = 0 - (nMaxData - nLength);
    }

    return nLength;
}


bool NTFS_MFT_Parse::_ParseMasterFileTableData(
    HANDLE hFile,
    PNTFS_BOOT_RECORD pBootRecord,
    PNTFS_FILE_RECORD_HEADER pFileHeaderStart,
    PNTFS_ATTRIBUTE_HEADER pAttrHeaderStart,
    const NTFS_VOLUME_INFO& volInfo,
    NtfsFilenameCb cb
)
{
    PNTFS_DATA pData = (PNTFS_DATA)pAttrHeaderStart;
    uint8_t* pRecordBufData = nullptr;
    uint32_t RecordBufSize = sizeof(NTFS_FILE_RECORD_HEADER) * NTFS_MFT_PARSE_FILE_BUF_COUNT;

    // 计算每簇字节数
    uint64_t BytesPerCluster = (uint64_t)pBootRecord->BytesPerSector * (uint64_t)pBootRecord->SectorsPerCluster;

    PNTFS_ATTRIBUTE_HEADER pHeader = (PNTFS_ATTRIBUTE_HEADER)pData;
    static std::mutex mtexAccess;

    // 主文件表的数据是非常驻属性
    if (pHeader->NonResidentFlag)
    {
        PNTFS_DATA_RUNS pDataRuns = (PNTFS_DATA_RUNS)((uint64_t)(pAttrHeaderStart) +pAttrHeaderStart->NonResident.DataRunsOffset);

        uint64_t FileRecordOffset = 0;
        uint64_t FileRecordIndex = 0;
        bool fAobrt = false;

        // 读取缓存分配
        pRecordBufData = new (std::nothrow) uint8_t[RecordBufSize];
        if (!pRecordBufData)
        {
            return false;
        }

        // 解析数据运行
        while (pDataRuns->LengthSize || pDataRuns->OffsetSize)
        {
            // 计算长度值与偏移值位置
            uint8_t* pOffsetData = (uint8_t*)pDataRuns + pDataRuns->LengthSize + 1;
            uint8_t* pLengthData = (uint8_t*)pDataRuns + 1;

            // 获取长度值与偏移值
            int64_t Offset = GetDataRunInt(pOffsetData, pDataRuns->OffsetSize);
            uint64_t Length = GetDataRunUint(pLengthData, pDataRuns->LengthSize);

            // 计算数据偏移, 数据量
            FileRecordOffset += Offset * BytesPerCluster;

            // 分块读取
            uint64_t DataBlockSize = RecordBufSize;
            uint64_t BufBlockOffset = FileRecordOffset;
            uint64_t DataBufSize = Length * BytesPerCluster;

            // 遍历解析文件记录块
            while (DataBufSize > 0)
            {
                // 数据块大小检查
                if (DataBufSize < DataBlockSize)
                {
                    DataBlockSize = DataBufSize;
                }

                // 设置读取偏移
                if (!SetFileOffset(hFile, BufBlockOffset, nullptr, 0))
                {
                    break;
                }

                // 读取文件记录块
                DWORD dwNumberOfBytesRead = 0;
                if (!::ReadFile(hFile, pRecordBufData, (DWORD)DataBlockSize, &dwNumberOfBytesRead, NULL))
                {
                    break;
                }

                // 遍历文件记录
                PNTFS_FILE_RECORD_HEADER pFileHeaderItem = (PNTFS_FILE_RECORD_HEADER)pRecordBufData;
                int64_t FileCount = DataBlockSize / sizeof(NTFS_FILE_RECORD_HEADER);
                for (int64_t i = 0; i < FileCount; i++)
                {
                    // 解析文件记录属性
                    if (FileRecordIndex >= NTFSFileNameType::e16_Unuse_Start &&
                        NTFS_FILE_MGAIC_NUMBER == pFileHeaderItem->MagicNumber)
                    {
                        PNTFS_ATTRIBUTE_HEADER pAttrHeaderItem = (PNTFS_ATTRIBUTE_HEADER)((uint8_t*)pFileHeaderItem + pFileHeaderItem->FirstAttributeOffset);

                        FILE_REFERENCE fileRef = { 0 };
                        fileRef.RecordNumber = pFileHeaderItem->RecordNumber;
                        fileRef.SequenceNumber = pFileHeaderItem->SequenceNumber;

                        // 存在基本文件记录段的文件引用, 则使用基本文件引用
                        if (pFileHeaderItem->BaseFileRecordReference.Data)
                        {
                            fileRef = pFileHeaderItem->BaseFileRecordReference;
                        }
                        else
                        {
                            fileRef.RecordNumber = pFileHeaderItem->RecordNumber;
                            fileRef.SequenceNumber = pFileHeaderItem->SequenceNumber;
                        }

                        MFT_FILE_INFO_LIST fileInfoList;
                        _ParseFileRecordAttributes(hFile, pBootRecord, pFileHeaderItem, pAttrHeaderItem, fileInfoList);

                        if (!fileInfoList.empty())
                        {
                            if (cb)
                            {
                                mtexAccess.lock();
                                bool fContinue = false;
                                fContinue = cb(volInfo, pFileHeaderItem, fileRef, fileInfoList);
                                mtexAccess.unlock();
                                if (!fContinue)
                                {
                                    fAobrt = true;
                                    break;
                                }
                            }
                        }
                    }

                    pFileHeaderItem++;
                    FileRecordIndex++;
                }

                if (fAobrt)
                {
                    break;
                }

                // 剩余数据量更新
                DataBufSize -= DataBlockSize;

                // 数据块偏移更新
                BufBlockOffset += DataBlockSize;
            }

            // 解析下一个数据运行位置
            pDataRuns = (PNTFS_DATA_RUNS)((uint8_t*)pDataRuns + pDataRuns->LengthSize + pDataRuns->OffsetSize + 1);
        }
    }

    if (pRecordBufData)
    {
        delete[] pRecordBufData;
    }

    return true;
}

二. 监控 USN 日志

当文件发生变动时,同步更新数据库,这里只需要关注文件创建,删除,更名即可。

 

可以定义这么一个结构体存储日志信息

// 日志更新信息
typedef struct _NTFS_USN_INFO
{
    _tstring            strFileName;            // 文件名
    FILE_REFERENCE      ReferenceNumber;        // 文件引用号
    FILE_REFERENCE      ParentReferenceNumber;  // 父文件引用号
    uint64_t            UpdateSequenceNumber;   // 更新序列号
    uint32_t            uDriveIndex;            // 卷索引号 如: 0: C 1: D 3:D 4:E
    NTFS_USN_REASON     Reason;                 // 更改原因
    uint32_t            FileAttributes;         // 文件属性
    uint8_t             Namespace;              // 命名空间

    _NTFS_USN_INFO() :
        ReferenceNumber{0},
        ParentReferenceNumber{0},
        UpdateSequenceNumber(0),
        uDriveIndex(0),
        Reason{ 0 }
    {

    }

}NTFS_USN_INFO, * PNTFS_USN_INFO;

 

以下是部分关键逻辑:


// 替换记录
#define SQL_QUERY_REPLACE R"(
REPLACE INTO file_list (id, parent_id, name, attr) VALUES (%llu, %llu, "%s", %d);
)"

// 删除记录
#define SQL_QUERY_DELETE R"(
DELETE FROM file_list where id = %llu AND parent_id = %llu AND name = "%s";
)"

// 更新子路径
#define SQL_QUERY_UPDATE_CHILD_PATH   R"(
WITH RECURSIVE sub_tree(id, parent_id, name, path) AS (
  SELECT id, parent_id, name, path AS path
  FROM file_list
  WHERE id = %llu AND parent_id = %llu
  
  UNION ALL
  
  SELECT c.id, c.parent_id, c.name, p.path || '\' || c.name
  FROM file_list c
  INNER JOIN sub_tree p ON c.parent_id = p.id
)
UPDATE file_list
SET path = st.path
FROM sub_tree st
WHERE file_list.id = st.id AND file_list.parent_id = st.parent_id AND file_list.name = st.name;
)"

// 更新文件路径
#define SQL_QUERY_UPDATE_FILE_PATH   R"(
WITH RECURSIVE path_cte(id, parent_id, name, path) AS (
    SELECT id, parent_id, name, name
    FROM file_list
    WHERE id = %llu AND parent_id = %llu AND name = "%s"

    UNION ALL

    SELECT f.id, f.parent_id, f.name, f.name || '\' ||p.path 
    FROM file_list f
    INNER JOIN path_cte p ON (f.id = p.parent_id) 
)
UPDATE file_list
SET path = (SELECT path FROM path_cte WHERE parent_id = 0)
WHERE id = %llu AND parent_id = %llu AND name = "%s";
)"

int NTFS_Search::_UpdateFilePath(SQL_FILE_ID fileID, SQL_FILE_ID parentID, _tstring strFilename)
{
    _tstring strSql = CStrUtils::Format(_T(SQL_QUERY_UPDATE_FILE_PATH), 
        fileID.data, parentID.data, strFilename.c_str(), 
        fileID.data, parentID.data, strFilename.c_str()
    );

    int nRes = m_sql3.Exec(CStrUtils::TStrToU8Str(strSql).c_str(), NULL, NULL, NULL);
    return nRes;
}

int NTFS_Search::_UpdateChildPath(SQL_FILE_ID fileID, SQL_FILE_ID parentID)
{
    _tstring strSql = CStrUtils::Format(_T(SQL_QUERY_UPDATE_CHILD_PATH), fileID.data, parentID.data);
    int nRes = m_sql3.Exec(CStrUtils::TStrToU8Str(strSql).c_str(), NULL, NULL, NULL);
    return nRes;
}

void NTFS_Search::UsnProc(const std::vector<NTFS_USN_INFO>& usnList)
{
    for (const auto& usnInfo : usnList)
    {
        strFileName = CStrUtils::TStrToU8Str(usnInfo.strFileName);
        fileParentID.ReferenceNumber = usnInfo.ParentReferenceNumber.RecordNumber;
        fileParentID.dwDriveIndex = usnInfo.uDriveIndex;
        fileID.ReferenceNumber = usnInfo.ReferenceNumber.RecordNumber;
        fileID.dwDriveIndex = usnInfo.uDriveIndex;

        bool fDirectory = (FILE_ATTRIBUTE_DIRECTORY == (usnInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY));

        //新建文件, 需要更新文件路径
        if (usnInfo.Reason.UsnFileCreate)
        {
            // 添加文件记录
            strSql = CStrUtils::FormatA(SQL_QUERY_REPLACE, fileID, fileParentID.data, strFileName.c_str(), fDirectory);
            m_sql3.Exec(strSql.c_str(), NULL, 0, nullptr);

            // 更新文件路径
            _UpdateFilePath(fileID, fileParentID, usnInfo.strFileName);
        }

        // 删除文件 | 重命名旧文件名, 需要更新子路径
        if (usnInfo.Reason.UsnRenameOldName || usnInfo.Reason.UsnFileDelete)
        {
            // 删除记录
            strSql = CStrUtils::FormatA(SQL_QUERY_DELETE, fileID, fileParentID, strFileName.c_str());
            m_sql3.Exec(strSql.c_str(), NULL, 0, nullptr);
        }

        // 重命名新文件名, 需要更新子路径
        if (usnInfo.Reason.UsnRenameNewName && usnInfo.Reason.UsnClose)
        {
            // 更新文件记录(不含路径)
            strFileName = CStrUtils::TStrToU8Str(usnInfo.strFileName);
            strSql = CStrUtils::FormatA(SQL_QUERY_REPLACE, fileID, fileParentID.data, strFileName.c_str(), fDirectory);
            m_sql3.Exec(strSql.c_str(), NULL, 0, nullptr);

            // 更新文件路径
            _UpdateFilePath(fileID, fileParentID, usnInfo.strFileName);

            // 如果此条记录是文件夹, 则更新子其所有子项路径
            if (fDirectory)
            {
                _UpdateChildPath(fileID, fileParentID);
            }
        }

        _SetUsn(usnInfo.uDriveIndex, usnInfo.UpdateSequenceNumber);

        if (m_fQuit)
        {
            break;
        }
    }
}

三. 数据库查询

采用Sqlite3进行数据库操作, 以下是部分关键代码


// 计数
#define SQL_QUERY_COUNT   R"(
SELECT count(*) AS count FROM file_list WHERE path NOT NULL
)"

// 更新根路径
#define SQL_QUERY_UPDATE_ROOT_PATH   R"(
WITH RECURSIVE sub_tree(id, parent_id, name, path) AS (
  SELECT id, parent_id, name, name AS path
  FROM file_list
  WHERE parent_id = 0
  
  UNION ALL
  
  SELECT c.id, c.parent_id, c.name, p.path || '\' || c.name
  FROM file_list c
  INNER JOIN sub_tree p ON c.parent_id = p.id
)
UPDATE file_list
SET path = (
SELECT path FROM sub_tree 
WHERE sub_tree.id = file_list.id AND sub_tree.parent_id = file_list.parent_id AND sub_tree.name = file_list.name
);
)"

// 删除索引
#define SQL_QUERY_DROP_INDEX   R"(
DROP INDEX IF EXISTS idx_file_list_id ON file_list;
DROP INDEX IF EXISTS idx_file_list_parent_id ON file_list;
DROP INDEX IF EXISTS idx_file_list_name ON file_list;
)"

// 创建索引
#define SQL_QUERY_CREATE_INDEX   R"(
CREATE INDEX IF NOT EXISTS idx_file_list_id ON file_list(id COLLATE BINARY ASC);
CREATE INDEX IF NOT EXISTS idx_file_list_parent_id ON file_list(parent_id COLLATE BINARY ASC);
--CREATE INDEX IF NOT EXISTS idx_file_list_name ON file_list(name COLLATE NOCASE ASC);
)"

// 删除索引
#define SQL_QUERY_DROP_SEARCH_INDEX   R"(
DROP INDEX IF EXISTS idx_file_list_path ON file_list;
)"

// 创建索引
#define SQL_QUERY_CREATE_SEARCH_INDEX   R"(
CREATE INDEX IF NOT EXISTS idx_file_list_path ON file_list(path COLLATE NOCASE ASC);
)"

// 按文件名查找
#define SQL_QUERY_SEARCH_NAME   R"(
SELECT path FROM file_list WHERE name like "%s" ESCAPE '\' AND path NOT NULL ORDER BY path
)"

// 按路径查找
#define SQL_QUERY_SEARCH_PATH   R"(
SELECT path FROM file_list WHERE path like "%s" ESCAPE '\' AND path NOT NULL ORDER BY path
)"

// 搜索全部
#define SQL_QUERY_SEARCH_ALL   R"(
SELECT path FROM file_list WHERE path NOT NULL ORDER BY path
)"

// 删除表
#define SQL_QUERY_DELETE_TABLE                R"(
DROP TABLE IF EXISTS file_list;
)"

// 创建表
#define SQL_QUERY_CREATE_TABLE                R"(
CREATE TABLE IF NOT EXISTS file_list (
    id        INTEGER NOT NULL,
    parent_id INT,
    name      TEXT,
    attr      INT,
    path      TEXT,
    PRIMARY KEY(id, parent_id, name)
);
)"

// 建表更新数据
#define SQL_QUERY_REPLACE_PREPQRE R"(
REPLACE INTO file_list (id, parent_id, name, attr, path) VALUES (?, ?, ?, ?, ?);
)"

// 替换记录
#define SQL_QUERY_REPLACE R"(
REPLACE INTO file_list (id, parent_id, name, attr) VALUES (%llu, %llu, "%s", %d);
)"

// 删除记录
#define SQL_QUERY_DELETE R"(
DELETE FROM file_list where id = %llu AND parent_id = %llu AND name = "%s";
)"


bool NTFS_Search::Search(
    const _tstring& strKeyWord, 
    std::vector<_tstring>& fileList,
    int64_t nLimit/* = -1*/
)
{
    if (!m_fInit || strKeyWord.empty())
    {
        return false;
    }

    _tstring strKey = strKeyWord;
    bool fSearchPath = false;
    bool fSearchFuzzy = false;

    // 检查搜索是否显式指定通配符, 没有通配符则需要前后添加通配符
    if (!(_tstring::npos != strKeyWord.find(_T("*")) || _tstring::npos != strKeyWord.find(_T("?"))))
    {
        fSearchFuzzy = true;
    }

    // 检查搜索是否为全部(关键字全是 * 字符)
    bool fAll = true;
    for (auto& ch : strKeyWord)
    {
        if (_T('*') != ch)
        {
            fAll = false;
            break;
        }
    }

    // 检查搜索是否包含路径
    if (_tstring::npos != strKeyWord.find(_T("\\")))
    {
        fSearchPath = true;
    }

    _tstring strFormat = _T(SQL_QUERY_SEARCH_ALL);

    // 搜索包含路径
    if (fSearchPath)
    {
        strFormat = _T(SQL_QUERY_SEARCH_PATH);
    }
    else
    {
        strFormat = _T(SQL_QUERY_SEARCH_NAME);
    }

    if (!fAll && fSearchFuzzy)
    {
        strKey = _T("*") + strKeyWord + _T("*");
    }

    // 转义处理
    CStrUtils::Replace(strKey, _T(R"(\)"), _T(R"(\\)"));
    CStrUtils::Replace(strKey, _T(R"(%)"), _T(R"(\%)"));
    CStrUtils::Replace(strKey, _T(R"(_)"), _T(R"(\_)"));
    CStrUtils::Replace(strKey, _T(R"(*)"), _T(R"(%)"));
    CStrUtils::Replace(strKey, _T(R"(?)"), _T(R"(_)"));

    _tstring strSql;
    if (fAll)
    {
        strSql = CStrUtils::Format(_T(SQL_QUERY_SEARCH_ALL), nLimit);
    }
    else
    {
        strSql = CStrUtils::Format(strFormat.c_str(), strKey.c_str(), nLimit);
    }

    std::string strQuery = CStrUtils::TStrToU8Str(strSql);
    int res = m_sql3.Exec(strQuery.c_str(), [](void* data, int col_count, char** col_data, char** col_name)->int {

        std::vector<_tstring>* pFileList = (std::vector<_tstring>*)data;
        for (int i = 0; i < col_count; i++)
        {
            if (0 == CStrUtils::CompareA("path", (char*)col_name[i]))
            {
                if (pFileList && col_data[i])
                {
                    pFileList->push_back(CStrUtils::U8StrToTStr(col_data[i]));
                }
            }
        }

        return 0;

        }
    ,(char**)&fileList, 0);

    return res;
}

 

对比:

ebc81f3f30ff45b2a86fa84ee43e59f5.png

性能上重建数据库耗时是everything的 3倍, 不过也差不多了, 以后慢慢优化

 

 

629c470a73d0459ea48f1be6e01de80c.png 

搜索速度比不上everything, 耗时是其2倍左右,但是也算是秒速了

 

 

 

 

 

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/940661.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【完美解决】windows打开cmd窗口的时候闪退解决办法

本章教程&#xff0c;主要记录&#xff0c;windows中打开cmd的时候&#xff0c;闪退问题。产生问题的原因&#xff0c;电脑上之前安装了anaconda&#xff0c;卸载之后&#xff0c;就发现cmd无法正常打开&#xff0c;一执行不会弹出窗口。 解决办法 一、打开注册表 regedit二、…

乐凡信息智能安全管控方案:助力油气田行业安全管控多方位升级

我国油田地域广阔&#xff0c;分布着大量各种油井&#xff0c;油井开采设备的连续稳定运行是保证石油开采的首要条件。然而&#xff0c;由于油田多位于特殊地理环境中&#xff0c;因而实现油井之间的通信首要问题就是要克服地理环境所带来的限制&#xff0c;传统通信系统的建设…

2024年企业中生成式 AI 的现状报告

从试点到生产&#xff0c;企业 AI 格局正在被实时改写。我们对 600 名美国企业 IT 决策者进行了调查&#xff0c;以揭示新兴的赢家和输家。 从试点到生产 2024 年标志着生成性人工智能成为企业关键任务的一年。这些数字讲述了一个戏剧性的故事&#xff1a;今年人工智能支出飙升…

L24.【LeetCode笔记】 杨辉三角

目录 1.题目 2.分析 模拟二维数组的大致思想 杨辉三角的特点 二维数组的元素设置代码 两个参数returnSize和returnColumnSizes 理解"有效"的含义 完整代码 提交结果 1.题目 给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 在「杨辉…

“从零到一:揭秘操作系统的奇妙世界”【操作系统中断和异常】

一开始看王道网课&#xff0c;它说内中断就是异常。但是我一查ai&#xff0c;它又说内中断和异常不能等同&#xff0c;是两个概念&#xff0c;这时候我觉得天都塌了。内中断到底是不是异常啊&#xff1f; 我心想我今天一定要把这个搞懂&#xff0c;我来交作业了&#xff01;我…

C#代码实现把中文录音文件(.mp3 .wav)转为文本文字内容

我们有一个中文录音文件.mp3格式或者是.wav格式&#xff0c;如果我们想要提取录音文件中的文字内容&#xff0c;我们可以采用以下方法&#xff0c;不需要使用Azure Speech API 密钥注册通过离线的方式实现。 1.首先我们先在NuGet中下载两个包 NAudio 2.2.1、Whisper.net 1.7.3…

Windows装Docker至D盘/其他盘(最新,最准确,直接装)

前言 Docker的默认安装路径为 C:\你的用户名\AppData\Local\Docker\wsl这样安装常常会导致C盘爆满。目前现有博客的安装方法往往不能把docker的container和image也装在非C盘。本博客旨在用最简单的方式&#xff0c;把Docker Deskstop的images和container装在D盘中。 安装前&a…

前端关于pptxgen.js个人使用介绍

官方文档链接:Quick Start Guide | PptxGenJS git地址&#xff1a;https://github.com/gitbrent/PptxGenJS/ 1. 安装命令 npm install pptxgenjs --save yarn add pptxgenjs 2. 示例demo import pptxgen from "pptxgenjs"; // 引入pptxgen // 1. Create a Presenta…

Vulnhub靶场Nginx解析漏洞复现

一.nginx_parsing 原理&#xff1a;这个解析漏洞其实是PHP CGI的漏洞&#xff0c;在PHP的配置⽂件中有⼀个关键的选项cgi.fix_pathinfo默认是开启的&#xff0c;当URL中有不存在的⽂件&#xff0c;PHP就会向前递归解析。在⼀个⽂件/xx.jpg后⾯加上/.php会将 /xx.jpg/xx.php 解…

harbor离线安装 配置https 全程记录

1. 下载harbor最新版本 下载网址: 找最新的版本: https://github.com/goharbor/harbor/releases/download/v2.11.2/harbor-offline-installer-v2.11.2.tgz 这里我直接使用迅雷下载, 然后上传 1.1解压 sudo tar -xf harbor-offline-installer-v2.11.2.tgz -C /opt/ 2. 配置Harb…

Next.js v15 - 服务器操作以及调用原理

约定 服务器操作是在服务器上执行的异步函数。它们可以在服务器组件和客户端组件中调用&#xff0c;用于处理 Next.js 应用程序中的表单提交和数据修改。 服务器操作可以通过 React 的 “use server” 指令定义。你可以将该指令放在 async 函数的顶部以将该函数标记为服务器操…

编译原理复习---目标代码生成

适用于电子科技大学编译原理期末考试复习。 1. 目标代码 是目标机器的汇编代码或机器码&#xff0c;在本课程中指的是类似于汇编代码的一种形式&#xff0c;由一条条的指令构成目标代码。 抽象机指令格式&#xff1a;OP 目的操作数&#xff0c;源操作数。 我们要做的&…

JaxaFx学习(三)

目录&#xff1a; &#xff08;1&#xff09;JavaFx MVVM架构实现 &#xff08;2&#xff09;javaFX知识点 &#xff08;3&#xff09;JavaFx的MVC架构 &#xff08;4&#xff09;JavaFx事件处理机制 &#xff08;5&#xff09;多窗体编程 &#xff08;6&#xff09;数据…

Type-C 接口电热毯:开启温暖智能新时代

在当今科技迅猛发展的时代&#xff0c;智能家居产品如同璀璨繁星般点缀着我们的生活&#xff0c;从智能灯光的温馨到温控系统的精准&#xff0c;处处都彰显着科技赋予生活的便捷与舒适。而在这股追求高效与智能化的洪流之中&#xff0c;一款极具创新的电热毯——Type-C 接口电热…

解决vscode ssh远程连接服务器一直卡在下载 vscode server问题

目录 方法1&#xff1a;使用科学上网 方法2&#xff1a;手动下载 方法3 在使用vscode使用ssh远程连接服务器时&#xff0c;一直卡在下载"vscode 服务器"阶段&#xff0c;但MobaXterm可以正常连接服务器&#xff0c;大概率是网络问题&#xff0c;解决方法如下: 方…

WSL Ubuntu

文章目录 1. 概述1.1 什么是适用于 Linux 的 Windows 子系统1.2 什么是 WSL 21.3 WSL 2 中的新增功能1.4 比较 WSL 2 和 WSL 1 2. 参考资料3. 修改存储位置4. 网络访问 1. 概述 1.1 什么是适用于 Linux 的 Windows 子系统 适用于 Linux 的 Windows 子系统可让开发人员按原样运…

clickhouse-数据库引擎

1、数据库引擎和表引擎 数据库引擎默认是Ordinary&#xff0c;在这种数据库下面的表可以是任意类型引擎。 生产环境中常用的表引擎是MergeTree系列&#xff0c;也是官方主推的引擎。 MergeTree是基础引擎&#xff0c;有主键索引、数据分区、数据副本、数据采样、删除和修改等功…

如何使用Python进行音频片断合成

以下是几种使用 Python 进行音频合成的方法&#xff1a; 使用 synthesizer 库 通过 pip install synthesizer 安装后&#xff0c;利用其提供的合成器类&#xff0c;可自定义振荡器类型&#xff0c;如锯齿波、方波或正弦波&#xff0c;并调制振幅来创造不同音色&#xff0c;还…

【SH】在Ubuntu Server 24中基于Python Web应用的Flask Web开发(实现POST请求)学习笔记

文章目录 Flask开发环境搭建保持Flask运行Debug调试 路由和视图可变路由 请求和响应获取请求信息Request属性响应状态码常见状态码CookieSession 表单GET请求POST请求 Flask 在用户使用浏览器访问网页的过程中&#xff0c;浏览器首先会发送一个请求到服务器&#xff0c;服务器…

CLION中运行远程的GUI程序

在CLION中运行远程GUI程序&#xff0c;很有可能会遇到下面错误 Gtk-WARNING **: cannot open display: 这是因为远程的GUI程序不能再本地机器上显示。这个问题一般有两种解决方法 通过SSH的ForwardX11的方法&#xff0c;就是将远程的GUI程序显示到本地机器上&#xff0c;一般在…