gtkfilesystem.c 24.7 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
2
 * gtkfilesystem.c: Filesystem abstraction functions.
Owen Taylor's avatar
Owen Taylor committed
3
 * Copyright (C) 2003, Red Hat, Inc.
4
 * Copyright (C) 2007-2008 Carlos Garnacho
Owen Taylor's avatar
Owen Taylor committed
5
 *
Javier Jardón's avatar
Javier Jardón committed
6
 * This library is free software; you can redistribute it and/or modify
7 8 9
 * 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.
Owen Taylor's avatar
Owen Taylor committed
10
 *
Javier Jardón's avatar
Javier Jardón committed
11
 * This library is distributed in the hope that it will be useful,
Owen Taylor's avatar
Owen Taylor committed
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Javier Jardón's avatar
Javier Jardón committed
14
 * GNU Lesser General Public License for more details.
Owen Taylor's avatar
Owen Taylor committed
15
 *
Javier Jardón's avatar
Javier Jardón committed
16 17
 * 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/>.
18 19
 *
 * Authors: Carlos Garnacho <carlos@imendio.com>
Owen Taylor's avatar
Owen Taylor committed
20 21
 */

Michael Natterer's avatar
Michael Natterer committed
22 23
#include "config.h"

24
#include "gtkfilesystem.h"
Michael Natterer's avatar
Michael Natterer committed
25

26
#include <string.h>
27
#include <glib/gi18n-lib.h>
28

29
#include "gtkfilechooser.h"
30
#include "gtkcssiconthemevalueprivate.h"
31
#include "gtkintl.h"
32 33
#include "gtkprivate.h"
#include "gtkstylecontextprivate.h"
Owen Taylor's avatar
Owen Taylor committed
34

35 36
/* #define DEBUG_MODE */
#ifdef DEBUG_MODE
37
#define DEBUG(x) g_debug (x);
38
#else
39
#define DEBUG(x)
40 41 42 43 44 45
#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
46
 * token for the fake “File System” volume.  So, we’ll return a pointer to
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
 * 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;

77
struct GtkFileSystemPrivate
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
{
  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;

  gpointer callback;
  gpointer data;
};
96

97
G_DEFINE_TYPE_WITH_PRIVATE (GtkFileSystem, _gtk_file_system, G_TYPE_OBJECT)
98

Owen Taylor's avatar
Owen Taylor committed
99

100 101 102 103 104
/* GtkFileSystem methods */
static void
volumes_changed (GVolumeMonitor *volume_monitor,
		 gpointer        volume,
		 gpointer        user_data)
Owen Taylor's avatar
Owen Taylor committed
105
{
106
  GtkFileSystem *file_system;
Owen Taylor's avatar
Owen Taylor committed
107

108
  gdk_threads_enter ();
Owen Taylor's avatar
Owen Taylor committed
109

110 111 112
  file_system = GTK_FILE_SYSTEM (user_data);
  g_signal_emit (file_system, fs_signals[VOLUMES_CHANGED], 0, volume);
  gdk_threads_leave ();
Owen Taylor's avatar
Owen Taylor committed
113 114
}

115 116
static void
gtk_file_system_dispose (GObject *object)
Owen Taylor's avatar
Owen Taylor committed
117
{
118
  GtkFileSystem *file_system = GTK_FILE_SYSTEM (object);
119
  GtkFileSystemPrivate *priv = file_system->priv;
Owen Taylor's avatar
Owen Taylor committed
120

121
  DEBUG ("dispose");
Owen Taylor's avatar
Owen Taylor committed
122

123 124
  if (priv->volumes)
    {
Matthias Clasen's avatar
Matthias Clasen committed
125
      g_slist_free_full (priv->volumes, g_object_unref);
126 127
      priv->volumes = NULL;
    }
Owen Taylor's avatar
Owen Taylor committed
128

129
  if (priv->volume_monitor)
Owen Taylor's avatar
Owen Taylor committed
130
    {
131 132 133
      g_signal_handlers_disconnect_by_func (priv->volume_monitor, volumes_changed, object);
      g_object_unref (priv->volume_monitor);
      priv->volume_monitor = NULL;
Owen Taylor's avatar
Owen Taylor committed
134
    }
135

136
  G_OBJECT_CLASS (_gtk_file_system_parent_class)->dispose (object);
Owen Taylor's avatar
Owen Taylor committed
137 138
}

139
static void
140
_gtk_file_system_class_init (GtkFileSystemClass *class)
Owen Taylor's avatar
Owen Taylor committed
141
{
142
  GObjectClass *object_class = G_OBJECT_CLASS (class);
Owen Taylor's avatar
Owen Taylor committed
143

144
  object_class->dispose = gtk_file_system_dispose;
Owen Taylor's avatar
Owen Taylor committed
145

146
  fs_signals[VOLUMES_CHANGED] =
147
    g_signal_new (I_("volumes-changed"),
148 149 150 151 152 153
		  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);
Owen Taylor's avatar
Owen Taylor committed
154 155
}

Matthias Clasen's avatar
Matthias Clasen committed
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
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;
}

189 190
static void
get_volumes_list (GtkFileSystem *file_system)
191
{
192
  GtkFileSystemPrivate *priv = file_system->priv;
193 194 195 196 197 198 199
  GList *l, *ll;
  GList *drives;
  GList *volumes;
  GList *mounts;
  GDrive *drive;
  GVolume *volume;
  GMount *mount;
200

201
  if (priv->volumes)
202
    {
Matthias Clasen's avatar
Matthias Clasen committed
203
      g_slist_free_full (priv->volumes, g_object_unref);
204 205 206 207 208 209 210 211 212 213
      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);
214

215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
      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);
            }
243 244
  
           g_list_free (volumes);
245 246 247 248 249 250 251 252 253 254 255 256 257
        }
      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));
258
	}
259 260

      g_object_unref (drive);
261 262
    }

263 264 265 266 267 268
  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)
269
    {
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
      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));
        }
292 293

      g_object_unref (volume);
294 295
    }

296 297
  /* add mounts that has no volume (/etc/mtab mounts, ftp, sftp,...) */
  mounts = g_volume_monitor_get_mounts (priv->volume_monitor);
298

299 300 301 302 303 304 305 306 307 308 309
  for (l = mounts; l != NULL; l = l->next)
    {
      mount = l->data;
      volume = g_mount_get_volume (mount);

      if (volume)
        {
          g_object_unref (volume);
          continue;
        }

Matthias Clasen's avatar
Matthias Clasen committed
310 311 312 313 314
      /* 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))
        {
315
          g_object_unref (mount);
Matthias Clasen's avatar
Matthias Clasen committed
316 317 318
          continue;
        }

319 320
      /* show this mount */
      priv->volumes = g_slist_prepend (priv->volumes, g_object_ref (mount));
321
      g_object_unref (mount);
322
    }
323

Matthias Clasen's avatar
Matthias Clasen committed
324 325
  g_list_free (volumes);

326 327
  g_list_free (mounts);
}
328 329

static void
330
_gtk_file_system_init (GtkFileSystem *file_system)
331
{
332
  GtkFileSystemPrivate *priv;
333 334 335

  DEBUG ("init");

336
  file_system->priv = priv = _gtk_file_system_get_instance_private (file_system);
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362

  /* 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 *
363
_gtk_file_system_new (void)
364
{
365
  return g_object_new (GTK_TYPE_FILE_SYSTEM, NULL);
366 367
}

368
GSList *
369
_gtk_file_system_list_volumes (GtkFileSystem *file_system)
370
{
371
  GtkFileSystemPrivate *priv = file_system->priv;
372 373 374 375
  GSList *list;

  DEBUG ("list_volumes");

376
  get_volumes_list (file_system);
377 378 379

  list = g_slist_copy (priv->volumes);

380
#ifndef G_OS_WIN32
381 382
  /* Prepend root volume */
  list = g_slist_prepend (list, (gpointer) root_volume_token);
383
#endif
384 385

  return list;
386 387
}

Owen Taylor's avatar
Owen Taylor committed
388
static void
389
free_async_data (AsyncFuncData *async_data)
Owen Taylor's avatar
Owen Taylor committed
390
{
391 392 393 394 395
  g_object_unref (async_data->file_system);
  g_object_unref (async_data->file);
  g_object_unref (async_data->cancellable);

  g_free (async_data);
Owen Taylor's avatar
Owen Taylor committed
396 397
}

398 399 400 401
static void
query_info_callback (GObject      *source_object,
		     GAsyncResult *result,
		     gpointer      user_data)
402
{
403 404 405 406
  AsyncFuncData *async_data;
  GError *error = NULL;
  GFileInfo *file_info;
  GFile *file;
407

408
  DEBUG ("query_info_callback");
409

410 411 412
  file = G_FILE (source_object);
  async_data = (AsyncFuncData *) user_data;
  file_info = g_file_query_info_finish (file, result, &error);
Owen Taylor's avatar
Owen Taylor committed
413

414 415 416 417 418 419 420 421 422 423 424 425 426 427 428
  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);
Owen Taylor's avatar
Owen Taylor committed
429 430
}

431
GCancellable *
432 433 434 435 436
_gtk_file_system_get_info (GtkFileSystem                *file_system,
			   GFile                        *file,
			   const gchar                  *attributes,
			   GtkFileSystemGetInfoCallback  callback,
			   gpointer                      data)
437
{
438 439 440
  GCancellable *cancellable;
  AsyncFuncData *async_data;

441
  g_return_val_if_fail (GTK_IS_FILE_SYSTEM (file_system), NULL);
442
  g_return_val_if_fail (G_IS_FILE (file), NULL);
443

444
  cancellable = g_cancellable_new ();
445

446 447 448 449 450 451 452
  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;
453

454 455 456 457 458 459 460 461 462
  g_file_query_info_async (file,
			   attributes,
			   G_FILE_QUERY_INFO_NONE,
			   G_PRIORITY_DEFAULT,
			   cancellable,
			   query_info_callback,
			   async_data);

  return cancellable;
463 464
}

465 466 467 468
static void
drive_poll_for_media_cb (GObject      *source_object,
                         GAsyncResult *result,
                         gpointer      user_data)
469
{
470 471 472 473 474
  AsyncFuncData *async_data;
  GError *error = NULL;

  g_drive_poll_for_media_finish (G_DRIVE (source_object), result, &error);
  async_data = (AsyncFuncData *) user_data;
475

476 477 478 479 480 481 482 483
  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);
484 485
}

486 487 488 489
static void
volume_mount_cb (GObject      *source_object,
		 GAsyncResult *result,
		 gpointer      user_data)
490
{
491 492 493 494 495
  AsyncFuncData *async_data;
  GError *error = NULL;

  g_volume_mount_finish (G_VOLUME (source_object), result, &error);
  async_data = (AsyncFuncData *) user_data;
496

497 498 499 500 501 502 503 504
  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);
505 506
}

507
GCancellable *
508 509 510 511 512
_gtk_file_system_mount_volume (GtkFileSystem                    *file_system,
			       GtkFileSystemVolume              *volume,
			       GMountOperation                  *mount_operation,
			       GtkFileSystemVolumeMountCallback  callback,
			       gpointer                          data)
513
{
514 515 516 517 518 519 520 521 522 523 524 525 526 527
  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;
528

529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547
  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;
548 549
}

550 551 552 553
static void
enclosing_volume_mount_cb (GObject      *source_object,
			   GAsyncResult *result,
			   gpointer      user_data)
554
{
555 556 557 558 559 560
  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);
561
  volume = _gtk_file_system_get_volume_for_file (async_data->file_system, G_FILE (source_object));
562

563 564 565 566 567
  /* 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);

568 569 570 571 572 573 574
  gdk_threads_enter ();
  ((GtkFileSystemVolumeMountCallback) async_data->callback) (async_data->cancellable, volume,
							     error, async_data->data);
  gdk_threads_leave ();

  if (error)
    g_error_free (error);
575 576

  _gtk_file_system_volume_unref (volume);
577 578
}

579
GCancellable *
580 581 582 583 584
_gtk_file_system_mount_enclosing_volume (GtkFileSystem                     *file_system,
					 GFile                             *file,
					 GMountOperation                   *mount_operation,
					 GtkFileSystemVolumeMountCallback   callback,
					 gpointer                           data)
585
{
586 587
  GCancellable *cancellable;
  AsyncFuncData *async_data;
588

589
  g_return_val_if_fail (GTK_IS_FILE_SYSTEM (file_system), NULL);
590
  g_return_val_if_fail (G_IS_FILE (file), NULL);
591

592
  DEBUG ("mount_enclosing_volume");
593

594
  cancellable = g_cancellable_new ();
595

596 597 598 599
  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);
600

601 602
  async_data->callback = callback;
  async_data->data = data;
603

604 605 606 607 608 609 610
  g_file_mount_enclosing_volume (file,
				 G_MOUNT_MOUNT_NONE,
				 mount_operation,
				 cancellable,
				 enclosing_volume_mount_cb,
				 async_data);
  return cancellable;
611 612
}

613
GtkFileSystemVolume *
614 615
_gtk_file_system_get_volume_for_file (GtkFileSystem *file_system,
				      GFile         *file)
616
{
617 618 619
  GMount *mount;

  DEBUG ("get_volume_for_file");
620

621 622 623 624 625 626
  mount = g_file_find_enclosing_mount (file, NULL, NULL);

  if (!mount && g_file_is_native (file))
    return (GtkFileSystemVolume *) root_volume_token;

  return (GtkFileSystemVolume *) mount;
627 628
}

629 630
/* GtkFileSystemVolume public methods */
gchar *
631
_gtk_file_system_volume_get_display_name (GtkFileSystemVolume *volume)
632
{
633
  DEBUG ("volume_get_display_name");
634

635 636 637 638 639 640 641 642
  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));
643

644 645
  return NULL;
}
646

647
gboolean
648
_gtk_file_system_volume_is_mounted (GtkFileSystemVolume *volume)
649
{
650
  gboolean mounted;
651

652
  DEBUG ("volume_is_mounted");
653

654 655
  if (IS_ROOT_VOLUME (volume))
    return TRUE;
656

657
  mounted = FALSE;
658

659 660 661
  if (G_IS_MOUNT (volume))
    mounted = TRUE;
  else if (G_IS_VOLUME (volume))
662
    {
663 664 665 666 667 668 669 670 671
      GMount *mount;

      mount = g_volume_get_mount (G_VOLUME (volume));

      if (mount)
        {
          mounted = TRUE;
          g_object_unref (mount);
        }
672 673
    }

674
  return mounted;
675 676
}

677
GFile *
678
_gtk_file_system_volume_get_root (GtkFileSystemVolume *volume)
679
{
680
  GFile *file = NULL;
681

682
  DEBUG ("volume_get_base");
683

684 685
  if (IS_ROOT_VOLUME (volume))
    return g_file_new_for_uri ("file:///");
686

687 688 689 690 691
  if (G_IS_MOUNT (volume))
    file = g_mount_get_root (G_MOUNT (volume));
  else if (G_IS_VOLUME (volume))
    {
      GMount *mount;
692

693
      mount = g_volume_get_mount (G_VOLUME (volume));
694

695 696 697 698 699 700 701 702
      if (mount)
	{
	  file = g_mount_get_root (mount);
	  g_object_unref (mount);
	}
    }

  return file;
703 704
}

705 706 707 708 709
static cairo_surface_t *
get_surface_from_gicon (GIcon      *icon,
			GtkWidget  *widget,
			gint        icon_size,
			GError    **error)
710
{
711
  GtkStyleContext *context;
712 713
  GtkIconTheme *icon_theme;
  GtkIconInfo *icon_info;
714
  GdkPixbuf *pixbuf;
715
  cairo_surface_t *surface;
716

717 718 719
  context = gtk_widget_get_style_context (widget);
  icon_theme = gtk_css_icon_theme_value_get_icon_theme
    (_gtk_style_context_peek_property (context, GTK_CSS_PROPERTY_ICON_THEME));
720

721
  icon_info = gtk_icon_theme_lookup_by_gicon_for_scale (icon_theme,
722 723 724 725
                                                        icon,
                                                        icon_size,
                                                        gtk_widget_get_scale_factor (widget),
                                                        GTK_ICON_LOOKUP_USE_BUILTIN);
726 727 728 729

  if (!icon_info)
    return NULL;

730
  pixbuf = gtk_icon_info_load_symbolic_for_context (icon_info,
731
                                                    context,
732 733
                                                    NULL,
                                                    error);
734

735
  g_object_unref (icon_info);
736

737 738 739 740 741 742 743 744
  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);

745
  return surface;
746 747
}

748
cairo_surface_t *
749 750 751 752
_gtk_file_system_volume_render_icon (GtkFileSystemVolume  *volume,
				     GtkWidget            *widget,
				     gint                  icon_size,
				     GError              **error)
753
{
754
  GIcon *icon = NULL;
755
  cairo_surface_t *surface;
756

757
  if (IS_ROOT_VOLUME (volume))
Matthias Clasen's avatar
Matthias Clasen committed
758
    icon = g_themed_icon_new ("drive-harddisk");
759 760 761 762 763
  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))
764
    icon = g_mount_get_icon (G_MOUNT (volume));
765

766 767
  if (!icon)
    return NULL;
768

769
  surface = get_surface_from_gicon (icon, widget, icon_size, error);
Paolo Borelli's avatar
Paolo Borelli committed
770 771 772

  g_object_unref (icon);

773
  return surface;
774
}
775

776 777 778 779 780 781 782 783 784 785 786 787 788 789 790
GIcon *
_gtk_file_system_volume_get_symbolic_icon (GtkFileSystemVolume *volume)
{
  if (IS_ROOT_VOLUME (volume))
    return g_themed_icon_new ("drive-harddisk-symbolic");
  else if (G_IS_DRIVE (volume))
    return g_drive_get_symbolic_icon (G_DRIVE (volume));
  else if (G_IS_VOLUME (volume))
    return g_volume_get_symbolic_icon (G_VOLUME (volume));
  else if (G_IS_MOUNT (volume))
    return g_mount_get_symbolic_icon (G_MOUNT (volume));
  else
    return NULL;
}

791 792 793 794 795 796
cairo_surface_t *
_gtk_file_system_volume_render_symbolic_icon (GtkFileSystemVolume  *volume,
				              GtkWidget            *widget,
				              gint                  icon_size,
				              GError              **error)
{
797
  GIcon *icon;
798 799
  cairo_surface_t *surface;

800
  icon = _gtk_file_system_volume_get_symbolic_icon (volume);
801 802 803 804 805 806
  surface = get_surface_from_gicon (icon, widget, icon_size, error);
  g_object_unref (icon);

  return surface;
}

807 808 809 810 811 812 813 814 815 816 817 818 819 820
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;
}

821
void
822
_gtk_file_system_volume_unref (GtkFileSystemVolume *volume)
823 824 825 826
{
  /* Root volume doesn't need to be freed */
  if (IS_ROOT_VOLUME (volume))
    return;
827

828 829 830 831 832
  if (G_IS_MOUNT (volume)  ||
      G_IS_VOLUME (volume) ||
      G_IS_DRIVE (volume))
    g_object_unref (volume);
}
833

834
/* GFileInfo helper functions */
835
static cairo_surface_t *
836 837 838 839
_gtk_file_info_render_icon_internal (GFileInfo *info,
			             GtkWidget *widget,
			             gint       icon_size,
                                     gboolean   symbolic)
840 841
{
  GIcon *icon;
842
  GdkPixbuf *pixbuf;
843
  const gchar *thumbnail_path;
844 845
  cairo_surface_t *surface = NULL;
  int scale;
846 847 848 849

  thumbnail_path = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH);

  if (thumbnail_path)
850 851 852 853 854 855
    {
      scale = gtk_widget_get_scale_factor (widget);
      pixbuf = gdk_pixbuf_new_from_file_at_size (thumbnail_path,
						 icon_size*scale, icon_size*scale,
						 NULL);

856 857 858 859 860 861
      if (pixbuf != NULL)
        {
          surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale,
                                                          gtk_widget_get_window (widget));
          g_object_unref (pixbuf);
        }
862
    }
863

864
  if (!surface)
865
    {
866 867 868 869
      if (symbolic)
        icon = g_file_info_get_symbolic_icon (info);
      else
        icon = g_file_info_get_icon (info);
870 871

      if (icon)
872
	surface = get_surface_from_gicon (icon, widget, icon_size, NULL);
873

874
      if (!surface)
875 876
	{
	   /* Use general fallback for all files without icon */
877 878 879 880
          if (symbolic)
	    icon = g_themed_icon_new ("text-x-generic-symbolic");
          else
	    icon = g_themed_icon_new ("text-x-generic");
881
	  surface = get_surface_from_gicon (icon, widget, icon_size, NULL);
882 883
	  g_object_unref (icon);
	}
884
    }
885

886
  return surface;
887
}
888

889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904
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);
}

905 906 907 908 909 910
gboolean
_gtk_file_info_consider_as_directory (GFileInfo *info)
{
  GFileType type = g_file_info_get_file_type (info);
  
  return (type == G_FILE_TYPE_DIRECTORY ||
911 912
          type == G_FILE_TYPE_MOUNTABLE ||
          type == G_FILE_TYPE_SHORTCUT);
913 914
}

915
gboolean
916
_gtk_file_has_native_path (GFile *file)
917 918
{
  char *local_file_path;
919
  gboolean has_native_path;
920

921
  /* Don't use g_file_is_native(), as we want to support FUSE paths if available */
922
  local_file_path = g_file_get_path (file);
923
  has_native_path = (local_file_path != NULL);
924 925
  g_free (local_file_path);

926
  return has_native_path;
927
}
928 929

static const gchar * const remote_types[] = {
930 931
  "afp",
  "google-drive",
932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951
  "sftp",
  "webdav",
  "ftp",
  "nfs",
  "cifs",
  NULL
};

gboolean
_gtk_file_consider_as_remote (GFile *file)
{
  GFileInfo *info;
  gboolean is_remote;

  info = g_file_query_filesystem_info (file, "filesystem::type", NULL, NULL);
  if (info)
    {
      const gchar *type;

      type = g_file_info_get_attribute_string (info, "filesystem::type");
952 953 954 955
      if (type != NULL)
        is_remote = g_strv_contains (remote_types, type);
      else
        is_remote = FALSE;
956 957 958 959 960 961 962 963

      g_object_unref (info);
    }
  else
    is_remote = FALSE;

  return is_remote;
}