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