Added PHYSFS_getPrefDir().
--- a/docs/TODO.txt Tue Mar 20 15:44:10 2012 -0400
+++ b/docs/TODO.txt Wed Mar 21 23:30:50 2012 -0400
@@ -6,11 +6,6 @@
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 @@
- 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.
--- a/extras/physfs-swig.i Tue Mar 20 15:44:10 2012 -0400
+++ b/extras/physfs-swig.i Wed Mar 21 23:30:50 2012 -0400
@@ -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"
--- a/src/physfs.c Tue Mar 20 15:44:10 2012 -0400
+++ b/src/physfs.c Wed Mar 21 23:30:50 2012 -0400
@@ -135,6 +135,7 @@
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 @@
userDir = NULL;
} /* if */
+ if (prefDir != NULL)
+ {
+ allocator.Free(prefDir);
+ prefDir = NULL;
+ } /* if */
+
allowSymLinks = 0;
initialized = 0;
@@ -1370,15 +1377,60 @@
} /* 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) /* not deprecated internal version. */
+{
+ return userDir; /* this is calculated in PHYSFS_init()... */
+} /* __PHYSFS_getUserDir */
+
+
const char *PHYSFS_getUserDir(void)
{
- return userDir; /* this is calculated in PHYSFS_init()... */
+ return __PHYSFS_getUserDir();
} /* PHYSFS_getUserDir */
--- a/src/physfs.h Tue Mar 20 15:44:10 2012 -0400
+++ b/src/physfs.h Wed Mar 21 23:30:50 2012 -0400
@@ -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 @@
*
* \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 @@
* \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 @@
* 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 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. */
--- a/src/physfs_internal.h Tue Mar 20 15:44:10 2012 -0400
+++ b/src/physfs_internal.h Wed Mar 21 23:30:50 2012 -0400
@@ -633,13 +633,26 @@
*/
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
--- a/src/platform_beos.cpp Tue Mar 20 15:44:10 2012 -0400
+++ b/src/platform_beos.cpp Wed Mar 21 23:30:50 2012 -0400
@@ -183,6 +183,19 @@
} /* __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);
--- a/src/platform_macosx.c Tue Mar 20 15:44:10 2012 -0400
+++ b/src/platform_macosx.c Wed Mar 21 23:30:50 2012 -0400
@@ -289,6 +289,19 @@
} /* __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;
--- a/src/platform_unix.c Tue Mar 20 15:44:10 2012 -0400
+++ b/src/platform_unix.c Wed Mar 21 23:30:50 2012 -0400
@@ -293,6 +293,36 @@
} /* __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. */
--- a/src/platform_windows.c Tue Mar 20 15:44:10 2012 -0400
+++ b/src/platform_windows.c Wed Mar 21 23:30:50 2012 -0400
@@ -88,6 +88,7 @@
} 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 @@
} /* __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;
--- a/test/test_physfs.c Tue Mar 20 15:44:10 2012 -0400
+++ b/test/test_physfs.c Wed Mar 21 23:30:50 2012 -0400
@@ -23,6 +23,9 @@
#include <time.h>
+/* 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 @@
} /* 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 @@
{ "getsearchpath", cmd_getsearchpath, 0, NULL },
{ "getbasedir", cmd_getbasedir, 0, NULL },
{ "getuserdir", cmd_getuserdir, 0, NULL },
+ { "getprefdir", cmd_getprefdir, 2, "<org> <app>" },
{ "getwritedir", cmd_getwritedir, 0, NULL },
{ "setwritedir", cmd_setwritedir, 1, "<newWriteDir>" },
{ "permitsymlinks", cmd_permitsyms, 1, "<1or0>" },
--- a/test/test_physfs.pl Tue Mar 20 15:44:10 2012 -0400
+++ b/test/test_physfs.pl Wed Mar 21 23:30:50 2012 -0400
@@ -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();
--- a/test/test_physfs.rb Tue Mar 20 15:44:10 2012 -0400
+++ b/test/test_physfs.rb Wed Mar 21 23:30:50 2012 -0400
@@ -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()