]> Pileus Git - ~andy/gtk/blob - gtk/gtkfilesystemunix.c
Fix #129872, based on a patch by Jan Arne Petersen <jpetersen@uni-bonn.de>
[~andy/gtk] / gtk / gtkfilesystemunix.c
1 /* GTK - The GIMP Toolkit
2  * gtkfilesystemunix.c: Default implementation of GtkFileSystem for UNIX-like systems
3  * Copyright (C) 2003, Red Hat, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 #include "gtkfilesystem.h"
22 #include "gtkfilesystemunix.h"
23 #include "gtkicontheme.h"
24 #include "gtkintl.h"
25
26 #define XDG_PREFIX _gtk_xdg
27 #include "xdgmime/xdgmime.h"
28
29 #include <errno.h>
30 #include <string.h>
31 #include <sys/stat.h>
32 #include <sys/types.h>
33 #include <unistd.h>
34 #include <stdio.h>
35
36 #define BOOKMARKS_FILENAME ".gtk-bookmarks"
37 #define BOOKMARKS_TMP_FILENAME ".gtk-bookmarks-XXXXXX"
38
39 typedef struct _GtkFileSystemUnixClass GtkFileSystemUnixClass;
40
41 #define GTK_FILE_SYSTEM_UNIX_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_SYSTEM_UNIX, GtkFileSystemUnixClass))
42 #define GTK_IS_FILE_SYSTEM_UNIX_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILE_SYSTEM_UNIX))
43 #define GTK_FILE_SYSTEM_UNIX_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILE_SYSTEM_UNIX, GtkFileSystemUnixClass))
44
45 struct _GtkFileSystemUnixClass
46 {
47   GObjectClass parent_class;
48 };
49
50 struct _GtkFileSystemUnix
51 {
52   GObject parent_instance;
53 };
54
55 /* Icon type, supplemented by MIME type
56  */
57 typedef enum {
58   ICON_NONE,
59   ICON_REGULAR, /* Use mime type for icon */
60   ICON_BLOCK_DEVICE,
61   ICON_BROKEN_SYMBOLIC_LINK,
62   ICON_CHARACTER_DEVICE,
63   ICON_DIRECTORY,
64   ICON_EXECUTABLE,
65   ICON_FIFO,
66   ICON_SOCKET
67 } IconType;
68
69
70 #define GTK_TYPE_FILE_FOLDER_UNIX             (gtk_file_folder_unix_get_type ())
71 #define GTK_FILE_FOLDER_UNIX(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_FILE_FOLDER_UNIX, GtkFileFolderUnix))
72 #define GTK_IS_FILE_FOLDER_UNIX(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_FILE_FOLDER_UNIX))
73 #define GTK_FILE_FOLDER_UNIX_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_FOLDER_UNIX, GtkFileFolderUnixClass))
74 #define GTK_IS_FILE_FOLDER_UNIX_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILE_FOLDER_UNIX))
75 #define GTK_FILE_FOLDER_UNIX_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILE_FOLDER_UNIX, GtkFileFolderUnixClass))
76
77 typedef struct _GtkFileFolderUnix      GtkFileFolderUnix;
78 typedef struct _GtkFileFolderUnixClass GtkFileFolderUnixClass;
79
80 struct _GtkFileFolderUnixClass
81 {
82   GObjectClass parent_class;
83 };
84
85 struct _GtkFileFolderUnix
86 {
87   GObject parent_instance;
88
89   GtkFileInfoType types;
90   gchar *filename;
91 };
92
93 static GObjectClass *system_parent_class;
94 static GObjectClass *folder_parent_class;
95
96 static void gtk_file_system_unix_class_init   (GtkFileSystemUnixClass *class);
97 static void gtk_file_system_unix_iface_init   (GtkFileSystemIface     *iface);
98 static void gtk_file_system_unix_init         (GtkFileSystemUnix      *impl);
99 static void gtk_file_system_unix_finalize     (GObject                *object);
100
101 static GSList *             gtk_file_system_unix_list_volumes        (GtkFileSystem     *file_system);
102 static GtkFileSystemVolume *gtk_file_system_unix_get_volume_for_path (GtkFileSystem     *file_system,
103                                                                       const GtkFilePath *path);
104
105 static GtkFileFolder *gtk_file_system_unix_get_folder    (GtkFileSystem      *file_system,
106                                                           const GtkFilePath  *path,
107                                                           GtkFileInfoType     types,
108                                                           GError            **error);
109 static gboolean       gtk_file_system_unix_create_folder (GtkFileSystem      *file_system,
110                                                           const GtkFilePath  *path,
111                                                           GError            **error);
112
113 static void         gtk_file_system_unix_volume_free             (GtkFileSystem       *file_system,
114                                                                   GtkFileSystemVolume *volume);
115 static GtkFilePath *gtk_file_system_unix_volume_get_base_path    (GtkFileSystem       *file_system,
116                                                                   GtkFileSystemVolume *volume);
117 static gboolean     gtk_file_system_unix_volume_get_is_mounted   (GtkFileSystem       *file_system,
118                                                                   GtkFileSystemVolume *volume);
119 static gboolean     gtk_file_system_unix_volume_mount            (GtkFileSystem       *file_system,
120                                                                   GtkFileSystemVolume *volume,
121                                                                   GError             **error);
122 static gchar *      gtk_file_system_unix_volume_get_display_name (GtkFileSystem       *file_system,
123                                                                   GtkFileSystemVolume *volume);
124 static GdkPixbuf *  gtk_file_system_unix_volume_render_icon      (GtkFileSystem        *file_system,
125                                                                   GtkFileSystemVolume  *volume,
126                                                                   GtkWidget            *widget,
127                                                                   gint                  pixel_size,
128                                                                   GError              **error);
129
130 static gboolean       gtk_file_system_unix_get_parent    (GtkFileSystem      *file_system,
131                                                           const GtkFilePath  *path,
132                                                           GtkFilePath       **parent,
133                                                           GError            **error);
134 static GtkFilePath *  gtk_file_system_unix_make_path     (GtkFileSystem      *file_system,
135                                                           const GtkFilePath  *base_path,
136                                                           const gchar        *display_name,
137                                                           GError            **error);
138 static gboolean       gtk_file_system_unix_parse         (GtkFileSystem      *file_system,
139                                                           const GtkFilePath  *base_path,
140                                                           const gchar        *str,
141                                                           GtkFilePath       **folder,
142                                                           gchar             **file_part,
143                                                           GError            **error);
144
145 static gchar *      gtk_file_system_unix_path_to_uri      (GtkFileSystem     *file_system,
146                                                            const GtkFilePath *path);
147 static gchar *      gtk_file_system_unix_path_to_filename (GtkFileSystem     *file_system,
148                                                            const GtkFilePath *path);
149 static GtkFilePath *gtk_file_system_unix_uri_to_path      (GtkFileSystem     *file_system,
150                                                            const gchar       *uri);
151 static GtkFilePath *gtk_file_system_unix_filename_to_path (GtkFileSystem     *file_system,
152                                                            const gchar       *filename);
153
154 static GdkPixbuf *gtk_file_system_unix_render_icon (GtkFileSystem     *file_system,
155                                                     const GtkFilePath *path,
156                                                     GtkWidget         *widget,
157                                                     gint               pixel_size,
158                                                     GError           **error);
159
160 static gboolean gtk_file_system_unix_add_bookmark    (GtkFileSystem     *file_system,
161                                                       const GtkFilePath *path,
162                                                       GError           **error);
163 static gboolean gtk_file_system_unix_remove_bookmark (GtkFileSystem     *file_system,
164                                                       const GtkFilePath *path,
165                                                       GError           **error);
166 static GSList * gtk_file_system_unix_list_bookmarks  (GtkFileSystem *file_system);
167
168 static GType gtk_file_folder_unix_get_type   (void);
169 static void  gtk_file_folder_unix_class_init (GtkFileFolderUnixClass *class);
170 static void  gtk_file_folder_unix_iface_init (GtkFileFolderIface     *iface);
171 static void  gtk_file_folder_unix_init       (GtkFileFolderUnix      *impl);
172 static void  gtk_file_folder_unix_finalize   (GObject                *object);
173
174 static GtkFileInfo *gtk_file_folder_unix_get_info      (GtkFileFolder  *folder,
175                                                         const GtkFilePath    *path,
176                                                         GError        **error);
177 static gboolean     gtk_file_folder_unix_list_children (GtkFileFolder  *folder,
178                                                         GSList        **children,
179                                                         GError        **error);
180
181 static GtkFilePath *filename_to_path   (const gchar       *filename);
182
183 static gboolean     filename_is_root  (const char       *filename);
184 static GtkFileInfo *filename_get_info (const gchar      *filename,
185                                        GtkFileInfoType   types,
186                                        GError          **error);
187
188 /*
189  * GtkFileSystemUnix
190  */
191 GType
192 gtk_file_system_unix_get_type (void)
193 {
194   static GType file_system_unix_type = 0;
195
196   if (!file_system_unix_type)
197     {
198       static const GTypeInfo file_system_unix_info =
199       {
200         sizeof (GtkFileSystemUnixClass),
201         NULL,           /* base_init */
202         NULL,           /* base_finalize */
203         (GClassInitFunc) gtk_file_system_unix_class_init,
204         NULL,           /* class_finalize */
205         NULL,           /* class_data */
206         sizeof (GtkFileSystemUnix),
207         0,              /* n_preallocs */
208         (GInstanceInitFunc) gtk_file_system_unix_init,
209       };
210
211       static const GInterfaceInfo file_system_info =
212       {
213         (GInterfaceInitFunc) gtk_file_system_unix_iface_init, /* interface_init */
214         NULL,                                                 /* interface_finalize */
215         NULL                                                  /* interface_data */
216       };
217
218       file_system_unix_type = g_type_register_static (G_TYPE_OBJECT,
219                                                       "GtkFileSystemUnix",
220                                                       &file_system_unix_info, 0);
221       g_type_add_interface_static (file_system_unix_type,
222                                    GTK_TYPE_FILE_SYSTEM,
223                                    &file_system_info);
224     }
225
226   return file_system_unix_type;
227 }
228
229 /**
230  * gtk_file_system_unix_new:
231  *
232  * Creates a new #GtkFileSystemUnix object. #GtkFileSystemUnix
233  * implements the #GtkFileSystem interface with direct access to
234  * the filesystem using Unix/Linux API calls
235  *
236  * Return value: the new #GtkFileSystemUnix object
237  **/
238 GtkFileSystem *
239 gtk_file_system_unix_new (void)
240 {
241   return g_object_new (GTK_TYPE_FILE_SYSTEM_UNIX, NULL);
242 }
243
244 static void
245 gtk_file_system_unix_class_init (GtkFileSystemUnixClass *class)
246 {
247   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
248
249   system_parent_class = g_type_class_peek_parent (class);
250
251   gobject_class->finalize = gtk_file_system_unix_finalize;
252 }
253
254 static void
255 gtk_file_system_unix_iface_init   (GtkFileSystemIface *iface)
256 {
257   iface->list_volumes = gtk_file_system_unix_list_volumes;
258   iface->get_volume_for_path = gtk_file_system_unix_get_volume_for_path;
259   iface->get_folder = gtk_file_system_unix_get_folder;
260   iface->create_folder = gtk_file_system_unix_create_folder;
261   iface->volume_free = gtk_file_system_unix_volume_free;
262   iface->volume_get_base_path = gtk_file_system_unix_volume_get_base_path;
263   iface->volume_get_is_mounted = gtk_file_system_unix_volume_get_is_mounted;
264   iface->volume_mount = gtk_file_system_unix_volume_mount;
265   iface->volume_get_display_name = gtk_file_system_unix_volume_get_display_name;
266   iface->volume_render_icon = gtk_file_system_unix_volume_render_icon;
267   iface->get_parent = gtk_file_system_unix_get_parent;
268   iface->make_path = gtk_file_system_unix_make_path;
269   iface->parse = gtk_file_system_unix_parse;
270   iface->path_to_uri = gtk_file_system_unix_path_to_uri;
271   iface->path_to_filename = gtk_file_system_unix_path_to_filename;
272   iface->uri_to_path = gtk_file_system_unix_uri_to_path;
273   iface->filename_to_path = gtk_file_system_unix_filename_to_path;
274   iface->render_icon = gtk_file_system_unix_render_icon;
275   iface->add_bookmark = gtk_file_system_unix_add_bookmark;
276   iface->remove_bookmark = gtk_file_system_unix_remove_bookmark;
277   iface->list_bookmarks = gtk_file_system_unix_list_bookmarks;
278 }
279
280 static void
281 gtk_file_system_unix_init (GtkFileSystemUnix *system_unix)
282 {
283 }
284
285 static void
286 gtk_file_system_unix_finalize (GObject *object)
287 {
288   system_parent_class->finalize (object);
289 }
290
291 /* Returns our single root volume */
292 static GtkFileSystemVolume *
293 get_root_volume (void)
294 {
295   return (GtkFileSystemVolume *) gtk_file_path_new_dup ("/");
296 }
297
298 static GSList *
299 gtk_file_system_unix_list_volumes (GtkFileSystem *file_system)
300 {
301   return g_slist_append (NULL, get_root_volume ());
302 }
303
304 static GtkFileSystemVolume *
305 gtk_file_system_unix_get_volume_for_path (GtkFileSystem     *file_system,
306                                           const GtkFilePath *path)
307 {
308   return get_root_volume ();
309 }
310
311 static GtkFileFolder *
312 gtk_file_system_unix_get_folder (GtkFileSystem     *file_system,
313                                  const GtkFilePath *path,
314                                  GtkFileInfoType    types,
315                                  GError           **error)
316 {
317   GtkFileFolderUnix *folder_unix;
318   const char *filename;
319
320   filename = gtk_file_path_get_string (path);
321   g_return_val_if_fail (filename != NULL, NULL);
322   g_return_val_if_fail (g_path_is_absolute (filename), NULL);
323
324   folder_unix = g_object_new (GTK_TYPE_FILE_FOLDER_UNIX, NULL);
325   folder_unix->filename = g_strdup (filename);
326   folder_unix->types = types;
327
328   return GTK_FILE_FOLDER (folder_unix);
329 }
330
331 static gboolean
332 gtk_file_system_unix_create_folder (GtkFileSystem     *file_system,
333                                     const GtkFilePath *path,
334                                     GError           **error)
335 {
336   const char *filename;
337   gboolean result;
338
339   filename = gtk_file_path_get_string (path);
340   g_return_val_if_fail (filename != NULL, FALSE);
341   g_return_val_if_fail (g_path_is_absolute (filename), FALSE);
342
343   result = mkdir (filename, 0777) == 0;
344
345   if (!result)
346     {
347       gchar *filename_utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
348       g_set_error (error,
349                    GTK_FILE_SYSTEM_ERROR,
350                    GTK_FILE_SYSTEM_ERROR_NONEXISTENT,
351                    _("error creating directory '%s': %s"),
352                    filename_utf8 ? filename_utf8 : "???",
353                    g_strerror (errno));
354       g_free (filename_utf8);
355     }
356
357   return result;
358 }
359
360 static void
361 gtk_file_system_unix_volume_free (GtkFileSystem        *file_system,
362                                   GtkFileSystemVolume  *volume)
363 {
364   GtkFilePath *path;
365
366   path = (GtkFilePath *) volume;
367   gtk_file_path_free (path);
368 }
369
370 static GtkFilePath *
371 gtk_file_system_unix_volume_get_base_path (GtkFileSystem        *file_system,
372                                            GtkFileSystemVolume  *volume)
373 {
374   return gtk_file_path_new_dup ("/");
375 }
376
377 static gboolean
378 gtk_file_system_unix_volume_get_is_mounted (GtkFileSystem        *file_system,
379                                             GtkFileSystemVolume  *volume)
380 {
381   return TRUE;
382 }
383
384 static gboolean
385 gtk_file_system_unix_volume_mount (GtkFileSystem        *file_system,
386                                    GtkFileSystemVolume  *volume,
387                                    GError              **error)
388 {
389   g_set_error (error,
390                GTK_FILE_SYSTEM_ERROR,
391                GTK_FILE_SYSTEM_ERROR_FAILED,
392                _("This file system does not support mounting"));
393   return FALSE;
394 }
395
396 static gchar *
397 gtk_file_system_unix_volume_get_display_name (GtkFileSystem       *file_system,
398                                               GtkFileSystemVolume *volume)
399 {
400   return g_strdup (_("Filesystem")); /* Same as Nautilus */
401 }
402
403 static IconType
404 get_icon_type (const char *filename,
405                GError    **error)
406 {
407   struct stat statbuf;
408   IconType icon_type;
409
410   /* If stat fails, try to fall back to lstat to catch broken links
411    */
412   if (stat (filename, &statbuf) != 0 &&
413       lstat (filename, &statbuf) != 0)
414     {
415       gchar *filename_utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
416       g_set_error (error,
417                    GTK_FILE_SYSTEM_ERROR,
418                    GTK_FILE_SYSTEM_ERROR_NONEXISTENT,
419                    _("error getting information for '%s': %s"),
420                    filename_utf8 ? filename_utf8 : "???",
421                    g_strerror (errno));
422       g_free (filename_utf8);
423
424       return ICON_NONE;
425     }
426
427   if (S_ISBLK (statbuf.st_mode))
428     icon_type = ICON_BLOCK_DEVICE;
429   else if (S_ISLNK (statbuf.st_mode))
430     icon_type = ICON_BROKEN_SYMBOLIC_LINK; /* See above */
431   else if (S_ISCHR (statbuf.st_mode))
432     icon_type = ICON_CHARACTER_DEVICE;
433   else if (S_ISDIR (statbuf.st_mode))
434     icon_type = ICON_DIRECTORY;
435   else if (S_ISFIFO (statbuf.st_mode))
436     icon_type =  ICON_FIFO;
437   else if (S_ISSOCK (statbuf.st_mode))
438     icon_type = ICON_SOCKET;
439   else
440     {
441       icon_type = ICON_REGULAR;
442
443 #if 0
444       if ((types & GTK_FILE_INFO_ICON) && icon_type == GTK_FILE_ICON_REGULAR &&
445           (statbuf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) &&
446           (strcmp (mime_type, XDG_MIME_TYPE_UNKNOWN) == 0 ||
447            strcmp (mime_type, "application/x-executable") == 0 ||
448            strcmp (mime_type, "application/x-shellscript") == 0))
449         gtk_file_info_set_icon_type (info, GTK_FILE_ICON_EXECUTABLE);
450 #endif
451     }
452
453   return icon_type;
454 }
455
456 typedef struct
457 {
458   gint size;
459   GdkPixbuf *pixbuf;
460 } IconCacheElement;
461
462 static void
463 icon_cache_element_free (IconCacheElement *element)
464 {
465   if (element->pixbuf)
466     g_object_unref (element->pixbuf);
467   g_free (element);
468 }
469
470 static void
471 icon_theme_changed (GtkIconTheme *icon_theme)
472 {
473   GHashTable *cache;
474
475   /* Difference from the initial creation is that we don't
476    * reconnect the signal
477    */
478   cache = g_hash_table_new_full (g_str_hash, g_str_equal,
479                                  (GDestroyNotify)g_free,
480                                  (GDestroyNotify)icon_cache_element_free);
481   g_object_set_data_full (G_OBJECT (icon_theme), "gtk-file-icon-cache",
482                           cache, (GDestroyNotify)g_hash_table_destroy);
483 }
484
485 static GdkPixbuf *
486 get_cached_icon (GtkWidget   *widget,
487                  const gchar *name,
488                  gint         pixel_size)
489 {
490   GtkIconTheme *icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget));
491   GHashTable *cache = g_object_get_data (G_OBJECT (icon_theme), "gtk-file-icon-cache");
492   IconCacheElement *element;
493
494   if (!cache)
495     {
496       cache = g_hash_table_new_full (g_str_hash, g_str_equal,
497                                      (GDestroyNotify)g_free,
498                                      (GDestroyNotify)icon_cache_element_free);
499
500       g_object_set_data_full (G_OBJECT (icon_theme), "gtk-file-icon-cache",
501                               cache, (GDestroyNotify)g_hash_table_destroy);
502       g_signal_connect (icon_theme, "changed",
503                         G_CALLBACK (icon_theme_changed), NULL);
504     }
505
506   element = g_hash_table_lookup (cache, name);
507   if (!element)
508     {
509       element = g_new0 (IconCacheElement, 1);
510       g_hash_table_insert (cache, g_strdup (name), element);
511     }
512
513   if (element->size != pixel_size)
514     {
515       if (element->pixbuf)
516         g_object_unref (element->pixbuf);
517       element->size = pixel_size;
518       element->pixbuf = gtk_icon_theme_load_icon (icon_theme, name,
519                                                   pixel_size, 0, NULL);
520     }
521
522   return element->pixbuf ? g_object_ref (element->pixbuf) : NULL;
523 }
524
525 static GdkPixbuf *
526 gtk_file_system_unix_volume_render_icon (GtkFileSystem        *file_system,
527                                          GtkFileSystemVolume  *volume,
528                                          GtkWidget            *widget,
529                                          gint                  pixel_size,
530                                          GError              **error)
531 {
532   /* FIXME: set the GError if we can't load the icon */
533   return get_cached_icon (widget, "gnome-fs-blockdev", pixel_size);
534 }
535
536 static gboolean
537 gtk_file_system_unix_get_parent (GtkFileSystem     *file_system,
538                                  const GtkFilePath *path,
539                                  GtkFilePath      **parent,
540                                  GError           **error)
541 {
542   const char *filename;
543
544   filename = gtk_file_path_get_string (path);
545   g_return_val_if_fail (filename != NULL, FALSE);
546   g_return_val_if_fail (g_path_is_absolute (filename), FALSE);
547
548   if (filename_is_root (filename))
549     {
550       *parent = NULL;
551     }
552   else
553     {
554       gchar *parent_filename = g_path_get_dirname (filename);
555       *parent = filename_to_path (parent_filename);
556       g_free (parent_filename);
557     }
558
559   return TRUE;
560 }
561
562 static GtkFilePath *
563 gtk_file_system_unix_make_path (GtkFileSystem    *file_system,
564                                const GtkFilePath *base_path,
565                                const gchar       *display_name,
566                                GError           **error)
567 {
568   const char *base_filename;
569   gchar *filename;
570   gchar *full_filename;
571   GError *tmp_error = NULL;
572   GtkFilePath *result;
573
574   base_filename = gtk_file_path_get_string (base_path);
575   g_return_val_if_fail (base_filename != NULL, NULL);
576   g_return_val_if_fail (g_path_is_absolute (base_filename), NULL);
577
578   filename = g_filename_from_utf8 (display_name, -1, NULL, NULL, &tmp_error);
579   if (!filename)
580     {
581       g_set_error (error,
582                    GTK_FILE_SYSTEM_ERROR,
583                    GTK_FILE_SYSTEM_ERROR_BAD_FILENAME,
584                    "%s",
585                    tmp_error->message);
586
587       g_error_free (tmp_error);
588
589       return NULL;
590     }
591
592   full_filename = g_build_filename (base_filename, filename, NULL);
593   result = filename_to_path (full_filename);
594   g_free (filename);
595   g_free (full_filename);
596
597   return result;
598 }
599
600 /* If this was a publically exported function, it should return
601  * a dup'ed result, but we make it modify-in-place for efficiency
602  * here, and because it works for us.
603  */
604 static void
605 canonicalize_filename (gchar *filename)
606 {
607   gchar *p, *q;
608   gboolean last_was_slash = FALSE;
609
610   p = filename;
611   q = filename;
612
613   while (*p)
614     {
615       if (*p == G_DIR_SEPARATOR)
616         {
617           if (!last_was_slash)
618             *q++ = G_DIR_SEPARATOR;
619
620           last_was_slash = TRUE;
621         }
622       else
623         {
624           if (last_was_slash && *p == '.')
625             {
626               if (*(p + 1) == G_DIR_SEPARATOR ||
627                   *(p + 1) == '\0')
628                 {
629                   if (*(p + 1) == '\0')
630                     break;
631
632                   p += 1;
633                 }
634               else if (*(p + 1) == '.' &&
635                        (*(p + 2) == G_DIR_SEPARATOR ||
636                         *(p + 2) == '\0'))
637                 {
638                   if (q > filename + 1)
639                     {
640                       q--;
641                       while (q > filename + 1 &&
642                              *(q - 1) != G_DIR_SEPARATOR)
643                         q--;
644                     }
645
646                   if (*(p + 2) == '\0')
647                     break;
648
649                   p += 2;
650                 }
651               else
652                 {
653                   *q++ = *p;
654                   last_was_slash = FALSE;
655                 }
656             }
657           else
658             {
659               *q++ = *p;
660               last_was_slash = FALSE;
661             }
662         }
663
664       p++;
665     }
666
667   if (q > filename + 1 && *(q - 1) == G_DIR_SEPARATOR)
668     q--;
669
670   *q = '\0';
671 }
672
673 static gboolean
674 gtk_file_system_unix_parse (GtkFileSystem     *file_system,
675                             const GtkFilePath *base_path,
676                             const gchar       *str,
677                             GtkFilePath      **folder,
678                             gchar            **file_part,
679                             GError           **error)
680 {
681   const char *base_filename;
682   gchar *last_slash;
683   gboolean result = FALSE;
684
685   base_filename = gtk_file_path_get_string (base_path);
686   g_return_val_if_fail (base_filename != NULL, FALSE);
687   g_return_val_if_fail (g_path_is_absolute (base_filename), FALSE);
688
689   last_slash = strrchr (str, G_DIR_SEPARATOR);
690   if (!last_slash)
691     {
692       *folder = gtk_file_path_copy (base_path);
693       *file_part = g_strdup (str);
694       result = TRUE;
695     }
696   else
697     {
698       gchar *folder_part;
699       gchar *folder_path;
700       GError *tmp_error = NULL;
701
702       if (last_slash == str)
703         folder_part = g_strdup ("/");
704       else
705         folder_part = g_filename_from_utf8 (str, last_slash - str,
706                                             NULL, NULL, &tmp_error);
707
708       if (!folder_part)
709         {
710           g_set_error (error,
711                        GTK_FILE_SYSTEM_ERROR,
712                        GTK_FILE_SYSTEM_ERROR_BAD_FILENAME,
713                        "%s",
714                        tmp_error->message);
715           g_error_free (tmp_error);
716         }
717       else
718         {
719           if (folder_part[0] == G_DIR_SEPARATOR)
720             folder_path = folder_part;
721           else
722             {
723               folder_path = g_build_filename (base_filename, folder_part, NULL);
724               g_free (folder_part);
725             }
726
727           canonicalize_filename (folder_path);
728
729           *folder = filename_to_path (folder_path);
730           *file_part = g_strdup (last_slash + 1);
731
732           g_free (folder_path);
733
734           result = TRUE;
735         }
736     }
737
738   return result;
739 }
740
741 static gchar *
742 gtk_file_system_unix_path_to_uri (GtkFileSystem     *file_system,
743                                   const GtkFilePath *path)
744 {
745   return g_filename_to_uri (gtk_file_path_get_string (path), NULL, NULL);
746 }
747
748 static gchar *
749 gtk_file_system_unix_path_to_filename (GtkFileSystem     *file_system,
750                                        const GtkFilePath *path)
751 {
752   return g_strdup (gtk_file_path_get_string (path));
753 }
754
755 static GtkFilePath *
756 gtk_file_system_unix_uri_to_path (GtkFileSystem     *file_system,
757                                   const gchar       *uri)
758 {
759   gchar *filename = g_filename_from_uri (uri, NULL, NULL);
760   if (filename)
761     return gtk_file_path_new_steal (filename);
762   else
763     return NULL;
764 }
765
766 static GtkFilePath *
767 gtk_file_system_unix_filename_to_path (GtkFileSystem *file_system,
768                                        const gchar   *filename)
769 {
770   return gtk_file_path_new_dup (filename);
771 }
772
773 static GdkPixbuf *
774 gtk_file_system_unix_render_icon (GtkFileSystem     *file_system,
775                                   const GtkFilePath *path,
776                                   GtkWidget         *widget,
777                                   gint               pixel_size,
778                                   GError           **error)
779 {
780   const char *filename;
781   IconType icon_type;
782   const char *mime_type;
783
784   filename = gtk_file_path_get_string (path);
785   icon_type = get_icon_type (filename, error);
786
787   /* FIXME: this function should not return NULL without setting the GError; we
788    * should perhaps provide a "never fails" generic stock icon for when all else
789    * fails.
790    */
791
792   if (icon_type == ICON_NONE)
793     return NULL;
794
795   if (icon_type != ICON_REGULAR)
796     {
797       const char *name;
798
799       switch (icon_type)
800         {
801         case ICON_BLOCK_DEVICE:
802           name = "gnome-fs-blockdev";
803           break;
804         case ICON_BROKEN_SYMBOLIC_LINK:
805           name = "gnome-fs-symlink";
806           break;
807         case ICON_CHARACTER_DEVICE:
808           name = "gnome-fs-chardev";
809           break;
810         case ICON_DIRECTORY:
811           name = "gnome-fs-directory";
812           break;
813         case ICON_EXECUTABLE:
814           name ="gnome-fs-executable";
815           break;
816         case ICON_FIFO:
817           name = "gnome-fs-fifo";
818           break;
819         case ICON_SOCKET:
820           name = "gnome-fs-socket";
821           break;
822         default:
823           g_assert_not_reached ();
824           return NULL;
825         }
826
827       return get_cached_icon (widget, name, pixel_size);
828     }
829
830   mime_type = xdg_mime_get_mime_type_for_file (filename);
831   if (mime_type)
832     {
833       const char *separator;
834       GString *icon_name;
835       GdkPixbuf *pixbuf;
836
837       separator = strchr (mime_type, '/');
838       if (!separator)
839         return NULL;
840
841       icon_name = g_string_new ("gnome-mime-");
842       g_string_append_len (icon_name, mime_type, separator - mime_type);
843       g_string_append_c (icon_name, '-');
844       g_string_append (icon_name, separator + 1);
845       pixbuf = get_cached_icon (widget, icon_name->str, pixel_size);
846       g_string_free (icon_name, TRUE);
847       if (pixbuf)
848         return pixbuf;
849
850       icon_name = g_string_new ("gnome-mime-");
851       g_string_append_len (icon_name, mime_type, separator - mime_type);
852       pixbuf = get_cached_icon (widget, icon_name->str, pixel_size);
853       g_string_free (icon_name, TRUE);
854       if (pixbuf)
855         return pixbuf;
856     }
857
858   return get_cached_icon (widget, "gnome-fs-regular", pixel_size);
859 }
860
861 static void
862 bookmark_list_free (GSList *list)
863 {
864   GSList *l;
865
866   for (l = list; l; l = l->next)
867     g_free (l->data);
868
869   g_slist_free (list);
870 }
871
872 /* Returns whether a URI is a local file:// */
873 static gboolean
874 is_local_uri (const char *uri)
875 {
876   char *filename;
877   char *hostname;
878   gboolean result;
879
880   /* This is rather crude, but hey */
881   filename = g_filename_from_uri (uri, &hostname, NULL);
882
883   result = (filename && !hostname);
884
885   g_free (filename);
886   g_free (hostname);
887
888   return result;
889 }
890
891 static char *
892 bookmark_get_filename (gboolean tmp_file)
893 {
894   char *filename;
895
896   filename = g_build_filename (g_get_home_dir (),
897                                tmp_file ? BOOKMARKS_TMP_FILENAME : BOOKMARKS_FILENAME,
898                                NULL);
899   g_assert (filename != NULL);
900   return filename;
901 }
902
903 static gboolean
904 bookmark_list_read (GSList **bookmarks, GError **error)
905 {
906   gchar *filename;
907   gchar *contents;
908   gboolean result;
909
910   filename = bookmark_get_filename (FALSE);
911   *bookmarks = NULL;
912
913   if (g_file_get_contents (filename, &contents, NULL, error))
914     {
915       gchar **lines = g_strsplit (contents, "\n", -1);
916       int i;
917       GHashTable *table;
918
919       table = g_hash_table_new (g_str_hash, g_str_equal);
920
921       for (i = 0; lines[i]; i++)
922         {
923           if (lines[i][0] && !g_hash_table_lookup (table, lines[i]))
924             {
925               *bookmarks = g_slist_prepend (*bookmarks, g_strdup (lines[i]));
926               g_hash_table_insert (table, lines[i], lines[i]);
927             }
928         }
929
930       g_free (contents);
931       g_hash_table_destroy (table);
932       g_strfreev (lines);
933
934       *bookmarks = g_slist_reverse (*bookmarks);
935       result = TRUE;
936     }
937
938   g_free (filename);
939
940   return result;
941 }
942
943 static gboolean
944 bookmark_list_write (GSList *bookmarks, GError **error)
945 {
946   char *tmp_filename;
947   char *filename;
948   gboolean result = TRUE;
949   FILE *file;
950   int fd;
951   int saved_errno;
952
953   /* First, write a temporary file */
954
955   tmp_filename = bookmark_get_filename (TRUE);
956   filename = bookmark_get_filename (FALSE);
957
958   fd = g_mkstemp (tmp_filename);
959   if (fd == -1)
960     {
961       saved_errno = errno;
962       goto io_error;
963     }
964
965   if ((file = fdopen (fd, "w")) != NULL)
966     {
967       GSList *l;
968
969       for (l = bookmarks; l; l = l->next)
970         if (fputs (l->data, file) == EOF
971             || fputs ("\n", file) == EOF)
972           {
973             saved_errno = errno;
974             goto io_error;
975           }
976
977       if (fclose (file) == EOF)
978         {
979           saved_errno = errno;
980           goto io_error;
981         }
982
983       if (rename (tmp_filename, filename) == -1)
984         {
985           saved_errno = errno;
986           goto io_error;
987         }
988
989       result = TRUE;
990       goto out;
991     }
992   else
993     {
994       saved_errno = errno;
995
996       /* fdopen() failed, so we can't do much error checking here anyway */
997       close (fd);
998     }
999
1000  io_error:
1001
1002   g_set_error (error,
1003                GTK_FILE_SYSTEM_ERROR,
1004                GTK_FILE_SYSTEM_ERROR_FAILED,
1005                _("Bookmark saving failed (%s)"),
1006                g_strerror (saved_errno));
1007   result = FALSE;
1008
1009   if (fd != -1)
1010     unlink (tmp_filename); /* again, not much error checking we can do here */
1011
1012  out:
1013
1014   g_free (filename);
1015   g_free (tmp_filename);
1016
1017   return result;
1018 }
1019
1020 static gboolean
1021 gtk_file_system_unix_add_bookmark (GtkFileSystem     *file_system,
1022                                    const GtkFilePath *path,
1023                                    GError           **error)
1024 {
1025   GSList *bookmarks;
1026   GSList *l;
1027   char *uri;
1028   gboolean result;
1029
1030   if (!bookmark_list_read (&bookmarks, error))
1031     return FALSE;
1032
1033   result = FALSE;
1034
1035   uri = gtk_file_system_unix_path_to_uri (file_system, path);
1036
1037   for (l = bookmarks; l; l = l->next)
1038     {
1039       const char *bookmark;
1040
1041       bookmark = l->data;
1042       if (strcmp (bookmark, uri) == 0)
1043         break;
1044     }
1045
1046   if (!l)
1047     {
1048       bookmarks = g_slist_append (bookmarks, g_strdup (uri));
1049       if (bookmark_list_write (bookmarks, error))
1050         {
1051           result = TRUE;
1052           g_signal_emit_by_name (file_system, "bookmarks-changed", 0);
1053         }
1054     }
1055
1056   g_free (uri);
1057   bookmark_list_free (bookmarks);
1058
1059   return result;
1060 }
1061
1062 static gboolean
1063 gtk_file_system_unix_remove_bookmark (GtkFileSystem     *file_system,
1064                                       const GtkFilePath *path,
1065                                       GError           **error)
1066 {
1067   GSList *bookmarks;
1068   char *uri;
1069   GSList *l;
1070   gboolean result;
1071
1072   if (!bookmark_list_read (&bookmarks, error))
1073     return FALSE;
1074
1075   result = FALSE;
1076
1077   uri = gtk_file_system_path_to_uri (file_system, path);
1078
1079   for (l = bookmarks; l; l = l->next)
1080     {
1081       const char *bookmark;
1082
1083       bookmark = l->data;
1084       if (strcmp (bookmark, uri) == 0)
1085         break;
1086     }
1087
1088   if (l)
1089     {
1090       g_free (l->data);
1091       bookmarks = g_slist_remove_link (bookmarks, l);
1092       g_slist_free_1 (l);
1093
1094       if (bookmark_list_write (bookmarks, error))
1095         result = TRUE;
1096     }
1097   else
1098     result = TRUE;
1099
1100   g_free (uri);
1101   bookmark_list_free (bookmarks);
1102
1103   if (result)
1104     g_signal_emit_by_name (file_system, "bookmarks-changed", 0);
1105
1106   return result;
1107 }
1108
1109 static GSList *
1110 gtk_file_system_unix_list_bookmarks (GtkFileSystem *file_system)
1111 {
1112   GSList *bookmarks;
1113   GSList *result;
1114   GSList *l;
1115
1116   if (!bookmark_list_read (&bookmarks, NULL))
1117     return NULL;
1118
1119   result = NULL;
1120
1121   for (l = bookmarks; l; l = l->next)
1122     {
1123       const char *name;
1124
1125       name = l->data;
1126
1127       if (is_local_uri (name))
1128         result = g_slist_prepend (result, gtk_file_system_unix_uri_to_path (file_system, name));
1129     }
1130
1131   bookmark_list_free (bookmarks);
1132
1133   result = g_slist_reverse (result);
1134   return result;
1135 }
1136
1137 /*
1138  * GtkFileFolderUnix
1139  */
1140 static GType
1141 gtk_file_folder_unix_get_type (void)
1142 {
1143   static GType file_folder_unix_type = 0;
1144
1145   if (!file_folder_unix_type)
1146     {
1147       static const GTypeInfo file_folder_unix_info =
1148       {
1149         sizeof (GtkFileFolderUnixClass),
1150         NULL,           /* base_init */
1151         NULL,           /* base_finalize */
1152         (GClassInitFunc) gtk_file_folder_unix_class_init,
1153         NULL,           /* class_finalize */
1154         NULL,           /* class_data */
1155         sizeof (GtkFileFolderUnix),
1156         0,              /* n_preallocs */
1157         (GInstanceInitFunc) gtk_file_folder_unix_init,
1158       };
1159
1160       static const GInterfaceInfo file_folder_info =
1161       {
1162         (GInterfaceInitFunc) gtk_file_folder_unix_iface_init, /* interface_init */
1163         NULL,                                                 /* interface_finalize */
1164         NULL                                                  /* interface_data */
1165       };
1166
1167       file_folder_unix_type = g_type_register_static (G_TYPE_OBJECT,
1168                                                       "GtkFileFolderUnix",
1169                                                       &file_folder_unix_info, 0);
1170       g_type_add_interface_static (file_folder_unix_type,
1171                                    GTK_TYPE_FILE_FOLDER,
1172                                    &file_folder_info);
1173     }
1174
1175   return file_folder_unix_type;
1176 }
1177
1178 static void
1179 gtk_file_folder_unix_class_init (GtkFileFolderUnixClass *class)
1180 {
1181   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
1182
1183   folder_parent_class = g_type_class_peek_parent (class);
1184
1185   gobject_class->finalize = gtk_file_folder_unix_finalize;
1186 }
1187
1188 static void
1189 gtk_file_folder_unix_iface_init (GtkFileFolderIface *iface)
1190 {
1191   iface->get_info = gtk_file_folder_unix_get_info;
1192   iface->list_children = gtk_file_folder_unix_list_children;
1193 }
1194
1195 static void
1196 gtk_file_folder_unix_init (GtkFileFolderUnix *impl)
1197 {
1198 }
1199
1200 static void
1201 gtk_file_folder_unix_finalize (GObject *object)
1202 {
1203   GtkFileFolderUnix *folder_unix = GTK_FILE_FOLDER_UNIX (object);
1204
1205   g_free (folder_unix->filename);
1206
1207   folder_parent_class->finalize (object);
1208 }
1209
1210 static GtkFileInfo *
1211 gtk_file_folder_unix_get_info (GtkFileFolder  *folder,
1212                                const GtkFilePath    *path,
1213                                GError        **error)
1214 {
1215   GtkFileFolderUnix *folder_unix = GTK_FILE_FOLDER_UNIX (folder);
1216   GtkFileInfo *info;
1217   gchar *dirname;
1218   const char *filename;
1219
1220   filename = gtk_file_path_get_string (path);
1221   g_return_val_if_fail (filename != NULL, NULL);
1222   g_return_val_if_fail (g_path_is_absolute (filename), NULL);
1223
1224   dirname = g_path_get_dirname (filename);
1225   g_return_val_if_fail (strcmp (dirname, folder_unix->filename) == 0, NULL);
1226   g_free (dirname);
1227
1228   info = filename_get_info (filename, folder_unix->types, error);
1229
1230   return info;
1231 }
1232
1233 static gboolean
1234 gtk_file_folder_unix_list_children (GtkFileFolder  *folder,
1235                                     GSList        **children,
1236                                     GError        **error)
1237 {
1238   GtkFileFolderUnix *folder_unix = GTK_FILE_FOLDER_UNIX (folder);
1239   GError *tmp_error = NULL;
1240   GDir *dir;
1241
1242   *children = NULL;
1243
1244   dir = g_dir_open (folder_unix->filename, 0, &tmp_error);
1245   if (!dir)
1246     {
1247       g_set_error (error,
1248                    GTK_FILE_SYSTEM_ERROR,
1249                    GTK_FILE_SYSTEM_ERROR_NONEXISTENT,
1250                    "%s",
1251                    tmp_error->message);
1252
1253       g_error_free (tmp_error);
1254
1255       return FALSE;
1256     }
1257
1258   while (TRUE)
1259     {
1260       const gchar *filename = g_dir_read_name (dir);
1261       gchar *fullname;
1262
1263       if (!filename)
1264         break;
1265
1266       fullname = g_build_filename (folder_unix->filename, filename, NULL);
1267       *children = g_slist_prepend (*children, filename_to_path (fullname));
1268       g_free (fullname);
1269     }
1270
1271   g_dir_close (dir);
1272
1273   *children = g_slist_reverse (*children);
1274
1275   return TRUE;
1276 }
1277
1278 static GtkFileInfo *
1279 filename_get_info (const gchar     *filename,
1280                    GtkFileInfoType  types,
1281                    GError         **error)
1282 {
1283   GtkFileInfo *info;
1284   struct stat statbuf;
1285
1286   /* If stat fails, try to fall back to lstat to catch broken links
1287    */
1288   if (stat (filename, &statbuf) != 0 &&
1289       lstat (filename, &statbuf) != 0)
1290     {
1291       gchar *filename_utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
1292       g_set_error (error,
1293                    GTK_FILE_SYSTEM_ERROR,
1294                    GTK_FILE_SYSTEM_ERROR_NONEXISTENT,
1295                    _("error getting information for '%s': %s"),
1296                    filename_utf8 ? filename_utf8 : "???",
1297                    g_strerror (errno));
1298       g_free (filename_utf8);
1299
1300       return NULL;
1301     }
1302
1303   info = gtk_file_info_new ();
1304
1305   if (filename_is_root (filename))
1306     {
1307       if (types & GTK_FILE_INFO_DISPLAY_NAME)
1308         gtk_file_info_set_display_name (info, "/");
1309
1310       if (types & GTK_FILE_INFO_IS_HIDDEN)
1311         gtk_file_info_set_is_hidden (info, FALSE);
1312     }
1313   else
1314     {
1315       gchar *basename = g_path_get_basename (filename);
1316
1317       if (types & GTK_FILE_INFO_DISPLAY_NAME)
1318         {
1319           gchar *display_name = g_filename_to_utf8 (basename, -1, NULL, NULL, NULL);
1320           if (!display_name)
1321             display_name = g_strescape (basename, NULL);
1322
1323           gtk_file_info_set_display_name (info, display_name);
1324
1325           g_free (display_name);
1326         }
1327
1328       if (types & GTK_FILE_INFO_IS_HIDDEN)
1329         {
1330           gtk_file_info_set_is_hidden (info, basename[0] == '.');
1331         }
1332
1333       g_free (basename);
1334     }
1335
1336   if (types & GTK_FILE_INFO_IS_FOLDER)
1337     {
1338       gtk_file_info_set_is_folder (info, S_ISDIR (statbuf.st_mode));
1339    }
1340
1341   if (types & GTK_FILE_INFO_MIME_TYPE)
1342     {
1343       const char *mime_type = xdg_mime_get_mime_type_for_file (filename);
1344       gtk_file_info_set_mime_type (info, mime_type);
1345     }
1346
1347   if (types & GTK_FILE_INFO_MODIFICATION_TIME)
1348     {
1349       gtk_file_info_set_modification_time (info, statbuf.st_mtime);
1350     }
1351
1352   if (types & GTK_FILE_INFO_SIZE)
1353     {
1354       gtk_file_info_set_size (info, (gint64)statbuf.st_size);
1355     }
1356
1357   return info;
1358 }
1359
1360 static GtkFilePath *
1361 filename_to_path (const char *filename)
1362 {
1363   return gtk_file_path_new_dup (filename);
1364 }
1365
1366 static gboolean
1367 filename_is_root (const char *filename)
1368 {
1369   const gchar *after_root;
1370
1371   after_root = g_path_skip_root (filename);
1372
1373   return (after_root != NULL && *after_root == '\0');
1374 }