263 lines
8.0 KiB
C++
263 lines
8.0 KiB
C++
/***
|
|
Copyright (C) 2023 J Reece Wilson (a/k/a "Reece"). All rights reserved.
|
|
|
|
File: FSCompress.cpp
|
|
Date: 2023-1-26
|
|
Author: Reece
|
|
***/
|
|
#include <Source/RuntimeInternal.hpp>
|
|
#include "FS.hpp"
|
|
#include "FS.Generic.hpp"
|
|
#include <Source/Time/Time.hpp>
|
|
|
|
namespace Aurora::IO::FS
|
|
{
|
|
static const AuUInt64 kFileCopyBlock = 0xFFFF * 4; // 64KiB
|
|
static auto const kCompressionType = AuCompression::ECompressionType::eZSTD;
|
|
static const AuString kStringSuffix = ".zst";
|
|
|
|
static bool CompressEx2(const AuString &path, const AuString &suffix, AuCompression::ECompressionType type, AuInt8 level, bool bCheck)
|
|
{
|
|
static const auto kCompressionReadChunks = kFileCopyBlock;
|
|
|
|
if (suffix.empty())
|
|
{
|
|
SysPushErrorInvalidArgPos2();
|
|
return {};
|
|
}
|
|
|
|
if (bCheck)
|
|
{
|
|
if (AuEndsWith(path, suffix))
|
|
{
|
|
SysPushErrorIO("File path (\"{}\") ends in {}, yet was requested to be compressed. A user or a developer is probably being stupid.", path, suffix);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
auto pFileSrc = OpenReadShared(path, EFileAdvisoryLockLevel::eBlockWrite);
|
|
if (!pFileSrc)
|
|
{
|
|
SysPushErrorIO("Couldn't open compression source path: {}", path);
|
|
return {};
|
|
}
|
|
|
|
auto qwLength = pFileSrc->GetLength();
|
|
if (!qwLength)
|
|
{
|
|
pFileSrc->Close();
|
|
return AuFS::Relink(path, path + suffix);
|
|
}
|
|
|
|
auto pFileDest = OpenWriteShared(path + suffix, EFileAdvisoryLockLevel::eBlockReadWrite);
|
|
if (!pFileDest)
|
|
{
|
|
SysPushErrorIO("Couldn't open compression destination path: {}{}", path, suffix);
|
|
return {};
|
|
}
|
|
|
|
if (pFileDest->GetLength())
|
|
{
|
|
SysPushErrorIO("File ({}) exists", path);
|
|
return {};
|
|
}
|
|
|
|
|
|
auto pFileStream = AuMakeShared<AuIO::FS::FileReader>(pFileSrc);
|
|
SysCheckNotNullMemory(pFileStream, {});
|
|
|
|
auto pDestStream = AuMakeShared<AuIO::FS::FileWriter>(pFileDest);
|
|
SysCheckNotNullMemory(pDestStream, {});
|
|
|
|
AuCompression::CompressInfo compress { type };
|
|
compress.uCompressionLevel = level;
|
|
compress.uInternalStreamSize = kCompressionReadChunks * 2;
|
|
auto pCompressor = AuCompression::CompressorUnique(pFileStream, compress);
|
|
if (!pCompressor)
|
|
{
|
|
SysPushErrorMemory("no compressor");
|
|
return {};
|
|
}
|
|
|
|
AuUInt64 qwTotalRead {};
|
|
{
|
|
AuByteBuffer tempMemory(kCompressionReadChunks);
|
|
if (!tempMemory)
|
|
{
|
|
SysPushErrorMemory();
|
|
return {};
|
|
}
|
|
|
|
for (AuUInt64 i = 0; i < qwLength; i += 0 /*kCompressionReadChunks*/)
|
|
{
|
|
auto [read, written] = pCompressor->Ingest(kCompressionReadChunks);
|
|
i += read;
|
|
|
|
if (read == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
qwTotalRead += read;
|
|
|
|
bool bAnyWritten {};
|
|
while (auto uBytes = pCompressor->Read(tempMemory))
|
|
{
|
|
tempMemory.writePtr += uBytes;
|
|
bAnyWritten = true;
|
|
|
|
AuUInt idc {};
|
|
if (AuIO::WriteAll(pDestStream.get(), { tempMemory, idc }) != AuIO::EStreamError::eErrorNone)
|
|
{
|
|
SysPushErrorIO("AuIO::WriteAll failed");
|
|
return {};
|
|
}
|
|
|
|
tempMemory.writePtr = tempMemory.base;
|
|
tempMemory.readPtr = tempMemory.base;
|
|
}
|
|
|
|
// zstd u ok?
|
|
// if i remove this, we get no data at all.
|
|
// even if i try to displace this to above `->Finish()` we still get nothing
|
|
// preemptively flushing fixes everything /shrug
|
|
pCompressor->Flush();
|
|
}
|
|
|
|
pCompressor->Finish();
|
|
|
|
while (auto uBytes = pCompressor->Read(tempMemory))
|
|
{
|
|
tempMemory.writePtr += uBytes;
|
|
|
|
AuUInt idc {};
|
|
if (AuIO::WriteAll(pDestStream.get(), { tempMemory, idc }) != AuIO::EStreamError::eErrorNone)
|
|
{
|
|
SysPushErrorIO("AuIO::WriteAll failed");
|
|
return {};
|
|
}
|
|
|
|
tempMemory.writePtr = tempMemory.base;
|
|
tempMemory.readPtr = tempMemory.base;
|
|
}
|
|
}
|
|
|
|
return qwTotalRead == qwLength;
|
|
}
|
|
|
|
AUKN_SYM bool CompressEx(const AuString &path, const AuString &suffix, AuCompression::ECompressionType type, AuInt8 level)
|
|
{
|
|
return CompressEx2(path, kStringSuffix, kCompressionType, level, false);
|
|
}
|
|
|
|
AUKN_SYM bool Compress(const AuString &path, AuInt8 level)
|
|
{
|
|
return CompressEx2(path, kStringSuffix, kCompressionType, level, true);
|
|
}
|
|
|
|
AUKN_SYM bool DecompressEx(const AuString &path, const AuString &suffix, AuCompression::ECompressionType type)
|
|
{
|
|
static const auto kCompressionReadChunks = kFileCopyBlock;
|
|
|
|
if (suffix.empty())
|
|
{
|
|
SysPushErrorInvalidArgPos2();
|
|
return {};
|
|
}
|
|
|
|
auto pFileSrc = OpenReadShared(path + suffix, EFileAdvisoryLockLevel::eBlockWrite);
|
|
if (!pFileSrc)
|
|
{
|
|
SysPushErrorIO("Couldn't open compression source path: {}{}", path, suffix);
|
|
return {};
|
|
}
|
|
|
|
auto qwLength = pFileSrc->GetLength();
|
|
if (!qwLength)
|
|
{
|
|
pFileSrc->Close();
|
|
return AuFS::Relink(path + suffix, path);
|
|
}
|
|
|
|
auto pFileDest = OpenWriteShared(path, EFileAdvisoryLockLevel::eBlockReadWrite);
|
|
if (!pFileDest)
|
|
{
|
|
SysPushErrorIO("Couldn't open decompression destination path: {}", path);
|
|
return {};
|
|
}
|
|
|
|
if (pFileDest->GetLength())
|
|
{
|
|
SysPushErrorIO("File exists path: {}", path);
|
|
return {};
|
|
}
|
|
|
|
auto pFileStream = AuMakeShared<AuIO::FS::FileReader>(pFileSrc);
|
|
if (!pFileStream)
|
|
{
|
|
SysPushErrorMemory();
|
|
return {};
|
|
}
|
|
|
|
auto pDestStream = AuMakeShared<AuIO::FS::FileWriter>(pFileDest);
|
|
if (!pDestStream)
|
|
{
|
|
SysPushErrorMemory();
|
|
return {};
|
|
}
|
|
|
|
AuCompression::DecompressInfo decompress{ type };
|
|
decompress.uInternalStreamSize = kCompressionReadChunks * 5;
|
|
auto pDecompressor = AuCompression::DecompressorUnique(pFileStream, decompress);
|
|
if (!pDecompressor)
|
|
{
|
|
SysPushErrorMemory("no decompressor");
|
|
return {};
|
|
}
|
|
|
|
AuUInt64 qwTotalRead {};
|
|
{
|
|
AuByteBuffer tempMemory(kCompressionReadChunks);
|
|
if (!tempMemory)
|
|
{
|
|
SysPushErrorMemory();
|
|
return {};
|
|
}
|
|
|
|
for (AuUInt64 i = 0; i < qwLength; i += 0 /*kCompressionReadChunks*/)
|
|
{
|
|
auto [read, written] = pDecompressor->Ingest(kCompressionReadChunks);
|
|
i += read;
|
|
|
|
if (read == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
qwTotalRead += read;
|
|
|
|
while (auto uBytes = pDecompressor->Read(tempMemory))
|
|
{
|
|
tempMemory.writePtr += uBytes;
|
|
|
|
AuUInt idc {};
|
|
if (AuIO::WriteAll(pDestStream.get(), { tempMemory, idc }) != AuIO::EStreamError::eErrorNone)
|
|
{
|
|
SysPushErrorIO("AuIO::WriteAll failed");
|
|
return {};
|
|
}
|
|
|
|
tempMemory.writePtr = tempMemory.base;
|
|
tempMemory.readPtr = tempMemory.base;
|
|
}
|
|
}
|
|
}
|
|
|
|
return qwTotalRead == qwLength;
|
|
}
|
|
|
|
AUKN_SYM bool Decompress(const AuString &path)
|
|
{
|
|
return DecompressEx(path, kStringSuffix, kCompressionType);
|
|
}
|
|
} |