当前位置: 首页 > article >正文

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倍左右,但是也算是秒速了

 

 

 

 

 

 

 

 

 


http://www.kler.cn/a/446874.html

相关文章:

  • “宏“知识详解
  • (OCPP服务器)SteVe编译搭建全过程
  • 让汉语和英语一样长的字符编码
  • 半连接转内连接规则的原理与代码解析 |OceanBase查询优化
  • 【原生js案例】让你的移动页面实现自定义的上拉加载和下拉刷新
  • 常用es命令
  • 宠物管理系统(2):utils
  • LeetCode136. 只出现一次的数字(2024冬季每日一题 38)
  • 基于SpringBoot+layui+html实现电影院售票系统【源码+数据库文件+包部署成功+答疑解惑问到会为止】
  • 相机(Camera)成像原理详解
  • JavaScript中,常用crypto模块进行rsa加密,crypto-js模块进行md5算法
  • 【数据库】SQL语句基础
  • Java中正则表达式的介绍、使用场景及示例代码
  • Java学习,输出数组元素
  • 31.设计模式
  • Element@2.15.14-tree checkStrictly 状态实现父项联动子项,实现节点自定义编辑、新增、删除功能
  • Java基础面试题17:GenericServlet和HttpServlet有什么区别?
  • 【Java】mac安装Java17(JDK17)
  • 前端数据持久化指南:LocalStorage、SessionStorage 等的区别与应用
  • 从零用java实现 小红书 springboot vue uniapp (4)个人主页优化
  • 首个!艾灵参编的工业边缘计算国家标准正式发布
  • Epic游戏使用mod
  • MySQL通过日志恢复数据的步骤
  • Java中的方法重写:深入解析与最佳实践
  • debian linux 连网自动调整时间 (报错 Unit systemd-timesyncd.service could not be found.)
  • .NET周刊【12月第2期 2024-12-08】