926 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			926 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* GTK - The GIMP Toolkit
 | ||
|  * gtkfilesystem.c: Filesystem abstraction functions.
 | ||
|  * Copyright (C) 2003, Red Hat, Inc.
 | ||
|  * Copyright (C) 2007-2008 Carlos Garnacho
 | ||
|  *
 | ||
|  * This library is free software; you can redistribute it and/or modify
 | ||
|  * it under the terms of the GNU Lesser General Public License as
 | ||
|  * published by the Free Software Foundation; either version 2 of the
 | ||
|  * License, or (at your option) any later version.
 | ||
|  *
 | ||
|  * This library is distributed in the hope that it will be useful,
 | ||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||
|  * GNU Lesser General Public License for more details.
 | ||
|  *
 | ||
|  * You should have received a copy of the GNU Lesser General Public
 | ||
|  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 | ||
|  *
 | ||
|  * Authors: Carlos Garnacho <carlos@imendio.com>
 | ||
|  */
 | ||
| 
 | ||
| #include "config.h"
 | ||
| 
 | ||
| #include <string.h>
 | ||
| 
 | ||
| #include <glib/gi18n-lib.h>
 | ||
| 
 | ||
| #include "gtkfilechooser.h"
 | ||
| #include "gtkfilesystem.h"
 | ||
| #include "gtkicontheme.h"
 | ||
| #include "gtkprivate.h"
 | ||
| 
 | ||
| /* #define DEBUG_MODE */
 | ||
| #ifdef DEBUG_MODE
 | ||
| #define DEBUG(x) g_debug (x);
 | ||
| #else
 | ||
| #define DEBUG(x)
 | ||
| #endif
 | ||
| 
 | ||
| #define FILES_PER_QUERY 100
 | ||
| 
 | ||
| /* The pointers we return for a GtkFileSystemVolume are opaque tokens; they are
 | ||
|  * really pointers to GDrive, GVolume or GMount objects.  We need an extra
 | ||
|  * token for the fake “File System” volume.  So, we’ll return a pointer to
 | ||
|  * this particular string.
 | ||
|  */
 | ||
| static const gchar *root_volume_token = N_("File System");
 | ||
| #define IS_ROOT_VOLUME(volume) ((gpointer) (volume) == (gpointer) root_volume_token)
 | ||
| 
 | ||
| enum {
 | ||
|   PROP_0,
 | ||
|   PROP_FILE,
 | ||
|   PROP_ENUMERATOR,
 | ||
|   PROP_ATTRIBUTES
 | ||
| };
 | ||
| 
 | ||
| enum {
 | ||
|   VOLUMES_CHANGED,
 | ||
|   FS_LAST_SIGNAL
 | ||
| };
 | ||
| 
 | ||
| enum {
 | ||
|   FILES_ADDED,
 | ||
|   FILES_REMOVED,
 | ||
|   FILES_CHANGED,
 | ||
|   FINISHED_LOADING,
 | ||
|   DELETED,
 | ||
|   FOLDER_LAST_SIGNAL
 | ||
| };
 | ||
| 
 | ||
| static guint fs_signals [FS_LAST_SIGNAL] = { 0, };
 | ||
| 
 | ||
| typedef struct AsyncFuncData AsyncFuncData;
 | ||
| 
 | ||
| struct GtkFileSystemPrivate
 | ||
| {
 | ||
|   GVolumeMonitor *volume_monitor;
 | ||
| 
 | ||
|   /* This list contains elements that can be
 | ||
|    * of type GDrive, GVolume and GMount
 | ||
|    */
 | ||
|   GSList *volumes;
 | ||
| };
 | ||
| 
 | ||
| struct AsyncFuncData
 | ||
| {
 | ||
|   GtkFileSystem *file_system;
 | ||
|   GFile *file;
 | ||
|   GCancellable *cancellable;
 | ||
|   gchar *attributes;
 | ||
| 
 | ||
|   gpointer callback;
 | ||
|   gpointer data;
 | ||
| };
 | ||
| 
 | ||
| G_DEFINE_TYPE_WITH_PRIVATE (GtkFileSystem, _gtk_file_system, G_TYPE_OBJECT)
 | ||
| 
 | ||
| 
 | ||
| /* GtkFileSystem methods */
 | ||
| static void
 | ||
| volumes_changed (GVolumeMonitor *volume_monitor,
 | ||
| 		 gpointer        volume,
 | ||
| 		 gpointer        user_data)
 | ||
| {
 | ||
|   GtkFileSystem *file_system;
 | ||
| 
 | ||
|   gdk_threads_enter ();
 | ||
| 
 | ||
|   file_system = GTK_FILE_SYSTEM (user_data);
 | ||
|   g_signal_emit (file_system, fs_signals[VOLUMES_CHANGED], 0, volume);
 | ||
|   gdk_threads_leave ();
 | ||
| }
 | ||
| 
 | ||
| static void
 | ||
| gtk_file_system_dispose (GObject *object)
 | ||
| {
 | ||
|   GtkFileSystem *file_system = GTK_FILE_SYSTEM (object);
 | ||
|   GtkFileSystemPrivate *priv = file_system->priv;
 | ||
| 
 | ||
|   DEBUG ("dispose");
 | ||
| 
 | ||
|   if (priv->volumes)
 | ||
|     {
 | ||
|       g_slist_foreach (priv->volumes, (GFunc) g_object_unref, NULL);
 | ||
|       g_slist_free (priv->volumes);
 | ||
|       priv->volumes = NULL;
 | ||
|     }
 | ||
| 
 | ||
|   if (priv->volume_monitor)
 | ||
|     {
 | ||
|       g_signal_handlers_disconnect_by_func (priv->volume_monitor, volumes_changed, object);
 | ||
|       g_object_unref (priv->volume_monitor);
 | ||
|       priv->volume_monitor = NULL;
 | ||
|     }
 | ||
| 
 | ||
|   G_OBJECT_CLASS (_gtk_file_system_parent_class)->dispose (object);
 | ||
| }
 | ||
| 
 | ||
| static void
 | ||
| _gtk_file_system_class_init (GtkFileSystemClass *class)
 | ||
| {
 | ||
|   GObjectClass *object_class = G_OBJECT_CLASS (class);
 | ||
| 
 | ||
|   object_class->dispose = gtk_file_system_dispose;
 | ||
| 
 | ||
|   fs_signals[VOLUMES_CHANGED] =
 | ||
|     g_signal_new ("volumes-changed",
 | ||
| 		  G_TYPE_FROM_CLASS (object_class),
 | ||
| 		  G_SIGNAL_RUN_LAST,
 | ||
| 		  G_STRUCT_OFFSET (GtkFileSystemClass, volumes_changed),
 | ||
| 		  NULL, NULL,
 | ||
| 		  g_cclosure_marshal_VOID__VOID,
 | ||
| 		  G_TYPE_NONE, 0);
 | ||
| }
 | ||
| 
 | ||
| static gboolean
 | ||
| mount_referenced_by_volume_activation_root (GList *volumes, GMount *mount)
 | ||
| {
 | ||
|   GList *l;
 | ||
|   GFile *mount_root;
 | ||
|   gboolean ret;
 | ||
| 
 | ||
|   ret = FALSE;
 | ||
| 
 | ||
|   mount_root = g_mount_get_root (mount);
 | ||
| 
 | ||
|   for (l = volumes; l != NULL; l = l->next)
 | ||
|     {
 | ||
|       GVolume *volume = G_VOLUME (l->data);
 | ||
|       GFile *volume_activation_root;
 | ||
| 
 | ||
|       volume_activation_root = g_volume_get_activation_root (volume);
 | ||
|       if (volume_activation_root != NULL)
 | ||
|         {
 | ||
|           if (g_file_has_prefix (volume_activation_root, mount_root))
 | ||
|             {
 | ||
|               ret = TRUE;
 | ||
|               g_object_unref (volume_activation_root);
 | ||
|               break;
 | ||
|             }
 | ||
|           g_object_unref (volume_activation_root);
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|   g_object_unref (mount_root);
 | ||
|   return ret;
 | ||
| }
 | ||
| 
 | ||
| static void
 | ||
| get_volumes_list (GtkFileSystem *file_system)
 | ||
| {
 | ||
|   GtkFileSystemPrivate *priv = file_system->priv;
 | ||
|   GList *l, *ll;
 | ||
|   GList *drives;
 | ||
|   GList *volumes;
 | ||
|   GList *mounts;
 | ||
|   GDrive *drive;
 | ||
|   GVolume *volume;
 | ||
|   GMount *mount;
 | ||
| 
 | ||
|   if (priv->volumes)
 | ||
|     {
 | ||
|       g_slist_foreach (priv->volumes, (GFunc) g_object_unref, NULL);
 | ||
|       g_slist_free (priv->volumes);
 | ||
|       priv->volumes = NULL;
 | ||
|     }
 | ||
| 
 | ||
|   /* first go through all connected drives */
 | ||
|   drives = g_volume_monitor_get_connected_drives (priv->volume_monitor);
 | ||
| 
 | ||
|   for (l = drives; l != NULL; l = l->next)
 | ||
|     {
 | ||
|       drive = l->data;
 | ||
|       volumes = g_drive_get_volumes (drive);
 | ||
| 
 | ||
|       if (volumes)
 | ||
|         {
 | ||
|           for (ll = volumes; ll != NULL; ll = ll->next)
 | ||
|             {
 | ||
|               volume = ll->data;
 | ||
|               mount = g_volume_get_mount (volume);
 | ||
| 
 | ||
|               if (mount)
 | ||
|                 {
 | ||
|                   /* Show mounted volume */
 | ||
|                   priv->volumes = g_slist_prepend (priv->volumes, g_object_ref (mount));
 | ||
|                   g_object_unref (mount);
 | ||
|                 }
 | ||
|               else
 | ||
|                 {
 | ||
|                   /* Do show the unmounted volumes in the sidebar;
 | ||
|                    * this is so the user can mount it (in case automounting
 | ||
|                    * is off).
 | ||
|                    *
 | ||
|                    * Also, even if automounting is enabled, this gives a visual
 | ||
|                    * cue that the user should remember to yank out the media if
 | ||
|                    * he just unmounted it.
 | ||
|                    */
 | ||
|                   priv->volumes = g_slist_prepend (priv->volumes, g_object_ref (volume));
 | ||
|                 }
 | ||
| 
 | ||
| 	      g_object_unref (volume);
 | ||
|             }
 | ||
|   
 | ||
|            g_list_free (volumes);
 | ||
|         }
 | ||
|       else if (g_drive_is_media_removable (drive) && !g_drive_is_media_check_automatic (drive))
 | ||
| 	{
 | ||
| 	  /* If the drive has no mountable volumes and we cannot detect media change.. we
 | ||
| 	   * display the drive in the sidebar so the user can manually poll the drive by
 | ||
| 	   * right clicking and selecting "Rescan..."
 | ||
| 	   *
 | ||
| 	   * This is mainly for drives like floppies where media detection doesn't
 | ||
| 	   * work.. but it's also for human beings who like to turn off media detection
 | ||
| 	   * in the OS to save battery juice.
 | ||
| 	   */
 | ||
| 
 | ||
| 	  priv->volumes = g_slist_prepend (priv->volumes, g_object_ref (drive));
 | ||
| 	}
 | ||
| 
 | ||
|       g_object_unref (drive);
 | ||
|     }
 | ||
| 
 | ||
|   g_list_free (drives);
 | ||
| 
 | ||
|   /* add all volumes that is not associated with a drive */
 | ||
|   volumes = g_volume_monitor_get_volumes (priv->volume_monitor);
 | ||
| 
 | ||
|   for (l = volumes; l != NULL; l = l->next)
 | ||
|     {
 | ||
|       volume = l->data;
 | ||
|       drive = g_volume_get_drive (volume);
 | ||
| 
 | ||
|       if (drive)
 | ||
|         {
 | ||
|           g_object_unref (drive);
 | ||
|           continue;
 | ||
|         }
 | ||
| 
 | ||
|       mount = g_volume_get_mount (volume);
 | ||
| 
 | ||
|       if (mount)
 | ||
|         {
 | ||
|           /* show this mount */
 | ||
|           priv->volumes = g_slist_prepend (priv->volumes, g_object_ref (mount));
 | ||
|           g_object_unref (mount);
 | ||
|         }
 | ||
|       else
 | ||
|         {
 | ||
|           /* see comment above in why we add an icon for a volume */
 | ||
|           priv->volumes = g_slist_prepend (priv->volumes, g_object_ref (volume));
 | ||
|         }
 | ||
| 
 | ||
|       g_object_unref (volume);
 | ||
|     }
 | ||
| 
 | ||
|   /* add mounts that has no volume (/etc/mtab mounts, ftp, sftp,...) */
 | ||
|   mounts = g_volume_monitor_get_mounts (priv->volume_monitor);
 | ||
| 
 | ||
|   for (l = mounts; l != NULL; l = l->next)
 | ||
|     {
 | ||
|       mount = l->data;
 | ||
|       volume = g_mount_get_volume (mount);
 | ||
| 
 | ||
|       if (volume)
 | ||
|         {
 | ||
|           g_object_unref (volume);
 | ||
|           continue;
 | ||
|         }
 | ||
| 
 | ||
|       /* if there's exists one or more volumes with an activation root inside the mount,
 | ||
|        * don't display the mount
 | ||
|        */
 | ||
|       if (mount_referenced_by_volume_activation_root (volumes, mount))
 | ||
|         {
 | ||
|           g_object_unref (mount);
 | ||
|           continue;
 | ||
|         }
 | ||
| 
 | ||
|       /* show this mount */
 | ||
|       priv->volumes = g_slist_prepend (priv->volumes, g_object_ref (mount));
 | ||
|       g_object_unref (mount);
 | ||
|     }
 | ||
| 
 | ||
|   g_list_free (volumes);
 | ||
| 
 | ||
|   g_list_free (mounts);
 | ||
| }
 | ||
| 
 | ||
| static void
 | ||
| _gtk_file_system_init (GtkFileSystem *file_system)
 | ||
| {
 | ||
|   GtkFileSystemPrivate *priv;
 | ||
| 
 | ||
|   DEBUG ("init");
 | ||
| 
 | ||
|   file_system->priv = priv = _gtk_file_system_get_instance_private (file_system);
 | ||
| 
 | ||
|   /* Volumes */
 | ||
|   priv->volume_monitor = g_volume_monitor_get ();
 | ||
| 
 | ||
|   g_signal_connect (priv->volume_monitor, "mount-added",
 | ||
| 		    G_CALLBACK (volumes_changed), file_system);
 | ||
|   g_signal_connect (priv->volume_monitor, "mount-removed",
 | ||
| 		    G_CALLBACK (volumes_changed), file_system);
 | ||
|   g_signal_connect (priv->volume_monitor, "mount-changed",
 | ||
| 		    G_CALLBACK (volumes_changed), file_system);
 | ||
|   g_signal_connect (priv->volume_monitor, "volume-added",
 | ||
| 		    G_CALLBACK (volumes_changed), file_system);
 | ||
|   g_signal_connect (priv->volume_monitor, "volume-removed",
 | ||
| 		    G_CALLBACK (volumes_changed), file_system);
 | ||
|   g_signal_connect (priv->volume_monitor, "volume-changed",
 | ||
| 		    G_CALLBACK (volumes_changed), file_system);
 | ||
|   g_signal_connect (priv->volume_monitor, "drive-connected",
 | ||
| 		    G_CALLBACK (volumes_changed), file_system);
 | ||
|   g_signal_connect (priv->volume_monitor, "drive-disconnected",
 | ||
| 		    G_CALLBACK (volumes_changed), file_system);
 | ||
|   g_signal_connect (priv->volume_monitor, "drive-changed",
 | ||
| 		    G_CALLBACK (volumes_changed), file_system);
 | ||
| }
 | ||
| 
 | ||
| /* GtkFileSystem public methods */
 | ||
| GtkFileSystem *
 | ||
| _gtk_file_system_new (void)
 | ||
| {
 | ||
|   return g_object_new (GTK_TYPE_FILE_SYSTEM, NULL);
 | ||
| }
 | ||
| 
 | ||
| GSList *
 | ||
| _gtk_file_system_list_volumes (GtkFileSystem *file_system)
 | ||
| {
 | ||
|   GtkFileSystemPrivate *priv = file_system->priv;
 | ||
|   GSList *list;
 | ||
| 
 | ||
|   DEBUG ("list_volumes");
 | ||
| 
 | ||
|   get_volumes_list (file_system);
 | ||
| 
 | ||
|   list = g_slist_copy (priv->volumes);
 | ||
| 
 | ||
| #ifndef G_OS_WIN32
 | ||
|   /* Prepend root volume */
 | ||
|   list = g_slist_prepend (list, (gpointer) root_volume_token);
 | ||
| #endif
 | ||
| 
 | ||
|   return list;
 | ||
| }
 | ||
| 
 | ||
| static void
 | ||
| free_async_data (AsyncFuncData *async_data)
 | ||
| {
 | ||
|   g_object_unref (async_data->file_system);
 | ||
|   g_object_unref (async_data->file);
 | ||
|   g_object_unref (async_data->cancellable);
 | ||
| 
 | ||
|   g_free (async_data->attributes);
 | ||
|   g_free (async_data);
 | ||
| }
 | ||
| 
 | ||
| static void
 | ||
| query_info_callback (GObject      *source_object,
 | ||
| 		     GAsyncResult *result,
 | ||
| 		     gpointer      user_data)
 | ||
| {
 | ||
|   AsyncFuncData *async_data;
 | ||
|   GError *error = NULL;
 | ||
|   GFileInfo *file_info;
 | ||
|   GFile *file;
 | ||
| 
 | ||
|   DEBUG ("query_info_callback");
 | ||
| 
 | ||
|   file = G_FILE (source_object);
 | ||
|   async_data = (AsyncFuncData *) user_data;
 | ||
|   file_info = g_file_query_info_finish (file, result, &error);
 | ||
| 
 | ||
|   if (async_data->callback)
 | ||
|     {
 | ||
|       gdk_threads_enter ();
 | ||
|       ((GtkFileSystemGetInfoCallback) async_data->callback) (async_data->cancellable,
 | ||
| 							     file_info, error, async_data->data);
 | ||
|       gdk_threads_leave ();
 | ||
|     }
 | ||
| 
 | ||
|   if (file_info)
 | ||
|     g_object_unref (file_info);
 | ||
| 
 | ||
|   if (error)
 | ||
|     g_error_free (error);
 | ||
| 
 | ||
|   free_async_data (async_data);
 | ||
| }
 | ||
| 
 | ||
| GCancellable *
 | ||
| _gtk_file_system_get_info (GtkFileSystem                *file_system,
 | ||
| 			   GFile                        *file,
 | ||
| 			   const gchar                  *attributes,
 | ||
| 			   GtkFileSystemGetInfoCallback  callback,
 | ||
| 			   gpointer                      data)
 | ||
| {
 | ||
|   GCancellable *cancellable;
 | ||
|   AsyncFuncData *async_data;
 | ||
| 
 | ||
|   g_return_val_if_fail (GTK_IS_FILE_SYSTEM (file_system), NULL);
 | ||
|   g_return_val_if_fail (G_IS_FILE (file), NULL);
 | ||
| 
 | ||
|   cancellable = g_cancellable_new ();
 | ||
| 
 | ||
|   async_data = g_new0 (AsyncFuncData, 1);
 | ||
|   async_data->file_system = g_object_ref (file_system);
 | ||
|   async_data->file = g_object_ref (file);
 | ||
|   async_data->cancellable = g_object_ref (cancellable);
 | ||
| 
 | ||
|   async_data->callback = callback;
 | ||
|   async_data->data = data;
 | ||
| 
 | ||
|   g_file_query_info_async (file,
 | ||
| 			   attributes,
 | ||
| 			   G_FILE_QUERY_INFO_NONE,
 | ||
| 			   G_PRIORITY_DEFAULT,
 | ||
| 			   cancellable,
 | ||
| 			   query_info_callback,
 | ||
| 			   async_data);
 | ||
| 
 | ||
|   return cancellable;
 | ||
| }
 | ||
| 
 | ||
| static void
 | ||
| drive_poll_for_media_cb (GObject      *source_object,
 | ||
|                          GAsyncResult *result,
 | ||
|                          gpointer      user_data)
 | ||
| {
 | ||
|   AsyncFuncData *async_data;
 | ||
|   GError *error = NULL;
 | ||
| 
 | ||
|   g_drive_poll_for_media_finish (G_DRIVE (source_object), result, &error);
 | ||
|   async_data = (AsyncFuncData *) user_data;
 | ||
| 
 | ||
|   gdk_threads_enter ();
 | ||
|   ((GtkFileSystemVolumeMountCallback) async_data->callback) (async_data->cancellable,
 | ||
| 							     (GtkFileSystemVolume *) source_object,
 | ||
| 							     error, async_data->data);
 | ||
|   gdk_threads_leave ();
 | ||
| 
 | ||
|   if (error)
 | ||
|     g_error_free (error);
 | ||
| }
 | ||
| 
 | ||
| static void
 | ||
| volume_mount_cb (GObject      *source_object,
 | ||
| 		 GAsyncResult *result,
 | ||
| 		 gpointer      user_data)
 | ||
| {
 | ||
|   AsyncFuncData *async_data;
 | ||
|   GError *error = NULL;
 | ||
| 
 | ||
|   g_volume_mount_finish (G_VOLUME (source_object), result, &error);
 | ||
|   async_data = (AsyncFuncData *) user_data;
 | ||
| 
 | ||
|   gdk_threads_enter ();
 | ||
|   ((GtkFileSystemVolumeMountCallback) async_data->callback) (async_data->cancellable,
 | ||
| 							     (GtkFileSystemVolume *) source_object,
 | ||
| 							     error, async_data->data);
 | ||
|   gdk_threads_leave ();
 | ||
| 
 | ||
|   if (error)
 | ||
|     g_error_free (error);
 | ||
| }
 | ||
| 
 | ||
| GCancellable *
 | ||
| _gtk_file_system_mount_volume (GtkFileSystem                    *file_system,
 | ||
| 			       GtkFileSystemVolume              *volume,
 | ||
| 			       GMountOperation                  *mount_operation,
 | ||
| 			       GtkFileSystemVolumeMountCallback  callback,
 | ||
| 			       gpointer                          data)
 | ||
| {
 | ||
|   GCancellable *cancellable;
 | ||
|   AsyncFuncData *async_data;
 | ||
|   gboolean handled = FALSE;
 | ||
| 
 | ||
|   DEBUG ("volume_mount");
 | ||
| 
 | ||
|   cancellable = g_cancellable_new ();
 | ||
| 
 | ||
|   async_data = g_new0 (AsyncFuncData, 1);
 | ||
|   async_data->file_system = g_object_ref (file_system);
 | ||
|   async_data->cancellable = g_object_ref (cancellable);
 | ||
| 
 | ||
|   async_data->callback = callback;
 | ||
|   async_data->data = data;
 | ||
| 
 | ||
|   if (G_IS_DRIVE (volume))
 | ||
|     {
 | ||
|       /* this path happens for drives that are not polled by the OS and where the last media
 | ||
|        * check indicated that no media was available. So the thing to do here is to
 | ||
|        * invoke poll_for_media() on the drive
 | ||
|        */
 | ||
|       g_drive_poll_for_media (G_DRIVE (volume), cancellable, drive_poll_for_media_cb, async_data);
 | ||
|       handled = TRUE;
 | ||
|     }
 | ||
|   else if (G_IS_VOLUME (volume))
 | ||
|     {
 | ||
|       g_volume_mount (G_VOLUME (volume), G_MOUNT_MOUNT_NONE, mount_operation, cancellable, volume_mount_cb, async_data);
 | ||
|       handled = TRUE;
 | ||
|     }
 | ||
| 
 | ||
|   if (!handled)
 | ||
|     free_async_data (async_data);
 | ||
| 
 | ||
|   return cancellable;
 | ||
| }
 | ||
| 
 | ||
| static void
 | ||
| enclosing_volume_mount_cb (GObject      *source_object,
 | ||
| 			   GAsyncResult *result,
 | ||
| 			   gpointer      user_data)
 | ||
| {
 | ||
|   GtkFileSystemVolume *volume;
 | ||
|   AsyncFuncData *async_data;
 | ||
|   GError *error = NULL;
 | ||
| 
 | ||
|   async_data = (AsyncFuncData *) user_data;
 | ||
|   g_file_mount_enclosing_volume_finish (G_FILE (source_object), result, &error);
 | ||
|   volume = _gtk_file_system_get_volume_for_file (async_data->file_system, G_FILE (source_object));
 | ||
| 
 | ||
|   /* Silently drop G_IO_ERROR_ALREADY_MOUNTED error for gvfs backends without visible mounts. */
 | ||
|   /* Better than doing query_info with additional I/O every time. */
 | ||
|   if (error && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_ALREADY_MOUNTED))
 | ||
|     g_clear_error (&error);
 | ||
| 
 | ||
|   gdk_threads_enter ();
 | ||
|   ((GtkFileSystemVolumeMountCallback) async_data->callback) (async_data->cancellable, volume,
 | ||
| 							     error, async_data->data);
 | ||
|   gdk_threads_leave ();
 | ||
| 
 | ||
|   if (error)
 | ||
|     g_error_free (error);
 | ||
| 
 | ||
|   _gtk_file_system_volume_unref (volume);
 | ||
| }
 | ||
| 
 | ||
| GCancellable *
 | ||
| _gtk_file_system_mount_enclosing_volume (GtkFileSystem                     *file_system,
 | ||
| 					 GFile                             *file,
 | ||
| 					 GMountOperation                   *mount_operation,
 | ||
| 					 GtkFileSystemVolumeMountCallback   callback,
 | ||
| 					 gpointer                           data)
 | ||
| {
 | ||
|   GCancellable *cancellable;
 | ||
|   AsyncFuncData *async_data;
 | ||
| 
 | ||
|   g_return_val_if_fail (GTK_IS_FILE_SYSTEM (file_system), NULL);
 | ||
|   g_return_val_if_fail (G_IS_FILE (file), NULL);
 | ||
| 
 | ||
|   DEBUG ("mount_enclosing_volume");
 | ||
| 
 | ||
|   cancellable = g_cancellable_new ();
 | ||
| 
 | ||
|   async_data = g_new0 (AsyncFuncData, 1);
 | ||
|   async_data->file_system = g_object_ref (file_system);
 | ||
|   async_data->file = g_object_ref (file);
 | ||
|   async_data->cancellable = g_object_ref (cancellable);
 | ||
| 
 | ||
|   async_data->callback = callback;
 | ||
|   async_data->data = data;
 | ||
| 
 | ||
|   g_file_mount_enclosing_volume (file,
 | ||
| 				 G_MOUNT_MOUNT_NONE,
 | ||
| 				 mount_operation,
 | ||
| 				 cancellable,
 | ||
| 				 enclosing_volume_mount_cb,
 | ||
| 				 async_data);
 | ||
|   return cancellable;
 | ||
| }
 | ||
| 
 | ||
| GtkFileSystemVolume *
 | ||
| _gtk_file_system_get_volume_for_file (GtkFileSystem *file_system,
 | ||
| 				      GFile         *file)
 | ||
| {
 | ||
|   GMount *mount;
 | ||
| 
 | ||
|   DEBUG ("get_volume_for_file");
 | ||
| 
 | ||
|   mount = g_file_find_enclosing_mount (file, NULL, NULL);
 | ||
| 
 | ||
|   if (!mount && g_file_is_native (file))
 | ||
|     return (GtkFileSystemVolume *) root_volume_token;
 | ||
| 
 | ||
|   return (GtkFileSystemVolume *) mount;
 | ||
| }
 | ||
| 
 | ||
| /* GtkFileSystemVolume public methods */
 | ||
| gchar *
 | ||
| _gtk_file_system_volume_get_display_name (GtkFileSystemVolume *volume)
 | ||
| {
 | ||
|   DEBUG ("volume_get_display_name");
 | ||
| 
 | ||
|   if (IS_ROOT_VOLUME (volume))
 | ||
|     return g_strdup (_(root_volume_token));
 | ||
|   if (G_IS_DRIVE (volume))
 | ||
|     return g_drive_get_name (G_DRIVE (volume));
 | ||
|   else if (G_IS_MOUNT (volume))
 | ||
|     return g_mount_get_name (G_MOUNT (volume));
 | ||
|   else if (G_IS_VOLUME (volume))
 | ||
|     return g_volume_get_name (G_VOLUME (volume));
 | ||
| 
 | ||
|   return NULL;
 | ||
| }
 | ||
| 
 | ||
| gboolean
 | ||
| _gtk_file_system_volume_is_mounted (GtkFileSystemVolume *volume)
 | ||
| {
 | ||
|   gboolean mounted;
 | ||
| 
 | ||
|   DEBUG ("volume_is_mounted");
 | ||
| 
 | ||
|   if (IS_ROOT_VOLUME (volume))
 | ||
|     return TRUE;
 | ||
| 
 | ||
|   mounted = FALSE;
 | ||
| 
 | ||
|   if (G_IS_MOUNT (volume))
 | ||
|     mounted = TRUE;
 | ||
|   else if (G_IS_VOLUME (volume))
 | ||
|     {
 | ||
|       GMount *mount;
 | ||
| 
 | ||
|       mount = g_volume_get_mount (G_VOLUME (volume));
 | ||
| 
 | ||
|       if (mount)
 | ||
|         {
 | ||
|           mounted = TRUE;
 | ||
|           g_object_unref (mount);
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|   return mounted;
 | ||
| }
 | ||
| 
 | ||
| GFile *
 | ||
| _gtk_file_system_volume_get_root (GtkFileSystemVolume *volume)
 | ||
| {
 | ||
|   GFile *file = NULL;
 | ||
| 
 | ||
|   DEBUG ("volume_get_base");
 | ||
| 
 | ||
|   if (IS_ROOT_VOLUME (volume))
 | ||
|     return g_file_new_for_uri ("file:///");
 | ||
| 
 | ||
|   if (G_IS_MOUNT (volume))
 | ||
|     file = g_mount_get_root (G_MOUNT (volume));
 | ||
|   else if (G_IS_VOLUME (volume))
 | ||
|     {
 | ||
|       GMount *mount;
 | ||
| 
 | ||
|       mount = g_volume_get_mount (G_VOLUME (volume));
 | ||
| 
 | ||
|       if (mount)
 | ||
| 	{
 | ||
| 	  file = g_mount_get_root (mount);
 | ||
| 	  g_object_unref (mount);
 | ||
| 	}
 | ||
|     }
 | ||
| 
 | ||
|   return file;
 | ||
| }
 | ||
| 
 | ||
| static cairo_surface_t *
 | ||
| get_surface_from_gicon (GIcon      *icon,
 | ||
| 			GtkWidget  *widget,
 | ||
| 			gint        icon_size,
 | ||
| 			GError    **error)
 | ||
| {
 | ||
|   GdkScreen *screen;
 | ||
|   GtkIconTheme *icon_theme;
 | ||
|   GtkIconInfo *icon_info;
 | ||
|   GdkPixbuf *pixbuf;
 | ||
|   cairo_surface_t *surface;
 | ||
| 
 | ||
|   screen = gtk_widget_get_screen (GTK_WIDGET (widget));
 | ||
|   icon_theme = gtk_icon_theme_get_for_screen (screen);
 | ||
| 
 | ||
|   icon_info = gtk_icon_theme_lookup_by_gicon_for_scale (icon_theme,
 | ||
|                                                         icon,
 | ||
|                                                         icon_size,
 | ||
|                                                         gtk_widget_get_scale_factor (widget),
 | ||
|                                                         GTK_ICON_LOOKUP_USE_BUILTIN);
 | ||
| 
 | ||
|   if (!icon_info)
 | ||
|     return NULL;
 | ||
| 
 | ||
|   pixbuf = gtk_icon_info_load_symbolic_for_context (icon_info,
 | ||
|                                                     gtk_widget_get_style_context (widget),
 | ||
|                                                     NULL,
 | ||
|                                                     error);
 | ||
| 
 | ||
|   g_object_unref (icon_info);
 | ||
| 
 | ||
|   if (pixbuf == NULL)
 | ||
|     return NULL;
 | ||
| 
 | ||
|   surface = gdk_cairo_surface_create_from_pixbuf (pixbuf,
 | ||
|                                                   gtk_widget_get_scale_factor (widget),
 | ||
| 					          gtk_widget_get_window (widget));
 | ||
|   g_object_unref (pixbuf);
 | ||
| 
 | ||
|   return surface;
 | ||
| }
 | ||
| 
 | ||
| cairo_surface_t *
 | ||
| _gtk_file_system_volume_render_icon (GtkFileSystemVolume  *volume,
 | ||
| 				     GtkWidget            *widget,
 | ||
| 				     gint                  icon_size,
 | ||
| 				     GError              **error)
 | ||
| {
 | ||
|   GIcon *icon = NULL;
 | ||
|   cairo_surface_t *surface;
 | ||
| 
 | ||
|   if (IS_ROOT_VOLUME (volume))
 | ||
|     icon = g_themed_icon_new ("drive-harddisk");
 | ||
|   else if (G_IS_DRIVE (volume))
 | ||
|     icon = g_drive_get_icon (G_DRIVE (volume));
 | ||
|   else if (G_IS_VOLUME (volume))
 | ||
|     icon = g_volume_get_icon (G_VOLUME (volume));
 | ||
|   else if (G_IS_MOUNT (volume))
 | ||
|     icon = g_mount_get_icon (G_MOUNT (volume));
 | ||
| 
 | ||
|   if (!icon)
 | ||
|     return NULL;
 | ||
| 
 | ||
|   surface = get_surface_from_gicon (icon, widget, icon_size, error);
 | ||
| 
 | ||
|   g_object_unref (icon);
 | ||
| 
 | ||
|   return surface;
 | ||
| }
 | ||
| 
 | ||
| cairo_surface_t *
 | ||
| _gtk_file_system_volume_render_symbolic_icon (GtkFileSystemVolume  *volume,
 | ||
| 				              GtkWidget            *widget,
 | ||
| 				              gint                  icon_size,
 | ||
| 				              GError              **error)
 | ||
| {
 | ||
|   GIcon *icon = NULL;
 | ||
|   cairo_surface_t *surface;
 | ||
| 
 | ||
|   if (IS_ROOT_VOLUME (volume))
 | ||
|     icon = g_themed_icon_new ("drive-harddisk-symbolic");
 | ||
|   else if (G_IS_DRIVE (volume))
 | ||
|     icon = g_drive_get_symbolic_icon (G_DRIVE (volume));
 | ||
|   else if (G_IS_VOLUME (volume))
 | ||
|     icon = g_volume_get_symbolic_icon (G_VOLUME (volume));
 | ||
|   else if (G_IS_MOUNT (volume))
 | ||
|     icon = g_mount_get_symbolic_icon (G_MOUNT (volume));
 | ||
| 
 | ||
|   if (!icon)
 | ||
|     return NULL;
 | ||
| 
 | ||
|   surface = get_surface_from_gicon (icon, widget, icon_size, error);
 | ||
| 
 | ||
|   g_object_unref (icon);
 | ||
| 
 | ||
|   return surface;
 | ||
| }
 | ||
| 
 | ||
| GtkFileSystemVolume *
 | ||
| _gtk_file_system_volume_ref (GtkFileSystemVolume *volume)
 | ||
| {
 | ||
|   if (IS_ROOT_VOLUME (volume))
 | ||
|     return volume;
 | ||
| 
 | ||
|   if (G_IS_MOUNT (volume)  ||
 | ||
|       G_IS_VOLUME (volume) ||
 | ||
|       G_IS_DRIVE (volume))
 | ||
|     g_object_ref (volume);
 | ||
| 
 | ||
|   return volume;
 | ||
| }
 | ||
| 
 | ||
| void
 | ||
| _gtk_file_system_volume_unref (GtkFileSystemVolume *volume)
 | ||
| {
 | ||
|   /* Root volume doesn't need to be freed */
 | ||
|   if (IS_ROOT_VOLUME (volume))
 | ||
|     return;
 | ||
| 
 | ||
|   if (G_IS_MOUNT (volume)  ||
 | ||
|       G_IS_VOLUME (volume) ||
 | ||
|       G_IS_DRIVE (volume))
 | ||
|     g_object_unref (volume);
 | ||
| }
 | ||
| 
 | ||
| /* GFileInfo helper functions */
 | ||
| static cairo_surface_t *
 | ||
| _gtk_file_info_render_icon_internal (GFileInfo *info,
 | ||
| 			             GtkWidget *widget,
 | ||
| 			             gint       icon_size,
 | ||
|                                      gboolean   symbolic)
 | ||
| {
 | ||
|   GIcon *icon;
 | ||
|   GdkPixbuf *pixbuf;
 | ||
|   const gchar *thumbnail_path;
 | ||
|   cairo_surface_t *surface = NULL;
 | ||
|   int scale;
 | ||
| 
 | ||
|   thumbnail_path = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH);
 | ||
| 
 | ||
|   if (thumbnail_path)
 | ||
|     {
 | ||
|       scale = gtk_widget_get_scale_factor (widget);
 | ||
|       pixbuf = gdk_pixbuf_new_from_file_at_size (thumbnail_path,
 | ||
| 						 icon_size*scale, icon_size*scale,
 | ||
| 						 NULL);
 | ||
| 
 | ||
|       if (pixbuf != NULL)
 | ||
|         {
 | ||
|           surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale,
 | ||
|                                                           gtk_widget_get_window (widget));
 | ||
|           g_object_unref (pixbuf);
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|   if (!surface)
 | ||
|     {
 | ||
|       if (symbolic)
 | ||
|         icon = g_file_info_get_symbolic_icon (info);
 | ||
|       else
 | ||
|         icon = g_file_info_get_icon (info);
 | ||
| 
 | ||
|       if (icon)
 | ||
| 	surface = get_surface_from_gicon (icon, widget, icon_size, NULL);
 | ||
| 
 | ||
|       if (!surface)
 | ||
| 	{
 | ||
| 	   /* Use general fallback for all files without icon */
 | ||
|           if (symbolic)
 | ||
| 	    icon = g_themed_icon_new ("text-x-generic-symbolic");
 | ||
|           else
 | ||
| 	    icon = g_themed_icon_new ("text-x-generic");
 | ||
| 	  surface = get_surface_from_gicon (icon, widget, icon_size, NULL);
 | ||
| 	  g_object_unref (icon);
 | ||
| 	}
 | ||
|     }
 | ||
| 
 | ||
|   return surface;
 | ||
| }
 | ||
| 
 | ||
| cairo_surface_t *
 | ||
| _gtk_file_info_render_icon (GFileInfo *info,
 | ||
| 			    GtkWidget *widget,
 | ||
| 			    gint       icon_size)
 | ||
| {
 | ||
|   return _gtk_file_info_render_icon_internal (info, widget, icon_size, FALSE);
 | ||
| }
 | ||
| 
 | ||
| cairo_surface_t *
 | ||
| _gtk_file_info_render_symbolic_icon (GFileInfo *info,
 | ||
| 			             GtkWidget *widget,
 | ||
| 			             gint       icon_size)
 | ||
| {
 | ||
|   return _gtk_file_info_render_icon_internal (info, widget, icon_size, TRUE);
 | ||
| }
 | ||
| 
 | ||
| gboolean
 | ||
| _gtk_file_info_consider_as_directory (GFileInfo *info)
 | ||
| {
 | ||
|   GFileType type = g_file_info_get_file_type (info);
 | ||
|   
 | ||
|   return (type == G_FILE_TYPE_DIRECTORY ||
 | ||
|           type == G_FILE_TYPE_MOUNTABLE ||
 | ||
|           type == G_FILE_TYPE_SHORTCUT);
 | ||
| }
 | ||
| 
 | ||
| gboolean
 | ||
| _gtk_file_has_native_path (GFile *file)
 | ||
| {
 | ||
|   char *local_file_path;
 | ||
|   gboolean has_native_path;
 | ||
| 
 | ||
|   /* Don't use g_file_is_native(), as we want to support FUSE paths if available */
 | ||
|   local_file_path = g_file_get_path (file);
 | ||
|   has_native_path = (local_file_path != NULL);
 | ||
|   g_free (local_file_path);
 | ||
| 
 | ||
|   return has_native_path;
 | ||
| }
 | 
