mirror of
https://github.com/J-D-K/JKSV.git
synced 2026-04-23 09:07:38 -05:00
Pop messages & revisions
This commit is contained in:
parent
73bf730f73
commit
8f30cada67
|
|
@ -35,6 +35,8 @@ class backupMenuState : public appState
|
|||
std::unique_ptr<ui::slidePanel> m_BackupPanel;
|
||||
// Folder listing
|
||||
std::unique_ptr<fs::directoryListing> m_BackupListing;
|
||||
// Listing to make sure there is a save
|
||||
std::unique_ptr<fs::directoryListing> m_SaveListing;
|
||||
// Folder menu
|
||||
std::unique_ptr<ui::menu> m_BackupMenu;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
#pragma once
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
|
||||
|
|
@ -6,6 +9,29 @@ 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
|
||||
{
|
||||
// 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<char> 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
|
||||
|
|
@ -15,7 +41,10 @@ namespace fs
|
|||
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
|
||||
// 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<threadStruct> sharedStruct);
|
||||
void writeThreadFunction(const std::string &destination, std::shared_ptr<threadStruct> sharedStruct);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,10 +4,16 @@
|
|||
|
||||
namespace jksv
|
||||
{
|
||||
// Inits all services/code needed
|
||||
bool init(void);
|
||||
// Exits and frees memory needed
|
||||
void exit(void);
|
||||
// Updates logic/states
|
||||
void update(void);
|
||||
// Renders states
|
||||
void render(void);
|
||||
// Returns if JKSV is still running
|
||||
const bool isRunning(void);
|
||||
// Pushes newAppState to back of vector
|
||||
void pushNewState(std::unique_ptr<appState> &newAppState);
|
||||
}
|
||||
20
inc/system/timer.hpp
Normal file
20
inc/system/timer.hpp
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace sys
|
||||
{
|
||||
class timer
|
||||
{
|
||||
public:
|
||||
// Inits timer with current ticks
|
||||
timer(const uint32_t &triggerTicks);
|
||||
// Returns if triggerTicks has been reached.
|
||||
bool triggered(void);
|
||||
|
||||
private:
|
||||
// Starting tick count
|
||||
uint32_t m_StartingTicks = 0;
|
||||
// Trigger tick count
|
||||
uint32_t m_TriggerTicks = 0;
|
||||
};
|
||||
}
|
||||
|
|
@ -81,6 +81,22 @@
|
|||
|
||||
#define LANG_THREAD_COMPRESSING_UPLOAD "threadStatusCompressingSaveForUpload"
|
||||
|
||||
// Popup message strings
|
||||
#define LANG_POP_CPU_BOOST "popCPUBoostEnabled"
|
||||
#define LANG_POP_ERROR_COMMITING "popErrorCommitingFile"
|
||||
#define LANG_POP_ZIP_EMPTY "popZipIsEmpty"
|
||||
#define LANG_POP_SAVE_EMPTY "popSaveIsEmpty"
|
||||
#define LANG_POP_FOLDER_EMPTY "popFolderIsEmpty"
|
||||
#define LANG_POP_PROCESS_SHUTDOWN "popProcessShutdown"
|
||||
#define LANG_POP_ADDED_PATH_FILTER "popAddedPathToFilter"
|
||||
#define LANG_POP_CHANGED_OUTPUT_DIR "popChangedOutputFolder"
|
||||
#define LANG_POP_CHANGED_OUTPUT_ERROR "popChangedOutputError"
|
||||
#define LANG_POP_TRASH_EMPTIED "popTrashEmptied"
|
||||
#define LANG_POP_SVI_EXPORTED "popSVIExported"
|
||||
#define LANG_POP_DRIVE_STARTED "popDriveStarted"
|
||||
#define LANG_POP_DRIVE_FAILED "popDriveFailed"
|
||||
#define LANG_POP_DRIVE_INACTIVE "popDriveNotActive"
|
||||
|
||||
namespace ui
|
||||
{
|
||||
namespace strings
|
||||
|
|
|
|||
33
inc/ui/popMessage.hpp
Normal file
33
inc/ui/popMessage.hpp
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "system/timer.hpp"
|
||||
|
||||
namespace ui
|
||||
{
|
||||
namespace popMessage
|
||||
{
|
||||
// This is the default and only used timer count for these messages.
|
||||
static const int POPMESSAGE_DEFAULT_TICKS = 2500;
|
||||
|
||||
// Actual message struct
|
||||
typedef struct
|
||||
{
|
||||
// Message string
|
||||
std::string message;
|
||||
// Timer for expiration
|
||||
std::unique_ptr<sys::timer> messageTimer;
|
||||
// Width of rectangle to render
|
||||
int rectangleWidth = 0;
|
||||
// Current Y
|
||||
int y = 0;
|
||||
} pMessage;
|
||||
|
||||
// Adds a message to the queue to be processed. tickLength is how long in ticks for it to be displayed.
|
||||
void newMessage(const std::string &newMessage, const int &tickLength);
|
||||
// Updates the message queue and vector
|
||||
void update(void);
|
||||
// Renders messages to screen
|
||||
void render(void);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
#include "ui/progressBar.hpp"
|
||||
#include "ui/slidePanel.hpp"
|
||||
#include "ui/titleSelection.hpp"
|
||||
#include "ui/popMessage.hpp"
|
||||
|
||||
namespace ui
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,12 +5,13 @@
|
|||
#include "config.hpp"
|
||||
#include "system/input.hpp"
|
||||
#include "graphics/graphics.hpp"
|
||||
#include "appstates/backupMenuState.hpp"
|
||||
#include "appStates/backupMenuState.hpp"
|
||||
#include "appStates/taskState.hpp"
|
||||
#include "filesystem/filesystem.hpp"
|
||||
#include "system/task.hpp"
|
||||
#include "stringUtil.hpp"
|
||||
#include "jksv.hpp"
|
||||
#include "log.hpp"
|
||||
|
||||
// This is for backup task
|
||||
struct backupArgs : sys::taskArgs
|
||||
|
|
@ -25,7 +26,7 @@ struct backupArgs : sys::taskArgs
|
|||
// This is shared by overwrite and restore
|
||||
struct pathArg : sys::taskArgs
|
||||
{
|
||||
std::string path;
|
||||
std::string source, destination;
|
||||
};
|
||||
|
||||
// These are the functions used for tasks
|
||||
|
|
@ -33,7 +34,7 @@ struct pathArg : sys::taskArgs
|
|||
void createNewBackup(sys::task *task, std::shared_ptr<sys::taskArgs> args)
|
||||
{
|
||||
// Make sure we weren't passed nullptr
|
||||
if(args == nullptr)
|
||||
if (args == nullptr)
|
||||
{
|
||||
task->finished();
|
||||
return;
|
||||
|
|
@ -45,15 +46,15 @@ void createNewBackup(sys::task *task, std::shared_ptr<sys::taskArgs> args)
|
|||
|
||||
// Process shortcuts or get name for backup
|
||||
std::string backupName;
|
||||
if(sys::input::buttonHeld(HidNpadButton_R) || config::getByKey(CONFIG_AUTONAME_BACKUPS))
|
||||
if (sys::input::buttonHeld(HidNpadButton_R) || config::getByKey(CONFIG_AUTONAME_BACKUPS))
|
||||
{
|
||||
backupName = argsIn->currentUser->getPathSafeUsername() + " - " + stringUtil::getTimeAndDateString(stringUtil::DATE_FORMAT_YMD);
|
||||
}
|
||||
else if(sys::input::buttonHeld(HidNpadButton_L))
|
||||
else if (sys::input::buttonHeld(HidNpadButton_L))
|
||||
{
|
||||
backupName = argsIn->currentUser->getPathSafeUsername() + " - " + stringUtil::getTimeAndDateString(stringUtil::DATE_FORMAT_YDM);
|
||||
}
|
||||
else if(sys::input::buttonHeld(HidNpadButton_ZL))
|
||||
else if (sys::input::buttonHeld(HidNpadButton_ZL))
|
||||
{
|
||||
backupName = argsIn->currentUser->getPathSafeUsername() + " - " + stringUtil::getTimeAndDateString(stringUtil::DATE_FORMAT_ASC);
|
||||
}
|
||||
|
|
@ -65,13 +66,13 @@ void createNewBackup(sys::task *task, std::shared_ptr<sys::taskArgs> args)
|
|||
}
|
||||
|
||||
// If it's not empty, check ZIP option or extension. Normally I avoid nested ifs when possible but...
|
||||
if(backupName.empty() == false)
|
||||
if (backupName.empty() == false)
|
||||
{
|
||||
// Get extension and full path
|
||||
std::string extension = stringUtil::getExtensionFromString(backupName);
|
||||
std::string path = argsIn->outputBasePath + backupName + "/";
|
||||
|
||||
if(config::getByKey(CONFIG_USE_ZIP) || extension == "zip")
|
||||
if (config::getByKey(CONFIG_USE_ZIP) || extension == "zip")
|
||||
{
|
||||
zipFile zipOut = zipOpen64(path.c_str(), 0);
|
||||
fs::io::copyDirectoryToZip(fs::DEFAULT_SAVE_MOUNT_DEVICE, zipOut);
|
||||
|
|
@ -83,6 +84,9 @@ void createNewBackup(sys::task *task, std::shared_ptr<sys::taskArgs> args)
|
|||
fs::io::copyDirectory(fs::DEFAULT_SAVE_MOUNT_DEVICE, path);
|
||||
}
|
||||
}
|
||||
{
|
||||
logger::log("Backup name empty.");
|
||||
}
|
||||
// Reload list to reflect changes
|
||||
argsIn->sendingState->loadDirectoryList();
|
||||
// Task is finished
|
||||
|
|
@ -93,7 +97,7 @@ void createNewBackup(sys::task *task, std::shared_ptr<sys::taskArgs> args)
|
|||
void overwriteBackup(sys::task *task, std::shared_ptr<sys::taskArgs> args)
|
||||
{
|
||||
// Bail if no args present
|
||||
if(args == nullptr)
|
||||
if (args == nullptr)
|
||||
{
|
||||
task->finished();
|
||||
return;
|
||||
|
|
@ -110,20 +114,20 @@ void overwriteBackup(sys::task *task, std::shared_ptr<sys::taskArgs> args)
|
|||
int saveFileCount = testListing.getListingCount();
|
||||
|
||||
// For checking if overwriting a zip
|
||||
std::string fileExtension = stringUtil::getExtensionFromString(argIn->path);
|
||||
std::string fileExtension = stringUtil::getExtensionFromString(argIn->destination);
|
||||
|
||||
if(std::filesystem::is_directory(argIn->path) && saveFileCount > 0)
|
||||
if (std::filesystem::is_directory(argIn->destination) && saveFileCount > 0)
|
||||
{
|
||||
// Delete backup then recreate directory
|
||||
std::filesystem::remove_all(argIn->path);
|
||||
std::filesystem::create_directory(argIn->path);
|
||||
fs::io::copyDirectory(fs::DEFAULT_SAVE_MOUNT_DEVICE, argIn->path);
|
||||
std::filesystem::remove_all(argIn->destination);
|
||||
std::filesystem::create_directories(argIn->destination);
|
||||
fs::io::copyDirectory(fs::DEFAULT_SAVE_MOUNT_DEVICE, argIn->destination);
|
||||
}
|
||||
else if(std::filesystem::is_directory(argIn->path) == false && fileExtension == "zip" && saveFileCount > 0)
|
||||
else if (std::filesystem::is_directory(argIn->destination) == false && fileExtension == "zip" && saveFileCount > 0)
|
||||
{
|
||||
std::filesystem::remove(argIn->path);
|
||||
zipFile zipOut = zipOpen64(argIn->path.c_str(), 0);
|
||||
fs::io::copyDirectoryToZip(argIn->path, zipOut);
|
||||
std::filesystem::remove(argIn->destination);
|
||||
zipFile zipOut = zipOpen64(argIn->destination.c_str(), 0);
|
||||
fs::io::copyDirectoryToZip(fs::DEFAULT_SAVE_MOUNT_DEVICE, zipOut);
|
||||
zipClose(zipOut, "");
|
||||
}
|
||||
task->finished();
|
||||
|
|
@ -132,7 +136,7 @@ void overwriteBackup(sys::task *task, std::shared_ptr<sys::taskArgs> args)
|
|||
// Wipes current save for game, then copies backup from SD to filesystem
|
||||
void restoreBackup(sys::task *task, std::shared_ptr<sys::taskArgs> args)
|
||||
{
|
||||
if(args == nullptr)
|
||||
if (args == nullptr)
|
||||
{
|
||||
task->finished();
|
||||
return;
|
||||
|
|
@ -142,9 +146,9 @@ void restoreBackup(sys::task *task, std::shared_ptr<sys::taskArgs> args)
|
|||
std::shared_ptr<pathArg> argIn = std::static_pointer_cast<pathArg>(args);
|
||||
|
||||
// Test if there are actually files in the save
|
||||
fs::directoryListing testListing(argIn->path);
|
||||
fs::directoryListing testListing(argIn->source);
|
||||
int saveFileCount = testListing.getListingCount();
|
||||
if(saveFileCount <= 0)
|
||||
if (saveFileCount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -166,34 +170,48 @@ backupMenuState::backupMenuState(data::user *currentUser, data::userSaveInfo *cu
|
|||
m_BackupMenu = std::make_unique<ui::menu>(10, 4, panelWidth + 44, 18, 7);
|
||||
// Directory listing of backups
|
||||
m_BackupListing = std::make_unique<fs::directoryListing>(m_OutputBasePath);
|
||||
// Directory listing of save
|
||||
m_SaveListing = std::make_unique<fs::directoryListing>(fs::DEFAULT_SAVE_MOUNT_DEVICE);
|
||||
|
||||
backupMenuState::loadDirectoryList();
|
||||
}
|
||||
|
||||
backupMenuState::~backupMenuState()
|
||||
backupMenuState::~backupMenuState()
|
||||
{
|
||||
fs::unmountSaveData();
|
||||
}
|
||||
|
||||
void backupMenuState::update(void)
|
||||
{
|
||||
// Update panel and menu
|
||||
m_BackupPanel->update();
|
||||
|
||||
m_BackupMenu->update();
|
||||
|
||||
// Get selected item to check what's selected
|
||||
int selected = m_BackupMenu->getSelected();
|
||||
if(sys::input::buttonDown(HidNpadButton_A) && selected == 0)
|
||||
if (sys::input::buttonDown(HidNpadButton_A) && selected == 0)
|
||||
{
|
||||
// Data to send to task
|
||||
std::shared_ptr<backupArgs> backupTaskArgs = std::make_shared<backupArgs>();
|
||||
backupTaskArgs->currentUser = m_CurrentUser;
|
||||
backupTaskArgs->currentTitleInfo = m_CurrentTitleInfo;
|
||||
backupTaskArgs->outputBasePath = m_OutputBasePath;
|
||||
backupTaskArgs->sendingState = this;
|
||||
// Check to make sure save data isn't empty before wasting time and space
|
||||
if (m_BackupListing->getListingCount() > 0)
|
||||
{
|
||||
// Data to send to task
|
||||
std::shared_ptr<backupArgs> backupTaskArgs = std::make_shared<backupArgs>();
|
||||
backupTaskArgs->currentUser = m_CurrentUser;
|
||||
backupTaskArgs->currentTitleInfo = m_CurrentTitleInfo;
|
||||
backupTaskArgs->outputBasePath = m_OutputBasePath;
|
||||
backupTaskArgs->sendingState = this;
|
||||
|
||||
// Task state
|
||||
std::unique_ptr<appState> backupTask = std::make_unique<taskState> (createNewBackup, backupTaskArgs);
|
||||
jksv::pushNewState(backupTask);
|
||||
// Task state
|
||||
std::unique_ptr<appState> backupTask = std::make_unique<taskState>(createNewBackup, backupTaskArgs);
|
||||
jksv::pushNewState(backupTask);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui::popMessage::newMessage(ui::strings::getString(LANG_POP_SAVE_EMPTY, 0), ui::popMessage::POPMESSAGE_DEFAULT_TICKS);
|
||||
}
|
||||
}
|
||||
else if (sys::input::buttonDown(HidNpadButton_Y))
|
||||
{
|
||||
}
|
||||
else if (sys::input::buttonDown(HidNpadButton_B))
|
||||
{
|
||||
|
|
@ -223,7 +241,7 @@ void backupMenuState::loadDirectoryList(void)
|
|||
|
||||
int listingCount = m_BackupListing->getListingCount();
|
||||
m_BackupMenu->addOpt(ui::strings::getString(LANG_FOLDER_MENU_NEW, 0));
|
||||
for(int i = 0; i < listingCount; i++)
|
||||
for (int i = 0; i < listingCount; i++)
|
||||
{
|
||||
m_BackupMenu->addOpt(m_BackupListing->getItemAt(i));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include "system/input.hpp"
|
||||
#include "ui/ui.hpp"
|
||||
#include "system/task.hpp"
|
||||
#include "stringUtil.hpp"
|
||||
#include "log.hpp"
|
||||
|
||||
// Tasks
|
||||
|
|
@ -61,7 +62,8 @@ void mainMenuState::update(void)
|
|||
}
|
||||
else
|
||||
{
|
||||
logger::log("No titles found for user %s", selectedUser->getUsername().c_str());
|
||||
std::string noSavesMessage = stringUtil::getFormattedString(ui::strings::getCString(LANG_SAVEDATA_NONE_FOUND, 0), selectedUser->getUsername().c_str());
|
||||
ui::popMessage::newMessage(noSavesMessage, ui::popMessage::POPMESSAGE_DEFAULT_TICKS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,52 +6,25 @@
|
|||
#include <condition_variable>
|
||||
#include <vector>
|
||||
#include "filesystem/filesystem.hpp"
|
||||
#include "filesystem/directoryListing.hpp"
|
||||
#include "filesystem/io.hpp"
|
||||
#include "system/task.hpp"
|
||||
#include "system/taskArgs.hpp"
|
||||
#include "log.hpp"
|
||||
|
||||
// Generic error for opening files
|
||||
static const char *s_ErrorOpening = "Error opening files for reading and/or writing.";
|
||||
|
||||
// Buffer size for read/write threads
|
||||
#define FILE_BUFFER_SIZE 0x400000
|
||||
|
||||
// Struct shared for reading/writing
|
||||
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<char> buffer;
|
||||
// Whether file needs to be commited on write
|
||||
bool commitWrite = false;
|
||||
// Journal size
|
||||
uint64_t journalSize = 0;
|
||||
} threadStruct;
|
||||
|
||||
// Read thread function
|
||||
void readFunction(const std::string &source, std::shared_ptr<threadStruct> sharedStruct)
|
||||
void fs::io::readThreadFunction(const std::string &source, std::shared_ptr<threadStruct> sharedStruct)
|
||||
{
|
||||
// Bytes read
|
||||
uint64_t bytesRead = 0;
|
||||
// File stream
|
||||
std::ifstream sourceFile(source, std::ios::binary);
|
||||
// Local buffer for reading
|
||||
std::vector<char> localBuffer(FILE_BUFFER_SIZE);
|
||||
std::vector<char> localBuffer(fs::io::FILE_BUFFER_SIZE);
|
||||
|
||||
// Loop until file is fully read
|
||||
while(bytesRead < sharedStruct->fileSize)
|
||||
{
|
||||
// Read to localBuffer
|
||||
sourceFile.read(localBuffer.data(), FILE_BUFFER_SIZE);
|
||||
sourceFile.read(localBuffer.data(), fs::io::FILE_BUFFER_SIZE);
|
||||
|
||||
// Wait for shared buffer to be emptied by write thread
|
||||
std::unique_lock sharedBufferLock(sharedStruct->bufferLock);
|
||||
|
|
@ -75,7 +48,7 @@ void readFunction(const std::string &source, std::shared_ptr<threadStruct> share
|
|||
}
|
||||
|
||||
// File writing thread function
|
||||
void writeFunction(const std::string &destination, std::shared_ptr<threadStruct> sharedStruct)
|
||||
void fs::io::writeThreadFunction(const std::string &destination, std::shared_ptr<threadStruct> sharedStruct)
|
||||
{
|
||||
// Keep track of bytes written
|
||||
uint64_t bytesWritten = 0;
|
||||
|
|
@ -96,6 +69,9 @@ void writeFunction(const std::string &destination, std::shared_ptr<threadStruct>
|
|||
// Copy shared buffer to localBuffer so read thread can continue
|
||||
localBuffer.assign(sharedStruct->buffer.begin(), sharedStruct->buffer.end());
|
||||
|
||||
// Update offet
|
||||
sharedStruct->currentOffset += localBuffer.size();
|
||||
|
||||
// Signal
|
||||
sharedStruct->bufferIsFull = false;
|
||||
|
||||
|
|
@ -145,8 +121,8 @@ void fs::io::copyFile(const std::string &source, const std::string &destination)
|
|||
sharedStruct->fileSize = fs::io::getFileSize(source);
|
||||
|
||||
// Read & write thread
|
||||
std::thread readThread(readFunction, source, sharedStruct);
|
||||
std::thread writeThread(writeFunction, destination, sharedStruct);
|
||||
std::thread readThread(fs::io::readThreadFunction, source, sharedStruct);
|
||||
std::thread writeThread(fs::io::writeThreadFunction, destination, sharedStruct);
|
||||
|
||||
// Wait for finish
|
||||
readThread.join();
|
||||
|
|
@ -164,8 +140,8 @@ void fs::io::copyFileCommit(const std::string &source, const std::string &destin
|
|||
sharedStruct->journalSize = journalSize;
|
||||
|
||||
// Threads
|
||||
std::thread readThread(readFunction, source, sharedStruct);
|
||||
std::thread writeThread(writeFunction, destination, sharedStruct);
|
||||
std::thread readThread(fs::io::readThreadFunction, source, sharedStruct);
|
||||
std::thread writeThread(fs::io::writeThreadFunction, destination, sharedStruct);
|
||||
|
||||
// Wait
|
||||
readThread.join();
|
||||
|
|
@ -184,7 +160,6 @@ 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);
|
||||
//std::filesystem::create_directory(newDestination.substr(0, newDestination.npos - 1));
|
||||
fs::io::copyDirectory(newSource, newDestination);
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ void fs::io::copyDirectoryToZip(const std::string &source, zipFile zip)
|
|||
.tm_hour = local->tm_hour,
|
||||
.tm_mday = local->tm_mday,
|
||||
.tm_mon = local->tm_mon,
|
||||
.tm_year = 1900 + local->tm_year
|
||||
.tm_year = local->tm_year + 1900
|
||||
},
|
||||
.dosDate = 0,
|
||||
.internal_fa = 0,
|
||||
|
|
|
|||
12
src/jksv.cpp
12
src/jksv.cpp
|
|
@ -37,12 +37,14 @@ bool jksv::init(void)
|
|||
|
||||
// Config doesn't return anything
|
||||
config::init();
|
||||
|
||||
// Filesystem
|
||||
if(fs::init() == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// ui doesn't return anything
|
||||
// ui just loads some things
|
||||
ui::init();
|
||||
|
||||
// Load data from system
|
||||
|
|
@ -93,6 +95,9 @@ void jksv::update(void)
|
|||
|
||||
// Update back of vector
|
||||
s_AppStateVector.back()->update();
|
||||
|
||||
// Update pop up messages
|
||||
ui::popMessage::update();
|
||||
}
|
||||
|
||||
void jksv::render(void)
|
||||
|
|
@ -103,11 +108,16 @@ void jksv::render(void)
|
|||
graphics::renderLine(NULL, 30, 648, 1250, 648, COLOR_WHITE);
|
||||
graphics::textureRender(s_HeaderIcon, NULL, 66, 27);
|
||||
graphics::systemFont::renderText("JKSV", NULL, 130, 38, 24, COLOR_WHITE);
|
||||
|
||||
// Render the state vector
|
||||
for (std::unique_ptr<appState> &a : s_AppStateVector)
|
||||
{
|
||||
a->render();
|
||||
}
|
||||
|
||||
// Render pop up messages
|
||||
ui::popMessage::render();
|
||||
|
||||
graphics::endFrame();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
#include <array>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <cstdarg>
|
||||
#include <cstdint>
|
||||
#include <ctime>
|
||||
#include <switch.h>
|
||||
#include "stringUtil.hpp"
|
||||
|
||||
// va buffer size
|
||||
static const int VA_BUFFER_SIZE = 0x800;
|
||||
|
||||
// Chars that shouldn't be in paths & function to test for them
|
||||
static const uint32_t forbiddenPathChars[] = {L',', L'/', L'\\', L'<', L'>', L':', L'"', L'|', L'?', L'*', L'™', L'©', L'®'};
|
||||
|
||||
|
|
@ -27,12 +33,15 @@ static bool codepointIsASCII(const uint32_t &codepoint)
|
|||
|
||||
std::string stringUtil::getFormattedString(const char *format, ...)
|
||||
{
|
||||
char vaBuffer[0x800];
|
||||
// Use C++ array for fun
|
||||
std::array<char, VA_BUFFER_SIZE> vaBuffer;
|
||||
// Use va to assemble string
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsnprintf(vaBuffer, 0x800, format, args);
|
||||
vsnprintf(vaBuffer.data(), VA_BUFFER_SIZE, format, args);
|
||||
va_end(args);
|
||||
return std::string(vaBuffer);
|
||||
// Return array as C++ string
|
||||
return std::string(vaBuffer.data());
|
||||
}
|
||||
|
||||
std::string stringUtil::getPathSafeString(const std::string &str)
|
||||
|
|
@ -113,6 +122,9 @@ std::string stringUtil::getExtensionFromString(const std::string &path)
|
|||
|
||||
std::string stringUtil::getTimeAndDateString(const dateFormats &dateFormat)
|
||||
{
|
||||
// String to return
|
||||
std::string dateString;
|
||||
|
||||
// Get local time from system
|
||||
std::time_t rawTime;
|
||||
std::time(&rawTime);
|
||||
|
|
@ -125,24 +137,25 @@ std::string stringUtil::getTimeAndDateString(const dateFormats &dateFormat)
|
|||
{
|
||||
case stringUtil::dateFormats::DATE_FORMAT_YMD:
|
||||
{
|
||||
return stringUtil::getFormattedString("%04d.%02d.%02d @ %02d.%02d.%02d", currentYear, local->tm_mon + 1, local->tm_mday, local->tm_min, local->tm_sec);
|
||||
dateString = stringUtil::getFormattedString("%04d.%02d.%02d @ %02d.%02d.%02d", currentYear, local->tm_mon + 1, local->tm_mday, local->tm_hour, local->tm_min, local->tm_sec);
|
||||
}
|
||||
break;
|
||||
|
||||
case stringUtil::dateFormats::DATE_FORMAT_YDM:
|
||||
{
|
||||
return stringUtil::getFormattedString("%04d.%02d.%02d @ %02d.%02d.%02d", currentYear, local->tm_mday, local->tm_mon + 1, local->tm_hour, local->tm_min, local->tm_sec);
|
||||
dateString = stringUtil::getFormattedString("%04d.%02d.%02d @ %02d.%02d.%02d", currentYear, local->tm_mday, local->tm_mon + 1, local->tm_hour, local->tm_min, local->tm_sec);
|
||||
}
|
||||
break;
|
||||
|
||||
case stringUtil::dateFormats::DATE_FORMAT_ASC:
|
||||
{
|
||||
std::string dateString = stringUtil::getFormattedString("%04d%02d%02d_%02d%02d", currentYear, local->tm_mon + 1, local->tm_mday, local->tm_hour, local->tm_min);
|
||||
// Assign date
|
||||
dateString.assign(std::asctime(local));
|
||||
// These need to be removed
|
||||
stringUtil::replaceInString(dateString, ":", "_");
|
||||
stringUtil::replaceInString(dateString, "\n", 0x00);
|
||||
return dateString;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return std::string("");
|
||||
return dateString;
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#include <vector>
|
||||
#include <cstring>
|
||||
#include "system/input.hpp"
|
||||
#include "log.hpp"
|
||||
|
|
@ -31,11 +32,10 @@ bool sys::input::buttonHeld(const HidNpadButton &button)
|
|||
|
||||
std::string sys::input::getString(const std::string &defaultText, const std::string &headerText, const size_t &maximumLength)
|
||||
{
|
||||
// Keyboard config
|
||||
SwkbdConfig config;
|
||||
char inputBuffer[maximumLength + 1];
|
||||
|
||||
// Clear inputBuffer
|
||||
std::memset(inputBuffer, 0x00, maximumLength + 1);
|
||||
// Input buffer
|
||||
std::vector<char> inputBuffer(maximumLength + 1);
|
||||
|
||||
// Setup soft keyboard
|
||||
swkbdCreate(&config, 0);
|
||||
|
|
@ -46,10 +46,13 @@ std::string sys::input::getString(const std::string &defaultText, const std::str
|
|||
swkbdConfigSetStringLenMax(&config, maximumLength);
|
||||
swkbdConfigSetKeySetDisableBitmask(&config, SwkbdKeyDisableBitmask_Backslash | SwkbdKeyDisableBitmask_Percent);
|
||||
|
||||
Result showKeyboard = swkbdShow(&config, inputBuffer, maximumLength + 1);
|
||||
Result showKeyboard = swkbdShow(&config, inputBuffer.data(), maximumLength + 1);
|
||||
if(R_FAILED(showKeyboard))
|
||||
{
|
||||
logger::log("Error showing keyboard: 0x%X", showKeyboard);
|
||||
logger::log("Error showing keyboard: 0x%X: %s", showKeyboard, inputBuffer.data());
|
||||
}
|
||||
return std::string(inputBuffer);
|
||||
// Free keyboard
|
||||
swkbdClose(&config);
|
||||
|
||||
return std::string(inputBuffer.data());
|
||||
}
|
||||
21
src/system/timer.cpp
Normal file
21
src/system/timer.cpp
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#include <SDL2/SDL.h>
|
||||
#include "system/timer.hpp"
|
||||
|
||||
sys::timer::timer(const uint32_t &triggerTicks) : m_TriggerTicks(triggerTicks)
|
||||
{
|
||||
// Save starting ticks
|
||||
m_StartingTicks = SDL_GetTicks();
|
||||
}
|
||||
|
||||
bool sys::timer::triggered(void)
|
||||
{
|
||||
// Get current ticks
|
||||
uint32_t currentTicks = SDL_GetTicks();
|
||||
// If amount is greater or equal, triggered
|
||||
if(currentTicks - m_StartingTicks >= m_TriggerTicks)
|
||||
{
|
||||
m_StartingTicks = currentTicks;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
82
src/ui/popMessage.cpp
Normal file
82
src/ui/popMessage.cpp
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
#include <vector>
|
||||
#include <SDL2/SDL.h>
|
||||
#include "config.hpp"
|
||||
#include "graphics/graphics.hpp"
|
||||
#include "ui/ui.hpp"
|
||||
#include "ui/popMessage.hpp"
|
||||
|
||||
static const int POPMESSAGE_X_COORDINATE = 24;
|
||||
static const int POPMESSAGE_Y_COORDINATE = 640;
|
||||
|
||||
namespace
|
||||
{
|
||||
// Queue to be processed. Needed to avoid threading graphics issues.
|
||||
std::vector<ui::popMessage::pMessage> s_MessageQueue;
|
||||
// Actual vector of messages
|
||||
std::vector<ui::popMessage::pMessage> s_Messages;
|
||||
}
|
||||
|
||||
void ui::popMessage::newMessage(const std::string &newMessage, const int &tickCount)
|
||||
{
|
||||
// Create message to push to processing queue
|
||||
ui::popMessage::pMessage newPopMessage =
|
||||
{
|
||||
.message = newMessage,
|
||||
.messageTimer = std::make_unique<sys::timer>(tickCount),
|
||||
.rectangleWidth = 0, // Will be calculated at update.
|
||||
.y = 720
|
||||
};
|
||||
|
||||
// Push it to queue
|
||||
s_MessageQueue.push_back(std::move(newPopMessage));
|
||||
}
|
||||
|
||||
void ui::popMessage::update(void)
|
||||
{
|
||||
// Loop through queue to make sure they're processed on main thread
|
||||
for(ui::popMessage::pMessage &pMessage : s_MessageQueue)
|
||||
{
|
||||
|
||||
// Calculate width of message rectangle
|
||||
pMessage.rectangleWidth = graphics::systemFont::getTextWidth(pMessage.message, 24) + 32;
|
||||
// Move it to actual message vector
|
||||
s_Messages.push_back(std::move(pMessage));
|
||||
}
|
||||
// Clear queue
|
||||
s_MessageQueue.clear();
|
||||
|
||||
// Need animation scaling to calculate positions
|
||||
float animationScaling = config::getAnimationScaling();
|
||||
|
||||
// Loop through message vector like this because we need current offset
|
||||
// targetY is starting target coordinate for messages to be displayed vertically upward
|
||||
int targetY = POPMESSAGE_Y_COORDINATE;
|
||||
for(unsigned int i = 0; i < s_Messages.size(); i++, targetY -= 52)
|
||||
{
|
||||
// Get reference to message we're working with
|
||||
ui::popMessage::pMessage ¤tMessage = s_Messages.at(i);
|
||||
|
||||
// If it has expired, erase, continue
|
||||
if(currentMessage.messageTimer->triggered())
|
||||
{
|
||||
s_Messages.erase(s_Messages.begin() + i);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate updated Y position
|
||||
if(currentMessage.y != targetY)
|
||||
{
|
||||
currentMessage.y += (targetY - currentMessage.y) / animationScaling;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ui::popMessage::render(void)
|
||||
{
|
||||
// Loop and render messages. Drawn directly to framebuffer
|
||||
for(ui::popMessage::pMessage &pMessage : s_Messages)
|
||||
{
|
||||
ui::renderDialogBox(NULL, POPMESSAGE_X_COORDINATE, pMessage.y, pMessage.rectangleWidth, 44);
|
||||
graphics::systemFont::renderText(pMessage.message, NULL, POPMESSAGE_X_COORDINATE + 16, pMessage.y + 10, 24, COLOR_WHITE);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user