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