]> Pileus Git - ~andy/gtk/blob - gtk/gtkfilesystemunix.c
Fix #139290:
[~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 <config.h>
22
23 #include "gtkfilesystem.h"
24 #include "gtkfilesystemunix.h"
25 #include "gtkicontheme.h"
26 #include "gtkintl.h"
27
28 #define XDG_PREFIX _gtk_xdg
29 #include "xdgmime/xdgmime.h"
30
31 #include <errno.h>
32 #include <string.h>
33 #include <sys/stat.h>
34 #include <sys/types.h>
35 #include <pwd.h>
36 #include <unistd.h>
37 #include <stdio.h>
38 #include <time.h>
39
40 #define BOOKMARKS_FILENAME ".gtk-bookmarks"
41 #define BOOKMARKS_TMP_FILENAME ".gtk-bookmarks-XXXXXX"
42
43 #define FOLDER_CACHE_LIFETIME 2 /* seconds */
44
45 typedef struct _GtkFileSystemUnixClass GtkFileSystemUnixClass;
46
47 #define GTK_FILE_SYSTEM_UNIX_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_SYSTEM_UNIX, GtkFileSystemUnixClass))
48 #define GTK_IS_FILE_SYSTEM_UNIX_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILE_SYSTEM_UNIX))
49 #define GTK_FILE_SYSTEM_UNIX_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILE_SYSTEM_UNIX, GtkFileSystemUnixClass))
50
51 struct _GtkFileSystemUnixClass
52 {
53   GObjectClass parent_class;
54 };
55
56 struct _GtkFileSystemUnix
57 {
58   GObject parent_instance;
59
60   GHashTable *folder_hash;
61 };
62
63 /* Icon type, supplemented by MIME type
64  */
65 typedef enum {
66   ICON_UNDECIDED,
67   ICON_NONE,
68   ICON_REGULAR, /* Use mime type for icon */
69   ICON_BLOCK_DEVICE,
70   ICON_BROKEN_SYMBOLIC_LINK,
71   ICON_CHARACTER_DEVICE,
72   ICON_DIRECTORY,
73   ICON_EXECUTABLE,
74   ICON_FIFO,
75   ICON_SOCKET
76 } IconType;
77
78
79 #define GTK_TYPE_FILE_FOLDER_UNIX             (gtk_file_folder_unix_get_type ())
80 #define GTK_FILE_FOLDER_UNIX(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_FILE_FOLDER_UNIX, GtkFileFolderUnix))
81 #define GTK_IS_FILE_FOLDER_UNIX(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_FILE_FOLDER_UNIX))
82 #define GTK_FILE_FOLDER_UNIX_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_FOLDER_UNIX, GtkFileFolderUnixClass))
83 #define GTK_IS_FILE_FOLDER_UNIX_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILE_FOLDER_UNIX))
84 #define GTK_FILE_FOLDER_UNIX_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILE_FOLDER_UNIX, GtkFileFolderUnixClass))
85
86 typedef struct _GtkFileFolderUnix      GtkFileFolderUnix;
87 typedef struct _GtkFileFolderUnixClass GtkFileFolderUnixClass;
88
89 struct _GtkFileFolderUnixClass
90 {
91   GObjectClass parent_class;
92 };
93
94 struct _GtkFileFolderUnix
95 {
96   GObject parent_instance;
97
98   GtkFileSystemUnix *system_unix;
99   GtkFileInfoType types;
100   gchar *filename;
101   GHashTable *stat_info;
102   unsigned int have_stat : 1;
103   unsigned int have_mime_type : 1;
104   time_t asof;
105 };
106
107 struct stat_info_entry {
108   struct stat statbuf;
109   char *mime_type;
110   IconType icon_type;
111 };
112
113 static const GtkFileInfoType STAT_NEEDED_MASK = (GTK_FILE_INFO_IS_FOLDER |
114                                                  GTK_FILE_INFO_IS_HIDDEN |
115                                                  GTK_FILE_INFO_MODIFICATION_TIME |
116                                                  GTK_FILE_INFO_SIZE);
117
118 static GObjectClass *system_parent_class;
119 static GObjectClass *folder_parent_class;
120
121 static void gtk_file_system_unix_class_init   (GtkFileSystemUnixClass *class);
122 static void gtk_file_system_unix_iface_init   (GtkFileSystemIface     *iface);
123 static void gtk_file_system_unix_init         (GtkFileSystemUnix      *impl);
124 static void gtk_file_system_unix_finalize     (GObject                *object);
125
126 static GSList *             gtk_file_system_unix_list_volumes        (GtkFileSystem     *file_system);
127 static GtkFileSystemVolume *gtk_file_system_unix_get_volume_for_path (GtkFileSystem     *file_system,
128                                                                       const GtkFilePath *path);
129
130 static GtkFileFolder *gtk_file_system_unix_get_folder    (GtkFileSystem      *file_system,
131                                                           const GtkFilePath  *path,
132                                                           GtkFileInfoType     types,
133                                                           GError            **error);
134 static gboolean       gtk_file_system_unix_create_folder (GtkFileSystem      *file_system,
135                                                           const GtkFilePath  *path,
136                                                           GError            **error);
137
138 static void         gtk_file_system_unix_volume_free             (GtkFileSystem       *file_system,
139                                                                   GtkFileSystemVolume *volume);
140 static GtkFilePath *gtk_file_system_unix_volume_get_base_path    (GtkFileSystem       *file_system,
141                                                                   GtkFileSystemVolume *volume);
142 static gboolean     gtk_file_system_unix_volume_get_is_mounted   (GtkFileSystem       *file_system,
143                                                                   GtkFileSystemVolume *volume);
144 static gboolean     gtk_file_system_unix_volume_mount            (GtkFileSystem       *file_system,
145                                                                   GtkFileSystemVolume *volume,
146                                                                   GError             **error);
147 static gchar *      gtk_file_system_unix_volume_get_display_name (GtkFileSystem       *file_system,
148                                                                   GtkFileSystemVolume *volume);
149 static GdkPixbuf *  gtk_file_system_unix_volume_render_icon      (GtkFileSystem        *file_system,
150                                                                   GtkFileSystemVolume  *volume,
151                                                                   GtkWidget            *widget,
152                                                                   gint                  pixel_size,
153                                                                   GError              **error);
154
155 static gboolean       gtk_file_system_unix_get_parent    (GtkFileSystem      *file_system,
156                                                           const GtkFilePath  *path,
157                                                           GtkFilePath       **parent,
158                                                           GError            **error);
159 static GtkFilePath *  gtk_file_system_unix_make_path     (GtkFileSystem      *file_system,
160                                                           const GtkFilePath  *base_path,
161                                                           const gchar        *display_name,
162                                                           GError            **error);
163 static gboolean       gtk_file_system_unix_parse         (GtkFileSystem      *file_system,
164                                                           const GtkFilePath  *base_path,
165                                                           const gchar        *str,
166                                                           GtkFilePath       **folder,
167                                                           gchar             **file_part,
168                                                           GError            **error);
169
170 static gchar *      gtk_file_system_unix_path_to_uri      (GtkFileSystem     *file_system,
171                                                            const GtkFilePath *path);
172 static gchar *      gtk_file_system_unix_path_to_filename (GtkFileSystem     *file_system,
173                                                            const GtkFilePath *path);
174 static GtkFilePath *gtk_file_system_unix_uri_to_path      (GtkFileSystem     *file_system,
175                                                            const gchar       *uri);
176 static GtkFilePath *gtk_file_system_unix_filename_to_path (GtkFileSystem     *file_system,
177                                                            const gchar       *filename);
178
179 static GdkPixbuf *gtk_file_system_unix_render_icon (GtkFileSystem     *file_system,
180                                                     const GtkFilePath *path,
181                                                     GtkWidget         *widget,
182                                                     gint               pixel_size,
183                                                     GError           **error);
184
185 static gboolean gtk_file_system_unix_insert_bookmark (GtkFileSystem     *file_system,
186                                                       const GtkFilePath *path,
187                                                       gint               position,
188                                                       GError           **error);
189 static gboolean gtk_file_system_unix_remove_bookmark (GtkFileSystem     *file_system,
190                                                       const GtkFilePath *path,
191                                                       GError           **error);
192 static GSList * gtk_file_system_unix_list_bookmarks  (GtkFileSystem *file_system);
193
194 static GType gtk_file_folder_unix_get_type   (void);
195 static void  gtk_file_folder_unix_class_init (GtkFileFolderUnixClass *class);
196 static void  gtk_file_folder_unix_iface_init (GtkFileFolderIface     *iface);
197 static void  gtk_file_folder_unix_init       (GtkFileFolderUnix      *impl);
198 static void  gtk_file_folder_unix_finalize   (GObject                *object);
199
200 static GtkFileInfo *gtk_file_folder_unix_get_info      (GtkFileFolder  *folder,
201                                                         const GtkFilePath    *path,
202                                                         GError        **error);
203 static gboolean     gtk_file_folder_unix_list_children (GtkFileFolder  *folder,
204                                                         GSList        **children,
205                                                         GError        **error);
206
207 static gboolean     gtk_file_folder_unix_is_finished_loading (GtkFileFolder *folder);
208
209 static GtkFilePath *filename_to_path   (const gchar       *filename);
210
211 static gboolean     filename_is_root  (const char       *filename);
212
213 static gboolean fill_in_names (GtkFileFolderUnix *folder_unix, GError **error);
214 static gboolean fill_in_stats (GtkFileFolderUnix *folder_unix, GError **error);
215 static gboolean fill_in_mime_type (GtkFileFolderUnix *folder_unix, GError **error);
216
217 static char *       get_parent_dir    (const char       *filename);
218
219 /*
220  * GtkFileSystemUnix
221  */
222 GType
223 gtk_file_system_unix_get_type (void)
224 {
225   static GType file_system_unix_type = 0;
226
227   if (!file_system_unix_type)
228     {
229       static const GTypeInfo file_system_unix_info =
230       {
231         sizeof (GtkFileSystemUnixClass),
232         NULL,           /* base_init */
233         NULL,           /* base_finalize */
234         (GClassInitFunc) gtk_file_system_unix_class_init,
235         NULL,           /* class_finalize */
236         NULL,           /* class_data */
237         sizeof (GtkFileSystemUnix),
238         0,              /* n_preallocs */
239         (GInstanceInitFunc) gtk_file_system_unix_init,
240       };
241
242       static const GInterfaceInfo file_system_info =
243       {
244         (GInterfaceInitFunc) gtk_file_system_unix_iface_init, /* interface_init */
245         NULL,                                                 /* interface_finalize */
246         NULL                                                  /* interface_data */
247       };
248
249       file_system_unix_type = g_type_register_static (G_TYPE_OBJECT,
250                                                       "GtkFileSystemUnix",
251                                                       &file_system_unix_info, 0);
252       g_type_add_interface_static (file_system_unix_type,
253                                    GTK_TYPE_FILE_SYSTEM,
254                                    &file_system_info);
255     }
256
257   return file_system_unix_type;
258 }
259
260 /**
261  * gtk_file_system_unix_new:
262  *
263  * Creates a new #GtkFileSystemUnix object. #GtkFileSystemUnix
264  * implements the #GtkFileSystem interface with direct access to
265  * the filesystem using Unix/Linux API calls
266  *
267  * Return value: the new #GtkFileSystemUnix object
268  **/
269 GtkFileSystem *
270 gtk_file_system_unix_new (void)
271 {
272   return g_object_new (GTK_TYPE_FILE_SYSTEM_UNIX, NULL);
273 }
274
275 static void
276 gtk_file_system_unix_class_init (GtkFileSystemUnixClass *class)
277 {
278   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
279
280   system_parent_class = g_type_class_peek_parent (class);
281
282   gobject_class->finalize = gtk_file_system_unix_finalize;
283 }
284
285 static void
286 gtk_file_system_unix_iface_init   (GtkFileSystemIface *iface)
287 {
288   iface->list_volumes = gtk_file_system_unix_list_volumes;
289   iface->get_volume_for_path = gtk_file_system_unix_get_volume_for_path;
290   iface->get_folder = gtk_file_system_unix_get_folder;
291   iface->create_folder = gtk_file_system_unix_create_folder;
292   iface->volume_free = gtk_file_system_unix_volume_free;
293   iface->volume_get_base_path = gtk_file_system_unix_volume_get_base_path;
294   iface->volume_get_is_mounted = gtk_file_system_unix_volume_get_is_mounted;
295   iface->volume_mount = gtk_file_system_unix_volume_mount;
296   iface->volume_get_display_name = gtk_file_system_unix_volume_get_display_name;
297   iface->volume_render_icon = gtk_file_system_unix_volume_render_icon;
298   iface->get_parent = gtk_file_system_unix_get_parent;
299   iface->make_path = gtk_file_system_unix_make_path;
300   iface->parse = gtk_file_system_unix_parse;
301   iface->path_to_uri = gtk_file_system_unix_path_to_uri;
302   iface->path_to_filename = gtk_file_system_unix_path_to_filename;
303   iface->uri_to_path = gtk_file_system_unix_uri_to_path;
304   iface->filename_to_path = gtk_file_system_unix_filename_to_path;
305   iface->render_icon = gtk_file_system_unix_render_icon;
306   iface->insert_bookmark = gtk_file_system_unix_insert_bookmark;
307   iface->remove_bookmark = gtk_file_system_unix_remove_bookmark;
308   iface->list_bookmarks = gtk_file_system_unix_list_bookmarks;
309 }
310
311 static void
312 gtk_file_system_unix_init (GtkFileSystemUnix *system_unix)
313 {
314   system_unix->folder_hash = g_hash_table_new (g_str_hash, g_str_equal);
315 }
316
317 static void
318 gtk_file_system_unix_finalize (GObject *object)
319 {
320   GtkFileSystemUnix *system_unix;
321
322   system_unix = GTK_FILE_SYSTEM_UNIX (object);
323
324   /* FIXME: assert that the hash is empty? */
325   g_hash_table_destroy (system_unix->folder_hash);
326
327   system_parent_class->finalize (object);
328 }
329
330 /* Returns our single root volume */
331 static GtkFileSystemVolume *
332 get_root_volume (void)
333 {
334   return (GtkFileSystemVolume *) gtk_file_path_new_dup ("/");
335 }
336
337 static GSList *
338 gtk_file_system_unix_list_volumes (GtkFileSystem *file_system)
339 {
340   return g_slist_append (NULL, get_root_volume ());
341 }
342
343 static GtkFileSystemVolume *
344 gtk_file_system_unix_get_volume_for_path (GtkFileSystem     *file_system,
345                                           const GtkFilePath *path)
346 {
347   return get_root_volume ();
348 }
349
350 static char *
351 remove_trailing_slash (const char *filename)
352 {
353   int len;
354
355   len = strlen (filename);
356
357   if (len > 1 && filename[len - 1] == '/')
358     return g_strndup (filename, len - 1);
359   else
360     return g_memdup (filename, len + 1);
361 }
362
363 static GtkFileFolder *
364 gtk_file_system_unix_get_folder (GtkFileSystem     *file_system,
365                                  const GtkFilePath *path,
366                                  GtkFileInfoType    types,
367                                  GError           **error)
368 {
369   GtkFileSystemUnix *system_unix;
370   GtkFileFolderUnix *folder_unix;
371   const char *filename;
372   char *filename_copy;
373   time_t now = time (NULL);
374
375   system_unix = GTK_FILE_SYSTEM_UNIX (file_system);
376
377   filename = gtk_file_path_get_string (path);
378   g_return_val_if_fail (filename != NULL, NULL);
379   g_return_val_if_fail (g_path_is_absolute (filename), NULL);
380
381   filename_copy = remove_trailing_slash (filename);
382   folder_unix = g_hash_table_lookup (system_unix->folder_hash, filename_copy);
383
384   if (folder_unix)
385     {
386       g_free (filename_copy);
387       if (now - folder_unix->asof >= FOLDER_CACHE_LIFETIME &&
388           folder_unix->stat_info)
389         {
390 #if 0
391           g_print ("Cleaning out cached directory %s\n", filename);
392 #endif
393           g_hash_table_destroy (folder_unix->stat_info);
394           folder_unix->stat_info = NULL;
395           folder_unix->have_mime_type = FALSE;
396           folder_unix->have_stat = FALSE;
397         }
398
399       g_object_ref (folder_unix);
400       folder_unix->types |= types;
401       types = folder_unix->types;
402     }
403   else
404     {
405       if (!g_file_test (filename, G_FILE_TEST_IS_DIR))
406         {
407           int save_errno = errno;
408           gchar *filename_utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
409
410           /* If g_file_test() returned FALSE but not due to an error, it means
411            * that the filename is not a directory.
412            */
413           if (save_errno == 0)
414             /* ENOTDIR */
415             g_set_error (error,
416                          GTK_FILE_SYSTEM_ERROR,
417                          GTK_FILE_SYSTEM_ERROR_NOT_FOLDER,
418                          _("%s: %s"),
419                          filename_utf8 ? filename_utf8 : "???",
420                          g_strerror (ENOTDIR));
421           else
422             g_set_error (error,
423                          GTK_FILE_SYSTEM_ERROR,
424                          GTK_FILE_SYSTEM_ERROR_NONEXISTENT,
425                          _("error getting information for '%s': %s"),
426                          filename_utf8 ? filename_utf8 : "???",
427                          g_strerror (save_errno));
428
429           g_free (filename_utf8);
430           g_free (filename_copy);
431           return NULL;
432         }
433
434       folder_unix = g_object_new (GTK_TYPE_FILE_FOLDER_UNIX, NULL);
435       folder_unix->system_unix = system_unix;
436       folder_unix->filename = filename_copy;
437       folder_unix->types = types;
438       folder_unix->stat_info = NULL;
439       folder_unix->asof = now;
440       folder_unix->have_mime_type = FALSE;
441       folder_unix->have_stat = FALSE;
442
443       g_hash_table_insert (system_unix->folder_hash,
444                            folder_unix->filename,
445                            folder_unix);
446     }
447
448   if ((types & STAT_NEEDED_MASK) && !fill_in_stats (folder_unix, error))
449     {
450       g_object_unref (folder_unix);
451       return NULL;
452     }
453   if ((types & GTK_FILE_INFO_MIME_TYPE) && !fill_in_mime_type (folder_unix, error))
454     {
455       g_object_unref (folder_unix);
456       return NULL;
457     }
458
459   return GTK_FILE_FOLDER (folder_unix);
460 }
461
462 static gboolean
463 gtk_file_system_unix_create_folder (GtkFileSystem     *file_system,
464                                     const GtkFilePath *path,
465                                     GError           **error)
466 {
467   GtkFileSystemUnix *system_unix;
468   const char *filename;
469   gboolean result;
470   char *parent, *tmp;
471
472   system_unix = GTK_FILE_SYSTEM_UNIX (file_system);
473
474   filename = gtk_file_path_get_string (path);
475   g_return_val_if_fail (filename != NULL, FALSE);
476   g_return_val_if_fail (g_path_is_absolute (filename), FALSE);
477
478   tmp = remove_trailing_slash (filename);
479   result = mkdir (tmp, 0777) == 0;
480   g_free (tmp);
481
482   if (!result)
483     {
484       int save_errno = errno;
485       gchar *filename_utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
486       g_set_error (error,
487                    GTK_FILE_SYSTEM_ERROR,
488                    GTK_FILE_SYSTEM_ERROR_NONEXISTENT,
489                    _("error creating directory '%s': %s"),
490                    filename_utf8 ? filename_utf8 : "???",
491                    g_strerror (save_errno));
492       g_free (filename_utf8);
493       return FALSE;
494     }
495
496   if (filename_is_root (filename))
497     return TRUE; /* hmmm, but with no notification */
498
499   parent = get_parent_dir (filename);
500   if (parent)
501     {
502       GtkFileFolderUnix *folder_unix;
503
504       folder_unix = g_hash_table_lookup (system_unix->folder_hash, parent);
505       if (folder_unix)
506         {
507           GtkFileInfoType types;
508           GtkFilePath *parent_path;
509           GSList *paths;
510           GtkFileFolder *folder;
511
512           /* This is sort of a hack.  We re-get the folder, to ensure that the
513            * newly-created directory gets read into the folder's info hash table.
514            */
515
516           types = folder_unix->types;
517
518           parent_path = gtk_file_path_new_dup (parent);
519           folder = gtk_file_system_get_folder (file_system, parent_path, types, NULL);
520           gtk_file_path_free (parent_path);
521
522           if (folder)
523             {
524               paths = g_slist_append (NULL, (GtkFilePath *) path);
525               g_signal_emit_by_name (folder, "files-added", paths);
526               g_slist_free (paths);
527               g_object_unref (folder);
528             }
529         }
530
531       g_free (parent);
532     }
533
534   return TRUE;
535 }
536
537 static void
538 gtk_file_system_unix_volume_free (GtkFileSystem        *file_system,
539                                   GtkFileSystemVolume  *volume)
540 {
541   GtkFilePath *path;
542
543   path = (GtkFilePath *) volume;
544   gtk_file_path_free (path);
545 }
546
547 static GtkFilePath *
548 gtk_file_system_unix_volume_get_base_path (GtkFileSystem        *file_system,
549                                            GtkFileSystemVolume  *volume)
550 {
551   return gtk_file_path_new_dup ("/");
552 }
553
554 static gboolean
555 gtk_file_system_unix_volume_get_is_mounted (GtkFileSystem        *file_system,
556                                             GtkFileSystemVolume  *volume)
557 {
558   return TRUE;
559 }
560
561 static gboolean
562 gtk_file_system_unix_volume_mount (GtkFileSystem        *file_system,
563                                    GtkFileSystemVolume  *volume,
564                                    GError              **error)
565 {
566   g_set_error (error,
567                GTK_FILE_SYSTEM_ERROR,
568                GTK_FILE_SYSTEM_ERROR_FAILED,
569                _("This file system does not support mounting"));
570   return FALSE;
571 }
572
573 static gchar *
574 gtk_file_system_unix_volume_get_display_name (GtkFileSystem       *file_system,
575                                               GtkFileSystemVolume *volume)
576 {
577   return g_strdup (_("Filesystem")); /* Same as Nautilus */
578 }
579
580 static IconType
581 get_icon_type_from_stat (struct stat *statp)
582 {
583   if (S_ISBLK (statp->st_mode))
584     return ICON_BLOCK_DEVICE;
585   else if (S_ISLNK (statp->st_mode))
586     return ICON_BROKEN_SYMBOLIC_LINK; /* See get_icon_type */
587   else if (S_ISCHR (statp->st_mode))
588     return ICON_CHARACTER_DEVICE;
589   else if (S_ISDIR (statp->st_mode))
590     return ICON_DIRECTORY;
591   else if (S_ISFIFO (statp->st_mode))
592     return  ICON_FIFO;
593   else if (S_ISSOCK (statp->st_mode))
594     return ICON_SOCKET;
595   else
596     return ICON_REGULAR;
597 }
598
599 static IconType
600 get_icon_type (const char *filename,
601                GError    **error)
602 {
603   struct stat statbuf;
604
605   /* If stat fails, try to fall back to lstat to catch broken links
606    */
607   if (stat (filename, &statbuf) != 0)
608     {
609       if (errno != ENOENT || lstat (filename, &statbuf) != 0)
610         {
611           int save_errno = errno;
612           gchar *filename_utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
613           g_set_error (error,
614                        GTK_FILE_SYSTEM_ERROR,
615                        GTK_FILE_SYSTEM_ERROR_NONEXISTENT,
616                        _("error getting information for '%s': %s"),
617                        filename_utf8 ? filename_utf8 : "???",
618                        g_strerror (save_errno));
619           g_free (filename_utf8);
620
621           return ICON_NONE;
622         }
623     }
624
625   return get_icon_type_from_stat (&statbuf);
626 }
627
628 typedef struct
629 {
630   gint size;
631   GdkPixbuf *pixbuf;
632 } IconCacheElement;
633
634 static void
635 icon_cache_element_free (IconCacheElement *element)
636 {
637   if (element->pixbuf)
638     g_object_unref (element->pixbuf);
639   g_free (element);
640 }
641
642 static void
643 icon_theme_changed (GtkIconTheme *icon_theme)
644 {
645   GHashTable *cache;
646
647   /* Difference from the initial creation is that we don't
648    * reconnect the signal
649    */
650   cache = g_hash_table_new_full (g_str_hash, g_str_equal,
651                                  (GDestroyNotify)g_free,
652                                  (GDestroyNotify)icon_cache_element_free);
653   g_object_set_data_full (G_OBJECT (icon_theme), "gtk-file-icon-cache",
654                           cache, (GDestroyNotify)g_hash_table_destroy);
655 }
656
657 static GdkPixbuf *
658 get_cached_icon (GtkWidget   *widget,
659                  const gchar *name,
660                  gint         pixel_size)
661 {
662   GtkIconTheme *icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget));
663   GHashTable *cache = g_object_get_data (G_OBJECT (icon_theme), "gtk-file-icon-cache");
664   IconCacheElement *element;
665
666   if (!cache)
667     {
668       cache = g_hash_table_new_full (g_str_hash, g_str_equal,
669                                      (GDestroyNotify)g_free,
670                                      (GDestroyNotify)icon_cache_element_free);
671
672       g_object_set_data_full (G_OBJECT (icon_theme), "gtk-file-icon-cache",
673                               cache, (GDestroyNotify)g_hash_table_destroy);
674       g_signal_connect (icon_theme, "changed",
675                         G_CALLBACK (icon_theme_changed), NULL);
676     }
677
678   element = g_hash_table_lookup (cache, name);
679   if (!element)
680     {
681       element = g_new0 (IconCacheElement, 1);
682       g_hash_table_insert (cache, g_strdup (name), element);
683     }
684
685   if (element->size != pixel_size)
686     {
687       if (element->pixbuf)
688         g_object_unref (element->pixbuf);
689       element->size = pixel_size;
690       element->pixbuf = gtk_icon_theme_load_icon (icon_theme, name,
691                                                   pixel_size, 0, NULL);
692     }
693
694   return element->pixbuf ? g_object_ref (element->pixbuf) : NULL;
695 }
696
697 static GdkPixbuf *
698 gtk_file_system_unix_volume_render_icon (GtkFileSystem        *file_system,
699                                          GtkFileSystemVolume  *volume,
700                                          GtkWidget            *widget,
701                                          gint                  pixel_size,
702                                          GError              **error)
703 {
704   /* FIXME: set the GError if we can't load the icon */
705   return get_cached_icon (widget, "gnome-fs-blockdev", pixel_size);
706 }
707
708 static char *
709 get_parent_dir (const char *filename)
710 {
711   int len;
712
713   len = strlen (filename);
714
715   /* Ignore trailing slashes */
716   if (len > 1 && filename[len - 1] == '/')
717     {
718       char *tmp, *parent;
719
720       tmp = g_strndup (filename, len - 1);
721
722       parent = g_path_get_dirname (tmp);
723       g_free (tmp);
724
725       return parent;
726     }
727   else
728     return g_path_get_dirname (filename);
729 }
730
731 static gboolean
732 gtk_file_system_unix_get_parent (GtkFileSystem     *file_system,
733                                  const GtkFilePath *path,
734                                  GtkFilePath      **parent,
735                                  GError           **error)
736 {
737   const char *filename;
738
739   filename = gtk_file_path_get_string (path);
740   g_return_val_if_fail (filename != NULL, FALSE);
741   g_return_val_if_fail (g_path_is_absolute (filename), FALSE);
742
743   if (filename_is_root (filename))
744     {
745       *parent = NULL;
746     }
747   else
748     {
749       gchar *parent_filename = get_parent_dir (filename);
750       *parent = filename_to_path (parent_filename);
751       g_free (parent_filename);
752     }
753
754   return TRUE;
755 }
756
757 static GtkFilePath *
758 gtk_file_system_unix_make_path (GtkFileSystem    *file_system,
759                                const GtkFilePath *base_path,
760                                const gchar       *display_name,
761                                GError           **error)
762 {
763   const char *base_filename;
764   gchar *filename;
765   gchar *full_filename;
766   GError *tmp_error = NULL;
767   GtkFilePath *result;
768
769   base_filename = gtk_file_path_get_string (base_path);
770   g_return_val_if_fail (base_filename != NULL, NULL);
771   g_return_val_if_fail (g_path_is_absolute (base_filename), NULL);
772
773   if (strchr (display_name, G_DIR_SEPARATOR))
774     {
775       g_set_error (error,
776                    GTK_FILE_SYSTEM_ERROR,
777                    GTK_FILE_SYSTEM_ERROR_BAD_FILENAME,
778                    _("The name \"%s\" is not valid because it contains the character \"%s\". "
779                      "Please use a different name."),
780                    display_name,
781                    G_DIR_SEPARATOR_S);
782       return NULL;
783     }
784
785   filename = g_filename_from_utf8 (display_name, -1, NULL, NULL, &tmp_error);
786   if (!filename)
787     {
788       g_set_error (error,
789                    GTK_FILE_SYSTEM_ERROR,
790                    GTK_FILE_SYSTEM_ERROR_BAD_FILENAME,
791                    "%s",
792                    tmp_error->message);
793
794       g_error_free (tmp_error);
795
796       return NULL;
797     }
798
799   full_filename = g_build_filename (base_filename, filename, NULL);
800   result = filename_to_path (full_filename);
801   g_free (filename);
802   g_free (full_filename);
803
804   return result;
805 }
806
807 /* If this was a publically exported function, it should return
808  * a dup'ed result, but we make it modify-in-place for efficiency
809  * here, and because it works for us.
810  */
811 static void
812 canonicalize_filename (gchar *filename)
813 {
814   gchar *p, *q;
815   gboolean last_was_slash = FALSE;
816
817   p = filename;
818   q = filename;
819
820   while (*p)
821     {
822       if (*p == G_DIR_SEPARATOR)
823         {
824           if (!last_was_slash)
825             *q++ = G_DIR_SEPARATOR;
826
827           last_was_slash = TRUE;
828         }
829       else
830         {
831           if (last_was_slash && *p == '.')
832             {
833               if (*(p + 1) == G_DIR_SEPARATOR ||
834                   *(p + 1) == '\0')
835                 {
836                   if (*(p + 1) == '\0')
837                     break;
838
839                   p += 1;
840                 }
841               else if (*(p + 1) == '.' &&
842                        (*(p + 2) == G_DIR_SEPARATOR ||
843                         *(p + 2) == '\0'))
844                 {
845                   if (q > filename + 1)
846                     {
847                       q--;
848                       while (q > filename + 1 &&
849                              *(q - 1) != G_DIR_SEPARATOR)
850                         q--;
851                     }
852
853                   if (*(p + 2) == '\0')
854                     break;
855
856                   p += 2;
857                 }
858               else
859                 {
860                   *q++ = *p;
861                   last_was_slash = FALSE;
862                 }
863             }
864           else
865             {
866               *q++ = *p;
867               last_was_slash = FALSE;
868             }
869         }
870
871       p++;
872     }
873
874   if (q > filename + 1 && *(q - 1) == G_DIR_SEPARATOR)
875     q--;
876
877   *q = '\0';
878 }
879
880 /* Takes a user-typed filename and expands a tilde at the beginning of the string */
881 static char *
882 expand_tilde (const char *filename)
883 {
884   const char *notilde;
885   const char *slash;
886   const char *home;
887
888   if (filename[0] != '~')
889     return g_strdup (filename);
890
891   notilde = filename + 1;
892
893   slash = strchr (notilde, G_DIR_SEPARATOR);
894   if (!slash)
895     return NULL;
896
897   if (slash == notilde)
898     {
899       home = g_get_home_dir ();
900
901       if (!home)
902         return g_strdup (filename);
903     }
904   else
905     {
906       char *username;
907       struct passwd *passwd;
908
909       username = g_strndup (notilde, slash - notilde);
910       passwd = getpwnam (username);
911       g_free (username);
912
913       if (!passwd)
914         return g_strdup (filename);
915
916       home = passwd->pw_dir;
917     }
918
919   return g_build_filename (home, G_DIR_SEPARATOR_S, slash + 1, NULL);
920 }
921
922 static gboolean
923 gtk_file_system_unix_parse (GtkFileSystem     *file_system,
924                             const GtkFilePath *base_path,
925                             const gchar       *str,
926                             GtkFilePath      **folder,
927                             gchar            **file_part,
928                             GError           **error)
929 {
930   const char *base_filename;
931   gchar *filename;
932   gchar *last_slash;
933   gboolean result = FALSE;
934
935   base_filename = gtk_file_path_get_string (base_path);
936   g_return_val_if_fail (base_filename != NULL, FALSE);
937   g_return_val_if_fail (g_path_is_absolute (base_filename), FALSE);
938
939   filename = expand_tilde (str);
940   if (!filename)
941     {
942       g_set_error (error,
943                    GTK_FILE_SYSTEM_ERROR,
944                    GTK_FILE_SYSTEM_ERROR_BAD_FILENAME,
945                    "%s", ""); /* nothing for now, as we are string-frozen */
946       return FALSE;
947     }
948
949   last_slash = strrchr (filename, G_DIR_SEPARATOR);
950   if (!last_slash)
951     {
952       *folder = gtk_file_path_copy (base_path);
953       *file_part = g_strdup (filename);
954       result = TRUE;
955     }
956   else
957     {
958       gchar *folder_part;
959       gchar *folder_path;
960       GError *tmp_error = NULL;
961
962       if (last_slash == filename)
963         folder_part = g_strdup ("/");
964       else
965         folder_part = g_filename_from_utf8 (filename, last_slash - filename,
966                                             NULL, NULL, &tmp_error);
967
968       if (!folder_part)
969         {
970           g_set_error (error,
971                        GTK_FILE_SYSTEM_ERROR,
972                        GTK_FILE_SYSTEM_ERROR_BAD_FILENAME,
973                        "%s",
974                        tmp_error->message);
975           g_error_free (tmp_error);
976         }
977       else
978         {
979           if (folder_part[0] == G_DIR_SEPARATOR)
980             folder_path = folder_part;
981           else
982             {
983               folder_path = g_build_filename (base_filename, folder_part, NULL);
984               g_free (folder_part);
985             }
986
987           canonicalize_filename (folder_path);
988
989           *folder = filename_to_path (folder_path);
990           *file_part = g_strdup (last_slash + 1);
991
992           g_free (folder_path);
993
994           result = TRUE;
995         }
996     }
997
998   g_free (filename);
999
1000   return result;
1001 }
1002
1003 static gchar *
1004 gtk_file_system_unix_path_to_uri (GtkFileSystem     *file_system,
1005                                   const GtkFilePath *path)
1006 {
1007   return g_filename_to_uri (gtk_file_path_get_string (path), NULL, NULL);
1008 }
1009
1010 static gchar *
1011 gtk_file_system_unix_path_to_filename (GtkFileSystem     *file_system,
1012                                        const GtkFilePath *path)
1013 {
1014   return g_strdup (gtk_file_path_get_string (path));
1015 }
1016
1017 static GtkFilePath *
1018 gtk_file_system_unix_uri_to_path (GtkFileSystem     *file_system,
1019                                   const gchar       *uri)
1020 {
1021   GtkFilePath *path;
1022   gchar *filename = g_filename_from_uri (uri, NULL, NULL);
1023
1024   if (filename)
1025     {
1026       path = filename_to_path (filename);
1027       g_free (filename);
1028     }
1029   else
1030     path = NULL;
1031
1032   return path;
1033 }
1034
1035 static GtkFilePath *
1036 gtk_file_system_unix_filename_to_path (GtkFileSystem *file_system,
1037                                        const gchar   *filename)
1038 {
1039   return filename_to_path (filename);
1040 }
1041
1042 static const char *
1043 get_icon_for_directory (const char *path)
1044 {
1045   static char *desktop_path = NULL;
1046
1047   if (!g_get_home_dir ())
1048     return "gnome-fs-directory";
1049
1050   if (!desktop_path)
1051       desktop_path = g_build_filename (g_get_home_dir (), "Desktop", NULL);
1052
1053   if (strcmp (g_get_home_dir (), path) == 0)
1054     return "gnome-fs-home";
1055   else if (strcmp (desktop_path, path) == 0)
1056     return "gnome-fs-desktop";
1057   else
1058     return "gnome-fs-directory";
1059 }
1060
1061 static GdkPixbuf *
1062 gtk_file_system_unix_render_icon (GtkFileSystem     *file_system,
1063                                   const GtkFilePath *path,
1064                                   GtkWidget         *widget,
1065                                   gint               pixel_size,
1066                                   GError           **error)
1067 {
1068   const char *filename;
1069   IconType icon_type;
1070   const char *mime_type = NULL;
1071   char *dirname;
1072   GtkFileSystemUnix *system_unix;
1073   GtkFileFolderUnix *folder_unix;
1074
1075   system_unix = GTK_FILE_SYSTEM_UNIX (file_system);
1076   filename = gtk_file_path_get_string (path);
1077   dirname = g_path_get_dirname (filename);
1078   folder_unix = g_hash_table_lookup (system_unix->folder_hash, dirname);
1079   g_free (dirname);
1080
1081   if (folder_unix)
1082     {
1083       char *basename;
1084       struct stat_info_entry *entry;
1085
1086       if (!fill_in_stats (folder_unix, error))
1087         return NULL;
1088
1089       basename = g_path_get_basename (filename);
1090       entry = g_hash_table_lookup (folder_unix->stat_info, basename);
1091       g_free (basename);
1092       if (entry)
1093         {
1094           if (entry->icon_type == ICON_UNDECIDED)
1095             entry->icon_type = get_icon_type_from_stat (&entry->statbuf);
1096           icon_type = entry->icon_type;
1097           if (icon_type == ICON_REGULAR)
1098             {
1099               (void)fill_in_mime_type (folder_unix, NULL);
1100               mime_type = entry->mime_type;
1101             }
1102         }
1103       else
1104         icon_type = ICON_NONE;
1105     }
1106   else
1107     {
1108 #if 0
1109       g_print ("No folder open for %s\n", filename);
1110 #endif
1111
1112       icon_type = get_icon_type (filename, error);
1113       if (icon_type == ICON_REGULAR)
1114         mime_type = xdg_mime_get_mime_type_for_file (filename);
1115     }
1116
1117
1118   /* FIXME: this function should not return NULL without setting the GError; we
1119    * should perhaps provide a "never fails" generic stock icon for when all else
1120    * fails.
1121    */
1122
1123   if (icon_type == ICON_NONE)
1124     return NULL;
1125
1126   if (icon_type != ICON_REGULAR)
1127     {
1128       const char *name;
1129
1130       switch (icon_type)
1131         {
1132         case ICON_BLOCK_DEVICE:
1133           name = "gnome-fs-blockdev";
1134           break;
1135         case ICON_BROKEN_SYMBOLIC_LINK:
1136           name = "gnome-fs-symlink";
1137           break;
1138         case ICON_CHARACTER_DEVICE:
1139           name = "gnome-fs-chardev";
1140           break;
1141         case ICON_DIRECTORY:
1142           name = get_icon_for_directory (filename);
1143           break;
1144         case ICON_EXECUTABLE:
1145           name ="gnome-fs-executable";
1146           break;
1147         case ICON_FIFO:
1148           name = "gnome-fs-fifo";
1149           break;
1150         case ICON_SOCKET:
1151           name = "gnome-fs-socket";
1152           break;
1153         default:
1154           g_assert_not_reached ();
1155           return NULL;
1156         }
1157
1158       return get_cached_icon (widget, name, pixel_size);
1159     }
1160
1161   if (mime_type)
1162     {
1163       const char *separator;
1164       GString *icon_name;
1165       GdkPixbuf *pixbuf;
1166
1167       separator = strchr (mime_type, '/');
1168       if (!separator)
1169         return NULL;
1170
1171       icon_name = g_string_new ("gnome-mime-");
1172       g_string_append_len (icon_name, mime_type, separator - mime_type);
1173       g_string_append_c (icon_name, '-');
1174       g_string_append (icon_name, separator + 1);
1175       pixbuf = get_cached_icon (widget, icon_name->str, pixel_size);
1176       g_string_free (icon_name, TRUE);
1177       if (pixbuf)
1178         return pixbuf;
1179
1180       icon_name = g_string_new ("gnome-mime-");
1181       g_string_append_len (icon_name, mime_type, separator - mime_type);
1182       pixbuf = get_cached_icon (widget, icon_name->str, pixel_size);
1183       g_string_free (icon_name, TRUE);
1184       if (pixbuf)
1185         return pixbuf;
1186     }
1187
1188   return get_cached_icon (widget, "gnome-fs-regular", pixel_size);
1189 }
1190
1191 static void
1192 bookmark_list_free (GSList *list)
1193 {
1194   GSList *l;
1195
1196   for (l = list; l; l = l->next)
1197     g_free (l->data);
1198
1199   g_slist_free (list);
1200 }
1201
1202 /* Returns whether a URI is a local file:// */
1203 static gboolean
1204 is_local_uri (const char *uri)
1205 {
1206   char *filename;
1207   char *hostname;
1208   gboolean result;
1209
1210   /* This is rather crude, but hey */
1211   filename = g_filename_from_uri (uri, &hostname, NULL);
1212
1213   result = (filename && !hostname);
1214
1215   g_free (filename);
1216   g_free (hostname);
1217
1218   return result;
1219 }
1220
1221 static char *
1222 bookmark_get_filename (gboolean tmp_file)
1223 {
1224   char *filename;
1225
1226   filename = g_build_filename (g_get_home_dir (),
1227                                tmp_file ? BOOKMARKS_TMP_FILENAME : BOOKMARKS_FILENAME,
1228                                NULL);
1229   g_assert (filename != NULL);
1230   return filename;
1231 }
1232
1233 static gboolean
1234 bookmark_list_read (GSList **bookmarks, GError **error)
1235 {
1236   gchar *filename;
1237   gchar *contents;
1238   gboolean result = FALSE;
1239
1240   filename = bookmark_get_filename (FALSE);
1241   *bookmarks = NULL;
1242
1243   if (g_file_get_contents (filename, &contents, NULL, error))
1244     {
1245       gchar **lines = g_strsplit (contents, "\n", -1);
1246       int i;
1247       GHashTable *table;
1248
1249       table = g_hash_table_new (g_str_hash, g_str_equal);
1250
1251       for (i = 0; lines[i]; i++)
1252         {
1253           if (lines[i][0] && !g_hash_table_lookup (table, lines[i]))
1254             {
1255               *bookmarks = g_slist_prepend (*bookmarks, g_strdup (lines[i]));
1256               g_hash_table_insert (table, lines[i], lines[i]);
1257             }
1258         }
1259
1260       g_free (contents);
1261       g_hash_table_destroy (table);
1262       g_strfreev (lines);
1263
1264       *bookmarks = g_slist_reverse (*bookmarks);
1265       result = TRUE;
1266     }
1267
1268   g_free (filename);
1269
1270   return result;
1271 }
1272
1273 static gboolean
1274 bookmark_list_write (GSList *bookmarks, GError **error)
1275 {
1276   char *tmp_filename;
1277   char *filename;
1278   gboolean result = TRUE;
1279   FILE *file;
1280   int fd;
1281   int saved_errno;
1282
1283   /* First, write a temporary file */
1284
1285   tmp_filename = bookmark_get_filename (TRUE);
1286   filename = bookmark_get_filename (FALSE);
1287
1288   fd = g_mkstemp (tmp_filename);
1289   if (fd == -1)
1290     {
1291       saved_errno = errno;
1292       goto io_error;
1293     }
1294
1295   if ((file = fdopen (fd, "w")) != NULL)
1296     {
1297       GSList *l;
1298
1299       for (l = bookmarks; l; l = l->next)
1300         if (fputs (l->data, file) == EOF
1301             || fputs ("\n", file) == EOF)
1302           {
1303             saved_errno = errno;
1304             goto io_error;
1305           }
1306
1307       if (fclose (file) == EOF)
1308         {
1309           saved_errno = errno;
1310           goto io_error;
1311         }
1312
1313       if (rename (tmp_filename, filename) == -1)
1314         {
1315           saved_errno = errno;
1316           goto io_error;
1317         }
1318
1319       result = TRUE;
1320       goto out;
1321     }
1322   else
1323     {
1324       saved_errno = errno;
1325
1326       /* fdopen() failed, so we can't do much error checking here anyway */
1327       close (fd);
1328     }
1329
1330  io_error:
1331
1332   g_set_error (error,
1333                GTK_FILE_SYSTEM_ERROR,
1334                GTK_FILE_SYSTEM_ERROR_FAILED,
1335                _("Bookmark saving failed (%s)"),
1336                g_strerror (saved_errno));
1337   result = FALSE;
1338
1339   if (fd != -1)
1340     unlink (tmp_filename); /* again, not much error checking we can do here */
1341
1342  out:
1343
1344   g_free (filename);
1345   g_free (tmp_filename);
1346
1347   return result;
1348 }
1349
1350 static gboolean
1351 gtk_file_system_unix_insert_bookmark (GtkFileSystem     *file_system,
1352                                       const GtkFilePath *path,
1353                                       gint               position,
1354                                       GError           **error)
1355 {
1356   GSList *bookmarks;
1357   int num_bookmarks;
1358   GSList *l;
1359   char *uri;
1360   gboolean result;
1361   GError *err;
1362
1363   err = NULL;
1364   if (!bookmark_list_read (&bookmarks, &err) && err->code != G_FILE_ERROR_NOENT)
1365     {
1366       g_propagate_error (error, err);
1367       return FALSE;
1368     }
1369
1370   num_bookmarks = g_slist_length (bookmarks);
1371   g_return_val_if_fail (position >= -1 && position <= num_bookmarks, FALSE);
1372
1373   result = FALSE;
1374
1375   uri = gtk_file_system_unix_path_to_uri (file_system, path);
1376
1377   for (l = bookmarks; l; l = l->next)
1378     {
1379       const char *bookmark;
1380
1381       bookmark = l->data;
1382       if (strcmp (bookmark, uri) == 0)
1383         {
1384           g_set_error (error,
1385                        GTK_FILE_SYSTEM_ERROR,
1386                        GTK_FILE_SYSTEM_ERROR_ALREADY_EXISTS,
1387                        "%s already exists in the bookmarks list",
1388                        uri);
1389           goto out;
1390         }
1391     }
1392
1393   bookmarks = g_slist_insert (bookmarks, g_strdup (uri), position);
1394   if (bookmark_list_write (bookmarks, error))
1395     {
1396       result = TRUE;
1397       g_signal_emit_by_name (file_system, "bookmarks-changed", 0);
1398     }
1399
1400  out:
1401
1402   g_free (uri);
1403   bookmark_list_free (bookmarks);
1404
1405   return result;
1406 }
1407
1408 static gboolean
1409 gtk_file_system_unix_remove_bookmark (GtkFileSystem     *file_system,
1410                                       const GtkFilePath *path,
1411                                       GError           **error)
1412 {
1413   GSList *bookmarks;
1414   char *uri;
1415   GSList *l;
1416   gboolean result;
1417
1418   if (!bookmark_list_read (&bookmarks, error))
1419     return FALSE;
1420
1421   result = FALSE;
1422
1423   uri = gtk_file_system_path_to_uri (file_system, path);
1424
1425   for (l = bookmarks; l; l = l->next)
1426     {
1427       const char *bookmark;
1428
1429       bookmark = l->data;
1430       if (strcmp (bookmark, uri) == 0)
1431         {
1432           g_free (l->data);
1433           bookmarks = g_slist_remove_link (bookmarks, l);
1434           g_slist_free_1 (l);
1435
1436           if (bookmark_list_write (bookmarks, error))
1437             {
1438               result = TRUE;
1439               g_signal_emit_by_name (file_system, "bookmarks-changed", 0);
1440             }
1441
1442           goto out;
1443         }
1444     }
1445
1446   g_set_error (error,
1447                GTK_FILE_SYSTEM_ERROR,
1448                GTK_FILE_SYSTEM_ERROR_NONEXISTENT,
1449                "%s does not exist in the bookmarks list",
1450                uri);
1451
1452  out:
1453
1454   g_free (uri);
1455   bookmark_list_free (bookmarks);
1456
1457   return result;
1458 }
1459
1460 static GSList *
1461 gtk_file_system_unix_list_bookmarks (GtkFileSystem *file_system)
1462 {
1463   GSList *bookmarks;
1464   GSList *result;
1465   GSList *l;
1466
1467   if (!bookmark_list_read (&bookmarks, NULL))
1468     return NULL;
1469
1470   result = NULL;
1471
1472   for (l = bookmarks; l; l = l->next)
1473     {
1474       const char *name;
1475
1476       name = l->data;
1477
1478       if (is_local_uri (name))
1479         result = g_slist_prepend (result, gtk_file_system_unix_uri_to_path (file_system, name));
1480     }
1481
1482   bookmark_list_free (bookmarks);
1483
1484   result = g_slist_reverse (result);
1485   return result;
1486 }
1487
1488 /*
1489  * GtkFileFolderUnix
1490  */
1491 static GType
1492 gtk_file_folder_unix_get_type (void)
1493 {
1494   static GType file_folder_unix_type = 0;
1495
1496   if (!file_folder_unix_type)
1497     {
1498       static const GTypeInfo file_folder_unix_info =
1499       {
1500         sizeof (GtkFileFolderUnixClass),
1501         NULL,           /* base_init */
1502         NULL,           /* base_finalize */
1503         (GClassInitFunc) gtk_file_folder_unix_class_init,
1504         NULL,           /* class_finalize */
1505         NULL,           /* class_data */
1506         sizeof (GtkFileFolderUnix),
1507         0,              /* n_preallocs */
1508         (GInstanceInitFunc) gtk_file_folder_unix_init,
1509       };
1510
1511       static const GInterfaceInfo file_folder_info =
1512       {
1513         (GInterfaceInitFunc) gtk_file_folder_unix_iface_init, /* interface_init */
1514         NULL,                                                 /* interface_finalize */
1515         NULL                                                  /* interface_data */
1516       };
1517
1518       file_folder_unix_type = g_type_register_static (G_TYPE_OBJECT,
1519                                                       "GtkFileFolderUnix",
1520                                                       &file_folder_unix_info, 0);
1521       g_type_add_interface_static (file_folder_unix_type,
1522                                    GTK_TYPE_FILE_FOLDER,
1523                                    &file_folder_info);
1524     }
1525
1526   return file_folder_unix_type;
1527 }
1528
1529 static void
1530 gtk_file_folder_unix_class_init (GtkFileFolderUnixClass *class)
1531 {
1532   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
1533
1534   folder_parent_class = g_type_class_peek_parent (class);
1535
1536   gobject_class->finalize = gtk_file_folder_unix_finalize;
1537 }
1538
1539 static void
1540 gtk_file_folder_unix_iface_init (GtkFileFolderIface *iface)
1541 {
1542   iface->get_info = gtk_file_folder_unix_get_info;
1543   iface->list_children = gtk_file_folder_unix_list_children;
1544   iface->is_finished_loading = gtk_file_folder_unix_is_finished_loading;
1545 }
1546
1547 static void
1548 gtk_file_folder_unix_init (GtkFileFolderUnix *impl)
1549 {
1550 }
1551
1552 static void
1553 gtk_file_folder_unix_finalize (GObject *object)
1554 {
1555   GtkFileFolderUnix *folder_unix = GTK_FILE_FOLDER_UNIX (object);
1556
1557   g_hash_table_remove (folder_unix->system_unix->folder_hash, folder_unix->filename);
1558
1559   if (folder_unix->stat_info)
1560     {
1561 #if 0
1562       g_print ("Releasing information for directory %s\n", folder_unix->filename);
1563 #endif
1564       g_hash_table_destroy (folder_unix->stat_info);
1565     }
1566
1567   g_free (folder_unix->filename);
1568
1569   folder_parent_class->finalize (object);
1570 }
1571
1572 static GtkFileInfo *
1573 gtk_file_folder_unix_get_info (GtkFileFolder      *folder,
1574                                const GtkFilePath  *path,
1575                                GError            **error)
1576 {
1577   GtkFileFolderUnix *folder_unix = GTK_FILE_FOLDER_UNIX (folder);
1578   GtkFileInfo *info;
1579   gchar *dirname, *basename;
1580   const char *filename;
1581   struct stat_info_entry *entry;
1582   gboolean file_must_exist;
1583   GtkFileInfoType types;
1584
1585   if (!path)
1586     {
1587       struct stat buf;
1588
1589       g_return_val_if_fail (filename_is_root (folder_unix->filename), NULL);
1590
1591       if (stat (folder_unix->filename, &buf) != 0)
1592         return NULL;
1593
1594       info = gtk_file_info_new ();
1595       gtk_file_info_set_display_name (info, "/");
1596       gtk_file_info_set_is_folder (info, TRUE);
1597       gtk_file_info_set_is_hidden (info, FALSE);
1598       gtk_file_info_set_mime_type (info, "x-directory/normal");
1599       gtk_file_info_set_modification_time (info, buf.st_mtime);
1600       gtk_file_info_set_size (info, buf.st_size);
1601
1602       return info;
1603     }
1604
1605   filename = gtk_file_path_get_string (path);
1606   g_return_val_if_fail (filename != NULL, NULL);
1607   g_return_val_if_fail (g_path_is_absolute (filename), NULL);
1608
1609   dirname = get_parent_dir (filename);
1610   g_return_val_if_fail (strcmp (dirname, folder_unix->filename) == 0, NULL);
1611   g_free (dirname);
1612
1613   basename = g_path_get_basename (filename);
1614   types = folder_unix->types;
1615   file_must_exist = (types & ~GTK_FILE_INFO_DISPLAY_NAME) != 0;
1616   entry = file_must_exist
1617     ? g_hash_table_lookup (folder_unix->stat_info, basename)
1618     : NULL;
1619   /* basename freed later.  */
1620
1621   if (!file_must_exist || entry)
1622     {
1623       info = gtk_file_info_new ();
1624     }
1625   else
1626     {
1627       gchar *filename_utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
1628       g_set_error (error,
1629                    GTK_FILE_SYSTEM_ERROR,
1630                    GTK_FILE_SYSTEM_ERROR_NONEXISTENT,
1631                    _("error getting information for '%s'"),
1632                    filename_utf8 ? filename_utf8 : "???");
1633       g_free (filename_utf8);
1634       info = NULL;
1635       types = 0;
1636     }
1637
1638   if (types & GTK_FILE_INFO_DISPLAY_NAME)
1639     {
1640       gchar *display_name = g_filename_to_utf8 (basename, -1, NULL, NULL, NULL);
1641       if (!display_name)
1642         display_name = g_strescape (basename, NULL);
1643
1644       gtk_file_info_set_display_name (info, display_name);
1645
1646       g_free (display_name);
1647     }
1648
1649   if (types & GTK_FILE_INFO_IS_HIDDEN)
1650     gtk_file_info_set_is_hidden (info, basename[0] == '.');
1651
1652   if (types & GTK_FILE_INFO_IS_FOLDER)
1653     gtk_file_info_set_is_folder (info, S_ISDIR (entry->statbuf.st_mode));
1654
1655   if (types & GTK_FILE_INFO_MIME_TYPE)
1656     gtk_file_info_set_mime_type (info, entry->mime_type);
1657
1658   if (types & GTK_FILE_INFO_MODIFICATION_TIME)
1659     gtk_file_info_set_modification_time (info, entry->statbuf.st_mtime);
1660
1661   if (types & GTK_FILE_INFO_SIZE)
1662     gtk_file_info_set_size (info, (gint64)entry->statbuf.st_size);
1663
1664   g_free (basename);
1665
1666   return info;
1667 }
1668
1669
1670 static void
1671 cb_list_children (gpointer key, gpointer value, gpointer user_data)
1672 {
1673   GSList **children = user_data;
1674   *children = g_slist_prepend (*children, key);
1675 }
1676
1677 static gboolean
1678 gtk_file_folder_unix_list_children (GtkFileFolder  *folder,
1679                                     GSList        **children,
1680                                     GError        **error)
1681 {
1682   GtkFileFolderUnix *folder_unix = GTK_FILE_FOLDER_UNIX (folder);
1683   GSList *l;
1684
1685   if (!fill_in_names (folder_unix, error))
1686     return FALSE;
1687
1688   *children = NULL;
1689
1690   /* Get the list of basenames.  */
1691   g_hash_table_foreach (folder_unix->stat_info, cb_list_children, children);
1692
1693   /* Turn basenames into GFilePaths.  */
1694   for (l = *children; l; l = l->next)
1695     {
1696       const char *basename = l->data;
1697       char *fullname = g_build_filename (folder_unix->filename, basename, NULL);
1698       l->data = filename_to_path (fullname);
1699       g_free (fullname);
1700     }
1701   return TRUE;
1702 }
1703
1704 static gboolean
1705 gtk_file_folder_unix_is_finished_loading (GtkFileFolder *folder)
1706 {
1707   /* Since we don't do asynchronous loads, we are always finished loading */
1708   return TRUE;
1709 }
1710
1711 static void
1712 free_stat_info_entry (struct stat_info_entry *entry)
1713 {
1714   g_free (entry->mime_type);
1715   g_free (entry);
1716 }
1717
1718 static gboolean
1719 fill_in_names (GtkFileFolderUnix *folder_unix, GError **error)
1720 {
1721   GDir *dir;
1722
1723   if (folder_unix->stat_info)
1724     return TRUE;
1725
1726 #if 0
1727   g_print ("Reading directory %s\n", folder_unix->filename);
1728 #endif
1729
1730   folder_unix->stat_info = g_hash_table_new_full (g_str_hash, g_str_equal,
1731                                                   (GDestroyNotify)g_free,
1732                                                   (GDestroyNotify)free_stat_info_entry);
1733   dir = g_dir_open (folder_unix->filename, 0, error);
1734   if (!dir)
1735     return FALSE;
1736
1737   while (TRUE)
1738     {
1739       const gchar *basename = g_dir_read_name (dir);
1740       if (!basename)
1741         break;
1742
1743       g_hash_table_insert (folder_unix->stat_info,
1744                            g_strdup (basename),
1745                            g_new0 (struct stat_info_entry, 1));
1746     }
1747
1748   g_dir_close (dir);
1749
1750   folder_unix->asof = time (NULL);
1751   return TRUE;
1752 }
1753
1754 static gboolean
1755 cb_fill_in_stats (gpointer key, gpointer value, gpointer user_data)
1756 {
1757   const char *basename = key;
1758   struct stat_info_entry *entry = value;
1759   GtkFileFolderUnix *folder_unix = user_data;
1760   char *fullname = g_build_filename (folder_unix->filename, basename, NULL);
1761   gboolean result;
1762
1763   if (stat (fullname, &entry->statbuf) == -1 &&
1764       (errno != ENOENT || lstat (fullname, &entry->statbuf) == -1))
1765     result = TRUE;  /* Couldn't stat -- remove from hash.  */
1766   else
1767     result = FALSE;
1768
1769   g_free (fullname);
1770   return result;
1771 }
1772
1773
1774 static gboolean
1775 fill_in_stats (GtkFileFolderUnix *folder_unix, GError **error)
1776 {
1777   if (folder_unix->have_stat)
1778     return TRUE;
1779
1780   if (!fill_in_names (folder_unix, error))
1781     return FALSE;
1782
1783 #if 0
1784   g_print ("Stating directory %s\n", folder_unix->filename);
1785 #endif
1786   g_hash_table_foreach_remove (folder_unix->stat_info,
1787                                cb_fill_in_stats,
1788                                folder_unix);
1789
1790   folder_unix->have_stat = TRUE;
1791   return TRUE;
1792 }
1793
1794
1795 static gboolean
1796 cb_fill_in_mime_type (gpointer key, gpointer value, gpointer user_data)
1797 {
1798   const char *basename = key;
1799   struct stat_info_entry *entry = value;
1800   GtkFileFolderUnix *folder_unix = user_data;
1801   char *fullname = g_build_filename (folder_unix->filename, basename, NULL);
1802
1803   /* FIXME: Should not need to re-stat.  */
1804   const char *mime_type = xdg_mime_get_mime_type_for_file (fullname);
1805   entry->mime_type = g_strdup (mime_type);
1806
1807   g_free (fullname);
1808   /* FIXME: free on NULL?  */
1809   return FALSE;
1810 }
1811
1812 static gboolean
1813 fill_in_mime_type (GtkFileFolderUnix *folder_unix, GError **error)
1814 {
1815   if (folder_unix->have_mime_type)
1816     return TRUE;
1817
1818 #if 0
1819   g_print ("Getting mime types for directory %s\n", folder_unix->filename);
1820 #endif
1821   g_hash_table_foreach_remove (folder_unix->stat_info,
1822                                cb_fill_in_mime_type,
1823                                folder_unix);
1824
1825   folder_unix->have_mime_type = TRUE;
1826   return TRUE;
1827 }
1828
1829 static GtkFilePath *
1830 filename_to_path (const char *filename)
1831 {
1832   char *tmp;
1833
1834   tmp = remove_trailing_slash (filename);
1835   return gtk_file_path_new_steal (tmp);
1836 }
1837
1838 static gboolean
1839 filename_is_root (const char *filename)
1840 {
1841   const gchar *after_root;
1842
1843   after_root = g_path_skip_root (filename);
1844
1845   return (after_root != NULL && *after_root == '\0');
1846 }