X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkfilesystemunix.c;h=b426afe764573106e8443f006907f424e93f5a91;hb=f35439bfacf90900e2c24f7ae3da173183c79d34;hp=6e12713999f922211c55a0d8a0248b4281150d6e;hpb=57cbeff08e786b0df6262816d050a7df664d0391;p=~andy%2Fgtk diff --git a/gtk/gtkfilesystemunix.c b/gtk/gtkfilesystemunix.c index 6e1271399..b426afe76 100644 --- a/gtk/gtkfilesystemunix.c +++ b/gtk/gtkfilesystemunix.c @@ -18,12 +18,19 @@ * Boston, MA 02111-1307, USA. */ +/* #define this if you want the program to crash when a file system gets + * finalized while async handles are still outstanding. + */ +#undef HANDLE_ME_HARDER + #include #include "gtkfilesystem.h" #include "gtkfilesystemunix.h" #include "gtkicontheme.h" #include "gtkintl.h" +#include "gtkstock.h" +#include "gtkalias.h" #define XDG_PREFIX _gtk_xdg #include "xdgmime/xdgmime.h" @@ -32,11 +39,18 @@ #include #include #include +#include +#ifdef HAVE_UNISTD_H #include +#endif #include +#include #define BOOKMARKS_FILENAME ".gtk-bookmarks" -#define BOOKMARKS_TMP_FILENAME ".gtk-bookmarks-XXXXXX" + +#define HIDDEN_FILENAME ".hidden" + +#define FOLDER_CACHE_LIFETIME 2 /* seconds */ typedef struct _GtkFileSystemUnixClass GtkFileSystemUnixClass; @@ -54,13 +68,26 @@ struct _GtkFileSystemUnix GObject parent_instance; GHashTable *folder_hash; + + /* For /afs and /net */ + struct stat afs_statbuf; + struct stat net_statbuf; + + GHashTable *handles; + + guint execute_callbacks_idle_id; + GSList *callbacks; + + guint have_afs : 1; + guint have_net : 1; }; /* Icon type, supplemented by MIME type */ typedef enum { - ICON_NONE, - ICON_REGULAR, /* Use mime type for icon */ + ICON_UNDECIDED, /* Only used while we have not yet computed the icon in a struct stat_info_entry */ + ICON_NONE, /* "Could not compute the icon type" */ + ICON_REGULAR, /* Use mime type for icon */ ICON_BLOCK_DEVICE, ICON_BROKEN_SYMBOLIC_LINK, ICON_CHARACTER_DEVICE, @@ -71,7 +98,7 @@ typedef enum { } IconType; -#define GTK_TYPE_FILE_FOLDER_UNIX (gtk_file_folder_unix_get_type ()) +#define GTK_TYPE_FILE_FOLDER_UNIX (_gtk_file_folder_unix_get_type ()) #define GTK_FILE_FOLDER_UNIX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_FILE_FOLDER_UNIX, GtkFileFolderUnix)) #define GTK_IS_FILE_FOLDER_UNIX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_FILE_FOLDER_UNIX)) #define GTK_FILE_FOLDER_UNIX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_FOLDER_UNIX, GtkFileFolderUnixClass)) @@ -93,27 +120,51 @@ struct _GtkFileFolderUnix GtkFileSystemUnix *system_unix; GtkFileInfoType types; gchar *filename; + GHashTable *stat_info; + guint load_folder_id; + guint have_stat : 1; + guint have_mime_type : 1; + guint is_network_dir : 1; + guint have_hidden : 1; + guint is_finished_loading : 1; + time_t asof; +}; + +struct stat_info_entry { + struct stat statbuf; + char *mime_type; + IconType icon_type; + gboolean hidden; }; -static GObjectClass *system_parent_class; -static GObjectClass *folder_parent_class; +static const GtkFileInfoType STAT_NEEDED_MASK = (GTK_FILE_INFO_IS_FOLDER | + GTK_FILE_INFO_MODIFICATION_TIME | + GTK_FILE_INFO_SIZE | + GTK_FILE_INFO_ICON); -static void gtk_file_system_unix_class_init (GtkFileSystemUnixClass *class); static void gtk_file_system_unix_iface_init (GtkFileSystemIface *iface); -static void gtk_file_system_unix_init (GtkFileSystemUnix *impl); +static void gtk_file_system_unix_dispose (GObject *object); static void gtk_file_system_unix_finalize (GObject *object); static GSList * gtk_file_system_unix_list_volumes (GtkFileSystem *file_system); static GtkFileSystemVolume *gtk_file_system_unix_get_volume_for_path (GtkFileSystem *file_system, const GtkFilePath *path); -static GtkFileFolder *gtk_file_system_unix_get_folder (GtkFileSystem *file_system, - const GtkFilePath *path, - GtkFileInfoType types, - GError **error); -static gboolean gtk_file_system_unix_create_folder (GtkFileSystem *file_system, - const GtkFilePath *path, - GError **error); +static GtkFileSystemHandle *gtk_file_system_unix_get_folder (GtkFileSystem *file_system, + const GtkFilePath *path, + GtkFileInfoType types, + GtkFileSystemGetFolderCallback callback, + gpointer data); +static GtkFileSystemHandle *gtk_file_system_unix_get_info (GtkFileSystem *file_system, + const GtkFilePath *path, + GtkFileInfoType types, + GtkFileSystemGetInfoCallback callback, + gpointer data); +static GtkFileSystemHandle *gtk_file_system_unix_create_folder (GtkFileSystem *file_system, + const GtkFilePath *path, + GtkFileSystemCreateFolderCallback callback, + gpointer data); +static void gtk_file_system_unix_cancel_operation (GtkFileSystemHandle *handle); static void gtk_file_system_unix_volume_free (GtkFileSystem *file_system, GtkFileSystemVolume *volume); @@ -121,15 +172,14 @@ static GtkFilePath *gtk_file_system_unix_volume_get_base_path (GtkFileSystem GtkFileSystemVolume *volume); static gboolean gtk_file_system_unix_volume_get_is_mounted (GtkFileSystem *file_system, GtkFileSystemVolume *volume); -static gboolean gtk_file_system_unix_volume_mount (GtkFileSystem *file_system, +static GtkFileSystemHandle *gtk_file_system_unix_volume_mount (GtkFileSystem *file_system, GtkFileSystemVolume *volume, - GError **error); + GtkFileSystemVolumeMountCallback callback, + gpointer data); static gchar * gtk_file_system_unix_volume_get_display_name (GtkFileSystem *file_system, GtkFileSystemVolume *volume); -static GdkPixbuf * gtk_file_system_unix_volume_render_icon (GtkFileSystem *file_system, +static gchar * gtk_file_system_unix_volume_get_icon_name (GtkFileSystem *file_system, GtkFileSystemVolume *volume, - GtkWidget *widget, - gint pixel_size, GError **error); static gboolean gtk_file_system_unix_get_parent (GtkFileSystem *file_system, @@ -156,11 +206,6 @@ static GtkFilePath *gtk_file_system_unix_uri_to_path (GtkFileSystem *fi static GtkFilePath *gtk_file_system_unix_filename_to_path (GtkFileSystem *file_system, const gchar *filename); -static GdkPixbuf *gtk_file_system_unix_render_icon (GtkFileSystem *file_system, - const GtkFilePath *path, - GtkWidget *widget, - gint pixel_size, - GError **error); static gboolean gtk_file_system_unix_insert_bookmark (GtkFileSystem *file_system, const GtkFilePath *path, @@ -169,12 +214,14 @@ static gboolean gtk_file_system_unix_insert_bookmark (GtkFileSystem *file_sy static gboolean gtk_file_system_unix_remove_bookmark (GtkFileSystem *file_system, const GtkFilePath *path, GError **error); -static GSList * gtk_file_system_unix_list_bookmarks (GtkFileSystem *file_system); +static GSList * gtk_file_system_unix_list_bookmarks (GtkFileSystem *file_system); +static gchar * gtk_file_system_unix_get_bookmark_label (GtkFileSystem *file_system, + const GtkFilePath *path); +static void gtk_file_system_unix_set_bookmark_label (GtkFileSystem *file_system, + const GtkFilePath *path, + const gchar *label); -static GType gtk_file_folder_unix_get_type (void); -static void gtk_file_folder_unix_class_init (GtkFileFolderUnixClass *class); static void gtk_file_folder_unix_iface_init (GtkFileFolderIface *iface); -static void gtk_file_folder_unix_init (GtkFileFolderUnix *impl); static void gtk_file_folder_unix_finalize (GObject *object); static GtkFileInfo *gtk_file_folder_unix_get_info (GtkFileFolder *folder, @@ -184,53 +231,60 @@ static gboolean gtk_file_folder_unix_list_children (GtkFileFolder *folder, GSList **children, GError **error); +static gboolean gtk_file_folder_unix_is_finished_loading (GtkFileFolder *folder); + static GtkFilePath *filename_to_path (const gchar *filename); static gboolean filename_is_root (const char *filename); -static GtkFileInfo *filename_get_info (const gchar *filename, - GtkFileInfoType types, - GError **error); + +static gboolean get_is_hidden_for_file (const char *filename, + const char *basename); +static gboolean file_is_hidden (GtkFileFolderUnix *folder_unix, + const char *basename); + +static GtkFileInfo *file_info_for_root_with_error (const char *root_name, + GError **error); +static gboolean stat_with_error (const char *filename, + struct stat *statbuf, + GError **error); +static GtkFileInfo *create_file_info (GtkFileFolderUnix *folder_unix, + const char *filename, + const char *basename, + GtkFileInfoType types, + struct stat *statbuf, + const char *mime_type); + +static gboolean execute_callbacks (gpointer data); + +static gboolean fill_in_names (GtkFileFolderUnix *folder_unix, + GError **error); +static void fill_in_stats (GtkFileFolderUnix *folder_unix); +static void fill_in_mime_type (GtkFileFolderUnix *folder_unix); +static void fill_in_hidden (GtkFileFolderUnix *folder_unix); + +static gboolean cb_fill_in_stats (gpointer key, + gpointer value, + gpointer user_data); +static gboolean cb_fill_in_mime_type (gpointer key, + gpointer value, + gpointer user_data); + +static char * get_parent_dir (const char *filename); /* * GtkFileSystemUnix */ -GType -gtk_file_system_unix_get_type (void) -{ - static GType file_system_unix_type = 0; +G_DEFINE_TYPE_WITH_CODE (GtkFileSystemUnix, gtk_file_system_unix, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_SYSTEM, + gtk_file_system_unix_iface_init)) - if (!file_system_unix_type) - { - static const GTypeInfo file_system_unix_info = - { - sizeof (GtkFileSystemUnixClass), - NULL, /* base_init */ - NULL, /* base_finalize */ - (GClassInitFunc) gtk_file_system_unix_class_init, - NULL, /* class_finalize */ - NULL, /* class_data */ - sizeof (GtkFileSystemUnix), - 0, /* n_preallocs */ - (GInstanceInitFunc) gtk_file_system_unix_init, - }; - - static const GInterfaceInfo file_system_info = - { - (GInterfaceInitFunc) gtk_file_system_unix_iface_init, /* interface_init */ - NULL, /* interface_finalize */ - NULL /* interface_data */ - }; - - file_system_unix_type = g_type_register_static (G_TYPE_OBJECT, - "GtkFileSystemUnix", - &file_system_unix_info, 0); - g_type_add_interface_static (file_system_unix_type, - GTK_TYPE_FILE_SYSTEM, - &file_system_info); - } +/* + * GtkFileFolderUnix + */ +G_DEFINE_TYPE_WITH_CODE (GtkFileFolderUnix, _gtk_file_folder_unix, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_FOLDER, + gtk_file_folder_unix_iface_init)) - return file_system_unix_type; -} /** * gtk_file_system_unix_new: @@ -252,24 +306,25 @@ gtk_file_system_unix_class_init (GtkFileSystemUnixClass *class) { GObjectClass *gobject_class = G_OBJECT_CLASS (class); - system_parent_class = g_type_class_peek_parent (class); - + gobject_class->dispose = gtk_file_system_unix_dispose; gobject_class->finalize = gtk_file_system_unix_finalize; } static void -gtk_file_system_unix_iface_init (GtkFileSystemIface *iface) +gtk_file_system_unix_iface_init (GtkFileSystemIface *iface) { iface->list_volumes = gtk_file_system_unix_list_volumes; iface->get_volume_for_path = gtk_file_system_unix_get_volume_for_path; iface->get_folder = gtk_file_system_unix_get_folder; + iface->get_info = gtk_file_system_unix_get_info; iface->create_folder = gtk_file_system_unix_create_folder; + iface->cancel_operation = gtk_file_system_unix_cancel_operation; iface->volume_free = gtk_file_system_unix_volume_free; iface->volume_get_base_path = gtk_file_system_unix_volume_get_base_path; iface->volume_get_is_mounted = gtk_file_system_unix_volume_get_is_mounted; iface->volume_mount = gtk_file_system_unix_volume_mount; iface->volume_get_display_name = gtk_file_system_unix_volume_get_display_name; - iface->volume_render_icon = gtk_file_system_unix_volume_render_icon; + iface->volume_get_icon_name = gtk_file_system_unix_volume_get_icon_name; iface->get_parent = gtk_file_system_unix_get_parent; iface->make_path = gtk_file_system_unix_make_path; iface->parse = gtk_file_system_unix_parse; @@ -277,16 +332,121 @@ gtk_file_system_unix_iface_init (GtkFileSystemIface *iface) iface->path_to_filename = gtk_file_system_unix_path_to_filename; iface->uri_to_path = gtk_file_system_unix_uri_to_path; iface->filename_to_path = gtk_file_system_unix_filename_to_path; - iface->render_icon = gtk_file_system_unix_render_icon; iface->insert_bookmark = gtk_file_system_unix_insert_bookmark; iface->remove_bookmark = gtk_file_system_unix_remove_bookmark; iface->list_bookmarks = gtk_file_system_unix_list_bookmarks; + iface->get_bookmark_label = gtk_file_system_unix_get_bookmark_label; + iface->set_bookmark_label = gtk_file_system_unix_set_bookmark_label; } static void gtk_file_system_unix_init (GtkFileSystemUnix *system_unix) { system_unix->folder_hash = g_hash_table_new (g_str_hash, g_str_equal); + + if (stat ("/afs", &system_unix->afs_statbuf) == 0) + system_unix->have_afs = TRUE; + else + system_unix->have_afs = FALSE; + + if (stat ("/net", &system_unix->net_statbuf) == 0) + system_unix->have_net = TRUE; + else + system_unix->have_net = FALSE; + + system_unix->handles = g_hash_table_new (g_direct_hash, g_direct_equal); + + system_unix->execute_callbacks_idle_id = 0; + system_unix->callbacks = NULL; +} + +static void +check_handle_fn (gpointer key, gpointer value, gpointer data) +{ + GtkFileSystemHandle *handle; + int *num_live_handles; + + handle = key; + num_live_handles = data; + + (*num_live_handles)++; + + g_warning ("file_system_unix=%p still has handle=%p at finalization which is %s!", + handle->file_system, + handle, + handle->cancelled ? "CANCELLED" : "NOT CANCELLED"); +} + +static void +check_handles_at_finalization (GtkFileSystemUnix *system_unix) +{ + int num_live_handles; + + num_live_handles = 0; + + g_hash_table_foreach (system_unix->handles, check_handle_fn, &num_live_handles); +#ifdef HANDLE_ME_HARDER + g_assert (num_live_handles == 0); +#endif + + g_hash_table_destroy (system_unix->handles); + system_unix->handles = NULL; +} + +#define GTK_TYPE_FILE_SYSTEM_HANDLE_UNIX (_gtk_file_system_handle_unix_get_type ()) + +typedef struct _GtkFileSystemHandle GtkFileSystemHandleUnix; +typedef struct _GtkFileSystemHandleClass GtkFileSystemHandleUnixClass; + +G_DEFINE_TYPE (GtkFileSystemHandleUnix, _gtk_file_system_handle_unix, GTK_TYPE_FILE_SYSTEM_HANDLE) + +static void +_gtk_file_system_handle_unix_init (GtkFileSystemHandleUnix *handle) +{ +} + +static void +_gtk_file_system_handle_unix_finalize (GObject *object) +{ + GtkFileSystemHandleUnix *handle; + GtkFileSystemUnix *system_unix; + + handle = (GtkFileSystemHandleUnix *)object; + + system_unix = GTK_FILE_SYSTEM_UNIX (GTK_FILE_SYSTEM_HANDLE (handle)->file_system); + + g_assert (g_hash_table_lookup (system_unix->handles, handle) != NULL); + g_hash_table_remove (system_unix->handles, handle); + + if (G_OBJECT_CLASS (_gtk_file_system_handle_unix_parent_class)->finalize) + G_OBJECT_CLASS (_gtk_file_system_handle_unix_parent_class)->finalize (object); +} + +static void +_gtk_file_system_handle_unix_class_init (GtkFileSystemHandleUnixClass *class) +{ + GObjectClass *gobject_class = (GObjectClass *) class; + + gobject_class->finalize = _gtk_file_system_handle_unix_finalize; +} + +static void +gtk_file_system_unix_dispose (GObject *object) +{ + GtkFileSystemUnix *system_unix; + + system_unix = GTK_FILE_SYSTEM_UNIX (object); + + if (system_unix->execute_callbacks_idle_id) + { + g_source_remove (system_unix->execute_callbacks_idle_id); + system_unix->execute_callbacks_idle_id = 0; + + /* call pending callbacks */ + execute_callbacks (system_unix); + } + + G_OBJECT_CLASS (gtk_file_system_unix_parent_class)->dispose (object); } static void @@ -296,10 +456,12 @@ gtk_file_system_unix_finalize (GObject *object) system_unix = GTK_FILE_SYSTEM_UNIX (object); + check_handles_at_finalization (system_unix); + /* FIXME: assert that the hash is empty? */ g_hash_table_destroy (system_unix->folder_hash); - system_parent_class->finalize (object); + G_OBJECT_CLASS (gtk_file_system_unix_parent_class)->finalize (object); } /* Returns our single root volume */ @@ -322,286 +484,812 @@ gtk_file_system_unix_get_volume_for_path (GtkFileSystem *file_system, return get_root_volume (); } -static GtkFileFolder * -gtk_file_system_unix_get_folder (GtkFileSystem *file_system, - const GtkFilePath *path, - GtkFileInfoType types, - GError **error) +static char * +remove_trailing_slash (const char *filename) { - GtkFileSystemUnix *system_unix; - GtkFileFolderUnix *folder_unix; - const char *filename; + int len; - system_unix = GTK_FILE_SYSTEM_UNIX (file_system); + len = strlen (filename); - filename = gtk_file_path_get_string (path); - g_return_val_if_fail (filename != NULL, NULL); - g_return_val_if_fail (g_path_is_absolute (filename), NULL); + if (len > 1 && filename[len - 1] == '/') + return g_strndup (filename, len - 1); + else + return g_memdup (filename, len + 1); +} - folder_unix = g_hash_table_lookup (system_unix->folder_hash, filename); +/* Delay callback dispatching + */ - if (folder_unix) - { - folder_unix->types |= types; - return g_object_ref (folder_unix); - } - else - { - folder_unix = g_object_new (GTK_TYPE_FILE_FOLDER_UNIX, NULL); - folder_unix->system_unix = system_unix; - folder_unix->filename = g_strdup (filename); - folder_unix->types = types; +enum callback_types +{ + CALLBACK_GET_INFO, + CALLBACK_GET_FOLDER, + CALLBACK_CREATE_FOLDER, + CALLBACK_VOLUME_MOUNT +}; - g_hash_table_insert (system_unix->folder_hash, folder_unix->filename, folder_unix); +static void queue_callback (GtkFileSystemUnix *system_unix, enum callback_types type, gpointer data); - return GTK_FILE_FOLDER (folder_unix); - } +struct get_info_callback +{ + GtkFileSystemGetInfoCallback callback; + GtkFileSystemHandle *handle; + GtkFileInfo *file_info; + GError *error; + gpointer data; +}; + +static inline void +dispatch_get_info_callback (struct get_info_callback *info) +{ + (* info->callback) (info->handle, info->file_info, info->error, info->data); + + if (info->file_info) + gtk_file_info_free (info->file_info); + + if (info->error) + g_error_free (info->error); + + g_object_unref (info->handle); + + g_free (info); } -static gboolean -gtk_file_system_unix_create_folder (GtkFileSystem *file_system, - const GtkFilePath *path, - GError **error) +static inline void +queue_get_info_callback (GtkFileSystemGetInfoCallback callback, + GtkFileSystemHandle *handle, + GtkFileInfo *file_info, + GError *error, + gpointer data) { - GtkFileSystemUnix *system_unix; - const char *filename; - gboolean result; - char *parent; + struct get_info_callback *info; - system_unix = GTK_FILE_SYSTEM_UNIX (file_system); + info = g_new (struct get_info_callback, 1); + info->callback = callback; + info->handle = handle; + info->file_info = file_info; + info->error = error; + info->data = data; - filename = gtk_file_path_get_string (path); - g_return_val_if_fail (filename != NULL, FALSE); - g_return_val_if_fail (g_path_is_absolute (filename), FALSE); + queue_callback (GTK_FILE_SYSTEM_UNIX (handle->file_system), CALLBACK_GET_INFO, info); +} - result = mkdir (filename, 0777) == 0; - if (!result) - { - int save_errno = errno; - gchar *filename_utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL); - g_set_error (error, - GTK_FILE_SYSTEM_ERROR, - GTK_FILE_SYSTEM_ERROR_NONEXISTENT, - _("error creating directory '%s': %s"), - filename_utf8 ? filename_utf8 : "???", - g_strerror (save_errno)); - g_free (filename_utf8); - return FALSE; - } +struct get_folder_callback +{ + GtkFileSystemGetFolderCallback callback; + GtkFileSystemHandle *handle; + GtkFileFolder *folder; + GError *error; + gpointer data; +}; - if (filename_is_root (filename)) - return TRUE; /* hmmm, but with no notification */ +static inline void +dispatch_get_folder_callback (struct get_folder_callback *info) +{ + (* info->callback) (info->handle, info->folder, info->error, info->data); - parent = g_path_get_dirname (filename); - if (parent) - { - GtkFileFolderUnix *folder_unix; + if (info->error) + g_error_free (info->error); - folder_unix = g_hash_table_lookup (system_unix->folder_hash, parent); - if (folder_unix) - { - GSList *paths; + g_object_unref (info->handle); - paths = g_slist_append (NULL, (GtkFilePath *) path); - g_signal_emit_by_name (folder_unix, "files-added", paths); - g_slist_free (paths); - } - } + g_free (info); +} - return TRUE; +static inline void +queue_get_folder_callback (GtkFileSystemGetFolderCallback callback, + GtkFileSystemHandle *handle, + GtkFileFolder *folder, + GError *error, + gpointer data) +{ + struct get_folder_callback *info; + + info = g_new (struct get_folder_callback, 1); + info->callback = callback; + info->handle = handle; + info->folder = folder; + info->error = error; + info->data = data; + + queue_callback (GTK_FILE_SYSTEM_UNIX (handle->file_system), CALLBACK_GET_FOLDER, info); } -static void -gtk_file_system_unix_volume_free (GtkFileSystem *file_system, - GtkFileSystemVolume *volume) + +struct create_folder_callback { + GtkFileSystemCreateFolderCallback callback; + GtkFileSystemHandle *handle; GtkFilePath *path; + GError *error; + gpointer data; +}; - path = (GtkFilePath *) volume; - gtk_file_path_free (path); +static inline void +dispatch_create_folder_callback (struct create_folder_callback *info) +{ + (* info->callback) (info->handle, info->path, info->error, info->data); + + if (info->error) + g_error_free (info->error); + + if (info->path) + gtk_file_path_free (info->path); + + g_object_unref (info->handle); + + g_free (info); } -static GtkFilePath * -gtk_file_system_unix_volume_get_base_path (GtkFileSystem *file_system, - GtkFileSystemVolume *volume) +static inline void +queue_create_folder_callback (GtkFileSystemCreateFolderCallback callback, + GtkFileSystemHandle *handle, + const GtkFilePath *path, + GError *error, + gpointer data) { - return gtk_file_path_new_dup ("/"); + struct create_folder_callback *info; + + info = g_new (struct create_folder_callback, 1); + info->callback = callback; + info->handle = handle; + info->path = gtk_file_path_copy (path); + info->error = error; + info->data = data; + + queue_callback (GTK_FILE_SYSTEM_UNIX (handle->file_system), CALLBACK_CREATE_FOLDER, info); } -static gboolean -gtk_file_system_unix_volume_get_is_mounted (GtkFileSystem *file_system, - GtkFileSystemVolume *volume) + +struct volume_mount_callback { - return TRUE; -} + GtkFileSystemVolumeMountCallback callback; + GtkFileSystemHandle *handle; + GtkFileSystemVolume *volume; + GError *error; + gpointer data; +}; -static gboolean -gtk_file_system_unix_volume_mount (GtkFileSystem *file_system, - GtkFileSystemVolume *volume, - GError **error) +static inline void +dispatch_volume_mount_callback (struct volume_mount_callback *info) { - g_set_error (error, - GTK_FILE_SYSTEM_ERROR, - GTK_FILE_SYSTEM_ERROR_FAILED, - _("This file system does not support mounting")); - return FALSE; + (* info->callback) (info->handle, info->volume, info->error, info->data); + + if (info->error) + g_error_free (info->error); + + g_object_unref (info->handle); + + g_free (info); } -static gchar * -gtk_file_system_unix_volume_get_display_name (GtkFileSystem *file_system, - GtkFileSystemVolume *volume) +static inline void +queue_volume_mount_callback (GtkFileSystemVolumeMountCallback callback, + GtkFileSystemHandle *handle, + GtkFileSystemVolume *volume, + GError *error, + gpointer data) { - return g_strdup (_("Filesystem")); /* Same as Nautilus */ + struct volume_mount_callback *info; + + info = g_new (struct volume_mount_callback, 1); + info->callback = callback; + info->handle = handle; + info->volume = volume; + info->error = error; + info->data = data; + + queue_callback (GTK_FILE_SYSTEM_UNIX (handle->file_system), CALLBACK_VOLUME_MOUNT, info); } -static IconType -get_icon_type (const char *filename, - GError **error) + +struct callback_info { - struct stat statbuf; - IconType icon_type; + enum callback_types type; + + union + { + struct get_info_callback *get_info; + struct get_folder_callback *get_folder; + struct create_folder_callback *create_folder; + struct volume_mount_callback *volume_mount; + } info; +}; - /* If stat fails, try to fall back to lstat to catch broken links - */ - if (stat (filename, &statbuf) != 0) - { - if (errno != ENOENT || lstat (filename, &statbuf) != 0) - { - int save_errno = errno; - gchar *filename_utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL); - g_set_error (error, - GTK_FILE_SYSTEM_ERROR, - GTK_FILE_SYSTEM_ERROR_NONEXISTENT, - _("error getting information for '%s': %s"), - filename_utf8 ? filename_utf8 : "???", - g_strerror (save_errno)); - g_free (filename_utf8); - return ICON_NONE; - } - } - if (S_ISBLK (statbuf.st_mode)) - icon_type = ICON_BLOCK_DEVICE; - else if (S_ISLNK (statbuf.st_mode)) - icon_type = ICON_BROKEN_SYMBOLIC_LINK; /* See above */ - else if (S_ISCHR (statbuf.st_mode)) - icon_type = ICON_CHARACTER_DEVICE; - else if (S_ISDIR (statbuf.st_mode)) - icon_type = ICON_DIRECTORY; - else if (S_ISFIFO (statbuf.st_mode)) - icon_type = ICON_FIFO; - else if (S_ISSOCK (statbuf.st_mode)) - icon_type = ICON_SOCKET; +static gboolean +execute_callbacks (gpointer data) +{ + GSList *l; + gboolean unref_file_system = TRUE; + GtkFileSystemUnix *system_unix = GTK_FILE_SYSTEM_UNIX (data); + + if (!system_unix->execute_callbacks_idle_id) + unref_file_system = FALSE; else + g_object_ref (system_unix); + + for (l = system_unix->callbacks; l; l = l->next) { - icon_type = ICON_REGULAR; + struct callback_info *info = l->data; -#if 0 - if ((types & GTK_FILE_INFO_ICON) && icon_type == GTK_FILE_ICON_REGULAR && - (statbuf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) && - (strcmp (mime_type, XDG_MIME_TYPE_UNKNOWN) == 0 || - strcmp (mime_type, "application/x-executable") == 0 || - strcmp (mime_type, "application/x-shellscript") == 0)) - gtk_file_info_set_icon_type (info, GTK_FILE_ICON_EXECUTABLE); -#endif + switch (info->type) + { + case CALLBACK_GET_INFO: + dispatch_get_info_callback (info->info.get_info); + break; + + case CALLBACK_GET_FOLDER: + dispatch_get_folder_callback (info->info.get_folder); + break; + + case CALLBACK_CREATE_FOLDER: + dispatch_create_folder_callback (info->info.create_folder); + break; + + case CALLBACK_VOLUME_MOUNT: + dispatch_volume_mount_callback (info->info.volume_mount); + break; + } + + g_free (info); } - return icon_type; -} + g_slist_free (system_unix->callbacks); + system_unix->callbacks = NULL; -typedef struct -{ - gint size; - GdkPixbuf *pixbuf; -} IconCacheElement; + if (unref_file_system) + g_object_unref (system_unix); -static void -icon_cache_element_free (IconCacheElement *element) -{ - if (element->pixbuf) - g_object_unref (element->pixbuf); - g_free (element); + system_unix->execute_callbacks_idle_id = 0; + + return FALSE; } static void -icon_theme_changed (GtkIconTheme *icon_theme) +queue_callback (GtkFileSystemUnix *system_unix, + enum callback_types type, + gpointer data) { - GHashTable *cache; - - /* Difference from the initial creation is that we don't - * reconnect the signal - */ - cache = g_hash_table_new_full (g_str_hash, g_str_equal, - (GDestroyNotify)g_free, - (GDestroyNotify)icon_cache_element_free); - g_object_set_data_full (G_OBJECT (icon_theme), "gtk-file-icon-cache", - cache, (GDestroyNotify)g_hash_table_destroy); -} + struct callback_info *info; -static GdkPixbuf * -get_cached_icon (GtkWidget *widget, - const gchar *name, - gint pixel_size) -{ - GtkIconTheme *icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget)); - GHashTable *cache = g_object_get_data (G_OBJECT (icon_theme), "gtk-file-icon-cache"); - IconCacheElement *element; + info = g_new (struct callback_info, 1); + info->type = type; - if (!cache) + switch (type) { - cache = g_hash_table_new_full (g_str_hash, g_str_equal, - (GDestroyNotify)g_free, - (GDestroyNotify)icon_cache_element_free); - - g_object_set_data_full (G_OBJECT (icon_theme), "gtk-file-icon-cache", - cache, (GDestroyNotify)g_hash_table_destroy); - g_signal_connect (icon_theme, "changed", - G_CALLBACK (icon_theme_changed), NULL); - } + case CALLBACK_GET_INFO: + info->info.get_info = data; + break; - element = g_hash_table_lookup (cache, name); - if (!element) - { - element = g_new0 (IconCacheElement, 1); - g_hash_table_insert (cache, g_strdup (name), element); - } + case CALLBACK_GET_FOLDER: + info->info.get_folder = data; + break; - if (element->size != pixel_size) - { - if (element->pixbuf) - g_object_unref (element->pixbuf); - element->size = pixel_size; - element->pixbuf = gtk_icon_theme_load_icon (icon_theme, name, - pixel_size, 0, NULL); + case CALLBACK_CREATE_FOLDER: + info->info.create_folder = data; + break; + + case CALLBACK_VOLUME_MOUNT: + info->info.volume_mount = data; + break; } - return element->pixbuf ? g_object_ref (element->pixbuf) : NULL; -} + system_unix->callbacks = g_slist_append (system_unix->callbacks, info); -static GdkPixbuf * -gtk_file_system_unix_volume_render_icon (GtkFileSystem *file_system, - GtkFileSystemVolume *volume, - GtkWidget *widget, - gint pixel_size, - GError **error) -{ - /* FIXME: set the GError if we can't load the icon */ - return get_cached_icon (widget, "gnome-fs-blockdev", pixel_size); + if (!system_unix->execute_callbacks_idle_id) + system_unix->execute_callbacks_idle_id = gdk_threads_add_idle (execute_callbacks, system_unix); } -static gboolean -gtk_file_system_unix_get_parent (GtkFileSystem *file_system, - const GtkFilePath *path, - GtkFilePath **parent, - GError **error) +static GtkFileSystemHandle * +create_handle (GtkFileSystem *file_system) { - const char *filename; + GtkFileSystemUnix *system_unix; + GtkFileSystemHandle *handle; - filename = gtk_file_path_get_string (path); - g_return_val_if_fail (filename != NULL, FALSE); - g_return_val_if_fail (g_path_is_absolute (filename), FALSE); + system_unix = GTK_FILE_SYSTEM_UNIX (file_system); + + handle = g_object_new (GTK_TYPE_FILE_SYSTEM_HANDLE_UNIX, NULL); + handle->file_system = file_system; + + g_assert (g_hash_table_lookup (system_unix->handles, handle) == NULL); + g_hash_table_insert (system_unix->handles, handle, handle); + + return handle; +} + + + +static GtkFileSystemHandle * +gtk_file_system_unix_get_info (GtkFileSystem *file_system, + const GtkFilePath *path, + GtkFileInfoType types, + GtkFileSystemGetInfoCallback callback, + gpointer data) +{ + GError *error = NULL; + GtkFileSystemUnix *system_unix; + GtkFileSystemHandle *handle; + const char *filename; + GtkFileInfo *info; + gchar *basename; + struct stat statbuf; + const char *mime_type; + + system_unix = GTK_FILE_SYSTEM_UNIX (file_system); + handle = create_handle (file_system); + + filename = gtk_file_path_get_string (path); + g_return_val_if_fail (filename != NULL, NULL); + g_return_val_if_fail (g_path_is_absolute (filename), NULL); + + if (!stat_with_error (filename, &statbuf, &error)) + { + g_object_ref (handle); + queue_get_info_callback (callback, handle, NULL, error, data); + return handle; + } + + if ((types & GTK_FILE_INFO_MIME_TYPE) != 0) + mime_type = xdg_mime_get_mime_type_for_file (filename, &statbuf); + else + mime_type = NULL; + + basename = g_path_get_basename (filename); + + info = create_file_info (NULL, filename, basename, types, &statbuf, + mime_type); + g_free (basename); + g_object_ref (handle); + queue_get_info_callback (callback, handle, info, NULL, data); + + return handle; +} + +static gboolean +load_folder (gpointer data) +{ + GtkFileFolderUnix *folder_unix = data; + GSList *children; + + if ((folder_unix->types & STAT_NEEDED_MASK) != 0) + fill_in_stats (folder_unix); + + if ((folder_unix->types & GTK_FILE_INFO_MIME_TYPE) != 0) + fill_in_mime_type (folder_unix); + + if (gtk_file_folder_unix_list_children (GTK_FILE_FOLDER (folder_unix), &children, NULL)) + { + folder_unix->is_finished_loading = TRUE; + g_signal_emit_by_name (folder_unix, "files-added", children); + gtk_file_paths_free (children); + } + + folder_unix->load_folder_id = 0; + + g_signal_emit_by_name (folder_unix, "finished-loading", 0); + + return FALSE; +} + +static GtkFileSystemHandle * +gtk_file_system_unix_get_folder (GtkFileSystem *file_system, + const GtkFilePath *path, + GtkFileInfoType types, + GtkFileSystemGetFolderCallback callback, + gpointer data) +{ + GError *error = NULL; + GtkFileSystemUnix *system_unix; + GtkFileFolderUnix *folder_unix; + GtkFileSystemHandle *handle; + const char *filename; + char *filename_copy; + gboolean set_asof = FALSE; + + system_unix = GTK_FILE_SYSTEM_UNIX (file_system); + + filename = gtk_file_path_get_string (path); + g_return_val_if_fail (filename != NULL, NULL); + g_return_val_if_fail (g_path_is_absolute (filename), NULL); + + handle = create_handle (file_system); + + filename_copy = remove_trailing_slash (filename); + folder_unix = g_hash_table_lookup (system_unix->folder_hash, filename_copy); + + if (folder_unix) + { + g_free (filename_copy); + if (folder_unix->stat_info && + time (NULL) - folder_unix->asof >= FOLDER_CACHE_LIFETIME) + { +#if 0 + g_print ("Cleaning out cached directory %s\n", filename); +#endif + g_hash_table_destroy (folder_unix->stat_info); + folder_unix->stat_info = NULL; + folder_unix->have_mime_type = FALSE; + folder_unix->have_stat = FALSE; + folder_unix->have_hidden = FALSE; + set_asof = TRUE; + } + + g_object_ref (folder_unix); + folder_unix->types |= types; + types = folder_unix->types; + } + else + { + struct stat statbuf; + int result; + int code; + int my_errno; + + code = my_errno = 0; /* shut up GCC */ + + result = stat (filename, &statbuf); + + if (result == 0) + { + if (!S_ISDIR (statbuf.st_mode)) + { + result = -1; + code = GTK_FILE_SYSTEM_ERROR_NOT_FOLDER; + my_errno = ENOTDIR; + } + } + else + { + my_errno = errno; + + if (my_errno == ENOENT) + code = GTK_FILE_SYSTEM_ERROR_NONEXISTENT; + else + code = GTK_FILE_SYSTEM_ERROR_FAILED; + } + + if (result != 0) + { + gchar *display_name = g_filename_display_name (filename); + g_set_error (&error, + GTK_FILE_SYSTEM_ERROR, + code, + _("Error getting information for '%s': %s"), + display_name, + g_strerror (my_errno)); + + g_object_ref (handle); + queue_get_folder_callback (callback, handle, NULL, error, data); + + g_free (display_name); + g_free (filename_copy); + return handle; + } + + folder_unix = g_object_new (GTK_TYPE_FILE_FOLDER_UNIX, NULL); + folder_unix->system_unix = system_unix; + folder_unix->filename = filename_copy; + folder_unix->types = types; + folder_unix->stat_info = NULL; + folder_unix->load_folder_id = 0; + folder_unix->have_mime_type = FALSE; + folder_unix->have_stat = FALSE; + folder_unix->have_hidden = FALSE; + folder_unix->is_finished_loading = FALSE; + set_asof = TRUE; + + if ((system_unix->have_afs && + system_unix->afs_statbuf.st_dev == statbuf.st_dev && + system_unix->afs_statbuf.st_ino == statbuf.st_ino) || + (system_unix->have_net && + system_unix->net_statbuf.st_dev == statbuf.st_dev && + system_unix->net_statbuf.st_ino == statbuf.st_ino)) + folder_unix->is_network_dir = TRUE; + else + folder_unix->is_network_dir = FALSE; + + g_hash_table_insert (system_unix->folder_hash, + folder_unix->filename, + folder_unix); + } + + if (set_asof) + folder_unix->asof = time (NULL); + + g_object_ref (handle); + queue_get_folder_callback (callback, handle, GTK_FILE_FOLDER (folder_unix), NULL, data); + + /* Start loading the folder contents in an idle */ + if (!folder_unix->load_folder_id) + folder_unix->load_folder_id = + gdk_threads_add_idle ((GSourceFunc) load_folder, folder_unix); + + return handle; +} + +static GtkFileSystemHandle * +gtk_file_system_unix_create_folder (GtkFileSystem *file_system, + const GtkFilePath *path, + GtkFileSystemCreateFolderCallback callback, + gpointer data) +{ + GError *error = NULL; + GtkFileSystemUnix *system_unix; + GtkFileSystemHandle *handle; + const char *filename; + gboolean result; + char *parent, *tmp; + int save_errno = errno; + + system_unix = GTK_FILE_SYSTEM_UNIX (file_system); + + filename = gtk_file_path_get_string (path); + g_return_val_if_fail (filename != NULL, NULL); + g_return_val_if_fail (g_path_is_absolute (filename), NULL); + + handle = create_handle (file_system); + + tmp = remove_trailing_slash (filename); + errno = 0; + result = mkdir (tmp, 0777) == 0; + save_errno = errno; + g_free (tmp); + + if (!result) + { + gchar *display_name = g_filename_display_name (filename); + g_set_error (&error, + GTK_FILE_SYSTEM_ERROR, + GTK_FILE_SYSTEM_ERROR_NONEXISTENT, + _("Error creating folder '%s': %s"), + display_name, + g_strerror (save_errno)); + + g_object_ref (handle); + queue_create_folder_callback (callback, handle, path, error, data); + + g_free (display_name); + return handle; + } + + g_object_ref (handle); + queue_create_folder_callback (callback, handle, path, NULL, data); + + parent = get_parent_dir (filename); + if (parent) + { + GtkFileFolderUnix *folder_unix; + + folder_unix = g_hash_table_lookup (system_unix->folder_hash, parent); + if (folder_unix) + { + GSList *paths; + char *basename; + struct stat_info_entry *entry; + + /* Make sure the new folder exists in the parent's folder */ + entry = g_new0 (struct stat_info_entry, 1); + if (folder_unix->is_network_dir) + { + entry->statbuf.st_mode = S_IFDIR; + entry->mime_type = g_strdup ("x-directory/normal"); + } + + basename = g_path_get_basename (filename); + g_hash_table_insert (folder_unix->stat_info, + basename, + entry); + + if (folder_unix->have_stat) + { + /* Cheating */ + if ((folder_unix->types & STAT_NEEDED_MASK) != 0) + cb_fill_in_stats (basename, entry, folder_unix); + + if ((folder_unix->types & GTK_FILE_INFO_MIME_TYPE) != 0) + cb_fill_in_mime_type (basename, entry, folder_unix); + } + + paths = g_slist_append (NULL, (GtkFilePath *) path); + g_signal_emit_by_name (folder_unix, "files-added", paths); + g_slist_free (paths); + } + + g_free (parent); + } + + return handle; +} + +static void +gtk_file_system_unix_cancel_operation (GtkFileSystemHandle *handle) +{ + /* We don't set "cancelled" to TRUE here, since the actual operation + * is executed in the function itself and not in a callback. So + * the operations can never be cancelled (since they will be already + * completed at this point. + */ +} + +static void +gtk_file_system_unix_volume_free (GtkFileSystem *file_system, + GtkFileSystemVolume *volume) +{ + GtkFilePath *path; + + path = (GtkFilePath *) volume; + gtk_file_path_free (path); +} + +static GtkFilePath * +gtk_file_system_unix_volume_get_base_path (GtkFileSystem *file_system, + GtkFileSystemVolume *volume) +{ + return gtk_file_path_new_dup ("/"); +} + +static gboolean +gtk_file_system_unix_volume_get_is_mounted (GtkFileSystem *file_system, + GtkFileSystemVolume *volume) +{ + return TRUE; +} + +static GtkFileSystemHandle * +gtk_file_system_unix_volume_mount (GtkFileSystem *file_system, + GtkFileSystemVolume *volume, + GtkFileSystemVolumeMountCallback callback, + gpointer data) +{ + GError *error = NULL; + GtkFileSystemHandle *handle = create_handle (file_system); + + g_set_error (&error, + GTK_FILE_SYSTEM_ERROR, + GTK_FILE_SYSTEM_ERROR_FAILED, + _("This file system does not support mounting")); + + g_object_ref (handle); + queue_volume_mount_callback (callback, handle, volume, error, data); + + return handle; +} + +static gchar * +gtk_file_system_unix_volume_get_display_name (GtkFileSystem *file_system, + GtkFileSystemVolume *volume) +{ + return g_strdup (_("File System")); /* Same as Nautilus */ +} + +static IconType +get_icon_type_from_stat (struct stat *statp) +{ + if (S_ISBLK (statp->st_mode)) + return ICON_BLOCK_DEVICE; + else if (S_ISLNK (statp->st_mode)) + return ICON_BROKEN_SYMBOLIC_LINK; /* See get_icon_type */ + else if (S_ISCHR (statp->st_mode)) + return ICON_CHARACTER_DEVICE; + else if (S_ISDIR (statp->st_mode)) + return ICON_DIRECTORY; +#ifdef S_ISFIFO + else if (S_ISFIFO (statp->st_mode)) + return ICON_FIFO; +#endif +#ifdef S_ISSOCK + else if (S_ISSOCK (statp->st_mode)) + return ICON_SOCKET; +#endif + else + return ICON_REGULAR; +} + +static IconType +get_icon_type (const char *filename, + GError **error) +{ + struct stat statbuf; + + /* If stat fails, try to fall back to lstat to catch broken links + */ + if (stat (filename, &statbuf) != 0) + { + if (errno != ENOENT || lstat (filename, &statbuf) != 0) + { + int save_errno = errno; + gchar *display_name = g_filename_display_name (filename); + g_set_error (error, + GTK_FILE_SYSTEM_ERROR, + GTK_FILE_SYSTEM_ERROR_NONEXISTENT, + _("Error getting information for '%s': %s"), + display_name, + g_strerror (save_errno)); + g_free (display_name); + + return ICON_NONE; + } + } + + return get_icon_type_from_stat (&statbuf); +} + +/* Renders a fallback icon from the stock system */ +static const gchar * +get_fallback_icon_name (IconType icon_type) +{ + const char *stock_name; + + switch (icon_type) + { + case ICON_BLOCK_DEVICE: + stock_name = GTK_STOCK_HARDDISK; + break; + + case ICON_DIRECTORY: + stock_name = GTK_STOCK_DIRECTORY; + break; + + case ICON_EXECUTABLE: + stock_name = GTK_STOCK_EXECUTE; + break; + + default: + stock_name = GTK_STOCK_FILE; + break; + } + + return stock_name; +} + +static gchar * +gtk_file_system_unix_volume_get_icon_name (GtkFileSystem *file_system, + GtkFileSystemVolume *volume, + GError **error) +{ + /* FIXME: maybe we just always want to return GTK_STOCK_HARDDISK here? + * or the new tango icon name? + */ + return g_strdup ("gnome-dev-harddisk"); +} + +static char * +get_parent_dir (const char *filename) +{ + int len; + + len = strlen (filename); + + /* Ignore trailing slashes */ + if (len > 1 && filename[len - 1] == '/') + { + char *tmp, *parent; + + tmp = g_strndup (filename, len - 1); + + parent = g_path_get_dirname (tmp); + g_free (tmp); + + return parent; + } + else + return g_path_get_dirname (filename); +} + +static gboolean +gtk_file_system_unix_get_parent (GtkFileSystem *file_system, + const GtkFilePath *path, + GtkFilePath **parent, + GError **error) +{ + const char *filename; + + filename = gtk_file_path_get_string (path); + g_return_val_if_fail (filename != NULL, FALSE); + g_return_val_if_fail (g_path_is_absolute (filename), FALSE); if (filename_is_root (filename)) { @@ -609,7 +1297,7 @@ gtk_file_system_unix_get_parent (GtkFileSystem *file_system, } else { - gchar *parent_filename = g_path_get_dirname (filename); + gchar *parent_filename = get_parent_dir (filename); *parent = filename_to_path (parent_filename); g_free (parent_filename); } @@ -633,6 +1321,18 @@ gtk_file_system_unix_make_path (GtkFileSystem *file_system, g_return_val_if_fail (base_filename != NULL, NULL); g_return_val_if_fail (g_path_is_absolute (base_filename), NULL); + if (strchr (display_name, G_DIR_SEPARATOR)) + { + g_set_error (error, + GTK_FILE_SYSTEM_ERROR, + GTK_FILE_SYSTEM_ERROR_BAD_FILENAME, + _("The name \"%s\" is not valid because it contains the character \"%s\". " + "Please use a different name."), + display_name, + G_DIR_SEPARATOR_S); + return NULL; + } + filename = g_filename_from_utf8 (display_name, -1, NULL, NULL, &tmp_error); if (!filename) { @@ -719,13 +1419,60 @@ canonicalize_filename (gchar *filename) } } - p++; + p++; + } + + if (q > filename + 1 && *(q - 1) == G_DIR_SEPARATOR) + q--; + + *q = '\0'; +} + +/* Takes a user-typed filename and expands a tilde at the beginning of the string */ +static char * +expand_tilde (const char *filename) +{ + const char *notilde; + const char *slash; + const char *home; + + if (filename[0] != '~') + return g_strdup (filename); + + notilde = filename + 1; + + slash = strchr (notilde, G_DIR_SEPARATOR); + + if (slash == notilde || !*notilde) + { + home = g_get_home_dir (); + + if (!home) + return g_strdup (filename); } + else + { + char *username; + struct passwd *passwd; - if (q > filename + 1 && *(q - 1) == G_DIR_SEPARATOR) - q--; + if (slash) + username = g_strndup (notilde, slash - notilde); + else + username = g_strdup (notilde); - *q = '\0'; + passwd = getpwnam (username); + g_free (username); + + if (!passwd) + return g_strdup (filename); + + home = passwd->pw_dir; + } + + if (slash) + return g_build_filename (home, G_DIR_SEPARATOR_S, slash + 1, NULL); + else + return g_strdup (home); } static gboolean @@ -737,6 +1484,7 @@ gtk_file_system_unix_parse (GtkFileSystem *file_system, GError **error) { const char *base_filename; + gchar *filename; gchar *last_slash; gboolean result = FALSE; @@ -744,11 +1492,21 @@ gtk_file_system_unix_parse (GtkFileSystem *file_system, g_return_val_if_fail (base_filename != NULL, FALSE); g_return_val_if_fail (g_path_is_absolute (base_filename), FALSE); - last_slash = strrchr (str, G_DIR_SEPARATOR); + filename = expand_tilde (str); + if (!filename) + { + g_set_error (error, + GTK_FILE_SYSTEM_ERROR, + GTK_FILE_SYSTEM_ERROR_BAD_FILENAME, + "%s", ""); /* nothing for now, as we are string-frozen */ + return FALSE; + } + + last_slash = strrchr (filename, G_DIR_SEPARATOR); if (!last_slash) { *folder = gtk_file_path_copy (base_path); - *file_part = g_strdup (str); + *file_part = g_strdup (filename); result = TRUE; } else @@ -757,10 +1515,10 @@ gtk_file_system_unix_parse (GtkFileSystem *file_system, gchar *folder_path; GError *tmp_error = NULL; - if (last_slash == str) + if (last_slash == filename) folder_part = g_strdup ("/"); else - folder_part = g_filename_from_utf8 (str, last_slash - str, + folder_part = g_filename_from_utf8 (filename, last_slash - filename, NULL, NULL, &tmp_error); if (!folder_part) @@ -793,6 +1551,8 @@ gtk_file_system_unix_parse (GtkFileSystem *file_system, } } + g_free (filename); + return result; } @@ -814,125 +1574,194 @@ static GtkFilePath * gtk_file_system_unix_uri_to_path (GtkFileSystem *file_system, const gchar *uri) { + GtkFilePath *path; gchar *filename = g_filename_from_uri (uri, NULL, NULL); + if (filename) - return gtk_file_path_new_steal (filename); + { + path = filename_to_path (filename); + g_free (filename); + } else - return NULL; + path = NULL; + + return path; } static GtkFilePath * gtk_file_system_unix_filename_to_path (GtkFileSystem *file_system, const gchar *filename) { - return gtk_file_path_new_dup (filename); + return filename_to_path (filename); } +/* Returns the name of the icon to be used for a path which is known to be a + * directory. This can vary for Home, Desktop, etc. + */ static const char * -get_icon_for_directory (const char *path) +get_icon_name_for_directory (const char *path) { - static char *desktop_path = NULL; - if (!g_get_home_dir ()) return "gnome-fs-directory"; - if (!desktop_path) - desktop_path = g_build_filename (g_get_home_dir (), "Desktop", NULL); - if (strcmp (g_get_home_dir (), path) == 0) return "gnome-fs-home"; - else if (strcmp (desktop_path, path) == 0) + else if (strcmp (g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP), path) == 0) return "gnome-fs-desktop"; else return "gnome-fs-directory"; + + return NULL; } -static GdkPixbuf * -gtk_file_system_unix_render_icon (GtkFileSystem *file_system, - const GtkFilePath *path, - GtkWidget *widget, - gint pixel_size, - GError **error) +/* Computes our internal icon type based on a path name; also returns the MIME + * type in case we come up with ICON_REGULAR. + */ +static IconType +get_icon_type_from_path (GtkFileFolderUnix *folder_unix, + struct stat *statbuf, + const char *filename, + const char **mime_type) { - const char *filename; IconType icon_type; - const char *mime_type; - - filename = gtk_file_path_get_string (path); - icon_type = get_icon_type (filename, error); - - /* FIXME: this function should not return NULL without setting the GError; we - * should perhaps provide a "never fails" generic stock icon for when all else - * fails. - */ - if (icon_type == ICON_NONE) - return NULL; + *mime_type = NULL; - if (icon_type != ICON_REGULAR) + if (folder_unix && folder_unix->have_stat) { - const char *name; + char *basename; + struct stat_info_entry *entry; - switch (icon_type) + g_assert (folder_unix->stat_info != NULL); + + basename = g_path_get_basename (filename); + entry = g_hash_table_lookup (folder_unix->stat_info, basename); + g_free (basename); + if (entry) { - case ICON_BLOCK_DEVICE: - name = "gnome-fs-blockdev"; - break; - case ICON_BROKEN_SYMBOLIC_LINK: - name = "gnome-fs-symlink"; - break; - case ICON_CHARACTER_DEVICE: - name = "gnome-fs-chardev"; - break; - case ICON_DIRECTORY: - name = get_icon_for_directory (filename); - break; - case ICON_EXECUTABLE: - name ="gnome-fs-executable"; - break; - case ICON_FIFO: - name = "gnome-fs-fifo"; - break; - case ICON_SOCKET: - name = "gnome-fs-socket"; - break; - default: - g_assert_not_reached (); - return NULL; - } + if (entry->icon_type == ICON_UNDECIDED) + { + entry->icon_type = get_icon_type_from_stat (&entry->statbuf); + g_assert (entry->icon_type != ICON_UNDECIDED); + } + icon_type = entry->icon_type; + if (icon_type == ICON_REGULAR) + { + fill_in_mime_type (folder_unix); + *mime_type = entry->mime_type; + } - return get_cached_icon (widget, name, pixel_size); + return icon_type; + } } - mime_type = xdg_mime_get_mime_type_for_file (filename); - if (mime_type) + if (statbuf) + return get_icon_type_from_stat (statbuf); + + icon_type = get_icon_type (filename, NULL); + if (icon_type == ICON_REGULAR) + *mime_type = xdg_mime_get_mime_type_for_file (filename, NULL); + + return icon_type; +} + +/* Renders an icon for a non-ICON_REGULAR file */ +static const gchar * +get_special_icon_name (IconType icon_type, + const gchar *filename) +{ + const char *name; + + g_assert (icon_type != ICON_REGULAR); + + switch (icon_type) { - const char *separator; - GString *icon_name; - GdkPixbuf *pixbuf; - - separator = strchr (mime_type, '/'); - if (!separator) - return NULL; - - icon_name = g_string_new ("gnome-mime-"); - g_string_append_len (icon_name, mime_type, separator - mime_type); - g_string_append_c (icon_name, '-'); - g_string_append (icon_name, separator + 1); - pixbuf = get_cached_icon (widget, icon_name->str, pixel_size); - g_string_free (icon_name, TRUE); - if (pixbuf) - return pixbuf; - - icon_name = g_string_new ("gnome-mime-"); - g_string_append_len (icon_name, mime_type, separator - mime_type); - pixbuf = get_cached_icon (widget, icon_name->str, pixel_size); - g_string_free (icon_name, TRUE); - if (pixbuf) - return pixbuf; + case ICON_BLOCK_DEVICE: + name = "gnome-fs-blockdev"; + break; + case ICON_BROKEN_SYMBOLIC_LINK: + name = "gnome-fs-symlink"; + break; + case ICON_CHARACTER_DEVICE: + name = "gnome-fs-chardev"; + break; + case ICON_DIRECTORY: + /* get_icon_name_for_directory() returns a dupped string */ + return get_icon_name_for_directory (filename); + case ICON_EXECUTABLE: + name ="gnome-fs-executable"; + break; + case ICON_FIFO: + name = "gnome-fs-fifo"; + break; + case ICON_SOCKET: + name = "gnome-fs-socket"; + break; + default: + g_assert_not_reached (); + return NULL; } - return get_cached_icon (widget, "gnome-fs-regular", pixel_size); + return name; +} + +static gchar * +get_icon_name_for_mime_type (const char *mime_type) +{ + char *name; + const char *separator; + GString *icon_name; + + if (!mime_type) + return NULL; + + separator = strchr (mime_type, '/'); + if (!separator) + return NULL; /* maybe we should return a GError with "invalid MIME-type" */ + + /* FIXME: we default to the gnome icon naming for now. Some question + * as below, how are we going to handle a second attempt? + */ +#if 0 + icon_name = g_string_new (""); + g_string_append_len (icon_name, mime_type, separator - mime_type); + g_string_append_c (icon_name, '-'); + g_string_append (icon_name, separator + 1); + pixbuf = get_cached_icon (widget, icon_name->str, pixel_size); + g_string_free (icon_name, TRUE); + if (pixbuf) + return pixbuf; + + icon_name = g_string_new (""); + g_string_append_len (icon_name, mime_type, separator - mime_type); + g_string_append (icon_name, "-x-generic"); + pixbuf = get_cached_icon (widget, icon_name->str, pixel_size); + g_string_free (icon_name, TRUE); + if (pixbuf) + return pixbuf; +#endif + + icon_name = g_string_new ("gnome-mime-"); + g_string_append_len (icon_name, mime_type, separator - mime_type); + g_string_append_c (icon_name, '-'); + g_string_append (icon_name, separator + 1); + name = icon_name->str; + g_string_free (icon_name, FALSE); + + return name; + + /* FIXME: how are we going to implement a second attempt? */ +#if 0 + if (pixbuf) + return pixbuf; + + icon_name = g_string_new ("gnome-mime-"); + g_string_append_len (icon_name, mime_type, separator - mime_type); + pixbuf = get_cached_icon (widget, icon_name->str, pixel_size); + g_string_free (icon_name, TRUE); + + return pixbuf; +#endif } static void @@ -966,13 +1795,12 @@ is_local_uri (const char *uri) } static char * -bookmark_get_filename (gboolean tmp_file) +bookmark_get_filename (void) { char *filename; - filename = g_build_filename (g_get_home_dir (), - tmp_file ? BOOKMARKS_TMP_FILENAME : BOOKMARKS_FILENAME, - NULL); + filename = g_build_filename (g_get_home_dir (), + BOOKMARKS_FILENAME, NULL); g_assert (filename != NULL); return filename; } @@ -984,7 +1812,7 @@ bookmark_list_read (GSList **bookmarks, GError **error) gchar *contents; gboolean result = FALSE; - filename = bookmark_get_filename (FALSE); + filename = bookmark_get_filename (); *bookmarks = NULL; if (g_file_get_contents (filename, &contents, NULL, error)) @@ -1018,79 +1846,41 @@ bookmark_list_read (GSList **bookmarks, GError **error) } static gboolean -bookmark_list_write (GSList *bookmarks, GError **error) +bookmark_list_write (GSList *bookmarks, + GError **error) { - char *tmp_filename; + GSList *l; + GString *string; char *filename; - gboolean result = TRUE; - FILE *file; - int fd; - int saved_errno; - - /* First, write a temporary file */ + GError *tmp_error = NULL; + gboolean result; - tmp_filename = bookmark_get_filename (TRUE); - filename = bookmark_get_filename (FALSE); + string = g_string_new (""); - fd = g_mkstemp (tmp_filename); - if (fd == -1) + for (l = bookmarks; l; l = l->next) { - saved_errno = errno; - goto io_error; + g_string_append (string, l->data); + g_string_append_c (string, '\n'); } - if ((file = fdopen (fd, "w")) != NULL) - { - GSList *l; - - for (l = bookmarks; l; l = l->next) - if (fputs (l->data, file) == EOF - || fputs ("\n", file) == EOF) - { - saved_errno = errno; - goto io_error; - } + filename = bookmark_get_filename (); - if (fclose (file) == EOF) - { - saved_errno = errno; - goto io_error; - } + result = g_file_set_contents (filename, string->str, -1, &tmp_error); - if (rename (tmp_filename, filename) == -1) - { - saved_errno = errno; - goto io_error; - } + g_free (filename); + g_string_free (string, TRUE); - result = TRUE; - goto out; - } - else + if (!result) { - saved_errno = errno; - - /* fdopen() failed, so we can't do much error checking here anyway */ - close (fd); + g_set_error (error, + GTK_FILE_SYSTEM_ERROR, + GTK_FILE_SYSTEM_ERROR_FAILED, + _("Bookmark saving failed: %s"), + tmp_error->message); + + g_error_free (tmp_error); } - io_error: - - g_set_error (error, - GTK_FILE_SYSTEM_ERROR, - GTK_FILE_SYSTEM_ERROR_FAILED, - _("Bookmark saving failed (%s)"), - g_strerror (saved_errno)); - result = FALSE; - - if (fd != -1) - unlink (tmp_filename); /* again, not much error checking we can do here */ - - out: - - g_free (filename); - g_free (tmp_filename); - return result; } @@ -1123,15 +1913,24 @@ gtk_file_system_unix_insert_bookmark (GtkFileSystem *file_system, for (l = bookmarks; l; l = l->next) { - const char *bookmark; + char *bookmark, *space; bookmark = l->data; - if (strcmp (bookmark, uri) == 0) + + space = strchr (bookmark, ' '); + if (space) + *space = '\0'; + if (strcmp (bookmark, uri) != 0) + { + if (space) + *space = ' '; + } + else { g_set_error (error, GTK_FILE_SYSTEM_ERROR, GTK_FILE_SYSTEM_ERROR_ALREADY_EXISTS, - "%s already exists in the bookmarks list", + _("'%s' already exists in the bookmarks list"), uri); goto out; } @@ -1171,10 +1970,19 @@ gtk_file_system_unix_remove_bookmark (GtkFileSystem *file_system, for (l = bookmarks; l; l = l->next) { - const char *bookmark; + char *bookmark, *space; - bookmark = l->data; - if (strcmp (bookmark, uri) == 0) + bookmark = (char *)l->data; + space = strchr (bookmark, ' '); + if (space) + *space = '\0'; + + if (strcmp (bookmark, uri) != 0) + { + if (space) + *space = ' '; + } + else { g_free (l->data); bookmarks = g_slist_remove_link (bookmarks, l); @@ -1183,7 +1991,8 @@ gtk_file_system_unix_remove_bookmark (GtkFileSystem *file_system, if (bookmark_list_write (bookmarks, error)) { result = TRUE; - g_signal_emit_by_name (file_system, "bookmarks-changed", 0); + + g_signal_emit_by_name (file_system, "bookmarks-changed", 0); } goto out; @@ -1193,141 +2002,442 @@ gtk_file_system_unix_remove_bookmark (GtkFileSystem *file_system, g_set_error (error, GTK_FILE_SYSTEM_ERROR, GTK_FILE_SYSTEM_ERROR_NONEXISTENT, - "%s does not exist in the bookmarks list", + _("'%s' does not exist in the bookmarks list"), uri); out: - g_free (uri); - bookmark_list_free (bookmarks); + g_free (uri); + bookmark_list_free (bookmarks); + + return result; +} + +static GSList * +gtk_file_system_unix_list_bookmarks (GtkFileSystem *file_system) +{ + GSList *bookmarks; + GSList *result; + GSList *l; + + if (!bookmark_list_read (&bookmarks, NULL)) + return NULL; + + result = NULL; + + for (l = bookmarks; l; l = l->next) + { + char *bookmark, *space; + + bookmark = (char *)l->data; + space = strchr (bookmark, ' '); + if (space) + *space = '\0'; + + if (is_local_uri (bookmark)) + result = g_slist_prepend (result, gtk_file_system_unix_uri_to_path (file_system, bookmark)); + } + + bookmark_list_free (bookmarks); + + result = g_slist_reverse (result); + return result; +} + +static gchar * +gtk_file_system_unix_get_bookmark_label (GtkFileSystem *file_system, + const GtkFilePath *path) +{ + GSList *labels; + gchar *label; + GSList *l; + char *bookmark, *space, *uri; + + labels = NULL; + label = NULL; + + uri = gtk_file_system_path_to_uri (file_system, path); + bookmark_list_read (&labels, NULL); + + for (l = labels; l && !label; l = l->next) + { + bookmark = (char *)l->data; + space = strchr (bookmark, ' '); + if (!space) + continue; + + *space = '\0'; + + if (strcmp (uri, bookmark) == 0) + label = g_strdup (space + 1); + } + + bookmark_list_free (labels); + g_free (uri); + + return label; +} + +static void +gtk_file_system_unix_set_bookmark_label (GtkFileSystem *file_system, + const GtkFilePath *path, + const gchar *label) +{ + GSList *labels; + GSList *l; + gchar *bookmark, *space, *uri; + gboolean found; + + labels = NULL; + + uri = gtk_file_system_path_to_uri (file_system, path); + bookmark_list_read (&labels, NULL); + + found = FALSE; + for (l = labels; l && !found; l = l->next) + { + bookmark = (gchar *)l->data; + space = strchr (bookmark, ' '); + if (space) + *space = '\0'; + + if (strcmp (bookmark, uri) != 0) + { + if (space) + *space = ' '; + } + else + { + g_free (bookmark); + + if (label && *label) + l->data = g_strdup_printf ("%s %s", uri, label); + else + l->data = g_strdup (uri); + + found = TRUE; + break; + } + } + + if (found) + { + if (bookmark_list_write (labels, NULL)) + g_signal_emit_by_name (file_system, "bookmarks-changed", 0); + } + + bookmark_list_free (labels); + g_free (uri); +} + +static void +_gtk_file_folder_unix_class_init (GtkFileFolderUnixClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + gobject_class->finalize = gtk_file_folder_unix_finalize; +} + +static void +gtk_file_folder_unix_iface_init (GtkFileFolderIface *iface) +{ + iface->get_info = gtk_file_folder_unix_get_info; + iface->list_children = gtk_file_folder_unix_list_children; + iface->is_finished_loading = gtk_file_folder_unix_is_finished_loading; +} + +static void +_gtk_file_folder_unix_init (GtkFileFolderUnix *impl) +{ +} + +static void +gtk_file_folder_unix_finalize (GObject *object) +{ + GtkFileFolderUnix *folder_unix = GTK_FILE_FOLDER_UNIX (object); + + if (folder_unix->load_folder_id) + { + g_source_remove (folder_unix->load_folder_id); + folder_unix->load_folder_id = 0; + } + + g_hash_table_remove (folder_unix->system_unix->folder_hash, folder_unix->filename); + + if (folder_unix->stat_info) + { +#if 0 + g_print ("Releasing information for directory %s\n", folder_unix->filename); +#endif + g_hash_table_destroy (folder_unix->stat_info); + } + + g_free (folder_unix->filename); + + G_OBJECT_CLASS (_gtk_file_folder_unix_parent_class)->finalize (object); +} + +/* Creates a GtkFileInfo for "/" by stat()ing it */ +static GtkFileInfo * +file_info_for_root_with_error (const char *root_name, + GError **error) +{ + struct stat statbuf; + GtkFileInfo *info; + + if (stat (root_name, &statbuf) != 0) + { + int saved_errno; + + saved_errno = errno; + g_set_error (error, + GTK_FILE_SYSTEM_ERROR, + GTK_FILE_SYSTEM_ERROR_FAILED, + _("Error getting information for '%s': %s"), + "/", g_strerror (saved_errno)); + + return NULL; + } + + info = gtk_file_info_new (); + gtk_file_info_set_display_name (info, "/"); + gtk_file_info_set_is_folder (info, TRUE); + gtk_file_info_set_is_hidden (info, FALSE); + gtk_file_info_set_mime_type (info, "x-directory/normal"); + gtk_file_info_set_modification_time (info, statbuf.st_mtime); + gtk_file_info_set_size (info, statbuf.st_size); - return result; + return info; } -static GSList * -gtk_file_system_unix_list_bookmarks (GtkFileSystem *file_system) +static gboolean +stat_with_error (const char *filename, + struct stat *statbuf, + GError **error) { - GSList *bookmarks; - GSList *result; - GSList *l; - - if (!bookmark_list_read (&bookmarks, NULL)) - return NULL; + if (stat (filename, statbuf) == -1 && + (errno != ENOENT || lstat (filename, statbuf) == -1)) + { + int saved_errno; + int code; + char *display_name; - result = NULL; + saved_errno = errno; - for (l = bookmarks; l; l = l->next) - { - const char *name; + if (saved_errno == ENOENT) + code = GTK_FILE_SYSTEM_ERROR_NONEXISTENT; + else + code = GTK_FILE_SYSTEM_ERROR_FAILED; - name = l->data; + display_name = g_filename_display_name (filename); + g_set_error (error, + GTK_FILE_SYSTEM_ERROR, + code, + _("Error getting information for '%s': %s"), + display_name, + g_strerror (saved_errno)); - if (is_local_uri (name)) - result = g_slist_prepend (result, gtk_file_system_unix_uri_to_path (file_system, name)); + g_free (display_name); + return FALSE; } - bookmark_list_free (bookmarks); - - result = g_slist_reverse (result); - return result; + return TRUE; } -/* - * GtkFileFolderUnix - */ -static GType -gtk_file_folder_unix_get_type (void) +/* Creates a new GtkFileInfo from the specified data */ +static GtkFileInfo * +create_file_info (GtkFileFolderUnix *folder_unix, + const char *filename, + const char *basename, + GtkFileInfoType types, + struct stat *statbuf, + const char *mime_type) { - static GType file_folder_unix_type = 0; + GtkFileInfo *info; - if (!file_folder_unix_type) + info = gtk_file_info_new (); + + if (types & GTK_FILE_INFO_DISPLAY_NAME) { - static const GTypeInfo file_folder_unix_info = - { - sizeof (GtkFileFolderUnixClass), - NULL, /* base_init */ - NULL, /* base_finalize */ - (GClassInitFunc) gtk_file_folder_unix_class_init, - NULL, /* class_finalize */ - NULL, /* class_data */ - sizeof (GtkFileFolderUnix), - 0, /* n_preallocs */ - (GInstanceInitFunc) gtk_file_folder_unix_init, - }; - - static const GInterfaceInfo file_folder_info = - { - (GInterfaceInitFunc) gtk_file_folder_unix_iface_init, /* interface_init */ - NULL, /* interface_finalize */ - NULL /* interface_data */ - }; - - file_folder_unix_type = g_type_register_static (G_TYPE_OBJECT, - "GtkFileFolderUnix", - &file_folder_unix_info, 0); - g_type_add_interface_static (file_folder_unix_type, - GTK_TYPE_FILE_FOLDER, - &file_folder_info); + gchar *display_name = g_filename_display_basename (filename); + gtk_file_info_set_display_name (info, display_name); + g_free (display_name); } - return file_folder_unix_type; -} + if (types & GTK_FILE_INFO_IS_HIDDEN) + { + if (folder_unix) + { + if (file_is_hidden (folder_unix, basename)) + gtk_file_info_set_is_hidden (info, TRUE); + } + else + { + if (get_is_hidden_for_file (filename, basename)) + gtk_file_info_set_is_hidden (info, TRUE); + } + } -static void -gtk_file_folder_unix_class_init (GtkFileFolderUnixClass *class) -{ - GObjectClass *gobject_class = G_OBJECT_CLASS (class); + if (types & GTK_FILE_INFO_IS_FOLDER) + gtk_file_info_set_is_folder (info, S_ISDIR (statbuf->st_mode)); - folder_parent_class = g_type_class_peek_parent (class); + if (types & GTK_FILE_INFO_MIME_TYPE) + gtk_file_info_set_mime_type (info, mime_type); - gobject_class->finalize = gtk_file_folder_unix_finalize; -} + if (types & GTK_FILE_INFO_MODIFICATION_TIME) + gtk_file_info_set_modification_time (info, statbuf->st_mtime); -static void -gtk_file_folder_unix_iface_init (GtkFileFolderIface *iface) -{ - iface->get_info = gtk_file_folder_unix_get_info; - iface->list_children = gtk_file_folder_unix_list_children; -} + if (types & GTK_FILE_INFO_SIZE) + gtk_file_info_set_size (info, (gint64) statbuf->st_size); -static void -gtk_file_folder_unix_init (GtkFileFolderUnix *impl) -{ + if (types & GTK_FILE_INFO_ICON) + { + IconType icon_type; + gboolean free_icon_name = FALSE; + const char *icon_name; + const char *icon_mime_type; + + icon_type = get_icon_type_from_path (folder_unix, statbuf, filename, &icon_mime_type); + + switch (icon_type) + { + case ICON_NONE: + icon_name = get_fallback_icon_name (icon_type); + break; + + case ICON_REGULAR: + free_icon_name = TRUE; + if (icon_mime_type) + icon_name = get_icon_name_for_mime_type (icon_mime_type); + else + icon_name = get_icon_name_for_mime_type (mime_type); + break; + + default: + icon_name = get_special_icon_name (icon_type, filename); + break; + } + + gtk_file_info_set_icon_name (info, icon_name); + + if (free_icon_name) + g_free ((char *) icon_name); + } + + return info; } -static void -gtk_file_folder_unix_finalize (GObject *object) +static struct stat_info_entry * +create_stat_info_entry_and_emit_add (GtkFileFolderUnix *folder_unix, + const char *filename, + const char *basename, + struct stat *statbuf) { - GtkFileFolderUnix *folder_unix = GTK_FILE_FOLDER_UNIX (object); + GSList *paths; + GtkFilePath *path; + struct stat_info_entry *entry; - g_hash_table_remove (folder_unix->system_unix->folder_hash, folder_unix->filename); + entry = g_new0 (struct stat_info_entry, 1); - g_free (folder_unix->filename); + if ((folder_unix->types & STAT_NEEDED_MASK) != 0) + entry->statbuf = *statbuf; + + if ((folder_unix->types & GTK_FILE_INFO_MIME_TYPE) != 0) + entry->mime_type = g_strdup (xdg_mime_get_mime_type_for_file (filename, statbuf)); + + g_hash_table_insert (folder_unix->stat_info, + g_strdup (basename), + entry); - folder_parent_class->finalize (object); + path = gtk_file_path_new_dup (filename); + paths = g_slist_append (NULL, path); + g_signal_emit_by_name (folder_unix, "files-added", paths); + gtk_file_path_free (path); + g_slist_free (paths); + + return entry; } static GtkFileInfo * -gtk_file_folder_unix_get_info (GtkFileFolder *folder, - const GtkFilePath *path, - GError **error) +gtk_file_folder_unix_get_info (GtkFileFolder *folder, + const GtkFilePath *path, + GError **error) { GtkFileFolderUnix *folder_unix = GTK_FILE_FOLDER_UNIX (folder); GtkFileInfo *info; - gchar *dirname; + gchar *dirname, *basename; const char *filename; + GtkFileInfoType types; + struct stat statbuf; + const char *mime_type; + + /* Get_info for "/" */ + if (!path) + { + g_return_val_if_fail (filename_is_root (folder_unix->filename), NULL); + return file_info_for_root_with_error (folder_unix->filename, error); + } + + /* Get_info for normal files */ filename = gtk_file_path_get_string (path); g_return_val_if_fail (filename != NULL, NULL); g_return_val_if_fail (g_path_is_absolute (filename), NULL); - dirname = g_path_get_dirname (filename); + dirname = get_parent_dir (filename); g_return_val_if_fail (strcmp (dirname, folder_unix->filename) == 0, NULL); g_free (dirname); - info = filename_get_info (filename, folder_unix->types, error); + basename = g_path_get_basename (filename); + types = folder_unix->types; - return info; + if (folder_unix->have_stat) + { + struct stat_info_entry *entry; + + g_assert (folder_unix->stat_info != NULL); + entry = g_hash_table_lookup (folder_unix->stat_info, basename); + + if (!entry) + { + if (!stat_with_error (filename, &statbuf, error)) + { + g_free (basename); + return NULL; + } + + entry = create_stat_info_entry_and_emit_add (folder_unix, filename, basename, &statbuf); + } + + info = create_file_info (folder_unix, filename, basename, types, &entry->statbuf, entry->mime_type); + g_free (basename); + return info; + } + else + { + if (!stat_with_error (filename, &statbuf, error)) + { + g_free (basename); + return NULL; + } + + if ((types & GTK_FILE_INFO_MIME_TYPE) != 0) + mime_type = xdg_mime_get_mime_type_for_file (filename, &statbuf); + else + mime_type = NULL; + + info = create_file_info (folder_unix, filename, basename, types, &statbuf, mime_type); + g_free (basename); + return info; + } +} + + +static void +cb_list_children (gpointer key, gpointer value, gpointer user_data) +{ + GSList **children = user_data; + *children = g_slist_prepend (*children, key); } static gboolean @@ -1336,138 +2446,219 @@ gtk_file_folder_unix_list_children (GtkFileFolder *folder, GError **error) { GtkFileFolderUnix *folder_unix = GTK_FILE_FOLDER_UNIX (folder); - GError *tmp_error = NULL; - GDir *dir; + GSList *l; *children = NULL; - dir = g_dir_open (folder_unix->filename, 0, &tmp_error); - if (!dir) + /* Get the list of basenames. */ + if (folder_unix->stat_info) + g_hash_table_foreach (folder_unix->stat_info, cb_list_children, children); + + /* Turn basenames into GFilePaths. */ + for (l = *children; l; l = l->next) { - g_set_error (error, - GTK_FILE_SYSTEM_ERROR, - GTK_FILE_SYSTEM_ERROR_NONEXISTENT, - "%s", - tmp_error->message); + const char *basename = l->data; + char *fullname = g_build_filename (folder_unix->filename, basename, NULL); + l->data = filename_to_path (fullname); + g_free (fullname); + } - g_error_free (tmp_error); + return TRUE; +} - return FALSE; - } +static gboolean +gtk_file_folder_unix_is_finished_loading (GtkFileFolder *folder) +{ + return GTK_FILE_FOLDER_UNIX (folder)->is_finished_loading; +} + +static void +free_stat_info_entry (struct stat_info_entry *entry) +{ + g_free (entry->mime_type); + g_free (entry); +} + +static gboolean +fill_in_names (GtkFileFolderUnix *folder_unix, GError **error) +{ + GDir *dir; + + if (folder_unix->stat_info) + return TRUE; + + dir = g_dir_open (folder_unix->filename, 0, error); + if (!dir) + return FALSE; + + folder_unix->stat_info = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) free_stat_info_entry); while (TRUE) { - const gchar *filename = g_dir_read_name (dir); - gchar *fullname; + struct stat_info_entry *entry; + const gchar *basename; - if (!filename) + basename = g_dir_read_name (dir); + if (!basename) break; - fullname = g_build_filename (folder_unix->filename, filename, NULL); - *children = g_slist_prepend (*children, filename_to_path (fullname)); - g_free (fullname); + entry = g_new0 (struct stat_info_entry, 1); + if (folder_unix->is_network_dir) + { + entry->statbuf.st_mode = S_IFDIR; + entry->mime_type = g_strdup ("x-directory/normal"); + } + + g_hash_table_insert (folder_unix->stat_info, + g_strdup (basename), + entry); } g_dir_close (dir); - *children = g_slist_reverse (*children); - + folder_unix->asof = time (NULL); return TRUE; } -static GtkFileInfo * -filename_get_info (const gchar *filename, - GtkFileInfoType types, - GError **error) +static gboolean +cb_fill_in_stats (gpointer key, gpointer value, gpointer user_data) { - GtkFileInfo *info; - struct stat statbuf; - gboolean do_stat = (types & (GTK_FILE_INFO_IS_FOLDER | - GTK_FILE_INFO_IS_HIDDEN | - GTK_FILE_INFO_MODIFICATION_TIME | - GTK_FILE_INFO_SIZE)); + const char *basename = key; + struct stat_info_entry *entry = value; + GtkFileFolderUnix *folder_unix = user_data; + char *fullname = g_build_filename (folder_unix->filename, basename, NULL); + gboolean result; - /* If stat fails, try to fall back to lstat to catch broken links - */ - if (do_stat && stat (filename, &statbuf) != 0) - { - if (errno != ENOENT || lstat (filename, &statbuf) != 0) - { - int save_errno = errno; - gchar *filename_utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL); - g_set_error (error, - GTK_FILE_SYSTEM_ERROR, - GTK_FILE_SYSTEM_ERROR_NONEXISTENT, - _("error getting information for '%s': %s"), - filename_utf8 ? filename_utf8 : "???", - g_strerror (save_errno)); - g_free (filename_utf8); + if (stat (fullname, &entry->statbuf) == -1 && + (errno != ENOENT || lstat (fullname, &entry->statbuf) == -1)) + result = TRUE; /* Couldn't stat -- remove from hash. */ + else + result = FALSE; - return NULL; - } - } + g_free (fullname); + return result; +} - info = gtk_file_info_new (); - if (filename_is_root (filename)) - { - if (types & GTK_FILE_INFO_DISPLAY_NAME) - gtk_file_info_set_display_name (info, "/"); +static void +fill_in_stats (GtkFileFolderUnix *folder_unix) +{ + if (folder_unix->have_stat) + return; - if (types & GTK_FILE_INFO_IS_HIDDEN) - gtk_file_info_set_is_hidden (info, FALSE); - } - else - { - gchar *basename = g_path_get_basename (filename); + if (!fill_in_names (folder_unix, NULL)) + return; - if (types & GTK_FILE_INFO_DISPLAY_NAME) - { - gchar *display_name = g_filename_to_utf8 (basename, -1, NULL, NULL, NULL); - if (!display_name) - display_name = g_strescape (basename, NULL); + if (!folder_unix->is_network_dir) + g_hash_table_foreach_remove (folder_unix->stat_info, + cb_fill_in_stats, + folder_unix); - gtk_file_info_set_display_name (info, display_name); + folder_unix->have_stat = TRUE; +} - g_free (display_name); - } - if (types & GTK_FILE_INFO_IS_HIDDEN) - { - gtk_file_info_set_is_hidden (info, basename[0] == '.'); - } +static gboolean +cb_fill_in_mime_type (gpointer key, gpointer value, gpointer user_data) +{ + const char *basename = key; + struct stat_info_entry *entry = value; + GtkFileFolderUnix *folder_unix = user_data; + char *fullname = g_build_filename (folder_unix->filename, basename, NULL); + struct stat *statbuf = NULL; + const char *mime_type; - g_free (basename); - } + if (folder_unix->have_stat) + statbuf = &entry->statbuf; - if (types & GTK_FILE_INFO_IS_FOLDER) - { - gtk_file_info_set_is_folder (info, S_ISDIR (statbuf.st_mode)); - } + mime_type = xdg_mime_get_mime_type_for_file (fullname, statbuf); + entry->mime_type = g_strdup (mime_type); - if (types & GTK_FILE_INFO_MIME_TYPE) - { - const char *mime_type = xdg_mime_get_mime_type_for_file (filename); - gtk_file_info_set_mime_type (info, mime_type); - } + g_free (fullname); - if (types & GTK_FILE_INFO_MODIFICATION_TIME) + return FALSE; +} + +static void +fill_in_mime_type (GtkFileFolderUnix *folder_unix) +{ + if (folder_unix->have_mime_type) + return; + + if (!folder_unix->have_stat) + return; + + g_assert (folder_unix->stat_info != NULL); + + if (!folder_unix->is_network_dir) + g_hash_table_foreach_remove (folder_unix->stat_info, + cb_fill_in_mime_type, + folder_unix); + + folder_unix->have_mime_type = TRUE; +} + +static gchar ** +read_hidden_file (const char *dirname) +{ + gchar **lines = NULL; + gchar *contents; + gchar *hidden_file; + + hidden_file = g_build_filename (dirname, HIDDEN_FILENAME, NULL); + + if (g_file_get_contents (hidden_file, &contents, NULL, NULL)) { - gtk_file_info_set_modification_time (info, statbuf.st_mtime); + lines = g_strsplit (contents, "\n", -1); + g_free (contents); } - if (types & GTK_FILE_INFO_SIZE) + g_free (hidden_file); + + return lines; +} + +static void +fill_in_hidden (GtkFileFolderUnix *folder_unix) +{ + gchar **lines; + + if (folder_unix->have_hidden) + return; + + lines = read_hidden_file (folder_unix->filename); + + if (lines) { - gtk_file_info_set_size (info, (gint64)statbuf.st_size); + int i; + + for (i = 0; lines[i]; i++) + { + if (lines[i][0]) + { + struct stat_info_entry *entry; + + entry = g_hash_table_lookup (folder_unix->stat_info, lines[i]); + if (entry != NULL) + entry->hidden = TRUE; + } + } + + g_strfreev (lines); } - return info; + folder_unix->have_hidden = TRUE; } static GtkFilePath * filename_to_path (const char *filename) { - return gtk_file_path_new_dup (filename); + char *tmp; + + tmp = remove_trailing_slash (filename); + return gtk_file_path_new_steal (tmp); } static gboolean @@ -1479,3 +2670,59 @@ filename_is_root (const char *filename) return (after_root != NULL && *after_root == '\0'); } + +static gboolean +get_is_hidden_for_file (const char *filename, + const char *basename) +{ + gchar *dirname; + gchar **lines; + gboolean hidden = FALSE; + + dirname = g_path_get_dirname (filename); + lines = read_hidden_file (dirname); + g_free (dirname); + + if (lines) + { + int i; + + for (i = 0; lines[i]; i++) + { + if (lines[i][0] && strcmp (lines[i], basename) == 0) + { + hidden = TRUE; + break; + } + } + + g_strfreev (lines); + } + + return hidden; +} + +static gboolean +file_is_hidden (GtkFileFolderUnix *folder_unix, + const char *basename) +{ + struct stat_info_entry *entry; + + if (basename[0] == '.' || basename[strlen (basename) - 1] == '~') + return TRUE; + + if (folder_unix->have_stat) + { + fill_in_hidden (folder_unix); + + entry = g_hash_table_lookup (folder_unix->stat_info, basename); + + if (entry) + return entry->hidden; + } + + return FALSE; +} + +#define __GTK_FILE_SYSTEM_UNIX_C__ +#include "gtkaliasdef.c"