1 /*
2 * Copyright (C) 2012 Bastien Nocera
3 *
4 * Contact: Bastien Nocera <hadess@hadess.net>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public License
8 * as published by the Free Software Foundation; version 2.1 of
9 * the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19 * 02110-1301 USA
20 *
21 */
22
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26
27 #include <grilo.h>
28 #include <gio/gio.h>
29 #include <glib/gi18n-lib.h>
30 #include <totem-pl-parser.h>
31 #include <string.h>
32 #include <stdlib.h>
33
34 #include "grl-optical-media.h"
35
36 /* --------- Logging -------- */
37
38 #define GRL_LOG_DOMAIN_DEFAULT optical_media_log_domain
39 GRL_LOG_DOMAIN_STATIC(optical_media_log_domain);
40
41 /* --- Plugin information --- */
42
43 #define SOURCE_ID "grl-optical-media"
44 #define SOURCE_NAME _("Optical Media")
45 #define SOURCE_DESC _("A source for browsing optical media")
46
47 /* --- Grilo OpticalMedia Private --- */
48
49 #define NUM_MONITOR_SIGNALS 3
50
51 struct _GrlOpticalMediaSourcePrivate {
52 GVolumeMonitor *monitor;
53 guint monitor_signal_ids[NUM_MONITOR_SIGNALS];
54 /* List of GrlMedia */
55 GList *list;
56 GHashTable *ignored_schemes;
57 GCancellable *cancellable;
58 gboolean notify_changes;
59 };
60
61 /* --- Data types --- */
62
63 static GrlOpticalMediaSource *grl_optical_media_source_new (void);
64
65 static void grl_optical_media_source_finalize (GObject *object);
66
67 gboolean grl_optical_media_plugin_init (GrlRegistry *registry,
68 GrlPlugin *plugin,
69 GList *configs);
70
71 static const GList *grl_optical_media_source_supported_keys (GrlSource *source);
72
73 static void grl_optical_media_source_browse (GrlSource *source,
74 GrlSourceBrowseSpec *bs);
75
76 static gboolean grl_optical_media_source_notify_change_start (GrlSource *source,
77 GError **error);
78
79 static gboolean grl_optical_media_source_notify_change_stop (GrlSource *source,
80 GError **error);
81
82 static void grl_optical_media_source_cancel (GrlSource *source,
83 guint operation_id);
84
85 static void
86 on_g_volume_monitor_removed_event (GVolumeMonitor *monitor,
87 GMount *mount,
88 GrlOpticalMediaSource *source);
89 static void
90 on_g_volume_monitor_changed_event (GVolumeMonitor *monitor,
91 GMount *mount,
92 GrlOpticalMediaSource *source);
93 static void
94 on_g_volume_monitor_added_event (GVolumeMonitor *monitor,
95 GMount *mount,
96 GrlOpticalMediaSource *source);
97
98 /* =================== OpticalMedia Plugin =============== */
99
100 static char *
normalise_scheme(const char * scheme)101 normalise_scheme (const char *scheme)
102 {
103 char *s;
104
105 if (scheme == NULL)
106 return NULL;
107
108 if (!g_ascii_isalnum (scheme[0])) {
109 GRL_DEBUG ("Ignoring 'ignore-scheme' '%s' as it is not valid", scheme);
110 return NULL;
111 }
112
113 for (s = (char *) (scheme + 1); *s != '\0'; s++) {
114 if (!g_ascii_isalnum (*s) &&
115 *s != '+' &&
116 *s != '-' &&
117 *s != '.') {
118 GRL_DEBUG ("Ignoring 'ignore-scheme' '%s' as it is not valid", scheme);
119 return NULL;
120 }
121 }
122
123 return g_ascii_strdown (scheme, -1);
124 }
125
126 gboolean
grl_optical_media_plugin_init(GrlRegistry * registry,GrlPlugin * plugin,GList * configs)127 grl_optical_media_plugin_init (GrlRegistry *registry,
128 GrlPlugin *plugin,
129 GList *configs)
130 {
131 GrlOpticalMediaSource *source;
132
133 GRL_LOG_DOMAIN_INIT (optical_media_log_domain, "optical_media");
134
135 GRL_DEBUG ("%s", __FUNCTION__);
136
137 /* Initialize i18n */
138 bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
139 bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
140
141 source = grl_optical_media_source_new ();
142 source->priv->ignored_schemes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
143
144 for (; configs; configs = g_list_next (configs)) {
145 GrlConfig *config = configs->data;
146 gchar *scheme, *normalised_scheme;
147
148 scheme = grl_config_get_string (config, GRILO_CONF_IGNORED_SCHEME);
149 normalised_scheme = normalise_scheme (scheme);
150 g_free (scheme);
151 if (normalised_scheme)
152 g_hash_table_insert (source->priv->ignored_schemes, normalised_scheme, GINT_TO_POINTER(1));
153 else
154 g_free (normalised_scheme);
155 }
156
157 grl_registry_register_source (registry,
158 plugin,
159 GRL_SOURCE (source),
160 NULL);
161
162 return TRUE;
163 }
164
165 GRL_PLUGIN_DEFINE (GRL_MAJOR,
166 GRL_MINOR,
167 OPTICAL_MEDIA_PLUGIN_ID,
168 "Optical Media",
169 "A plugin for browsing optical media",
170 "GNOME",
171 VERSION,
172 "LGPL-2.1-or-later",
173 "http://www.gnome.org",
174 grl_optical_media_plugin_init,
175 NULL,
176 NULL);
177
178 /* ================== OpticalMedia GObject ================ */
179
180
G_DEFINE_TYPE_WITH_PRIVATE(GrlOpticalMediaSource,grl_optical_media_source,GRL_TYPE_SOURCE)181 G_DEFINE_TYPE_WITH_PRIVATE (GrlOpticalMediaSource, grl_optical_media_source, GRL_TYPE_SOURCE)
182
183 static GrlOpticalMediaSource *
184 grl_optical_media_source_new (void)
185 {
186 GRL_DEBUG ("%s", __FUNCTION__);
187
188 return g_object_new (GRL_OPTICAL_MEDIA_SOURCE_TYPE,
189 "source-id", SOURCE_ID,
190 "source-name", SOURCE_NAME,
191 "source-desc", SOURCE_DESC,
192 NULL);
193 }
194
195 static void
grl_optical_media_source_class_init(GrlOpticalMediaSourceClass * klass)196 grl_optical_media_source_class_init (GrlOpticalMediaSourceClass * klass)
197 {
198 GObjectClass *object_class = G_OBJECT_CLASS (klass);
199 GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass);
200
201 object_class->finalize = grl_optical_media_source_finalize;
202
203 source_class->supported_keys = grl_optical_media_source_supported_keys;
204 source_class->cancel = grl_optical_media_source_cancel;
205 source_class->browse = grl_optical_media_source_browse;
206
207 source_class->notify_change_start = grl_optical_media_source_notify_change_start;
208 source_class->notify_change_stop = grl_optical_media_source_notify_change_stop;
209 }
210
211 static void
grl_optical_media_source_init(GrlOpticalMediaSource * source)212 grl_optical_media_source_init (GrlOpticalMediaSource *source)
213 {
214 source->priv = grl_optical_media_source_get_instance_private (source);
215
216 source->priv->cancellable = g_cancellable_new ();
217 source->priv->monitor = g_volume_monitor_get ();
218
219 source->priv->monitor_signal_ids[0] = g_signal_connect (G_OBJECT (source->priv->monitor), "mount-added",
220 G_CALLBACK (on_g_volume_monitor_added_event), source);
221 source->priv->monitor_signal_ids[1] = g_signal_connect (G_OBJECT (source->priv->monitor), "mount-changed",
222 G_CALLBACK (on_g_volume_monitor_changed_event), source);
223 source->priv->monitor_signal_ids[2] = g_signal_connect (G_OBJECT (source->priv->monitor), "mount-removed",
224 G_CALLBACK (on_g_volume_monitor_removed_event), source);
225 }
226
227 static void
grl_optical_media_source_finalize(GObject * object)228 grl_optical_media_source_finalize (GObject *object)
229 {
230 GrlOpticalMediaSource *source = GRL_OPTICAL_MEDIA_SOURCE (object);
231 guint i;
232
233 g_cancellable_cancel (source->priv->cancellable);
234 g_clear_object (&source->priv->cancellable);
235 g_hash_table_destroy (source->priv->ignored_schemes);
236 source->priv->ignored_schemes = NULL;
237
238 for (i = 0; i < NUM_MONITOR_SIGNALS; i++) {
239 g_signal_handler_disconnect (G_OBJECT (source->priv->monitor),
240 source->priv->monitor_signal_ids[i]);
241 }
242
243 g_list_free_full (source->priv->list, g_object_unref);
244
245 g_clear_object (&source->priv->monitor);
246
247 G_OBJECT_CLASS (grl_optical_media_source_parent_class)->finalize (object);
248 }
249
250 /* ======================= Utilities ==================== */
251
252 static char *
create_mount_id(GMount * mount)253 create_mount_id (GMount *mount)
254 {
255 GFile *root;
256 char *uri;
257
258 root = g_mount_get_root (mount);
259 uri = g_file_get_uri (root);
260 g_object_unref (root);
261
262 return uri;
263 }
264
265 static char *
get_uri_for_gicon(GIcon * icon)266 get_uri_for_gicon (GIcon *icon)
267 {
268 char *uri;
269
270 uri = NULL;
271
272 if (G_IS_EMBLEMED_ICON (icon) != FALSE) {
273 GIcon *new_icon;
274 new_icon = g_emblemed_icon_get_icon (G_EMBLEMED_ICON (icon));
275 icon = new_icon;
276 }
277
278 if (G_IS_FILE_ICON (icon) != FALSE) {
279 GFile *file;
280
281 file = g_file_icon_get_file (G_FILE_ICON (icon));
282 uri = g_file_get_uri (file);
283
284 return uri;
285 }
286
287 /* We leave the themed icons up to the applications to set */
288
289 return uri;
290 }
291
292 static void
media_set_metadata(GMount * mount,GrlMedia * media)293 media_set_metadata (GMount *mount,
294 GrlMedia *media)
295 {
296 char *name, *icon_uri;
297 GIcon *icon;
298
299 /* Work out an icon to display */
300 icon = g_mount_get_icon (mount);
301 icon_uri = get_uri_for_gicon (icon);
302 g_object_unref (icon);
303 grl_media_set_thumbnail (media, icon_uri);
304 g_free (icon_uri);
305
306 /* Get the mount's pretty name for the menu label */
307 name = g_mount_get_name (mount);
308 g_strstrip (name);
309 grl_media_set_title (media, name);
310 g_free (name);
311 }
312
313 static gint
find_mount(gconstpointer a,gconstpointer b)314 find_mount (gconstpointer a,
315 gconstpointer b)
316 {
317 GrlMedia *media = (GrlMedia *) a;
318 GMount *mount = (GMount *) b;
319 char *id;
320 gint ret;
321
322 id = create_mount_id (mount);
323 ret = g_strcmp0 (id, grl_media_get_id (media));
324 g_free (id);
325 return ret;
326 }
327
328 static gboolean
ignore_drive(GDrive * drive)329 ignore_drive (GDrive *drive)
330 {
331 GIcon *icon;
332
333 if (g_drive_can_eject (drive) == FALSE ||
334 g_drive_has_media (drive) == FALSE) {
335 GRL_DEBUG ("%s: Not adding %s as cannot eject or has no media", __FUNCTION__,
336 g_drive_get_name (drive));
337 return TRUE;
338 }
339
340 /* Hack to avoid USB devices showing up
341 * https://bugzilla.gnome.org/show_bug.cgi?id=679624 */
342 icon = g_drive_get_icon (drive);
343 if (icon && G_IS_THEMED_ICON (icon)) {
344 const gchar * const * names;
345 names = g_themed_icon_get_names (G_THEMED_ICON (icon));
346 if (names && names[0] && !g_str_has_prefix (names[0], "drive-optical")) {
347 g_object_unref (icon);
348 GRL_DEBUG ("%s: Not adding drive %s as is not optical drive", __FUNCTION__,
349 g_drive_get_name (drive));
350 return TRUE;
351 }
352 }
353 g_clear_object (&icon);
354
355 return FALSE;
356 }
357
358 static gboolean
ignore_volume(GVolume * volume)359 ignore_volume (GVolume *volume)
360 {
361 gboolean ret = TRUE;
362 char *path;
363 GDrive *drive;
364
365 /* Ignore drive? */
366 drive = g_volume_get_drive (volume);
367 if (drive != NULL && ignore_drive (drive)) {
368 g_object_unref (drive);
369 return TRUE;
370 }
371 g_clear_object (&drive);
372
373 path = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
374
375 if (path != NULL) {
376 ret = FALSE;
377 g_free (path);
378 } else {
379 GRL_DEBUG ("%s: Not adding volume %s as it has no identifier", __FUNCTION__,
380 g_volume_get_name (volume));
381 }
382
383 return ret;
384 }
385
386 static gboolean
ignore_mount(GMount * mount)387 ignore_mount (GMount *mount)
388 {
389 GFile *root;
390 GVolume *volume;
391 gboolean ret = TRUE;
392
393 root = g_mount_get_root (mount);
394
395 if (g_file_has_uri_scheme (root, "burn") != FALSE || g_file_has_uri_scheme (root, "cdda") != FALSE) {
396 /* We don't add Audio CDs, or blank media */
397 g_object_unref (root);
398 GRL_DEBUG ("%s: Not adding mount %s as is burn or cdda", __FUNCTION__,
399 g_mount_get_name (mount));
400 return TRUE;
401 }
402 g_object_unref (root);
403
404 volume = g_mount_get_volume (mount);
405 if (volume == NULL)
406 return ret;
407
408 ret = ignore_volume (volume);
409 g_object_unref (volume);
410
411 return ret;
412 }
413
414 static GrlMedia *
create_media_from_mount(GMount * mount)415 create_media_from_mount (GMount *mount)
416 {
417 char *id;
418 GrlMedia *media;
419
420 /* Is it an audio CD or a blank media */
421 if (ignore_mount (mount)) {
422 GRL_DEBUG ("%s: Ignoring mount %s", __FUNCTION__,
423 g_mount_get_name (mount));
424 g_object_unref (mount);
425 return NULL;
426 }
427
428 id = create_mount_id (mount);
429 if (id == NULL) {
430 GRL_DEBUG ("%s: Not adding mount %s as has no device path", __FUNCTION__,
431 g_mount_get_name (mount));
432 return NULL;
433 }
434
435 media = grl_media_video_new ();
436
437 grl_media_set_id (media, id);
438 g_free (id);
439
440 media_set_metadata (mount, media);
441 grl_media_set_mime (media, "x-special/device-block");
442
443 GRL_DEBUG ("%s: Adding mount %s (id: %s)", __FUNCTION__,
444 g_mount_get_name (mount), grl_media_get_id (media));
445
446 return media;
447 }
448
449 static void
parsed_finished_item(TotemPlParser * pl,GAsyncResult * result,GrlOpticalMediaSource * source)450 parsed_finished_item (TotemPlParser *pl,
451 GAsyncResult *result,
452 GrlOpticalMediaSource *source)
453 {
454 GrlMedia **media;
455 TotemPlParserResult retval;
456
457 media = g_object_get_data (G_OBJECT (pl), "media");
458 retval = totem_pl_parser_parse_finish (TOTEM_PL_PARSER (pl), result, NULL);
459 if (retval == TOTEM_PL_PARSER_RESULT_SUCCESS &&
460 grl_media_get_url (*media) != NULL) {
461 source->priv->list = g_list_append (source->priv->list, g_object_ref (*media));
462 if (source->priv->notify_changes) {
463 grl_source_notify_change (GRL_SOURCE (source), *media, GRL_CONTENT_ADDED, FALSE);
464 }
465 }
466
467 g_object_unref (*media);
468 g_object_unref (pl);
469 }
470
471 static void
entry_parsed_cb(TotemPlParser * parser,const char * uri,GHashTable * metadata,GrlMedia ** media)472 entry_parsed_cb (TotemPlParser *parser,
473 const char *uri,
474 GHashTable *metadata,
475 GrlMedia **media)
476 {
477 char *scheme;
478
479 g_return_if_fail (*media != NULL);
480 if (grl_media_get_url (*media) != NULL) {
481 GRL_WARNING ("Was going to set media '%s' to URL '%s' but already has URL '%s'",
482 grl_media_get_id (*media),
483 uri,
484 grl_media_get_url (*media));
485 return;
486 }
487
488 scheme = g_uri_parse_scheme (uri);
489 if (scheme != NULL && !g_str_equal (scheme, "file"))
490 grl_media_set_url (*media, uri);
491 g_free (scheme);
492 }
493
494 static void
on_g_volume_monitor_added_event(GVolumeMonitor * monitor,GMount * mount,GrlOpticalMediaSource * source)495 on_g_volume_monitor_added_event (GVolumeMonitor *monitor,
496 GMount *mount,
497 GrlOpticalMediaSource *source)
498 {
499 GrlMedia **media;
500 TotemPlParser *pl;
501
502 if (ignore_mount (mount))
503 return;
504
505 media = (GrlMedia **) g_new0 (gpointer, 1);
506 *media = create_media_from_mount (mount);
507 if (*media == NULL) {
508 g_free (media);
509 return;
510 }
511
512 pl = totem_pl_parser_new ();
513 g_object_set_data (G_OBJECT (pl), "media", media);
514 g_object_set (pl, "recurse", FALSE, NULL);
515 g_signal_connect (G_OBJECT (pl), "entry-parsed",
516 G_CALLBACK (entry_parsed_cb), media);
517 totem_pl_parser_parse_async (pl,
518 grl_media_get_id (*media),
519 FALSE,
520 source->priv->cancellable,
521 (GAsyncReadyCallback) parsed_finished_item,
522 source);
523 }
524
525 static void
on_g_volume_monitor_removed_event(GVolumeMonitor * monitor,GMount * mount,GrlOpticalMediaSource * source)526 on_g_volume_monitor_removed_event (GVolumeMonitor *monitor,
527 GMount *mount,
528 GrlOpticalMediaSource *source)
529 {
530 GList *l;
531 GrlMedia *media;
532
533 l = g_list_find_custom (source->priv->list, mount, find_mount);
534 if (!l)
535 return;
536
537 media = l->data;
538 source->priv->list = g_list_remove (source->priv->list, media);
539 if (source->priv->notify_changes) {
540 grl_source_notify_change (GRL_SOURCE (source), media, GRL_CONTENT_REMOVED, FALSE);
541 }
542 g_object_unref (media);
543 }
544
545 static void
on_g_volume_monitor_changed_event(GVolumeMonitor * monitor,GMount * mount,GrlOpticalMediaSource * source)546 on_g_volume_monitor_changed_event (GVolumeMonitor *monitor,
547 GMount *mount,
548 GrlOpticalMediaSource *source)
549 {
550 GList *l;
551
552 l = g_list_find_custom (source->priv->list, mount, find_mount);
553 if (!l)
554 return;
555
556 media_set_metadata (mount, l->data);
557
558 if (source->priv->notify_changes) {
559 grl_source_notify_change (GRL_SOURCE (source), l->data, GRL_CONTENT_CHANGED, FALSE);
560 }
561 }
562
563 /* ================== API Implementation ================ */
564
565 static const GList *
grl_optical_media_source_supported_keys(GrlSource * source)566 grl_optical_media_source_supported_keys (GrlSource *source)
567 {
568 static GList *keys = NULL;
569 if (!keys) {
570 keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ID,
571 GRL_METADATA_KEY_TITLE,
572 GRL_METADATA_KEY_URL,
573 GRL_METADATA_KEY_MIME,
574 NULL);
575 }
576 return keys;
577 }
578
579 typedef struct {
580 TotemPlParser *parser;
581 GCancellable *cancellable;
582 GrlSource *source;
583 GrlSourceBrowseSpec *bs;
584 GList *media_list;
585 GrlMedia *media;
586 } BrowseData;
587
588 static void resolve_disc_urls (BrowseData *data);
589
590 static gboolean
ignore_url(BrowseData * data)591 ignore_url (BrowseData *data)
592 {
593 GrlOpticalMediaSource *source = GRL_OPTICAL_MEDIA_SOURCE (data->bs->source);
594 GrlMedia *media = data->media;
595 char *scheme, *scheme_lower;
596 gboolean ret = FALSE;
597 const char *url;
598
599 url = grl_media_get_url (media);
600 if (url == NULL)
601 return TRUE;
602
603 scheme = g_uri_parse_scheme (url);
604 scheme_lower = g_ascii_strdown (scheme, -1);
605 g_free (scheme);
606 ret = g_hash_table_lookup (source->priv->ignored_schemes, scheme_lower) != NULL;
607 g_free (scheme_lower);
608
609 return ret;
610 }
611
612 static void
parsed_finished(TotemPlParser * pl,GAsyncResult * result,BrowseData * data)613 parsed_finished (TotemPlParser *pl, GAsyncResult *result, BrowseData *data)
614 {
615 TotemPlParserResult retval;
616 GError *error = NULL;
617
618 retval = totem_pl_parser_parse_finish (TOTEM_PL_PARSER (pl), result, &error);
619
620 /* Do the fallback ourselves */
621 if (retval == TOTEM_PL_PARSER_RESULT_IGNORED) {
622 GRL_DEBUG ("%s: Falling back for %s as has it's been ignored", __FUNCTION__,
623 grl_media_get_id (data->media));
624 grl_media_set_url (data->media, grl_media_get_id (data->media));
625 retval = TOTEM_PL_PARSER_RESULT_SUCCESS;
626 }
627
628 if (retval == TOTEM_PL_PARSER_RESULT_SUCCESS &&
629 !ignore_url (data)) {
630 GrlOpticalMediaSource *source;
631
632 source = GRL_OPTICAL_MEDIA_SOURCE (data->bs->source);
633
634 GRL_DEBUG ("%s: Adding %s which resolved to %s", __FUNCTION__,
635 grl_media_get_id (data->media),
636 grl_media_get_url (data->media));
637 data->bs->callback (GRL_SOURCE (source),
638 data->bs->operation_id,
639 data->media,
640 -1,
641 data->bs->user_data,
642 NULL);
643 source->priv->list = g_list_append (source->priv->list, g_object_ref (data->media));
644 } else {
645 if (retval == TOTEM_PL_PARSER_RESULT_ERROR ||
646 retval == TOTEM_PL_PARSER_RESULT_CANCELLED) {
647 GRL_WARNING ("Failed to parse '%s': %s",
648 grl_media_get_id (data->media),
649 error ? error->message : "No reason");
650 g_error_free (error);
651 }
652 g_object_unref (data->media);
653 }
654 data->media = NULL;
655
656 resolve_disc_urls (data);
657 }
658
659 static void
resolve_disc_urls(BrowseData * data)660 resolve_disc_urls (BrowseData *data)
661 {
662 g_assert (data->media == NULL);
663
664 if (data->media_list == NULL ||
665 g_cancellable_is_cancelled (data->cancellable)) {
666 /* If we got cancelled, there's still some media
667 * to resolve here */
668 if (data->media_list) {
669 g_list_free_full (data->media_list, g_object_unref);
670 }
671 /* No media left, we're done */
672 data->bs->callback (data->bs->source,
673 data->bs->operation_id,
674 NULL,
675 0,
676 data->bs->user_data,
677 NULL);
678 g_object_unref (data->cancellable);
679 g_object_unref (data->parser);
680 g_free (data);
681 return;
682 }
683
684 data->media = data->media_list->data;
685 data->media_list = g_list_delete_link (data->media_list, data->media_list);
686
687 totem_pl_parser_parse_async (data->parser,
688 grl_media_get_id (data->media),
689 FALSE,
690 data->cancellable,
691 (GAsyncReadyCallback) parsed_finished,
692 data);
693 }
694
695 static void
grl_optical_media_source_browse(GrlSource * source,GrlSourceBrowseSpec * bs)696 grl_optical_media_source_browse (GrlSource *source,
697 GrlSourceBrowseSpec *bs)
698 {
699 GList *mounts, *l;
700 GrlOpticalMediaSourcePrivate *priv = GRL_OPTICAL_MEDIA_SOURCE (source)->priv;
701 BrowseData *data;
702 GList *media_list;
703
704 GRL_DEBUG ("%s", __FUNCTION__);
705
706 g_list_free_full (priv->list, g_object_unref);
707 priv->list = NULL;
708
709 media_list = NULL;
710
711 /* Look for loopback-mounted ISO images and discs */
712 mounts = g_volume_monitor_get_mounts (priv->monitor);
713 for (l = mounts; l != NULL; l = l->next) {
714 GMount *mount = l->data;
715
716 if (!ignore_mount (mount)) {
717 GrlMedia *media;
718 media = create_media_from_mount (mount);
719 if (media)
720 media_list = g_list_prepend (media_list, media);
721 }
722
723 g_object_unref (mount);
724 }
725 g_list_free (mounts);
726
727 /* Got nothing? */
728 if (media_list == NULL) {
729 /* Tell the caller we're done */
730 bs->callback (bs->source,
731 bs->operation_id,
732 NULL,
733 0,
734 bs->user_data,
735 NULL);
736 return;
737 }
738
739 media_list = g_list_reverse (media_list);
740
741 /* And go to resolve all those devices */
742 data = g_new0 (BrowseData, 1);
743 data->source = source;
744 data->bs = bs;
745 data->media_list = media_list;
746 data->cancellable = g_cancellable_new ();
747
748 grl_operation_set_data (bs->operation_id, data->cancellable);
749
750 data->parser = totem_pl_parser_new ();
751 g_object_set (data->parser, "recurse", FALSE, NULL);
752 g_signal_connect (G_OBJECT (data->parser), "entry-parsed",
753 G_CALLBACK (entry_parsed_cb), &data->media);
754
755 resolve_disc_urls (data);
756 }
757
758 static gboolean
grl_optical_media_source_notify_change_start(GrlSource * source,GError ** error)759 grl_optical_media_source_notify_change_start (GrlSource *source,
760 GError **error)
761 {
762 GrlOpticalMediaSourcePrivate *priv = GRL_OPTICAL_MEDIA_SOURCE (source)->priv;
763
764 priv->notify_changes = TRUE;
765
766 return TRUE;
767 }
768
769 static gboolean
grl_optical_media_source_notify_change_stop(GrlSource * source,GError ** error)770 grl_optical_media_source_notify_change_stop (GrlSource *source,
771 GError **error)
772 {
773 GrlOpticalMediaSourcePrivate *priv = GRL_OPTICAL_MEDIA_SOURCE (source)->priv;
774
775 priv->notify_changes = FALSE;
776
777 return TRUE;
778 }
779
780 static void
grl_optical_media_source_cancel(GrlSource * source,guint operation_id)781 grl_optical_media_source_cancel (GrlSource *source, guint operation_id)
782 {
783 GCancellable *cancellable;
784
785 cancellable = G_CANCELLABLE (grl_operation_get_data (operation_id));
786
787 if (cancellable) {
788 g_cancellable_cancel (cancellable);
789 }
790 }
791