filebrowser: Add ability to browse other devices (ur0, sd0 etc)

This commit is contained in:
Joel16 2022-01-16 12:38:03 -05:00
parent 8ecfd83057
commit fa7e3fde54
11 changed files with 128 additions and 65 deletions

View File

@ -3,7 +3,7 @@
A simple homebrew file browser that is used for viewing various image formats on the PlayStation VITA. This is a stripped down port of NX-Shell (Next) for Nintendo Switch. It only supports viewing images/gifs.
<p align="center">
<img src="https://i.imgur.com/KKXkLeG.jpg" alt="VITAlbum Screenshot" width="640" height="362"/>
<img src="https://i.imgur.com/OWzsz2f.png" alt="VITAlbum Screenshot" width="640" height="362"/>
</p>
# Supported Formats:

View File

@ -5,8 +5,8 @@
typedef struct {
int sort = 0;
bool dev_options = false;
bool image_filename = false;
std::string device;
std::string cwd;
} config_t;

View File

@ -9,6 +9,7 @@
namespace FS {
bool FileExists(const std::string &path);
bool DirExists(const std::string &path);
int MakeDir(const std::string &path);
int GetFileSize(const std::string &path, SceOff &size);
std::string GetFileExt(const std::string &filename);
bool IsImageType(const std::string &filename);

View File

@ -4,19 +4,23 @@
#include "config.h"
#include "fs.h"
#include "log.h"
#include "utils.h"
#define CONFIG_VERSION 2
#define CONFIG_VERSION 3
config_t cfg;
namespace Config {
static const char *config_file = "{\n\t\"config_ver\": %d,\n\t\"dev_options\": %s,\n\t\"image_filename\": %s,\n\t\"last_dir\": \"%s\",\n\t\"sort\": %d\n}";
static constexpr char config_path[] = "ux0:data/VITAlbum/config.json";
static const char *config_file = "{\n\t\"version\": %d,\n\t\"cwd\": \"%s\",\n\t\"device\": \"%s\",\n\t\"filename\": %s,\n\t\"sort\": %d\n}";
static int config_version_holder = 0;
class Allocator : public sce::Json::MemAllocator {
public:
Allocator() {}
Allocator() {
}
virtual void *allocateMemory(size_t size, void *unk) override {
return std::malloc(size);
@ -29,11 +33,11 @@ namespace Config {
int Save(config_t &config) {
int ret = 0;
char *buf = new char[1024];
SceSize len = std::snprintf(buf, 1024, config_file, CONFIG_VERSION, config.dev_options? "true" : "false",
config.image_filename? "true" : "false", config.cwd.c_str(), config.sort);
char *buf = new char[512];
SceSize len = std::snprintf(buf, 512, config_file, CONFIG_VERSION, config.cwd.c_str(), config.device.c_str(),
config.image_filename? "true" : "false", config.sort);
if (R_FAILED(ret = FS::WriteFile("ux0:data/VITAlbum/config.json", buf, len))) {
if (R_FAILED(ret = FS::WriteFile(config_path, buf, len))) {
delete[] buf;
return ret;
}
@ -44,38 +48,37 @@ namespace Config {
static void SetDefault(config_t &config) {
config.sort = 0;
config.dev_options = false;
config.image_filename = false;
config.cwd = "ux0:";
config.device = "ux0:";
config.cwd = "/";
}
int Load(void) {
int ret = 0;
if (!FS::DirExists("ux0:data"))
sceIoMkdir("ux0:data", 0777);
if (!FS::DirExists("ux0:data/VITAlbum"))
sceIoMkdir("ux0:data/VITAlbum", 0777);
if (!FS::FileExists("ux0:data/VITAlbum/config.json")) {
if (!FS::FileExists(config_path)) {
Config::SetDefault(cfg);
return Config::Save(cfg);
}
SceUID file = 0;
if (R_FAILED(ret = file = sceIoOpen("ux0:data/VITAlbum/config.json", SCE_O_RDONLY, 0)))
if (R_FAILED(ret = file = sceIoOpen(config_path, SCE_O_RDONLY, 0))) {
Log::Error("sceIoOpen(%s) failed: 0x%lx\n", config_path, ret);
return ret;
}
SceOff size = sceIoLseek(file, 0, SEEK_END);
char *buffer = new char[size + 1];
SceSize size = sceIoLseek(file, 0, SCE_SEEK_END);
char *buffer = new char[size];
if (R_FAILED(ret = sceIoPread(file, buffer, size + 1, SCE_SEEK_SET))) {
if (R_FAILED(ret = sceIoPread(file, buffer, size, SCE_SEEK_SET))) {
Log::Error("sceIoRead(%s) failed: 0x%lx\n", config_path, ret);
delete[] buffer;
sceIoClose(file);
return ret;
}
if (R_FAILED(ret = sceIoClose(file))) {
Log::Error("sceIoClose(%s) failed: 0x%lx\n", config_path, ret);
delete[] buffer;
return ret;
}
@ -84,34 +87,52 @@ namespace Config {
sce::Json::InitParameter params;
params.allocator = alloc;
params.bufSize = static_cast<SceSize>(size);
params.bufSize = size;
sce::Json::Initializer init = sce::Json::Initializer();
init.initialize(&params);
if (R_FAILED(ret = init.initialize(&params))) {
Log::Error("sce::Json::Initializer::initialize failed 0x%lx\n", ret);
delete[] buffer;
init.terminate();
delete alloc;
return ret;
}
sce::Json::Value value = sce::Json::Value();
sce::Json::Parser::parse(value, buffer, params.bufSize);
if (R_FAILED(ret = sce::Json::Parser::parse(value, buffer, params.bufSize))) {
Log::Error("sce::Json::Parser::parse failed 0x%lx\n", ret);
delete[] buffer;
init.terminate();
delete alloc;
return ret;
}
// We know sceJson API loops through the child values in root alphabetically.
config_version_holder = value.getValue(0).getInteger();
cfg.dev_options = value.getValue(1).getBoolean();
cfg.cwd = value.getValue(0).getString().c_str();
cfg.device = value.getValue(1).getString().c_str();
cfg.image_filename = value.getValue(2).getBoolean();
cfg.cwd = value.getValue(3).getString().c_str();
cfg.sort = value.getValue(4).getInteger();
cfg.sort = value.getValue(3).getInteger();
config_version_holder = value.getValue(4).getInteger();
// Build path with device + cwd
std::string path = cfg.device + cfg.cwd;
init.terminate();
delete alloc;
delete[] buffer;
if (!FS::DirExists(cfg.cwd))
cfg.cwd = "ux0:";
if (!FS::DirExists(path)) {
cfg.device = "ux0:";
cfg.cwd = "/";
}
// Delete config file if config file is updated. This will rarely happen.
if (config_version_holder < CONFIG_VERSION) {
sceIoRemove("ux0:data/VITAlbum/config.json");
sceIoRemove(config_path);
Config::SetDefault(cfg);
return Config::Save(cfg);
}
return 0;
}
}

View File

@ -34,6 +34,26 @@ namespace FS {
return false;
}
// Recursive mkdir based on -> https://newbedev.com/mkdir-c-function
int MakeDir(const std::string &path) {
std::string current_level = "";
std::string level;
std::stringstream ss(path);
// split path using slash as a separator
while (std::getline(ss, level, '/')) {
current_level += level; // append folder to the current level
// create current level
if (!FS::DirExists(current_level) && sceIoMkdir(current_level.c_str(), 0777) != 0)
return -1;
current_level += "/"; // don't forget to append a slash
}
return 0;
}
int GetFileSize(const std::string &path, SceOff &size) {
int ret = 0;
SceIoStat stat;
@ -110,8 +130,9 @@ namespace FS {
static int ChangeDir(const std::string &path, std::vector<SceIoDirent> &entries) {
int ret = 0;
std::vector<SceIoDirent> new_entries;
const std::string new_path = cfg.device + path;
if (R_FAILED(ret = FS::GetDirList(path, new_entries)))
if (R_FAILED(ret = FS::GetDirList(new_path, new_entries)))
return ret;
// Free entries and change the current working directory.
@ -123,7 +144,7 @@ namespace FS {
}
static int GetPrevPath(char path[256]) {
if (cfg.cwd.c_str() == "ux0:")
if (cfg.cwd.c_str() == "")
return -1;
// Remove upmost directory
@ -148,7 +169,10 @@ namespace FS {
int ChangeDirNext(const std::string &path, std::vector<SceIoDirent> &entries) {
std::string new_path = cfg.cwd;
new_path.append("/");
if (new_path != "/")
new_path.append("/");
new_path.append(path);
return FS::ChangeDir(new_path, entries);
}
@ -162,7 +186,7 @@ namespace FS {
}
const std::string BuildPath(SceIoDirent &entry) {
std::string path = cfg.cwd;
std::string path = cfg.device + cfg.cwd;
path.append("/");
path.append(entry.d_name);
return path;
@ -194,7 +218,7 @@ namespace FS {
return ret;
}
size = sceIoLseek(file, 0, SEEK_END);
size = sceIoLseek(file, 0, SCE_SEEK_END);
*buffer = new unsigned char[size];
if (R_FAILED(ret = sceIoPread(file, *buffer, size, SCE_SEEK_SET))) {

View File

@ -34,7 +34,8 @@ namespace GUI {
SceCtrlData pad = { 0 };
int ret = 0;
if (R_FAILED(ret = FS::GetDirList(cfg.cwd, data.entries)))
const std::string path = cfg.device + cfg.cwd;
if (R_FAILED(ret = FS::GetDirList(path, data.entries)))
return ret;
while (!done) {

View File

@ -1,47 +1,38 @@
#include <psp2/io/fcntl.h>
#include <cstdarg>
#include <cstdio>
#include <psp2/kernel/clib.h>
#include "fs.h"
#include "config.h"
#include "utils.h"
namespace Log {
static SceUID log_file = 0;
void Init(void) {
if (!cfg.dev_options)
return;
if (!FS::FileExists("ux0:data/VITAlbum/debug.log"))
FS::CreateFile("ux0:data/VITAlbum/debug.log");
constexpr char log_path[] = "ux0:data/VITAlbum/debug.log";
if (!FS::FileExists(log_path))
FS::CreateFile(log_path);
if (R_FAILED(log_file = sceIoOpen("ux0:data/VITAlbum/debug.log", SCE_O_WRONLY | SCE_O_APPEND, 0)))
if (R_FAILED(log_file = sceIoOpen(log_path, SCE_O_WRONLY | SCE_O_APPEND, 0)))
return;
}
void Exit(void) {
if (!cfg.dev_options)
return;
if (R_FAILED(sceIoClose(log_file)))
return;
}
void Error(const char *data, ...) {
if (!cfg.dev_options)
return;
char buf[512];
va_list args;
va_start(args, data);
std::vsnprintf(buf, sizeof(buf), data, args);
sceClibVsnprintf(buf, sizeof(buf), data, args);
va_end(args);
std::string error_string = "[ERROR] ";
error_string.append(buf);
std::printf("%s", error_string.c_str());
sceClibPrintf("%s", error_string.c_str());
if (R_FAILED(sceIoWrite(log_file, error_string.data(), error_string.length())))
return;
}

View File

@ -3,6 +3,7 @@
#include <vitaGL.h>
#include "config.h"
#include "fs.h"
#include "gui.h"
#include "log.h"
#include "textures.h"
@ -81,9 +82,12 @@ namespace Services {
ImGui::StyleColorsDark();
sceSysmoduleLoadModule(SCE_SYSMODULE_JSON);
if (!FS::DirExists("ux0:data/VITAlbum"))
FS::MakeDir("ux0:data/VITAlbum");
Config::Load();
Log::Init();
Config::Load();
Textures::Init();
Utils::InitAppUtil();

View File

@ -48,7 +48,9 @@ namespace FileBrowser {
}
namespace Tabs {
static int device = 0;
static const ImVec2 tex_size = ImVec2(22, 22);
static const char *devices[] = { "os0:", "pd0:", "sa0:", "tm0:", "ud0:", "ur0:", "ux0:", "vd0:", "vs0:"};
// Sort using ImGuiTableSortSpecs
bool Sort(const SceIoDirent &entryA, const SceIoDirent &entryB) {
@ -93,6 +95,32 @@ namespace Tabs {
void FileBrowser(WindowData &data) {
if (ImGui::BeginTabItem("File Browser")) {
ImGui::PushID("device_list");
ImGui::PushItemWidth(75.f);
if (ImGui::BeginCombo("", cfg.device.c_str())) {
for (int i = 0; i < IM_ARRAYSIZE(devices); i++) {
const bool is_selected = (cfg.device == devices[i]);
if (ImGui::Selectable(devices[i], is_selected)) {
cfg.device = devices[i];
cfg.cwd = "/";
data.entries.clear();
const std::string path = cfg.device + cfg.cwd;
FS::GetDirList(path, data.entries);
sort = -1;
}
if (is_selected)
ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
ImGui::PopItemWidth();
ImGui::PopID();
ImGui::SameLine();
// Display current working directory
ImGui::TextColored(ImVec4(1.00f, 1.00f, 1.00f, 1.00f), cfg.cwd.c_str());

View File

@ -28,14 +28,6 @@ namespace Tabs {
Tabs::Separator();
if (ImGui::TreeNode("Developer Options")) {
ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing
ImGui::Checkbox(" Enable logs", &cfg.dev_options);
ImGui::TreePop();
}
Tabs::Separator();
if (ImGui::TreeNode("About")) {
ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing
std::string vitalbum_ver = APP_VERSION;

View File

@ -145,7 +145,8 @@ namespace Windows {
if (pressed & SCE_CTRL_CANCEL) {
Config::Save(cfg);
data.entries.clear();
FS::GetDirList(cfg.cwd, data.entries);
const std::string path = cfg.device + cfg.cwd;
FS::GetDirList(path, data.entries);
data.state = WINDOW_STATE_FILEBROWSER;
}