簡單說說自己遇到的坑:
-
分清楚三個元件:zlib、minizip和libzip。zlib是底層和最基礎的C庫,用於使用Deflate演算法壓縮和解壓縮檔案流或者單個檔案,但是如果要壓縮資料夾就很麻煩,主要是不知道如何歸檔,在zip內部形成對應的目錄。這時就需要用更高階別的庫,也就是minizip或libzip。
-
minizip、libzip隨著版本迭代介面一直變化,我連續使用了通義千問、文心一言、gemini三個AI,基本上沒給出能使用的程式碼,主要是函式介面總是不對,或者引數多了或者少了。像這種情況就不要再參考AI給出的答案了,趕緊翻官方文件才是正經。
-
minizip和libzip都是基於zlib實現的,都嘗試使用過,感覺還是libzip的介面設計更清晰一點,官方文件說明也還不錯。
-
壓縮資料夾的功能需要藉助於操作檔案系統的庫來組織zip內部的歸檔目錄,我這裡使用的是C++17的std::filesystem。
具體程式碼實現如下:
#include <zip.h>
#include <filesystem>
#include <fstream>
#include <iostream>
using namespace std;
void CompressFile2Zip(std::filesystem::path unZipFilePath,
const char* relativeName, zip_t* zipArchive) {
std::ifstream file(unZipFilePath, std::ios::binary);
file.seekg(0, std::ios::end);
size_t bufferSize = file.tellg();
char* bufferData = (char*)malloc(bufferSize);
file.seekg(0, std::ios::beg);
file.read(bufferData, bufferSize);
//第四個引數如果非0,會自動託管申請的資源,直到zip_close之前自動銷燬。
zip_source_t* source =
zip_source_buffer(zipArchive, bufferData, bufferSize, 1);
if (source) {
if (zip_file_add(zipArchive, relativeName, source, ZIP_FL_OVERWRITE) < 0) {
std::cerr << "Failed to add file " << unZipFilePath
<< " to zip: " << zip_strerror(zipArchive) << std::endl;
zip_source_free(source);
}
} else {
std::cerr << "Failed to create zip source for " << unZipFilePath << ": "
<< zip_strerror(zipArchive) << std::endl;
}
}
void CompressFile(std::filesystem::path unZipFilePath,
std::filesystem::path zipFilePath) {
int errorCode = 0;
zip_t* zipArchive = zip_open(zipFilePath.generic_u8string().c_str(),
ZIP_CREATE | ZIP_TRUNCATE, &errorCode);
if (zipArchive) {
CompressFile2Zip(unZipFilePath, unZipFilePath.filename().string().c_str(),
zipArchive);
errorCode = zip_close(zipArchive);
if (errorCode != 0) {
zip_error_t zipError;
zip_error_init_with_code(&zipError, errorCode);
std::cerr << zip_error_strerror(&zipError) << std::endl;
zip_error_fini(&zipError);
}
} else {
zip_error_t zipError;
zip_error_init_with_code(&zipError, errorCode);
std::cerr << "Failed to open output file " << zipFilePath << ": "
<< zip_error_strerror(&zipError) << std::endl;
zip_error_fini(&zipError);
}
}
void CompressDirectory2Zip(std::filesystem::path rootDirectoryPath,
std::filesystem::path directoryPath,
zip_t* zipArchive) {
if (rootDirectoryPath != directoryPath) {
if (zip_dir_add(zipArchive,
std::filesystem::relative(directoryPath, rootDirectoryPath)
.generic_u8string()
.c_str(),
ZIP_FL_ENC_UTF_8) < 0) {
std::cerr << "Failed to add directory " << directoryPath
<< " to zip: " << zip_strerror(zipArchive) << std::endl;
}
}
for (const auto& entry : std::filesystem::directory_iterator(directoryPath)) {
if (entry.is_regular_file()) {
CompressFile2Zip(
entry.path().generic_u8string(),
std::filesystem::relative(entry.path(), rootDirectoryPath)
.generic_u8string()
.c_str(),
zipArchive);
} else if (entry.is_directory()) {
CompressDirectory2Zip(rootDirectoryPath, entry.path().generic_u8string(),
zipArchive);
}
}
}
void CompressDirectory(std::filesystem::path directoryPath,
std::filesystem::path zipFilePath) {
int errorCode = 0;
zip_t* zipArchive = zip_open(zipFilePath.generic_u8string().c_str(),
ZIP_CREATE | ZIP_TRUNCATE, &errorCode);
if (zipArchive) {
CompressDirectory2Zip(directoryPath, directoryPath, zipArchive);
errorCode = zip_close(zipArchive);
if (errorCode != 0) {
zip_error_t zipError;
zip_error_init_with_code(&zipError, errorCode);
std::cerr << zip_error_strerror(&zipError) << std::endl;
zip_error_fini(&zipError);
}
} else {
zip_error_t zipError;
zip_error_init_with_code(&zipError, errorCode);
std::cerr << "Failed to open output file " << zipFilePath << ": "
<< zip_error_strerror(&zipError) << std::endl;
zip_error_fini(&zipError);
}
}
int main() {
//壓縮檔案
//CompressFile("C:/Data/Builder/Demo/view.tmp", "C:/Data/Builder/Demo/view.zip");
//壓縮資料夾
CompressDirectory("C:/Data/Builder/Demo", "C:/Data/Builder/Demo.zip");
return 0;
}
關於使用的libzip,有以下幾點值得注意:
- libzip壓縮的zip內部的檔名預設採用UTF-8編碼。
- libzip要求使用正斜槓 ('/') 作為目錄分隔符。
- libzip操作不同的zip執行緒安全,操作同一個zip執行緒不安全。
- zip_source_buffer這個函式的介面的第四個引數如果非0,會自動託管申請的資源。官方文件提到需要保證傳入zip_source_buffer的資料資源需要保證跟zip_source_t一樣的宣告週期,但是筆者經過測試,正確的行為應該是傳入zip_source_buffer的資料資源需要保證呼叫zip_close之前都有效,否則就有問題。