hythmbox-2.98/lib/rb-file-helpers.c

No issues found

   1 /*
   2  *  Copyright (C) 2002 Jorn Baayen
   3  *
   4  *  This program is free software; you can redistribute it and/or modify
   5  *  it under the terms of the GNU General Public License as published by
   6  *  the Free Software Foundation; either version 2, or (at your option)
   7  *  any later version.
   8  *
   9  *  The Rhythmbox authors hereby grant permission for non-GPL compatible
  10  *  GStreamer plugins to be used and distributed together with GStreamer
  11  *  and Rhythmbox. This permission is above and beyond the permissions granted
  12  *  by the GPL license by which Rhythmbox is covered. If you modify this code
  13  *  you may extend this exception to your version of the code, but you are not
  14  *  obligated to do so. If you do not wish to do so, delete this exception
  15  *  statement from your version.
  16  *
  17  *  This program is distributed in the hope that it will be useful,
  18  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  19  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  20  *  GNU General Public License for more details.
  21  *
  22  *  You should have received a copy of the GNU General Public License
  23  *  along with this program; if not, write to the Free Software
  24  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
  25  *
  26  */
  27 
  28 /**
  29  * SECTION:rb-file-helpers
  30  * @short_description: An assortment of file and URI helper functions
  31  *
  32  * This is a variety of functions for dealing with files and URIs, including
  33  * locating installed files, finding user cache and config directories,
  34  * and dealing with file naming restrictions for various filesystems.
  35  */
  36 
  37 #include "config.h"
  38 
  39 #include <gtk/gtk.h>
  40 #include <glib.h>
  41 #include <glib/gi18n.h>
  42 #include <glib/gstdio.h>
  43 #include <libpeas/peas.h>
  44 
  45 #include <stdio.h>
  46 #include <string.h>
  47 #include <unistd.h>
  48 #include <stdlib.h>
  49 
  50 #include "rb-file-helpers.h"
  51 #include "rb-debug.h"
  52 #include "rb-util.h"
  53 
  54 static GHashTable *files = NULL;
  55 
  56 static char *dot_dir = NULL;
  57 static char *user_data_dir = NULL;
  58 static char *user_cache_dir = NULL;
  59 
  60 static char *uninstalled_paths[] = {
  61 	SHARE_UNINSTALLED_DIR "/",
  62 	SHARE_UNINSTALLED_DIR "/ui/",
  63 	SHARE_UNINSTALLED_DIR "/art/",
  64 	SHARE_UNINSTALLED_BUILDDIR "/",
  65 	SHARE_UNINSTALLED_BUILDDIR "/ui/",
  66 	SHARE_UNINSTALLED_BUILDDIR "/art/",
  67 	SHARE_DIR "/",
  68 	SHARE_DIR "/art/",
  69 	NULL
  70 };
  71 
  72 static char *installed_paths[] = {
  73 	SHARE_DIR "/",
  74 	SHARE_DIR "/art/",
  75 	NULL
  76 };
  77 
  78 static char **search_paths;
  79 
  80 /**
  81  * rb_locale_dir:
  82  *
  83  * Returns the locale directory identified at build configuration time.
  84  *
  85  * Return value: locale dir
  86  */
  87 const char *
  88 rb_locale_dir (void)
  89 {
  90 	return GNOMELOCALEDIR;
  91 }
  92 
  93 /**
  94  * rb_file:
  95  * @filename: name of file to search for
  96  *
  97  * Searches for an installed file, returning the full path name
  98  * if found, NULL otherwise.
  99  *
 100  * Return value: Full file name, if found.  Must not be freed.
 101  */
 102 const char *
 103 rb_file (const char *filename)
 104 {
 105 	char *ret;
 106 	int i;
 107 
 108 	g_assert (files != NULL);
 109 
 110 	ret = g_hash_table_lookup (files, filename);
 111 	if (ret != NULL)
 112 		return ret;
 113 
 114 	for (i = 0; search_paths[i] != NULL; i++) {
 115 		ret = g_strconcat (search_paths[i], filename, NULL);
 116 		if (g_file_test (ret, G_FILE_TEST_EXISTS) == TRUE) {
 117 			g_hash_table_insert (files, g_strdup (filename), ret);
 118 			return (const char *) ret;
 119 		}
 120 		g_free (ret);
 121 	}
 122 
 123 	return NULL;
 124 }
 125 
 126 /**
 127  * rb_user_data_dir:
 128  *
 129  * This will create the rhythmbox user data directory, using the XDG Base
 130  * Directory specification.  If none of the XDG environment variables are
 131  * set, this will be ~/.local/share/rhythmbox.
 132  *
 133  * Returns: string holding the path to the rhythmbox user data directory, or
 134  * NULL if the directory does not exist and cannot be created.
 135  */
 136 const char *
 137 rb_user_data_dir (void)
 138 {
 139 	if (user_data_dir == NULL) {
 140 		user_data_dir = g_build_filename (g_get_user_data_dir (),
 141 						  "rhythmbox",
 142 						  NULL);
 143 		if (g_mkdir_with_parents (user_data_dir, 0700) == -1)
 144 			rb_debug ("unable to create Rhythmbox's user data dir, %s", user_data_dir);
 145 	}
 146 	
 147 	return user_data_dir;
 148 }
 149 
 150 /**
 151  * rb_user_cache_dir:
 152  *
 153  * This will create the rhythmbox user cache directory, using the XDG
 154  * Base Directory specification.  If none of the XDG environment
 155  * variables are set, this will be ~/.cache/rhythmbox.
 156  *
 157  * Returns: string holding the path to the rhythmbox user cache directory, or
 158  * NULL if the directory does not exist and could not be created.
 159  */
 160 const char *
 161 rb_user_cache_dir (void)
 162 {
 163 	if (user_cache_dir == NULL) {
 164 		user_cache_dir = g_build_filename (g_get_user_cache_dir (),
 165 						   "rhythmbox",
 166 						   NULL);
 167 		if (g_mkdir_with_parents (user_cache_dir, 0700) == -1)
 168 			rb_debug ("unable to create Rhythmbox's user cache dir, %s", user_cache_dir);
 169 	}
 170 
 171 	return user_cache_dir;
 172 }
 173 
 174 
 175 /**
 176  * rb_music_dir:
 177  *
 178  * Returns the default directory for the user's music library.
 179  * This will usually be the 'Music' directory under the home directory.
 180  *
 181  * Return value: user's music directory.  must not be freed.
 182  */
 183 const char *
 184 rb_music_dir (void)
 185 {
 186 	const char *dir;
 187 	dir = g_get_user_special_dir (G_USER_DIRECTORY_MUSIC);
 188 	if (dir == NULL) {
 189 		dir = getenv ("HOME");
 190 		if (dir == NULL) {
 191 			dir = "/tmp";
 192 		}
 193 	}
 194 	rb_debug ("user music dir: %s", dir);
 195 	return dir;
 196 }
 197 
 198 /**
 199  * rb_find_user_data_file:
 200  * @name: name of file to find
 201  *
 202  * Determines the full path to use for user-specific files, such as rhythmdb.xml,
 203  * within the user data directory (see @rb_user_data_dir).
 204  *
 205  * Returns: allocated string containing the location of the file to use, even if
 206  *  an error occurred.
 207  */
 208 char *
 209 rb_find_user_data_file (const char *name)
 210 {
 211 	return g_build_filename (rb_user_data_dir (), name, NULL);
 212 }
 213 
 214 /**
 215  * rb_find_user_cache_file:
 216  * @name: name of file to find
 217  *
 218  * Determines the full path to use for user-specific cached files
 219  * within the user cache directory.
 220  *
 221  * Returns: allocated string containing the location of the file to use, even if
 222  *  an error occurred.
 223  */
 224 char *
 225 rb_find_user_cache_file (const char *name)
 226 {
 227 	return g_build_filename (rb_user_cache_dir (), name, NULL);
 228 }
 229 
 230 /**
 231  * rb_find_plugin_data_file:
 232  * @plugin: the plugin object
 233  * @name: name of the file to find
 234  *
 235  * Locates a file under the plugin's data directory.
 236  *
 237  * Returns: allocated string containing the location of the file
 238  */
 239 char *
 240 rb_find_plugin_data_file (GObject *object, const char *name)
 241 {
 242 	PeasPluginInfo *info;
 243 	char *ret = NULL;
 244 	const char *plugin_name = "<unknown>";
 245 
 246 	g_object_get (object, "plugin-info", &info, NULL);
 247 	if (info != NULL) {
 248 		char *tmp;
 249 
 250 		tmp = g_build_filename (peas_plugin_info_get_data_dir (info), name, NULL);
 251 		if (g_file_test (tmp, G_FILE_TEST_EXISTS)) {
 252 			ret = tmp;
 253 		} else {
 254 			g_free (tmp);
 255 		}
 256 
 257 		plugin_name = peas_plugin_info_get_name (info);
 258 	}
 259 	
 260 	if (ret == NULL) {
 261 		const char *f;
 262 		f = rb_file (name);
 263 		if (f != NULL) {
 264 			ret = g_strdup (f);
 265 		}
 266 	}
 267 
 268 	rb_debug ("found '%s' when searching for file '%s' for plugin '%s'",
 269 		  ret, name, plugin_name);
 270 
 271 	/* ensure it's an absolute path */
 272 	if (ret != NULL && ret[0] != '/') {
 273 		char *pwd = g_get_current_dir ();
 274 		char *path = g_strconcat (pwd, G_DIR_SEPARATOR_S, ret, NULL);
 275 		g_free (ret);
 276 		g_free (pwd);
 277 		ret = path;
 278 	}
 279 
 280 	return ret;
 281 }
 282 
 283 /**
 284  * rb_file_helpers_init:
 285  * @uninstalled: if %TRUE, search in source and build directories
 286  * as well as installed locations
 287  *
 288  * Sets up file search paths for @rb_file.  Must be called on startup.
 289  */
 290 void
 291 rb_file_helpers_init (gboolean uninstalled)
 292 {
 293 	if (uninstalled)
 294 		search_paths = uninstalled_paths;
 295 	else
 296 		search_paths = installed_paths;
 297 
 298 	files = g_hash_table_new_full (g_str_hash,
 299 				       g_str_equal,
 300 				       (GDestroyNotify) g_free,
 301 				       (GDestroyNotify) g_free);
 302 }
 303 
 304 /**
 305  * rb_file_helpers_shutdown:
 306  *
 307  * Frees various data allocated by file helper functions.
 308  * Should be called on shutdown.
 309  */
 310 void
 311 rb_file_helpers_shutdown (void)
 312 {
 313 	g_hash_table_destroy (files);
 314 	g_free (dot_dir);
 315 	g_free (user_data_dir);
 316 	g_free (user_cache_dir);
 317 }
 318 
 319 #define MAX_LINK_LEVEL 5
 320 
 321 /* not sure this is really useful */
 322 
 323 /**
 324  * rb_uri_resolve_symlink:
 325  * @uri: the URI to process
 326  * @error: returns error information
 327  *
 328  * Attempts to resolve symlinks in @uri and return a canonical URI for the file
 329  * it identifies.
 330  *
 331  * Return value: resolved URI, or NULL on error
 332  */
 333 char *
 334 rb_uri_resolve_symlink (const char *uri, GError **error)
 335 {
 336 	GFile *file = NULL;
 337 	GFileInfo *file_info = NULL;
 338 	int link_count = 0;
 339 	char *result = NULL;
 340 	const char *attr = G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET;
 341 	GError *l_error = NULL;
 342 	
 343 	file = g_file_new_for_uri (uri);
 344 
 345 	while (link_count < MAX_LINK_LEVEL) {
 346 		GFile *parent;
 347 		GFile *new_file;
 348 		const char *target;
 349 
 350 		/* look for a symlink target */
 351 		file_info = g_file_query_info (file,
 352 					       attr,
 353 					       G_FILE_QUERY_INFO_NONE,
 354 					       NULL, &l_error);
 355 		if (l_error != NULL) {
 356 			/* argh */
 357 			result = g_file_get_uri (file);
 358 			rb_debug ("error querying %s: %s", result, l_error->message);
 359 			g_free (result);
 360 			result = NULL;
 361 			break;
 362 		} else if (g_file_info_has_attribute (file_info, attr) == FALSE) {
 363 			/* no symlink, so return the path */
 364 			result = g_file_get_uri (file);
 365 			if (link_count > 0) {
 366 				rb_debug ("resolved symlinks: %s -> %s", uri, result);
 367 			}
 368 			break;
 369 		}
 370 
 371 		/* resolve it and try again */
 372 		new_file = NULL;
 373 		parent = g_file_get_parent (file);
 374 		if (parent == NULL) {
 375 			/* dang */
 376 			break;
 377 		}
 378 
 379 		target = g_file_info_get_attribute_byte_string (file_info, attr);
 380 		new_file = g_file_resolve_relative_path (parent, target);
 381 		g_object_unref (parent);
 382 
 383 		g_object_unref (file_info);
 384 		file_info = NULL;
 385 
 386 		g_object_unref (file);
 387 		file = new_file;
 388 
 389 		if (file == NULL) {
 390 			/* dang */
 391 			break;
 392 		}
 393 
 394 		link_count++;
 395 	}
 396 
 397 	if (file != NULL) {
 398 		g_object_unref (file);
 399 	}
 400 	if (file_info != NULL) {
 401 		g_object_unref (file_info);
 402 	}
 403 	if (result == NULL && error == NULL) {
 404 		rb_debug ("too many symlinks while resolving %s", uri);
 405 		l_error = g_error_new (G_IO_ERROR,
 406 				       G_IO_ERROR_TOO_MANY_LINKS,
 407 				       _("Too many symlinks"));
 408 	}
 409 	if (l_error != NULL) {
 410 		g_propagate_error (error, l_error);
 411 	}
 412 
 413 	return result;
 414 }
 415 
 416 /**
 417  * rb_uri_is_directory:
 418  * @uri: the URI to check
 419  *
 420  * Checks if @uri identifies a directory.
 421  *
 422  * Return value: %TRUE if @uri is a directory
 423  */
 424 gboolean
 425 rb_uri_is_directory (const char *uri)
 426 {
 427 	GFile *f;
 428 	GFileInfo *fi;
 429 	GFileType ftype;
 430 
 431 	f = g_file_new_for_uri (uri);
 432 	fi = g_file_query_info (f, G_FILE_ATTRIBUTE_STANDARD_TYPE, 0, NULL, NULL);
 433 	g_object_unref (f);
 434 	if (fi == NULL) {
 435 		/* ? */
 436 		return FALSE;
 437 	}
 438 
 439 	ftype = g_file_info_get_attribute_uint32 (fi, G_FILE_ATTRIBUTE_STANDARD_TYPE);
 440 	g_object_unref (fi);
 441 	return (ftype == G_FILE_TYPE_DIRECTORY);
 442 }
 443 
 444 /**
 445  * rb_uri_exists:
 446  * @uri: a URI to check
 447  *
 448  * Checks if a URI identifies a resource that exists
 449  *
 450  * Return value: %TRUE if @uri exists
 451  */
 452 gboolean
 453 rb_uri_exists (const char *uri)
 454 {
 455 	GFile *f;
 456 	gboolean exists;
 457 
 458 	f = g_file_new_for_uri (uri);
 459 	exists = g_file_query_exists (f, NULL);
 460 	g_object_unref (f);
 461 	return exists;
 462 }
 463 
 464 static gboolean
 465 get_uri_perm (const char *uri, const char *perm_attribute)
 466 {
 467 	GFile *f;
 468 	GFileInfo *info;
 469 	GError *error = NULL;
 470 	gboolean result;
 471 
 472 	f = g_file_new_for_uri (uri);
 473 	info = g_file_query_info (f, perm_attribute, 0, NULL, &error);
 474 	if (error != NULL) {
 475 		result = FALSE;
 476 		g_error_free (error);
 477 	} else {
 478 		result = g_file_info_get_attribute_boolean (info, perm_attribute);
 479 	}
 480 
 481 	if (info != NULL) {
 482 		g_object_unref (info);
 483 	}
 484 	g_object_unref (f);
 485 	return result;
 486 }
 487 
 488 /**
 489  * rb_uri_is_readable:
 490  * @uri: a URI to check
 491  *
 492  * Checks if the user can read the resource identified by @uri
 493  *
 494  * Return value: %TRUE if @uri is readable
 495  */
 496 gboolean
 497 rb_uri_is_readable (const char *uri)
 498 {
 499 	return get_uri_perm (uri, G_FILE_ATTRIBUTE_ACCESS_CAN_READ);
 500 }
 501 
 502 /**
 503  * rb_uri_is_writable:
 504  * @uri: a URI to check
 505  *
 506  * Checks if the user can write to the resource identified by @uri
 507  *
 508  * Return value: %TRUE if @uri is writable
 509  */
 510 gboolean
 511 rb_uri_is_writable (const char *uri)
 512 {
 513 	return get_uri_perm (uri, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
 514 }
 515 
 516 /**
 517  * rb_uri_is_local:
 518  * @uri: a URI to check
 519  *
 520  * Checks if @uri identifies a local resource.  Currently this just
 521  * checks that it uses the 'file' URI scheme.
 522  *
 523  * Return value: %TRUE if @uri is local
 524  */
 525 gboolean
 526 rb_uri_is_local (const char *uri)
 527 {
 528 	return g_str_has_prefix (uri, "file://");
 529 }
 530 
 531 /**
 532  * rb_uri_is_hidden:
 533  * @uri: a URI to check
 534  *
 535  * Checks if @uri is hidden, according to the Unix filename convention.
 536  * If the filename component of @uri begins with a dot, the file is considered
 537  * hidden.
 538  *
 539  * Return value: %TRUE if @uri is hidden
 540  */
 541 gboolean
 542 rb_uri_is_hidden (const char *uri)
 543 {
 544 	return g_utf8_strrchr (uri, -1, '/')[1] == '.';
 545 }
 546 
 547 /**
 548  * rb_uri_could_be_podcast:
 549  * @uri: a URI to check
 550  * @is_opml: returns whether the URI identifies an OPML document
 551  *
 552  * Checks if @uri identifies a resource that is probably a podcast
 553  * (RSS or Atom feed).  This does not perform any IO, it just guesses
 554  * based on the URI itself.
 555  *
 556  * Return value: %TRUE if @uri may be a podcast
 557  */
 558 gboolean
 559 rb_uri_could_be_podcast (const char *uri, gboolean *is_opml)
 560 {
 561 	const char *query_string;
 562 
 563 	if (is_opml != NULL)
 564 		*is_opml = FALSE;
 565 
 566 	/* feed:// URIs are always podcasts */
 567 	if (g_str_has_prefix (uri, "feed:")) {
 568 		rb_debug ("'%s' must be a podcast", uri);
 569 		return TRUE;
 570 	}
 571 
 572 	/* Check the scheme is a possible one first */
 573 	if (g_str_has_prefix (uri, "http") == FALSE &&
 574 	    g_str_has_prefix (uri, "itpc:") == FALSE &&
 575 	    g_str_has_prefix (uri, "itms:") == FALSE) {
 576 	    	rb_debug ("'%s' can't be a Podcast or OPML file, not the right scheme", uri);
 577 	    	return FALSE;
 578 	}
 579 
 580 	/* Now, check whether the iTunes Music Store link
 581 	 * is a podcast */
 582 	if (g_str_has_prefix (uri, "itms:") != FALSE
 583 	    && strstr (uri, "phobos.apple.com") != NULL
 584 	    && strstr (uri, "viewPodcast") != NULL)
 585 		return TRUE;
 586 
 587 	query_string = strchr (uri, '?');
 588 	if (query_string == NULL) {
 589 		query_string = uri + strlen (uri);
 590 	}
 591 
 592 	/* FIXME hacks */
 593 	if (strstr (uri, "rss") != NULL ||
 594 	    strstr (uri, "atom") != NULL ||
 595 	    strstr (uri, "feed") != NULL) {
 596 	    	rb_debug ("'%s' should be Podcast file, HACK", uri);
 597 	    	return TRUE;
 598 	} else if (strstr (uri, "opml") != NULL) {
 599 		rb_debug ("'%s' should be an OPML file, HACK", uri);
 600 		if (is_opml != NULL)
 601 			*is_opml = TRUE;
 602 		return TRUE;
 603 	}
 604 
 605 	if (strncmp (query_string - 4, ".rss", 4) == 0 ||
 606 	    strncmp (query_string - 4, ".xml", 4) == 0 ||
 607 	    strncmp (query_string - 5, ".atom", 5) == 0 ||
 608 	    strncmp (uri, "itpc", 4) == 0 ||
 609 	    (strstr (uri, "phobos.apple.com/") != NULL && strstr (uri, "viewPodcast") != NULL) ||
 610 	    strstr (uri, "itunes.com/podcast") != NULL) {
 611 	    	rb_debug ("'%s' should be Podcast file", uri);
 612 	    	return TRUE;
 613 	} else if (strncmp (query_string - 5, ".opml", 5) == 0) {
 614 		rb_debug ("'%s' should be an OPML file", uri);
 615 		if (is_opml != NULL)
 616 			*is_opml = TRUE;
 617 		return TRUE;
 618 	}
 619 
 620 	return FALSE;
 621 }
 622 
 623 /**
 624  * rb_uri_make_hidden:
 625  * @uri: a URI to construct a hidden version of
 626  *
 627  * Constructs a URI that is similar to @uri but which identifies
 628  * a hidden file.  This can be used for temporary files that should not
 629  * be visible to the user while they are in use.
 630  *
 631  * Return value: hidden URI, must be freed by the caller.
 632  */
 633 char *
 634 rb_uri_make_hidden (const char *uri)
 635 {
 636 	GFile *file;
 637 	GFile *parent;
 638 	char *shortname;
 639 	char *dotted;
 640 	char *ret = NULL;
 641 
 642 	if (rb_uri_is_hidden (uri))
 643 		return g_strdup (uri);
 644 
 645 	file = g_file_new_for_uri (uri);
 646 
 647 	shortname = g_file_get_basename (file);
 648 	if (shortname == NULL) {
 649 		g_object_unref (file);
 650 		return NULL;
 651 	}
 652 
 653 	parent = g_file_get_parent (file);
 654 	if (parent == NULL) {
 655 		g_object_unref (file);
 656 		g_free (shortname);
 657 		return NULL;
 658 	}
 659 	g_object_unref (file);
 660 
 661 	dotted = g_strdup_printf (".%s", shortname);
 662 	g_free (shortname);
 663 
 664 	file = g_file_get_child (parent, dotted);
 665 	g_object_unref (parent);
 666 	g_free (dotted);
 667 
 668 	if (file != NULL) {
 669 		ret = g_file_get_uri (file);
 670 		g_object_unref (file);
 671 	}
 672 	return ret;
 673 }
 674 
 675 typedef struct {
 676 	char *uri;
 677 	GCancellable *cancel;
 678 	RBUriRecurseFunc func;
 679 	gpointer user_data;
 680 	GDestroyNotify data_destroy;
 681 
 682 	GMutex results_lock;
 683 	guint results_idle_id;
 684 	GList *file_results;
 685 	GList *dir_results;
 686 } RBUriHandleRecursivelyAsyncData;
 687 
 688 static gboolean
 689 _should_process (GFileInfo *info)
 690 {
 691 	/* check that the file is non-hidden and readable */
 692 	if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ)) {
 693 		if (g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ) == FALSE) {
 694 			return FALSE;
 695 		}
 696 	}
 697 	if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN)) {
 698 		if (g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN)) {
 699 			return FALSE;
 700 		}
 701 	}
 702 	return TRUE;
 703 }
 704 
 705 static void
 706 _uri_handle_recurse (GFile *dir,
 707 		     GCancellable *cancel,
 708 		     GHashTable *handled,
 709 		     RBUriRecurseFunc func,
 710 		     gpointer user_data)
 711 {
 712 	GFileEnumerator *files;
 713 	GFileInfo *info;
 714 	GError *error = NULL;
 715 	GFileType file_type;
 716 	const char *file_id;
 717 	gboolean file_handled;
 718 	const char *attributes = 
 719 		G_FILE_ATTRIBUTE_STANDARD_NAME ","
 720 		G_FILE_ATTRIBUTE_STANDARD_TYPE ","
 721 		G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
 722 		G_FILE_ATTRIBUTE_ID_FILE ","
 723 		G_FILE_ATTRIBUTE_ACCESS_CAN_READ;
 724 
 725 	files = g_file_enumerate_children (dir, attributes, G_FILE_QUERY_INFO_NONE, cancel, &error);
 726 	if (error != NULL) {
 727 		char *where;
 728 
 729 		/* handle the case where we're given a single file to process */
 730 		if (error->code == G_IO_ERROR_NOT_DIRECTORY) {
 731 			g_clear_error (&error);
 732 			info = g_file_query_info (dir, attributes, G_FILE_QUERY_INFO_NONE, cancel, &error);
 733 			if (error == NULL) {
 734 				if (_should_process (info)) {
 735 					(func) (dir, FALSE, user_data);
 736 				}
 737 				g_object_unref (info);
 738 				return;
 739 			}
 740 		}
 741 
 742 		where = g_file_get_uri (dir);
 743 		rb_debug ("error enumerating %s: %s", where, error->message);
 744 		g_free (where);
 745 		g_error_free (error);
 746 		return;
 747 	}
 748 
 749 	while (1) {
 750 		GFile *child;
 751 		gboolean is_dir;
 752 		gboolean ret;
 753 
 754 		ret = TRUE;
 755 		info = g_file_enumerator_next_file (files, cancel, &error);
 756 		if (error != NULL) {
 757 			rb_debug ("error enumerating files: %s", error->message);
 758 			break;
 759 		} else if (info == NULL) {
 760 			break;
 761 		}
 762 
 763 		if (_should_process (info) == FALSE) {
 764 			g_object_unref (info);
 765 			continue;
 766 		}
 767 
 768 		/* already handled? */
 769 		file_id = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILE);
 770 		if (file_id == NULL) {
 771 			/* have to hope for the best, I guess */
 772 			file_handled = FALSE;
 773 		} else if (g_hash_table_lookup (handled, file_id) != NULL) {
 774 			file_handled = TRUE;
 775 		} else {
 776 			file_handled = FALSE;
 777 			g_hash_table_insert (handled, g_strdup (file_id), GINT_TO_POINTER (1));
 778 		}
 779 
 780 		/* type? */
 781 		file_type = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_STANDARD_TYPE);
 782 		switch (file_type) {
 783 		case G_FILE_TYPE_DIRECTORY:
 784 		case G_FILE_TYPE_MOUNTABLE:
 785 			is_dir = TRUE;
 786 			break;
 787 		
 788 		default:
 789 			is_dir = FALSE;
 790 			break;
 791 		}
 792 
 793 		if (file_handled == FALSE) {
 794 			child = g_file_get_child (dir, g_file_info_get_name (info));
 795 			ret = (func) (child, is_dir, user_data);
 796 
 797 			if (is_dir) {
 798 				_uri_handle_recurse (child, cancel, handled, func, user_data);
 799 			}
 800 			g_object_unref (child);
 801 		}
 802 	
 803 		g_object_unref (info);
 804 
 805 		if (ret == FALSE)
 806 			break;
 807 	}
 808 
 809 	g_object_unref (files);
 810 }
 811 
 812 /**
 813  * rb_uri_handle_recursively:
 814  * @uri: URI to visit
 815  * @cancel: an optional #GCancellable to allow cancellation
 816  * @func: (scope call): Callback function
 817  * @user_data: Data for callback function
 818  *
 819  * Calls @func for each file found under the directory identified by @uri.
 820  * If @uri identifies a file, calls @func for that instead.
 821  */
 822 void
 823 rb_uri_handle_recursively (const char *uri,
 824 			   GCancellable *cancel,
 825 			   RBUriRecurseFunc func,
 826 			   gpointer user_data)
 827 {
 828 	GFile *file;
 829 	GHashTable *handled;
 830 
 831 	file = g_file_new_for_uri (uri);
 832 	handled = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
 833 
 834 	_uri_handle_recurse (file, cancel, handled, func, user_data);
 835 
 836 	g_hash_table_destroy (handled);
 837 	g_object_unref (file);
 838 }
 839 
 840 
 841 /* runs in main thread */
 842 static gboolean
 843 _recurse_async_idle_cb (RBUriHandleRecursivelyAsyncData *data)
 844 {
 845 	GList *ul, *dl;
 846 
 847 	g_mutex_lock (&data->results_lock);
 848 
 849 	for (ul = data->file_results, dl = data->dir_results;
 850 	     ul != NULL;
 851 	     ul = g_list_next (ul), dl = g_list_next (dl)) {
 852 		g_assert (dl != NULL);
 853 
 854 		data->func (G_FILE (ul->data), (GPOINTER_TO_INT (dl->data) == 1), data->user_data);
 855 		g_object_unref (ul->data);
 856 	}
 857 	g_assert (dl == NULL);
 858 
 859 	g_list_free (data->file_results);
 860 	data->file_results = NULL;
 861 	g_list_free (data->dir_results);
 862 	data->dir_results = NULL;
 863 
 864 	data->results_idle_id = 0;
 865 	g_mutex_unlock (&data->results_lock);
 866 	return FALSE;
 867 }
 868 
 869 /* runs in main thread */
 870 static gboolean
 871 _recurse_async_data_free (RBUriHandleRecursivelyAsyncData *data)
 872 {
 873 	GList *i;
 874 
 875 	if (data->results_idle_id) {
 876 		g_source_remove (data->results_idle_id);
 877 		_recurse_async_idle_cb (data); /* process last results */
 878 	}
 879 
 880 	for (i = data->file_results; i != NULL; i = i->next) {
 881 		GFile *file = G_FILE (i->data);
 882 		g_object_unref (file);
 883 	}
 884 
 885 	g_list_free (data->file_results);
 886 	data->file_results = NULL;
 887 	g_list_free (data->dir_results);
 888 	data->dir_results = NULL;
 889 
 890 	if (data->data_destroy != NULL) {
 891 		(data->data_destroy) (data->user_data);
 892 	}
 893 	if (data->cancel != NULL) {
 894 		g_object_unref (data->cancel);
 895 	}
 896 
 897 	g_free (data->uri);
 898 	return FALSE;
 899 }
 900 
 901 /* runs in worker thread */
 902 static gboolean
 903 _recurse_async_cb (GFile *file, gboolean dir, RBUriHandleRecursivelyAsyncData *data)
 904 {
 905 	g_mutex_lock (&data->results_lock);
 906 
 907 	data->file_results = g_list_prepend (data->file_results, g_object_ref (file));
 908 	data->dir_results = g_list_prepend (data->dir_results, GINT_TO_POINTER (dir ? 1 : 0));
 909 	if (data->results_idle_id == 0) {
 910 		g_idle_add ((GSourceFunc)_recurse_async_idle_cb, data);
 911 	}
 912 
 913 	g_mutex_unlock (&data->results_lock);
 914 	return TRUE;
 915 }
 916 
 917 static gpointer
 918 _recurse_async_func (RBUriHandleRecursivelyAsyncData *data)
 919 {
 920 	rb_uri_handle_recursively (data->uri,
 921 				   data->cancel,
 922 				   (RBUriRecurseFunc) _recurse_async_cb,
 923 				   data);
 924 
 925 	g_idle_add ((GSourceFunc)_recurse_async_data_free, data);
 926 	return NULL;
 927 }
 928 
 929 /**
 930  * rb_uri_handle_recursively_async:
 931  * @uri: the URI to visit
 932  * @cancel: a #GCancellable to allow cancellation
 933  * @func: callback function
 934  * @user_data: data to pass to callback
 935  * @data_destroy: function to call to free @user_data
 936  *
 937  * Calls @func for each file found under the directory identified
 938  * by @uri, or if @uri identifies a file, calls it once
 939  * with that.
 940  *
 941  * Directory recursion happens on a separate thread, but the callbacks
 942  * are called on the main thread.
 943  *
 944  * If non-NULL, @destroy_data will be called once all files have been
 945  * processed, or when the operation is cancelled.
 946  */
 947 void
 948 rb_uri_handle_recursively_async (const char *uri,
 949 				 GCancellable *cancel,
 950 			         RBUriRecurseFunc func,
 951 			         gpointer user_data,
 952 				 GDestroyNotify data_destroy)
 953 {
 954 	RBUriHandleRecursivelyAsyncData *data = g_new0 (RBUriHandleRecursivelyAsyncData, 1);
 955 	
 956 	data->uri = g_strdup (uri);
 957 	data->user_data = user_data;
 958 	if (cancel != NULL) {
 959 		data->cancel = g_object_ref (cancel);
 960 	}
 961 	data->data_destroy = data_destroy;
 962 
 963 	g_mutex_init (&data->results_lock);
 964 	data->func = func;
 965 	data->user_data = user_data;
 966 
 967 	g_thread_new ("rb-uri-recurse", (GThreadFunc)_recurse_async_func, data);
 968 }
 969 
 970 /**
 971  * rb_uri_mkstemp:
 972  * @prefix: URI prefix
 973  * @uri_ret: returns the temporary file URI
 974  * @stream: returns a @GOutputStream for the temporary file
 975  * @error: returns error information
 976  *
 977  * Creates a temporary file whose URI begins with @prefix, returning
 978  * the file URI and an output stream for writing to it.
 979  *
 980  * Return value: %TRUE if successful
 981  */
 982 gboolean
 983 rb_uri_mkstemp (const char *prefix, char **uri_ret, GOutputStream **stream, GError **error)
 984 {
 985 	GFile *file;
 986 	char *uri = NULL;
 987 	GFileOutputStream *fstream;
 988 	GError *e = NULL;
 989 
 990 	do {
 991 		g_free (uri);
 992 		uri = g_strdup_printf ("%s%06X", prefix, g_random_int_range (0, 0xFFFFFF));
 993 
 994 		file = g_file_new_for_uri (uri);
 995 		fstream = g_file_create (file, G_FILE_CREATE_PRIVATE, NULL, &e);
 996 		if (e != NULL) {
 997 			if (g_error_matches (e, G_IO_ERROR, G_IO_ERROR_EXISTS)) {
 998 				g_error_free (e);
 999 				e = NULL;
1000 			}
1001 		}
1002 	} while (e == NULL && fstream == NULL);
1003 
1004 	if (fstream != NULL) {
1005 		*uri_ret = uri;
1006 		*stream = G_OUTPUT_STREAM (fstream);
1007 		return TRUE;
1008 	} else {
1009 		g_free (uri);
1010 		return FALSE;
1011 	}
1012 }
1013 
1014 /**
1015  * rb_canonicalise_uri:
1016  * @uri: URI to canonicalise
1017  *
1018  * Converts @uri to canonical URI form, ensuring it doesn't contain
1019  * any redundant directory fragments or unnecessarily escaped characters.
1020  * All URIs passed to #RhythmDB functions should be canonicalised.
1021  *
1022  * Return value: canonical URI, must be freed by caller
1023  */
1024 char *
1025 rb_canonicalise_uri (const char *uri)
1026 {
1027 	GFile *file;
1028 	char *result = NULL;
1029 
1030 	g_return_val_if_fail (uri != NULL, NULL);
1031 
1032 	/* gio does more or less what we want, I think */
1033 	file = g_file_new_for_commandline_arg (uri);
1034 	result = g_file_get_uri (file);
1035 	g_object_unref (file);
1036 
1037 	return result;
1038 }
1039 
1040 /**
1041  * rb_uri_append_path:
1042  * @uri: the URI to append to
1043  * @path: the path fragment to append
1044  *
1045  * Creates a new URI consisting of @path appended to @uri.
1046  *
1047  * Return value: new URI, must be freed by caller
1048  */
1049 char*
1050 rb_uri_append_path (const char *uri, const char *path)
1051 {
1052 	GFile *file;
1053 	GFile *relfile;
1054 	char *result;
1055 
1056 	/* all paths we get are relative, so skip
1057 	 * leading slashes.
1058 	 */
1059 	while (path[0] == '/') {
1060 		path++;
1061 	}
1062 
1063 	file = g_file_new_for_uri (uri);
1064 	relfile = g_file_resolve_relative_path (file, path);
1065 	result = g_file_get_uri (relfile);
1066 	g_object_unref (relfile);
1067 	g_object_unref (file);
1068 
1069 	return result;
1070 }
1071 
1072 /**
1073  * rb_uri_append_uri:
1074  * @uri: the URI to append to
1075  * @fragment: the URI fragment to append
1076  *
1077  * Creates a new URI consisting of @fragment appended to @uri.
1078  * Generally isn't a good idea.
1079  *
1080  * Return value: new URI, must be freed by caller
1081  */
1082 char*
1083 rb_uri_append_uri (const char *uri, const char *fragment)
1084 {
1085 	char *path;
1086 	char *rv;
1087 	GFile *f = g_file_new_for_uri (fragment);
1088 
1089 	path = g_file_get_path (f);
1090 	if (path == NULL) {
1091 		g_object_unref (f);
1092 		return NULL;
1093 	}
1094 
1095 	rv = rb_uri_append_path (uri, path);
1096 	g_free (path);
1097 	g_object_unref (f);
1098 
1099 	return rv;
1100 }
1101 
1102 /**
1103  * rb_uri_get_dir_name:
1104  * @uri: a URI
1105  *
1106  * Returns the directory component of @uri, that is, everything up
1107  * to the start of the filename.
1108  *
1109  * Return value: new URI for parent of @uri, must be freed by caller.
1110  */
1111 char *
1112 rb_uri_get_dir_name (const char *uri)
1113 {
1114 	GFile *file;
1115 	GFile *parent;
1116 	char *dirname;
1117 
1118 	file = g_file_new_for_uri (uri);
1119 	parent = g_file_get_parent (file);
1120 	
1121 	dirname = g_file_get_uri (parent);
1122 
1123 	g_object_unref (parent);
1124 	g_object_unref (file);
1125 	return dirname;
1126 }
1127 
1128 /**
1129  * rb_uri_get_short_path_name:
1130  * @uri: a URI
1131  *
1132  * Returns the filename component of @uri, that is, everything after the
1133  * final slash and before the start of the query string or fragment.
1134  *
1135  * Return value: filename component of @uri, must be freed by caller
1136  */
1137 char *
1138 rb_uri_get_short_path_name (const char *uri)
1139 {
1140 	const char *start;
1141 	const char *end;
1142 
1143 	if (uri == NULL)
1144 		return NULL;
1145 
1146 	/* skip query string */
1147 	end = g_utf8_strchr (uri, -1, '?');
1148 
1149 	start = g_utf8_strrchr (uri, end ? (end - uri) : -1, '/');
1150 	if (start == NULL) {
1151 		/* no separator, just a single file name */
1152 	} else if ((start + 1 == end) || *(start + 1) == '\0') {
1153 		/* last character is the separator, so find the previous one */
1154 		end = start;
1155 		start = g_utf8_strrchr (uri, (end - uri)-1, '/');
1156 
1157 		if (start != NULL)
1158 			start++;
1159 	} else {
1160 		start++;
1161 	}
1162 
1163 	if (start == NULL)
1164 		start = uri;
1165 
1166 	if (end == NULL) {
1167 		return g_strdup (start);
1168 	} else {
1169 		return g_strndup (start, (end - start));
1170 	}
1171 }
1172 
1173 /**
1174  * rb_check_dir_has_space:
1175  * @dir: a #GFile to check
1176  * @bytes_needed: number of bytes to check for
1177  *
1178  * Checks that the filesystem holding @file has at least @bytes_needed
1179  * bytes available.
1180  *
1181  * Return value: %TRUE if enough space is available.
1182  */
1183 gboolean
1184 rb_check_dir_has_space (GFile *dir,
1185 			guint64 bytes_needed)
1186 {
1187 	GFile *extant;
1188 	GFileInfo *fs_info;
1189 	GError *error = NULL;
1190 	guint64 free_bytes;
1191 
1192 	extant = rb_file_find_extant_parent (dir);
1193 	if (extant == NULL) {
1194 		char *uri = g_file_get_uri (dir);
1195 		g_warning ("Cannot get free space at %s: none of the directory structure exists", uri);
1196 		g_free (uri);
1197 		return FALSE;
1198 	}
1199 
1200 	fs_info = g_file_query_filesystem_info (extant,
1201 						G_FILE_ATTRIBUTE_FILESYSTEM_FREE,
1202 						NULL,
1203 						&error);
1204 	g_object_unref (extant);
1205 
1206 	if (error != NULL) {
1207 		char *uri;
1208 		uri = g_file_get_uri (dir);
1209 		g_warning (_("Cannot get free space at %s: %s"), uri, error->message);
1210 		g_free (uri);
1211 		return FALSE;
1212 	}
1213 
1214 	free_bytes = g_file_info_get_attribute_uint64 (fs_info,
1215 						       G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
1216 	g_object_unref (fs_info);
1217 	if (bytes_needed >= free_bytes)
1218 		return FALSE;
1219 
1220 	return TRUE;
1221 }
1222 
1223 /**
1224  * rb_check_dir_has_space_uri:
1225  * @uri: a URI to check
1226  * @bytes_needed: number of bytes to check for
1227  *
1228  * Checks that the filesystem holding @uri has at least @bytes_needed
1229  * bytes available.
1230  *
1231  * Return value: %TRUE if enough space is available.
1232  */
1233 gboolean
1234 rb_check_dir_has_space_uri (const char *uri,
1235 			    guint64 bytes_needed)
1236 {
1237 	GFile *file;
1238 	gboolean result;
1239 
1240 	file = g_file_new_for_uri (uri);
1241 	result = rb_check_dir_has_space (file, bytes_needed);
1242 	g_object_unref (file);
1243 
1244 	return result;
1245 }
1246 
1247 /**
1248  * rb_uri_get_mount_point:
1249  * @uri: a URI
1250  *
1251  * Returns the mount point of the filesystem holding @uri.
1252  * If @uri is on a normal filesystem mount (such as /, /home,
1253  * /var, etc.) this will be NULL.
1254  *
1255  * Return value: filesystem mount point (must be freed by caller)
1256  *  or NULL.
1257  */
1258 gchar *
1259 rb_uri_get_mount_point (const char *uri)
1260 {
1261 	GFile *file;
1262 	GMount *mount;
1263 	char *mountpoint;
1264 	GError *error = NULL;
1265 
1266 	file = g_file_new_for_uri (uri);
1267 	mount = g_file_find_enclosing_mount (file, NULL, &error);
1268 	if (error != NULL) {
1269 		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) == FALSE) {
1270 			rb_debug ("finding mount for %s: %s", uri, error->message);
1271 		}
1272 		g_error_free (error);
1273 		mountpoint = NULL;
1274 	} else {
1275 		GFile *root;
1276 		root = g_mount_get_root (mount);
1277 		mountpoint = g_file_get_uri (root);
1278 		g_object_unref (root);
1279 		g_object_unref (mount);
1280 	}
1281 
1282 	g_object_unref (file);
1283 	return mountpoint;
1284 }
1285 
1286 static gboolean
1287 check_file_is_directory (GFile *file, GError **error)
1288 {
1289 	GFileInfo *info;
1290 
1291 	info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_TYPE, G_FILE_QUERY_INFO_NONE, NULL, error);
1292 	if (*error == NULL) {
1293 		/* check it's a directory */
1294 		GFileType filetype;
1295 		gboolean ret = TRUE;
1296 
1297 		filetype = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_STANDARD_TYPE);
1298 		if (filetype != G_FILE_TYPE_DIRECTORY) {
1299 			/* um.. */
1300 			ret = FALSE;
1301 		}
1302 
1303 		g_object_unref (info);
1304 		return ret;
1305 	}
1306 
1307 	if (g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
1308 		g_clear_error (error);
1309 	}
1310 	return FALSE;
1311 }
1312 
1313 
1314 /**
1315  * rb_uri_create_parent_dirs:
1316  * @uri: a URI for which to create parent directories
1317  * @error: returns error information
1318  *
1319  * Ensures that all parent directories of @uri exist so that
1320  * @uri itself can be created directly.
1321  *
1322  * Return value: %TRUE if successful
1323  */
1324 gboolean
1325 rb_uri_create_parent_dirs (const char *uri, GError **error)
1326 {
1327 	GFile *file;
1328 	GFile *parent;
1329 	gboolean ret;
1330 
1331 	/* ignore internal URI schemes */
1332 	if (g_str_has_prefix (uri, "xrb")) {
1333 		return TRUE;
1334 	}
1335 
1336 	file = g_file_new_for_uri (uri);
1337 	parent = g_file_get_parent (file);
1338 	g_object_unref (file);
1339 	if (parent == NULL) {
1340 		/* now what? */
1341 		return TRUE;
1342 	}
1343 
1344 	ret = check_file_is_directory (parent, error);
1345 	if (ret == FALSE && *error == NULL) {
1346 		ret = g_file_make_directory_with_parents (parent, NULL, error);
1347 	}
1348 
1349 	g_object_unref (parent);
1350 	return ret;
1351 }
1352 
1353 /**
1354  * rb_file_find_extant_parent:
1355  * @file: a #GFile to find an extant ancestor of
1356  *
1357  * Walks up the filesystem hierarchy to find a #GFile representing
1358  * the nearest extant ancestor of the specified file, which may be
1359  * the file itself if it exists.
1360  * 
1361  * Return value: (transfer full): #GFile for the nearest extant ancestor
1362  */
1363 GFile *
1364 rb_file_find_extant_parent (GFile *file)
1365 {
1366 	g_object_ref (file);
1367 	while (g_file_query_exists (file, NULL) == FALSE) {
1368 		GFile *parent;
1369 
1370 		parent = g_file_get_parent (file);
1371 		if (parent == NULL) {
1372 			char *uri = g_file_get_uri (file);
1373 			g_warning ("filesystem root %s apparently doesn't exist!", uri);
1374 			g_free (uri);
1375 			g_object_unref (file);
1376 			return NULL;
1377 		}
1378 
1379 		g_object_unref (file);
1380 		file = parent;
1381 	}
1382 
1383 	return file;
1384 }
1385 
1386 /**
1387  * rb_uri_get_filesystem_type:
1388  * @uri: URI to get filesystem type for
1389  * @mount_point: optionally returns the mount point for the filesystem as a URI
1390  *
1391  * Returns a string describing the type of the filesystem containing @uri.
1392  *
1393  * Return value: filesystem type string, must be freed by caller.
1394  */
1395 char *
1396 rb_uri_get_filesystem_type (const char *uri, char **mount_point)
1397 {
1398 	GFile *file;
1399 	GFile *extant;
1400 	GFileInfo *info;
1401 	char *fstype = NULL;
1402 	GError *error = NULL;
1403 
1404 	if (mount_point != NULL) {
1405 		*mount_point = NULL;
1406 	}
1407 
1408 	/* ignore our own internal URI schemes */
1409 	if (g_str_has_prefix (uri, "xrb")) {
1410 		return NULL;
1411 	}
1412 
1413 	/* if the file doesn't exist, walk up the directory structure
1414 	 * until we find something that does.
1415 	 */
1416 	file = g_file_new_for_uri (uri);
1417 
1418 	extant = rb_file_find_extant_parent (file);
1419 	if (extant == NULL) {
1420 		rb_debug ("unable to get filesystem type for %s: none of the directory structure exists", uri);
1421 		g_object_unref (file);
1422 		return NULL;
1423 	}
1424 
1425 	if (mount_point != NULL) {
1426 		char *extant_uri;
1427 		extant_uri = g_file_get_uri (extant);
1428 		*mount_point = rb_uri_get_mount_point (extant_uri);
1429 		g_free (extant_uri);
1430 	}
1431 
1432 	info = g_file_query_filesystem_info (extant, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, NULL, &error);
1433 	if (info != NULL) {
1434 		fstype = g_file_info_get_attribute_as_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE);
1435 		g_object_unref (info);
1436 	} else {
1437 		rb_debug ("error querying filesystem info: %s", error->message);
1438 	}
1439 	g_clear_error (&error);
1440 	g_object_unref (file);
1441 	g_object_unref (extant);
1442 	return fstype;
1443 }
1444 
1445 /**
1446  * rb_sanitize_path_for_msdos_filesystem:
1447  * @path: a path to sanitize (modified in place)
1448  *
1449  * Modifies @path such that it represents a legal path for MS DOS
1450  * filesystems.
1451  */
1452 void
1453 rb_sanitize_path_for_msdos_filesystem (char *path)
1454 {
1455 	g_strdelimit (path, "\"", '\'');
1456 	g_strdelimit (path, ":|<>*?\\", '_');
1457 }
1458 
1459 /**
1460  * rb_sanitize_uri_for_filesystem:
1461  * @uri: a URI to sanitize
1462  *
1463  * Removes characters from @uri that are not allowed by the filesystem
1464  * on which it would be stored.  At present, this only supports MS DOS
1465  * filesystems.
1466  *
1467  * Return value: sanitized copy of @uri, must be freed by caller.
1468  */
1469 char *
1470 rb_sanitize_uri_for_filesystem (const char *uri)
1471 {
1472 	char *mountpoint = NULL;
1473 	char *filesystem;
1474 	char *sane_uri = NULL;
1475 
1476 	filesystem = rb_uri_get_filesystem_type (uri, &mountpoint);
1477 	if (!filesystem)
1478 		return g_strdup (uri);
1479 
1480 	if (!strcmp (filesystem, "fat") ||
1481 	    !strcmp (filesystem, "vfat") ||
1482 	    !strcmp (filesystem, "msdos")) {
1483 	    	char *hostname = NULL;
1484 		GError *error = NULL;
1485 		char *full_path;
1486 		char *fat_path;
1487 
1488 		full_path = g_filename_from_uri (uri, &hostname, &error);
1489 
1490 		if (error) {
1491 			g_error_free (error);
1492 			g_free (filesystem);
1493 			g_free (full_path);
1494 			g_free (mountpoint);
1495 			return g_strdup (uri);
1496 		}
1497 
1498 		/* if we got a mount point, don't sanitize it.  the mountpoint must be
1499 		 * valid for the filesystem that contains it, but it may not be valid for
1500 		 * the filesystem it contains.  for example, a vfat filesystem mounted
1501 		 * at "/media/Pl1:".
1502 		 */
1503 		fat_path = full_path;
1504 		if (mountpoint != NULL) {
1505 			char *mount_path;
1506 			mount_path = g_filename_from_uri (mountpoint, NULL, &error);
1507 			if (error) {
1508 				rb_debug ("can't convert mountpoint %s to a path: %s", mountpoint, error->message);
1509 				g_error_free (error);
1510 			} else if (g_str_has_prefix (full_path, mount_path)) {
1511 				fat_path = full_path + strlen (mount_path);
1512 			} else {
1513 				rb_debug ("path %s doesn't begin with mount path %s somehow", full_path, mount_path);
1514 			}
1515 
1516 			g_free (mount_path);
1517 		} else {
1518 			rb_debug ("couldn't get mount point for %s", uri);
1519 		}
1520 
1521 		rb_debug ("sanitizing path %s", fat_path);
1522 		rb_sanitize_path_for_msdos_filesystem (fat_path);
1523 
1524 		/* create a new uri from this */
1525 		sane_uri = g_filename_to_uri (full_path, hostname, &error);
1526 		rb_debug ("sanitized URI: %s", sane_uri);
1527 
1528 		g_free (hostname);
1529 		g_free (full_path);
1530 
1531 		if (error) {
1532 			g_error_free (error);
1533 			g_free (filesystem);
1534 			g_free (mountpoint);
1535 			return g_strdup (uri);
1536 		}
1537 	}
1538 
1539 	/* add workarounds for other filesystems limitations here */
1540 
1541 	g_free (filesystem);
1542 	g_free (mountpoint);
1543 	return sane_uri ? sane_uri : g_strdup (uri);
1544 }