paths.c (3939B)
1 /* paths.c - rutas XDG de la app y reloj. En Windows todo va a %APPDATA%\APP_NAME 2 * (salvo que XDG_* esten definidas). En la web (emscripten) todo va a /data/APP_NAME, 3 * un IDBFS que persiste en IndexedDB (lo monta web/pre.js antes de main). */ 4 #include <dirent.h> 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <string.h> 8 #include <sys/stat.h> 9 #include <time.h> 10 #include "plat.h" 11 #include "paths.h" 12 #include "app_config.h" 13 14 #ifdef __EMSCRIPTEN__ 15 #include <emscripten.h> 16 #endif 17 18 #ifdef _WIN32 19 long long now_ms(void) { return (long long)GetTickCount64(); } 20 21 /* en Windows datos y config van juntos en %APPDATA% */ 22 static const char *home_dir(const char *posix_sub, char *buf, size_t n) 23 { 24 (void)posix_sub; 25 const char *a = getenv("APPDATA"); 26 snprintf(buf, n, "%s", a && *a ? a : "."); 27 return buf; 28 } 29 #else 30 #ifndef __EMSCRIPTEN__ 31 static const char *home_dir(const char *posix_sub, char *buf, size_t n) 32 { 33 const char *h = getenv("HOME"); 34 snprintf(buf, n, "%s/%s", h ? h : ".", posix_sub); 35 return buf; 36 } 37 #endif 38 39 long long now_ms(void) 40 { 41 struct timespec ts; 42 clock_gettime(CLOCK_MONOTONIC, &ts); 43 return (long long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; 44 } 45 #endif 46 47 void mkdirs(const char *path) 48 { 49 char tmp[512]; 50 snprintf(tmp, sizeof tmp, "%s", path); 51 for (char *p = tmp + 1; *p; p++) 52 if (*p == '/' || *p == '\\') { char c = *p; *p = 0; mkdir(tmp, 0755); *p = c; } 53 mkdir(tmp, 0755); 54 } 55 56 const char *data_dir(void) 57 { 58 static char dir[512]; 59 if (!dir[0]) { 60 #ifdef __EMSCRIPTEN__ 61 snprintf(dir, sizeof dir, "/data/" APP_NAME); 62 #else 63 const char *x = getenv("XDG_DATA_HOME"); 64 char base[400]; 65 snprintf(dir, sizeof dir, "%s/" APP_NAME, x && *x ? x : home_dir(".local/share", base, sizeof base)); 66 #endif 67 char saves[600]; 68 snprintf(saves, sizeof saves, "%s/saves", dir); 69 mkdirs(saves); 70 } 71 return dir; 72 } 73 74 const char *config_path(void) 75 { 76 static char path[512]; 77 if (!path[0]) { 78 #ifdef __EMSCRIPTEN__ 79 snprintf(path, sizeof path, "%s/settings", data_dir()); 80 #else 81 const char *x = getenv("XDG_CONFIG_HOME"); 82 char dir[480], base[400]; 83 snprintf(dir, sizeof dir, "%s/" APP_NAME, x && *x ? x : home_dir(".config", base, sizeof base)); 84 mkdirs(dir); 85 snprintf(path, sizeof path, "%s/settings", dir); 86 #endif 87 } 88 return path; 89 } 90 91 void paths_flush(void) 92 { 93 #ifdef __EMSCRIPTEN__ 94 /* FS.syncfs es asincronico: se espera (cediendo al navegador) hasta que termine, 95 * con un tope por si IndexedDB no responde */ 96 if (EM_ASM_INT({ return Module.noPersist ? 1 : 0; })) return; /* la carga fallo: no pisar IndexedDB */ 97 int id = EM_ASM_INT({ 98 var id = Module.fsSeq = (Module.fsSeq || 0) + 1; 99 Module.fsDone = Module.fsDone || {}; 100 FS.syncfs(false, function(err) { Module.fsDone[id] = true; if (err) console.warn('syncfs', err); }); 101 return id; 102 }); 103 /* se espera solo a este sync (si uno viejo nunca volvio, no bloquea a los demas) */ 104 for (int i = 0; i < 200 && !EM_ASM_INT({ return Module.fsDone[$0] ? 1 : 0; }, id); i++) emscripten_sleep(10); 105 EM_ASM({ delete Module.fsDone[$0]; }, id); 106 #endif 107 } 108 109 bool latest_file(const char *ext, char *out, int n) 110 { 111 char dir[600]; 112 snprintf(dir, sizeof dir, "%s/saves", data_dir()); 113 DIR *d = opendir(dir); 114 if (!d) return false; 115 struct dirent *e; 116 time_t best = 0; 117 bool found = false; 118 size_t el = strlen(ext); 119 while ((e = readdir(d))) { 120 size_t l = strlen(e->d_name); 121 if (l < el + 1 || strcmp(e->d_name + l - el, ext)) continue; 122 char p[900]; 123 snprintf(p, sizeof p, "%s/%s", dir, e->d_name); 124 struct stat st; 125 if (stat(p, &st) == 0 && st.st_mtime >= best) { 126 best = st.st_mtime; 127 snprintf(out, (size_t)n, "%s", p); 128 found = true; 129 } 130 } 131 closedir(d); 132 return found; 133 }