diff --git a/CMakeLists.txt b/CMakeLists.txt index 539068d..fd8ae75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,7 +81,9 @@ set(APP_SOURCES source/input_types.cpp source/mouse.cpp source/options.cpp + source/shutdown.cpp source/stage.cpp + source/system_utils.cpp source/utils.cpp ) diff --git a/Makefile b/Makefile index 53c0974..ac3085e 100644 --- a/Makefile +++ b/Makefile @@ -22,37 +22,42 @@ RASPI_RELEASE := $(TARGET_FILE)-$(VERSION)-raspberry.tar.gz # Lista completa de archivos fuente (basada en CMakeLists.txt) APP_SOURCES := \ + source/animated_sprite.cpp \ source/asset.cpp \ source/audio.cpp \ - source/director.cpp \ - source/global_events.cpp \ - source/global_inputs.cpp \ - source/input.cpp \ - source/lang.cpp \ - source/main.cpp \ - source/param.cpp \ - source/resource.cpp \ - source/screen.cpp \ - source/text.cpp \ - source/writer.cpp \ - source/ui/menu_option.cpp \ - source/ui/menu_renderer.cpp \ - source/ui/notifier.cpp \ - source/ui/service_menu.cpp \ - source/ui/ui_message.cpp \ - source/ui/window_message.cpp \ + source/background.cpp \ source/balloon_formations.cpp \ source/balloon_manager.cpp \ source/balloon.cpp \ source/bullet.cpp \ + source/color.cpp \ + source/define_buttons.cpp \ + source/difficulty.cpp \ + source/director.cpp \ source/enter_name.cpp \ source/explosions.cpp \ + source/external/gif.cpp \ + source/external/jail_audio.cpp \ + source/external/jail_shader.cpp \ + source/fade.cpp \ source/game_logo.cpp \ + source/global_events.cpp \ + source/global_inputs.cpp \ + source/input_types.cpp \ + source/input.cpp \ source/item.cpp \ + source/lang.cpp \ + source/main.cpp \ source/manage_hiscore_table.cpp \ + source/mouse.cpp \ + source/moving_sprite.cpp \ + source/options.cpp \ + source/param.cpp \ + source/path_sprite.cpp \ source/player.cpp \ + source/resource.cpp \ source/scoreboard.cpp \ - source/tabe.cpp \ + source/screen.cpp \ source/sections/credits.cpp \ source/sections/game.cpp \ source/sections/hiscore_table.cpp \ @@ -60,26 +65,23 @@ APP_SOURCES := \ source/sections/intro.cpp \ source/sections/logo.cpp \ source/sections/title.cpp \ - source/animated_sprite.cpp \ - source/background.cpp \ - source/fade.cpp \ - source/moving_sprite.cpp \ - source/path_sprite.cpp \ + source/shutdown.cpp \ source/smart_sprite.cpp \ source/sprite.cpp \ + source/stage.cpp \ + source/system_utils.cpp \ + source/tabe.cpp \ + source/text.cpp \ source/texture.cpp \ source/tiled_bg.cpp \ - source/color.cpp \ - source/define_buttons.cpp \ - source/difficulty.cpp \ - source/input_types.cpp \ - source/mouse.cpp \ - source/options.cpp \ - source/stage.cpp \ + source/ui/menu_option.cpp \ + source/ui/menu_renderer.cpp \ + source/ui/notifier.cpp \ + source/ui/service_menu.cpp \ + source/ui/ui_message.cpp \ + source/ui/window_message.cpp \ source/utils.cpp \ - source/external/jail_shader.cpp \ - source/external/jail_audio.cpp \ - source/external/gif.cpp + source/writer.cpp # Includes INCLUDES := -Isource -Isource/external diff --git a/linux_utils/check_one_file_includes.sh b/linux_utils/check_one_file_includes.sh index c17bd27..13c5aa5 100755 --- a/linux_utils/check_one_file_includes.sh +++ b/linux_utils/check_one_file_includes.sh @@ -8,9 +8,8 @@ fi FILE="$1" -include-what-you-use -D DEBUG -D VERBOSE -std=c++20 -Wall "$FILE" \ +include-what-you-use -D _DEBUG -std=c++20 -Wall "$FILE" \ +-I../source \ -Xiwyu --mapping_file=sdl3_mapping.imp \ -Xiwyu --update_comments \ --Xiwyu --verbose=3 \ -| \ -python3 /usr/bin/fix_includes.py --update_comments +-Xiwyu --verbose=3 \ No newline at end of file diff --git a/source/director.cpp b/source/director.cpp index f464aa6..d473684 100644 --- a/source/director.cpp +++ b/source/director.cpp @@ -1,22 +1,17 @@ // IWYU pragma: no_include #include "director.h" -#include // Para SDL_LogCategory, SDL_LogInfo, SDL_SetLogPriority, SDL_LogPriority, SDL_Quit -#include // Para mkdir, stat, S_IRWXU -#include -#include // Para getuid +#include // Para SDL_LogInfo, SDL_LogCategory, SDL_SetLogPriority, SDL_LogPriority, SDL_Quit -#include // Para errno, EEXIST, EACCES, ENAMETOOLONG -#include // Para printf, perror -#include // Para exit, EXIT_FAILURE, srand, rand, system +#include // Para srand, exit, rand, EXIT_FAILURE #include // Para time -#include // Para std::cerr +#include // Para basic_ostream, operator<<, cerr, endl #include // Para make_unique, unique_ptr #include // Para span #include // Para runtime_error -#include // Para operator+, allocator, char_traits, operator==, string, basic_string +#include // Para allocator, char_traits, operator==, string, basic_string, operator+ -#include "asset.h" // Para Asset, AssetType +#include "asset.h" // Para Asset #include "audio.h" // Para Audio #include "input.h" // Para Input #include "lang.h" // Para setLanguage @@ -34,19 +29,12 @@ #include "sections/intro.h" // Para Intro #include "sections/logo.h" // Para Logo #include "sections/title.h" // Para Title +#include "shutdown.h" // Para resultToString, shutdownSystem, ShutdownResult +#include "system_utils.h" // Para createApplicationFolder, resultToString, Result #include "ui/notifier.h" // Para Notifier #include "ui/service_menu.h" // Para ServiceMenu #include "utils.h" // Para Overrides, overrides, getPath -#ifndef _WIN32 -#include // Para getpwuid, passwd -#endif - -#ifdef _WIN32 -#include -#include -#endif - // Constructor Director::Director(int argc, std::span argv) { #ifdef RECORDING @@ -88,18 +76,14 @@ Director::~Director() { // Inicializa todo void Director::init() { // Configuración inicial de parametros - Asset::init(executable_path_); // Inicializa el sistema de gestión de archivos - loadAssets(); // Crea el índice de archivos - - Input::init( - Asset::get()->get("gamecontrollerdb.txt"), - Asset::get()->get("controllers.json")); // Carga configuración de controles - - Options::setConfigFile(Asset::get()->get("config.txt")); // Establece el fichero de configuración - Options::setControllersFile(Asset::get()->get("controllers.json")); // Establece el fichero de configuración de mandos - Options::loadFromFile(); // Carga el archivo de configuración - loadParams(); // Carga los parámetros del programa - loadScoreFile(); // Carga el archivo de puntuaciones + Asset::init(executable_path_); // Inicializa el sistema de gestión de archivos + loadAssets(); // Crea el índice de archivos + Input::init(Asset::get()->get("gamecontrollerdb.txt"), Asset::get()->get("controllers.json")); // Carga configuración de controles + Options::setConfigFile(Asset::get()->get("config.txt")); // Establece el fichero de configuración + Options::setControllersFile(Asset::get()->get("controllers.json")); // Establece el fichero de configuración de mandos + Options::loadFromFile(); // Carga el archivo de configuración + loadParams(); // Carga los parámetros del programa + loadScoreFile(); // Carga el archivo de puntuaciones // Inicialización de subsistemas principales Lang::setLanguage(Options::settings.language); // Carga el archivo de idioma @@ -200,56 +184,12 @@ void Director::checkProgramArguments(int argc, std::span argv) { // Crea la carpeta del sistema donde guardar datos void Director::createSystemFolder(const std::string &folder) { -#ifdef _WIN32 - system_folder_ = std::string(getenv("APPDATA")) + "/" + folder; -#elif __APPLE__ - struct passwd *pw = getpwuid(getuid()); - const char *homedir = pw->pw_dir; - system_folder_ = std::string(homedir) + "/Library/Application Support" + "/" + folder; -#elif __linux__ - struct passwd *pw = getpwuid(getuid()); - const char *homedir = pw->pw_dir; - system_folder_ = std::string(homedir) + "/.config/" + folder; + auto result = SystemUtils::createApplicationFolder(folder, system_folder_); - { - // Intenta crear ".config", per si no existeix - std::string config_base_folder = std::string(homedir) + "/.config"; - int ret = mkdir(config_base_folder.c_str(), S_IRWXU); - if (ret == -1 && errno != EEXIST) { - printf("ERROR CREATING CONFIG BASE FOLDER."); - exit(EXIT_FAILURE); - } - } -#endif - - struct stat st = {0}; - if (stat(system_folder_.c_str(), &st) == -1) { - errno = 0; -#ifdef _WIN32 - int ret = mkdir(system_folder_.c_str()); -#else - int ret = mkdir(system_folder_.c_str(), S_IRWXU); -#endif - - if (ret == -1) { - switch (errno) { - case EACCES: - printf("the parent directory does not allow write"); - exit(EXIT_FAILURE); - - case EEXIST: - printf("pathname already exists"); - exit(EXIT_FAILURE); - - case ENAMETOOLONG: - printf("pathname is too long"); - exit(EXIT_FAILURE); - - default: - perror("mkdir"); - exit(EXIT_FAILURE); - } - } + if (result != SystemUtils::Result::SUCCESS) { + std::cerr << "Error creando carpeta del sistema: " + << SystemUtils::resultToString(result) << std::endl; + exit(EXIT_FAILURE); } } @@ -377,75 +317,11 @@ auto Director::run() -> int { // Apaga el sistema de forma segura void Director::shutdownSystem(bool should_shutdown) { - if (!should_shutdown) { - return; - } + if (should_shutdown) { + auto result = SystemShutdown::shutdownSystem(5, true); // 5 segundos, forzar apps -#ifdef _WIN32 - // Windows: Usar API nativa de Windows - if (!InitiateSystemShutdownA( - NULL, // lpMachineName (NULL = local machine) - "Sistema apagándose...", // lpMessage - 5, // dwTimeout (5 segundos) - TRUE, // bForceAppsClosed - FALSE // bRebootAfterShutdown (FALSE = shutdown) - )) { - std::cerr << "Error: No se pudo iniciar el apagado en Windows. Código: " - << GetLastError() << std::endl; - } - -#elif __APPLE__ - // macOS: Usar execvp con fork - pid_t pid = fork(); - if (pid == 0) { - // Proceso hijo - ejecutar shutdown - char *args[] = { - const_cast("shutdown"), - const_cast("-h"), - const_cast("+0.1"), - NULL}; - execvp("shutdown", args); - // Si execvp falla, salir del proceso hijo - std::cerr << "Error: execvp falló en macOS" << std::endl; - _exit(1); - } else if (pid > 0) { - // Proceso padre - esperar al hijo - int status; - waitpid(pid, &status, 0); - if (WEXITSTATUS(status) != 0) { - std::cerr << "Error: Comando shutdown falló en macOS" << std::endl; + if (result != SystemShutdown::ShutdownResult::SUCCESS) { + std::cerr << SystemShutdown::resultToString(result) << std::endl; } - } else { - std::cerr << "Error: No se pudo crear proceso fork en macOS" << std::endl; } - -#elif __linux__ - // Linux: Usar execvp con fork - pid_t pid = fork(); - if (pid == 0) { - // Proceso hijo - primero sleep, luego shutdown - sleep(5); - char *args[] = { - const_cast("shutdown"), - const_cast("-h"), - const_cast("now"), - NULL}; - execvp("shutdown", args); - // Si execvp falla, salir del proceso hijo - std::cerr << "Error: execvp falló en Linux" << std::endl; - _exit(1); - } else if (pid > 0) { - // Proceso padre - esperar al hijo - int status; - waitpid(pid, &status, 0); - if (WEXITSTATUS(status) != 0) { - std::cerr << "Error: Comando shutdown falló en Linux" << std::endl; - } - } else { - std::cerr << "Error: No se pudo crear proceso fork en Linux" << std::endl; - } - -#else -#error "Sistema operativo no soportado" -#endif } \ No newline at end of file diff --git a/source/director.h b/source/director.h index 9162bd1..02f8e0c 100644 --- a/source/director.h +++ b/source/director.h @@ -1,7 +1,7 @@ #pragma once -#include -#include // Para manejar cadenas de texto +#include // Para Span +#include // Para string namespace Lang { enum class Code : int; diff --git a/source/shutdown.cpp b/source/shutdown.cpp new file mode 100644 index 0000000..a36af10 --- /dev/null +++ b/source/shutdown.cpp @@ -0,0 +1,165 @@ +#include "shutdown.h" +#include + +#ifdef _WIN32 + #include +#else + #include + #include + #include +#endif + +namespace SystemShutdown { + +#ifndef _WIN32 + // Función auxiliar para sistemas Unix-like + ShutdownResult executeUnixShutdown(const char* command, char* const args[]) { + pid_t pid = fork(); + + if (pid == 0) { + // Proceso hijo + execvp(command, args); + // Si llegamos aquí, execvp falló + std::cerr << "Error: No se pudo ejecutar " << command << std::endl; + _exit(1); + } else if (pid > 0) { + // Proceso padre + int status; + waitpid(pid, &status, 0); + return (WEXITSTATUS(status) == 0) ? ShutdownResult::SUCCESS : ShutdownResult::ERROR_SYSTEM_CALL; + } else { + return ShutdownResult::ERROR_FORK_FAILED; + } + } +#endif + + // Implementación de las funciones públicas + ShutdownResult shutdownSystem() { + ShutdownConfig config; + return shutdownSystem(config); + } + + ShutdownResult shutdownSystem(int delay_seconds, bool force_apps) { + ShutdownConfig config; + config.delay_seconds = delay_seconds; + config.force_close_apps = force_apps; + return shutdownSystem(config); + } + + ShutdownResult shutdownSystem(const ShutdownConfig& config) { +#ifdef _WIN32 + // Windows: Usar CreateProcess + STARTUPINFOA si = {0}; + PROCESS_INFORMATION pi = {0}; + si.cb = sizeof(si); + + // Crear comando con el delay especificado + std::string command = "shutdown.exe /s /t " + std::to_string(config.delay_seconds); + if (config.force_close_apps) { + command += " /f"; + } + + // CreateProcess necesita un array de char modificable + char* cmd_buffer = new char[command.length() + 1]; + strcpy(cmd_buffer, command.c_str()); + + bool success = CreateProcessA( + NULL, // lpApplicationName + cmd_buffer, // lpCommandLine + NULL, // lpProcessAttributes + NULL, // lpThreadAttributes + FALSE, // bInheritHandles + 0, // dwCreationFlags + NULL, // lpEnvironment + NULL, // lpCurrentDirectory + &si, // lpStartupInfo + &pi // lpProcessInformation + ); + + delete[] cmd_buffer; + + if (success) { + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return ShutdownResult::SUCCESS; + } else { + DWORD error = GetLastError(); + if (error == ERROR_ACCESS_DENIED) { + return ShutdownResult::ERROR_PERMISSION; + } + return ShutdownResult::ERROR_SYSTEM_CALL; + } + +#elif __APPLE__ + // macOS + std::string delay_str = "+" + std::to_string(config.delay_seconds / 60.0, 1); // convertir a minutos + if (config.delay_seconds < 60) { + delay_str = "+0.1"; // mínimo delay en macOS + } + + char* args[] = { + const_cast("shutdown"), + const_cast("-h"), + const_cast(delay_str.c_str()), + NULL + }; + + return executeUnixShutdown("shutdown", args); + +#elif __linux__ + // Linux + std::string delay_str = "+" + std::to_string((config.delay_seconds + 59) / 60); // redondear hacia arriba a minutos + if (config.delay_seconds == 0) { + delay_str = "now"; + } + + char* args[] = { + const_cast("shutdown"), + const_cast("-h"), + const_cast(delay_str.c_str()), + NULL + }; + + return executeUnixShutdown("shutdown", args); + +#else + return ShutdownResult::ERROR_UNSUPPORTED; +#endif + } + + const char* resultToString(ShutdownResult result) { + switch (result) { + case ShutdownResult::SUCCESS: + return "Apagado iniciado exitosamente"; + case ShutdownResult::ERROR_PERMISSION: + return "Error: Permisos insuficientes"; + case ShutdownResult::ERROR_SYSTEM_CALL: + return "Error: Fallo en la llamada al sistema"; + case ShutdownResult::ERROR_FORK_FAILED: + return "Error: No se pudo crear proceso hijo"; + case ShutdownResult::ERROR_UNSUPPORTED: + return "Error: Sistema operativo no soportado"; + default: + return "Error desconocido"; + } + } + + bool isShutdownSupported() { +#if defined(_WIN32) || defined(__APPLE__) || defined(__linux__) + return true; +#else + return false; +#endif + } + + const char* getRequiredPermissions() { +#ifdef _WIN32 + return "Requiere permisos de Administrador en Windows"; +#elif defined(__APPLE__) || defined(__linux__) + return "Requiere permisos de root/sudo en Unix"; +#else + return "Sistema no soportado"; +#endif + } + +} // namespace SystemShutdown \ No newline at end of file diff --git a/source/shutdown.h b/source/shutdown.h new file mode 100644 index 0000000..f186779 --- /dev/null +++ b/source/shutdown.h @@ -0,0 +1,47 @@ +#pragma once + +// Utilidad multiplataforma para apagar el sistema de forma segura +namespace SystemShutdown { + + // Códigos de resultado para las operaciones de apagado + enum class ShutdownResult { + SUCCESS = 0, + ERROR_PERMISSION, // Error de permisos insuficientes + ERROR_SYSTEM_CALL, // Error en la llamada al sistema + ERROR_FORK_FAILED, // Error al crear proceso hijo (Unix) + ERROR_UNSUPPORTED // Sistema operativo no soportado + }; + + // Configuración para el apagado del sistema + struct ShutdownConfig { + int delay_seconds; + bool force_close_apps; + const char* shutdown_message; + + // Constructor con valores por defecto + ShutdownConfig() + : delay_seconds(5) + , force_close_apps(true) + , shutdown_message("El sistema se apagará...") + {} + }; + + // Apaga el sistema con configuración por defecto + ShutdownResult shutdownSystem(); + + // Apaga el sistema con configuración personalizada + ShutdownResult shutdownSystem(const ShutdownConfig& config); + + // Apaga el sistema con parámetros simples + ShutdownResult shutdownSystem(int delay_seconds, bool force_apps = true); + + // Convierte un código de resultado a string descriptivo + const char* resultToString(ShutdownResult result); + + // Verifica si el sistema actual soporta apagado programático + bool isShutdownSupported(); + + // Obtiene información sobre los permisos necesarios en la plataforma actual + const char* getRequiredPermissions(); + +} // namespace SystemShutdown diff --git a/source/system_utils.cpp b/source/system_utils.cpp new file mode 100644 index 0000000..51d0338 --- /dev/null +++ b/source/system_utils.cpp @@ -0,0 +1,190 @@ +#include "system_utils.h" +#include +#include +#include +#include + +#ifdef _WIN32 + #include + #include + #include + // Evitar conflictos con macros de Windows + #ifdef ERROR_ALREADY_EXISTS + #undef ERROR_ALREADY_EXISTS + #endif +#else + #include + #include +#endif + +namespace SystemUtils { + + // Función auxiliar para crear una carpeta individual + Result createSingleFolder(const std::string& path, int permissions) { + struct stat st = {0}; + + // Verificar si ya existe + if (stat(path.c_str(), &st) == 0) { + return Result::SUCCESS; // Ya existe, no es error por defecto + } + + // Intentar crear la carpeta + int result; +#ifdef _WIN32 + result = _mkdir(path.c_str()); +#else + result = mkdir(path.c_str(), permissions); +#endif + + if (result == -1) { + switch (errno) { + case EACCES: + return Result::PERMISSION_DENIED; + case EEXIST: + return Result::ALREADY_EXISTS; + case ENAMETOOLONG: + return Result::PATH_TOO_LONG; + default: + return Result::UNKNOWN_ERROR; + } + } + + return Result::SUCCESS; + } + + // Función auxiliar para crear carpetas padre recursivamente + Result createParentFolders(const std::string& path, int permissions) { + size_t pos = 0; + + while ((pos = path.find('/', pos + 1)) != std::string::npos) { + std::string parent = path.substr(0, pos); + if (!parent.empty() && !folderExists(parent)) { + Result result = createSingleFolder(parent, permissions); + if (result != Result::SUCCESS && result != Result::ALREADY_EXISTS) { + return result; + } + } + } + + return Result::SUCCESS; + } + + Result createApplicationFolder(const std::string& app_name, std::string& out_path) { + FolderConfig config; + return createApplicationFolder(app_name, out_path, config); + } + + Result createApplicationFolder(const std::string& app_name, std::string& out_path, const FolderConfig& config) { + out_path = getApplicationDataPath(app_name); + return createFolder(out_path, config); + } + + Result createFolder(const std::string& path) { + FolderConfig config; + return createFolder(path, config); + } + + Result createFolder(const std::string& path, const FolderConfig& config) { + if (path.empty()) { + return Result::INVALID_PATH; + } + + // Verificar si ya existe y si eso es un error + if (folderExists(path) && config.fail_if_exists) { + return Result::ALREADY_EXISTS; + } + + // Crear carpetas padre si es necesario + if (config.create_parents) { + Result parent_result = createParentFolders(path, config.permissions); + if (parent_result != Result::SUCCESS) { + return parent_result; + } + } + + // Crear la carpeta final + return createSingleFolder(path, config.permissions); + } + + std::string getApplicationDataPath(const std::string& app_name) { +#ifdef _WIN32 + char* appdata = getenv("APPDATA"); + if (appdata) { + return std::string(appdata) + "/" + app_name; + } + return "C:/Users/Default/AppData/Roaming/" + app_name; + +#elif __APPLE__ + std::string home = getHomeDirectory(); + return home + "/Library/Application Support/" + app_name; + +#elif __linux__ + std::string home = getHomeDirectory(); + return home + "/.config/" + app_name; + +#else + // Fallback genérico + std::string home = getHomeDirectory(); + return home + "/." + app_name; +#endif + } + + bool folderExists(const std::string& path) { + struct stat st = {0}; + return (stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode)); + } + + const char* resultToString(Result result) { + switch (result) { + case Result::SUCCESS: + return "Operación exitosa"; + case Result::PERMISSION_DENIED: + return "Error: Permisos insuficientes"; + case Result::PATH_TOO_LONG: + return "Error: Ruta demasiado larga"; + case Result::ALREADY_EXISTS: + return "Error: La carpeta ya existe"; + case Result::INVALID_PATH: + return "Error: Ruta inválida"; + case Result::UNKNOWN_ERROR: + return "Error desconocido"; + default: + return "Error no identificado"; + } + } + + std::string getHomeDirectory() { +#ifdef _WIN32 + char* userprofile = getenv("USERPROFILE"); + if (userprofile) { + return std::string(userprofile); + } + return "C:/Users/Default"; +#else + struct passwd *pw = getpwuid(getuid()); + if (pw && pw->pw_dir) { + return std::string(pw->pw_dir); + } + + // Fallback + char* home = getenv("HOME"); + if (home) { + return std::string(home); + } + return "/tmp"; +#endif + } + + std::string getTempDirectory() { +#ifdef _WIN32 + char* temp = getenv("TEMP"); + if (temp) { + return std::string(temp); + } + return "C:/Windows/Temp"; +#else + return "/tmp"; +#endif + } + +} // namespace SystemUtils \ No newline at end of file diff --git a/source/system_utils.h b/source/system_utils.h new file mode 100644 index 0000000..85390a0 --- /dev/null +++ b/source/system_utils.h @@ -0,0 +1,59 @@ +#pragma once + +#include + +// Utilidades multiplataforma para operaciones del sistema +namespace SystemUtils { + + // Códigos de resultado para operaciones del sistema + enum class Result { + SUCCESS = 0, + PERMISSION_DENIED, // Sin permisos para crear la carpeta + PATH_TOO_LONG, // Ruta demasiado larga + ALREADY_EXISTS, // Ya existe (solo si se considera error) + INVALID_PATH, // Ruta inválida + UNKNOWN_ERROR // Error desconocido + }; + + // Configuración para creación de carpetas + struct FolderConfig { + bool create_parents; // Crear carpetas padre si no existen + bool fail_if_exists; // Fallar si la carpeta ya existe + int permissions; // Permisos Unix (ignorado en Windows) + + // Constructor con valores por defecto + FolderConfig() + : create_parents(true) + , fail_if_exists(false) + , permissions(0755) // rwxr-xr-x + {} + }; + + // Crea la carpeta del sistema donde guardar datos de la aplicación + Result createApplicationFolder(const std::string& app_name, std::string& out_path); + + // Crea la carpeta del sistema con configuración personalizada + Result createApplicationFolder(const std::string& app_name, std::string& out_path, const FolderConfig& config); + + // Crea una carpeta en la ruta especificada + Result createFolder(const std::string& path); + + // Crea una carpeta con configuración personalizada + Result createFolder(const std::string& path, const FolderConfig& config); + + // Obtiene la ruta de datos de la aplicación (sin crearla) + std::string getApplicationDataPath(const std::string& app_name); + + // Verifica si una carpeta existe + bool folderExists(const std::string& path); + + // Convierte un código de resultado a string descriptivo + const char* resultToString(Result result); + + // Obtiene el directorio home del usuario + std::string getHomeDirectory(); + + // Obtiene el directorio temporal del sistema + std::string getTempDirectory(); + +} // namespace SystemUtils