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

news2024/11/13 12:27:46

目录

项目介绍

项目调研背景

项目需求分析

开发环境

项目涉及基础知识点

项目设计

设计基础

项目框架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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PB从入坑到放弃(三)数据窗口

PB从入坑到放弃&#xff08;三&#xff09;数据窗口 写在前面一、 数据窗口画板1.1 Design 视窗1.1.1 General tab页设置1.1.2 Generation tab页设置1.1.3 Prefixes tab页设置 1.2 Preview 视窗1.2.1 查找数据1.2.2 翻页1.2.3 增加、删除数据 1.3 Control List 视窗1.4 Data 视…

学生成绩分析项目

数据采集 导入必要的库 import pandas as pd import matplotlib.pyplot as plt import seaborn as sns加载数据集 df pd.read_csv(D:\\桌面\\数据\\student_marks.csv)显示数据框的前几行 # 显示数据框的形状 print("Shape of the dataframe:", df.shape)#显示…

pycharm最新版默认菜单栏等工具不见了

原因 pycharm2022.3新版本&#xff0c;默认使用新UI&#xff08;如下图&#xff09;&#xff0c;这让很多小伙伴来说不太适应&#xff0c;我还是习惯旧版本的界面。在网上搜了许多恢复旧版本界面的教程&#xff0c;说的解决方案都是由于“手贱把菜单栏给隐藏了”&#xff0c;这…

C语言“教父“

在中国&#xff0c;"C语言教父"一般指的是C语言网的创始人黄老师。他通过C语言网为广大学习者提供了丰富的学习资源和知识分享平台&#xff0c;包括大量的C语言教程、C语言题目以及相应的文档、资源等等&#xff0c;为C语言的推广、教学做出了巨大的贡献和推动作用

Go自带库的使用说明

Go 中的时间操作 Golang中与时间有关的操作&#xff0c;主要涉及到 time 包&#xff0c;核心数据结构是 time.Time&#xff0c;如下&#xff1a; type Time struct {wall uint64ext int64loc *Location }1、获取时间相关函数 1.1 获取当前时间 // 返回当前时间&#xff0c…

腾讯云TRTC服务实现小程序语音/视屏会议

腾讯云TRTC服务的入门 TRTC 是腾讯云主打全平台互通的多人音视频通话和低延时互动直播解决方案。TRTC服务有多种客户端的支持&#xff0c;对于IOS、Android、React native等都支持的比较好&#xff0c;我们主要在于 IOS、Android、Web三端进行处理&#xff0c;其中 TRTC Web S…

HarmonyOS学习路之开发篇—流转

流转概述 介绍 随着全场景多设备生活方式的不断深入&#xff0c;用户拥有的设备越来越多&#xff0c;每个设备都能在适合的场景下提供良好的体验&#xff0c;例如&#xff1a;手表可以提供及时的信息查看能力&#xff0c;电视可以带来沉浸的观影体验。但是&#xff0c;每个设备…

网络变压器常见封装

1、单口千兆&#xff08;Single Port&#xff09; 封装类型常见型号Dimension SMD24_1 PIN Pitch&#xff1a;1.00 Length&#xff1a;15.1 Width&#xff1a;7.1/10.0 Height&#xff1a;6.00 JWD&#xff1a; SG24002G Group-tek&#xff1a; HST-24015SR SMD24_2 PIN…

pyspark

连接命令&#xff1a; pyspark --master spark://node1:7077 一个application 大任务可以分解成 多个小任务 jobs&#xff0c; 一个job又可以分解成多个 stages 阶段&#xff0c; 一个stage又可以分解成 多个tasks&#xff08;可以认为是两个线程&#xff09; standalone Zo…

使用Gradio库创建交互式滑块组件

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

QT DAY3

完善文本编辑器 1.mainwindow.h文件 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include <QDebug> #include <QIcon> #include <QLabel> #include <QLineEdit> #include <QPushButton> #include <QMessageBo…

如何用Stable Diffusion模型生成个人专属创意名片?

目录 1 什么是二维码&#xff1f;2 什么是扩散模型&#xff1f;3 Stable Diffusion环境搭建4 开始制作创意名片结语 1 什么是二维码&#xff1f; 二维码是一种用于存储和传输信息的方便而广泛使用的图像编码技术。它是由黑色方块和白色空白区域组成的二维图形&#xff0c;可以…

光镊背后的电磁理论 | 涡旋电磁波在无线通信系统中的应用

2018年诺贝尔物理学奖于北京时间10月2日17点50分正式揭晓&#xff0c;发明光镊技术的美国物理学家&#xff0c;阿瑟阿什金&#xff08;Arthur Ashkin&#xff09;&#xff0c;以及开创了啁啾脉冲放大技术的唐娜斯特里克兰&#xff08;Donna Strickland&#xff09;、 热拉尔穆鲁…

持之以恒,安之有度 | 持安科技2周年!

新征程 新未来 持安的同学们已经一起走进 第三个年头啦 近日&#xff0c;持安 北京 上海 深圳 所有公司成员齐聚一堂 共 同 庆 祝 持安科技 成立2周年 持安一体化零信任平台 &#xff0c;引领应用层零信任落地新局面 2021年&#xff0c;何艺&#xff08;持安创始人兼CE…

经典轻量级神经网络(3)ShuffleNet V1及其在Fashion-MNIST数据集上的应用

经典轻量级神经网络(3)ShuffleNet V1及其在Fashion-MNIST数据集上的应用 1 ShuffleNet V1的简述 ShuffleNet 提出了 1x1分组卷积通道混洗 的策略&#xff0c;在保证准确率的同时大幅降低计算成本。 ShuffleNet 专为计算能力有限的设备&#xff08;如&#xff1a;10~150MFLOP…

开发uniapp苹果app,苹果签名证书的创建方法

在uniapp云打包界面&#xff0c;打包苹果app&#xff0c;需要私钥证书p12文件&#xff0c;还需要证书profile文件和证书密码。 这两个文件到底是从什么地方获取的呢&#xff1f;答案是这两个证书需要在苹果开发者中心生成&#xff0c;下面我们这篇教程&#xff0c;将教会大家如…

Java小白的学习之路——day12

目录 一、final 什么是final&#xff1f; 二、接口概述 什么是接口&#xff1f; 与抽象类的区别 常量接口 接口传参多态 四、内部类 什么是内部类&#xff1f; 成员内部类 静态内部类 局部内部类 一、final 什么是final&#xff1f; final从字面意思来看时最终的&a…

你真的了解JS垃圾回收机制吗?

目录 前言 堆栈内存管理 JS垃圾回收机制 标记清除&#xff08;Mark and Sweep&#xff09; 标记阶段 清除阶段 标记清除的特点 优点 缺点 引用计数&#xff08;Reference Counting&#xff09; 引用计数器的维护 引用计数的跟踪 垃圾回收的触发 回收对象 引用计…

视频转音频MP3格式怎么做?教你几种转换小妙招

当我们需要编辑视频中的声音&#xff0c;例如去除噪音、调整音量、加入配乐等&#xff0c;此时需要先将视频中的音频提取出来进行编辑&#xff0c;再将编辑后的音频重新与视频合并&#xff0c;以便达到一个最佳效果。那么怎么将视频转换成MP3格式的音频文件呢&#xff1f;教大家…

SpringBoot项目多模块打包部署Docker实战

前言 我们好多程序员都只关注功能代码的编写&#xff0c;在一些运维工作上则显得略有不足。这篇文章通过介绍最常见的Maven管理的Spring Boot项目多模块打包部署Docker来介绍一下项目部署过程中操作流程和几个需要注意的点。文章假设读者有前面提到的技术点的前置知识&#xf…