diff --git a/coding/coding_tests/zip_creator_test.cpp b/coding/coding_tests/zip_creator_test.cpp index 5ac0abcaf9..51c9258682 100644 --- a/coding/coding_tests/zip_creator_test.cpp +++ b/coding/coding_tests/zip_creator_test.cpp @@ -6,15 +6,14 @@ #include "coding/file_writer.hpp" #include "coding/constants.hpp" +#include "base/scope_guard.hpp" + #include #include -using namespace std; - namespace { - -void CreateAndTestZip(string const & filePath, string const & zipPath) +void CreateAndTestZip(std::string const & filePath, std::string const & zipPath) { TEST(CreateZipFromPathDeflatedAndDefaultCompression(filePath, zipPath), ()); @@ -22,7 +21,7 @@ void CreateAndTestZip(string const & filePath, string const & zipPath) ZipFileReader::FilesList(zipPath, files); TEST_EQUAL(files[0].second, FileReader(filePath).Size(), ()); - string const unzippedFile = "unzipped.tmp"; + std::string const unzippedFile = "unzipped.tmp"; ZipFileReader::UnzipFile(zipPath, files[0].first, unzippedFile); TEST(base::IsEqualFiles(filePath, unzippedFile), ()); @@ -31,15 +30,40 @@ void CreateAndTestZip(string const & filePath, string const & zipPath) TEST(base::DeleteFileX(unzippedFile), ()); } +void CreateAndTestZip(std::vector const & files, std::string const & zipPath, + CompressionLevel compression) +{ + TEST(CreateZipFromFiles(files, zipPath, compression), ()); + + ZipFileReader::FileList fileList; + ZipFileReader::FilesList(zipPath, fileList); + std::string const unzippedFile = "unzipped.tmp"; + for (size_t i = 0; i < files.size(); ++i) + { + TEST_EQUAL(fileList[i].second, FileReader(files[i]).Size(), ()); + + ZipFileReader::UnzipFile(zipPath, fileList[i].first, unzippedFile); + + TEST(base::IsEqualFiles(files[i], unzippedFile), ()); + TEST(base::DeleteFileX(unzippedFile), ()); + } + TEST(base::DeleteFileX(zipPath), ()); +} + +std::vector GetCompressionLevels() +{ + return {CompressionLevel::DefaultCompression, CompressionLevel::BestCompression, + CompressionLevel::BestSpeed, CompressionLevel::NoCompression}; +} } UNIT_TEST(CreateZip_BigFile) { - string const name = "testfileforzip.txt"; + std::string const name = "testfileforzip.txt"; { FileWriter f(name); - string s(READ_FILE_BUFFER_SIZE + 1, '1'); + std::string s(READ_FILE_BUFFER_SIZE + 1, '1'); f.Write(s.c_str(), s.size()); } @@ -48,7 +72,7 @@ UNIT_TEST(CreateZip_BigFile) UNIT_TEST(CreateZip_Smoke) { - string const name = "testfileforzip.txt"; + std::string const name = "testfileforzip.txt"; { FileWriter f(name); @@ -57,3 +81,40 @@ UNIT_TEST(CreateZip_Smoke) CreateAndTestZip(name, "testzip.zip"); } + +UNIT_TEST(CreateZip_MultipleFiles) +{ + std::vector const fileData{"testf1", "testfile2", "testfile3_longname.txt.xml.csv"}; + SCOPE_GUARD(deleteFileGuard, [&fileData]() { + for (auto const & file : fileData) + TEST(base::DeleteFileX(file), ()); + }); + + for (auto const & name : fileData) + { + FileWriter f(name); + f.Write(name.c_str(), name.size()); + } + + for (auto compression : GetCompressionLevels()) + CreateAndTestZip(fileData, "testzip.zip", compression); + + deleteFileGuard.release(); +} + +UNIT_TEST(CreateZip_MultipleFilesSingleEmpty) +{ + std::vector const fileData{"singleEmptyfile.txt"}; + SCOPE_GUARD(deleteFileGuard, [&fileData]() { base::DeleteFileX(fileData[0]); }); + + { + FileWriter f(fileData[0]); + } + + for (auto compression : GetCompressionLevels()) + { + CreateAndTestZip(fileData, "testzip.zip", compression); + } + + deleteFileGuard.release(); +} diff --git a/coding/zip_creator.cpp b/coding/zip_creator.cpp index 2123798e37..6cc049762f 100644 --- a/coding/zip_creator.cpp +++ b/coding/zip_creator.cpp @@ -2,31 +2,30 @@ #include "base/string_utils.hpp" +#include "coding/constants.hpp" #include "coding/internal/file_data.hpp" #include "coding/reader.hpp" -#include "coding/constants.hpp" #include "base/file_name_utils.hpp" #include "base/logging.hpp" #include "base/scope_guard.hpp" -#include -#include -#include - #include "3party/minizip/zip.h" -using namespace std; +#include +#include +#include +#include +#include namespace { - class ZipHandle { zipFile m_zipFileHandle; public: - explicit ZipHandle(string const & filePath) + explicit ZipHandle(std::string const & filePath) { m_zipFileHandle = zipOpen(filePath.c_str(), 0); } @@ -44,8 +43,8 @@ void CreateTMZip(tm_zip & res) { time_t rawtime; struct tm * timeinfo; - time ( &rawtime ); - timeinfo = localtime ( &rawtime ); + time(&rawtime); + timeinfo = localtime(&rawtime); res.tm_sec = timeinfo->tm_sec; res.tm_min = timeinfo->tm_min; res.tm_hour = timeinfo->tm_hour; @@ -54,12 +53,26 @@ void CreateTMZip(tm_zip & res) res.tm_year = timeinfo->tm_year; } -} - -bool CreateZipFromPathDeflatedAndDefaultCompression(string const & filePath, string const & zipFilePath) +int GetCompressionLevel(CompressionLevel compression) { - // 2. Open zip file for writing. - SCOPE_GUARD(outFileGuard, bind(&base::DeleteFileX, cref(zipFilePath))); + switch (compression) + { + case CompressionLevel::NoCompression: return Z_NO_COMPRESSION; + case CompressionLevel::BestSpeed: return Z_BEST_SPEED; + case CompressionLevel::BestCompression: return Z_BEST_COMPRESSION; + case CompressionLevel::DefaultCompression: return Z_DEFAULT_COMPRESSION; + case CompressionLevel::Count: UNREACHABLE(); + } + UNREACHABLE(); +} +} // namespace + +bool CreateZipFromPathDeflatedAndDefaultCompression(std::string const & filePath, + std::string const & zipFilePath) +{ + // Open zip file for writing. + SCOPE_GUARD(outFileGuard, [&zipFilePath]() { base::DeleteFileX(zipFilePath); }); + ZipHandle zip(zipFilePath); if (!zip.Handle()) return false; @@ -67,13 +80,13 @@ bool CreateZipFromPathDeflatedAndDefaultCompression(string const & filePath, str zip_fileinfo zipInfo = {}; CreateTMZip(zipInfo.tmz_date); - string fileName = filePath; + std::string fileName = filePath; base::GetNameFromFullPath(fileName); if (!strings::IsASCIIString(fileName)) fileName = "MapsMe.kml"; - if (zipOpenNewFileInZip(zip.Handle(), fileName.c_str(), &zipInfo, - NULL, 0, NULL, 0, "ZIP from MapsWithMe", Z_DEFLATED, Z_DEFAULT_COMPRESSION) < 0) + if (zipOpenNewFileInZip(zip.Handle(), fileName.c_str(), &zipInfo, nullptr, 0, nullptr, 0, + "ZIP from MapsWithMe", Z_DEFLATED, Z_DEFAULT_COMPRESSION) < 0) { return false; } @@ -88,7 +101,8 @@ bool CreateZipFromPathDeflatedAndDefaultCompression(string const & filePath, str char buffer[ZIP_FILE_BUFFER_SIZE]; while (currSize < fileSize) { - unsigned int const toRead = min(ZIP_FILE_BUFFER_SIZE, static_cast(fileSize - currSize)); + unsigned int const toRead = + std::min(ZIP_FILE_BUFFER_SIZE, static_cast(fileSize - currSize)); file.Read(currSize, buffer, toRead); if (ZIP_OK != zipWriteInFileInZip(zip.Handle(), buffer, toRead)) @@ -103,7 +117,56 @@ bool CreateZipFromPathDeflatedAndDefaultCompression(string const & filePath, str return false; } - // Success. + outFileGuard.release(); + return true; +} + +bool CreateZipFromFiles(std::vector const & files, std::string const & zipFilePath, + CompressionLevel compression) +{ + SCOPE_GUARD(outFileGuard, [&zipFilePath]() { base::DeleteFileX(zipFilePath); }); + + ZipHandle zip(zipFilePath); + if (!zip.Handle()) + return false; + + int const compressionLevel = GetCompressionLevel(compression); + zip_fileinfo const fileInfo = {}; + + try + { + for (auto const & filePath : files) + { + if (zipOpenNewFileInZip(zip.Handle(), filePath.c_str(), &fileInfo, nullptr, 0, nullptr, 0, "", + Z_DEFLATED, compressionLevel) != Z_OK) + { + return false; + } + + base::FileData file(filePath, base::FileData::OP_READ); + uint64_t const fileSize = file.Size(); + uint64_t writtenSize = 0; + std::array bufferForZip; + + while (writtenSize < fileSize) + { + unsigned int const filePartSize = + std::min(ZIP_FILE_BUFFER_SIZE, static_cast(fileSize - writtenSize)); + file.Read(writtenSize, bufferForZip.data(), filePartSize); + + if (zipWriteInFileInZip(zip.Handle(), bufferForZip.data(), filePartSize) != ZIP_OK) + return false; + + writtenSize += filePartSize; + } + } + } + catch (std::exception const & e) + { + LOG(LERROR, ("Error adding files to the archive", zipFilePath, e.what())); + return false; + } + outFileGuard.release(); return true; } diff --git a/coding/zip_creator.hpp b/coding/zip_creator.hpp index 5753486937..6d76428fa3 100644 --- a/coding/zip_creator.hpp +++ b/coding/zip_creator.hpp @@ -1,6 +1,19 @@ #pragma once #include +#include + +enum class CompressionLevel +{ + NoCompression = 0, + BestSpeed, + BestCompression, + DefaultCompression, + Count +}; bool CreateZipFromPathDeflatedAndDefaultCompression(std::string const & filePath, std::string const & zipFilePath); + +bool CreateZipFromFiles(std::vector const & files, std::string const & zipFilePath, + CompressionLevel compression = CompressionLevel::DefaultCompression);