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