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 }