yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
platform_paths.cc
Go to the documentation of this file.
2
3#include <cstdlib>
4
5#include "absl/strings/str_cat.h"
6#include "absl/strings/str_replace.h"
7
8#ifdef _WIN32
9#include <windows.h>
10#include <shlobj.h>
11#else
12#include <unistd.h>
13#include <pwd.h>
14#include <climits> // For PATH_MAX
15#ifdef __APPLE__
16#include <mach-o/dyld.h> // For _NSGetExecutablePath
17#endif
18#endif
19
20namespace yaze {
21namespace util {
22
23std::filesystem::path PlatformPaths::GetHomeDirectory() {
24 try {
25#ifdef _WIN32
26 // Windows: Use USERPROFILE environment variable
27 const char* userprofile = std::getenv("USERPROFILE");
28 if (userprofile && *userprofile) {
29 return std::filesystem::path(userprofile);
30 }
31
32 // Fallback to HOMEDRIVE + HOMEPATH
33 const char* homedrive = std::getenv("HOMEDRIVE");
34 const char* homepath = std::getenv("HOMEPATH");
35 if (homedrive && homepath) {
36 return std::filesystem::path(std::string(homedrive) + std::string(homepath));
37 }
38
39 // Last resort: use temp directory
40 std::error_code ec;
41 auto temp = std::filesystem::temp_directory_path(ec);
42 if (!ec) {
43 return temp;
44 }
45 return std::filesystem::path(".");
46#else
47 // Unix/macOS: Use HOME environment variable
48 const char* home = std::getenv("HOME");
49 if (home && *home) {
50 return std::filesystem::path(home);
51 }
52
53 // Fallback: try getpwuid
54 struct passwd* pw = getpwuid(getuid());
55 if (pw && pw->pw_dir) {
56 return std::filesystem::path(pw->pw_dir);
57 }
58
59 // Last resort: current directory (with error handling)
60 std::error_code ec;
61 auto cwd = std::filesystem::current_path(ec);
62 if (!ec) {
63 return cwd;
64 }
65 return std::filesystem::path(".");
66#endif
67 } catch (...) {
68 // If everything fails, return current directory placeholder
69 return std::filesystem::path(".");
70 }
71}
72
73absl::StatusOr<std::filesystem::path> PlatformPaths::GetAppDataDirectory() {
74#ifdef _WIN32
75 wchar_t path[MAX_PATH];
76 if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, path))) {
77 std::filesystem::path app_data = std::filesystem::path(path) / "yaze";
78 auto status = EnsureDirectoryExists(app_data);
79 if (!status.ok()) {
80 return status;
81 }
82 return app_data;
83 }
84 // Fallback if SHGetFolderPathW fails
85 std::filesystem::path home = GetHomeDirectory();
86 std::filesystem::path app_data = home / "yaze_data";
87 auto status = EnsureDirectoryExists(app_data);
88 if (!status.ok()) {
89 return status;
90 }
91 return app_data;
92#else
93 // Unix/macOS: Use ~/.yaze for simplicity and consistency
94 // This is simpler than XDG or Application Support for a dev tool
95 std::filesystem::path home = GetHomeDirectory();
96 std::filesystem::path app_data = home / ".yaze";
97 auto status = EnsureDirectoryExists(app_data);
98 if (!status.ok()) {
99 return status;
100 }
101 return app_data;
102#endif
103}
104
105absl::StatusOr<std::filesystem::path> PlatformPaths::GetConfigDirectory() {
106 // For yaze, config and data directories are the same.
107 // This provides a semantically clearer API for config access.
108 return GetAppDataDirectory();
109}
110
111absl::StatusOr<std::filesystem::path> PlatformPaths::GetAppDataSubdirectory(
112 const std::string& subdir) {
113 auto app_data_result = GetAppDataDirectory();
114 if (!app_data_result.ok()) {
115 return app_data_result.status();
116 }
117
118 std::filesystem::path subdir_path = *app_data_result / subdir;
119
120 auto status = EnsureDirectoryExists(subdir_path);
121 if (!status.ok()) {
122 return status;
123 }
124
125 return subdir_path;
126}
127
129 const std::filesystem::path& path) {
130 if (Exists(path)) {
131 return absl::OkStatus();
132 }
133
134 std::error_code ec;
135 if (!std::filesystem::create_directories(path, ec)) {
136 if (ec) {
137 return absl::InternalError(
138 absl::StrCat("Failed to create directory: ", path.string(),
139 " - Error: ", ec.message()));
140 }
141 }
142
143 return absl::OkStatus();
144}
145
146bool PlatformPaths::Exists(const std::filesystem::path& path) {
147 std::error_code ec;
148 bool exists = std::filesystem::exists(path, ec);
149 return exists && !ec;
150}
151
152absl::StatusOr<std::filesystem::path> PlatformPaths::GetTempDirectory() {
153 std::error_code ec;
154 std::filesystem::path temp_base = std::filesystem::temp_directory_path(ec);
155
156 if (ec) {
157 return absl::InternalError(
158 absl::StrCat("Failed to get temp directory: ", ec.message()));
159 }
160
161 std::filesystem::path yaze_temp = temp_base / "yaze";
162
163 auto status = EnsureDirectoryExists(yaze_temp);
164 if (!status.ok()) {
165 return status;
166 }
167
168 return yaze_temp;
169}
170
172 const std::filesystem::path& path) {
173 // Convert to string and replace backslashes with forward slashes
174 // Forward slashes work on all platforms for display purposes
175 std::string path_str = path.string();
176 return absl::StrReplaceAll(path_str, {{"\\", "/"}});
177}
178
179std::string PlatformPaths::ToNativePath(const std::filesystem::path& path) {
180 // std::filesystem::path::string() already returns the native format
181 return path.string();
182}
183
184absl::StatusOr<std::filesystem::path> PlatformPaths::FindAsset(
185 const std::string& relative_path) {
186 std::vector<std::filesystem::path> search_paths;
187
188 try {
189 // 1. Check compile-time YAZE_ASSETS_PATH if defined
190#ifdef YAZE_ASSETS_PATH
191 try {
192 search_paths.push_back(std::filesystem::path(YAZE_ASSETS_PATH) / relative_path);
193 } catch (...) {
194 // Skip if path construction fails
195 }
196#endif
197
198 // 2. Current working directory + assets/ (cached to avoid repeated calls)
199 static std::filesystem::path cached_cwd;
200 static bool cwd_cached = false;
201
202 if (!cwd_cached) {
203 std::error_code ec;
204 cached_cwd = std::filesystem::current_path(ec);
205 cwd_cached = true; // Only try once to avoid repeated slow calls
206 }
207
208 if (!cached_cwd.empty()) {
209 try {
210 search_paths.push_back(cached_cwd / "assets" / relative_path);
211 } catch (...) {
212 // Skip if path construction fails
213 }
214 }
215
216 // 3. Executable directory + assets/ (cached to avoid repeated OS calls)
217 static std::filesystem::path cached_exe_dir;
218 static bool exe_dir_cached = false;
219
220 if (!exe_dir_cached) {
221 try {
222#ifdef __APPLE__
223 char exe_path[PATH_MAX];
224 uint32_t size = sizeof(exe_path);
225 if (_NSGetExecutablePath(exe_path, &size) == 0) {
226 cached_exe_dir = std::filesystem::path(exe_path).parent_path();
227 }
228#elif defined(__linux__)
229 char exe_path[PATH_MAX];
230 ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
231 if (len != -1) {
232 exe_path[len] = '\0';
233 cached_exe_dir = std::filesystem::path(exe_path).parent_path();
234 }
235#elif defined(_WIN32)
236 wchar_t exe_path[MAX_PATH];
237 if (GetModuleFileNameW(NULL, exe_path, MAX_PATH) != 0) {
238 cached_exe_dir = std::filesystem::path(exe_path).parent_path();
239 }
240#endif
241 } catch (...) {
242 // Skip if exe path detection fails
243 }
244 exe_dir_cached = true; // Only try once
245 }
246
247 if (!cached_exe_dir.empty()) {
248 try {
249 search_paths.push_back(cached_exe_dir / "assets" / relative_path);
250 // Also check parent (for build/bin/yaze case)
251 search_paths.push_back(cached_exe_dir.parent_path() / "assets" / relative_path);
252 } catch (...) {
253 // Skip if path construction fails
254 }
255 }
256
257 // 4. Parent directories (for running from build subdirectories)
258 if (!cached_cwd.empty()) {
259 try {
260 auto parent = cached_cwd.parent_path();
261 if (!parent.empty() && parent != cached_cwd) {
262 search_paths.push_back(parent / "assets" / relative_path);
263 auto grandparent = parent.parent_path();
264 if (!grandparent.empty() && grandparent != parent) {
265 search_paths.push_back(grandparent / "assets" / relative_path);
266 }
267 }
268 } catch (...) {
269 // Skip if path operations fail
270 }
271 }
272
273 // 5. User directory ~/.yaze/assets/ (cached home directory)
274 static std::filesystem::path cached_home;
275 static bool home_cached = false;
276
277 if (!home_cached) {
278 try {
279 cached_home = GetHomeDirectory();
280 } catch (...) {
281 // Skip if home lookup fails
282 }
283 home_cached = true; // Only try once
284 }
285
286 if (!cached_home.empty() && cached_home != ".") {
287 try {
288 search_paths.push_back(cached_home / ".yaze" / "assets" / relative_path);
289 } catch (...) {
290 // Skip if path construction fails
291 }
292 }
293
294 // 6. System-wide installation (Unix only)
295#ifndef _WIN32
296 try {
297 search_paths.push_back(std::filesystem::path("/usr/local/share/yaze/assets") / relative_path);
298 search_paths.push_back(std::filesystem::path("/usr/share/yaze/assets") / relative_path);
299 } catch (...) {
300 // Skip if path construction fails
301 }
302#endif
303
304 // Search all paths and return the first one that exists
305 // Limit search to prevent infinite loops on weird filesystems
306 const size_t max_paths_to_check = 20;
307 size_t checked = 0;
308
309 for (const auto& candidate : search_paths) {
310 if (++checked > max_paths_to_check) {
311 break; // Safety limit
312 }
313
314 try {
315 // Use std::filesystem::exists with error code to avoid exceptions
316 std::error_code exists_ec;
317 if (std::filesystem::exists(candidate, exists_ec) && !exists_ec) {
318 // Double-check it's a regular file or directory, not a broken symlink
319 auto status = std::filesystem::status(candidate, exists_ec);
320 if (!exists_ec && status.type() != std::filesystem::file_type::not_found) {
321 return candidate;
322 }
323 }
324 } catch (...) {
325 // Skip this candidate if checking fails
326 continue;
327 }
328 }
329
330 // If not found, return a simple error
331 return absl::NotFoundError(
332 absl::StrCat("Asset not found: ", relative_path));
333
334 } catch (const std::exception& e) {
335 return absl::InternalError(
336 absl::StrCat("Exception while searching for asset: ", e.what()));
337 } catch (...) {
338 return absl::InternalError("Unknown exception while searching for asset");
339 }
340}
341
342} // namespace util
343} // namespace yaze
static absl::StatusOr< std::filesystem::path > GetTempDirectory()
Get a temporary directory for the application.
static absl::StatusOr< std::filesystem::path > GetAppDataDirectory()
Get the user-specific application data directory for YAZE.
static absl::StatusOr< std::filesystem::path > GetAppDataSubdirectory(const std::string &subdir)
Get a subdirectory within the app data folder.
static std::string ToNativePath(const std::filesystem::path &path)
Convert path to native format.
static absl::StatusOr< std::filesystem::path > GetConfigDirectory()
Get the user-specific configuration directory for YAZE.
static absl::StatusOr< std::filesystem::path > FindAsset(const std::string &relative_path)
Find an asset file in multiple standard locations.
static absl::Status EnsureDirectoryExists(const std::filesystem::path &path)
Ensure a directory exists, creating it if necessary.
static std::string NormalizePathForDisplay(const std::filesystem::path &path)
Normalize path separators for display.
static bool Exists(const std::filesystem::path &path)
Check if a file or directory exists.
static std::filesystem::path GetHomeDirectory()
Get the user's home directory in a cross-platform way.
Main namespace for the application.