Skip to content

Commit

Permalink
reworked extract & address some thread safety (#25)
Browse files Browse the repository at this point in the history
* moved to libarchive
* slowed down ui refresh & moved to async
* used atomic types for shared progress
* update Image to use unique_ptr
  • Loading branch information
dslatt authored Oct 26, 2024
1 parent 0e79e5b commit 9c335c2
Show file tree
Hide file tree
Showing 13 changed files with 210 additions and 171 deletions.
6 changes: 3 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ include(${BOREALIS_LIBRARY}/cmake/toolchain.cmake)
project(nso-icon-tool)
set(VERSION_MAJOR "0")
set(VERSION_MINOR "4")
set(VERSION_ALTER "2")
set(VERSION_ALTER "3")
set(VERSION_BUILD "0")
set(PROJECT_AUTHOR "dslatt")
set(PROJECT_ICON ${CMAKE_CURRENT_SOURCE_DIR}/resources/img/dev.jpg)
Expand Down Expand Up @@ -49,10 +49,10 @@ if (PLATFORM_SWITCH)
glfw3 EGL glapi drm_nouveau
# base lib
nx m
# extract
archive
# curl
curl z bz2 zstd lzma lz4
# extract
minizip
# hash
xxhash
)
Expand Down
4 changes: 1 addition & 3 deletions include/util/extract.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,5 @@

namespace extract
{
void extract(
const std::string &filename, const std::string &workingPath, bool overwriteExisting, std::function<void()> func = []()
{ return; });
void extract(const std::string &filename, const std::string &workingPath, bool overwriteExisting);
}
10 changes: 8 additions & 2 deletions include/util/image.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@

struct Image
{
unsigned char* img = nullptr;
struct HandleDeleter
{
void operator()(unsigned char *p) const { if (p) std::free(p); }
};

using Handle = std::unique_ptr<unsigned char, HandleDeleter>;
Handle data;
int size = 0; // raw size in bytes
int pixels = 0; // pixel count
int x = 0, y = 0, n = 0;

~Image();
~Image() = default;

Image(const Image &other);
Image(Image &&other) noexcept;
Expand Down
22 changes: 10 additions & 12 deletions include/util/progress_event.hpp
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
#pragma once

#include <atomic>

class ProgressEvent
{
private:
ProgressEvent() {}
int _current = 0;
int _max = 60;
double _now = 0;
double _total = 0;
double _speed = 0;
long _status_code = 0;
bool _interupt = false;
double _timeStep = 0;
std::string _msg = "";
std::atomic_int _current = 0;
std::atomic_int _max = 60;
std::atomic<double> _now = 0;
std::atomic<double> _total = 0;
std::atomic<double> _speed = 0;
std::atomic<long> _status_code = 0;
std::atomic<bool> _interupt = false;
std::atomic<double> _timeStep = 0;

public:
ProgressEvent(const ProgressEvent &) = delete;
Expand All @@ -36,11 +37,8 @@ class ProgressEvent
_status_code = 0;
_interupt = false;
_timeStep = 0;
_msg = "";
}

inline void setMsg(std::string msg) { _msg = std::move(msg); }
inline const std::string &getMsg() { return _msg; }
inline void setTotalSteps(int steps) { _max = steps; }
inline void setTotalCount(double total) { _total = total; }
inline void setSpeed(double speed) { _speed = speed; }
Expand Down
11 changes: 6 additions & 5 deletions include/view/download_view.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <borealis.hpp>
#include <atomic>

typedef brls::Event<std::string> DownloadDoneEvent;

Expand Down Expand Up @@ -31,14 +32,14 @@ class DownloadView : public brls::Box
void downloadFile();
void updateProgress();

std::thread updateThread;
std::thread downloadThread;
std::jthread updateThread;
std::jthread downloadThread;
std::mutex threadMutex;
std::condition_variable threadCondition;

DownloadDoneEvent::Callback cb;

bool downloadFinished = false;
bool extractFinished = false;
bool overwriteExisting = false;
std::atomic_flag downloadFinished;
std::atomic_flag extractFinished;
bool overwriteExisting;
};
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<img src="https://img.shields.io/static/v1?label=license&message=GPLV3&labelColor=111111&color=0057da&style=for-the-badge&logo=data%3Aimage/png%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAIGNIUk0AAHpFAACAgwAA/FcAAIDoAAB5FgAA8QEAADtfAAAcheDStWoAAAFGSURBVHjarJK9LgRhFIafWUuiEH/rJwrJClEq3IELUKgo3IrETWh0FC7BNVih0AoKBQoEydq11qMwm5yMsbPEm3yZd55zvnfO92VQKVhLak09UZeL%2BrsVZ9Qdv2tXnf1NYEndUushZFGthvemuq32FwWuq%2BeZid5DvZGpXambeYGr6qnd9dGldqaudQL3QuFWvVbbmaC6%2BprDr9WbwA4SdQW4BwaABb50CTykfjjwC%2BAx9SPAfOANYDxRCXpOnxNAM4ePA63Ul8NHR4E2QClsGgGG0jUR%2BFjglcAn8/pj4HTwUz/42FPJ68lOSDhCkR/O46XM0Qh3VcRH83jph%2BZefKUosBr8XA%2B%2BmufLAR4Dh6k/CrzWA691YOc/3Ejv6iNM3k59Xw%2B8D3gC9hN1ErjjfzSbqHVg8J8CG2XgBXgL4/9VCdD6HACaHdcHGCRMgQAAAABJRU5ErkJggg%3D%3D" alt=License>
</a>
<a rel="VERSION" href="https://github.com/dslatt/nso-icon-tool">
<img src="https://img.shields.io/static/v1?label=version&message=0.4.2&labelColor=111111&color=06f&style=for-the-badge" alt="Version">
<img src="https://img.shields.io/static/v1?label=version&message=0.4.3&labelColor=111111&color=06f&style=for-the-badge" alt="Version">
</a>
<a rel="BUILD" href="https://github.com/dslatt/nso-icon-tool/actions">
<img src="https://img.shields.io/github/actions/workflow/status/dslatt/nso-icon-tool/build-switch-release.yml?branch=main&labelColor=111111&color=06f&style=for-the-badge" alt=Build>
Expand Down
208 changes: 124 additions & 84 deletions source/util/extract.cpp
Original file line number Diff line number Diff line change
@@ -1,126 +1,166 @@
#include "util/extract.hpp"

#include <dirent.h>
#include <minizip/unzip.h>

#include <string>
#include <vector>
#include <borealis.hpp>
#include <strings.h>
#include <filesystem>
#include <fstream>
#include <archive.h>
#include <archive_entry.h>

#include <switch.h>
#include <chrono>

#include "util/progress_event.hpp"

using namespace brls::literals; // for _i18n
namespace fs = std::filesystem;

constexpr size_t WRITE_BUFFER_SIZE = 0x10000;
using ArchivePtr = std::unique_ptr<struct archive, decltype(&archive_read_free)>;

namespace extract
{
namespace
std::tuple<int64_t, int64_t> getFileStats(const std::string &archivePath)
{
bool caselessCompare(const std::string &a, const std::string &b)
{
return strcasecmp(a.c_str(), b.c_str()) == 0;
std::tuple<int64_t, int64_t> stats{0, 0};
ArchivePtr archive(archive_read_new(), archive_read_free);
struct archive_entry* entry;

archive_read_support_format_all(archive.get());
archive_read_support_filter_all(archive.get());

if(archive_read_open_filename(archive.get(), archivePath.c_str(), 10240) == ARCHIVE_OK) {
while(archive_read_next_header(archive.get(), &entry) == ARCHIVE_OK) {
std::get<0>(stats) += 1;
std::get<1>(stats) += archive_entry_size(entry);
}
}

s64 getUncompressedSize(const std::string &archivePath)
return stats;
}

void ensureAvailableStorage(size_t uncompressedSize)
{
s64 freeStorage;

if (R_SUCCEEDED(nsGetFreeSpaceSize(NcmStorageId_SdCard, &freeStorage)))
{
s64 size = 0;
unzFile zfile = unzOpen(archivePath.c_str());
unz_global_info gi;
unzGetGlobalInfo(zfile, &gi);
for (uLong i = 0; i < gi.number_entry; ++i)
brls::Logger::info("Uncompressed size of archive: {}. Available: {}", uncompressedSize, freeStorage);
if (uncompressedSize * 1.1 > freeStorage)
{
unz_file_info fi;
unzOpenCurrentFile(zfile);
unzGetCurrentFileInfo(zfile, &fi, NULL, 0, NULL, 0, NULL, 0);
size += fi.uncompressed_size;
unzCloseCurrentFile(zfile);
unzGoToNextFile(zfile);
brls::Application::crash("app/errors/insufficient_storage"_i18n);
std::this_thread::sleep_for(std::chrono::microseconds(2000000));
brls::Application::quit();
}
unzClose(zfile);
return size; // in B
}
}

void ensureAvailableStorage(const std::string &archivePath)
{
s64 uncompressedSize = getUncompressedSize(archivePath);
s64 freeStorage;
void extract(const std::string &archivePath, const std::string &workingPath, bool overwriteExisting) {
auto start = std::chrono::high_resolution_clock::now();
int count = 0;

if (R_SUCCEEDED(nsGetFreeSpaceSize(NcmStorageId_SdCard, &freeStorage)))
{
brls::Logger::info("Uncompressed size of archive {}: {}. Available: {}", archivePath, uncompressedSize, freeStorage);
if (uncompressedSize * 1.1 > freeStorage)
{
brls::Application::crash("app/errors/insufficient_storage"_i18n);
std::this_thread::sleep_for(std::chrono::microseconds(2000000));
brls::Application::quit();
}
}
try {

auto [totalFiles, totalSize] = getFileStats(archivePath);
ensureAvailableStorage(totalSize);

brls::sync([totalFiles, totalSize]() {
brls::Logger::info("Extracting {} entries of size {} bytes", totalFiles, totalSize);
});

ProgressEvent::instance().setTotalSteps(totalFiles);
ProgressEvent::instance().setStep(0);

ArchivePtr archive(archive_read_new(), archive_read_free);
struct archive_entry *entry;
int err = 0, i = 0;

archive_read_support_format_all(archive.get());
archive_read_support_filter_all(archive.get());

if ((err = archive_read_open_filename(archive.get(), archivePath.c_str(), 10240))) {
brls::sync([err = std::string(archive_error_string(archive.get()))]() {
brls::Logger::error("Error opening archive: {}", err);
});
return;
}

void extractEntry(std::string filename, unzFile &zfile, bool forceCreateTree = false)
{
if (filename.back() == '/')
{
fs::create_directories(filename);
return;
for (;;) {
if (ProgressEvent::instance().getInterupt()) {
ProgressEvent::instance().setStep(ProgressEvent::instance().getMax());
break;
}
if (forceCreateTree)
{
fs::create_directories(filename);

err = archive_read_next_header(archive.get(), &entry);
if (err == ARCHIVE_EOF) {
ProgressEvent::instance().setStep(ProgressEvent::instance().getMax());
break;
}
void *buf = malloc(WRITE_BUFFER_SIZE);
FILE *outfile;
outfile = fopen(filename.c_str(), "wb");
for (int j = unzReadCurrentFile(zfile, buf, WRITE_BUFFER_SIZE); j > 0; j = unzReadCurrentFile(zfile, buf, WRITE_BUFFER_SIZE))
{
fwrite(buf, 1, j, outfile);
if (err < ARCHIVE_OK)
brls::sync([archivePath, err = std::string(archive_error_string(archive.get()))]() {
brls::Logger::error("Error reading archive entry: {}", err);
});
if (err < ARCHIVE_WARN) {
break;
}
free(buf);
fclose(outfile);
}
}

void extract(const std::string &archivePath, const std::string &workingPath, bool overwriteExisting, std::function<void()> func)
{
ensureAvailableStorage(archivePath);

unzFile zfile = unzOpen(archivePath.c_str());
unz_global_info gi;
unzGetGlobalInfo(zfile, &gi);
auto filepath = fs::path(workingPath) / archive_entry_pathname(entry);

ProgressEvent::instance().setTotalSteps(gi.number_entry);
ProgressEvent::instance().setStep(0);
if (archive_entry_filetype(entry) == AE_IFDIR) {
fs::create_directories(filepath);
ProgressEvent::instance().setStep(++i);
continue;
}

for (uLong i = 0; i < gi.number_entry; ++i)
{
char szFilename[0x301] = "";
unzOpenCurrentFile(zfile);
unzGetCurrentFileInfo(zfile, NULL, szFilename, sizeof(szFilename), NULL, 0, NULL, 0);
std::string filename = workingPath + szFilename;
if (fs::exists(filepath) && !overwriteExisting) {
ProgressEvent::instance().setStep(++i);
continue;
}

if (ProgressEvent::instance().getInterupt())
{
unzCloseCurrentFile(zfile);
std::ofstream outfile(filepath.string(), std::ios::binary | std::ios::trunc);
if (!outfile.is_open()) {
brls::sync([path = filepath.string()]() {
brls::Logger::error("Error opening write file for archive entry: {}", path);
});
break;
}

if (overwriteExisting || !fs::exists(filename))
{
ProgressEvent::instance().setMsg(filename);
extractEntry(filename, zfile);
const void* buff = nullptr;
size_t size = 0;
int64_t offset = 0;
int res = -1;
while ((res = archive_read_data_block(archive.get(), &buff, &size, &offset)) == ARCHIVE_OK) {
try {
outfile.write(static_cast<const char*>(buff), size);
} catch(const std::exception& e) {
res = ARCHIVE_FATAL;
break;
}
}

if (res != ARCHIVE_EOF) {
brls::sync([res = std::string(archive_error_string(archive.get()))]() {
brls::Logger::error("Error writing out archive entry: {}", res);
});
outfile.close();
fs::remove(filepath);
break;
}

ProgressEvent::instance().setStep(i);
unzCloseCurrentFile(zfile);
unzGoToNextFile(zfile);
count++;
ProgressEvent::instance().setStep(++i);
}

} catch(const std::exception& e) {
brls::sync([e = std::string(e.what())]() {
brls::Logger::error("Unexpected error extracting archive: {}", e);
});
}
unzClose(zfile);
ProgressEvent::instance().setStep(ProgressEvent::instance().getMax());

auto end = std::chrono::high_resolution_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(end - start).count();

brls::sync([elapsed, count]() {
brls::Logger::info("Total extraction time: {}s for {} files", elapsed, count);
});
}
}
Loading

0 comments on commit 9c335c2

Please sign in to comment.