生成统计报告
改进选项参数
对于程序的选项参数,这次我们使用struct把它们聚集起来,这与使用独立的全局变量进行存储本质上没什么区别,但从思想上讲,我们相当于对数据进行了抽象。
// ccleaner.c
// 程序运行时选项
typedef struct cc_options
{
char suffix_list[BUFSIZ];
int remove_empty_dir;
bool match_all;
} ccopts;
布尔变量match_all指示当前配置的后缀列表是否为"*.*",这样在delete_check()函数中检测文件是否可删除时,会减少一次字符串搜索操作。ccleaner_config接口现在可以设计为:
void ccleaner_config(cc_options *opts);
但是我们并没有这么做,这样的设计使得struct cc_options必须被定义在cclenaer.h中,而我们的意图是隐藏具体的实现,也就是说我们不希望用户(使用ccleaner模块的其它模块)知道配置选项的细节,因此接口原型维持不变。
/**
* 配置清理选项
* suffix:要清理的文件后缀名列表,后缀名以空格分隔,如 ".obj .log";
* rmdir:是否删除空目录,1(是),0(否)
*/
void ccleaner_config(const char *suffix, int rmdir)
{
if (suffix == NULL)
suffix = ".obj .log .tmp .exe";
strcpy_s(opts.suffix_list, BUFSIZ, suffix);
opts.remove_empty_dir = rmdir;
opts.match_all = strstr(suffix, "*.*") != NULL;
}
opts是struct cc_options的一个全局变量,match_all字段在配置期间被设置,这个小优化让检测函数有更有效。
/*
* 根据文件后缀名判断当前文件是否属于被删除文件类型之一
*/
static bool delete_check(const char *path)
{
// 程序已配置为删除所有文件
if (opts.match_all)
return true;
//...
}
增加统计信息
ccleaner为了向用户报告清理结果,引入cc_context结构体表达。
// ccleaner.c
// 程序运行时数据
typedef struct cc_context
{
int found_count; // 总共扫描的文件数(包含文件夹)
int match_count; // 匹配可删除的文件数(不包含文件夹)
int delete_count; // 实际删除的文件数
int delete_dir_count; // 实际删除的文件夹计数
int delete_failed_count; // 删除失败的文件数
} cctx, dirctx;
程序定义了该结构体的一个全局变量actx,用于追踪统计信息。
结构体的delete_count字段要说明一下,
- 针对在当前文件夹的清理过程中,它描述了包含文件夹在内的已删除文件计数。
- 针对全局统计信息的
actx.delete_count,则是不包含文件夹的已删除文件计数。
前者的目的是为了方便在本层扫描结束后,快速判断文件夹的可删除性;后者则从用户的角度出发,他更注重的是实际删除的文件数。
改进的clear_core()函数增加了对计数信息的使用,这里需要理解的是递归前后的文件计数的含义。
static bool clear_core(const char *dir, dirctx *ctx)
{
if (!dir) return false;
if (!ctx) return false;
// 重置本层目录上下文信息(计数清零)
reset_ctx(ctx);
// 为路径追加'\*.*'以遍历所有文件
char path[BUFSIZ];
sprintf_s(path, BUFSIZ, "%s\\*.*", dir);
// 开始查找文件
struct _finddata_t fd;
intptr_t handle = _findfirst(path, &fd);
if (handle != -1)
{
do
{
// 当前文件类型是目录
if (fd.attrib & _A_SUBDIR)
{
if (strcmp(fd.name, ".") != 0 && strcmp(fd.name, "..") != 0)
{
char subdir[BUFSIZ];
sprintf_s(subdir, BUFSIZ, "%s\\%s", dir, fd.name);
// 当前层文件计数加1
ctx->found_count += 1;
// 递归遍历子文件夹
dirctx subctx;
bool ok = clear_core(subdir, &subctx);
// 遍历完成 && 移除空文件夹 && 本次在子文件夹查找与删除的文件计数一致
if (ok && opts.remove_empty_dir && (subctx.found_count == subctx.delete_count))
{
// 在当前层删除子文件夹,成功后,文件删除计数加1
if (_rmdir(subdir) == 0)
{
ctx->delete_count += 1;
ctx->delete_dir_count += 1;
}
}
}
}
else
{
// 当前层文件计数加1
ctx->found_count += 1;
// 文件属于可被删除的类型
if (delete_check(fd.name))
{
// 匹配计数加1
ctx->match_count += 1;
char path[BUFSIZ];
sprintf_s(path, BUFSIZ, "%s\\%s", dir, fd.name);
// 在当前层删除文件,成功后,文件删除计数加1
if (delete_file(path)) ctx->delete_count += 1;
else ctx->delete_failed_count += 1;
}
}
} while (_findnext(handle, &fd) == 0);
// 本次查找结束
_findclose(handle);
}
// 本次统计信息累加到全局计数中
actx.found_count += ctx->found_count;
actx.match_count += ctx->match_count;
actx.delete_count += ctx->delete_count - ctx->delete_dir_count; // 文件夹不计算在内
actx.delete_failed_count += ctx->delete_failed_count;
return handle != -1;
}
报告清理结果
全局变量actx在清理过程中已经完成了数据统计工作,现在所需要的只是将这些信息传递出去,这是通过增加接口函数ccleaner_result实现的。
/**
* 获取清理结果
* fcnt:总共扫描的文件计数
* mcnt:匹配的可删除文件计数
* dcnt:实际删除的文件计数
* ncnt:删除失败的文件计数
*/
void ccleaner_result(int *fcnt, int *mcnt, int *dcnt, int *ncnt)
{
if (fcnt) *fcnt = actx.found_count;
if (mcnt) *mcnt = actx.match_count;
if (dcnt) *dcnt = actx.delete_count;
if (ncnt) *ncnt = actx.delete_failed_count;
}
与ccleaner_config()函数的设计思路一致,我们并不想暴露一些实现细节,所以没有设计接受cc_context*版的接口。
void ccleaner_result(cctx *pctx)
{
//...
}
主程序
主程序的思路不变,通过接口函数将程序的逻辑组装起来,最后输出一份报告给用户,并等待用户按下任意键后退出程序。
#include <stdio.h>
#include "ccleaner.h"
int main()
{
ccleaner_config("*.*", 1);
ccleaner_run("debug");
int mcnt, dcnt, ncnt;
ccleaner_result(NULL, &mcnt, &dcnt, &ncnt);
printf("\n");
printf(" C.C.L.E.A.N.E.R \n");
printf("+--------------------------------------+\n");
printf("| %-12s%-12s%-12s |\n", "Found", "Delete", "Failed");
printf("| %-12d%-12d%-12d |\n", mcnt, dcnt, ncnt);
printf("+--------------------------------------+\n");
printf("\n");
return 0;
}
