From b132686a6390b8d1fea6b6b2179838f0bf4b8616 Mon Sep 17 00:00:00 2001 From: John Zaitseff Date: Thu, 7 Jan 2021 23:19:41 +1100 Subject: [PATCH] Use the XDG Base Directory Specification for storing game files --- NEWS | 8 +++++ doc/trader.6 | 23 +++++++++++--- src/utils.c | 84 +++++++++++++++++++++++++++++++++++++++++++++------- src/utils.h | 18 +++++++---- 4 files changed, 113 insertions(+), 20 deletions(-) diff --git a/NEWS b/NEWS index c89531d..3bf4fee 100644 --- a/NEWS +++ b/NEWS @@ -25,6 +25,14 @@ __ https://www.zap.org.au/ Version 7.15 (not yet released) ------------------------------- +Changed the default location where game files are saved from `~/.trader` +to `~/.local/share/trader`, so as to follow the `XDG Base Directory +Specification`__. If set, the ``XDG_DATA_HOME`` environment variable +will override this location. If the `~/.trader` directory is already +present, it will continue to be used. Updated the manual page to suit. + +__ https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html + Updated to GNU Gettext 0.20 or later. Updated to the latest snapshot of the Gnulib GNU Portability Library. Also updated the `INSTALL` file to list the latest tested versions of operating systems, including a new diff --git a/doc/trader.6 b/doc/trader.6 index 0180fb7..85edde9 100644 --- a/doc/trader.6 +++ b/doc/trader.6 @@ -154,6 +154,15 @@ Or, if you prefer the old amber screens of yesteryear: .\" ********************************************************************* .SH ENVIRONMENT .TP +.BR XDG_DATA_HOME ", " HOME +If \fBXDG_DATA_HOME\fR is set to an absolute pathname (that is, a path +that starts with \*(lq\fI/\fR\*(rq), Star Traders will use that +directory, with a subdirectory \fItrader\fR, to store game files. If +this environment variable is not set or does not start with +\*(lq\fI/\fR\*(rq, \fI\(ti/.local/share/trader\fR will be used instead, +where \*(lq\fI\(ti\fR\*(rq represents your home directory, as contained +in the \fBHOME\fR environment variable. +.TP .BR LINES ", " COLUMNS Star Traders uses the Curses library for displaying text on the screen. As such, it will access these two environment variables if the underlying @@ -177,11 +186,17 @@ manual pages for more details on locale settings. .\" ********************************************************************* .SH FILES .TP +.IB \(ti/.local/share/trader/game N +Star Traders stores saved game files in the \fI.local/share/trader\fR +subdirectory in your home directory (unless overriden by the +\fBXDG_DATA_HOME\fR environment variable). \fIN\fR is a number between +\fB1\fR and \fB9\fR inclusive. The game file is scrambled to prevent you +or others from casually cheating! +.TP .IB \(ti/.trader/game N -Star Traders stores saved game files in the \fI.trader\fR subdirectory in -your home directory. \fIN\fR is a number between \fB1\fR and \fB9\fR -inclusive. The game file is scrambled to prevent you or others from -casually cheating! +If the \fI\(ti/.trader\fR directory exists, game files will be read from +and saved to this location instead. This is for compatibility with +versions of Star Traders prior to version 7.15. .\" ********************************************************************* .SH BUGS None yet known... diff --git a/src/utils.c b/src/utils.c index c708e20..4fe5af0 100644 --- a/src/utils.c +++ b/src/utils.c @@ -53,6 +53,10 @@ wchar_t *mon_thousands_sep; // Locale's monetary thousands separator * Module-specific constants and macros * ************************************************************************/ +#define DIRSEP "/" // Directory separator +#define HIDDEN_PATH "." // Hidden file start char +#define XDG_DATA_DEFAULT ".local" DIRSEP "share" + #define GAME_FILENAME_PROTO "game%d" #define GAME_FILENAME_BUFSIZE 16 @@ -306,10 +310,15 @@ void init_program_name (const char *argv0) /* This implementation assumes a POSIX environment with an ASCII-safe character encoding (such as ASCII or UTF-8). */ + const char *dirsep = DIRSEP; + + + assert(strlen(dirsep) == 1); + if (argv0 == NULL || *argv0 == '\0') { program_name = PACKAGE; } else { - char *p = strrchr(argv0, '/'); + char *p = strrchr(argv0, dirsep[0]); if (p != NULL && *++p != '\0') { program_name = xstrdup(p); @@ -343,21 +352,74 @@ const char *home_directory (void) const char *data_directory (void) { - /* This implementation assumes a POSIX environment by using "/" as + /* This implementation assumes a POSIX environment by using DIRSEP as the directory separator. It also assumes a dot-starting directory name is permissible (again, true on POSIX systems) and that the character encoding is ASCII-safe. */ + const char *dirsep = DIRSEP; + const char *dirsep_hiddenpath = DIRSEP HIDDEN_PATH; + const char *datahome_part = DIRSEP XDG_DATA_DEFAULT DIRSEP; + + struct stat statbuf; + + + assert(strlen(dirsep) == 1); + if (data_directory_str == NULL) { const char *home = home_directory(); + const char *xdg_data_home = getenv("XDG_DATA_HOME"); - if (program_name != NULL && home != NULL) { - char *p = xmalloc(strlen(home) + strlen(program_name) + 3); + if (program_name != NULL) { + int len_program_name = strlen(program_name); - strcpy(p, home); - strcat(p, "/."); - strcat(p, program_name); - data_directory_str = p; + if (home != NULL) { + char *p = xmalloc(strlen(home) + strlen(dirsep_hiddenpath) + + len_program_name + 1); + + strcpy(p, home); + strcat(p, dirsep_hiddenpath); + strcat(p, program_name); + + if (stat(p, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) { + // Directory "$HOME/.trader" exists: use that + data_directory_str = p; + } else { + free(p); + } + } + + if (data_directory_str == NULL && xdg_data_home != NULL + && *xdg_data_home != '\0' && *xdg_data_home == dirsep[0]) { + // Use directory "$XDG_DATA_HOME/trader" + + /* Note that the XDG Base Directory Specification v0.7 + requires XDG_DATA_HOME to contain an absolute path: + the variable must be ignored if it does not start with + the directory separator DIRSEP ("/") */ + + char *p = xmalloc(strlen(xdg_data_home) + strlen(DIRSEP) + + len_program_name + 1); + + strcpy(p, xdg_data_home); + strcat(p, dirsep); + strcat(p, program_name); + + data_directory_str = p; + } + + if (data_directory_str == NULL && home != NULL) { + // Use directory "$HOME/.local/share/trader" + + char *p = xmalloc(strlen(home) + strlen(datahome_part) + + len_program_name + 1); + + strcpy(p, home); + strcat(p, datahome_part); + strcat(p, program_name); + + data_directory_str = p; + } } } @@ -373,6 +435,8 @@ char *game_filename (int gamenum) /* This implementation assumes a POSIX environment and an ASCII-safe character encoding. */ + const char *dirsep = DIRSEP; + char buf[GAME_FILENAME_BUFSIZE]; // Buffer for part of filename const char *dd; // Data directory @@ -387,10 +451,10 @@ char *game_filename (int gamenum) if (dd == NULL) { return xstrdup(buf); } else { - char *p = xmalloc(strlen(dd) + strlen(buf) + 2); + char *p = xmalloc(strlen(dd) + strlen(dirsep) + strlen(buf) + 1); strcpy(p, dd); - strcat(p, "/"); + strcat(p, dirsep); strcat(p, buf); return p; } diff --git a/src/utils.h b/src/utils.h index ff408a9..a513f7f 100644 --- a/src/utils.h +++ b/src/utils.h @@ -99,12 +99,18 @@ extern const char *home_directory (void); Parameters: (none) Returns: const char * - Pointer to data directory - This function returns the full pathname to a potentially-writable - subdirectory within the user's home directory. Essentially, this - function returns home_directory() + "/." + program_name. Note that - this path is NOT created by this function, nor is the ability to write - to this path checked. NULL is returned if this path cannot be - determined. + This function returns the full pathname to a potentially-writable data + directory, usually within the user's home directory. + + Assuming program_name is set to "trader", if "$HOME/.trader" exists, + that directory is returned as the data directory. Otherwise, if the + environment variable XDG_DATA_HOME is set and contains an absolute + pathname, "$XDG_DATA_HOME/trader" is returned. Otherwise, + "$HOME/.local/share/trader" is returned. + + Note that the returned path is NOT created by this function, nor is the + ability to read from or write to this path checked. NULL is returned + if the path cannot be determined. */ extern const char *data_directory (void);