From d154485bc75ac40665ed810b5669f7a8e7315f3a Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 4 Jun 2007 14:54:49 +0000 Subject: [PATCH] Add support for a number of special directories, as defined by the 2007-06-04 Matthias Clasen Add support for a number of special directories, as defined by the xdg-user-dirs specification. (#432651, Bastien Nocera, Emmanuele Bassi, Michael Natterer) * glib/glib.symbols: * glib/gutils.[hc]: Add the GUserDirectory enum and g_get_user_special_dir(), with implementations based on the xdg-user-dirs spec and on native interfaces for Win32 and Carbon. * configure.in: Add Carbon checks. * tests/tetsglib.c: Test g_get_user_special_dir(). svn path=/trunk/; revision=5528 --- ChangeLog | 16 ++ configure.in | 18 +- docs/reference/ChangeLog | 6 + docs/reference/glib/glib-sections.txt | 2 + docs/reference/glib/tmpl/misc_utils.sgml | 24 ++ glib/glib.symbols | 1 + glib/gutils.c | 337 +++++++++++++++++++++++ glib/gutils.h | 37 +++ tests/testglib.c | 8 + 9 files changed, 448 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 78c356d7c..b1893ceae 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2007-06-04 Matthias Clasen + + Add support for a number of special directories, as + defined by the xdg-user-dirs specification. (#432651, + Bastien Nocera, Emmanuele Bassi, Michael Natterer) + + * glib/glib.symbols: + * glib/gutils.[hc]: Add the GUserDirectory enum and + g_get_user_special_dir(), with implementations based + on the xdg-user-dirs spec and on native interfaces + for Win32 and Carbon. + + * configure.in: Add Carbon checks. + + * tests/tetsglib.c: Test g_get_user_special_dir(). + 2007-06-03 Yevgen Muntyan * glib/gregex.c: fixed g_regex_fetch_named* for cases when (?J) diff --git a/configure.in b/configure.in index 68b45dce3..bcdfa1047 100644 --- a/configure.in +++ b/configure.in @@ -145,11 +145,21 @@ case $host in ;; esac - AC_MSG_RESULT([$glib_native_win32]) + +glib_have_carbon=no +AC_MSG_CHECKING([for Mac OS X Carbon support]) +AC_TRY_CPP([ +#include +#include +], glib_have_carbon=yes) + +AC_MSG_RESULT([$glib_have_carbon]) + AM_CONDITIONAL(OS_WIN32, [test "$glib_native_win32" = "yes"]) AM_CONDITIONAL(OS_UNIX, [test "$glib_native_win32" != "yes"]) AM_CONDITIONAL(OS_LINUX, [test "$glib_os_linux" = "yes"]) +AM_CONDITIONAL(OS_CARBON, [test "$glib_have_carbon" = "yes"]) AC_SUBST(GLIB_DEF) AC_SUBST(GMODULE_DEF) AC_SUBST(GOBJECT_DEF) @@ -180,6 +190,12 @@ else fi AC_SUBST(LIBTOOL_EXPORT_OPTIONS) +if test "x$glib_have_carbon" = "xyes"; then + AC_DEFINE(HAVE_CARBON, 1, [define to 1 if Carbon is available]) + LDFLAGS="$LDFLAGS -framework Carbon" +fi + + dnl declare --enable-* args and collect ac_help strings AC_ARG_ENABLE(debug, AC_HELP_STRING([--enable-debug=@<:@no/minimum/yes@:>@], diff --git a/docs/reference/ChangeLog b/docs/reference/ChangeLog index 58b8c4005..da22463b0 100644 --- a/docs/reference/ChangeLog +++ b/docs/reference/ChangeLog @@ -1,3 +1,9 @@ +2007-06-04 Matthias Clasen + + * glib/glib-sections.txt: + * glib/tmpl/misc_utils.sgml: Add g_get_special_user_dir() + and GUserDirectory. + 2007-05-30 Matthias Clasen * glib/tmpl/i18n.sgml: Add some hints about diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index 04a5542f5..2f50e2de8 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -1388,6 +1388,8 @@ g_get_real_name g_get_user_cache_dir g_get_user_data_dir g_get_user_config_dir +GUserDirectory +g_get_user_special_dir g_get_system_data_dirs g_get_system_config_dirs diff --git a/docs/reference/glib/tmpl/misc_utils.sgml b/docs/reference/glib/tmpl/misc_utils.sgml index 7fc5f9839..f0f322389 100644 --- a/docs/reference/glib/tmpl/misc_utils.sgml +++ b/docs/reference/glib/tmpl/misc_utils.sgml @@ -125,6 +125,30 @@ These are portable utility functions. @Returns: + + + + + +@G_USER_DIRECTORY_DESKTOP: +@G_USER_DIRECTORY_DOCUMENTS: +@G_USER_DIRECTORY_DOWNLOAD: +@G_USER_DIRECTORY_MUSIC: +@G_USER_DIRECTORY_PICTURES: +@G_USER_DIRECTORY_PUBLIC_SHARE: +@G_USER_DIRECTORY_TEMPLATES: +@G_USER_DIRECTORY_VIDEOS: +@G_USER_N_DIRECTORIES: + + + + + + +@directory: +@Returns: + + diff --git a/glib/glib.symbols b/glib/glib.symbols index 9725f20ce..a924b6ddd 100644 --- a/glib/glib.symbols +++ b/glib/glib.symbols @@ -1397,6 +1397,7 @@ g_get_tmp_dir_utf8 g_get_user_cache_dir g_get_user_config_dir g_get_user_data_dir +g_get_user_special_dir g_get_user_name PRIVATE #ifdef G_OS_WIN32 g_get_user_name_utf8 diff --git a/glib/gutils.c b/glib/gutils.c index a91abb8a2..89413494c 100644 --- a/glib/gutils.c +++ b/glib/gutils.c @@ -40,6 +40,8 @@ #include #include /* For tolower() */ #include +#include +#include #ifdef HAVE_PWD_H #include #endif @@ -100,6 +102,10 @@ # include #endif +#ifdef HAVE_CARBON +#include +#endif + #ifdef HAVE_CODESET #include #endif @@ -1387,6 +1393,13 @@ static gchar *g_user_cache_dir = NULL; static gchar *g_user_config_dir = NULL; static gchar **g_system_config_dirs = NULL; +static gchar **g_user_special_dirs = NULL; +static time_t g_user_special_dirs_mtime = (time_t) -1; +static time_t g_user_special_dirs_stat_time = (time_t) -1; + +/* fifteen minutes of fame for everybody */ +#define G_USER_DIRS_EXPIRE 15 * 60 + #ifdef G_OS_WIN32 static gchar * @@ -2095,6 +2108,330 @@ g_get_user_cache_dir (void) return cache_dir; } +#ifdef HAVE_CARBON + +static gchar * +find_folder (OSType type) +{ + gchar *filename = NULL; + FSRef found; + + if (FSFindFolder (kUserDomain, type, kDontCreateFolder, &found) == noErr) + { + CFURLRef url = CFURLCreateFromFSRef (kCFAllocatorSystemDefault, &found); + + if (url) + { + CFStringRef path = CFURLCopyFileSystemPath (url, kCFURLPOSIXPathStyle); + + if (path) + { + filename = g_strdup (CFStringGetCStringPtr (path, kCFStringEncodingUTF8)); + + if (! filename) + { + filename = g_new0 (gchar, CFStringGetLength (path) * 3 + 1); + + CFStringGetCString (path, filename, + CFStringGetLength (path) * 3 + 1, + kCFStringEncodingUTF8); + } + + CFRelease (path); + } + + CFRelease (url); + } + } + + return filename; +} + +static void +load_user_special_dirs (void) +{ + g_user_special_dirs[G_USER_DIRECTORY_DESKTOP] = find_folder (kDesktopFolderType); + g_user_special_dirs[G_USER_DIRECTORY_DOCUMENTS] = find_folder (kDocumentsFolderType); + g_user_special_dirs[G_USER_DIRECTORY_DOWNLOAD] = find_folder (kDesktopFolderType); /* XXX correct ? */ + g_user_special_dirs[G_USER_DIRECTORY_MUSIC] = find_folder (kMusicDocumentsFolderType); + g_user_special_dirs[G_USER_DIRECTORY_PICTURES] = find_folder (kPictureDocumentsFolderType); + g_user_special_dirs[G_USER_DIRECTORY_PUBLIC_SHARE] = NULL; + g_user_special_dirs[G_USER_DIRECTORY_TEMPLATES] = NULL; + g_user_special_dirs[G_USER_DIRECTORY_VIDEOS] = find_folder (kMovieDocumentsFolderType); +} + +#endif /* HAVE_CARBON */ + +#if defined(G_OS_WIN32) +static void +load_user_special_dirs (void) +{ + g_user_special_dirs[G_USER_DIRECTORY_DESKTOP] = get_special_folder (CSIDL_DESKTOPDIRECTORY); + g_user_special_dirs[G_USER_DIRECTORY_DOCUMENTS] = get_special_folder (CSIDL_MYDOCUMENTS); + g_user_special_dirs[G_USER_DIRECTORY_DOWNLOAD] = get_special_folder (CSIDL_DESKTOPDIRECTORY); /* XXX correct ? */ + g_user_special_dirs[G_USER_DIRECTORY_MUSIC] = get_special_folder (CSIDL_MYMUSIC); + g_user_special_dirs[G_USER_DIRECTORY_PICTURES] = get_special_folder (CSIDL_MYPICTURES); + g_user_special_dirs[G_USER_DIRECTORY_PUBLIC_SHARE] = get_special_folder (CSIDL_COMMON_DOCUMENTS); /* XXX correct ? */ + g_user_special_dirs[G_USER_DIRECTORY_TEMPLATES] = get_special_folder (CSIDL_TEMPLATES); + g_user_special_dirs[G_USER_DIRECTORY_VIDEOS] = get_special_folder (CSIDL_MYVIDEO); +} +#endif /* G_OS_WIN32 */ + +#if defined(G_OS_WIN32) || defined(HAVE_CARBON) +static void +maybe_expire_user_special_dirs (void) +{ + /* expire the user dirs after G_USER_DIRS_EXPIRE seconds */ + time_t now; + + time (&now); + if (now > g_user_special_dirs_mtime + G_USER_DIRS_EXPIRE && + g_user_special_dirs) + { + gint i; + + for (i = 0; i < G_USER_N_DIRECTORIES; i++) + g_free (g_user_special_dirs[i]); + + g_free (g_user_special_dirs); + g_user_special_dirs = NULL; + } + + if (g_user_special_dirs == NULL) + g_user_special_dirs_mtime = now; +} +#endif + +#if defined(G_OS_UNIX) && !defined(HAVE_CARBON) + +/* expire g_user_special_dirs if the config file + * was modified between different reads + */ +static void +maybe_expire_user_special_dirs (void) +{ + gchar *config_file; + struct stat stat_buf; + time_t now; + + /* don't stat() the file more often than necessary */ + time (&now); + if (now < g_user_special_dirs_stat_time + 5) + return; + + g_user_special_dirs_stat_time = now; + + config_file = g_build_filename (g_get_user_config_dir (), + "user-dirs.dirs", + NULL); + + if (stat (config_file, &stat_buf) < 0) + goto out; + + if (stat_buf.st_mtime != g_user_special_dirs_mtime && + g_user_special_dirs != NULL) + { + gint i; + + for (i = 0; i < G_USER_N_DIRECTORIES; i++) + g_free (g_user_special_dirs[i]); + + g_free (g_user_special_dirs); + g_user_special_dirs = NULL; + } + + g_user_special_dirs_mtime = stat_buf.st_mtime; + +out: + g_free (config_file); +} + +/* adapted from xdg-user-dir-lookup.c + * + * Copyright (C) 2007 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be +/* included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +static void +load_user_special_dirs (void) +{ + gchar *config_file; + gchar *data; + gchar **lines; + gint n_lines, i; + + config_file = g_build_filename (g_get_user_config_dir (), + "user-dirs.dirs", + NULL); + + if (!g_file_get_contents (config_file, &data, NULL, NULL)) + { + g_free (config_file); + return; + } + + lines = g_strsplit (data, "\n", -1); + n_lines = g_strv_length (lines); + g_free (data); + + for (i = 0; i < n_lines; i++) + { + gchar *buffer = lines[i]; + gchar *d, *p; + gint len; + gboolean is_relative = FALSE; + GUserDirectory directory; + + /* Remove newline at end */ + len = strlen (buffer); + if (len > 0 && buffer[len - 1] == '\n') + buffer[len - 1] = 0; + + p = buffer; + while (*p == ' ' || *p == '\t') + p++; + + if (strncmp (p, "XDG_DESKTOP_DIR", strlen ("XDG_DESKTOP_DIR")) == 0) + { + directory = G_USER_DIRECTORY_DESKTOP; + p += strlen ("XDG_DESKTOP_DIR"); + } + else if (strncmp (p, "XDG_DOCUMENTS_DIR", strlen ("XDG_DOCUMENTS_DIR")) == 0) + { + directory = G_USER_DIRECTORY_DOCUMENTS; + p += strlen ("XDG_DOCUMENTS_DIR"); + } + else if (strncmp (p, "XDG_DOWNLOAD_DIR", strlen ("XDG_DOWNLOAD_DIR")) == 0) + { + directory = G_USER_DIRECTORY_DOWNLOAD; + p += strlen ("XDG_DOWNLOAD_DIR"); + } + else if (strncmp (p, "XDG_MUSIC_DIR", strlen ("XDG_MUSIC_DIR")) == 0) + { + directory = G_USER_DIRECTORY_MUSIC; + p += strlen ("XDG_MUSIC_DIR"); + } + else if (strncmp (p, "XDG_PICTURES_DIR", strlen ("XDG_PICTURES_DIR")) == 0) + { + directory = G_USER_DIRECTORY_PICTURES; + p += strlen ("XDG_PICTURES_DIR"); + } + else if (strncmp (p, "XDG_PUBLICSHARE_DIR", strlen ("XDG_PUBLICSHARE_DIR")) == 0) + { + directory = G_USER_DIRECTORY_PUBLIC_SHARE; + p += strlen ("XDG_PUBLICSHARE_DIR"); + } + else if (strncmp (p, "XDG_TEMPLATES_DIR", strlen ("XDG_TEMPLATES_DIR")) == 0) + { + directory = G_USER_DIRECTORY_TEMPLATES; + p += strlen ("XDG_TEMPLATES_DIR"); + } + else if (strncmp (p, "XDG_VIDEOS_DIR", strlen ("XDG_VIDEOS_DIR")) == 0) + { + directory = G_USER_DIRECTORY_VIDEOS; + p += strlen ("XDG_VIDEOS_DIR"); + } + else + continue; + + while (*p == ' ' || *p == '\t') + p++; + + if (*p != '=') + continue; + p++; + + while (*p == ' ' || *p == '\t') + p++; + + if (*p != '"') + continue; + p++; + + if (strncmp (p, "$HOME", 5) == 0) + { + p += 5; + is_relative = TRUE; + } + else if (*p != '/') + continue; + + d = strrchr (p, '"'); + if (!d) + continue; + *d = 0; + + d = p; + + /* remove trailing slashes */ + len = strlen (d); + if (d[len - 1] == '/') + d[len - 1] = 0; + + if (is_relative) + g_user_special_dirs[directory] = g_build_filename (g_get_home_dir (), d, NULL); + else + g_user_special_dirs[directory] = g_strdup (d); + } + + g_strfreev (lines); + g_free (config_file); +} + +#endif /* G_OS_UNIX && !HAVE_CARBON */ + +/** + * g_get_user_special_dir: + * @directory: the logical id of special directory + * + * Returns the full path of a special directory using its logical id. + * + * On Unix this is done using the XDG special user directories. + * + * Return value: the path to the specified special directory, or %NULL + * if the logical id was not found. The returned string is owned by + * GLib and should not be modified or freed. + * + * Since: 2.14 + */ +G_CONST_RETURN gchar * +g_get_user_special_dir (GUserDirectory directory) +{ + g_return_val_if_fail (directory >= G_USER_DIRECTORY_DESKTOP && + directory < G_USER_N_DIRECTORIES, NULL); + + G_LOCK (g_utils_global); + + maybe_expire_user_special_dirs (); + if (g_user_special_dirs == NULL) + { + g_user_special_dirs = g_new0 (gchar *, G_USER_N_DIRECTORIES); + load_user_special_dirs (); + } + + G_UNLOCK (g_utils_global); + + return g_user_special_dirs[directory]; +} + #ifdef G_OS_WIN32 #undef g_get_system_data_dirs diff --git a/glib/gutils.h b/glib/gutils.h index 03df07613..4e64b71c9 100644 --- a/glib/gutils.h +++ b/glib/gutils.h @@ -145,6 +145,43 @@ G_CONST_RETURN gchar* G_CONST_RETURN * g_get_system_config_dirs (void); G_CONST_RETURN gchar* G_CONST_RETURN * g_get_language_names (void); +/** + * GUserDirectory: + * + * These are logical ids for special directories which are defined + * depending on the platform used. You should use g_get_user_special_dir() + * to retrieve the full path associated to the logical id. + * + * The #GUserDirectory enumeration can be extended at later date. Not + * every platform has a directory for every logical id in this + * enumeration. + * + * @G_USER_DIRECTORY_DESKTOP: the user's Desktop directory + * @G_USER_DIRECTORY_DOCUMENTS: the user's Documents directory + * @G_USER_DIRECTORY_DOWNLOAD: the user's Downloads directory + * @G_USER_DIRECTORY_MUSIC: the user's Music directory + * @G_USER_DIRECTORY_PICTURES: the user's Pictures directory + * @G_USER_DIRECTORY_PUBLIC_SHARE: the user's shared directory + * @G_USER_DIRECTORY_TEMPLATES: the user's Templates directory + * @G_USER_DIRECTORY_VIDEOS: the user's Movies directory + * + * Since: 2.14 + */ +typedef enum { + G_USER_DIRECTORY_DESKTOP, + G_USER_DIRECTORY_DOCUMENTS, + G_USER_DIRECTORY_DOWNLOAD, + G_USER_DIRECTORY_MUSIC, + G_USER_DIRECTORY_PICTURES, + G_USER_DIRECTORY_PUBLIC_SHARE, + G_USER_DIRECTORY_TEMPLATES, + G_USER_DIRECTORY_VIDEOS, + + G_USER_N_DIRECTORIES +} GUserDirectory; + +G_CONST_RETURN gchar* g_get_user_special_dir (GUserDirectory directory); + typedef struct _GDebugKey GDebugKey; struct _GDebugKey { diff --git a/tests/testglib.c b/tests/testglib.c index 0a846653c..9105321fb 100644 --- a/tests/testglib.c +++ b/tests/testglib.c @@ -596,6 +596,14 @@ main (int argc, sv = (gchar **) g_get_language_names (); g_print ("languages: %s\n", s ? g_strjoinv (":", sv) : "NULL!"); + /* special dirs */ + s = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP); + g_print ("user_special[DESKTOP]: %s\n", s ? s : "NULL!"); + s = g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS); + g_print ("user_special[DOCUMENTS]: %s\n", s ? s : "NULL!"); + s = g_get_user_special_dir (G_USER_DIRECTORY_PUBLIC_SHARE); + g_print ("user_special[PUBLIC_SHARE]: %s\n", s ? s : "NULL!"); + /* type sizes */ g_print ("checking size of gint8: %" G_GSIZE_FORMAT, sizeof (gint8)); TEST (NULL, sizeof (gint8) == 1);