feat(asset): implement asset path resolution and streamline file loading
- Introduced a new utility function `FindAsset` in `PlatformPaths` to locate asset files across multiple standard directories, enhancing flexibility in asset management. - Updated various components to utilize `FindAsset` for loading configuration and prompt files, replacing hardcoded search paths with a more robust solution. - Improved error handling and logging for asset loading failures, ensuring clearer feedback during runtime. - Refactored existing code in `gemini_ai_service`, `prompt_builder`, and `platform_paths` to leverage the new asset resolution mechanism, promoting code consistency and maintainability.
This commit is contained in:
@@ -11,44 +11,63 @@
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
#include <climits> // For PATH_MAX
|
||||
#ifdef __APPLE__
|
||||
#include <mach-o/dyld.h> // For _NSGetExecutablePath
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace yaze {
|
||||
namespace util {
|
||||
|
||||
std::filesystem::path PlatformPaths::GetHomeDirectory() {
|
||||
try {
|
||||
#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();
|
||||
// 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
|
||||
std::error_code ec;
|
||||
auto temp = std::filesystem::temp_directory_path(ec);
|
||||
if (!ec) {
|
||||
return temp;
|
||||
}
|
||||
return std::filesystem::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();
|
||||
// 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 (with error handling)
|
||||
std::error_code ec;
|
||||
auto cwd = std::filesystem::current_path(ec);
|
||||
if (!ec) {
|
||||
return cwd;
|
||||
}
|
||||
return std::filesystem::path(".");
|
||||
#endif
|
||||
} catch (...) {
|
||||
// If everything fails, return current directory placeholder
|
||||
return std::filesystem::path(".");
|
||||
}
|
||||
}
|
||||
|
||||
absl::StatusOr<std::filesystem::path> PlatformPaths::GetAppDataDirectory() {
|
||||
@@ -70,23 +89,11 @@ absl::StatusOr<std::filesystem::path> PlatformPaths::GetAppDataDirectory() {
|
||||
return status;
|
||||
}
|
||||
return app_data;
|
||||
#elif defined(__APPLE__)
|
||||
#else
|
||||
// Unix/macOS: Use ~/.yaze for simplicity and consistency
|
||||
// This is simpler than XDG or Application Support for a dev tool
|
||||
std::filesystem::path home = GetHomeDirectory();
|
||||
std::filesystem::path app_data = home / "Library" / "Application Support" / "yaze";
|
||||
auto status = EnsureDirectoryExists(app_data);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
return app_data;
|
||||
#else // Linux and other Unix-like systems
|
||||
const char* xdg_config_home = std::getenv("XDG_CONFIG_HOME");
|
||||
std::filesystem::path config_dir;
|
||||
if (xdg_config_home && *xdg_config_home) {
|
||||
config_dir = std::filesystem::path(xdg_config_home);
|
||||
} else {
|
||||
config_dir = GetHomeDirectory() / ".config";
|
||||
}
|
||||
std::filesystem::path app_data = config_dir / "yaze";
|
||||
std::filesystem::path app_data = home / ".yaze";
|
||||
auto status = EnsureDirectoryExists(app_data);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
@@ -174,5 +181,163 @@ std::string PlatformPaths::ToNativePath(const std::filesystem::path& path) {
|
||||
return path.string();
|
||||
}
|
||||
|
||||
absl::StatusOr<std::filesystem::path> PlatformPaths::FindAsset(
|
||||
const std::string& relative_path) {
|
||||
std::vector<std::filesystem::path> search_paths;
|
||||
|
||||
try {
|
||||
// 1. Check compile-time YAZE_ASSETS_PATH if defined
|
||||
#ifdef YAZE_ASSETS_PATH
|
||||
try {
|
||||
search_paths.push_back(std::filesystem::path(YAZE_ASSETS_PATH) / relative_path);
|
||||
} catch (...) {
|
||||
// Skip if path construction fails
|
||||
}
|
||||
#endif
|
||||
|
||||
// 2. Current working directory + assets/ (cached to avoid repeated calls)
|
||||
static std::filesystem::path cached_cwd;
|
||||
static bool cwd_cached = false;
|
||||
|
||||
if (!cwd_cached) {
|
||||
std::error_code ec;
|
||||
cached_cwd = std::filesystem::current_path(ec);
|
||||
cwd_cached = true; // Only try once to avoid repeated slow calls
|
||||
}
|
||||
|
||||
if (!cached_cwd.empty()) {
|
||||
try {
|
||||
search_paths.push_back(cached_cwd / "assets" / relative_path);
|
||||
} catch (...) {
|
||||
// Skip if path construction fails
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Executable directory + assets/ (cached to avoid repeated OS calls)
|
||||
static std::filesystem::path cached_exe_dir;
|
||||
static bool exe_dir_cached = false;
|
||||
|
||||
if (!exe_dir_cached) {
|
||||
try {
|
||||
#ifdef __APPLE__
|
||||
char exe_path[PATH_MAX];
|
||||
uint32_t size = sizeof(exe_path);
|
||||
if (_NSGetExecutablePath(exe_path, &size) == 0) {
|
||||
cached_exe_dir = std::filesystem::path(exe_path).parent_path();
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
char exe_path[PATH_MAX];
|
||||
ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
|
||||
if (len != -1) {
|
||||
exe_path[len] = '\0';
|
||||
cached_exe_dir = std::filesystem::path(exe_path).parent_path();
|
||||
}
|
||||
#elif defined(_WIN32)
|
||||
wchar_t exe_path[MAX_PATH];
|
||||
if (GetModuleFileNameW(NULL, exe_path, MAX_PATH) != 0) {
|
||||
cached_exe_dir = std::filesystem::path(exe_path).parent_path();
|
||||
}
|
||||
#endif
|
||||
} catch (...) {
|
||||
// Skip if exe path detection fails
|
||||
}
|
||||
exe_dir_cached = true; // Only try once
|
||||
}
|
||||
|
||||
if (!cached_exe_dir.empty()) {
|
||||
try {
|
||||
search_paths.push_back(cached_exe_dir / "assets" / relative_path);
|
||||
// Also check parent (for build/bin/yaze case)
|
||||
search_paths.push_back(cached_exe_dir.parent_path() / "assets" / relative_path);
|
||||
} catch (...) {
|
||||
// Skip if path construction fails
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Parent directories (for running from build subdirectories)
|
||||
if (!cached_cwd.empty()) {
|
||||
try {
|
||||
auto parent = cached_cwd.parent_path();
|
||||
if (!parent.empty() && parent != cached_cwd) {
|
||||
search_paths.push_back(parent / "assets" / relative_path);
|
||||
auto grandparent = parent.parent_path();
|
||||
if (!grandparent.empty() && grandparent != parent) {
|
||||
search_paths.push_back(grandparent / "assets" / relative_path);
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
// Skip if path operations fail
|
||||
}
|
||||
}
|
||||
|
||||
// 5. User directory ~/.yaze/assets/ (cached home directory)
|
||||
static std::filesystem::path cached_home;
|
||||
static bool home_cached = false;
|
||||
|
||||
if (!home_cached) {
|
||||
try {
|
||||
cached_home = GetHomeDirectory();
|
||||
} catch (...) {
|
||||
// Skip if home lookup fails
|
||||
}
|
||||
home_cached = true; // Only try once
|
||||
}
|
||||
|
||||
if (!cached_home.empty() && cached_home != ".") {
|
||||
try {
|
||||
search_paths.push_back(cached_home / ".yaze" / "assets" / relative_path);
|
||||
} catch (...) {
|
||||
// Skip if path construction fails
|
||||
}
|
||||
}
|
||||
|
||||
// 6. System-wide installation (Unix only)
|
||||
#ifndef _WIN32
|
||||
try {
|
||||
search_paths.push_back(std::filesystem::path("/usr/local/share/yaze/assets") / relative_path);
|
||||
search_paths.push_back(std::filesystem::path("/usr/share/yaze/assets") / relative_path);
|
||||
} catch (...) {
|
||||
// Skip if path construction fails
|
||||
}
|
||||
#endif
|
||||
|
||||
// Search all paths and return the first one that exists
|
||||
// Limit search to prevent infinite loops on weird filesystems
|
||||
const size_t max_paths_to_check = 20;
|
||||
size_t checked = 0;
|
||||
|
||||
for (const auto& candidate : search_paths) {
|
||||
if (++checked > max_paths_to_check) {
|
||||
break; // Safety limit
|
||||
}
|
||||
|
||||
try {
|
||||
// Use std::filesystem::exists with error code to avoid exceptions
|
||||
std::error_code exists_ec;
|
||||
if (std::filesystem::exists(candidate, exists_ec) && !exists_ec) {
|
||||
// Double-check it's a regular file or directory, not a broken symlink
|
||||
auto status = std::filesystem::status(candidate, exists_ec);
|
||||
if (!exists_ec && status.type() != std::filesystem::file_type::not_found) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
// Skip this candidate if checking fails
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// If not found, return a simple error
|
||||
return absl::NotFoundError(
|
||||
absl::StrCat("Asset not found: ", relative_path));
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
return absl::InternalError(
|
||||
absl::StrCat("Exception while searching for asset: ", e.what()));
|
||||
} catch (...) {
|
||||
return absl::InternalError("Unknown exception while searching for asset");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
} // namespace yaze
|
||||
|
||||
@@ -35,8 +35,7 @@ class PlatformPaths {
|
||||
* and cache files. The directory is created if it doesn't exist.
|
||||
*
|
||||
* - Windows: `%APPDATA%\yaze` (e.g., C:\Users\user\AppData\Roaming\yaze)
|
||||
* - macOS: `~/Library/Application Support/yaze`
|
||||
* - Linux: `$XDG_CONFIG_HOME/yaze` or `~/.config/yaze`
|
||||
* - macOS/Unix: `~/.yaze`
|
||||
*
|
||||
* @return StatusOr with path to the application data directory.
|
||||
*/
|
||||
@@ -46,13 +45,10 @@ class PlatformPaths {
|
||||
* @brief Get the user-specific configuration directory for YAZE.
|
||||
*
|
||||
* This is the standard location for storing user configuration files.
|
||||
* It often maps to the same location as GetAppDataDirectory but provides
|
||||
* a more semantically correct API for config files.
|
||||
* The directory is created if it doesn't exist.
|
||||
*
|
||||
* - Windows: `%APPDATA%\yaze`
|
||||
* - macOS: `~/Library/Application Support/yaze`
|
||||
* - Linux: `$XDG_CONFIG_HOME/yaze` or `~/.config/yaze`
|
||||
* - macOS/Unix: `~/.yaze`
|
||||
*
|
||||
* @return StatusOr with path to the configuration directory.
|
||||
*/
|
||||
@@ -117,6 +113,23 @@ class PlatformPaths {
|
||||
* @return Native path string
|
||||
*/
|
||||
static std::string ToNativePath(const std::filesystem::path& path);
|
||||
|
||||
/**
|
||||
* @brief Find an asset file in multiple standard locations
|
||||
*
|
||||
* Searches for an asset file in the following order:
|
||||
* 1. YAZE_ASSETS_PATH (if defined at compile time) + relative_path
|
||||
* 2. Current working directory + assets/ + relative_path
|
||||
* 3. Executable directory + assets/ + relative_path
|
||||
* 4. Parent directory + assets/ + relative_path
|
||||
* 5. ~/.yaze/assets/ + relative_path (user-installed assets)
|
||||
* 6. /usr/local/share/yaze/assets/ + relative_path (system-wide on Unix)
|
||||
*
|
||||
* @param relative_path Path relative to assets directory (e.g., "agent/prompt_catalogue.yaml")
|
||||
* @return StatusOr with absolute path to found asset file
|
||||
*/
|
||||
static absl::StatusOr<std::filesystem::path> FindAsset(
|
||||
const std::string& relative_path);
|
||||
};
|
||||
|
||||
} // namespace util
|
||||
|
||||
Reference in New Issue
Block a user