Skip to content

Commit

Permalink
Added PHYSFS_getPrefDir().
Browse files Browse the repository at this point in the history
  • Loading branch information
icculus committed Mar 22, 2012
1 parent 584119a commit 24d6a92
Show file tree
Hide file tree
Showing 12 changed files with 235 additions and 14 deletions.
6 changes: 0 additions & 6 deletions docs/TODO.txt
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions extras/physfs-swig.i
Expand Up @@ -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"
Expand Down
54 changes: 53 additions & 1 deletion src/physfs.c
Expand Up @@ -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 ... */
Expand Down Expand Up @@ -1312,6 +1313,12 @@ int PHYSFS_deinit(void)
userDir = NULL;
} /* if */

if (prefDir != NULL)
{
allocator.Free(prefDir);
prefDir = NULL;
} /* if */

allowSymLinks = 0;
initialized = 0;

Expand Down Expand Up @@ -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 */


Expand Down
81 changes: 75 additions & 6 deletions src/physfs.h
Expand Up @@ -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
Expand Down Expand Up @@ -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);

Expand All @@ -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
Expand All @@ -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;


/**
Expand Down Expand Up @@ -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. */


Expand Down
15 changes: 14 additions & 1 deletion src/physfs_internal.h
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions src/platform_beos.cpp
Expand Up @@ -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);
Expand Down
13 changes: 13 additions & 0 deletions src/platform_macosx.c
Expand Up @@ -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;
Expand Down
30 changes: 30 additions & 0 deletions src/platform_unix.c
Expand Up @@ -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. */
Expand Down
17 changes: 17 additions & 0 deletions src/platform_windows.c
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 24d6a92

Please sign in to comment.