diff --git a/inc/appStates/titleOptionState.hpp b/inc/appStates/titleOptionState.hpp new file mode 100644 index 0000000..645787f --- /dev/null +++ b/inc/appStates/titleOptionState.hpp @@ -0,0 +1,27 @@ +#pragma once +#include +#include "appStates/appState.hpp" +#include "data/data.hpp" +#include "ui/ui.hpp" + +class titleOptionState : public appState +{ + public: + titleOptionState(data::user *currentUser, data::userSaveInfo *currentUserSaveInfo, data::titleInfo *currentTitleInfo); + ~titleOptionState(); + + void update(void); + void render(void); + + private: + // Pointer to current user + data::user *m_CurrentUser; + // Pointer to current user's save info + data::userSaveInfo *m_CurrentUserSaveInfo; + // Pointer to current title + data::titleInfo *m_CurrentTitleInfo; + // Slide out panel + std::unique_ptr m_SlidePanel; + // Menu + std::unique_ptr m_OptionsMenu; +}; \ No newline at end of file diff --git a/inc/data/titleInfo.hpp b/inc/data/titleInfo.hpp index 761dc5e..d2f2fe8 100644 --- a/inc/data/titleInfo.hpp +++ b/inc/data/titleInfo.hpp @@ -16,6 +16,8 @@ namespace data std::string getPathSafeTitle(void); // Returns icon texture SDL_Texture *getIcon(void); + // Returns journal size + uint64_t getJournalSize(const FsSaveDataType &saveType); private: // Title ID is needed for a few things diff --git a/inc/filesystem/filesystem.hpp b/inc/filesystem/filesystem.hpp index 91803ca..15748b8 100644 --- a/inc/filesystem/filesystem.hpp +++ b/inc/filesystem/filesystem.hpp @@ -17,4 +17,6 @@ namespace fs void unmountSaveData(void); // Commits to default save path void commitSaveData(void); + // Erases current save data from device + void eraseSaveData(void); } \ No newline at end of file diff --git a/inc/filesystem/io.hpp b/inc/filesystem/io.hpp index c981a51..cf267fd 100644 --- a/inc/filesystem/io.hpp +++ b/inc/filesystem/io.hpp @@ -9,42 +9,47 @@ namespace fs { namespace io { - // Default buffer size used for transfering files - static const int FILE_BUFFER_SIZE = 0x400000; - // Struct for threads reading/writing threads. Here because other parts of JKSV can share the writeFunction - typedef struct + namespace file { - // File's size - uint64_t fileSize; - // Mutex - std::mutex bufferLock; - // Conditional for making sure buffer is empty/full - std::condition_variable bufferIsReady; - // Bool to check - bool bufferIsFull = false; - // Buffer shared for transfer - std::vector buffer; - // Whether file needs to be commited on write - bool commitWrite = false; - // Journal size - uint64_t journalSize = 0; - // Current offset - uint64_t currentOffset = 0; - } threadStruct; + // Default buffer size used for transfering files + static const int FILE_BUFFER_SIZE = 0x400000; + // Struct for threads reading/writing threads. Here because other parts of JKSV can share the writeFunction + typedef struct + { + // File's size + uint64_t fileSize; + // Mutex + std::mutex bufferLock; + // Conditional for making sure buffer is empty/full + std::condition_variable bufferIsReady; + // Bool to check + bool bufferIsFull = false; + // Buffer shared for transfer + std::vector buffer; + // Whether file needs to be commited on write + bool commitWrite = false; + // Journal size + uint64_t journalSize = 0; + // Current offset + uint64_t currentOffset = 0; + } threadStruct; - // Just returns size of file - int getFileSize(const std::string &filePath); - // These functions are all threaded/task wrapper functions - // Copies source to destination. - void copyFile(const std::string &source, const std::string &destination); - // Copies source to destination, but commits to save device after journalSize bytes have been written - void copyFileCommit(const std::string &source, const std::string &destination, const uint64_t &journalSize); - // Recusively copies source to destination - void copyDirectory(const std::string &source, const std::string &destination); - // Recursively copies source to destination - void copyDirectoryCommit(const std::string &source, const std::string &destination, const uint64_t &journalSize); - // These are functions for threaded copying that other parts of JKSV can use. - void readThreadFunction(const std::string &source, std::shared_ptr sharedStruct); - void writeThreadFunction(const std::string &destination, std::shared_ptr sharedStruct); + // Just returns size of file + int getFileSize(const std::string &filePath); + // These functions are all threaded/task wrapper functions + // Copies source to destination. + void copyFile(const std::string &source, const std::string &destination); + // Copies source to destination, but commits to save device after journalSize bytes have been written + void copyFileCommit(const std::string &source, const std::string &destination, const uint64_t &journalSize); + // Recusively copies source to destination + void copyDirectory(const std::string &source, const std::string &destination); + // Recursively copies source to destination + void copyDirectoryCommit(const std::string &source, const std::string &destination, const uint64_t &journalSize); + // std::filesystem::remove all crashes switch? Had to write my own... + void deleteDirectoryRecursively(const std::string &target); + // These are functions for threaded copying that other parts of JKSV can use. + void readThreadFunction(const std::string &source, std::shared_ptr sharedStruct); + void writeThreadFunction(const std::string &destination, std::shared_ptr sharedStruct); + } } } \ No newline at end of file diff --git a/inc/filesystem/zip.hpp b/inc/filesystem/zip.hpp index 2a3000b..5fd01cd 100644 --- a/inc/filesystem/zip.hpp +++ b/inc/filesystem/zip.hpp @@ -9,9 +9,16 @@ namespace fs { namespace io { - // Copies and compresses source directory to zipFile passed - void copyDirectoryToZip(const std::string &source, zipFile zip); - // This is only used to write to save filesystems, so it requires journal size - void copyZipToDirectory(unzFile unzip, const std::string &destination, const uint64_t &journalSize); + namespace zip + { + // Copies and compresses source directory to zipFile passed + void copyDirectoryToZip(const std::string &source, zipFile zip); + // This is only used to write to save filesystems, so it requires journal size + void copyZipToDirectory(unzFile unzip, const std::string &destination, const uint64_t &journalSize); + // Returns the total uncompressed sizes of files in zip + uint64_t getZipTotalFileSize(unzFile unzip); + // Returns if it's possible to get to a first file in unz + bool zipFileIsNotEmpty(unzFile unzip); + } } } \ No newline at end of file diff --git a/inc/ui/strings.hpp b/inc/ui/strings.hpp index 4ae8881..f4086f2 100644 --- a/inc/ui/strings.hpp +++ b/inc/ui/strings.hpp @@ -58,6 +58,8 @@ #define LANG_TITLE_OPTIONS_MENU "titleOptions" #define LANG_SAVE_TYPE_MAIN_MENU "saveTypeMainMenu" +#define LANG_TITLE_OPTIONS_MENU "titleOptions" + // Thread status strings #define LANG_THREAD_CREATING_SAVE "threadStatusCreatingSaveData" #define LANG_THREAD_COPYING_FILE "threadStatusCopyingFile" diff --git a/src/appStates/backupMenuState.cpp b/src/appStates/backupMenuState.cpp index 746d68d..5d09520 100644 --- a/src/appStates/backupMenuState.cpp +++ b/src/appStates/backupMenuState.cpp @@ -25,9 +25,12 @@ struct backupArgs : sys::taskArgs }; // This is shared by overwrite and restore +// To do: Find a better name since this holds more than paths now struct pathArgs : sys::taskArgs { std::string source, destination; + // Journal size for commits + uint64_t journalSize = 0; // For reloading folder list backupMenuState *sendingState = NULL; }; @@ -78,18 +81,16 @@ void createNewBackup(sys::task *task, std::shared_ptr args) if (config::getByKey(CONFIG_USE_ZIP) || extension == "zip") { zipFile zipOut = zipOpen64(path.c_str(), 0); - fs::io::copyDirectoryToZip(fs::DEFAULT_SAVE_MOUNT_DEVICE, zipOut); + fs::io::zip::copyDirectoryToZip(fs::DEFAULT_SAVE_MOUNT_DEVICE, zipOut); zipClose(zipOut, ""); } else { std::filesystem::create_directories(path); - fs::io::copyDirectory(fs::DEFAULT_SAVE_MOUNT_DEVICE, path); + fs::io::file::copyDirectory(fs::DEFAULT_SAVE_MOUNT_DEVICE, path); } } - { - logger::log("Backup name empty."); - } + // Reload list to reflect changes argsIn->sendingState->loadDirectoryList(); // Task is finished @@ -108,35 +109,38 @@ void overwriteBackup(sys::task *task, std::shared_ptr args) // Set status, cast args to type task->setThreadStatus("REPLACE THIS LATER"); - std::shared_ptr argIn = std::static_pointer_cast(args); - - // Get a test listing - fs::directoryListing testListing(fs::DEFAULT_SAVE_MOUNT_DEVICE); - - // So JKSV doesn't create empty folders - int saveFileCount = testListing.getListingCount(); + std::shared_ptr argsIn = std::static_pointer_cast(args); // For checking if overwriting a zip - std::string fileExtension = stringUtil::getExtensionFromString(argIn->destination); + std::string fileExtension = stringUtil::getExtensionFromString(argsIn->destination); - if (std::filesystem::is_directory(argIn->destination) && saveFileCount > 0) + if (std::filesystem::is_directory(argsIn->destination)) { // Delete backup then recreate directory - std::filesystem::remove_all(argIn->destination); - std::filesystem::create_directories(argIn->destination); - fs::io::copyDirectory(fs::DEFAULT_SAVE_MOUNT_DEVICE, argIn->destination); + // Add trailing slash first. + std::string target = argsIn->destination + "/"; + // std::filesystem::remove_all keeps crashing my switch, so I wrote my own again + fs::io::file::deleteDirectoryRecursively(target); + // Recreate whole path. + std::filesystem::create_directories(argsIn->destination); + // Create the new backup in the same folder + fs::io::file::copyDirectory(fs::DEFAULT_SAVE_MOUNT_DEVICE, argsIn->destination); } - else if (std::filesystem::is_directory(argIn->destination) == false && fileExtension == "zip" && saveFileCount > 0) + else if (std::filesystem::is_directory(argsIn->destination) == false && fileExtension == "zip") { - std::filesystem::remove(argIn->destination); - zipFile zipOut = zipOpen64(argIn->destination.c_str(), 0); - fs::io::copyDirectoryToZip(fs::DEFAULT_SAVE_MOUNT_DEVICE, zipOut); + // Just delete zip file + std::filesystem::remove(argsIn->destination); + // Create a new one with the same name + zipFile zipOut = zipOpen64(argsIn->destination.c_str(), 0); + // Create new zip with old name + fs::io::zip::copyDirectoryToZip(fs::DEFAULT_SAVE_MOUNT_DEVICE, zipOut); zipClose(zipOut, ""); } task->finished(); } // Wipes current save for game, then copies backup from SD to filesystem +// Note: Needs to be tested better some time void restoreBackup(sys::task *task, std::shared_ptr args) { if (args == nullptr) @@ -145,21 +149,45 @@ void restoreBackup(sys::task *task, std::shared_ptr args) return; } + // Set status, cast args task->setThreadStatus("REPLACE THIS LATER"); - std::shared_ptr argIn = std::static_pointer_cast(args); + std::shared_ptr argsIn = std::static_pointer_cast(args); - // Test if there are actually files in the save - fs::directoryListing testListing(argIn->source); - int saveFileCount = testListing.getListingCount(); - if (saveFileCount <= 0) + // Get extension before we try to unzip something that isn't a zip + std::string fileExtension = stringUtil::getExtensionFromString(argsIn->source); + + if(std::filesystem::is_directory(argsIn->source)) { - return; + // Add trailing slash + std::string source = argsIn->source + "/"; + // Get a test listing first to prevent writing an empty folder to save + fs::directoryListing testListing(source); + + if(testListing.getListingCount() > 0) + { + // Folder isn't empty, erase + fs::eraseSaveData(); + // Copy source to save container + fs::io::file::copyDirectoryCommit(source, fs::DEFAULT_SAVE_MOUNT_DEVICE, argsIn->journalSize); + } + } + else if(std::filesystem::is_directory(argsIn->source) == false && fileExtension == "zip") + { + // Open zip for decompressing + unzFile unzip = unzOpen64(argsIn->source.c_str()); + // Check if opening was successful and not empty first + if(unzip != NULL && fs::io::zip::zipFileIsNotEmpty(unzip)) + { + fs::io::zip::copyZipToDirectory(unzip, fs::DEFAULT_SAVE_MOUNT_DEVICE, argsIn->journalSize); + } + // Close zip + unzClose(unzip); } } void deleteBackup(sys::task *task, std::shared_ptr args) { - if(args == nullptr) + if (args == nullptr) { task->finished(); return; @@ -171,15 +199,18 @@ void deleteBackup(sys::task *task, std::shared_ptr args) // Cast args std::shared_ptr argsIn = std::static_pointer_cast(args); - if(std::filesystem::is_directory(argsIn->destination)) + if (std::filesystem::is_directory(argsIn->destination)) { - std::filesystem::remove_all(argsIn->destination); + // Doesn't have trailing slash + std::string target = argsIn->destination + "/"; + fs::io::file::deleteDirectoryRecursively(target); } else { + // Just remove it since it's probably a zip file std::filesystem::remove(argsIn->destination); } - + // Refresh to reflect changes argsIn->sendingState->loadDirectoryList(); task->finished(); @@ -243,7 +274,7 @@ void backupMenuState::update(void) ui::popMessage::newMessage(ui::strings::getString(LANG_POP_SAVE_EMPTY, 0), ui::popMessage::POPMESSAGE_DEFAULT_TICKS); } } - else if(sys::input::buttonDown(HidNpadButton_A) && selected > 0) + else if (sys::input::buttonDown(HidNpadButton_A) && selected > 0 && m_SaveListing->getListingCount() > 0) { // Selected item std::string selectedItem = m_BackupListing->getItemAt(selected - 1); @@ -269,13 +300,14 @@ void backupMenuState::update(void) // Setup confirmation arg pointer std::shared_ptr paths = std::make_shared(); paths->source = m_BackupListing->getFullPathToItemAt(selected - 1); // Need to subtract one to account for 'New' option + paths->journalSize = m_CurrentTitleInfo->getJournalSize(static_cast(m_CurrentUserSaveInfo->getSaveDataInfo().save_data_type)); // Needed to prevent commit errors // Create confirmation std::unique_ptr restoreConfirmationState = std::make_unique(confirmRestore, restoreBackup, paths); // Push it to vector jksv::pushNewState(restoreConfirmationState); } - else if(sys::input::buttonDown(HidNpadButton_X) && selected > 0) + else if (sys::input::buttonDown(HidNpadButton_X) && selected > 0) { // Get selected std::string selectedItem = m_BackupListing->getItemAt(selected - 1); diff --git a/src/appStates/titleOptionState.cpp b/src/appStates/titleOptionState.cpp new file mode 100644 index 0000000..92b4ea5 --- /dev/null +++ b/src/appStates/titleOptionState.cpp @@ -0,0 +1,81 @@ +#include "appStates/titleOptionState.hpp" +#include "filesystem/filesystem.hpp" +#include "system/input.hpp" + +enum +{ + TITLE_INFO, + ADD_TO_BLACKLIST, + CHANGE_OUTPUT_FOLDER, + OPEN_IN_FILE_MODE, + DELETE_ALL_BACKUPS, + RESET_SAVE_DATA, + DELETE_SAVE_DATA, + EXTEND_SAVE_DATA, + EXPORT_SVI +}; + +titleOptionState::titleOptionState(data::user *currentUser, data::userSaveInfo *currentUserSaveInfo, data::titleInfo *currentTitleInfo) : m_CurrentUser(currentUser), m_CurrentUserSaveInfo(currentUserSaveInfo), m_CurrentTitleInfo(currentTitleInfo) +{ + // Slide out panel + m_SlidePanel = std::make_unique("titleOptionPanel", 410, ui::PANEL_SIDE_RIGHT); + // Menu + m_OptionsMenu = std::make_unique(10, 32, 390, 20, 7); + // Setup menu + for(int i = 0; i < 9; i++) + { + m_OptionsMenu->addOpt(ui::strings::getString(LANG_TITLE_OPTIONS_MENU, i)); + } +} + +titleOptionState::~titleOptionState() { } + +void titleOptionState::update(void) +{ + // Update panel and menu + m_SlidePanel->update(); + m_OptionsMenu->update(); + + if(sys::input::buttonDown(HidNpadButton_A)) + { + switch(m_OptionsMenu->getSelected()) + { + case RESET_SAVE_DATA: + { + if(fs::mountSaveData(m_CurrentUserSaveInfo->getSaveDataInfo())) + { + fs::io::file::deleteDirectoryRecursively(fs::DEFAULT_SAVE_MOUNT_DEVICE); + fs::commitSaveData(); + fs::unmountSaveData(); + } + } + break; + + default: + { + ui::popMessage::newMessage("Not implemented yet...", ui::popMessage::POPMESSAGE_DEFAULT_TICKS); + } + break; + } + } + else if(sys::input::buttonDown(HidNpadButton_B)) + { + m_SlidePanel->closePanel(); + } + else if(m_SlidePanel->isClosed()) + { + appState::deactivateState(); + } +} + +void titleOptionState::render(void) +{ + // Get panel render target + SDL_Texture *panelTarget = m_SlidePanel->getPanelRenderTarget(); + // Clear it + graphics::textureClear(panelTarget, COLOR_SLIDE_PANEL_TARGET); + // Render menu to panel + m_OptionsMenu->render(panelTarget); + // Render panel to framebuffer + m_SlidePanel->render(); +} \ No newline at end of file diff --git a/src/appStates/titleSelectionState.cpp b/src/appStates/titleSelectionState.cpp index 09ce2c1..21db7c6 100644 --- a/src/appStates/titleSelectionState.cpp +++ b/src/appStates/titleSelectionState.cpp @@ -1,5 +1,7 @@ +#include #include "appStates/titleSelectionState.hpp" #include "appStates/backupMenuState.hpp" +#include "appStates/titleOptionState.hpp" #include "filesystem/filesystem.hpp" #include "graphics/graphics.hpp" #include "system/input.hpp" @@ -42,6 +44,18 @@ void titleSelectionState::update(void) logger::log("Error mounting save for %016lX", currentUserSaveInfo->getTitleID()); } } + else if(sys::input::buttonDown(HidNpadButton_X)) + { + // Get some stuff needed + int selected = m_TitleSelection->getSelected(); + data::userSaveInfo *currentUserSaveInfo = m_CurrentUser->getUserSaveInfoAt(selected); + data::titleInfo *currentTitleInfo = data::getTitleInfoByTitleID(currentUserSaveInfo->getTitleID()); + + // Create option state + std::unique_ptr optionState = std::make_unique(m_CurrentUser, currentUserSaveInfo, currentTitleInfo); + // Push it + jksv::pushNewState(optionState); + } else if(sys::input::buttonDown(HidNpadButton_B)) { appState::deactivateState(); diff --git a/src/data/titleInfo.cpp b/src/data/titleInfo.cpp index 10ea0f8..4e451d8 100644 --- a/src/data/titleInfo.cpp +++ b/src/data/titleInfo.cpp @@ -1,6 +1,7 @@ #include #include #include +#include "filesystem/filesystem.hpp" #include "data/data.hpp" #include "graphics/graphics.hpp" #include "stringUtil.hpp" @@ -9,9 +10,13 @@ data::titleInfo::titleInfo(const uint64_t &titleID) : m_TitleID(titleID) { + // This is the size of the control data. Used for getting icon size uint64_t nsAppControlSize = 0; + // Icon size size_t appIconSize = 0; + // Actual control data. std::unique_ptr nsAppConrolData = std::make_unique(); + // Title ID in hex format std::string titleIDHexString = stringUtil::getFormattedString("%016lX", m_TitleID); Result nsAppControlRes = nsGetApplicationControlData(NsApplicationControlSource_Storage, m_TitleID, nsAppConrolData.get(), sizeof(NsApplicationControlData), &nsAppControlSize); @@ -38,18 +43,23 @@ data::titleInfo::titleInfo(const uint64_t &titleID) : m_TitleID(titleID) std::string data::titleInfo::getTitle(void) { + // Try to get title from NACP NacpLanguageEntry *entry; nacpGetLanguageEntry(&m_Nacp, &entry); + // If it fails, return title ID as hex string if(entry == NULL) { return stringUtil::getFormattedString("%016lX", m_TitleID); } + // Return the title return std::string(entry->name); } std::string data::titleInfo::getPathSafeTitle(void) { + // Try to get a pathSafeTitle std::string pathSafeTitle = stringUtil::getPathSafeString(data::titleInfo::getTitle()); + // If that fails, it will be empty. Just return ID hex string if(pathSafeTitle.empty()) { return stringUtil::getFormattedString("%016lX", m_TitleID); @@ -60,4 +70,42 @@ std::string data::titleInfo::getPathSafeTitle(void) SDL_Texture *data::titleInfo::getIcon(void) { return m_Icon; +} + +uint64_t data::titleInfo::getJournalSize(const FsSaveDataType &saveType) +{ + // Journal size to return + uint64_t journalSize = 0; + + // Different save types have different journal limits + switch(saveType) + { + case FsSaveDataType_Account: + { + // Just this is safe for accounts + journalSize = m_Nacp.user_account_save_data_journal_size; + } + break; + + case FsSaveDataType_Bcat: + { + // Let's just hope this works all the time + journalSize = m_Nacp.bcat_delivery_cache_storage_size; + } + break; + + case FsSaveDataType_Device: + { + journalSize = m_Nacp.device_save_data_journal_size; + } + break; + + default: + { + // This should make it commit on every write. Not the fastest, but safest. + journalSize = 0; + } + break; + } + return journalSize; } \ No newline at end of file diff --git a/src/filesystem/filesystem.cpp b/src/filesystem/filesystem.cpp index 776f6ed..0fe3cf1 100644 --- a/src/filesystem/filesystem.cpp +++ b/src/filesystem/filesystem.cpp @@ -101,4 +101,10 @@ void fs::unmountSaveData(void) void fs::commitSaveData(void) { fsdevCommitDevice(DEFAULT_SAVE_MOUNT_DEVICE); +} + +void fs::eraseSaveData(void) +{ + fs::io::file::deleteDirectoryRecursively(fs::DEFAULT_SAVE_MOUNT_DEVICE); + fs::commitSaveData(); } \ No newline at end of file diff --git a/src/filesystem/io.cpp b/src/filesystem/io.cpp index 1f670d9..8319c74 100644 --- a/src/filesystem/io.cpp +++ b/src/filesystem/io.cpp @@ -11,20 +11,20 @@ #include "log.hpp" // Read thread function -void fs::io::readThreadFunction(const std::string &source, std::shared_ptr sharedStruct) +void fs::io::file::readThreadFunction(const std::string &source, std::shared_ptr sharedStruct) { // Bytes read uint64_t bytesRead = 0; // File stream std::ifstream sourceFile(source, std::ios::binary); // Local buffer for reading - std::vector localBuffer(fs::io::FILE_BUFFER_SIZE); + std::vector localBuffer(fs::io::file::FILE_BUFFER_SIZE); // Loop until file is fully read while(bytesRead < sharedStruct->fileSize) { // Read to localBuffer - sourceFile.read(localBuffer.data(), fs::io::FILE_BUFFER_SIZE); + sourceFile.read(localBuffer.data(), fs::io::file::FILE_BUFFER_SIZE); // Wait for shared buffer to be emptied by write thread std::unique_lock sharedBufferLock(sharedStruct->bufferLock); @@ -48,7 +48,7 @@ void fs::io::readThreadFunction(const std::string &source, std::shared_ptr sharedStruct) +void fs::io::file::writeThreadFunction(const std::string &destination, std::shared_ptr sharedStruct) { // Keep track of bytes written uint64_t bytesWritten = 0; @@ -105,50 +105,50 @@ void fs::io::writeThreadFunction(const std::string &destination, std::shared_ptr fs::commitSaveData(); } -int fs::io::getFileSize(const std::string &filePath) +int fs::io::file::getFileSize(const std::string &filePath) { std::ifstream file(filePath, std::ios::binary); file.seekg(std::ios::end); return file.tellg(); } -void fs::io::copyFile(const std::string &source, const std::string &destination) +void fs::io::file::copyFile(const std::string &source, const std::string &destination) { // Create shared thread struct std::shared_ptr sharedStruct = std::make_shared(); // Everything else is set by default - sharedStruct->fileSize = fs::io::getFileSize(source); + sharedStruct->fileSize = fs::io::file::getFileSize(source); // Read & write thread - std::thread readThread(fs::io::readThreadFunction, source, sharedStruct); - std::thread writeThread(fs::io::writeThreadFunction, destination, sharedStruct); + std::thread readThread(fs::io::file::readThreadFunction, source, sharedStruct); + std::thread writeThread(fs::io::file::writeThreadFunction, destination, sharedStruct); // Wait for finish readThread.join(); writeThread.join(); } -void fs::io::copyFileCommit(const std::string &source, const std::string &destination, const uint64_t &journalSize) +void fs::io::file::copyFileCommit(const std::string &source, const std::string &destination, const uint64_t &journalSize) { // Shared struct std::shared_ptr sharedStruct = std::make_shared(); // Set vars - sharedStruct->fileSize = fs::io::getFileSize(source); + sharedStruct->fileSize = fs::io::file::getFileSize(source); sharedStruct->commitWrite = true; sharedStruct->journalSize = journalSize; // Threads - std::thread readThread(fs::io::readThreadFunction, source, sharedStruct); - std::thread writeThread(fs::io::writeThreadFunction, destination, sharedStruct); + std::thread readThread(fs::io::file::readThreadFunction, source, sharedStruct); + std::thread writeThread(fs::io::file::writeThreadFunction, destination, sharedStruct); // Wait readThread.join(); writeThread.join(); } -void fs::io::copyDirectory(const std::string &source, const std::string &destination) +void fs::io::file::copyDirectory(const std::string &source, const std::string &destination) { fs::directoryListing list(source); @@ -160,18 +160,18 @@ void fs::io::copyDirectory(const std::string &source, const std::string &destina std::string newSource = source + list.getItemAt(i) + "/"; std::string newDestination = destination + list.getItemAt(i) + "/"; std::filesystem::create_directories(newDestination); - fs::io::copyDirectory(newSource, newDestination); + fs::io::file::copyDirectory(newSource, newDestination); } else { std::string fullSource = source + list.getItemAt(i); std::string fullDestination = destination + list.getItemAt(i); - fs::io::copyFile(fullSource, fullDestination); + fs::io::file::copyFile(fullSource, fullDestination); } } } -void fs::io::copyDirectoryCommit(const std::string &source, const std::string &destination, const uint64_t &journalSize) +void fs::io::file::copyDirectoryCommit(const std::string &source, const std::string &destination, const uint64_t &journalSize) { fs::directoryListing list(source); @@ -183,13 +183,41 @@ void fs::io::copyDirectoryCommit(const std::string &source, const std::string &d std::string newSource = source + list.getItemAt(i) + "/"; std::string newDestination = destination + list.getItemAt(i) + "/"; std::filesystem::create_directory(newDestination.substr(0, newDestination.npos - 1)); - fs::io::copyDirectoryCommit(newSource, newDestination, journalSize); + fs::io::file::copyDirectoryCommit(newSource, newDestination, journalSize); } else { std::string fullSource = source + list.getItemAt(i); std::string fullDestination = destination + list.getItemAt(i); - fs::io::copyFileCommit(fullSource, fullDestination, journalSize); + fs::io::file::copyFileCommit(fullSource, fullDestination, journalSize); } } +} + +void fs::io::file::deleteDirectoryRecursively(const std::string &target) +{ + // Get directory listing + fs::directoryListing listing(target); + // Get item count + int listingCount = listing.getListingCount(); + // Loop through + for(int i = 0; i < listingCount; i++) + { + if(listing.itemAtIsDirectory(i)) + { + // New target path + std::string newTarget = listing.getFullPathToItemAt(i) + "/"; + // Call self to continue + fs::io::file::deleteDirectoryRecursively(newTarget); + // Delete directory + std::filesystem::remove(listing.getFullPathToItemAt(i)); + } + else + { + // Just delete file + std::filesystem::remove(listing.getFullPathToItemAt(i)); + } + } + // Finally delete last directory + std::filesystem::remove(target); } \ No newline at end of file diff --git a/src/filesystem/zip.cpp b/src/filesystem/zip.cpp index 925cf9b..59ffec1 100644 --- a/src/filesystem/zip.cpp +++ b/src/filesystem/zip.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -11,7 +12,7 @@ #define ZIP_BUFFER_SIZE 0x80000 -void fs::io::copyDirectoryToZip(const std::string &source, zipFile zip) +void fs::io::zip::copyDirectoryToZip(const std::string &source, zipFile zip) { // Source file listing fs::directoryListing listing(source); @@ -23,7 +24,7 @@ void fs::io::copyDirectoryToZip(const std::string &source, zipFile zip) if(listing.itemAtIsDirectory(i)) { std::string newSource = source + listing.getItemAt(i) + "/"; - fs::io::copyDirectoryToZip(newSource, zip); + fs::io::zip::copyDirectoryToZip(newSource, zip); } else { @@ -86,12 +87,12 @@ void fs::io::copyDirectoryToZip(const std::string &source, zipFile zip) } } -void fs::io::copyZipToDirectory(unzFile unzip, const std::string &destination, const uint64_t &journalSize) +void fs::io::zip::copyZipToDirectory(unzFile unzip, const std::string &destination, const uint64_t &journalSize) { // Buffer to decompress to std::vector buffer(ZIP_BUFFER_SIZE); // Filename of file being decompressed - char currentFileName[FS_MAX_PATH]; + std::array currentFileName; // This contains CRC etc. I don't use it but it's needed. unz_file_info64 unzipFileInfo; // Keep track of journaling space commits @@ -100,11 +101,11 @@ void fs::io::copyZipToDirectory(unzFile unzip, const std::string &destination, c uint32_t bytesRead = 0; do { - unzGetCurrentFileInfo64(unzip, &unzipFileInfo, currentFileName, FS_MAX_PATH, NULL, 0, NULL, 0); + unzGetCurrentFileInfo64(unzip, &unzipFileInfo, currentFileName.data(), FS_MAX_PATH, NULL, 0, NULL, 0); if(unzOpenCurrentFile(unzip) == UNZ_OK) { // Full path to output - std::string fullDestination = destination + currentFileName; + std::string fullDestination = destination + currentFileName.data(); // Make sure full path to exists std::filesystem::create_directories(fullDestination.substr(0, fullDestination.find_last_of('/') + 1)); // File stream @@ -135,3 +136,32 @@ void fs::io::copyZipToDirectory(unzFile unzip, const std::string &destination, c fs::commitSaveData(); } +uint64_t fs::io::zip::getZipTotalFileSize(unzFile unzip) +{ + // Total size to return + uint64_t totalSize = 0; + if(unzGoToFirstFile(unzip) == UNZ_OK) + { + // Needed to get size + unz_file_info64 fileInfo = { 0 }; + std::array filename; + + // Loop through zip file + do + { + // Get info + unzGetCurrentFileInfo64(unzip, &fileInfo, filename.data(), FS_MAX_PATH, NULL, 0, NULL, 0); + // Add size to total size + totalSize += fileInfo.uncompressed_size; + } while (unzGoToNextFile(unzip) != UNZ_END_OF_LIST_OF_FILE); + // Reset to beginning of zip + unzGoToFirstFile(unzip); + } + return totalSize; +} + +bool fs::io::zip::zipFileIsNotEmpty(unzFile unzip) +{ + // Just return if we can get first file + return unzGoToFirstFile(unzip) == UNZ_OK; +} \ No newline at end of file