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