From 24d6a925d1e70c420ca7e83eb96eefd388e188a8 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 21 Mar 2012 23:30:50 -0400 Subject: [PATCH] Added PHYSFS_getPrefDir(). --- docs/TODO.txt | 6 ---- extras/physfs-swig.i | 1 + src/physfs.c | 54 +++++++++++++++++++++++++++- src/physfs.h | 81 ++++++++++++++++++++++++++++++++++++++---- src/physfs_internal.h | 15 +++++++- src/platform_beos.cpp | 13 +++++++ src/platform_macosx.c | 13 +++++++ src/platform_unix.c | 30 ++++++++++++++++ src/platform_windows.c | 17 +++++++++ test/test_physfs.c | 17 +++++++++ test/test_physfs.pl | 1 + test/test_physfs.rb | 1 + 12 files changed, 235 insertions(+), 14 deletions(-) diff --git a/docs/TODO.txt b/docs/TODO.txt index 05d1a013..0b507418 100644 --- a/docs/TODO.txt +++ b/docs/TODO.txt @@ -6,11 +6,6 @@ Some might be dupes, some might be done already, some might be bad ideas. From http://icculus.org/pipermail/physfs/2009-March/000698.html ... -- Add an API to find the "pref path" ... this is the directory where an - app's configuration data is supposed to go...which is usually somewhere - under the user directory, but not always. As it is platform-dependent, - platform-version dependent and sometimes even user-dependent, this should be - handled by the library and not the app. - Archives formats provided by the implementation. - Write support for various archives. I haven't decided how to do this yet, but I'd like to. @@ -50,7 +45,6 @@ From old TODO.txt... - Use __cdecl in physfs.h? - Look for FIXMEs (many marked with "!!!" in comments). - Find some way to relax or remove the security model for external tools. -- OSX shouldn't use ~/.app for userdir. - fscanf and fprintf support in extras dir. - Why do we call it openArchive and dirClose? - Sanity check byte order at runtime. diff --git a/extras/physfs-swig.i b/extras/physfs-swig.i index faa69f87..040039b3 100644 --- a/extras/physfs-swig.i +++ b/extras/physfs-swig.i @@ -91,6 +91,7 @@ %rename(unmount) PHYSFS_unmount; %rename(mountMemory) PHYSFS_mountMemory; %rename(mountHandle) PHYSFS_mountHandle; +%rename(getPrefDir) PHYSFS_getPrefDir; #endif %include "../src/physfs.h" diff --git a/src/physfs.c b/src/physfs.c index 4a1cd3a8..25415f7c 100644 --- a/src/physfs.c +++ b/src/physfs.c @@ -135,6 +135,7 @@ static FileHandle *openWriteList = NULL; static FileHandle *openReadList = NULL; static char *baseDir = NULL; static char *userDir = NULL; +static char *prefDir = NULL; static int allowSymLinks = 0; /* mutexes ... */ @@ -1312,6 +1313,12 @@ int PHYSFS_deinit(void) userDir = NULL; } /* if */ + if (prefDir != NULL) + { + allocator.Free(prefDir); + prefDir = NULL; + } /* if */ + allowSymLinks = 0; initialized = 0; @@ -1370,15 +1377,60 @@ void PHYSFS_getCdRomDirsCallback(PHYSFS_StringCallback callback, void *data) } /* PHYSFS_getCdRomDirsCallback */ +const char *PHYSFS_getPrefDir(const char *org, const char *app) +{ + const char dirsep = __PHYSFS_platformDirSeparator; + char *ptr = NULL; + PHYSFS_Stat statbuf; + int exists = 0; + + BAIL_IF_MACRO(!initialized, PHYSFS_ERR_NOT_INITIALIZED, 0); + BAIL_IF_MACRO(!org, PHYSFS_ERR_INVALID_ARGUMENT, NULL); + BAIL_IF_MACRO(*org == '\0', PHYSFS_ERR_INVALID_ARGUMENT, NULL); + BAIL_IF_MACRO(!app, PHYSFS_ERR_INVALID_ARGUMENT, NULL); + BAIL_IF_MACRO(*app == '\0', PHYSFS_ERR_INVALID_ARGUMENT, NULL); + + allocator.Free(prefDir); + prefDir = __PHYSFS_platformCalcPrefDir(org, app); + BAIL_IF_MACRO(!prefDir, ERRPASS, NULL); + + #if !PHYSFS_PLATFORM_WINDOWS /* Windows guarantees the dir exists here. */ + if (__PHYSFS_platformStat(prefDir, &exists, &statbuf)) + return prefDir; + + for (ptr = strchr(prefDir, dirsep); ptr; ptr = strchr(ptr+1, dirsep)) + { + *ptr = '\0'; + __PHYSFS_platformMkDir(prefDir); + *ptr = dirsep; + } /* for */ + + if (!__PHYSFS_platformMkDir(prefDir)) + { + allocator.Free(prefDir); + prefDir = NULL; + } /* if */ + #endif + + return prefDir; +} /* PHYSFS_getPrefDir */ + + const char *PHYSFS_getBaseDir(void) { return baseDir; /* this is calculated in PHYSFS_init()... */ } /* PHYSFS_getBaseDir */ -const char *PHYSFS_getUserDir(void) +const char *__PHYSFS_getUserDir(void) /* not deprecated internal version. */ { return userDir; /* this is calculated in PHYSFS_init()... */ +} /* __PHYSFS_getUserDir */ + + +const char *PHYSFS_getUserDir(void) +{ + return __PHYSFS_getUserDir(); } /* PHYSFS_getUserDir */ diff --git a/src/physfs.h b/src/physfs.h index 0537c9fd..b261d685 100644 --- a/src/physfs.h +++ b/src/physfs.h @@ -111,7 +111,7 @@ * use the base dir for both searching and writing. There is a helper * function (PHYSFS_setSaneConfig()) that puts together a basic configuration * for you, based on a few parameters. Also see the comments on - * PHYSFS_getBaseDir(), and PHYSFS_getUserDir() for info on what those + * PHYSFS_getBaseDir(), and PHYSFS_getPrefDir() for info on what those * are and how they can help you determine an optimal search path. * * PhysicsFS 2.0 adds the concept of "mounting" archives to arbitrary points @@ -765,7 +765,7 @@ PHYSFS_DECL char **PHYSFS_getCdRomDirs(void); * * \return READ ONLY string of base dir in platform-dependent notation. * - * \sa PHYSFS_getUserDir + * \sa PHYSFS_getPrefDir */ PHYSFS_DECL const char *PHYSFS_getBaseDir(void); @@ -774,6 +774,8 @@ PHYSFS_DECL const char *PHYSFS_getBaseDir(void); * \fn const char *PHYSFS_getUserDir(void) * \brief Get the path where user's home directory resides. * + * \deprecated As of PhysicsFS 2.1, you probably want PHYSFS_getPrefDir(). + * * Helper function. * * Get the "user dir". This is meant to be a suggestion of where a specific @@ -783,14 +785,12 @@ PHYSFS_DECL const char *PHYSFS_getBaseDir(void); * where "username" will either be the login name, or "default" if the * platform doesn't support multiple users, either. * - * You should probably use the user dir as the basis for your write dir, and - * also put it near the beginning of your search path. - * * \return READ ONLY string of user dir in platform-dependent notation. * * \sa PHYSFS_getBaseDir + * \sa PHYSFS_getPrefDir */ -PHYSFS_DECL const char *PHYSFS_getUserDir(void); +PHYSFS_DECL const char *PHYSFS_getUserDir(void) PHYSFS_DEPRECATED; /** @@ -3230,6 +3230,75 @@ PHYSFS_DECL const char *PHYSFS_getErrorByCode(PHYSFS_ErrorCode code); */ PHYSFS_DECL void PHYSFS_setErrorCode(PHYSFS_ErrorCode code); + +/** + * \fn const char *PHYSFS_getPrefDir(const char *org, const char *app) + * \brief Get the user-and-app-specific path where files can be written. + * + * Helper function. + * + * Get the "pref dir". This is meant to be where users can write personal + * files (preferences and save games, etc) that are specific to your + * application. This directory is unique per user, per application. + * + * This function will decide the appropriate location in the native filesystem, + * create the directory if necessary, and return a string in + * platform-dependent notation, suitable for passing to PHYSFS_setWriteDir(). + * + * On Windows, this might look like: + * "C:\\Users\\bob\\AppData\\Roaming\\My Company\\My Program Name" + * + * On Linux, this might look like: + * "/home/bob/.local/share/My Program Name" + * + * On Mac OS X, this might look like: + * "/Users/bob/Library/Application Support/My Program Name" + * + * (etc.) + * + * You should probably use the pref dir for your write dir, and also put it + * near the beginning of your search path. Older versions of PhysicsFS + * offered only PHYSFS_getUserDir() and left you to figure out where the + * files should go under that tree. This finds the correct location + * for whatever platform, which not only changes between operating systems, + * but also versions of the same operating system. + * + * You specify the name of your organization (if it's not a real organization, + * your name or an Internet domain you own might do) and the name of your + * application. These should be proper names. + * + * Both the (org) and (app) strings may become part of a directory name, so + * please follow these rules: + * + * - Try to use the same org string (including case-sensitivity) for + * all your applications that use this function. + * - Always use a unique app string for each one, and make sure it never + * changes for an app once you've decided on it. + * - Unicode characters are legal, as long as it's UTF-8 encoded, but... + * - ...only use letters, numbers, and spaces. Avoid punctuation like + * "Game Name 2: Bad Guy's Revenge!" ... "Game Name 2" is sufficient. + * + * The pointer returned by this function remains valid until you call this + * function again, or call PHYSFS_deinit(). This is not necessarily a fast + * call, though, so you should call this once at startup and copy the string + * if you need it. + * + * You should assume the path returned by this function is the only safe + * place to write files (and that PHYSFS_getUserDir() and PHYSFS_getBaseDir(), + * while they might be writable, or even parents of the returned path, aren't + * where you should be writing things). + * + * \param org The name of your organization. + * \param app The name of your application. + * \return READ ONLY string of user dir in platform-dependent notation. NULL + * if there's a problem (creating directory failed, etc). + * + * \sa PHYSFS_getBaseDir + * \sa PHYSFS_getUserDir + */ +PHYSFS_DECL const char *PHYSFS_getPrefDir(const char *org, const char *app); + + /* Everything above this line is part of the PhysicsFS 2.1 API. */ diff --git a/src/physfs_internal.h b/src/physfs_internal.h index 50ab89ca..a60d4dba 100644 --- a/src/physfs_internal.h +++ b/src/physfs_internal.h @@ -633,13 +633,26 @@ char *__PHYSFS_platformCalcBaseDir(const char *argv0); */ char *__PHYSFS_platformGetUserName(void); -/* +/* !!! FIXME: should this be CalcUserDir, to match CalcBaseDir? * Get the platform-specific user dir. * Caller will allocator.Free() the retval if it's not NULL. If it's NULL, * the userdir will default to basedir/username. */ char *__PHYSFS_platformGetUserDir(void); + +/* This is the cached version from PHYSFS_init(). This is a fast call. */ +const char *__PHYSFS_getUserDir(void); /* not deprecated internal version. */ + +/* + * Get the platform-specific pref dir. + * Caller will allocator.Free() the retval if it's not NULL. If it's NULL, + * it's a total failure. Caller will make missing directories if necessary; + * this just reports the final path. + */ +char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app); + + /* * Return a pointer that uniquely identifies the current thread. * On a platform without threading, (0x1) will suffice. These numbers are diff --git a/src/platform_beos.cpp b/src/platform_beos.cpp index c9a93635..9b09c8c1 100644 --- a/src/platform_beos.cpp +++ b/src/platform_beos.cpp @@ -183,6 +183,19 @@ char *__PHYSFS_platformCalcBaseDir(const char *argv0) } /* __PHYSFS_platformCalcBaseDir */ +char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app) +{ + /* !!! FIXME: there's a real API to determine this */ + const char *userdir = __PHYSFS_getUserDir(); + const char *append = "config/settings/"; + const size_t len = strlen(userdir) + strlen(append) + strlen(app) + 1; + char *retval = allocator.Malloc(len); + BAIL_IF_MACRO(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + snprintf(retval, len, "%s%s%s", userdir, append, app); + return retval; +} /* __PHYSFS_platformCalcPrefDir */ + + void *__PHYSFS_platformGetThreadID(void) { return (void *) find_thread(NULL); diff --git a/src/platform_macosx.c b/src/platform_macosx.c index 2cb72b85..cc44ed91 100644 --- a/src/platform_macosx.c +++ b/src/platform_macosx.c @@ -289,6 +289,19 @@ char *__PHYSFS_platformCalcBaseDir(const char *argv0) } /* __PHYSFS_platformCalcBaseDir */ +char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app) +{ + /* !!! FIXME: there's a real API to determine this */ + const char *userdir = __PHYSFS_getUserDir(); + const char *append = "Library/Application Support/"; + const size_t len = strlen(userdir) + strlen(append) + strlen(app) + 1; + char *retval = allocator.Malloc(len); + BAIL_IF_MACRO(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + snprintf(retval, len, "%s%s%s", userdir, append, app); + return retval; +} /* __PHYSFS_platformCalcPrefDir */ + + /* Platform allocator uses default CFAllocator at PHYSFS_init() time. */ static CFAllocatorRef cfallocdef = NULL; diff --git a/src/platform_unix.c b/src/platform_unix.c index c992537b..20e42a96 100644 --- a/src/platform_unix.c +++ b/src/platform_unix.c @@ -293,6 +293,36 @@ char *__PHYSFS_platformCalcBaseDir(const char *argv0) } /* __PHYSFS_platformCalcBaseDir */ +char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app) +{ + /* + * We use XDG's base directory spec, even if you're not on Linux. + * This isn't strictly correct, but the results are relatively sane + * in any case. + * + * http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + */ + const char *envr = getenv("XDG_DATA_HOME"); + const char *append = "/"; + char *retval = NULL; + size_t len = 0; + + if (!envr) + { + /* You end up with "$HOME/.local/share/Game Name 2" */ + envr = __PHYSFS_getUserDir(); + BAIL_IF_MACRO(!envr, ERRPASS, NULL); /* oh well. */ + append = ".local/share/"; + } /* if */ + + len = strlen(envr) + strlen(append) + strlen(app) + 1; + retval = (char *) allocator.Malloc(len); + BAIL_IF_MACRO(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + snprintf(retval, len, "%s%s%s", envr, append, app); + return retval; +} /* __PHYSFS_platformCalcPrefDir */ + + int __PHYSFS_platformSetDefaultAllocator(PHYSFS_Allocator *a) { return 0; /* just use malloc() and friends. */ diff --git a/src/platform_windows.c b/src/platform_windows.c index f7d0013e..fd288fc2 100644 --- a/src/platform_windows.c +++ b/src/platform_windows.c @@ -88,6 +88,7 @@ typedef struct } WinApiFile; +/* !!! FIXME: we cache userDir in physfs.c during PHYSFS_init(), too. */ static char *userDir = NULL; static HANDLE libUserEnv = NULL; static HANDLE detectCDThreadHandle = NULL; @@ -441,6 +442,22 @@ char *__PHYSFS_platformCalcBaseDir(const char *argv0) } /* __PHYSFS_platformCalcBaseDir */ +char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app) +{ + // Vista and later has a new API for this, but SHGetFolderPath works there, + // and apparently just wraps the new API. This is the new way to do it: + // SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_CREATE, + // NULL, &wszPath); + + WCHAR path[MAX_PATH]; + if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, + NULL, 0, path))) + BAIL_MACRO(PHYSFS_ERR_OS_ERROR, NULL); + + return unicodeToUtf8Heap(path); +} /* __PHYSFS_platformCalcPrefDir */ + + char *__PHYSFS_platformGetUserName(void) { DWORD bufsize = 0; diff --git a/test/test_physfs.c b/test/test_physfs.c index 2b77e772..fa771f2f 100644 --- a/test/test_physfs.c +++ b/test/test_physfs.c @@ -23,6 +23,9 @@ #include +/* Define this, so the compiler doesn't complain about using old APIs. */ +#define PHYSFS_DEPRECATED + #include "physfs.h" #define TEST_VERSION_MAJOR 2 @@ -386,6 +389,19 @@ static int cmd_getuserdir(char *args) } /* cmd_getuserdir */ +static int cmd_getprefdir(char *args) +{ + char *org; + char *appName; + char *ptr = args; + + org = ptr; + ptr = strchr(ptr, ' '); *ptr = '\0'; ptr++; appName = ptr; + printf("Pref dir is [%s].\n", PHYSFS_getPrefDir(org, appName)); + return 1; +} /* cmd_getprefdir */ + + static int cmd_getwritedir(char *args) { printf("Write dir is [%s].\n", PHYSFS_getWriteDir()); @@ -1130,6 +1146,7 @@ static const command_info commands[] = { "getsearchpath", cmd_getsearchpath, 0, NULL }, { "getbasedir", cmd_getbasedir, 0, NULL }, { "getuserdir", cmd_getuserdir, 0, NULL }, + { "getprefdir", cmd_getprefdir, 2, " " }, { "getwritedir", cmd_getwritedir, 0, NULL }, { "setwritedir", cmd_setwritedir, 1, "" }, { "permitsymlinks", cmd_permitsyms, 1, "<1or0>" }, diff --git a/test/test_physfs.pl b/test/test_physfs.pl index 9add64cc..a7cdeec5 100755 --- a/test/test_physfs.pl +++ b/test/test_physfs.pl @@ -11,6 +11,7 @@ print "user dir: ", physfs::getUserDir(), "\n"; print "base dir: ", physfs::getBaseDir(), "\n"; +print "pref dir: ", physfs::getPrefDir("icculus.org", "test_physfs"), "\n"; print "deinit...\n"; physfs::deinit(); diff --git a/test/test_physfs.rb b/test/test_physfs.rb index b6acb5b7..9632c4b5 100755 --- a/test/test_physfs.rb +++ b/test/test_physfs.rb @@ -9,6 +9,7 @@ puts "user dir: " + Physfs.getUserDir() puts "base dir: " + Physfs.getBaseDir() +puts "pref dir: " + Physfs.getPrefDir("icculus.org", "test_physfs") puts "deinit..." Physfs.deinit()