Blob Blame History Raw

#include "path.h"

#include <unistd.h>
#include <sys/stat.h>



#ifndef APPNAME
#define APPNAME "jigsaw"
#endif

#ifndef SUBDIR
#define SUBDIR "games/" APPNAME
#endif

#ifndef PREFIX
#define PREFIX "/usr"
#endif

#ifndef PATHENV_DATA
#define PATHENV_DATA "JIGSAW_DATA"
#endif

#ifndef PATHENV_SAVE
#define PATHENV_SAVE "JIGSAW_SAVE"
#endif



const char* filename(const char *path) {
  const char *r = path;
  for(const char *c = r; *c; ++c) if (*c == '/' || *c == '\\') r = c+1;
  return r;
}


const char* fileext(const char *path) {
  const char* r = path = filename(path), *c = r;
  for(;*c; ++c) if (*c == '.') r = c;
  return r == path ? c : r;
}


char* filebase(const char *path) {
  const char* c0 = filename(path);
  const char* c1 = fileext(c0);
  char* r = (char*)alloc(c1 - c0 + 1);
  return strncpy(r, c0, c1 - c0);
}


char* filedir(const char *path) {
  const char* c = filename(path);
  while(c > path && (*(c-1) == '/' || *(c-1) == '\\')) --c;
  char* r = (char*)alloc(c - path + 1);
  return strncpy(r, path, c - path);
}




time_t filetime(const char *path) {
  struct stat st = {};
  return stat(path, &st) ? 0 : st.st_mtime;
}


int makeDirectoryRecursive(const char *path) {
  if (directoryExists(path)) return 1;
  char *parent = filedir(path);
  if (*parent) makeDirectoryRecursive(parent);
  free(parent);
  return makeDirectory(path);
}



PathList* mergePaths(PathList a, PathList b) {
  int cnt = 0;
  size_t size = ASIZEOF(PathList);
  for(int i = 0, j = 0; i < a.cnt || j < b.cnt; ++cnt) {
    int r = i < a.cnt && j < b.cnt ? strcmp(filename(a.l[i]), filename(b.l[i]))
          : i < a.cnt ? -1 : 1;
    if (r < 0) { size += strlen(a.l[i++]) + 1; }
          else { size += strlen(b.l[j++]) + 1; i += !r; }
  }
  size += cnt*ASIZEOF(char*);
  
  PathList *pl = alloc(size);
  char **l = (char**)(pl+1);
  char *c = (char*)(l+cnt);
  pl->l = (StrList)l;
  pl->cnt = cnt;
  for(int i = 0, j = 0; i < a.cnt || j < b.cnt; ++cnt) {
    int r = i < a.cnt && j < b.cnt ? strcmp(filename(a.l[i]), filename(b.l[i]))
          : i < a.cnt ? -1 : 1;
    if (r < 0) { *l++ = strcpy(c, a.l[i++]); }
          else { *l++ = strcpy(c, b.l[j++]); i += !r; }
    c += strlen(c) + 1;
  }
  
  return pl;
}


PathList* readDir(const char *path, const char *ext) {
  Directory d = openDirectory(path);
  if (!d) return alloc(sizeof(PathList));
  
  int cnt = directoryGetCount(d);
  size_t size = ASIZEOF(PathList) + cnt*ASIZEOF(char*);
  for(int i = 0; i < cnt; ++i) {
    const char *p = directoryGetFull(d, i);
    if (!fileExists(p) || strcmp(fileext(p), ext)) continue;
    size += strlen(p) + 1;
  }
  
  PathList *pl = alloc(size);
  char **l = (char**)(pl+1);
  char *c = (char*)(l+cnt);
  pl->l = (StrList)l;
  for(int i = 0; i < cnt; ++i) {
    const char *p = directoryGetFull(d, i);
    if (!fileExists(p) || strcmp(fileext(p), ext)) continue;
    l[pl->cnt++] = strcpy(c, p);
    c += strlen(c) + 1;
  }
  
  closeDirectory(d);
  return pl;
}


PathList* readDirs(PathList paths, const char *ext) {
  PathList *a = alloc(sizeof(PathList));
  for(int i = 0; i < paths.cnt; ++i) {
    PathList *b = readDir(paths.l[i], ext);
    PathList *c = mergePaths(*a, *b);
    free(a); free(b); a = c;
  }
  return a;
}


char* findFile(PathList paths, const char *name) {
  for(int i = paths.cnt-1; i >= 0; --i) {
    char *fn = strprintf("%s/%s", paths.l[i], name);
    if (fileExists(fn)) return fn;
    free(fn);
  }
  return NULL;
}



PathList imagePaths() {
  static PathList pl;
  if (pl.l) return pl;
  
  const char *sys = PREFIX "/local/share:" PREFIX "/share";
  const char *xdd = getenv("XDG_DATA_DIRS");
  if (!xdd) xdd = "";
  const char *home = getenv("HOME");
  
  char *paths = home && *home ? strprintf("%s/.local/share:%s:%s", home, xdd, sys)
                              : strprintf("%s:%s", xdd, sys);
  int cnt = 3; // :tail + wd + ud
  for(char* c = paths; *c; ++c) if (*c == ':') ++cnt;
  
  char **l = (char**)alloc(cnt*ASIZEOF(*l));
  pl.l = (StrList)l;
  
  while(pl.cnt < cnt) {
    char* c = strrchr(paths, ':');
    if (c) *c++ = 0; else c = paths;
    if (*c) l[pl.cnt++] = strprintf("%s/%s", c, SUBDIR);
    if (c == paths) break;
  }

  const char *wd = getcwd(NULL, 0);
  const char *ud = getenv(PATHENV_DATA);
  if (wd && *wd) l[pl.cnt++] = strprintf("%s/data/images", wd);
  if (ud && *ud) l[pl.cnt++] = strprintf("%s", ud);
  
  for(int i = 0; i < pl.cnt; ++i) INF("images directory: %s", l[i]);
  return pl;
}


const char* savesPath() {
  static char *path;
  if (path) return path;
  
  if (!path) {
    const char *p = getenv(PATHENV_SAVE);
    if (p && *p) path = strprintf("%s", p);
  }
  if (!path) {
    const char *p = getenv("HOME");
    if (p && *p) path = strprintf("%s/.local/share/%s/saves", p, SUBDIR);
  }
  if (!path) {
    const char *p = getcwd(NULL, 0);
    if (p && *p) path = strprintf("%s/data/saves", p);
  }
  if (!path)
    path = strprintf("./data/saves");

  INF("saves directory: %s", path);
  if (!makeDirectoryRecursive(path))
    WRN("cannot create saves directory: %s", path);
  return path;
}


const char* thumbsPath() {
  static char *path;
  if (!path) {
    path = strprintf("%s/thumbs", savesPath());
    INF("thumbs directory: %s", path);
    if (!makeDirectoryRecursive(path))
      WRN("cannot create thumbs directory: %s", path);
  }
  return path;  
}


const char* settingsFile() {
  static char *path;
  if (!path) {
    path = strprintf("%s/settings.ini", savesPath());
    INF("settings file: %s", path);
  }
  return path;  
}


char* findImage(const char *name) {
  char* fn = strprintf("%s.png", name);
  char* r = findFile(imagePaths(), fn);
  free(fn);
  return r;
}


PathList* imageFiles() { return readDirs(imagePaths(), ".png"); }
PathList* saveFiles() { return readDir(savesPath(), ".puzzle"); }