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 }