目录
项目介绍
项目调研背景
项目需求分析
开发环境
项目涉及基础知识点
项目设计
设计基础
项目框架or架构
项目框架的搭建
系统工具模块--遍历目录
SQLite数据库
在Windows上安装SQLite
SQLite命令
SQLite-C/C++的API
安装SQLite源码
数据库操作的重要接口
连接数据库
创建表
插入数据
查询数据
删除数据
数据库管理模块
封装sqlite数据库
新增数据管理模块:dataManager.h
dataManage.cpp
封装数据管理类
扫描模块
搜索函数
简易搜索模块
利用RAII机制解决表结果的自动释放
对SQLite进行静态链接库的使用
日志模块
实时扫描功能
数据管理模块的单例化
扫描管理类的单例化
新增监控模块
中间逻辑层实现
客户端的实现
增加界面
界面实现
重构搜索
项目介绍
项目调研背景
- Windows自带搜索工具为暴力搜索,效率极低,速度奇慢。
- Everything工具能够快速的实现搜索,但是 只适用于NTFS格式、无法匹配拼音搜索。
- QQ搜索可以快速搜索,且支持拼音以及首字母的搜索方式。
- 通过以上对比,希望能拥有一个针对文档搜索的工具,且能够快速搜索以及支持多种搜索方式,以上便是此项目的项目背景。
项目需求分析
- 支持文档常规搜索;
- 支持拼音的全拼搜索;
- 支持拼音的首字母搜索;
- 支持搜索关键字高亮显示;
- 扫描和监控(用户无感知)。
开发环境
- 编译器:VS系列编译器
- 编程语言:C++/C++ 11
- 数据库:sqlite3
项目涉及基础知识点
- 数据库操作:基础增删查改
- 静态库和动态库:静态库和动态的制作,动态库和动态的使用
- 设计模式(单例)
- 多线程
- 同步机制(互斥量、条件变量)
- 汉字与拼音的转换
项目设计
设计基础
使用数据库的原因:可以体现工具的快速搜索,相对于系统来说,数据库更加快速,通过SQL语句实现快速查询。
项目框架or架构
项目框架的搭建
- 创建common.h公共模块
- 创建DocFastSearchToolMain.cpp驱动模块
- 创建sysutil.h系统工具模块
系统工具模块--遍历目录
1、需要使用的函数
//功能是搜索与指定文件名匹配的第一个实例,若成功则返回第一个实例的句柄,失败返回-1
long _findfirst(char *filespec, struct _finddata_t *fileinfo);
//_findnext提供的搜索文件名称匹配的下一个实例,成功返回0,失败返回-1
int _findnext(long handle, struct _finddata_t *dileinfo);
//关闭句柄,释放由fingfirst分配的内存
int _findclose(long handle);
2、_finddata_t结构
//用来存储文件各种信息的结构体,定义如下:
struct _finddata_t
{
unsigned attrib;
time_t time_create;
time_t time_access;
time_t time_write;
_fsize_t size;
char name[_MAX_FNAME];
};
3、目录显示函数的实现
void DirectionList(const string& path,vector<string> &sub_dir,vector<string> &sub_file)
{
struct _finddata_t file;
string _path = path;
_path += "\\*.*";
long handle = _findfirst(_path.c_str(), &file);
if (handle == -1)
{
printf("扫描目录失败.\n");
return;
}
do
{
if (file.name[0] == '.')
continue;
//cout << file.name << endl;
if (file.attrib & _A_SUBDIR)
sub_dir.push_back(file.name);
else
sub_file.push_back(file.name);
if (file.attrib & _A_SUBDIR)
{
//文件为目录(文件夹)
string tmp_path = path;
tmp_path += "\\";
tmp_path += file.name;
//目录递归遍历
DirectionList(tmp_path,sub_dir,sub_file);
}
} while (_findnext(handle, &file) == 0);
_findclose(handle);
}
4、目录显示函数数据保存
void DirectionList(const string &path, vector<string> &sub_dir, vector<string> &sub_file)
{
struct _finddata_t file;
string _path = path;
_path += "\\*.*";
long handle = _findfirst(_path.c_str(), &file);
if(handle == -1)
{
printf("扫描目录失败.\n");
return;
}
do
{
if(file.name[0] == '.')
continue;
//cout<<file.name<<endl;
if(file.attrib & _A_SUBDIR)
sub_dir.push_back(file.name);
else
sub_file.push_back(file.name);
if(file.attrib & _A_SUBDIR)
{
//文件为目录(文件夹)
string tmp_path = path;
tmp_path += "\\";
tmp_path += file.name;
//目录递归遍历
DirectionList(tmp_path, sub_dir, sub_file);
}
}while(_findnext(handle,&file) == 0);
_findclose(handle);
}
SQLite数据库
1、菜鸟教程:www.runoob.com
2、SQL
- SQL是用于访问和处理数据库的标准计算机语言
- SQL(结构化查询语言)适用于管理关系数据库管理系统(RDBMS)。SQL的范围包括数据插入、查询、更新和删除,数据库模式的创建和修改,以及数据访问控制。
3、sqlite
SQLite是一个软件库,实现了自给自足、无服务器的、零配置的、事务性的SQL数据库引擎。SQLite是在世界上最广泛部署的SQL数据库引擎。SQLite源代码不受版权限制。
4、为什么选择SQLite(优点)
- 无服务器
- 不需要配置
- 非常小,轻量级
- 自给自足不需要外部的依赖
- 完全兼容ACID,允许多个进程或者线程安全访问
- 支持SQL92标准的大多数查询语言的功能
- SQLite使用ANSI-C编写,停工了简单和易于使用的接口
- 可以在UNIX中运行
在Windows上安装SQLite
-
请访问 SQLite 下载页面,从 Windows 区下载预编译的二进制文件。
-
您需要下载 sqlite-tools-win32-*.zip 和 sqlite-dll-win32-*.zip 压缩文件。
-
创建文件夹 C:\sqlite,并在此文件夹下解压上面两个压缩文件,将得到 sqlite3.def、sqlite3.dll 和 sqlite3.exe 文件。
-
添加 C:\sqlite 到 PATH 环境变量,最后在命令提示符下,使用 sqlite3 命令,将显示如下结果。
SQLite命令
- 进入数据库:在命令行下直接sqlite3,进入数据库。
- 推出数据库:.exit / .quit
- 点命令:.exit |.quit | .help | .database | .tables
- 创建数据库:sqlite3 databasename.db通过.database显示或者启动数据库通过.open databasename.db来创建数据库。
- 创建表:
- 删除表
- 插入数据
- 查询数据
- 删除数据
- 修改数据
//语法
//创建表
CREATE TABLE database_name.table_name(
column1 datatype PRIMARY KEY(one or more columns),
column2 datatype,
column3 datatype,
.....
columnN datatype,
);
//查找表
.tables
//删除表
DROP TABLE table_name;
//插入数据
INSERT INTO table_name values("name","path");
//查询数据
select * from table_name;
//删除数据
DELETE from table_name where doc_name="name";
//修改数据
updata table_name set doc_name="name1";
SQLite-C/C++的API
安装SQLite源码
原理就是将sqlite3.h与sqlite3.c安装到工程文件下。
教程在SQLite 安装 | 菜鸟教程 (runoob.com)
数据库操作的重要接口
//打开数据库
int sqlite3_open(const char *filename, sqlite3 **ppDb);
//关闭数据库
int sqlite3_close(sqlite3*);
//对数据库进行操作,执行sql语句
int sqlite3_exec(sqlite3*, const char *sql, sqlite_callback,void *data, char **errmsg);
int sqlite3_get_table(
sqlite3 *db, /* An open database */
const char *zSql, /* SQL to be evaluated */
char ***pazResult, /* Results of the query */
int *pnRow, /* Number of result rows written here */
int *pnColumn, /* Number of result columns written here */
char **pzErrmsg /* Error msg written here */
);
void sqlite3_free_table(char **result);
连接数据库
#include<iostream>
#include"sqlite3.h"
using namespace std;
void main()
{
sqlite3 *p_db;
int rc = sqlite3_open("doc.db", &p_db);
if (rc != SQLITE_OK)
{
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(p_db));
exit(0);
}
else
{
fprintf(stderr, "Opened database successfully\n");
}
sqlite3_close(p_db);
}
创建表
#include<iostream>
#include"sqlite3.h"
using namespace std;
void main()
{
sqlite3 *p_db;
int rc = sqlite3_open("doc.db", &p_db);
if (rc != SQLITE_OK)
{
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(p_db));
exit(0);
}
else
{
fprintf(stderr, "Opened database successfully\n");
}
const char *sql = "CREATE TABLE if not exists doc_info(\
id integer primary key autoincrement,\
doc_name text,\
doc_path text)";
char *zErrMsg = 0;
rc = sqlite3_exec(p_db, sql, 0, 0, &zErrMsg);
if (rc != SQLITE_OK)
{
fprintf(stderr, "SQL error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}
else
{
fprintf(stdout, "Table created successfully\n");
}
sqlite3_close(p_db);
}
插入数据
#include<iostream>
#include"sqlite3.h"
using namespace std;
void main()
{
sqlite3 *p_db;
int rc = sqlite3_open("doc.db", &p_db);
if (rc != SQLITE_OK)
{
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(p_db));
exit(0);
}
else
{
fprintf(stderr, "Opened database successfully\n");
}
//const char *sql = "INSERT INTO doc_info values(null, 'C++.pdf','C:\\Book\\')";
const char *sql = "INSERT INTO doc_info values(null, 'C++.pdf',
'C:\\Book\\');"\
"INSERT INTO doc_info values(null, 'Linux.pdf',
'C:\\Book\\');"\
"INSERT INTO doc_info values(null, 'Java.pdf',
'C:\\Book\\');";
char *zErrMsg = 0;
rc = sqlite3_exec(p_db, sql, 0, 0, &zErrMsg);
if (rc != SQLITE_OK)
{
fprintf(stderr, "SQL error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}
else
{
fprintf(stdout, "Insert Data successfully\n");
}
sqlite3_close(p_db);
}
查询数据
//方法一
int callback(void *data, int argc, char **argv, char **azColName)
{
//fprintf(stderr, "%s: \n", (const char*)data);
for (int i = 0; i < argc; i++)
{
//printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
printf("%s ", argv[i]);
}
printf("\n");
return 0;
}
void main()
{
sqlite3 *p_db;
int rc = sqlite3_open("bit77.db", &p_db);
if (rc != SQLITE_OK)
{
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(p_db));
exit(0);
}
else
{
fprintf(stderr, "Opened database successfully\n");
}
const char *sql = "SELECT * from doc_info";
char* data = "Callback function called";
char *zErrMsg = 0;
rc = sqlite3_exec(p_db, sql, callback, data, &zErrMsg);
if (rc != SQLITE_OK)
{
fprintf(stderr, "SQL error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}
else
{
fprintf(stdout, "Select Data successfully\n");
}
qlite3_close(p_db);
}
//方法二:通过获取表的形式得到结果
void main()
{
sqlite3 *p_db;
int rc = sqlite3_open("bit77.db", &p_db);
if (rc != SQLITE_OK)
{
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(p_db));
exit(0);
}
else
{
fprintf(stderr, "Opened database successfully\n");
}
const char *sql = "SELECT * from doc_info";
char* *ppResult = 0;
int row, col;
char *zErrMsg = 0;
rc = sqlite3_get_table(p_db, sql, &ppResult, &row, &col, &zErrMsg);
if(rc != SQLITE_OK)
{
fprintf(stderr, "SQL Error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}
else
{
printf("row = %d, col = %d\n", row, col);
for(int i=1; i<=row; ++i)
{
for(int j=0; j<col; ++j)
{
//cout<< *(ppResult + (i*col) + j) <<"\t";
printf("%s", *(ppResult + (i*col) + j));
printf("\t");
}
printf("\n");
}
}
sqlite3_free_table(ppResult);
sqlite3_close(p_db);
}
删除数据
#include<iostream>
#include"sqlite3.h"
using namespace std;
void main()
{
sqlite3 *p_db;
int rc = sqlite3_open("bit77.db", &p_db);
if (rc != SQLITE_OK)
{
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(p_db));
exit(0);
}
else
{
fprintf(stderr, "Opened database successfully\n");
}
const char *sql = "DELETE from doc_info where doc_name='Linux.pdf'";
char *zErrMsg = 0;
rc = sqlite3_exec(p_db, sql, 0, 0, &zErrMsg);
if (rc != SQLITE_OK)
{
fprintf(stderr, "SQL error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}
else
{
fprintf(stdout, "Delete Data successfully\n");
}
sqlite3_close(p_db);
}
数据库管理模块
封装sqlite数据库
新增数据管理模块:dataManager.h
class SqliteManager
{
public:
SqliteManager();
~SqliteManager();
public:
void Open(const string &database); //打开或创建数据库
void Close(); //关闭数据库
void ExecuteSql(const string &sql); //执行SQL
void GetResultTable(const string &sql, char **&ppRet, int &row, int
&col);
private:
sqlite3 *m_db;
};
dataManage.cpp
#include"dataManager.h"
SqliteManager::SqliteManager():m_db(nullptr)
{}
SqliteManager::~SqliteManager()
{}
void SqliteManager::Open(const string &database)
{
int rc = sqlite3_open(database.c_str(), &m_db);
if (rc != SQLITE_OK)
{
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(m_db));
exit(1);
}
else
{
fprintf(stderr, "Opened database successfully\n");
}
}
void SqliteManager::Close()
{
int rc = sqlite3_close(m_db);
if (rc != SQLITE_OK)
{
fprintf(stderr, "Can't close database: %s\n",
sqlite3_errmsg(m_db));
exit(1);
}
else
{
fprintf(stderr, "Close database successfully\n");
}
}
void SqliteManager::ExecuteSql(const string &sql)
{
char *zErrMsg = 0;
int rc = sqlite3_exec(m_db, sql.c_str(), 0, 0, &zErrMsg);
if (rc != SQLITE_OK)
{
fprintf(stderr, "SQL error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}
else
{
fprintf(stdout, "Operation sql successfully\n");
}
}
void SqliteManager::GetResultTable(const string &sql, char **&ppRet, int
&row, int &col)
{
char *zErrMsg = 0;
int rc = sqlite3_get_table(m_db, sql.c_str(), &ppRet, &row, &col,
&zErrMsg);
if(rc != SQLITE_OK)
{
fprintf(stderr, "SQL Error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}
else
{
fprintf(stdout, "Get Result Table successfully\n");
}
}
封装数据管理类
//封装数据管理类
class DataManager
{
public:
DataManager();
~DataManager();
public:
void InitSqlite(); //初始化数据库
void InsertDoc(const string &path, const string &doc);
void DeleteDoc(const string &path, const string &doc);
void GetDoc(const string &path, multiset<string> &docs);
private:
SqliteManager m_dbmgr;
};
DataManager::DataManager()
{
m_dbmgr.Open(DOC_DB);
InitSqlite(); //创建表
}
DataManager::~DataManager()
{}
void DataManager::InitSqlite()
{
char sql[SQL_BUFFER_SIZE] = {0};
sprintf(sql, "CREATE TABLE if not exists %s(\
id integer primary key autoincrement,\
doc_name text,\
doc_path text)", DOC_TB);
m_dbmgr.ExecuteSql(sql);
}
void DataManager::InsertDoc(const string &path, const string &doc)
{
char sql[SQL_BUFFER_SIZE] = {0};
sprintf(sql, "INSERT INTO %s values(null, '%s', '%s')",
DOC_TB, doc.c_str(), path.c_str());
m_dbmgr.ExecuteSql(sql);
}
void DataManager::DeleteDoc(const string &path, const string &doc)
{
char sql[SQL_BUFFER_SIZE] = {0};
sprintf(sql, "DELETE FROM %s where doc_path='%s' and doc_name='%s'",
DOC_TB, path.c_str(), doc.c_str());
m_dbmgr.ExecuteSql(sql);
}
void DataManager::GetDoc(const string &path, multiset<string> &docs)
{
char sql[SQL_BUFFER_SIZE] = {0};
sprintf(sql, "SELECT doc_name from %s where doc_path='%s'",
DOC_TB, path.c_str());
char **ppRet = 0;
int row = 0, col = 0;
m_dbmgr.GetResultTable(sql, ppRet, row, col);
for(int i=1; i<=row; ++i)
docs.insert(ppRet[i]);
//释放表结果
sqlite3_free_table(ppRet);
}
扫描模块
class ScanManager
{
public:
//同步数据
void ScanDirectory(const string &path);
private:
DataManager m_dbmgr;
};
//同步本地文件和数据库文件的数据
void ScanManager::ScanDirectory(const string &path)
{
//1 扫描本地文件
vector<string> local_dir;
vector<string> local_file;
DirectionList(path, local_dir, local_file);
multiset<string> local_set;
local_set.insert(local_file.begin(), local_file.end());
local_set.insert(local_dir.begin(), local_dir.end());
//2 扫描数据库文件
multiset<string> db_set;
m_dbmgr.GetDoc(path, db_set);
//3 同步数据
auto local_it = local_set.begin();
auto db_it = db_set.begin();
while(local_it!=local_set.end() && db_it!=db_set.end())
{
if(*local_it < *db_it)
{
//本地有,数据库没有,数据库插入文件
m_dbmgr.InsertDoc(path, *local_it);
++local_it;
}
else if(*local_it > *db_it)
{
//本地没有,数据库有,数据库删除文件
m_dbmgr.DeleteDoc(path, *db_it);
++db_it;
}
else
{
//两者都有
++local_it;
++db_it;
}
}
while(local_it != local_set.end())
{
//本地有,数据库没有,数据库插入文件
m_dbmgr.InsertDoc(path, *local_it);
++local_it;
}
while(db_it != db_set.end())
{
//本地没有,数据库有,数据库删除文件
m_dbmgr.DeleteDoc(path, *db_it);
++db_it;
}
}
搜索函数
在dataManager类中新增函数:
void Search(const string %key, vector<pair<string,string>> &doc_path);
void DataManager::Search(const string &key, vector<pair<string,string>>
&doc_path)
{
char sql[SQL_BUFFER_SIZE] = {0};
sprintf(sql, "SELECT doc_name, doc_path from %s where doc_name like
'%%%s%%'",
DOC_TB, key.c_str());
char **ppRet;
int row, col;
m_dbmgr.GetResultTable(sql, ppRet, row, col);
for(int i=1; i<=row; ++i)
{
doc_path.push_back(make_pair(ppRet[i*col], ppRet[i*col+1]));
}
sqlite3_free_table(ppRet);
}
简易搜索模块
int main(int argc, char *argv[])
{
const string path = "C:\\Bit\\Code\\bit77\\Pro_文档快速搜索工具\\TestDoc";
//扫描目录
ScanManager sm;
sm.ScanDirectory(path);
//搜索
DataManager dm;
vector<pair<string,string>> doc_path;
string key;
while(1)
{
cout<<"数据搜索关键字:>";
cin>>key;
dm.Search(key, doc_path);
printf("%-25s%-50s\n", "名称","路劲");
for(const auto &e : doc_path)
printf("%-25s%-50s\n", e.first.c_str(), e.second.c_str());
}
return 0;
}
利用RAII机制解决表结果的自动释放
class AutoGetResultTable
{
public:
AutoGetResultTable(SqliteManager &db, const string &sql, char **&ppRet,
int &row, int &col);
~AutoGetResultTable();
private:
SqliteManager &m_db;
char **m_ppRet;
};
AutoGetResultTable::AutoGetResultTable(SqliteManager &db, const string
&sql,
char **&ppRet, int &row, int &col)
:m_db(db), m_ppRet(nullptr)
{
m_db.GetResultTable(sql, ppRet, row, col);
m_ppRet = ppRet;
}
AutoGetResultTable::~AutoGetResultTable()
{
if(m_ppRet)
sqlite3_free_table(m_ppRet);
}
对SQLite进行静态链接库的使用
-
静态库和动态库:.lib为静态链接库 .dll围殴动态链接库
-
创建静态库工程:
-
添加程序的头文件和源文件,生成静态链接:
-
使用生成静态链接库:将程序的头文件.h+静态链接库文件.lib拷贝至工程
-
通过 #pragma comment(lib, "xxxx.lib")
-
按照上述步骤制作 sqlite 的静态链接库
-
删除 sqlite3.c ,使用 sqlite3.lib进行替换,然后通过命令引入静态库:#pragma comment(lib, "./sqlite3/sqlite3.lib")
日志模块
- 什么是日志
- 日志的级别
日志一共分成5个等级,从低到高分别是:
DEBUG
INFO
WARNING
ERROR
CRITICAL
说明:
DEBUG:详细的信息,通常只出现在诊断问题上
INFO:确认一切按预期运行
WARNING:一个迹象表明,一些意想不到的事情发生了,或表明一些问题在不久的将来(例如。磁盘空间
低”)。这个软件还能按预期工作。
ERROR:更严重的问题,软件没能执行一些功能
CRITICAL:一个严重的错误,这表明程序本身可能无法继续运行
这5个等级,也分别对应5种打日志的方法: debug 、info 、warning 、error 、critical。
默认的是WARNING,当在WARNING或之上时才被跟踪。
- 日志实现
//获取文件名
string GetFileName(const string &path);
//追踪日志
void __TraceDebug(const char *filename, int line, const char *function,
const char *date, const char *time,const char *format, ...);
//错误日志
void __ErrorDebug(const char *filename, int line, const char *function,
const char *date, const char *time,
const char *format, ...);
#define TRACE_LOG(...) __TraceDebug(__FILE__, __LINE__, __FUNCTION__,
__DATE__, __TIME__, __VA_ARGS__)
#define ERROR_LOG(...) __ErrorDebug(__FILE__, __LINE__, __FUNCTION__,
__DATE__, __TIME__, __VA_ARGS__)
string GetFileName(const string &path)
{
char token = '\\';
size_t pos = path.rfind(token);
if(pos == string::npos)
return path;
return path.substr(pos+1);
}
void __TraceDebug(const char *filename, int line, const char *function,
const char *date, const char *time,
const char *format, ...)
{
#ifdef __TRACE__
fprintf(stdout, "[TRACE][%s:%d:%s %s:%s]:",
GetFileName(filename).c_str(),
line, function,
date, time);
//读取可变参数
va_list args; //char *args;
va_start(args, format);
vfprintf(stdout, format, args);
va_end(args);
fprintf(stdout, "\n");
#endif
}
void __ErrorDebug(const char *filename, int line, const char *function,
const char *date, const char *time,
const char *format, ...)
{
#ifdef __ERROR__
fprintf(stdout, "[ERROR][%s:%d:%s %s:%s]:",
GetFileName(filename).c_str(),
line, function,
date, time);
//读取可变参数
va_list args; //char *args;
va_start(args, format);
vfprintf(stdout, format, args);
va_end(args);
fprintf(stdout, "\n");
#endif
}
实时扫描功能
在ScanManager类中新增构造函数和扫描线程函数
class ScanManager
{
public:
ScanManager(const string &path);
public:
//........
//扫描线程
void ScanThread(const string &path);
private:
DataManager m_dbmgr;
};
ScanManager::ScanManager(const string &path)
{
thread ScanObj(&ScanManager::ScanThread, this, path);
ScanObj.detach();
}
void ScanManager::ScanThread(const string &path)
{
while(1)
{
ScanDirectory(path);
}
}
数据管理模块的单例化
构造函数私有化
class DataManager
{
//.............
protected:
DataManager(); //构造函数私有化
private:
SqliteManager m_dbmgr;
};
新增获取对象实例的接口
class DataManager
{
public:
static DataManager& GetInstance();
//.....................
};
DataManager& DataManager::GetInstance()
{
//懒汉模式
static DataManager _inst;
return _inst;
}
扫描管理类的单例化
构造私有化,新增获取实例接口
class ScanManager
{
public:
static ScanManager& GetInstance(const string &path);
protected:
ScanManager(const string &path);
ScanManager(ScanManager &);
ScanManager& operator=(const ScanManager&);
private:
//DataManager m_dbmgr;
};
ScanManager& ScanManager::GetInstance(const string &path)
{
static ScanManager _inst(path);
return _inst;
}
新增监控模块
需要使用到的API
#include<windows.h>
HANDLE FindFirstChangeNotification(
LPCTSTR lpPathName, // pointer to name of directory to watch
BOOL bWatchSubtree, // flag for monitoring directory or
// directory tree
DWORD dwNotifyFilter // filter conditions to watch for
);
BOOL FindNextChangeNotification(
HANDLE hChangeHandle // handle to change notification to signal
);
DWORD WaitForSingleObject(
HANDLE hHandle, // handle to object to wait for
DWORD dwMilliseconds // time-out interval in milliseconds
);
添加互斥量和条件变量
#include<mutex>
#include<condition_variable>
class ScanManager
{
//...............
mutex m_mutex;
condition_variable m_cond;
};
调整字符集
监控模块实现
void ScanManager::ScanThread(const string &path)
{
//初始化扫描
ScanDirectory(path);
while(1)
{
unique_lock<mutex> lock(m_mutex);
m_cond.wait(lock); //条件阻塞
ScanDirectory(path);
}
}
void ScanManager::WatchThread(const string &path)
{
HANDLE hd = FindFirstChangeNotification(path.c_str(), true,
FILE_NOTIFY_CHANGE_FILE_NAME |
FILE_NOTIFY_CHANGE_DIR_NAME);
if(hd == INVALID_HANDLE_VALUE)
{
//cout<<"监控目录失败."<<endl;
ERROR_LOG("监控目录失败.");
return;
}
while(1)
{
WaitForSingleObject(hd, INFINITE); //永不超时等待
m_cond.notify_one();
FindNextChangeNotification(hd);
}
}
中间逻辑层实现
准备工具函数
//汉字转拼音
string ChineseConvertPinYinAllSpell(const string &dest_chinese);
//汉字转拼音首字母
string ChineseConvertPinYinInitials(const string &name);
实现拼音全拼和首字母的搜索
- 对数据库表新增字段
void DataManager::InitSqlite()
{
char sql[SQL_BUFFER_SIZE] = {0};
sprintf(sql, "CREATE TABLE if not exists %s(\
id integer primary key autoincrement,\
doc_name text,\
doc_name_py text,\
doc_name_initials text,\
doc_path text)", DOC_TB);
m_dbmgr.ExecuteSql(sql);
}
- 新增数据
void DataManager::InsertDoc(const string &path, const string &doc)
{
//汉字转拼音
string doc_py = ChineseConvertPinYinAllSpell(doc);
//汉字转首字母
string doc_initials = ChineseConvertPinYinInitials(doc);
char sql[SQL_BUFFER_SIZE] = {0};
sprintf(sql, "INSERT INTO %s values(null, '%s', '%s','%s', '%s')",
DOC_TB, doc.c_str(), doc_py.c_str(), doc_initials.c_str(),
path.c_str());
m_dbmgr.ExecuteSql(sql);
}
- 新增拼音和首字母的搜索
void DataManager::Search(const string &key, vector<pair<string,string>>
&doc_path)
{
//汉字转拼音
string doc_py = ChineseConvertPinYinAllSpell(key);
//汉字转首字母
string doc_initials = ChineseConvertPinYinInitials(key);
char sql[SQL_BUFFER_SIZE] = {0};
sprintf(sql, "SELECT doc_name, doc_path from %s where doc_name like
'%%%s%%' or\
doc_name_py like '%%%s%%' or doc_name_initials like
'%%%s%%'",
DOC_TB, key.c_str(), doc_py.c_str(), doc_initials.c_str());
char **ppRet;
int row, col;
//m_dbmgr.GetResultTable(sql, ppRet, row, col);
AutoGetResultTable at(m_dbmgr, sql, ppRet, row, col);
doc_path.clear(); //清除之前搜索的数据
for(int i=1; i<=row; ++i)
{
doc_path.push_back(make_pair(ppRet[i*col], ppRet[i*col+1]));
}
//释放表结果
//sqlite3_free_table(ppRet);
}
高亮显示
- 新增颜色打印函数
// 颜色高亮显示一段字符串
void ColourPrintf(const char* str)
{
// 0-黑 1-蓝 2-绿 3-浅绿 4-红 5-紫 6-黄 7-白 8-灰 9-淡蓝 10-淡绿
// 11-淡浅绿 12-淡红 13-淡紫 14-淡黄 15-亮白
//颜色:前景色 + 背景色*0x10
//例如:字是红色,背景色是白色,即 红色 + 亮白 = 4 + 15*0x10
WORD color = 9 + 0 * 0x10;
WORD colorOld;
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO csbi;
GetConsoleScreenBufferInfo(handle, &csbi);
colorOld = csbi.wAttributes;
SetConsoleTextAttribute(handle, color);
printf("%s", str);
SetConsoleTextAttribute(handle, colorOld);
}
- 实现高亮分割函数
在dataManager类中新增分割函数
//封装数据管理类
class DataManager
{
public:
static void SplitHighLight(const string &str, const string &key,
string &prefix, string &highlight, string
&suffix);
};
void DataManager::SplitHighLight(const string &str, const string &key,
string &prefix, string &highlight, string
&suffix)
{
//忽略大小的匹配
string strlower = str;
string keylower = key;
transform(strlower.begin(), strlower.end(), strlower.begin(),
tolower);
transform(keylower.begin(), keylower.end(), keylower.begin(),
tolower);
//原始字符串能够匹配
size_t pos = strlower.find(keylower);
if(pos != string::npos)
{
prefix = str.substr(0, pos);
highlight = str.substr(pos, keylower.size());
suffix = str.substr(pos+keylower.size(), str.size());
return;
}
//拼音全拼搜索分割
string str_py = ChineseConvertPinYinAllSpell(strlower);
pos = str_py.find(keylower);
if(pos != string::npos)
{
int str_index = 0; //控制原始字符串的下标
int py_index = 0; //控制拼音字符串的下标
int highlight_index = 0; //控制高亮显示字符串的起始位置
int highlight_len = 0; //控制高亮字符串的长度
while(str_index < str.size())
{
if(py_index == pos)
{
//记录高亮的起始位置
highlight_index = str_index;
}
if(py_index >= pos+keylower.size())
{
//关键字搜索结束
highlight_len = str_index - highlight_index;
break;
}
if(str[str_index]>=0 && str[str_index]<=127)
{
//原始字符串是一个字符
str_index++;
py_index++;
}
else
{
//原始字符串是一个汉字
string word(str, str_index, 2); //截取一个汉字 //校
string word_py = ChineseConvertPinYinAllSpell(word);//xiao
str_index += 2;
py_index += word_py.size();
}
}
prefix = str.substr(0, highlight_index);
highlight = str.substr(highlight_index, highlight_len);
suffix = str.substr(highlight_index+highlight_len, str.size());
return;
}
//首字母搜索
string str_initials = ChineseConvertPinYinInitials(strlower);
pos = str_initials.find(keylower);
if(pos != string::npos)
{
int str_index = 0;
int initials_index = 0;
int highlight_index = 0;
int highlight_len = 0;
while(str_index < str.size())
{
if(initials_index == pos)
{
//记录高亮的起始位置
highlight_index = str_index;
}
if(initials_index >= pos+keylower.size())
{
highlight_len = str_index - highlight_index;
break;
}
if(str[str_index]>=0 && str[str_index]<=127)
{
//原始字符串是一个字符
str_index++;
initials_index++;
}
else
{
//原始字符串是一个汉字
str_index += 2;
initials_index++;
}
}
prefix = str.substr(0, highlight_index);
highlight = str.substr(highlight_index, highlight_len);
suffix = str.substr(highlight_index+highlight_len, str.size());
return;
}
//没有搜索到关键字
prefix = str;
highlight.clear();
suffix.clear();
}
- 实现搜索高亮显示
在主函数内部完成测试
客户端的实现
- 新增客户端模块sysFrame.h和sysFrame.cpp
- 界面核心技术system
增加界面
void SetCurPos(int col, int row);
void HideCursor();
void DrawCol(int x, int y);
void DrawRow(int x, int y);
void DrawFrame(const char *title);
void DrawMenu();
界面实现
#define WIDTH 120
#define HEIGHT 30
#define MAX_TITLE_SIZE 100
void SetCurPos(int col, int row)
{
//获取句柄
HANDLE hd = GetStdHandle(STD_OUTPUT_HANDLE);
//x代表列, y代表行
COORD pos = {col, row};
SetConsoleCursorPosition(hd, pos);
}
void HideCursor()
{
//获取句柄
HANDLE hd = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cur_info = {100, false};
SetConsoleCursorInfo(hd, &cur_info);
}
void DrawCol(int x, int y)
{
for (int i = 0; i < HEIGHT; ++i)
{
SetCurPos(x, y + i);
printf("||");
}
}
void DrawRow(int x, int y)
{
for (int i = 0; i < WIDTH - 4; ++i)
{
SetCurPos(x + i, y);
printf("=");
}
}
void DrawFrame(const char *title)
{
char buffer[MAX_TITLE_SIZE + 6 + 1] = "title "; //6:title%20 1:\0
strcat(buffer, title);
system(buffer); //设置系统标题
char mode[128] = { 0 };
sprintf(mode, "mode con cols=%d lines=%d", WIDTH, HEIGHT);
system(mode); //设置控制台的长度和宽度
system("color 0F");//设置颜色
DrawCol(0, 0);
DrawCol(WIDTH - 2, 0);
DrawRow(2, 0);
DrawRow(2, 2);
DrawRow(2, 4);
DrawRow(2, HEIGHT - 4);
DrawRow(2, HEIGHT - 2);
}
extern const char *title;
void DrawMenu()
{
//标题的设置
SetCurPos((WIDTH - 4 - strlen(title)) / 2, 1);
printf("%s", title);
//名称 路径
SetCurPos(2, 3);
printf("%-30s %-85s", "名称", "路径");
//退出设置
SetCurPos((WIDTH - 4 - strlen("exit 退出系统 .")) / 2, HEIGHT - 3);
printf("%s", "exit 退出系统 .");
DrawRow(2, HEIGHT - 6);
//SetCurPos((WIDTH-4-strlen("请输入:>"))/2, 15);
SetCurPos(2, HEIGHT - 5);
printf("%s", "请输入:>");
}
重构搜索
const char *title = "文档快速搜索工具";
int main(int argc, char *argv[])
{
const string path = "C:\\Bit\\Code\\bit77\\Pro_文档快速搜索工具\\TestDoc";
//扫描目录
ScanManager &sm = ScanManager::GetInstance(path);
//搜索
DataManager &dm = DataManager::GetInstance();
vector<pair<string,string>> doc_path;
string key;
while(1)
{
//显示界面
DrawFrame(title);
DrawMenu();
cin>>key;
if(key == "exit")
break;
dm.Search(key, doc_path);
int row = 5; //默认5行
int count = 0; //显示的行数
string prefix, highlight, suffix;
for(const auto &e : doc_path) //e : doc_name doc_path
{
//高亮分割
string doc_name = e.first;
DataManager::SplitHighLight(doc_name, key, prefix, highlight,
suffix);
//设置文档名显示位置
SetCurPos(2, row+count++);
cout<<prefix;
ColourPrintf(highlight.c_str());
cout<<suffix;
//设置路劲名显示位置
SetCurPos(33, row+count-1);
printf("%--85s\n", e.second.c_str());
}
SystemEnd();
SystemPause();
}
SystemEnd();
return 0;
}