Zipper: now computeing crc32 on the fly during zip creation

This commit is contained in:
emeric
2020-08-30 15:13:46 +02:00
parent cd25f6ee1c
commit c9aed37fc2
6 changed files with 137 additions and 150 deletions
+4 -6
View File
@@ -26,17 +26,16 @@
#include <array> #include <array>
#include <fstream> #include <fstream>
#include <boost/crc.hpp> // for boost::crc_32_type
#include <boost/tokenizer.hpp> #include <boost/tokenizer.hpp>
#include "utils/Crc32Calculator.hpp"
#include "utils/Exception.hpp" #include "utils/Exception.hpp"
#include "utils/Logger.hpp" #include "utils/Logger.hpp"
std::uint32_t std::uint32_t
computeCrc32(const std::filesystem::path& p) computeCrc32(const std::filesystem::path& p)
{ {
using crc_type = boost::crc_32_type; Utils::Crc32Calculator crc32;
crc_type result;
std::ifstream ifs {p.string().c_str(), std::ios_base::binary}; std::ifstream ifs {p.string().c_str(), std::ios_base::binary};
if (ifs) if (ifs)
@@ -46,7 +45,7 @@ computeCrc32(const std::filesystem::path& p)
std::array<char,1024> buffer; std::array<char,1024> buffer;
ifs.read( buffer.data(), buffer.size() ); ifs.read( buffer.data(), buffer.size() );
result.process_bytes( buffer.data(), ifs.gcount() ); crc32.processBytes( reinterpret_cast<const std::byte*>(buffer.data()), ifs.gcount() );
} }
while (ifs); while (ifs);
} }
@@ -56,8 +55,7 @@ computeCrc32(const std::filesystem::path& p)
throw LmsException("Failed to open file '" + p.string() + "'" ); throw LmsException("Failed to open file '" + p.string() + "'" );
} }
return crc32.getResult();
return result.checksum();
} }
bool bool
+67 -123
View File
@@ -23,7 +23,6 @@
#include <fstream> #include <fstream>
#include "utils/Path.hpp" #include "utils/Path.hpp"
#include "utils/Logger.hpp"
// Done using specs from https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT // Done using specs from https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
@@ -49,6 +48,8 @@ namespace Zip
NoCompression = 0, NoCompression = 0,
}; };
static constexpr std::size_t UnknownCrc32 {0};
static constexpr std::size_t UnknownFileSize {0};
protected: protected:
@@ -89,82 +90,34 @@ namespace Zip
using ZipHeader::ZipHeader; using ZipHeader::ZipHeader;
// Setters // Setters
void setSignature(); void setSignature() { write32(0, 0x04034b50); }
void setVersionNeededToExtract(unsigned major, unsigned minor); void setVersionNeededToExtract(unsigned major, unsigned minor) { assert(minor < 10); write16(4, major*10 + minor); }
void setGeneralPurposeFlags(std::uint16_t flags); void setGeneralPurposeFlags(std::uint16_t flags) { write16(6, flags); }
void setCompressionMethod(CompressionMethod compressionMethod); void setCompressionMethod(CompressionMethod compressionMethod) { write16(8, compressionMethod); }
void setLastModifiedDateTime(); void setLastModifiedDateTime()
void setCrc32UncompressedData(std::uint32_t crc); { // TODO
void setCompressedSize(std::size_t size); write16(10, 0); // time
void setUncompressedSize(std::size_t size); write16(12, 0); // date
void setFileNameLength(std::size_t size); }
void setExtraFieldLength(std::size_t size); void setCrc32UncompressedData(std::uint32_t crc) { write32(14, crc); }
void setCompressedSize(std::size_t size) { write32(18, size); }
void setUncompressedSize(std::size_t size) { write32(22, size); }
void setFileNameLength(std::size_t size) { write16(26, size); }
void setExtraFieldLength(std::size_t size) { write16(28, size); }
static constexpr std::size_t getHeaderSize() { return 30; } static constexpr std::size_t getHeaderSize() { return 30; }
}; };
void class DataDescriptor : public ZipHeader
LocalFileHeader::setSignature()
{ {
write32(0, 0x04034b50); public:
} using ZipHeader::ZipHeader;
void
LocalFileHeader::setVersionNeededToExtract(unsigned major, unsigned minor)
{
assert(minor < 10);
write16(4, major*10 + minor);
}
void
LocalFileHeader::setGeneralPurposeFlags(std::uint16_t flags)
{
write16(6, flags);
}
void
LocalFileHeader::setCompressionMethod(CompressionMethod compressionMethod)
{
write16(8, compressionMethod);
}
void
LocalFileHeader::setLastModifiedDateTime()
{
// TODO
write16(10, 0); // time
write16(12, 0); // date
}
void
LocalFileHeader::setCrc32UncompressedData(std::uint32_t crc)
{
write32(14, crc);
}
void
LocalFileHeader::setCompressedSize(std::size_t size)
{
write32(18, size);
}
void
LocalFileHeader::setUncompressedSize(std::size_t size)
{
write32(22, size);
}
void
LocalFileHeader::setFileNameLength(std::size_t size)
{
write16(26, size);
}
void
LocalFileHeader::setExtraFieldLength(std::size_t size)
{
write16(28, size);
}
void setSignature() { write32(0, 0x08074b50 ); }
void setCrc32UncompressedData(std::uint32_t crc32) { write32(4, crc32); }
void setCompressedSize(std::size_t size) { write32(8, size); }
void setUncompressedSize(std::size_t size) { write32(12, size); }
static constexpr std::size_t getHeaderSize() { return 16; }
};
class CentralDirectoryHeader : public ZipHeader class CentralDirectoryHeader : public ZipHeader
{ {
@@ -210,8 +163,7 @@ namespace Zip
static constexpr std::size_t getHeaderSize() { return 22; } static constexpr std::size_t getHeaderSize() { return 22; }
}; };
Zipper::Zipper(const std::map<std::string, std::filesystem::path>& files, CompressionMethod compMethod) Zipper::Zipper(const std::map<std::string, std::filesystem::path>& files)
: _compMethod {compMethod}
{ {
for (const auto& [filename, filePath] : files) for (const auto& [filename, filePath] : files)
{ {
@@ -221,13 +173,7 @@ namespace Zip
std::error_code ec; std::error_code ec;
fileContext.fileSize = std::filesystem::file_size(filePath, ec); fileContext.fileSize = std::filesystem::file_size(filePath, ec);
if (ec) if (ec)
{ throw ZipperException {"Cannot get file size for '" + filePath.string() + "': " + ec.message()};
LMS_LOG(UTILS, INFO) << "Cannot get file size for '" << filePath.string() << "': " << ec.message();
continue;
}
fileContext.fileCrc32 = computeCrc32(filePath);
LMS_LOG(UTILS, DEBUG) << "Processing '" << filePath.string() << "': File size = " << fileContext.fileSize;
_files[filename] = std::move(fileContext); _files[filename] = std::move(fileContext);
} }
@@ -247,9 +193,6 @@ namespace Zip
{ {
std::size_t nbWrittenBytes {}; std::size_t nbWrittenBytes {};
LMS_LOG(UTILS, DEBUG) << "Global offset = " << _currentZipOffset;
LMS_LOG(UTILS, DEBUG) << "Buffer ptr = " << buffer << ", remaining size = " << bufferSize;
switch (_writeState) switch (_writeState)
{ {
case WriteState::LocalFileHeader: case WriteState::LocalFileHeader:
@@ -264,6 +207,10 @@ namespace Zip
nbWrittenBytes = writeFileData(buffer, bufferSize); nbWrittenBytes = writeFileData(buffer, bufferSize);
break; break;
case WriteState::DataDescriptor:
nbWrittenBytes = writeDataDescriptor(buffer, bufferSize);
break;
case WriteState::CentralDirectoryHeader: case WriteState::CentralDirectoryHeader:
nbWrittenBytes = writeCentralDirectoryHeader(buffer, bufferSize); nbWrittenBytes = writeCentralDirectoryHeader(buffer, bufferSize);
break; break;
@@ -280,8 +227,6 @@ namespace Zip
break; break;
} }
LMS_LOG(UI, DEBUG) << "nbWrittenBytes = " << nbWrittenBytes;
buffer += nbWrittenBytes; buffer += nbWrittenBytes;
bufferSize -= nbWrittenBytes; bufferSize -= nbWrittenBytes;
_currentZipOffset += nbWrittenBytes; _currentZipOffset += nbWrittenBytes;
@@ -311,21 +256,15 @@ namespace Zip
return 0; return 0;
} }
LMS_LOG(UTILS, INFO) << "writeLocalFileHeader. crc = " << _currentFile->second.fileCrc32;
LocalFileHeader header {buffer, bufferSize}; LocalFileHeader header {buffer, bufferSize};
header.setSignature(); header.setSignature();
header.setVersionNeededToExtract(1, 0); header.setVersionNeededToExtract(1, 0);
header.setGeneralPurposeFlags(ZipHeader::GeneralPurposeFlag::LanguageEncoding); header.setGeneralPurposeFlags(ZipHeader::GeneralPurposeFlag::LanguageEncoding | ZipHeader::GeneralPurposeFlag::UseDataDescriptor);
header.setCrc32UncompressedData(_currentFile->second.fileCrc32); header.setCompressionMethod(ZipHeader::CompressionMethod::NoCompression);
switch (_compMethod) header.setCrc32UncompressedData(ZipHeader::UnknownCrc32);
{ header.setCompressedSize(ZipHeader::UnknownFileSize);
case CompressionMethod::NoCompression: header.setUncompressedSize(ZipHeader::UnknownFileSize);
header.setCompressionMethod(ZipHeader::CompressionMethod::NoCompression);
header.setCompressedSize(_currentFile->second.fileSize);
header.setUncompressedSize(_currentFile->second.fileSize);
break;
}
header.setLastModifiedDateTime(); // getLastWriteTime(*_currentFile)); header.setLastModifiedDateTime(); // getLastWriteTime(*_currentFile));
header.setFileNameLength(_currentFile->first.size()); header.setFileNameLength(_currentFile->first.size());
header.setExtraFieldLength(0); header.setExtraFieldLength(0);
@@ -339,6 +278,8 @@ namespace Zip
std::size_t std::size_t
Zipper::writeLocalFileHeaderFileName(std::byte* buffer, std::size_t bufferSize) Zipper::writeLocalFileHeaderFileName(std::byte* buffer, std::size_t bufferSize)
{ {
assert(_currentFile != std::end(_files));
const std::string& fileName {_currentFile->first}; const std::string& fileName {_currentFile->first};
assert(_currentOffset <= fileName.size()); assert(_currentOffset <= fileName.size());
@@ -349,10 +290,7 @@ namespace Zip
return 0; return 0;
} }
LMS_LOG(UTILS, INFO) << "writeLocalFileHeaderFileName";
const std::size_t nbBytesToCopy {std::min<std::size_t>(fileName.size() - _currentOffset, bufferSize)}; const std::size_t nbBytesToCopy {std::min<std::size_t>(fileName.size() - _currentOffset, bufferSize)};
LMS_LOG(UTILS, INFO) << "\tnbBytesToCopy = " << nbBytesToCopy;
std::copy(std::next(std::begin(fileName), _currentOffset), std::next(std::begin(fileName), nbBytesToCopy), reinterpret_cast<unsigned char*>(buffer)); std::copy(std::next(std::begin(fileName), _currentOffset), std::next(std::begin(fileName), nbBytesToCopy), reinterpret_cast<unsigned char*>(buffer));
@@ -363,12 +301,12 @@ namespace Zip
std::size_t std::size_t
Zipper::writeFileData(std::byte* buffer, std::size_t bufferSize) Zipper::writeFileData(std::byte* buffer, std::size_t bufferSize)
{ {
assert(_currentFile != std::end(_files));
if (_currentOffset == _currentFile->second.fileSize) if (_currentOffset == _currentFile->second.fileSize)
{ {
_currentOffset = 0; _currentOffset = 0;
++_currentFile; _writeState = WriteState::DataDescriptor;
_writeState = WriteState::LocalFileHeader;
return 0; return 0;
} }
@@ -391,13 +329,32 @@ namespace Zip
ifs.read(reinterpret_cast<char*>(buffer), nbBytesToRead ); ifs.read(reinterpret_cast<char*>(buffer), nbBytesToRead );
const ::uint64_t actualReadSize {static_cast<::uint64_t>(ifs.gcount())}; const ::uint64_t actualReadSize {static_cast<::uint64_t>(ifs.gcount())};
LMS_LOG(UTILS, INFO) << "writeFileData: to read = " << nbBytesToRead << ", actually read = " << actualReadSize; _currentFile->second.fileCrc32.processBytes(buffer, actualReadSize);
_currentOffset += actualReadSize; _currentOffset += actualReadSize;
return actualReadSize; return actualReadSize;
} }
std::size_t
Zipper::writeDataDescriptor(std::byte* buffer, std::size_t bufferSize)
{
assert(bufferSize >= minOutputBufferSize);
static_assert(DataDescriptor::getHeaderSize() <= minOutputBufferSize);
assert(_currentFile != std::end(_files));
DataDescriptor desc {buffer, bufferSize};
desc.setSignature();
desc.setCrc32UncompressedData(_currentFile->second.fileCrc32.getResult());
desc.setCompressedSize(_currentFile->second.fileSize);
desc.setUncompressedSize(_currentFile->second.fileSize);
++_currentFile;
_writeState = WriteState::LocalFileHeader;
return desc.getHeaderSize();
}
std::size_t std::size_t
Zipper::writeCentralDirectoryHeader(std::byte* buffer, std::size_t bufferSize) Zipper::writeCentralDirectoryHeader(std::byte* buffer, std::size_t bufferSize)
{ {
@@ -405,10 +362,7 @@ namespace Zip
static_assert(CentralDirectoryHeader::getHeaderSize() <= minOutputBufferSize); static_assert(CentralDirectoryHeader::getHeaderSize() <= minOutputBufferSize);
if (_currentFile == std::begin(_files)) if (_currentFile == std::begin(_files))
{
LMS_LOG(UI, INFO) << "First record! _currentZipOffset = " << _currentZipOffset;
_centralDirectoryOffset = _currentZipOffset; _centralDirectoryOffset = _currentZipOffset;
}
if (_currentFile == std::end(_files)) if (_currentFile == std::end(_files))
{ {
@@ -417,23 +371,16 @@ namespace Zip
return 0; return 0;
} }
LMS_LOG(UTILS, INFO) << "writeCentralDirectoryHeader. Relative offset = " << _currentFile->second.localFileHeaderOffset << ", crc = " << std::hex << _currentFile->second.fileCrc32;
CentralDirectoryHeader header {buffer, bufferSize}; CentralDirectoryHeader header {buffer, bufferSize};
header.setSignature(); header.setSignature();
header.setVersionMadeBy(2, 0); header.setVersionMadeBy(2, 0);
header.setVersionNeededToExtract(1, 0); header.setVersionNeededToExtract(1, 0);
header.setGeneralPurposeFlags(ZipHeader::GeneralPurposeFlag::LanguageEncoding); header.setGeneralPurposeFlags(ZipHeader::GeneralPurposeFlag::LanguageEncoding | ZipHeader::GeneralPurposeFlag::UseDataDescriptor);
switch (_compMethod) header.setCompressionMethod(ZipHeader::CompressionMethod::NoCompression);
{ header.setCompressedSize(_currentFile->second.fileSize);
case CompressionMethod::NoCompression: header.setUncompressedSize(_currentFile->second.fileSize);
header.setCompressionMethod(ZipHeader::CompressionMethod::NoCompression);
header.setCompressedSize(_currentFile->second.fileSize);
header.setUncompressedSize(_currentFile->second.fileSize);
break;
}
header.setLastModifiedDateTime(); // getLastWriteTime(*_currentFile)); header.setLastModifiedDateTime(); // getLastWriteTime(*_currentFile));
header.setCrc32UncompressedData(_currentFile->second.fileCrc32); header.setCrc32UncompressedData(_currentFile->second.fileCrc32.getResult());
header.setFileNameLength(_currentFile->first.size()); header.setFileNameLength(_currentFile->first.size());
header.setExtraFieldLength(0); header.setExtraFieldLength(0);
header.setFileCommentLength(0); header.setFileCommentLength(0);
@@ -463,7 +410,6 @@ namespace Zip
return 0; return 0;
} }
LMS_LOG(UTILS, INFO) << "writeCentralDirectoryHeaderFileName";
const std::size_t nbBytesToCopy {std::min<std::size_t>(fileName.size() - _currentOffset, bufferSize)}; const std::size_t nbBytesToCopy {std::min<std::size_t>(fileName.size() - _currentOffset, bufferSize)};
std::copy(std::next(std::begin(fileName), _currentOffset), std::next(std::begin(fileName), nbBytesToCopy), reinterpret_cast<unsigned char*>(buffer)); std::copy(std::next(std::begin(fileName), _currentOffset), std::next(std::begin(fileName), nbBytesToCopy), reinterpret_cast<unsigned char*>(buffer));
@@ -482,8 +428,6 @@ namespace Zip
EndOfCentralDirectoryRecord record {buffer, bufferSize}; EndOfCentralDirectoryRecord record {buffer, bufferSize};
LMS_LOG(UTILS, DEBUG) << "Writing EOR. nb records = " << _files.size() << ", offset = " << _centralDirectoryOffset << ", size = " << _centralDirectorySize;
record.setSignature(); record.setSignature();
record.setDiskNumber(0); record.setDiskNumber(0);
record.setCentralDirectoryDiskNumber(0); record.setCentralDirectoryDiskNumber(0);
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2020 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <boost/crc.hpp> // for boost::crc_32_type
namespace Utils
{
class Crc32Calculator
{
public:
void processBytes(const std::byte* _data, std::size_t dataSize)
{
_result.process_bytes(_data, dataSize);
}
std::uint32_t getResult() const
{
return _result.checksum();
}
private:
using Crc32Type = boost::crc_32_type;
Crc32Type _result;
};
}
+6 -9
View File
@@ -24,6 +24,7 @@
#include <filesystem> #include <filesystem>
#include "Exception.hpp" #include "Exception.hpp"
#include "utils/Crc32Calculator.hpp"
namespace Zip namespace Zip
{ {
@@ -33,17 +34,12 @@ namespace Zip
using LmsException::LmsException; using LmsException::LmsException;
}; };
// Very simple on-the-fly zip creator // Very simple on-the-fly zip creator, "store" method only
class Zipper class Zipper
{ {
public: public:
enum class CompressionMethod Zipper(const std::map<std::string, std::filesystem::path>& files);
{
NoCompression,
};
Zipper(const std::map<std::string, std::filesystem::path>& files, CompressionMethod comp = CompressionMethod::NoCompression);
static constexpr std::size_t minOutputBufferSize = 64; static constexpr std::size_t minOutputBufferSize = 64;
std::size_t writeSome(std::byte* buffer, std::size_t bufferSize); std::size_t writeSome(std::byte* buffer, std::size_t bufferSize);
@@ -55,6 +51,7 @@ namespace Zip
std::size_t writeLocalFileHeader(std::byte* buffer, std::size_t bufferSize); std::size_t writeLocalFileHeader(std::byte* buffer, std::size_t bufferSize);
std::size_t writeLocalFileHeaderFileName(std::byte* buffer, std::size_t bufferSize); std::size_t writeLocalFileHeaderFileName(std::byte* buffer, std::size_t bufferSize);
std::size_t writeFileData(std::byte* buffer, std::size_t bufferSize); std::size_t writeFileData(std::byte* buffer, std::size_t bufferSize);
std::size_t writeDataDescriptor(std::byte* buffer, std::size_t bufferSize);
std::size_t writeCentralDirectoryHeader(std::byte* buffer, std::size_t bufferSize); std::size_t writeCentralDirectoryHeader(std::byte* buffer, std::size_t bufferSize);
std::size_t writeCentralDirectoryHeaderFileName(std::byte* buffer, std::size_t bufferSize); std::size_t writeCentralDirectoryHeaderFileName(std::byte* buffer, std::size_t bufferSize);
std::size_t writeEndOfCentralDirectoryRecord(std::byte* buffer, std::size_t bufferSize); std::size_t writeEndOfCentralDirectoryRecord(std::byte* buffer, std::size_t bufferSize);
@@ -63,7 +60,7 @@ namespace Zip
{ {
std::filesystem::path filePath; std::filesystem::path filePath;
std::size_t fileSize; std::size_t fileSize;
std::uint32_t fileCrc32; Utils::Crc32Calculator fileCrc32;
std::size_t localFileHeaderOffset {}; std::size_t localFileHeaderOffset {};
}; };
@@ -75,13 +72,13 @@ namespace Zip
LocalFileHeader, LocalFileHeader,
LocalFileHeaderFileName, LocalFileHeaderFileName,
FileData, FileData,
DataDescriptor,
CentralDirectoryHeader, CentralDirectoryHeader,
CentralDirectoryHeaderFileName, CentralDirectoryHeaderFileName,
EndOfCentralDirectoryRecord, EndOfCentralDirectoryRecord,
Complete, Complete,
}; };
CompressionMethod _compMethod {CompressionMethod::NoCompression};
WriteState _writeState {WriteState::LocalFileHeader}; WriteState _writeState {WriteState::LocalFileHeader};
FileContainer::iterator _currentFile; FileContainer::iterator _currentFile;
std::size_t _currentOffset {}; std::size_t _currentOffset {};
+12 -11
View File
@@ -19,6 +19,9 @@
#include "DownloadResource.hpp" #include "DownloadResource.hpp"
#include <iostream>
#include <iomanip>
#include <Wt/WApplication.h> #include <Wt/WApplication.h>
#include <Wt/Http/Response.h> #include <Wt/Http/Response.h>
@@ -124,20 +127,18 @@ static
std::string std::string
getTrackPathName(Database::Track::pointer track) getTrackPathName(Database::Track::pointer track)
{ {
std::string fileName; std::ostringstream fileName;
auto trackNumber {track->getTrackNumber()}; if (auto discNumber {track->getDiscNumber()})
auto discNumber {track->getDiscNumber()}; fileName << *discNumber << ".";
if (auto trackNumber {track->getTrackNumber()})
fileName << std::setw(2) << std::setfill('0') << *trackNumber << " - ";
if (discNumber) fileName << StringUtils::replaceInString(track->getName(), "/", "_") << track->getPath().filename().extension().string();
fileName += std::to_string(*discNumber) + ".";
if (trackNumber)
fileName += std::to_string(*trackNumber) + " - ";
fileName += track->getName() + track->getPath().filename().extension().string(); LOG(DEBUG) << "Forged filename = '" << fileName.str() << "'";
fileName = StringUtils::replaceInString(fileName , "/", "_");
return fileName; return fileName.str();
} }
static static
@@ -166,7 +167,7 @@ createZipper(const std::vector<Database::Track::pointer>& tracks)
files.emplace(fileName, track->getPath()); files.emplace(fileName, track->getPath());
} }
return std::make_unique<Zip::Zipper>(files, Zip::Zipper::CompressionMethod::NoCompression); return std::make_unique<Zip::Zipper>(files);
} }
DownloadReleaseResource::DownloadReleaseResource(Database::IdType releaseId) DownloadReleaseResource::DownloadReleaseResource(Database::IdType releaseId)
+1 -1
View File
@@ -54,7 +54,7 @@ int main(int argc, char* argv[])
return EXIT_FAILURE; return EXIT_FAILURE;
} }
Zipper zipper {files, Zipper::CompressionMethod::NoCompression}; Zipper zipper {files};
while (!zipper.isComplete()) while (!zipper.isComplete())
{ {