mirror of
https://github.com/joel16/VITAlbum.git
synced 2024-11-26 21:10:25 +00:00
filebrowser: Add ability to browse other devices (ur0, sd0 etc)
This commit is contained in:
parent
8ecfd83057
commit
fa7e3fde54
@ -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:
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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(¶ms);
|
||||
if (R_FAILED(ret = init.initialize(¶ms))) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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))) {
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user