DocFastSearchTool(文档快速搜索工具)开发日志

目录

项目介绍

项目调研背景

项目需求分析

开发环境

项目涉及基础知识点

项目设计

设计基础

项目框架or架构

项目框架的搭建

系统工具模块--遍历目录

SQLite数据库

在Windows上安装SQLite

SQLite命令

SQLite-C/C++的API

安装SQLite源码

 数据库操作的重要接口

连接数据库

创建表

插入数据

查询数据

删除数据

数据库管理模块

封装sqlite数据库

新增数据管理模块:dataManager.h

dataManage.cpp

封装数据管理类

扫描模块

搜索函数

简易搜索模块

利用RAII机制解决表结果的自动释放

对SQLite进行静态链接库的使用

日志模块

实时扫描功能

数据管理模块的单例化

扫描管理类的单例化

新增监控模块

中间逻辑层实现

 客户端的实现

 增加界面

界面实现

重构搜索


项目介绍

项目调研背景

  1. Windows自带搜索工具为暴力搜索,效率极低,速度奇慢。
  2. Everything工具能够快速的实现搜索,但是 只适用于NTFS格式、无法匹配拼音搜索。
  3. QQ搜索可以快速搜索,且支持拼音以及首字母的搜索方式。
  4. 通过以上对比,希望能拥有一个针对文档搜索的工具,且能够快速搜索以及支持多种搜索方式,以上便是此项目的项目背景。

项目需求分析

  • 支持文档常规搜索;
  • 支持拼音的全拼搜索;
  • 支持拼音的首字母搜索;
  • 支持搜索关键字高亮显示;
  • 扫描和监控(用户无感知)。

开发环境

  • 编译器:VS系列编译器
  • 编程语言:C++/C++ 11
  • 数据库:sqlite3

项目涉及基础知识点

  • 数据库操作:基础增删查改
  • 静态库和动态库:静态库和动态的制作,动态库和动态的使用
  • 设计模式(单例)
  • 多线程
  • 同步机制(互斥量、条件变量)
  • 汉字与拼音的转换

项目设计

设计基础

使用数据库的原因:可以体现工具的快速搜索,相对于系统来说,数据库更加快速,通过SQL语句实现快速查询

项目框架or架构

项目框架的搭建

  1. 创建common.h公共模块
  2. 创建DocFastSearchToolMain.cpp驱动模块
  3. 创建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进行静态链接库的使用

  1. 静态库和动态库:.lib为静态链接库  .dll围殴动态链接库

  2. 创建静态库工程:

  3. 添加程序的头文件和源文件,生成静态链接:

  4. 使用生成静态链接库:将程序的头文件.h+静态链接库文件.lib拷贝至工程

  5. 通过 #pragma comment(lib, "xxxx.lib")
  6. 按照上述步骤制作 sqlite 的静态链接库
  7. 删除 sqlite3.c ,使用 sqlite3.lib进行替换,然后通过命令引入静态库:#pragma comment(lib, "./sqlite3/sqlite3.lib")

日志模块

  • 什么是日志
网络设备、系统及服务程序等在运作时都会产生一个叫 log 的事件记录;每一行日志都记载着日期、时间、使用者及动作等相关操作的描述;它记录了用户访问系统的全过程:哪些人在什么时间,通过什么渠道(比如搜索引擎、网址输入)来过,都执行了哪些操作;系统是否产生了错误;甚至包括用户的IP、HTTP 请求的时间,用户代理等。
  • 日志的级别
日志一共分成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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值