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;
}
对比:
性能上重建数据库耗时是everything的 3倍, 不过也差不多了, 以后慢慢优化
搜索速度比不上everything, 耗时是其2倍左右,但是也算是秒速了