张银峰的编程课堂

生成统计报告

改进选项参数

对于程序的选项参数,这次我们使用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;
}

glimix.com