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