Мне нужно было разархивировать файлы .zip в моем проекте Qt. Поэтому я установил libzip. Я написал функцию для распаковки ZIP-файла с учетом его местоположения и пути к целевому каталогу. Функция может корректно распаковывать простые текстовые файлы (например, .json, .txt и другие), но всегда повреждает двоичные файлы любого типа, такие как PNG или MP4. Вот функция, которую я написал:
void Converter::unzipFile(QString srcFilePath_, QString destinationDirectoryPath_) {
std::string srcFilePath = srcFilePath_.toStdString();
std::string destinationDirectoryPath = destinationDirectoryPath_.toStdString();
char bufferStr[100];
int error = 0;
int fileHandle;
struct zip *zipArchive = zip_open(srcFilePath.c_str(), 0, &error);
struct zip_file *zippedFile;
struct zip_stat zippedFileStats;
if (not QDir().exists(destinationDirectoryPath_)) {
QDir().mkpath(destinationDirectoryPath_);
}
if (zipArchive == NULL) {
zip_error_to_str(bufferStr, sizeof(bufferStr), error, errno);
std::cout << "[ERROR] Can not open the zip file: " << error << ":\n" << bufferStr;
}
for (int index = 0; index < zip_get_num_entries(zipArchive, 0); index++) {
if (zip_stat_index(zipArchive, index, 0, &zippedFileStats) == 0) {
int zipFileNameLength = strlen(zippedFileStats.name);
if (zippedFileStats.name[zipFileNameLength - 1] == '/') { // i.e. folder
QDir().mkpath(destinationDirectoryPath_ + "/" + zippedFileStats.name);
} else { // i.e. file
zippedFile = zip_fopen_index(zipArchive, index, 0);
if (zippedFile == NULL) {
qDebug() << "[ERROR] Can not open the file in zip archive.";
continue;
}
fileHandle = open((destinationDirectoryPath + "/" + zippedFileStats.name).c_str(), O_RDWR | O_TRUNC | O_CREAT, 0644);
if (fileHandle < 0) {
qDebug() << "[ERROR] Can not create the file (into which zipped data is to be extracted).";
continue;
}
int totalFileDataLength = 0;
while (totalFileDataLength != (long long) zippedFileStats.size) {
int fileDataLength = zip_fread(zippedFile, bufferStr, 100);
if (fileDataLength < 0) {
qDebug() << "[ERROR] Can not read the zipped file.";
exit(1);
}
write(fileHandle, bufferStr, fileDataLength);
totalFileDataLength += fileDataLength;
}
close(fileHandle);
zip_fclose(zippedFile);
}
} else {
qDebug() << "IDK what is here 🫥.";
}
}
if (zip_close(zipArchive) == -1) {
qDebug() << "[ERROR] Cannot close the zip file.";
}
}
ПРИМЕЧАНИЕ. Все, что связано с Qt, работает, я проверил все переменные и убедился, что они имеют правильные значения пути. Обычные текстовые файлы легко распаковываются. Эта проблема только с бинарными файлами.
ПРИМЕЧАНИЕ. Я перепробовал все соответствующие решения в Интернете, но ничего не помогло, поэтому этот вопрос не является дубликатом.
Что мне делать? Любая помощь будет оценена.
@FreudianSlip используемые функции (open()
, write()
) не имеют отдельных текстовых/двоичных режимов. write()
записывает только необработанные байты. Однако рассматриваемый код явно C++, поэтому вместо специфичных для платформы функций std::ofstream
/ios::binary
следует использовать стандартный класс open()
в режиме write ()
.
@ManbirJudge функции, которые вы используете для распаковки отдельных файлов и записи их на диск, работают только с необработанными байтами, и AFAICS вы правильно обрабатываете эти байты. Как ТОЧНО повреждаются бинарные файлы? Пожалуйста, будьте более конкретными. Сравните необработанные байты вашего вывода с необработанными байтами, полученными другими инструментами распаковки, в чем между ними разница?
@RemyLebeau Это отчет о различиях между PNG-файлом, разархивированным WinRAR, и этой функцией: Отчет о различиях.
Вместо этого:
int totalFileDataLength = 0;
while (totalFileDataLength != (long long) zippedFileStats.size) {
int fileDataLength = zip_fread(zippedFile, bufferStr, 100);
Этот:
zip_uint64_t totalFileDataLength = 0;
while (totalFileDataLength != zippedFileStats.size) {
zip_int64_t fileDataLength = zip_fread(zippedFile, bufferStr, 100);
Я не уверен, что это ваша ошибка, но без вышеуказанного изменения у вас будет проблема для любого файла размером более 2 ГБ. Это может привести к бесконечному циклу или усеченному файлу. Поскольку write
ожидает параметр size_t (который может быть 32-битным или 64-битным), вы можете безопасно выполнить приведение, если компилятор жалуется:
write(fileHandle, bufferStr, (size_t)fileDataLength);
Обновлять
Вам нужно выйти из цикла, когда zip_fread возвращает ноль. Обновим весь цикл:
while (true) {
zip_int64_t fileDataLength = zip_fread(zippedFile, bufferStr, 100);
if (fileDataLength == 0) {
break; // end of file
}
if (fileDataLength < 0) {
qDebug() << "[ERROR] Can not read the zipped file.";
exit(1);
}
write(fileHandle, bufferStr, (size_t)fileDataLength);
}
Окончательное обновление
Windows нужно, чтобы вы передали O_BINARY или (_O_BINARY) в открытый вызов.
fileHandle = open((destinationDirectoryPath + "/" + zippedFileStats.name).c_str(), O_RDWR | O_TRUNC | O_CREAT | O_BINARY, 0644);
Проверьте режим, в котором вы открываете файл для записи — есть ли какие-то особые флаги, которые могут вам понадобиться для включения двоичной записи? («wb»/ios::binary?)