2 * arch-tag: Implementation of various Rhythmbox utility functions for URIs and files
4 * Copyright (C) 2002 Jorn Baayen
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2, or (at your option)
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
27 #include <libgnome/gnome-i18n.h>
28 #include <libgnome/gnome-init.h>
29 #include <libgnomevfs/gnome-vfs-utils.h>
30 #include <libgnomevfs/gnome-vfs-file-info.h>
31 #include <libgnomevfs/gnome-vfs-ops.h>
32 #include <libgnomevfs/gnome-vfs-directory.h>
35 #include "rb-file-helpers.h"
38 static GHashTable
*files
= NULL
;
40 static char *dot_dir
= NULL
;
43 rb_file (const char *filename
)
48 static char *paths
[] = {
49 #ifdef SHARE_UNINSTALLED_DIR
50 SHARE_UNINSTALLED_DIR
"/",
51 SHARE_UNINSTALLED_DIR
"/ui/",
52 SHARE_UNINSTALLED_DIR
"/glade/",
53 SHARE_UNINSTALLED_DIR
"/art/",
60 g_assert (files
!= NULL
);
62 ret
= g_hash_table_lookup (files
, filename
);
66 for (i
= 0; i
< (int) G_N_ELEMENTS (paths
); i
++) {
67 ret
= g_strconcat (paths
[i
], filename
, NULL
);
68 if (g_file_test (ret
, G_FILE_TEST_EXISTS
) == TRUE
) {
69 g_hash_table_insert (files
, g_strdup (filename
), ret
);
70 return (const char *) ret
;
81 if (dot_dir
== NULL
) {
82 dot_dir
= g_build_filename (g_get_home_dir (),
86 if (mkdir (dot_dir
, 0750) == -1)
87 rb_debug ("unable to create Rhythmbox's dot dir");
94 rb_file_helpers_init (void)
96 files
= g_hash_table_new_full (g_str_hash
,
98 (GDestroyNotify
) g_free
,
99 (GDestroyNotify
) g_free
);
103 rb_file_helpers_shutdown (void)
105 g_hash_table_destroy (files
);
110 #define MAX_LINK_LEVEL 5
113 rb_uri_resolve_symlink (const char *uri
)
116 GnomeVFSFileInfo
*info
;
119 g_return_val_if_fail (uri
!= NULL
, NULL
);
121 info
= gnome_vfs_file_info_new ();
122 gnome_vfs_get_file_info (uri
, info
, GNOME_VFS_FILE_INFO_DEFAULT
);
124 if (info
->type
!= GNOME_VFS_FILE_TYPE_SYMBOLIC_LINK
) {
125 gnome_vfs_file_info_unref (info
);
126 return g_strdup (uri
);
130 followed
= g_strdup (uri
);
131 while (link_count
< MAX_LINK_LEVEL
) {
132 GnomeVFSURI
*vfs_uri
;
133 GnomeVFSURI
*new_vfs_uri
;
136 vfs_uri
= gnome_vfs_uri_new (followed
);
137 escaped_path
= gnome_vfs_escape_path_string (info
->symlink_name
);
138 new_vfs_uri
= gnome_vfs_uri_resolve_relative (vfs_uri
,
140 g_free (escaped_path
);
143 followed
= gnome_vfs_uri_to_string (new_vfs_uri
,
144 GNOME_VFS_URI_HIDE_NONE
);
147 gnome_vfs_uri_unref (new_vfs_uri
);
148 gnome_vfs_uri_unref (vfs_uri
);
150 gnome_vfs_file_info_clear (info
);
151 gnome_vfs_get_file_info (followed
, info
,
152 GNOME_VFS_FILE_INFO_DEFAULT
);
154 if (info
->type
!= GNOME_VFS_FILE_TYPE_SYMBOLIC_LINK
) {
155 gnome_vfs_file_info_unref (info
);
160 /* Too many symlinks */
162 gnome_vfs_file_info_unref (info
);
168 rb_uri_is_directory (const char *uri
)
170 GnomeVFSFileInfo
*info
;
173 g_return_val_if_fail (uri
!= NULL
, FALSE
);
175 info
= gnome_vfs_file_info_new ();
177 gnome_vfs_get_file_info (uri
, info
,
178 GNOME_VFS_FILE_INFO_FORCE_FAST_MIME_TYPE
|
179 GNOME_VFS_FILE_INFO_FOLLOW_LINKS
);
181 if (info
->type
== GNOME_VFS_FILE_TYPE_DIRECTORY
)
186 gnome_vfs_file_info_unref (info
);
192 rb_uri_exists (const char *uri
)
197 g_return_val_if_fail (uri
!= NULL
, FALSE
);
199 vuri
= gnome_vfs_uri_new (uri
);
200 ret
= gnome_vfs_uri_exists (vuri
);
201 gnome_vfs_uri_unref (vuri
);
207 is_valid_scheme_character (char c
)
209 return g_ascii_isalnum (c
) || c
== '+' || c
== '-' || c
== '.';
213 * FIXME this is not the simplest or most time-efficent way
214 * to do this. Probably a far more clear way of doing this processing
215 * is to split the path into segments, rather than doing the processing
219 remove_internal_relative_components (char *uri_current
)
221 char *segment_prev
, *segment_cur
;
222 size_t len_prev
, len_cur
;
224 len_prev
= len_cur
= 0;
227 g_return_if_fail (uri_current
!= NULL
);
229 segment_cur
= uri_current
;
231 while (*segment_cur
) {
232 len_cur
= strcspn (segment_cur
, "/");
234 if (len_cur
== 1 && segment_cur
[0] == '.') {
236 if (segment_cur
[1] == '\0') {
237 segment_cur
[0] = '\0';
240 memmove (segment_cur
, segment_cur
+ 2, strlen (segment_cur
+ 2) + 1);
243 } else if (len_cur
== 2 && segment_cur
[0] == '.' && segment_cur
[1] == '.' ) {
244 /* Remove ".."'s (and the component to the left of it) that aren't at the
245 * beginning or to the right of other ..'s
249 && segment_prev
[0] == '.'
250 && segment_prev
[1] == '.')) {
251 if (segment_cur
[2] == '\0') {
252 segment_prev
[0] = '\0';
255 memmove (segment_prev
, segment_cur
+ 3, strlen (segment_cur
+ 3) + 1);
257 segment_cur
= segment_prev
;
260 /* now we find the previous segment_prev */
261 if (segment_prev
== uri_current
) {
263 } else if (segment_prev
- uri_current
>= 2) {
265 for ( ; segment_prev
> uri_current
&& segment_prev
[0] != '/'
267 if (segment_prev
[0] == '/') {
277 /*Forward to next segment */
279 if (segment_cur
[len_cur
] == '\0') {
283 segment_prev
= segment_cur
;
285 segment_cur
+= len_cur
+ 1;
290 is_uri_partial (const char *uri
)
294 /* RFC 2396 section 3.1 */
297 && ((*current
>= 'a' && *current
<= 'z')
298 || (*current
>= 'A' && *current
<= 'Z')
299 || (*current
>= '0' && *current
<= '9')
302 || ('.' == *current
)) ;
305 return !(':' == *current
);
309 * eel_uri_make_full_from_relative:
311 * Returns a full URI given a full base URI, and a secondary URI which may
314 * Return value: the URI (NULL for some bad errors).
316 * FIXME: This code has been copied from eel-mozilla-content-view
317 * because eel-mozilla-content-view cannot link with libeel-extensions
318 * due to lame license issues. Really, this belongs in gnome-vfs, but was added
319 * after the Gnome 1.4 gnome-vfs API freeze
323 eel_uri_make_full_from_relative (const char *base_uri
, const char *relative_uri
)
327 /* See section 5.2 in RFC 2396 */
329 if (base_uri
== NULL
&& relative_uri
== NULL
) {
331 } else if (base_uri
== NULL
) {
332 result
= g_strdup (relative_uri
);
333 } else if (relative_uri
== NULL
) {
334 result
= g_strdup (base_uri
);
335 } else if (!is_uri_partial (relative_uri
)) {
336 result
= g_strdup (relative_uri
);
338 char *mutable_base_uri
;
342 size_t base_uri_length
;
345 mutable_base_uri
= g_strdup (base_uri
);
346 uri_current
= mutable_uri
= g_strdup (relative_uri
);
348 /* Chew off Fragment and Query from the base_url */
350 separator
= strrchr (mutable_base_uri
, '#');
356 separator
= strrchr (mutable_base_uri
, '?');
362 if ('/' == uri_current
[0] && '/' == uri_current
[1]) {
363 /* Relative URI's beginning with the authority
364 * component inherit only the scheme from their parents
367 separator
= strchr (mutable_base_uri
, ':');
372 } else if ('/' == uri_current
[0]) {
373 /* Relative URI's beginning with '/' absolute-path based
374 * at the root of the base uri
377 separator
= strchr (mutable_base_uri
, ':');
379 /* g_assert (separator), really */
381 /* If we start with //, skip past the authority section */
382 if ('/' == separator
[1] && '/' == separator
[2]) {
383 separator
= strchr (separator
+ 3, '/');
388 /* If there's no //, just assume the scheme is the root */
392 } else if ('#' != uri_current
[0]) {
393 /* Handle the ".." convention for relative uri's */
395 /* If there's a trailing '/' on base_url, treat base_url
396 * as a directory path.
397 * Otherwise, treat it as a file path, and chop off the filename
400 base_uri_length
= strlen (mutable_base_uri
);
401 if ('/' == mutable_base_uri
[base_uri_length
-1]) {
402 /* Trim off '/' for the operation below */
403 mutable_base_uri
[base_uri_length
-1] = 0;
405 separator
= strrchr (mutable_base_uri
, '/');
411 remove_internal_relative_components (uri_current
);
413 /* handle the "../"'s at the beginning of the relative URI */
414 while (0 == strncmp ("../", uri_current
, 3)) {
416 separator
= strrchr (mutable_base_uri
, '/');
425 /* handle a ".." at the end */
426 if (uri_current
[0] == '.' && uri_current
[1] == '.'
427 && uri_current
[2] == '\0') {
430 separator
= strrchr (mutable_base_uri
, '/');
436 /* Re-append the '/' */
437 mutable_base_uri
[strlen(mutable_base_uri
)+1] = '\0';
438 mutable_base_uri
[strlen(mutable_base_uri
)] = '/';
441 result
= g_strconcat (mutable_base_uri
, uri_current
, NULL
);
442 g_free (mutable_base_uri
);
443 g_free (mutable_uri
);
449 /* Note that NULL's and full paths are also handled by this function.
450 * A NULL location will return the current working directory
453 file_uri_from_local_relative_path (const char *location
)
456 char *base_uri
, *base_uri_slash
;
457 char *location_escaped
;
460 current_dir
= g_get_current_dir ();
461 base_uri
= gnome_vfs_get_uri_from_local_path (current_dir
);
462 /* g_get_current_dir returns w/o trailing / */
463 base_uri_slash
= g_strconcat (base_uri
, "/", NULL
);
465 location_escaped
= gnome_vfs_escape_path_string (location
);
467 uri
= eel_uri_make_full_from_relative (base_uri_slash
, location_escaped
);
469 g_free (location_escaped
);
470 g_free (base_uri_slash
);
472 g_free (current_dir
);
478 has_valid_scheme (const char *uri
)
484 if (!is_valid_scheme_character (*p
)) {
490 } while (is_valid_scheme_character (*p
));
496 * eel_make_uri_from_shell_arg:
498 * Similar to eel_make_uri_from_input, except that:
500 * 1) guesses relative paths instead of http domains
501 * 2) doesn't bother stripping leading/trailing white space
502 * 3) doesn't bother with ~ expansion--that's done by the shell
504 * @location: a possibly mangled "uri"
506 * returns a newly allocated uri
510 rb_uri_resolve_relative (const char *location
)
514 g_return_val_if_fail (location
!= NULL
, g_strdup (""));
516 switch (location
[0]) {
521 uri
= gnome_vfs_get_uri_from_local_path (location
);
524 if (has_valid_scheme (location
)) {
525 uri
= g_strdup (location
);
527 uri
= file_uri_from_local_relative_path (location
);
537 return (uid
== getuid ());
546 n_groups
= getgroups (100, gids
);
548 for (i
= 0; i
< n_groups
; i
++)
550 if (gids
[i
] == getegid ())
560 rb_uri_is_readable (const char *text_uri
)
562 GnomeVFSFileInfo
*info
;
563 gboolean ret
= FALSE
;
565 info
= gnome_vfs_file_info_new ();
568 if (gnome_vfs_get_file_info (text_uri
, info
, GNOME_VFS_FILE_INFO_FOLLOW_LINKS
) != GNOME_VFS_OK
)
571 if ((info
->permissions
& GNOME_VFS_PERM_OTHER_READ
) ||
572 ((info
->permissions
& GNOME_VFS_PERM_USER_READ
) &&
573 (have_uid (info
->uid
) == TRUE
)) ||
574 ((info
->permissions
& GNOME_VFS_PERM_GROUP_READ
) &&
575 (have_gid (info
->gid
) == TRUE
)))
578 gnome_vfs_file_info_unref (info
);
584 rb_uri_is_writable (const char *text_uri
)
586 GnomeVFSFileInfo
*info
;
587 gboolean ret
= FALSE
;
589 info
= gnome_vfs_file_info_new ();
592 if (gnome_vfs_get_file_info (text_uri
, info
, GNOME_VFS_FILE_INFO_FOLLOW_LINKS
) != GNOME_VFS_OK
)
595 if ((info
->permissions
& GNOME_VFS_PERM_OTHER_WRITE
) ||
596 ((info
->permissions
& GNOME_VFS_PERM_USER_WRITE
) &&
597 (have_uid (info
->uid
) == TRUE
)) ||
598 ((info
->permissions
& GNOME_VFS_PERM_GROUP_WRITE
) &&
599 (have_gid (info
->gid
) == TRUE
)))
602 gnome_vfs_file_info_unref (info
);
608 rb_uri_is_local (const char *text_uri
)
610 return g_str_has_prefix (text_uri
, "file://");
614 * gnome_vfs_uri_new escapes a few extra characters that
615 * gnome_vfs_escape_path doesn't ('&' and '='). If we
616 * don't adjust our URIs to match, we end up with duplicate
617 * entries, one with the characters encoded and one without.
620 escape_extra_gnome_vfs_chars (char *uri
)
622 if (strspn (uri
, "&=") != strlen (uri
)) {
623 char *tmp
= gnome_vfs_escape_set (uri
, "&=");
635 gboolean
*cancel_flag
;
636 } RBUriHandleRecursivelyData
;
639 rb_uri_handle_recursively_cb (const gchar
*rel_path
,
640 GnomeVFSFileInfo
*info
,
641 gboolean recursing_will_loop
,
642 RBUriHandleRecursivelyData
*data
,
645 if (data
->cancel_flag
&& *data
->cancel_flag
)
648 /* skip hidden and unreadable files and directories */
649 if (g_str_has_prefix (rel_path
, ".") ||
650 ((info
->valid_fields
& GNOME_VFS_FILE_INFO_FIELDS_ACCESS
) &&
651 !(info
->permissions
& GNOME_VFS_PERM_ACCESS_READABLE
))) {
656 if (info
->type
== GNOME_VFS_FILE_TYPE_REGULAR
) {
657 char *path
, *escaped_rel_path
;
659 escaped_rel_path
= gnome_vfs_escape_path_string (rel_path
);
660 escaped_rel_path
= escape_extra_gnome_vfs_chars (escaped_rel_path
);
661 path
= g_build_filename (data
->uri
, escaped_rel_path
, NULL
);
662 (data
->func
) (path
, data
->user_data
);
663 g_free (escaped_rel_path
);
667 *recurse
= !recursing_will_loop
;
672 rb_uri_handle_recursively (const char *text_uri
,
674 gboolean
*cancelflag
,
677 RBUriHandleRecursivelyData
*data
= g_new0 (RBUriHandleRecursivelyData
, 1);
678 GnomeVFSFileInfoOptions flags
;
679 GnomeVFSResult result
;
681 data
->uri
= text_uri
;
683 data
->user_data
= user_data
;
684 data
->cancel_flag
= cancelflag
;
686 flags
= GNOME_VFS_FILE_INFO_GET_MIME_TYPE
|
687 GNOME_VFS_FILE_INFO_FORCE_FAST_MIME_TYPE
|
688 GNOME_VFS_FILE_INFO_FOLLOW_LINKS
|
689 GNOME_VFS_FILE_INFO_GET_ACCESS_RIGHTS
;
690 result
= gnome_vfs_directory_visit (text_uri
,
692 GNOME_VFS_DIRECTORY_VISIT_LOOPCHECK
,
693 (GnomeVFSDirectoryVisitFunc
)rb_uri_handle_recursively_cb
,
699 rb_uri_mkstemp (const char *prefix
, char **uri_ret
, GnomeVFSHandle
**ret
)
701 GnomeVFSHandle
*handle
= NULL
;
703 GnomeVFSResult result
= GNOME_VFS_ERROR_FILE_EXISTS
;
708 uri
= g_strdup_printf ("%s%06X", prefix
, g_random_int_range (0, 0xFFFFFF));
709 result
= gnome_vfs_create (&handle
, uri
, GNOME_VFS_OPEN_WRITE
| GNOME_VFS_OPEN_RANDOM
, TRUE
, 0644);
710 } while (result
== GNOME_VFS_ERROR_FILE_EXISTS
);
712 if (result
== GNOME_VFS_OK
) {
723 rb_canonicalise_uri (const char *uri
)
730 result
= gnome_vfs_make_path_name_canonical (uri
);
731 tmp
= gnome_vfs_escape_path_string (result
);
733 tmp
= escape_extra_gnome_vfs_chars (tmp
);
734 result
= g_strconcat ("file://", tmp
, NULL
);
736 } else if (g_str_has_prefix (uri
, "file://")) {
737 /* local file, rhythmdb wants this path escaped */
739 tmp1
= gnome_vfs_unescape_string (uri
+ 7, NULL
); /* ignore "file://" */
740 tmp2
= gnome_vfs_escape_path_string (tmp1
);
742 tmp2
= escape_extra_gnome_vfs_chars (tmp2
);
743 result
= g_strconcat ("file://", tmp2
, NULL
); /* re-add scheme */
746 GnomeVFSURI
*vfsuri
= gnome_vfs_uri_new (uri
);
748 if (vfsuri
!= NULL
) {
749 /* non-local uri, leave as-is */
750 gnome_vfs_uri_unref (vfsuri
);
751 result
= g_strdup (uri
);
753 /* this may just mean that gnome-vfs doesn't recognise the
754 * uri scheme, so return it as is */
755 rb_debug ("Error processing probable URI %s", uri
);
756 result
= g_strdup (uri
);
764 rb_uri_append_path (const char *uri
, const char *path
)
766 GnomeVFSURI
*vfs_uri
, *full_uri
;
769 vfs_uri
= gnome_vfs_uri_new (uri
);
770 full_uri
= gnome_vfs_uri_append_path (vfs_uri
, path
);
771 gnome_vfs_uri_unref (vfs_uri
);
772 result
= gnome_vfs_uri_to_string (full_uri
, GNOME_VFS_URI_HIDE_NONE
);
773 gnome_vfs_uri_unref (full_uri
);
779 rb_uri_append_uri (const char *uri
, const char *fragment
)
781 GnomeVFSURI
*vfs_uri
, *full_uri
;
784 vfs_uri
= gnome_vfs_uri_new (uri
);
785 full_uri
= gnome_vfs_uri_append_string (vfs_uri
, fragment
);
786 gnome_vfs_uri_unref (vfs_uri
);
787 result
= gnome_vfs_uri_to_string (full_uri
, GNOME_VFS_URI_HIDE_NONE
);
788 gnome_vfs_uri_unref (full_uri
);
794 rb_uri_get_dir_name (const char *uri
)
796 GnomeVFSURI
*vfs_uri
;
799 vfs_uri
= gnome_vfs_uri_new (uri
);
800 dirname
= gnome_vfs_uri_extract_dirname (vfs_uri
);
801 gnome_vfs_uri_unref (vfs_uri
);