tracker-0.16.2/src/libtracker-miner/tracker-storage.c

No issues found

   1 /*
   2  * Copyright (C) 2008, Nokia <ivan.frade@nokia.com>
   3  *
   4  * This library is free software; you can redistribute it and/or
   5  * modify it under the terms of the GNU Lesser General Public
   6  * License as published by the Free Software Foundation; either
   7  * version 2.1 of the License, or (at your option) any later version.
   8  *
   9  * This library is distributed in the hope that it will be useful,
  10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  12  * Lesser General Public License for more details.
  13  *
  14  * You should have received a copy of the GNU Lesser General Public
  15  * License along with this library; if not, write to the
  16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  17  * Boston, MA  02110-1301, USA.
  18  */
  19 
  20 #include "config.h"
  21 
  22 #include <string.h>
  23 
  24 #include <gio/gio.h>
  25 #include <gio/gunixmounts.h>
  26 
  27 #include <libtracker-common/tracker-log.h>
  28 
  29 #include "tracker-storage.h"
  30 #include "tracker-utils.h"
  31 #include "tracker-marshal.h"
  32 
  33 /**
  34  * SECTION:tracker-storage
  35  * @short_description: Removable storage and mount point convenience API
  36  * @include: libtracker-miner/tracker-miner.h
  37  *
  38  * This API is a convenience to to be able to keep track of volumes
  39  * which are mounted and also the type of removable media available.
  40  * The API is built upon the top of GIO's #GMount, #GDrive and #GVolume API.
  41  **/
  42 
  43 #define TRACKER_STORAGE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TRACKER_TYPE_STORAGE, TrackerStoragePrivate))
  44 
  45 typedef struct {
  46 	GVolumeMonitor *volume_monitor;
  47 
  48 	GNode *mounts;
  49 	GHashTable *mounts_by_uuid;
  50 	GHashTable *unmount_watchdogs;
  51 } TrackerStoragePrivate;
  52 
  53 typedef struct {
  54 	gchar *mount_point;
  55 	gchar *uuid;
  56 	guint unmount_timer_id;
  57 	guint removable : 1;
  58 	guint optical : 1;
  59 } MountInfo;
  60 
  61 typedef struct {
  62 	const gchar *path;
  63 	GNode *node;
  64 } TraverseData;
  65 
  66 typedef struct {
  67 	GSList *roots;
  68 	TrackerStorageType type;
  69 	gboolean exact_match;
  70 } GetRoots;
  71 
  72 typedef struct {
  73 	TrackerStorage *storage;
  74 	GMount *mount;
  75 } UnmountCheckData;
  76 
  77 static void     tracker_storage_finalize (GObject        *object);
  78 static gboolean mount_info_free          (GNode          *node,
  79                                           gpointer        user_data);
  80 static void     mount_node_free          (GNode          *node);
  81 static gboolean mounts_setup             (TrackerStorage *storage);
  82 static void     mount_added_cb           (GVolumeMonitor *monitor,
  83                                           GMount         *mount,
  84                                           gpointer        user_data);
  85 static void     mount_removed_cb         (GVolumeMonitor *monitor,
  86                                           GMount         *mount,
  87                                           gpointer        user_data);
  88 static void     mount_pre_removed_cb     (GVolumeMonitor *monitor,
  89                                           GMount         *mount,
  90                                           gpointer        user_data);
  91 
  92 enum {
  93 	MOUNT_POINT_ADDED,
  94 	MOUNT_POINT_REMOVED,
  95 	LAST_SIGNAL
  96 };
  97 
  98 static guint signals[LAST_SIGNAL] = {0};
  99 
 100 G_DEFINE_TYPE (TrackerStorage, tracker_storage, G_TYPE_OBJECT);
 101 
 102 static void
 103 tracker_storage_class_init (TrackerStorageClass *klass)
 104 {
 105 	GObjectClass *object_class;
 106 
 107 	object_class = G_OBJECT_CLASS (klass);
 108 
 109 	object_class->finalize     = tracker_storage_finalize;
 110 
 111 	signals[MOUNT_POINT_ADDED] =
 112 		g_signal_new ("mount-point-added",
 113 		              G_TYPE_FROM_CLASS (klass),
 114 		              G_SIGNAL_RUN_LAST,
 115 		              0,
 116 		              NULL, NULL,
 117 		              tracker_marshal_VOID__STRING_STRING_STRING_BOOLEAN_BOOLEAN,
 118 		              G_TYPE_NONE,
 119 		              5,
 120 		              G_TYPE_STRING,
 121 		              G_TYPE_STRING,
 122                               G_TYPE_STRING,
 123 		              G_TYPE_BOOLEAN,
 124 		              G_TYPE_BOOLEAN);
 125 
 126 	signals[MOUNT_POINT_REMOVED] =
 127 		g_signal_new ("mount-point-removed",
 128 		              G_TYPE_FROM_CLASS (klass),
 129 		              G_SIGNAL_RUN_LAST,
 130 		              0,
 131 		              NULL, NULL,
 132 		              tracker_marshal_VOID__STRING_STRING,
 133 		              G_TYPE_NONE,
 134 		              2,
 135 		              G_TYPE_STRING,
 136 		              G_TYPE_STRING);
 137 
 138 	g_type_class_add_private (object_class, sizeof (TrackerStoragePrivate));
 139 }
 140 
 141 static void
 142 tracker_storage_init (TrackerStorage *storage)
 143 {
 144 	TrackerStoragePrivate *priv;
 145 
 146 	g_message ("Initializing Storage...");
 147 
 148 	priv = TRACKER_STORAGE_GET_PRIVATE (storage);
 149 
 150 	priv->mounts = g_node_new (NULL);
 151 
 152 	priv->mounts_by_uuid = g_hash_table_new_full (g_str_hash,
 153 	                                              g_str_equal,
 154 	                                              (GDestroyNotify) g_free,
 155 	                                              NULL);
 156 	priv->unmount_watchdogs = g_hash_table_new_full (NULL, NULL, NULL,
 157 							 (GDestroyNotify) g_source_remove);
 158 
 159 	priv->volume_monitor = g_volume_monitor_get ();
 160 
 161 	/* Volume and property notification callbacks */
 162 	g_signal_connect_object (priv->volume_monitor, "mount-removed",
 163 	                         G_CALLBACK (mount_removed_cb), storage, 0);
 164 	g_signal_connect_object (priv->volume_monitor, "mount-pre-unmount",
 165 	                         G_CALLBACK (mount_pre_removed_cb), storage, 0);
 166 	g_signal_connect_object (priv->volume_monitor, "mount-added",
 167 	                         G_CALLBACK (mount_added_cb), storage, 0);
 168 
 169 	g_message ("Mount monitors set up for to watch for added, removed and pre-unmounts...");
 170 
 171 	/* Get all mounts and set them up */
 172 	if (!mounts_setup (storage)) {
 173 		return;
 174 	}
 175 }
 176 
 177 static void
 178 tracker_storage_finalize (GObject *object)
 179 {
 180 	TrackerStoragePrivate *priv;
 181 
 182 	priv = TRACKER_STORAGE_GET_PRIVATE (object);
 183 
 184 	g_hash_table_destroy (priv->unmount_watchdogs);
 185 
 186 	if (priv->mounts_by_uuid) {
 187 		g_hash_table_unref (priv->mounts_by_uuid);
 188 	}
 189 
 190 	if (priv->mounts) {
 191 		mount_node_free (priv->mounts);
 192 	}
 193 
 194 	if (priv->volume_monitor) {
 195 		g_object_unref (priv->volume_monitor);
 196 	}
 197 
 198 	(G_OBJECT_CLASS (tracker_storage_parent_class)->finalize) (object);
 199 }
 200 
 201 static void
 202 mount_node_free (GNode *node)
 203 {
 204 	g_node_traverse (node,
 205 	                 G_POST_ORDER,
 206 	                 G_TRAVERSE_ALL,
 207 	                 -1,
 208 	                 mount_info_free,
 209 	                 NULL);
 210 
 211 	g_node_destroy (node);
 212 }
 213 
 214 static gboolean
 215 mount_node_traverse_func (GNode    *node,
 216                           gpointer  user_data)
 217 {
 218 	TraverseData *data;
 219 	MountInfo *info;
 220 
 221 	if (!node->data) {
 222 		/* Root node */
 223 		return FALSE;
 224 	}
 225 
 226 	data = user_data;
 227 	info = node->data;
 228 
 229 	if (g_str_has_prefix (data->path, info->mount_point)) {
 230 		data->node = node;
 231 		return TRUE;
 232 	}
 233 
 234 	return FALSE;
 235 }
 236 
 237 static GNode *
 238 mount_node_find (GNode       *root,
 239                  const gchar *path)
 240 {
 241 	TraverseData data = { path, NULL };
 242 
 243 	g_node_traverse (root,
 244 	                 G_POST_ORDER,
 245 	                 G_TRAVERSE_ALL,
 246 	                 -1,
 247 	                 mount_node_traverse_func,
 248 	                 &data);
 249 
 250 	return data.node;
 251 }
 252 
 253 static gboolean
 254 mount_info_free (GNode    *node,
 255                  gpointer  user_data)
 256 {
 257 	MountInfo *info;
 258 
 259 	info = node->data;
 260 
 261 	if (info) {
 262 		g_free (info->mount_point);
 263 		g_free (info->uuid);
 264 
 265 		g_slice_free (MountInfo, info);
 266 	}
 267 
 268 	return FALSE;
 269 }
 270 
 271 static MountInfo *
 272 mount_info_find (GNode       *root,
 273                  const gchar *path)
 274 {
 275 	GNode *node;
 276 
 277 	node = mount_node_find (root, path);
 278 	return (node) ? node->data : NULL;
 279 }
 280 
 281 static TrackerStorageType
 282 mount_info_get_type (MountInfo *info)
 283 {
 284 	TrackerStorageType mount_type = 0;
 285 
 286 	if (info->removable) {
 287 		mount_type |= TRACKER_STORAGE_REMOVABLE;
 288 	}
 289 
 290 	if (info->optical) {
 291 		mount_type |= TRACKER_STORAGE_OPTICAL;
 292 	}
 293 
 294 	return mount_type;
 295 }
 296 
 297 static gchar *
 298 mount_point_normalize (const gchar *mount_point)
 299 {
 300 	gchar *mp;
 301 
 302 	/* Normalize all mount points to have a / at the end */
 303 	if (g_str_has_suffix (mount_point, G_DIR_SEPARATOR_S)) {
 304 		mp = g_strdup (mount_point);
 305 	} else {
 306 		mp = g_strconcat (mount_point, G_DIR_SEPARATOR_S, NULL);
 307 	}
 308 
 309 	return mp;
 310 }
 311 
 312 static GNode *
 313 mount_add_hierarchy (GNode       *root,
 314                      const gchar *uuid,
 315                      const gchar *mount_point,
 316                      gboolean     removable,
 317                      gboolean     optical)
 318 {
 319 	MountInfo *info;
 320 	GNode *node;
 321 	gchar *mp;
 322 
 323 	mp = mount_point_normalize (mount_point);
 324 	node = mount_node_find (root, mp);
 325 
 326 	if (!node) {
 327 		node = root;
 328 	}
 329 
 330 	info = g_slice_new (MountInfo);
 331 	info->mount_point = mp;
 332 	info->uuid = g_strdup (uuid);
 333 	info->removable = removable;
 334 	info->optical = optical;
 335 
 336 	return g_node_append_data (node, info);
 337 }
 338 
 339 static void
 340 mount_add_new (TrackerStorage *storage,
 341                const gchar    *uuid,
 342                const gchar    *mount_point,
 343                const gchar    *mount_name,
 344                gboolean        removable_device,
 345                gboolean        optical_disc)
 346 {
 347 	TrackerStoragePrivate *priv;
 348 	GNode *node;
 349 
 350 	priv = TRACKER_STORAGE_GET_PRIVATE (storage);
 351 
 352 	node = mount_add_hierarchy (priv->mounts, uuid, mount_point, removable_device, optical_disc);
 353 	g_hash_table_insert (priv->mounts_by_uuid, g_strdup (uuid), node);
 354 
 355 	g_signal_emit (storage,
 356 	               signals[MOUNT_POINT_ADDED],
 357 	               0,
 358 	               uuid,
 359 	               mount_point,
 360                        mount_name,
 361 	               removable_device,
 362 	               optical_disc,
 363 	               NULL);
 364 }
 365 
 366 static gchar *
 367 mount_guess_content_type (GMount   *mount,
 368                           gboolean *is_optical,
 369                           gboolean *is_multimedia,
 370                           gboolean *is_blank)
 371 {
 372 	gchar *content_type = NULL;
 373 	gchar **guess_type;
 374 
 375 	*is_optical = FALSE;
 376 	*is_multimedia = FALSE;
 377 	*is_blank = FALSE;
 378 
 379 	/* This function has 2 purposes:
 380 	 *
 381 	 * 1. Detect if we are using optical media
 382 	 * 2. Detect if we are video or music, we can't index those types
 383 	 *
 384 	 * We try to determine the content type because we don't want
 385 	 * to store Volume information in Tracker about DVDs and media
 386 	 * which has no real data for us to mine.
 387 	 *
 388 	 * Generally, if is_multimedia is TRUE then we end up ignoring
 389 	 * the media.
 390 	 */
 391 	guess_type = g_mount_guess_content_type_sync (mount, TRUE, NULL, NULL);
 392 
 393 	if (guess_type) {
 394 		gint i = 0;
 395 
 396 		while (!content_type && guess_type[i]) {
 397 			if (!g_strcmp0 (guess_type[i], "x-content/image-picturecd")) {
 398 				/* Images */
 399 				content_type = g_strdup (guess_type[i]);
 400 			} else if (!g_strcmp0 (guess_type[i], "x-content/video-bluray") ||
 401 			           !g_strcmp0 (guess_type[i], "x-content/video-dvd") ||
 402 			           !g_strcmp0 (guess_type[i], "x-content/video-hddvd") ||
 403 			           !g_strcmp0 (guess_type[i], "x-content/video-svcd") ||
 404 			           !g_strcmp0 (guess_type[i], "x-content/video-vcd")) {
 405 				/* Videos */
 406 				*is_multimedia = TRUE;
 407 				content_type = g_strdup (guess_type[i]);
 408 			} else if (!g_strcmp0 (guess_type[i], "x-content/audio-cdda") ||
 409 			           !g_strcmp0 (guess_type[i], "x-content/audio-dvd") ||
 410 			           !g_strcmp0 (guess_type[i], "x-content/audio-player")) {
 411 				/* Audios */
 412 				*is_multimedia = TRUE;
 413 				content_type = g_strdup (guess_type[i]);
 414 			} else if (!g_strcmp0 (guess_type[i], "x-content/blank-bd") ||
 415 			           !g_strcmp0 (guess_type[i], "x-content/blank-cd") ||
 416 			           !g_strcmp0 (guess_type[i], "x-content/blank-dvd") ||
 417 			           !g_strcmp0 (guess_type[i], "x-content/blank-hddvd")) {
 418 				/* Blank */
 419 				*is_blank = TRUE;
 420 				content_type = g_strdup (guess_type[i]);
 421 			} else if (!g_strcmp0 (guess_type[i], "x-content/software") ||
 422 			           !g_strcmp0 (guess_type[i], "x-content/unix-software") ||
 423 			           !g_strcmp0 (guess_type[i], "x-content/win32-software")) {
 424 				/* NOTE: This one is a guess, can we
 425 				 * have this content type on
 426 				 * none-optical mount points?
 427 				 */
 428 				content_type = g_strdup (guess_type[i]);
 429 			} else {
 430 				/* else, keep on with the next guess, if any */
 431 				i++;
 432 			}
 433 		}
 434 
 435 		/* If we didn't have an exact match on possible guessed content types,
 436 		 *  then use the first one returned (best guess always first) if any */
 437 		if (!content_type && guess_type[0]) {
 438 			content_type = g_strdup (guess_type[0]);
 439 		}
 440 
 441 		g_strfreev (guess_type);
 442 	}
 443 
 444 	if (content_type) {
 445 		if (strstr (content_type, "vcd") ||
 446 		    strstr (content_type, "cdda") ||
 447 		    strstr (content_type, "dvd") ||
 448 		    strstr (content_type, "bluray")) {
 449 			*is_optical = TRUE;
 450 		}
 451 	} else {
 452 		GUnixMountEntry *entry;
 453 		gchar *mount_path;
 454 		GFile *mount_root;
 455 
 456 		/* No content type was guessed, try to find out
 457 		 * at least whether it's an optical media or not
 458 		 */
 459 		mount_root = g_mount_get_root (mount);
 460 		mount_path = g_file_get_path (mount_root);
 461 
 462 		/* FIXME: Try to assume we have a unix mount :(
 463 		 * EEK, once in a while, I have to write crack, oh well
 464 		 */
 465 		if (mount_path &&
 466 		    (entry = g_unix_mount_at (mount_path, NULL)) != NULL) {
 467 			const gchar *filesystem_type;
 468 			gchar *device_path = NULL;
 469 			GVolume *volume;
 470 
 471 			volume = g_mount_get_volume (mount);
 472 			filesystem_type = g_unix_mount_get_fs_type (entry);
 473 			g_debug ("  Using filesystem type:'%s'",
 474 			         filesystem_type);
 475 
 476 			/* Volume may be NULL */
 477 			if (volume) {
 478 				device_path = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
 479 				g_debug ("  Using device path:'%s'",
 480 				         device_path);
 481 				g_object_unref (volume);
 482 			}
 483 
 484 			/* NOTE: This code was taken from guess_mount_type()
 485 			 * in GIO's gunixmounts.c and adapted purely for
 486 			 * guessing optical media. We don't use the guessing
 487 			 * code for other types such as MEMSTICKS, ZIPs,
 488 			 * IPODs, etc.
 489 			 *
 490 			 * This code may need updating over time since it is
 491 			 * very situational depending on how distributions
 492 			 * mount their devices and how devices are named in
 493 			 * /dev.
 494 			 */
 495 			if (strcmp (filesystem_type, "udf") == 0 ||
 496 			    strcmp (filesystem_type, "iso9660") == 0 ||
 497 			    strcmp (filesystem_type, "cd9660") == 0 ||
 498 			    (device_path &&
 499 			     (g_str_has_prefix (device_path, "/dev/cdrom") ||
 500 			      g_str_has_prefix (device_path, "/dev/acd") ||
 501 			      g_str_has_prefix (device_path, "/dev/cd")))) {
 502 				*is_optical = TRUE;
 503 			} else if (device_path &&
 504 			           g_str_has_prefix (device_path, "/vol/")) {
 505 				const gchar *name;
 506 
 507 				name = mount_path + strlen ("/");
 508 
 509 				if (g_str_has_prefix (name, "cdrom")) {
 510 					*is_optical = TRUE;
 511 				}
 512 			} else {
 513 				gchar *basename = g_path_get_basename (mount_path);
 514 
 515 				if (g_str_has_prefix (basename, "cdr") ||
 516 				    g_str_has_prefix (basename, "cdwriter") ||
 517 				    g_str_has_prefix (basename, "burn") ||
 518 				    g_str_has_prefix (basename, "dvdr")) {
 519 					*is_optical = TRUE;
 520 				}
 521 
 522 				g_free (basename);
 523 			}
 524 
 525 			g_free (device_path);
 526 			g_free (mount_path);
 527 			g_unix_mount_free (entry);
 528 		} else {
 529 			g_debug ("  No GUnixMountEntry found, needed for detecting if optical media... :(");
 530 			g_free (mount_path);
 531 		}
 532 
 533 		g_object_unref (mount_root);
 534 	}
 535 
 536 	return content_type;
 537 }
 538 
 539 static void
 540 mount_add (TrackerStorage *storage,
 541            GMount         *mount)
 542 {
 543 	TrackerStoragePrivate *priv;
 544 	GFile *root;
 545 	GVolume *volume;
 546 	gchar *mount_name, *mount_path, *uuid;
 547 	gboolean is_optical = FALSE;
 548 	gboolean is_removable = FALSE;
 549 
 550 	/* Get mount name */
 551 	mount_name = g_mount_get_name (mount);
 552 
 553 	/* Get root path of the mount */
 554 	root = g_mount_get_root (mount);
 555 	mount_path = g_file_get_path (root);
 556 
 557 	g_debug ("Found '%s' mounted on path '%s'",
 558 	         mount_name,
 559 	         mount_path);
 560 
 561 	/* Do not process shadowed mounts! */
 562 	if (g_mount_is_shadowed (mount)) {
 563 		g_debug ("  Skipping shadowed mount '%s'", mount_name);
 564 		g_object_unref (root);
 565 		g_free (mount_path);
 566 		g_free (mount_name);
 567 		return;
 568 	}
 569 
 570 	priv = TRACKER_STORAGE_GET_PRIVATE (storage);
 571 
 572 	/* fstab partitions may not have corresponding
 573 	 * GVolumes, so volume may be NULL */
 574 	volume = g_mount_get_volume (mount);
 575 	if (volume) {
 576 		/* GMount with GVolume */
 577 
 578 		/* Try to get UUID from the Volume.
 579 		 * Note that g_volume_get_uuid() is NOT equivalent */
 580 		uuid = g_volume_get_identifier (volume,
 581 		                                G_VOLUME_IDENTIFIER_KIND_UUID);
 582 		if (!uuid) {
 583 			gchar *content_type;
 584 			gboolean is_multimedia;
 585 			gboolean is_blank;
 586 
 587 			/* Optical discs usually won't have UUID in the GVolume */
 588 			content_type = mount_guess_content_type (mount, &is_optical, &is_multimedia, &is_blank);
 589 			is_removable = TRUE;
 590 
 591 			/* We don't index content which is video, music or blank */
 592 			if (!is_multimedia && !is_blank) {
 593 				uuid = g_compute_checksum_for_string (G_CHECKSUM_MD5,
 594 				                                      mount_name,
 595 				                                      -1);
 596 				g_debug ("  No UUID, generated:'%s' (based on mount name)", uuid);
 597 				g_debug ("  Assuming GVolume has removable media, if wrong report a bug! "
 598 				         "content type is '%s'",
 599 				         content_type);
 600 			} else {
 601 				g_debug ("  Being ignored because mount with volume is music/video/blank "
 602 				         "(content type:%s, optical:%s, multimedia:%s, blank:%s)",
 603 				         content_type,
 604 				         is_optical ? "yes" : "no",
 605 				         is_multimedia ? "yes" : "no",
 606 				         is_blank ? "yes" : "no");
 607 			}
 608 
 609 			g_free (content_type);
 610 		} else {
 611 			/* Any other removable media will have UUID in the
 612 			 * GVolume. Note that this also may include some
 613 			 * partitions in the machine which have GVolumes
 614 			 * associated to the GMounts. We also check a drive
 615 			 * exists to be sure the device is local. */
 616 			GDrive *drive;
 617 
 618 			drive = g_volume_get_drive (volume);
 619 
 620 			if (drive) {
 621 				/* We can't mount/unmount system volumes, so tag
 622 				 * them as non removable. */
 623 				is_removable = g_volume_can_mount (volume);
 624 				g_debug ("  Found mount with volume and drive which %s be mounted: "
 625 				         "Assuming it's %s removable, if wrong report a bug!",
 626 				         is_removable ? "can" : "cannot",
 627 				         is_removable ? "" : "not");
 628 				g_object_unref (drive);
 629 			} else {
 630 				/* Note: not sure when this can happen... */
 631 				g_debug ("  Mount with volume but no drive, "
 632 				         "assuming not a removable device, "
 633 				         "if wrong report a bug!");
 634 				is_removable = FALSE;
 635 			}
 636 		}
 637 
 638 		g_object_unref (volume);
 639 	} else {
 640 		/* GMount without GVolume.
 641 		 * Note: Never found a case where this g_mount_get_uuid() returns
 642 		 * non-NULL... :-) */
 643 		uuid = g_mount_get_uuid (mount);
 644 		if (!uuid) {
 645 			if (mount_path) {
 646 				gchar *content_type;
 647 				gboolean is_multimedia;
 648 				gboolean is_blank;
 649 
 650 				content_type = mount_guess_content_type (mount, &is_optical, &is_multimedia, &is_blank);
 651 
 652 				/* Note: for GMounts without GVolume, is_blank should NOT be considered,
 653 				 * as it may give unwanted results... */
 654 				if (!is_multimedia) {
 655 					uuid = g_compute_checksum_for_string (G_CHECKSUM_MD5,
 656 					                                      mount_path,
 657 					                                      -1);
 658 					g_debug ("  No UUID, generated:'%s' (based on mount path)", uuid);
 659 				} else {
 660 					g_debug ("  Being ignored because mount is music/video "
 661 					         "(content type:%s, optical:%s, multimedia:%s)",
 662 					         content_type,
 663 					         is_optical ? "yes" : "no",
 664 					         is_multimedia ? "yes" : "no");
 665 				}
 666 
 667 				g_free (content_type);
 668 			} else {
 669 				g_debug ("  Being ignored because mount has no GVolume (i.e. not user mountable) "
 670 				         "and has no mount root path available");
 671 			}
 672 		}
 673 	}
 674 
 675 	/* If we got something to be used as UUID, then add the mount
 676 	 * to the TrackerStorage */
 677 	if (uuid && mount_path && !g_hash_table_lookup (priv->mounts_by_uuid, uuid)) {
 678 		g_debug ("  Adding mount point with UUID: '%s', removable: %s, optical: %s, path: '%s'",
 679 		         uuid,
 680 		         is_removable ? "yes" : "no",
 681 		         is_optical ? "yes" : "no",
 682 		         mount_path);
 683 		mount_add_new (storage, uuid, mount_path, mount_name, is_removable, is_optical);
 684 	} else {
 685 		g_debug ("  Skipping mount point with UUID: '%s', path: '%s', already managed: '%s'",
 686 		         uuid ? uuid : "none",
 687 		         mount_path ? mount_path : "none",
 688 		         (uuid && g_hash_table_lookup (priv->mounts_by_uuid, uuid)) ? "yes" : "no");
 689 	}
 690 
 691 	g_free (mount_name);
 692 	g_free (mount_path);
 693 	g_free (uuid);
 694 	g_object_unref (root);
 695 }
 696 
 697 static gboolean
 698 mounts_setup (TrackerStorage *storage)
 699 {
 700 	TrackerStoragePrivate *priv;
 701 	GList *mounts, *lm;
 702 
 703 	priv = TRACKER_STORAGE_GET_PRIVATE (storage);
 704 
 705 	mounts = g_volume_monitor_get_mounts (priv->volume_monitor);
 706 
 707 	if (!mounts) {
 708 		g_message ("No mounts found to iterate");
 709 		return TRUE;
 710 	}
 711 
 712 	/* Iterate over all available mounts and add them.
 713 	 * Note that GVolumeMonitor shows only those mounts which are
 714 	 * actually mounted. */
 715 	for (lm = mounts; lm; lm = g_list_next (lm)) {
 716 		mount_add (storage, lm->data);
 717 		g_object_unref (lm->data);
 718 	}
 719 
 720 	g_list_free (mounts);
 721 
 722 	return TRUE;
 723 }
 724 
 725 static void
 726 mount_added_cb (GVolumeMonitor *monitor,
 727                 GMount         *mount,
 728                 gpointer        user_data)
 729 {
 730 	mount_add (user_data, mount);
 731 }
 732 
 733 static void
 734 mount_remove (TrackerStorage *storage,
 735               GMount         *mount)
 736 {
 737 	TrackerStoragePrivate *priv;
 738 	MountInfo *info;
 739 	GNode *node;
 740 	GFile *file;
 741 	gchar *name;
 742 	gchar *mount_point;
 743 	gchar *mp;
 744 
 745 	priv = TRACKER_STORAGE_GET_PRIVATE (storage);
 746 
 747 	file = g_mount_get_root (mount);
 748 	mount_point = g_file_get_path (file);
 749 	name = g_mount_get_name (mount);
 750 
 751 	mp = mount_point_normalize (mount_point);
 752 	node = mount_node_find (priv->mounts, mp);
 753 	g_free (mp);
 754 
 755 	if (node) {
 756 		info = node->data;
 757 
 758 		g_message ("Mount:'%s' with UUID:'%s' now unmounted from:'%s'",
 759 		           name,
 760 		           info->uuid,
 761 		           mount_point);
 762 
 763 		g_signal_emit (storage, signals[MOUNT_POINT_REMOVED], 0, info->uuid, mount_point, NULL);
 764 
 765 		g_hash_table_remove (priv->mounts_by_uuid, info->uuid);
 766 		mount_node_free (node);
 767 	} else {
 768 		g_message ("Mount:'%s' now unmounted from:'%s' (was not tracked)",
 769 		           name,
 770 		           mount_point);
 771 	}
 772 
 773 	g_free (name);
 774 	g_free (mount_point);
 775 	g_object_unref (file);
 776 }
 777 
 778 static void
 779 mount_removed_cb (GVolumeMonitor *monitor,
 780                   GMount         *mount,
 781                   gpointer        user_data)
 782 {
 783 	TrackerStorage *storage;
 784 	TrackerStoragePrivate *priv;
 785 
 786 	storage = user_data;
 787 	priv = TRACKER_STORAGE_GET_PRIVATE (storage);
 788 
 789 	mount_remove (storage, mount);
 790 
 791 	/* Unmount suceeded, remove the pending check */
 792 	g_hash_table_remove (priv->unmount_watchdogs, mount);
 793 }
 794 
 795 static gboolean
 796 unmount_failed_cb (gpointer user_data)
 797 {
 798 	UnmountCheckData *data = user_data;
 799 	TrackerStoragePrivate *priv;
 800 
 801 	/* If this timeout gets to be executed, this is due
 802 	 * to a pre-unmount signal with no corresponding
 803 	 * unmount in a timely fashion, we assume this is
 804 	 * due to an error, and add back the still mounted
 805 	 * path.
 806 	 */
 807 	priv = TRACKER_STORAGE_GET_PRIVATE (data->storage);
 808 
 809 	g_warning ("Unmount operation failed, adding back mount point...");
 810 
 811 	mount_add (data->storage, data->mount);
 812 
 813 	g_hash_table_remove (priv->unmount_watchdogs, data->mount);
 814 	return FALSE;
 815 }
 816 
 817 static void
 818 mount_pre_removed_cb (GVolumeMonitor *monitor,
 819                       GMount         *mount,
 820                       gpointer        user_data)
 821 {
 822 	TrackerStorage *storage;
 823 	TrackerStoragePrivate *priv;
 824 	UnmountCheckData *data;
 825 	guint id;
 826 
 827 	storage = user_data;
 828 	priv = TRACKER_STORAGE_GET_PRIVATE (storage);
 829 
 830 	mount_remove (storage, mount);
 831 
 832 	/* Add check for failed unmounts */
 833 	data = g_new (UnmountCheckData, 1);
 834 	data->storage = storage;
 835 	data->mount = mount;
 836 
 837 	id = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT_IDLE + 10, 3,
 838 	                                 unmount_failed_cb,
 839 	                                 data, (GDestroyNotify) g_free);
 840 	g_hash_table_insert (priv->unmount_watchdogs, data->mount,
 841 	                     GUINT_TO_POINTER (id));
 842 }
 843 
 844 /**
 845  * tracker_storage_new:
 846  *
 847  * Creates a new instance of #TrackerStorage.
 848  *
 849  * Returns: The newly created #TrackerStorage.
 850  *
 851  * Since: 0.8
 852  **/
 853 TrackerStorage *
 854 tracker_storage_new (void)
 855 {
 856 	return g_object_new (TRACKER_TYPE_STORAGE, NULL);
 857 }
 858 
 859 static void
 860 get_mount_point_by_uuid_foreach (gpointer key,
 861                                  gpointer value,
 862                                  gpointer user_data)
 863 {
 864 	GetRoots *gr;
 865 	GNode *node;
 866 	MountInfo *info;
 867 	TrackerStorageType mount_type;
 868 
 869 	gr = user_data;
 870 	node = value;
 871 	info = node->data;
 872 	mount_type = mount_info_get_type (info);
 873 
 874 	/* is mount of the type we're looking for? */
 875 	if ((gr->exact_match && mount_type == gr->type) ||
 876 	    (!gr->exact_match && (mount_type & gr->type))) {
 877 		gchar *normalized_mount_point;
 878 		gint len;
 879 
 880 		normalized_mount_point = g_strdup (info->mount_point);
 881 		len = strlen (normalized_mount_point);
 882 
 883 		/* Don't include trailing slashes */
 884 		if (len > 2 && normalized_mount_point[len - 1] == G_DIR_SEPARATOR) {
 885 			normalized_mount_point[len - 1] = '\0';
 886 		}
 887 
 888 		gr->roots = g_slist_prepend (gr->roots, normalized_mount_point);
 889 	}
 890 }
 891 
 892 /**
 893  * tracker_storage_get_device_roots:
 894  * @storage: A #TrackerStorage
 895  * @type: A #TrackerStorageType
 896  * @exact_match: if all devices should exactly match the types
 897  *
 898  * Returns: (transfer full) (element-type utf8): a #GSList of strings
 899  * containing the root directories for devices with @type based on
 900  * @exact_match. Each element must be freed using g_free() and the
 901  * list itself through g_slist_free().
 902  *
 903  * Since: 0.8
 904  **/
 905 GSList *
 906 tracker_storage_get_device_roots (TrackerStorage     *storage,
 907                                   TrackerStorageType  type,
 908                                   gboolean            exact_match)
 909 {
 910 	TrackerStoragePrivate *priv;
 911 	GetRoots gr;
 912 
 913 	g_return_val_if_fail (TRACKER_IS_STORAGE (storage), NULL);
 914 
 915 	priv = TRACKER_STORAGE_GET_PRIVATE (storage);
 916 
 917 	gr.roots = NULL;
 918 	gr.type = type;
 919 	gr.exact_match = exact_match;
 920 
 921 	g_hash_table_foreach (priv->mounts_by_uuid,
 922 	                      get_mount_point_by_uuid_foreach,
 923 	                      &gr);
 924 
 925 	return gr.roots;
 926 }
 927 
 928 /**
 929  * tracker_storage_get_device_uuids:
 930  * @storage: A #TrackerStorage
 931  * @type: A #TrackerStorageType
 932  * @exact_match: if all devices should exactly match the types
 933  *
 934  * Returns: (transfer full) (element-type utf8): a #GSList of
 935  * strings containing the UUID for devices with @type based
 936  * on @exact_match. Each element must be freed using g_free()
 937  * and the list itself through g_slist_free().
 938  *
 939  * Since: 0.8
 940  **/
 941 GSList *
 942 tracker_storage_get_device_uuids (TrackerStorage     *storage,
 943                                   TrackerStorageType  type,
 944                                   gboolean            exact_match)
 945 {
 946 	TrackerStoragePrivate *priv;
 947 	GHashTableIter iter;
 948 	gpointer key, value;
 949 	GSList *uuids;
 950 
 951 	g_return_val_if_fail (TRACKER_IS_STORAGE (storage), NULL);
 952 
 953 	priv = TRACKER_STORAGE_GET_PRIVATE (storage);
 954 
 955 	uuids = NULL;
 956 
 957 	g_hash_table_iter_init (&iter, priv->mounts_by_uuid);
 958 
 959 	while (g_hash_table_iter_next (&iter, &key, &value)) {
 960 		const gchar *uuid;
 961 		GNode *node;
 962 		MountInfo *info;
 963 		TrackerStorageType mount_type;
 964 
 965 		uuid = key;
 966 		node = value;
 967 		info = node->data;
 968 
 969 		mount_type = mount_info_get_type (info);
 970 
 971 		/* is mount of the type we're looking for? */
 972 		if ((exact_match && mount_type == type) ||
 973 		    (!exact_match && (mount_type & type))) {
 974 			uuids = g_slist_prepend (uuids, g_strdup (uuid));
 975 		}
 976 	}
 977 
 978 	return uuids;
 979 }
 980 
 981 /**
 982  * tracker_storage_get_mount_point_for_uuid:
 983  * @storage: A #TrackerStorage
 984  * @uuid: A string pointer to the UUID for the %GVolume.
 985  *
 986  * Returns: The mount point for @uuid, this should not be freed.
 987  *
 988  * Since: 0.8
 989  **/
 990 const gchar *
 991 tracker_storage_get_mount_point_for_uuid (TrackerStorage *storage,
 992                                           const gchar    *uuid)
 993 {
 994 	TrackerStoragePrivate *priv;
 995 	GNode *node;
 996 	MountInfo *info;
 997 
 998 	g_return_val_if_fail (TRACKER_IS_STORAGE (storage), NULL);
 999 	g_return_val_if_fail (uuid != NULL, NULL);
1000 
1001 	priv = TRACKER_STORAGE_GET_PRIVATE (storage);
1002 
1003 	node = g_hash_table_lookup (priv->mounts_by_uuid, uuid);
1004 
1005 	if (!node) {
1006 		return NULL;
1007 	}
1008 
1009 	info = node->data;
1010 
1011 	return info->mount_point;
1012 }
1013 
1014 /**
1015  * tracker_storage_get_type_for_uuid:
1016  * @storage: A #TrackerStorage
1017  * @uuid: A string pointer to the UUID for the %GVolume.
1018  *
1019  * Returns: The type flags for @uuid.
1020  *
1021  * Since: 0.10
1022  **/
1023 TrackerStorageType
1024 tracker_storage_get_type_for_uuid (TrackerStorage     *storage,
1025                                    const gchar        *uuid)
1026 {
1027 	TrackerStoragePrivate *priv;
1028 	GNode *node;
1029 	TrackerStorageType type = 0;
1030 
1031 	g_return_val_if_fail (TRACKER_IS_STORAGE (storage), 0);
1032 	g_return_val_if_fail (uuid != NULL, 0);
1033 
1034 	priv = TRACKER_STORAGE_GET_PRIVATE (storage);
1035 
1036 	node = g_hash_table_lookup (priv->mounts_by_uuid, uuid);
1037 
1038 	if (node) {
1039 		MountInfo *info;
1040 
1041 		info = node->data;
1042 
1043 		if (info->removable) {
1044 			type |= TRACKER_STORAGE_REMOVABLE;
1045 		}
1046 		if (info->optical) {
1047 			type |= TRACKER_STORAGE_OPTICAL;
1048 		}
1049 	}
1050 
1051 	return type;
1052 }
1053 
1054 /**
1055  * tracker_storage_get_uuid_for_file:
1056  * @storage: A #TrackerStorage
1057  * @file: a file
1058  *
1059  * Returns the UUID of the removable device for @file
1060  *
1061  * Returns: Returns the UUID of the removable device for @file, this
1062  * should not be freed.
1063  *
1064  * Since: 0.8
1065  **/
1066 const gchar *
1067 tracker_storage_get_uuid_for_file (TrackerStorage *storage,
1068                                    GFile          *file)
1069 {
1070 	TrackerStoragePrivate *priv;
1071 	gchar *path;
1072 	MountInfo *info;
1073 
1074 	g_return_val_if_fail (TRACKER_IS_STORAGE (storage), FALSE);
1075 
1076 	path = g_file_get_path (file);
1077 
1078 	if (!path) {
1079 		return NULL;
1080 	}
1081 
1082 	/* Normalize all paths to have a / at the end */
1083 	if (!g_str_has_suffix (path, G_DIR_SEPARATOR_S)) {
1084 		gchar *norm_path;
1085 
1086 		norm_path = g_strconcat (path, G_DIR_SEPARATOR_S, NULL);
1087 		g_free (path);
1088 		path = norm_path;
1089 	}
1090 
1091 	priv = TRACKER_STORAGE_GET_PRIVATE (storage);
1092 
1093 	info = mount_info_find (priv->mounts, path);
1094 
1095 	if (!info) {
1096 		g_free (path);
1097 		return NULL;
1098 	}
1099 
1100 	/* g_debug ("Mount for path '%s' is '%s' (UUID:'%s')", */
1101 	/*          path, info->mount_point, info->uuid); */
1102 
1103 	g_free (path);
1104 
1105 	return info->uuid;
1106 }