DeepSeek辅助编写的正确处理递归目录压缩的ZIP 工具C代码

/**
 * zipzstd.c - 支持多种压缩算法的 ZIP 工具
 * 支持参数: 通配符, 递归, 压缩级别, 压缩算法, 输出文件, 解压缩, 排除顶级目录
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zip.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fnmatch.h>
#include <errno.h>
#include <getopt.h>
#include <libgen.h>
#include <unistd.h>

#ifdef _WIN32
#include <direct.h>
#define PATH_SEPARATOR '\\'
#else
#define PATH_SEPARATOR '/'
#endif

// 压缩算法类型
typedef enum {
    COMPRESSION_ZSTD,
    COMPRESSION_ZLIB,
    COMPRESSION_BZIP2,
    COMPRESSION_STORE
} CompressionType;

// 配置结构体
typedef struct {
    int compress;               // 1=压缩, 0=解压
    int recursive;              // 递归子目录
    int compression_level;      // 压缩级别
    CompressionType comp_type;  // 压缩算法类型
    char *output_file;          // 输出文件名
    int no_top_dir;             // 不包括顶级目录
    char **input_patterns;      // 输入文件模式
    int pattern_count;          // 模式数量
} Config;

// 文件信息结构体
typedef struct {
    char *path;            // 文件完整路径
    char *archive_path;    // 在压缩包中的路径
    struct stat file_stat; // 文件状态信息
} FileInfo;

// 全局文件列表
FileInfo *file_list = NULL;
size_t file_count = 0;
size_t file_capacity = 0;

// 初始化配置
void init_config(Config *config) {
    config->compress = 1;
    config->recursive = 0;
    config->compression_level = 6;
    config->comp_type = COMPRESSION_ZSTD;
    config->output_file = NULL;
    config->no_top_dir = 0;
    config->input_patterns = NULL;
    config->pattern_count = 0;
}

// 释放配置资源
void free_config(Config *config) {
    if (config->output_file) free(config->output_file);
    for (int i = 0; i < config->pattern_count; i++) {
        free(config->input_patterns[i]);
    }
    free(config->input_patterns);
}

// 解析压缩算法参数
int parse_compression_type(const char *type_str, CompressionType *type) {
    if (strcmp(type_str, "zstd") == 0) {
        *type = COMPRESSION_ZSTD;
        return 0;
    } else if (strcmp(type_str, "zlib") == 0) {
        *type = COMPRESSION_ZLIB;
        return 0;
    } else if (strcmp(type_str, "bzip2") == 0) {
        *type = COMPRESSION_BZIP2;
        return 0;
    } else if (strcmp(type_str, "store") == 0) {
        *type = COMPRESSION_STORE;
        return 0;
    }
    return -1;
}

// 获取压缩方法常量
zip_uint16_t get_compression_method(CompressionType type) {
    switch (type) {
        case COMPRESSION_ZSTD:  return ZIP_CM_ZSTD;
        case COMPRESSION_ZLIB:  return ZIP_CM_DEFLATE;
        case COMPRESSION_BZIP2: return ZIP_CM_BZIP2;
        case COMPRESSION_STORE: return ZIP_CM_STORE;
        default:                return ZIP_CM_ZSTD;
    }
}

// 获取压缩算法名称
const char *get_compression_name(CompressionType type) {
    switch (type) {
        case COMPRESSION_ZSTD:  return "Zstandard";
        case COMPRESSION_ZLIB:  return "Zlib/Deflate";
        case COMPRESSION_BZIP2: return "BZIP2";
        case COMPRESSION_STORE: return "Store (无压缩)";
        default:                return "Unknown";
    }
}

// 打印帮助信息
void print_help(const char *program_name) {
    printf("用法: %s [选项] <文件/模式...>\n", program_name);
    printf("选项:\n");
    printf("  -d          解压缩模式\n");
    printf("  -r          递归处理子目录\n");
    printf("  -数字       压缩级别 (1-22, 默认:6)\n");
    printf("  -en算法     压缩算法: zstd, zlib, bzip2, store (默认:zstd)\n");
    printf("  -o文件      输出文件名\n");
    printf("  -notop      不包括顶级目录\n");
    printf("  -h          显示帮助信息\n");
    printf("\n示例:\n");
    printf("  %s *.txt -r -en zstd -9 -o archive.zip\n", program_name);
    printf("  %s -d archive.zip -o ./output\n", program_name);
    printf("  %s docs/ -r -en bzip2 -o docs.bzip2.zip\n", program_name);
}

// 检查文件是否匹配模式
int matches_pattern(const char *filename, Config *config) {
    if (config->pattern_count == 0) return 1;
    
    for (int i = 0; i < config->pattern_count; i++) {
        if (fnmatch(config->input_patterns[i], filename, 0) == 0) {
            return 1;
        }
    }
    return 0;
}

// 解析命令行参数
int parse_arguments(int argc, char *argv[], Config *config) {
    int opt;
    while ((opt = getopt(argc, argv, "drT:o:en:h")) != -1) {
        switch (opt) {
            case 'd':
                config->compress = 0;
                break;
            case 'r':
                config->recursive = 1;
                break;
            case 'T':
                // 保留线程参数但不使用(为了兼容性)
                break;
            case 'o':
                config->output_file = strdup(optarg);
                break;
            case 'e':
                // -en 参数处理
                if (optarg && strncmp(optarg, "n", 1) == 0) {
                    const char *algo = optarg + 1;
                    if (parse_compression_type(algo, &config->comp_type) != 0) {
                        fprintf(stderr, "未知的压缩算法: %s\n", algo);
                        return -1;
                    }
                }
                break;
            case 'h':
                print_help(argv[0]);
                exit(0);
            default:
                // 检查数字参数 (压缩级别)
                if (optopt == 0 && optarg && optarg[0] == '-') {
                    char *p = optarg + 1;
                    if (*p >= '0' && *p <= '9') {
                        config->compression_level = atoi(p);
                        if (config->compression_level < 1) config->compression_level = 1;
                        if (config->compression_level > 22) config->compression_level = 22;
                        break;
                    }
                }
                fprintf(stderr, "未知选项: -%c\n", opt);
                return -1;
        }
    }

    // 检查 -notop 参数
    for (int i = optind; i < argc; i++) {
        if (strcmp(argv[i], "-notop") == 0) {
            config->no_top_dir = 1;
            // 移除这个参数
            for (int j = i; j < argc - 1; j++) {
                argv[j] = argv[j + 1];
            }
            argc--;
            break;
        }
    }

    // 获取输入模式
    if (optind < argc) {
        config->pattern_count = argc - optind;
        config->input_patterns = malloc(config->pattern_count * sizeof(char *));
        for (int i = 0; i < config->pattern_count; i++) {
            config->input_patterns[i] = strdup(argv[optind + i]);
        }
    } else {
        fprintf(stderr, "错误: 需要指定输入文件或模式\n");
        return -1;
    }

    return 0;
}

// 在原有代码基础上添加以下结构和函数

// 文件信息结构体
typedef struct {
    char *path;            // 文件完整路径
    char *archive_path;    // 在压缩包中的路径(处理后)
    struct stat file_stat; // 文件状态信息
} FileEntry;

// 目录信息结构体  
typedef struct {
    char *path;            // 目录完整路径
    char *archive_path;    // 在压缩包中的路径(处理后)
    int is_empty;          // 是否为空目录
} DirEntry;



DirEntry *dir_list = NULL;
size_t dir_count = 0;
size_t dir_capacity = 0;

// 检查目录是否为空
int is_directory_empty(const char *path) {
    DIR *dir = opendir(path);
    if (!dir) return 0;

    struct dirent *entry;
    int item_count = 0;
    
    while ((entry = readdir(dir)) != NULL) {
        if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) {
            item_count++;
        }
    }
    closedir(dir);
    
    return (item_count == 0) ? 1 : 0;
}

// 递归收集所有文件和目录信息
void collect_all_items(const char *base_path, const char *current_path) {
    char full_path[1024];
    if (strlen(current_path) > 0) {
        snprintf(full_path, sizeof(full_path), "%s%c%s", base_path, PATH_SEPARATOR, current_path);
    } else {
        snprintf(full_path, sizeof(full_path), "%s", base_path);
    }

    DIR *dir = opendir(full_path);
    if (!dir) return;

    struct dirent *entry;
    while ((entry = readdir(dir)) != NULL) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
            continue;
        }

        char new_path[1024];
        if (strlen(current_path) > 0) {
            snprintf(new_path, sizeof(new_path), "%s%c%s", current_path, PATH_SEPARATOR, entry->d_name);
        } else {
            snprintf(new_path, sizeof(new_path), "%s", entry->d_name);
        }

        char absolute_path[1024];
        snprintf(absolute_path, sizeof(absolute_path), "%s%c%s", full_path, PATH_SEPARATOR, entry->d_name);

        struct stat st;
        if (stat(absolute_path, &st) == 0) {
            if (S_ISDIR(st.st_mode)) {
                // 添加到目录列表
                if (dir_count >= dir_capacity) {
                    dir_capacity = dir_capacity ? dir_capacity * 2 : 64;
                    dir_list = realloc(dir_list, dir_capacity * sizeof(DirEntry));
                }
                
                dir_list[dir_count].path = strdup(absolute_path);
                dir_list[dir_count].is_empty = is_directory_empty(absolute_path);
                dir_list[dir_count].archive_path = NULL; // 稍后处理
                dir_count++;

                // 递归处理子目录
                collect_all_items(base_path, new_path);
            } else {
                // 添加到文件列表
                if (file_count >= file_capacity) {
                    file_capacity = file_capacity ? file_capacity * 2 : 64;
                    file_list = realloc(file_list, file_capacity * sizeof(FileEntry));
                }
                
                file_list[file_count].path = strdup(absolute_path);
                file_list[file_count].archive_path = NULL; // 稍后处理
                file_list[file_count].file_stat = st;
                file_count++;
            }
        }
    }
    closedir(dir);
}

// 统一处理所有路径的前缀
void process_archive_paths(Config *config) {
    // 处理文件路径
    for (size_t i = 0; i < file_count; i++) {
        // 找到相对于base_path的路径
        const char *base_path = config->input_patterns[0];
        const char *full_path = file_list[i].path;

        // 安全检查:确保 base_path 不是完整路径的一部分
        if (strlen(full_path) == strlen(base_path) && strcmp(full_path, base_path) == 0) {
            // 单个文件匹配模式
            file_list[i].archive_path = strdup(basename((char *)full_path));
            continue;
        }
                
        // 检查是否以base_path开头
        if (strncmp(full_path, base_path, strlen(base_path)) == 0) {
            const char *relative_path = full_path + strlen(base_path);
            // 移除开头的路径分隔符
            if (*relative_path == PATH_SEPARATOR) {
                relative_path++;
            }
            
            if (config->no_top_dir) {
                // 移除顶级目录:找到第一个路径分隔符
                char *first_sep = strchr(relative_path, PATH_SEPARATOR);
                if (first_sep) {
                    file_list[i].archive_path = strdup(first_sep + 1);
                } else {
                    file_list[i].archive_path = strdup(relative_path);
                }
            } else {
                file_list[i].archive_path = strdup(relative_path);
            }
        } else {
            // 如果不以base_path开头,使用完整路径
            file_list[i].archive_path = strdup(full_path);
        }
    }

    // 处理目录路径(只处理空目录)
    for (size_t i = 0; i < dir_count; i++) {
        if (dir_list[i].is_empty) {
            const char *base_path = config->input_patterns[0];
            const char *full_path = dir_list[i].path;
            
            if (strncmp(full_path, base_path, strlen(base_path)) == 0) {
                const char *relative_path = full_path + strlen(base_path);
                if (*relative_path == PATH_SEPARATOR) {
                    relative_path++;
                }
                
                if (config->no_top_dir) {
                    char *first_sep = strchr(relative_path, PATH_SEPARATOR);
                    if (first_sep) {
                        char *archive_path = malloc(strlen(first_sep + 1) + 2);
                        strcpy(archive_path, first_sep + 1);
                        strcat(archive_path, "/");
                        dir_list[i].archive_path = archive_path;
                    } else {
                        char *archive_path = malloc(strlen(relative_path) + 2);
                        strcpy(archive_path, relative_path);
                        strcat(archive_path, "/");
                        dir_list[i].archive_path = archive_path;
                    }
                } else {
                    char *archive_path = malloc(strlen(relative_path) + 2);
                    strcpy(archive_path, relative_path);
                    strcat(archive_path, "/");
                    dir_list[i].archive_path = archive_path;
                }
            }
        }
    }
}

// 修改 compress_files 函数
int compress_files(Config *config) {
    // 清空之前的列表
    file_count = 0;
    dir_count = 0;

    // 收集所有文件和目录信息
    for (int i = 0; i < config->pattern_count; i++) {
        struct stat st;
        if (stat(config->input_patterns[i], &st) == 0) {
            if (S_ISDIR(st.st_mode)) {
                collect_all_items(config->input_patterns[i], "");
            } else {
                // 单个文件
                if (file_count >= file_capacity) {
                    file_capacity = file_capacity ? file_capacity * 2 : 64;
                    file_list = realloc(file_list, file_capacity * sizeof(FileEntry));
                }
                file_list[file_count].path = strdup(config->input_patterns[i]);
                file_list[file_count].archive_path = NULL;
                file_list[file_count].file_stat = st;
                file_count++;
            }
        }
    }

    // 统一处理所有路径
    process_archive_paths(config);

    printf("找到 %zu 个文件和 %zu 个空目录\n", file_count, dir_count);

// 输出文件列表
printf("\n文件列表:\n");
for (size_t i = 0; i < file_count; i++) {
    printf("  %s -> %s\n", file_list[i].path, file_list[i].archive_path);
}

// 输出空目录列表
printf("\n空目录列表:\n");
for (size_t i = 0; i < dir_count; i++) {
    if (dir_list[i].is_empty && dir_list[i].archive_path) {
        printf("  %s -> %s\n", dir_list[i].path, dir_list[i].archive_path);
    }
}
printf("\n");
    // 创建压缩包并添加文件和空目录...
    // [原有的压缩逻辑,分别处理文件和目录]
    // 创建压缩包
    int err;
    struct zip *archive = zip_open(config->output_file, ZIP_CREATE | ZIP_TRUNCATE, &err);
    if (!archive) {
        zip_error_t error;
        zip_error_init_with_code(&error, err);
        fprintf(stderr, "无法创建压缩包: %s\n", zip_error_strerror(&error));
        zip_error_fini(&error);
        return -1;
    }

    // 在 compress_files 函数中修改文件添加逻辑
for (size_t i = 0; i < file_count; i++) {
    // 检查是否是目录(通过路径结尾的 / 判断)
    int is_directory = (file_list[i].archive_path[strlen(file_list[i].archive_path) - 1] == '/');
    
    if (is_directory) {
        // 处理目录:创建目录条目
        zip_int64_t index = zip_dir_add(archive, file_list[i].archive_path, ZIP_FL_ENC_UTF_8);
        if (index >= 0) {
            printf("已添加目录: %s -> %s\n", file_list[i].path, file_list[i].archive_path);
        } else {
            fprintf(stderr, "添加目录失败: %s\n", file_list[i].archive_path);
        }
    } else {
        // 处理文件:正常添加文件内容
        FILE *fp = fopen(file_list[i].path, "rb");
        if (!fp) {
            fprintf(stderr, "无法打开文件: %s\n", file_list[i].path);
            continue;
        }

        fseek(fp, 0, SEEK_END);
        long size = ftell(fp);
        fseek(fp, 0, SEEK_SET);

        char *buffer = malloc(size);
        fread(buffer, 1, size, fp);
        fclose(fp);

        struct zip_source *source = zip_source_buffer(archive, buffer, size, 1);
        if (source) {
            zip_int64_t index = zip_file_add(archive, file_list[i].archive_path, source, ZIP_FL_ENC_UTF_8);
            if (index >= 0) {
                zip_uint16_t method = get_compression_method(config->comp_type);
                zip_set_file_compression(archive, index, method, config->compression_level);
                printf("已添加文件: %s -> %s\n", file_list[i].path, file_list[i].archive_path);
            } else {
                zip_source_free(source);
                fprintf(stderr, "添加文件失败: %s\n", file_list[i].archive_path);
            }
        } else {
            free(buffer);
            fprintf(stderr, "创建源失败: %s\n", file_list[i].archive_path);
        }
    }
}

// !!!在这里添加空目录的代码!!!
for (size_t i = 0; i < dir_count; i++) {
    if (dir_list[i].is_empty && dir_list[i].archive_path) {
        zip_int64_t index = zip_dir_add(archive, dir_list[i].archive_path, ZIP_FL_ENC_UTF_8);
        if (index >= 0) {
            printf("已添加空目录: %s -> %s\n", dir_list[i].path, dir_list[i].archive_path);
        } else {
            fprintf(stderr, "添加空目录失败: %s\n", dir_list[i].archive_path);
        }
    }
}

    // 关闭压缩包
    if (zip_close(archive) != 0) {
        fprintf(stderr, "关闭压缩包失败: %s\n", zip_strerror(archive));
        return -1;
    }

    printf("压缩完成: %s (%zu 个文件, %s)\n", 
           config->output_file, file_count, get_compression_name(config->comp_type));
    return 0;
}

// 解压文件(简化版)
int decompress_files(Config *config) {
    // 解压逻辑保持不变(省略以节省空间)
    printf("解压功能暂未实现\n");
    return -1;
}

int main(int argc, char *argv[]) {
    Config config;
    init_config(&config);

    if (parse_arguments(argc, argv, &config) != 0) {
        free_config(&config);
        return 1;
    }

    int result;
    if (config.compress) {
        result = compress_files(&config);
    } else {
        result = decompress_files(&config);
    }

    // 清理资源
    free_config(&config);
    if (file_list) {
        for (size_t i = 0; i < file_count; i++) {
            free(file_list[i].path);
            free(file_list[i].archive_path);
        }
        free(file_list);
    }

    return result;
}

压缩文件示例

./zipm zipm*.c -o c_zipm.zip
找到 8 个文件和 0 个空目录

文件列表:
  zipm2.c -> zipm2.c
  zipm3.c -> zipm3.c
  zipm4.c -> zipm4.c
  zipm5.c -> zipm5.c
  zipm6.c -> zipm6.c
  zipm7.c -> zipm7.c
  zipm8.c -> zipm8.c
  zipm.c -> zipm.c

空目录列表:

已添加文件: zipm2.c -> zipm2.c
已添加文件: zipm3.c -> zipm3.c
已添加文件: zipm4.c -> zipm4.c
已添加文件: zipm5.c -> zipm5.c
已添加文件: zipm6.c -> zipm6.c
已添加文件: zipm7.c -> zipm7.c
已添加文件: zipm8.c -> zipm8.c
已添加文件: zipm.c -> zipm.c
压缩完成: c_zipm.zip (8 个文件, Zstandard)

递归压缩目录示例

./zipm target -r -o top5.zip
找到 9 个文件和 7 个空目录

文件列表:
  target/[Content_Types].xml -> [Content_Types].xml
  target/docProps/app.xml -> docProps/app.xml
  target/docProps/core.xml -> docProps/core.xml
  target/xl/theme/theme1.xml -> xl/theme/theme1.xml
  target/xl/worksheets/sheet1.xml -> xl/worksheets/sheet1.xml
  target/xl/styles.xml -> xl/styles.xml
  target/xl/_rels/workbook.xml.rels -> xl/_rels/workbook.xml.rels
  target/xl/workbook.xml -> xl/workbook.xml
  target/_rels/.rels -> _rels/.rels

空目录列表:
  target/_rels/emptydir -> _rels/emptydir/

已添加文件: target/[Content_Types].xml -> [Content_Types].xml
已添加文件: target/docProps/app.xml -> docProps/app.xml
已添加文件: target/docProps/core.xml -> docProps/core.xml
已添加文件: target/xl/theme/theme1.xml -> xl/theme/theme1.xml
已添加文件: target/xl/worksheets/sheet1.xml -> xl/worksheets/sheet1.xml
已添加文件: target/xl/styles.xml -> xl/styles.xml
已添加文件: target/xl/_rels/workbook.xml.rels -> xl/_rels/workbook.xml.rels
已添加文件: target/xl/workbook.xml -> xl/workbook.xml
已添加文件: target/_rels/.rels -> _rels/.rels
已添加空目录: target/_rels/emptydir -> _rels/emptydir/
压缩完成: top5.zip (9 个文件, Zstandard)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值