From 631f32a4c9de9c7ee19e857d2dae6816180483a2 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Tue, 22 Oct 2024 20:46:35 +1000 Subject: [PATCH] Common: Add ZipHelpers --- src/common/zip_helpers.h | 239 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 src/common/zip_helpers.h diff --git a/src/common/zip_helpers.h b/src/common/zip_helpers.h new file mode 100644 index 000000000..54656ff7f --- /dev/null +++ b/src/common/zip_helpers.h @@ -0,0 +1,239 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#pragma once + +#include "error.h" +#include "log.h" + +#include +#include +#include +#include +#include + +#include "zip.h" + +namespace ZipHelpers { + +[[maybe_unused]] static void SetErrorObject(Error* error, std::string_view msg, zip_error_t* ze) +{ + Error::SetStringFmt(error, "{}{}", msg, ze ? zip_error_strerror(ze) : "UNKNOWN"); + if (ze) + zip_error_fini(ze); +} + +struct ZipDeleter +{ + void operator()(zip_t* zf) + { + if (!zf) + return; + + const int err = zip_close(zf); + if (err != 0) + { + Log::FastWrite("ZipHelpers", __FUNCTION__, Log::Level::Error, "Failed to close zip file: {}", err); + zip_discard(zf); + } + } +}; + +struct ZipFileDeleter +{ + void operator()(zip_file_t* zf) + { + if (!zf) + return; + + zip_fclose(zf); + } +}; + +using ManagedZipT = std::unique_ptr; +using ManagedZipFileT = std::unique_ptr; + +[[maybe_unused]] static inline ManagedZipT OpenManagedZipFile(const char* filename, int flags, Error* error = nullptr) +{ + zip_error_t ze; + zip_source_t* zs = zip_source_file_create(filename, 0, 0, &ze); + zip_t* zip; + if (!zs) + { + SetErrorObject(error, "zip_source_file_create() failed: ", &ze); + zip = nullptr; + } + else + { + if (!(zip = zip_open_from_source(zs, flags, &ze))) + { + // have to clean up source + SetErrorObject(error, "zip_open_from_source() failed: {}", &ze); + zip_source_free(zs); + } + } + + return ManagedZipT(zip); +} + +[[maybe_unused]] static inline ManagedZipT OpenManagedZipCFile(std::FILE* fp, int flags, Error* error = nullptr) +{ + zip_error_t ze; + zip_source_t* zs = zip_source_filep_create(fp, 0, 0, &ze); + zip_t* zip; + if (!zs) + { + SetErrorObject(error, "zip_source_filep_create() failed: ", &ze); + std::fclose(fp); + zip = nullptr; + } + else + { + if (!(zip = zip_open_from_source(zs, flags, &ze))) + { + // have to clean up source + SetErrorObject(error, "zip_open_from_source() failed: {}", &ze); + zip_source_free(zs); + } + } + + return ManagedZipT(zip); +} + +[[maybe_unused]] static inline ManagedZipT OpenManagedZipBuffer(const void* buffer, size_t size, int flags, + bool free_buffer, Error* error = nullptr) +{ + zip_error_t ze; + zip_source_t* zs = zip_source_buffer_create(buffer, size, free_buffer, &ze); + zip_t* zip; + if (!zs) + { + SetErrorObject(error, "zip_source_buffer_create() failed: ", &ze); + if (free_buffer) + std::free(const_cast(buffer)); + zip = nullptr; + } + else + { + if (!(zip = zip_open_from_source(zs, flags, &ze))) + { + // have to clean up source + SetErrorObject(error, "zip_open_from_source() failed: {}", &ze); + zip_source_free(zs); + } + } + + return ManagedZipT(zip); +} + +[[maybe_unused]] static inline ManagedZipFileT OpenManagedFileInZip(zip_t* zip, const char* filename, zip_flags_t flags, + Error* error = nullptr) +{ + zip_file_t* zf = zip_fopen(zip, filename, flags); + if (!zf) + SetErrorObject(error, "zip_fopen() failed: ", zip_get_error(zip)); + return ManagedZipFileT(zf); +} + +[[maybe_unused]] static inline ManagedZipFileT OpenManagedFileIndexInZip(zip_t* zip, zip_uint64_t index, + zip_flags_t flags, Error* error = nullptr) +{ + zip_file_t* zf = zip_fopen_index(zip, index, flags); + if (!zf) + SetErrorObject(error, "zip_fopen_index() failed: ", zip_get_error(zip)); + return ManagedZipFileT(zf); +} + +template +[[maybe_unused]] static inline std::optional +ReadFileInZipToContainer(zip_t* zip, const char* name, bool case_sensitive = true, Error* error = nullptr) +{ + const int flags = case_sensitive ? 0 : ZIP_FL_NOCASE; + + std::optional ret; + const zip_int64_t file_index = zip_name_locate(zip, name, flags); + if (file_index >= 0) + { + zip_stat_t zst; + if (zip_stat_index(zip, file_index, flags, &zst) == 0) + { + zip_file_t* zf = zip_fopen_index(zip, file_index, flags); + if (zf) + { + ret = T(); + ret->resize(static_cast(zst.size)); + if (zip_fread(zf, ret->data(), ret->size()) != static_cast(ret->size())) + { + SetErrorObject(error, "zip_fread() failed: ", zip_get_error(zip)); + ret.reset(); + } + + zip_fclose(zf); + } + } + else + { + SetErrorObject(error, "zip_stat_index() failed: ", zip_get_error(zip)); + } + } + else + { + SetErrorObject(error, "zip_name_locate() failed: ", zip_get_error(zip)); + } + + return ret; +} + +template +[[maybe_unused]] static inline std::optional ReadFileInZipToContainer(zip_file_t* file, u32 chunk_size = 4096, + Error* error = nullptr) +{ + std::optional ret = T(); + for (;;) + { + const size_t pos = ret->size(); + ret->resize(pos + chunk_size); + const s64 read = zip_fread(file, ret->data() + pos, chunk_size); + if (read < 0) + { + // read error + Error::SetStringView(error, "zip_fread() failed"); + break; + } + + // if less than chunk size, we're EOF + if (read != static_cast(chunk_size)) + { + ret->resize(pos + static_cast(read)); + break; + } + } + + return ret; +} + +[[maybe_unused]] static inline std::optional +ReadFileInZipToString(zip_t* zip, const char* name, bool case_sensitive = true, Error* error = nullptr) +{ + return ReadFileInZipToContainer(zip, name, case_sensitive, error); +} + +[[maybe_unused]] static inline std::optional ReadFileInZipToString(zip_file_t* file, u32 chunk_size = 4096, + Error* error = nullptr) +{ + return ReadFileInZipToContainer(file, chunk_size, error); +} + +[[maybe_unused]] static inline std::optional> +ReadBinaryFileInZip(zip_t* zip, const char* name, bool case_sensitive = true, Error* error = nullptr) +{ + return ReadFileInZipToContainer>(zip, name, case_sensitive, error); +} + +[[maybe_unused]] static inline std::optional> +ReadBinaryFileInZip(zip_file_t* file, u32 chunk_size = 4096, Error* error = nullptr) +{ + return ReadFileInZipToContainer>(file, chunk_size, error); +} + +} // namespace ZipHelpers \ No newline at end of file