refactor: Restructure file dialog handling and introduce utility classes

- Updated file dialog references across the application to utilize a new `util::FileDialogWrapper` for consistent file handling.
- Refactored existing code to replace direct calls to `core::FileDialogWrapper` with the new utility class, enhancing modularity and maintainability.
- Introduced `util::PlatformPaths` for cross-platform directory management, ensuring consistent access to user directories and application data paths.
- Added new utility functions for file operations, improving the overall file handling capabilities within the application.
- Updated CMake configurations to include new utility source files, streamlining the build process.
This commit is contained in:
scawful
2025-10-04 23:26:42 -04:00
parent 429506e503
commit bcc8f8e8f9
39 changed files with 385 additions and 144 deletions

727
src/util/file_util.cc Normal file
View File

@@ -0,0 +1,727 @@
#include "util/file_util.h"
#ifdef _WIN32
// Include Windows-specific headers
#include <shobjidl.h>
#include <windows.h>
#else // Linux and MacOS
#include <dirent.h>
#include <sys/stat.h>
#include <cerrno>
#endif
#include <cstdlib>
#include <fstream>
#include <sstream>
#include <cstring>
#include "app/core/features.h"
namespace yaze {
namespace util {
std::string GetFileExtension(const std::string &filename) {
size_t dot = filename.find_last_of('.');
if (dot == std::string::npos) {
return "";
}
return filename.substr(dot + 1);
}
std::string GetFileName(const std::string &filename) {
size_t slash = filename.find_last_of('/');
if (slash == std::string::npos) {
return filename;
}
return filename.substr(slash + 1);
}
std::string LoadFile(const std::string &filename) {
std::string contents;
std::ifstream file(filename);
if (file.is_open()) {
std::stringstream buffer;
buffer << file.rdbuf();
contents = buffer.str();
file.close();
} else {
// Throw an exception
throw std::runtime_error("Could not open file: " + filename);
}
return contents;
}
std::string LoadConfigFile(const std::string &filename) {
std::string contents;
std::string filepath = GetConfigDirectory() + "/" + filename;
std::ifstream file(filepath);
if (file.is_open()) {
std::stringstream buffer;
buffer << file.rdbuf();
contents = buffer.str();
file.close();
}
return contents;
}
void SaveFile(const std::string &filename, const std::string &contents) {
std::string filepath = GetConfigDirectory() + "/" + filename;
std::ofstream file(filepath);
if (file.is_open()) {
file << contents;
file.close();
}
}
std::string GetResourcePath(const std::string &resource_path) {
#ifdef __APPLE__
#if TARGET_OS_IOS == 1
const std::string kBundlePath = GetBundleResourcePath();
return kBundlePath + resource_path;
#else
return GetBundleResourcePath() + "Contents/Resources/" + resource_path;
#endif
#else
return resource_path; // On Linux/Windows, resources are relative to executable
#endif
}
std::string ExpandHomePath(const std::string& path) {
if (path.empty() || path[0] != '~') {
return path;
}
const char* home = nullptr;
#ifdef _WIN32
home = std::getenv("USERPROFILE");
if (!home) {
home = std::getenv("HOMEDRIVE");
const char* homePath = std::getenv("HOMEPATH");
if (home && homePath) {
static std::string full_path;
full_path = std::string(home) + std::string(homePath);
home = full_path.c_str();
}
}
#else
home = std::getenv("HOME");
#endif
if (!home) {
return path; // Fallback to original path if HOME not found
}
// Replace ~ with home directory
if (path.size() == 1 || path[1] == '/') {
return std::string(home) + path.substr(1);
}
return path;
}
bool EnsureConfigDirectoryExists() {
std::string config_dir = GetConfigDirectory();
#ifdef _WIN32
// Windows directory creation
DWORD attr = GetFileAttributesA(config_dir.c_str());
if (attr == INVALID_FILE_ATTRIBUTES) {
// Directory doesn't exist, create it
if (!CreateDirectoryA(config_dir.c_str(), NULL)) {
DWORD error = GetLastError();
if (error != ERROR_ALREADY_EXISTS) {
return false;
}
}
}
#else
// Unix-like directory creation
struct stat st;
if (stat(config_dir.c_str(), &st) != 0) {
// Directory doesn't exist, create it with 0755 permissions
if (mkdir(config_dir.c_str(), 0755) != 0) {
if (errno != EEXIST) {
return false;
}
}
}
#endif
return true;
}
std::string GetConfigDirectory() {
std::string config_directory = ".yaze";
Platform platform;
#if defined(__APPLE__) && defined(__MACH__)
#if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
platform = Platform::kiOS;
#elif TARGET_OS_MAC == 1
platform = Platform::kMacOS;
#else
platform = Platform::kMacOS; // Default for macOS
#endif
#elif defined(_WIN32)
platform = Platform::kWindows;
#elif defined(__linux__)
platform = Platform::kLinux;
#else
platform = Platform::kUnknown;
#endif
switch (platform) {
case Platform::kWindows:
config_directory = "~/AppData/Roaming/yaze";
break;
case Platform::kMacOS:
case Platform::kLinux:
config_directory = "~/.config/yaze";
break;
default:
break;
}
// Expand the home directory path
return ExpandHomePath(config_directory);
}
#ifdef _WIN32
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
#include <nfd.h>
#endif
std::string FileDialogWrapper::ShowOpenFileDialog() {
// Use global feature flag to choose implementation
if (FeatureFlags::get().kUseNativeFileDialog) {
return ShowOpenFileDialogNFD();
} else {
return ShowOpenFileDialogBespoke();
}
}
std::string FileDialogWrapper::ShowOpenFileDialogNFD() {
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
NFD_Init();
nfdu8char_t *out_path = NULL;
nfdu8filteritem_t filters[1] = {{"ROM Files", "sfc,smc"}};
nfdopendialogu8args_t args = {0};
args.filterList = filters;
args.filterCount = 1;
nfdresult_t result = NFD_OpenDialogU8_With(&out_path, &args);
if (result == NFD_OKAY) {
std::string file_path(out_path);
NFD_FreePath(out_path);
NFD_Quit();
return file_path;
} else if (result == NFD_CANCEL) {
NFD_Quit();
return "";
}
NFD_Quit();
return "";
#else
// NFD not available - fallback to bespoke
return ShowOpenFileDialogBespoke();
#endif
}
std::string FileDialogWrapper::ShowOpenFileDialogBespoke() {
// Use Windows COM-based IFileOpenDialog as fallback when NFD not available
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (FAILED(hr)) {
return "";
}
std::string result;
IFileOpenDialog *pFileOpen = NULL;
// Create the FileOpenDialog object
hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
IID_IFileOpenDialog, reinterpret_cast<void**>(&pFileOpen));
if (SUCCEEDED(hr)) {
// Set file type filters
COMDLG_FILTERSPEC rgSpec[] = {
{L"ROM Files", L"*.sfc;*.smc"},
{L"All Files", L"*.*"}
};
pFileOpen->SetFileTypes(ARRAYSIZE(rgSpec), rgSpec);
pFileOpen->SetFileTypeIndex(1);
pFileOpen->SetDefaultExtension(L"sfc");
// Show the Open dialog
hr = pFileOpen->Show(NULL);
if (SUCCEEDED(hr)) {
// Get the file name from the dialog
IShellItem *pItem;
hr = pFileOpen->GetResult(&pItem);
if (SUCCEEDED(hr)) {
PWSTR pszFilePath;
hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
if (SUCCEEDED(hr)) {
// Convert wide string to narrow string
int size_needed = WideCharToMultiByte(CP_UTF8, 0, pszFilePath, -1, NULL, 0, NULL, NULL);
if (size_needed > 0) {
std::vector<char> buffer(size_needed);
WideCharToMultiByte(CP_UTF8, 0, pszFilePath, -1, buffer.data(), size_needed, NULL, NULL);
result = std::string(buffer.data());
}
CoTaskMemFree(pszFilePath);
}
pItem->Release();
}
}
pFileOpen->Release();
}
CoUninitialize();
return result;
}
std::string FileDialogWrapper::ShowSaveFileDialog(const std::string& default_name,
const std::string& default_extension) {
// Use global feature flag to choose implementation
if (FeatureFlags::get().kUseNativeFileDialog) {
return ShowSaveFileDialogNFD(default_name, default_extension);
} else {
return ShowSaveFileDialogBespoke(default_name, default_extension);
}
}
std::string FileDialogWrapper::ShowSaveFileDialogNFD(const std::string& default_name,
const std::string& default_extension) {
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
NFD_Init();
nfdu8char_t *out_path = NULL;
nfdsavedialogu8args_t args = {0};
if (!default_extension.empty()) {
// Create filter for the save dialog
static nfdu8filteritem_t filters[3] = {
{"Theme Files", "theme"},
{"YAZE Project Files", "yaze"},
{"ROM Files", "sfc,smc"}
};
if (default_extension == "theme") {
args.filterList = &filters[0];
args.filterCount = 1;
} else if (default_extension == "yaze") {
args.filterList = &filters[1];
args.filterCount = 1;
} else if (default_extension == "sfc" || default_extension == "smc") {
args.filterList = &filters[2];
args.filterCount = 1;
}
}
if (!default_name.empty()) {
args.defaultName = default_name.c_str();
}
nfdresult_t result = NFD_SaveDialogU8_With(&out_path, &args);
if (result == NFD_OKAY) {
std::string file_path(out_path);
NFD_FreePath(out_path);
NFD_Quit();
return file_path;
} else if (result == NFD_CANCEL) {
NFD_Quit();
return "";
}
NFD_Quit();
return "";
#else
// NFD not available - fallback to bespoke
return ShowSaveFileDialogBespoke(default_name, default_extension);
#endif
}
std::string FileDialogWrapper::ShowSaveFileDialogBespoke(const std::string& default_name,
const std::string& default_extension) {
// Use Windows COM-based IFileSaveDialog as fallback when NFD not available
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (FAILED(hr)) {
return "";
}
std::string result;
IFileSaveDialog *pFileSave = NULL;
// Create the FileSaveDialog object
hr = CoCreateInstance(CLSID_FileSaveDialog, NULL, CLSCTX_ALL,
IID_IFileSaveDialog, reinterpret_cast<void**>(&pFileSave));
if (SUCCEEDED(hr)) {
// Set file type filters based on extension
if (default_extension == "theme") {
COMDLG_FILTERSPEC rgSpec[] = {{L"Theme Files", L"*.theme"}};
pFileSave->SetFileTypes(ARRAYSIZE(rgSpec), rgSpec);
pFileSave->SetDefaultExtension(L"theme");
} else if (default_extension == "yaze") {
COMDLG_FILTERSPEC rgSpec[] = {{L"YAZE Project Files", L"*.yaze"}};
pFileSave->SetFileTypes(ARRAYSIZE(rgSpec), rgSpec);
pFileSave->SetDefaultExtension(L"yaze");
} else if (default_extension == "sfc" || default_extension == "smc") {
COMDLG_FILTERSPEC rgSpec[] = {
{L"SFC ROM Files", L"*.sfc"},
{L"SMC ROM Files", L"*.smc"}
};
pFileSave->SetFileTypes(ARRAYSIZE(rgSpec), rgSpec);
pFileSave->SetDefaultExtension(default_extension == "sfc" ? L"sfc" : L"smc");
}
// Set default filename if provided
if (!default_name.empty()) {
int size_needed = MultiByteToWideChar(CP_UTF8, 0, default_name.c_str(), -1, NULL, 0);
if (size_needed > 0) {
std::vector<wchar_t> wname(size_needed);
MultiByteToWideChar(CP_UTF8, 0, default_name.c_str(), -1, wname.data(), size_needed);
pFileSave->SetFileName(wname.data());
}
}
// Show the Save dialog
hr = pFileSave->Show(NULL);
if (SUCCEEDED(hr)) {
// Get the file name from the dialog
IShellItem *pItem;
hr = pFileSave->GetResult(&pItem);
if (SUCCEEDED(hr)) {
PWSTR pszFilePath;
hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
if (SUCCEEDED(hr)) {
// Convert wide string to narrow string
int size_needed = WideCharToMultiByte(CP_UTF8, 0, pszFilePath, -1, NULL, 0, NULL, NULL);
if (size_needed > 0) {
std::vector<char> buffer(size_needed);
WideCharToMultiByte(CP_UTF8, 0, pszFilePath, -1, buffer.data(), size_needed, NULL, NULL);
result = std::string(buffer.data());
}
CoTaskMemFree(pszFilePath);
}
pItem->Release();
}
}
pFileSave->Release();
}
CoUninitialize();
return result;
}
std::string FileDialogWrapper::ShowOpenFolderDialog() {
// Use global feature flag to choose implementation
if (FeatureFlags::get().kUseNativeFileDialog) {
return ShowOpenFolderDialogNFD();
} else {
return ShowOpenFolderDialogBespoke();
}
}
std::string FileDialogWrapper::ShowOpenFolderDialogNFD() {
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
NFD_Init();
nfdu8char_t *out_path = NULL;
nfdresult_t result = NFD_PickFolderU8(&out_path, NULL);
if (result == NFD_OKAY) {
std::string folder_path(out_path);
NFD_FreePath(out_path);
NFD_Quit();
return folder_path;
} else if (result == NFD_CANCEL) {
NFD_Quit();
return "";
}
NFD_Quit();
return "";
#else
// NFD not available - fallback to bespoke
return ShowOpenFolderDialogBespoke();
#endif
}
std::string FileDialogWrapper::ShowOpenFolderDialogBespoke() {
// Use Windows COM-based IFileOpenDialog in folder-picking mode as fallback
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (FAILED(hr)) {
return "";
}
std::string result;
IFileOpenDialog *pFileOpen = NULL;
// Create the FileOpenDialog object
hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
IID_IFileOpenDialog, reinterpret_cast<void**>(&pFileOpen));
if (SUCCEEDED(hr)) {
// Set options to pick folders instead of files
DWORD dwOptions;
hr = pFileOpen->GetOptions(&dwOptions);
if (SUCCEEDED(hr)) {
hr = pFileOpen->SetOptions(dwOptions | FOS_PICKFOLDERS);
}
if (SUCCEEDED(hr)) {
// Show the Open dialog
hr = pFileOpen->Show(NULL);
if (SUCCEEDED(hr)) {
// Get the folder path from the dialog
IShellItem *pItem;
hr = pFileOpen->GetResult(&pItem);
if (SUCCEEDED(hr)) {
PWSTR pszFolderPath;
hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFolderPath);
if (SUCCEEDED(hr)) {
// Convert wide string to narrow string
int size_needed = WideCharToMultiByte(CP_UTF8, 0, pszFolderPath, -1, NULL, 0, NULL, NULL);
if (size_needed > 0) {
std::vector<char> buffer(size_needed);
WideCharToMultiByte(CP_UTF8, 0, pszFolderPath, -1, buffer.data(), size_needed, NULL, NULL);
result = std::string(buffer.data());
}
CoTaskMemFree(pszFolderPath);
}
pItem->Release();
}
}
}
pFileOpen->Release();
}
CoUninitialize();
return result;
}
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(
const std::string &folder_path) {
std::vector<std::string> subdirectories;
WIN32_FIND_DATA findFileData;
HANDLE hFind = FindFirstFile((folder_path + "\\*").c_str(), &findFileData);
if (hFind != INVALID_HANDLE_VALUE) {
do {
if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if (strcmp(findFileData.cFileName, ".") != 0 &&
strcmp(findFileData.cFileName, "..") != 0) {
subdirectories.emplace_back(findFileData.cFileName);
}
}
} while (FindNextFile(hFind, &findFileData) != 0);
FindClose(hFind);
}
return subdirectories;
}
std::vector<std::string> FileDialogWrapper::GetFilesInFolder(
const std::string &folder_path) {
std::vector<std::string> files;
WIN32_FIND_DATA findFileData;
HANDLE hFind = FindFirstFile((folder_path + "\\*").c_str(), &findFileData);
if (hFind != INVALID_HANDLE_VALUE) {
do {
if (!(findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
files.emplace_back(findFileData.cFileName);
}
} while (FindNextFile(hFind, &findFileData) != 0);
FindClose(hFind);
}
return files;
}
#elif defined(__linux__)
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
#include <nfd.h>
#endif
std::string FileDialogWrapper::ShowOpenFileDialog() {
// Use global feature flag to choose implementation
if (FeatureFlags::get().kUseNativeFileDialog) {
return ShowOpenFileDialogNFD();
} else {
return ShowOpenFileDialogBespoke();
}
}
std::string FileDialogWrapper::ShowOpenFileDialogNFD() {
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
NFD_Init();
nfdu8char_t *out_path = NULL;
nfdu8filteritem_t filters[1] = {{"Rom File", "sfc,smc"}};
nfdopendialogu8args_t args = {0};
args.filterList = filters;
args.filterCount = 1;
nfdresult_t result = NFD_OpenDialogU8_With(&out_path, &args);
if (result == NFD_OKAY) {
std::string file_path_linux(out_path);
NFD_FreePath(out_path);
NFD_Quit();
return file_path_linux;
} else if (result == NFD_CANCEL) {
NFD_Quit();
return "";
}
NFD_Quit();
return "Error: NFD_OpenDialog";
#else
// NFD not available - fallback to bespoke
return ShowOpenFileDialogBespoke();
#endif
}
std::string FileDialogWrapper::ShowOpenFileDialogBespoke() {
// Implement bespoke file dialog or return placeholder
// This would contain the custom Linux implementation
return ""; // Placeholder for bespoke implementation
}
std::string FileDialogWrapper::ShowSaveFileDialogNFD(const std::string& default_name,
const std::string& default_extension) {
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
NFD_Init();
nfdu8char_t *out_path = NULL;
nfdsavedialogu8args_t args = {0};
if (!default_extension.empty()) {
// Create filter for the save dialog
static nfdu8filteritem_t filters[3] = {
{"Theme File", "theme"},
{"Project File", "yaze"},
{"ROM File", "sfc,smc"}
};
if (default_extension == "theme") {
args.filterList = &filters[0];
args.filterCount = 1;
} else if (default_extension == "yaze") {
args.filterList = &filters[1];
args.filterCount = 1;
} else if (default_extension == "sfc" || default_extension == "smc") {
args.filterList = &filters[2];
args.filterCount = 1;
}
}
if (!default_name.empty()) {
args.defaultName = default_name.c_str();
}
nfdresult_t result = NFD_SaveDialogU8_With(&out_path, &args);
if (result == NFD_OKAY) {
std::string file_path(out_path);
NFD_FreePath(out_path);
NFD_Quit();
return file_path;
} else if (result == NFD_CANCEL) {
NFD_Quit();
return "";
}
NFD_Quit();
return "";
#else
// NFD not available - fallback to bespoke
return ShowSaveFileDialogBespoke(default_name, default_extension);
#endif
}
std::string FileDialogWrapper::ShowSaveFileDialogBespoke(const std::string& default_name,
const std::string& default_extension) {
// Basic Linux implementation using system command
// For CI/CD, just return a placeholder path
if (!default_name.empty() && !default_extension.empty()) {
return default_name + "." + default_extension;
}
return ""; // For now return empty - full implementation can be added later
}
std::string FileDialogWrapper::ShowSaveFileDialog(const std::string& default_name,
const std::string& default_extension) {
// Use global feature flag to choose implementation
if (FeatureFlags::get().kUseNativeFileDialog) {
return ShowSaveFileDialogNFD(default_name, default_extension);
} else {
return ShowSaveFileDialogBespoke(default_name, default_extension);
}
}
std::string FileDialogWrapper::ShowOpenFolderDialog() {
// Use global feature flag to choose implementation
if (FeatureFlags::get().kUseNativeFileDialog) {
return ShowOpenFolderDialogNFD();
} else {
return ShowOpenFolderDialogBespoke();
}
}
std::string FileDialogWrapper::ShowOpenFolderDialogNFD() {
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
NFD_Init();
nfdu8char_t *out_path = NULL;
nfdresult_t result = NFD_PickFolderU8(&out_path, NULL);
if (result == NFD_OKAY) {
std::string folder_path_linux(out_path);
NFD_FreePath(out_path);
NFD_Quit();
return folder_path_linux;
} else if (result == NFD_CANCEL) {
NFD_Quit();
return "";
}
NFD_Quit();
return "Error: NFD_PickFolder";
#else
// NFD not available - fallback to bespoke
return ShowOpenFolderDialogBespoke();
#endif
}
std::string FileDialogWrapper::ShowOpenFolderDialogBespoke() {
// Implement bespoke folder dialog or return placeholder
// This would contain the custom macOS implementation
return ""; // Placeholder for bespoke implementation
}
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(
const std::string &folder_path) {
std::vector<std::string> subdirectories;
DIR *dir;
struct dirent *ent;
if ((dir = opendir(folder_path.c_str())) != NULL) {
while ((ent = readdir(dir)) != NULL) {
if (ent->d_type == DT_DIR) {
if (strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0) {
subdirectories.push_back(ent->d_name);
}
}
}
closedir(dir);
}
return subdirectories;
}
std::vector<std::string> FileDialogWrapper::GetFilesInFolder(
const std::string &folder_path) {
std::vector<std::string> files;
DIR *dir;
struct dirent *ent;
if ((dir = opendir(folder_path.c_str())) != NULL) {
while ((ent = readdir(dir)) != NULL) {
if (ent->d_type == DT_REG) {
files.push_back(ent->d_name);
}
}
closedir(dir);
}
return files;
}
#endif
} // namespace util
} // namespace yaze

67
src/util/file_util.h Normal file
View File

@@ -0,0 +1,67 @@
#ifndef YAZE_UTIL_FILE_UTIL_H_
#define YAZE_UTIL_FILE_UTIL_H_
#include <string>
#include <vector>
namespace yaze {
namespace util {
class FileDialogWrapper {
public:
/**
* @brief ShowOpenFileDialog opens a file dialog and returns the selected
* filepath. Uses global feature flag to choose implementation.
*/
static std::string ShowOpenFileDialog();
/**
* @brief ShowOpenFolderDialog opens a file dialog and returns the selected
* folder path. Uses global feature flag to choose implementation.
*/
static std::string ShowOpenFolderDialog();
/**
* @brief ShowSaveFileDialog opens a save file dialog and returns the selected
* filepath. Uses global feature flag to choose implementation.
*/
static std::string ShowSaveFileDialog(const std::string& default_name = "",
const std::string& default_extension = "");
// Specific implementations for testing
static std::string ShowOpenFileDialogNFD();
static std::string ShowOpenFileDialogBespoke();
static std::string ShowSaveFileDialogNFD(const std::string& default_name = "",
const std::string& default_extension = "");
static std::string ShowSaveFileDialogBespoke(const std::string& default_name = "",
const std::string& default_extension = "");
static std::string ShowOpenFolderDialogNFD();
static std::string ShowOpenFolderDialogBespoke();
static std::vector<std::string> GetSubdirectoriesInFolder(
const std::string &folder_path);
static std::vector<std::string> GetFilesInFolder(
const std::string &folder_path);
};
/**
* @brief GetBundleResourcePath returns the path to the bundle resource
* directory. Specific to MacOS.
*/
std::string GetBundleResourcePath();
enum class Platform { kUnknown, kMacOS, kiOS, kWindows, kLinux };
std::string GetFileExtension(const std::string &filename);
std::string GetFileName(const std::string &filename);
std::string LoadFile(const std::string &filename);
std::string LoadConfigFile(const std::string &filename);
std::string GetConfigDirectory();
bool EnsureConfigDirectoryExists();
std::string ExpandHomePath(const std::string& path);
std::string GetResourcePath(const std::string &resource_path);
void SaveFile(const std::string &filename, const std::string &data);
} // namespace util
} // namespace yaze
#endif // YAZE_UTIL_FILE_UTIL_H_

140
src/util/platform_paths.cc Normal file
View File

@@ -0,0 +1,140 @@
#include "util/platform_paths.h"
#include <cstdlib>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_replace.h"
#ifdef _WIN32
#include <windows.h>
#include <shlobj.h>
#else
#include <unistd.h>
#include <pwd.h>
#endif
namespace yaze {
namespace util {
std::filesystem::path PlatformPaths::GetHomeDirectory() {
#ifdef _WIN32
// Windows: Use USERPROFILE environment variable
const char* userprofile = std::getenv("USERPROFILE");
if (userprofile && *userprofile) {
return std::filesystem::path(userprofile);
}
// Fallback to HOMEDRIVE + HOMEPATH
const char* homedrive = std::getenv("HOMEDRIVE");
const char* homepath = std::getenv("HOMEPATH");
if (homedrive && homepath) {
return std::filesystem::path(std::string(homedrive) + std::string(homepath));
}
// Last resort: use temp directory
return std::filesystem::temp_directory_path();
#else
// Unix/macOS: Use HOME environment variable
const char* home = std::getenv("HOME");
if (home && *home) {
return std::filesystem::path(home);
}
// Fallback: try getpwuid
struct passwd* pw = getpwuid(getuid());
if (pw && pw->pw_dir) {
return std::filesystem::path(pw->pw_dir);
}
// Last resort: current directory
return std::filesystem::current_path();
#endif
}
absl::StatusOr<std::filesystem::path> PlatformPaths::GetAppDataDirectory() {
std::filesystem::path home = GetHomeDirectory();
std::filesystem::path app_data = home / ".yaze";
auto status = EnsureDirectoryExists(app_data);
if (!status.ok()) {
return status;
}
return app_data;
}
absl::StatusOr<std::filesystem::path> PlatformPaths::GetAppDataSubdirectory(
const std::string& subdir) {
auto app_data_result = GetAppDataDirectory();
if (!app_data_result.ok()) {
return app_data_result.status();
}
std::filesystem::path subdir_path = *app_data_result / subdir;
auto status = EnsureDirectoryExists(subdir_path);
if (!status.ok()) {
return status;
}
return subdir_path;
}
absl::Status PlatformPaths::EnsureDirectoryExists(
const std::filesystem::path& path) {
if (Exists(path)) {
return absl::OkStatus();
}
std::error_code ec;
if (!std::filesystem::create_directories(path, ec)) {
if (ec) {
return absl::InternalError(
absl::StrCat("Failed to create directory: ", path.string(),
" - Error: ", ec.message()));
}
}
return absl::OkStatus();
}
bool PlatformPaths::Exists(const std::filesystem::path& path) {
std::error_code ec;
bool exists = std::filesystem::exists(path, ec);
return exists && !ec;
}
absl::StatusOr<std::filesystem::path> PlatformPaths::GetTempDirectory() {
std::error_code ec;
std::filesystem::path temp_base = std::filesystem::temp_directory_path(ec);
if (ec) {
return absl::InternalError(
absl::StrCat("Failed to get temp directory: ", ec.message()));
}
std::filesystem::path yaze_temp = temp_base / "yaze";
auto status = EnsureDirectoryExists(yaze_temp);
if (!status.ok()) {
return status;
}
return yaze_temp;
}
std::string PlatformPaths::NormalizePathForDisplay(
const std::filesystem::path& path) {
// Convert to string and replace backslashes with forward slashes
// Forward slashes work on all platforms for display purposes
std::string path_str = path.string();
return absl::StrReplaceAll(path_str, {{"\\", "/"}});
}
std::string PlatformPaths::ToNativePath(const std::filesystem::path& path) {
// std::filesystem::path::string() already returns the native format
return path.string();
}
} // namespace util
} // namespace yaze

107
src/util/platform_paths.h Normal file
View File

@@ -0,0 +1,107 @@
#ifndef YAZE_UTIL_PLATFORM_PATHS_H_
#define YAZE_UTIL_PLATFORM_PATHS_H_
#include <filesystem>
#include <string>
#include "absl/status/statusor.h"
namespace yaze {
namespace util {
/**
* @brief Cross-platform utilities for file system paths
*
* Provides consistent, platform-independent file and directory operations
* that work correctly on Windows (MSVC), macOS, and Linux.
*/
class PlatformPaths {
public:
/**
* @brief Get the user's home directory in a cross-platform way
*
* - Windows: Uses USERPROFILE environment variable
* - Unix/macOS: Uses HOME environment variable
* - Fallback: Returns current directory
*
* @return Path to user's home directory, or "." if not available
*/
static std::filesystem::path GetHomeDirectory();
/**
* @brief Get application data directory for YAZE
*
* Creates the directory if it doesn't exist.
*
* - Windows: %USERPROFILE%\.yaze
* - Unix/macOS: $HOME/.yaze
*
* @return StatusOr with path to data directory
*/
static absl::StatusOr<std::filesystem::path> GetAppDataDirectory();
/**
* @brief Get a subdirectory within the app data folder
*
* Creates the directory if it doesn't exist.
*
* @param subdir Subdirectory name (e.g., "agent", "cache", "logs")
* @return StatusOr with path to subdirectory
*/
static absl::StatusOr<std::filesystem::path> GetAppDataSubdirectory(
const std::string& subdir);
/**
* @brief Ensure a directory exists, creating it if necessary
*
* Uses std::filesystem::create_directories which works cross-platform.
*
* @param path Directory path to create
* @return OK if directory exists or was created successfully
*/
static absl::Status EnsureDirectoryExists(const std::filesystem::path& path);
/**
* @brief Check if a file or directory exists
*
* @param path Path to check
* @return true if exists, false otherwise
*/
static bool Exists(const std::filesystem::path& path);
/**
* @brief Get a temporary directory for the application
*
* - Windows: %TEMP%\yaze
* - Unix: /tmp/yaze or $TMPDIR/yaze
*
* @return StatusOr with path to temp directory
*/
static absl::StatusOr<std::filesystem::path> GetTempDirectory();
/**
* @brief Normalize path separators for display
*
* Converts all path separators to forward slashes for consistent
* output in logs and UI (forward slashes work on all platforms).
*
* @param path Path to normalize
* @return Normalized path string
*/
static std::string NormalizePathForDisplay(const std::filesystem::path& path);
/**
* @brief Convert path to native format
*
* Ensures path uses the correct separator for the current platform.
*
* @param path Path to convert
* @return Native path string
*/
static std::string ToNativePath(const std::filesystem::path& path);
};
} // namespace util
} // namespace yaze
#endif // YAZE_UTIL_PLATFORM_PATHS_H_

38
src/util/sdl_deleter.h Normal file
View File

@@ -0,0 +1,38 @@
#ifndef YAZE_UTIL_SDL_DELETER_H_
#define YAZE_UTIL_SDL_DELETER_H_
#include <SDL.h>
namespace yaze {
namespace util {
/**
* @brief Deleter for SDL_Window and SDL_Renderer.
*/
struct SDL_Deleter {
void operator()(SDL_Window* p) const { SDL_DestroyWindow(p); }
void operator()(SDL_Renderer* p) const { SDL_DestroyRenderer(p); }
};
// Custom deleter for SDL_Surface
struct SDL_Surface_Deleter {
void operator()(SDL_Surface* p) const {
if (p) {
SDL_FreeSurface(p);
}
}
};
// Custom deleter for SDL_Texture
struct SDL_Texture_Deleter {
void operator()(SDL_Texture* p) const {
if (p) {
SDL_DestroyTexture(p);
}
}
};
} // namespace util
} // namespace yaze
#endif // YAZE_UTIL_SDL_DELETER_H_

View File

@@ -15,6 +15,8 @@ set(YAZE_UTIL_SRC
util/flag.cc
util/hex.cc
util/log.cc
util/platform_paths.cc
util/file_util.cc
)
add_library(yaze_util STATIC ${YAZE_UTIL_SRC})