No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2002,2003 Colin Walters <walters@debian.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * The Rhythmbox authors hereby grant permission for non-GPL compatible
11 * GStreamer plugins to be used and distributed together with GStreamer
12 * and Rhythmbox. This permission is above and beyond the permissions granted
13 * by the GPL license by which Rhythmbox is covered. If you modify this code
14 * you may extend this exception to your version of the code, but you are not
15 * obligated to do so. If you do not wish to do so, delete this exception
16 * statement from your version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
26 *
27 */
28
29 #include "config.h"
30
31 #include <string.h>
32
33 #include <glib/gi18n.h>
34 #include <gtk/gtk.h>
35 #include <libxml/tree.h>
36
37 #include "rb-iradio-source.h"
38 #include "rb-iradio-source-search.h"
39
40 #include "rhythmdb-query-model.h"
41 #include "rb-stock-icons.h"
42 #include "rb-entry-view.h"
43 #include "rb-property-view.h"
44 #include "rb-util.h"
45 #include "rb-file-helpers.h"
46 #include "totem-pl-parser.h"
47 #include "rb-dialog.h"
48 #include "rb-station-properties-dialog.h"
49 #include "rb-uri-dialog.h"
50 #include "rb-debug.h"
51 #include "rb-shell-player.h"
52 #include "rb-player.h"
53 #include "rb-metadata.h"
54 #include "rb-cut-and-paste-code.h"
55 #include "rb-source-search-basic.h"
56 #include "rb-source-toolbar.h"
57
58 /* icon names */
59 #define IRADIO_SOURCE_ICON "library-internet-radio"
60 #define IRADIO_NEW_STATION_ICON "internet-radio-new"
61
62 typedef struct _RhythmDBEntryType RBIRadioEntryType;
63 typedef struct _RhythmDBEntryTypeClass RBIRadioEntryTypeClass;
64
65 static void rb_iradio_source_class_init (RBIRadioSourceClass *klass);
66 static void rb_iradio_source_init (RBIRadioSource *source);
67 static void rb_iradio_source_constructed (GObject *object);
68 static void rb_iradio_source_dispose (GObject *object);
69 static void rb_iradio_source_set_property (GObject *object,
70 guint prop_id,
71 const GValue *value,
72 GParamSpec *pspec);
73 static void rb_iradio_source_get_property (GObject *object,
74 guint prop_id,
75 GValue *value,
76 GParamSpec *pspec);
77 static void rb_iradio_source_songs_show_popup_cb (RBEntryView *view,
78 gboolean over_entry,
79 RBIRadioSource *source);
80 static void genre_selected_cb (RBPropertyView *propview, const char *name,
81 RBIRadioSource *iradio_source);
82 static void genre_selection_reset_cb (RBPropertyView *propview, RBIRadioSource *iradio_source);
83 static void rb_iradio_source_songs_view_sort_order_changed_cb (GObject *object, GParamSpec *pspec, RBIRadioSource *source);
84 static char *guess_uri_scheme (const char *uri);
85
86 /* entry type */
87 static void rb_iradio_entry_type_class_init (RBIRadioEntryTypeClass *klass);
88 static void rb_iradio_entry_type_init (RBIRadioEntryType *etype);
89 GType rb_iradio_entry_type_get_type (void);
90
91 /* page methods */
92 static gboolean impl_show_popup (RBDisplayPage *page);
93 static void impl_get_status (RBDisplayPage *page, char **text, char **progress_text, float *progress);
94
95 /* source methods */
96 static RBEntryView *impl_get_entry_view (RBSource *source);
97 static void impl_search (RBSource *source, RBSourceSearch *search, const char *cur_text, const char *new_text);
98 static void impl_delete (RBSource *source);
99 static void impl_song_properties (RBSource *source);
100 static guint impl_want_uri (RBSource *source, const char *uri);
101 static void impl_add_uri (RBSource *source,
102 const char *uri,
103 const char *title,
104 const char *genre,
105 RBSourceAddCallback callback,
106 gpointer data,
107 GDestroyNotify destroy_data);
108
109 static void rb_iradio_source_do_query (RBIRadioSource *source);
110 static void impl_reset_filters (RBSource *source);
111
112 void rb_iradio_source_show_columns_changed_cb (GtkToggleButton *button,
113 RBIRadioSource *source);
114 static void stations_view_drag_data_received_cb (GtkWidget *widget,
115 GdkDragContext *dc,
116 gint x, gint y,
117 GtkSelectionData *data,
118 guint info, guint time,
119 RBIRadioSource *source);
120 static void rb_iradio_source_cmd_new_station (GtkAction *action,
121 RBIRadioSource *source);
122
123 static void playing_source_changed_cb (RBShellPlayer *player,
124 RBSource *source,
125 RBIRadioSource *iradio_source);
126
127 enum
128 {
129 PROP_0,
130 PROP_SHOW_BROWSER
131 };
132
133 struct RBIRadioSourcePrivate
134 {
135 RhythmDB *db;
136
137 GtkActionGroup *action_group;
138
139 RBSourceToolbar *toolbar;
140 RBPropertyView *genres;
141 RBEntryView *stations;
142 gboolean setting_new_query;
143
144 char *selected_genre;
145 RhythmDBQuery *search_query;
146 RBSourceSearch *default_search;
147
148 RBShellPlayer *player;
149
150 gint info_available_id;
151
152 gboolean dispose_has_run;
153 };
154
155 #define RB_IRADIO_SOURCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_IRADIO_SOURCE, RBIRadioSourcePrivate))
156
157 static GtkActionEntry rb_iradio_source_actions [] =
158 {
159 { "MusicNewInternetRadioStation", IRADIO_NEW_STATION_ICON, N_("New Internet _Radio Station..."), "<control>I",
160 N_("Create a new Internet Radio station"),
161 G_CALLBACK (rb_iradio_source_cmd_new_station) }
162 };
163
164 static const GtkTargetEntry stations_view_drag_types[] = {
165 { "text/uri-list", 0, 0 },
166 { "_NETSCAPE_URL", 0, 1 },
167 };
168
169 G_DEFINE_DYNAMIC_TYPE (RBIRadioSource, rb_iradio_source, RB_TYPE_STREAMING_SOURCE);
170
171 G_DEFINE_DYNAMIC_TYPE (RBIRadioEntryType, rb_iradio_entry_type, RHYTHMDB_TYPE_ENTRY_TYPE);
172
173 static void
174 rb_iradio_entry_type_class_init (RBIRadioEntryTypeClass *klass)
175 {
176 RhythmDBEntryTypeClass *etype_class = RHYTHMDB_ENTRY_TYPE_CLASS (klass);
177 etype_class->can_sync_metadata = (RhythmDBEntryTypeBooleanFunc) rb_true_function;
178 etype_class->sync_metadata = (RhythmDBEntryTypeSyncFunc) rb_null_function;
179 }
180
181 static void
182 rb_iradio_entry_type_class_finalize (RBIRadioEntryTypeClass *klass)
183 {
184 }
185
186 static void
187 rb_iradio_entry_type_init (RBIRadioEntryType *etype)
188 {
189 }
190
191 static void
192 rb_iradio_source_class_init (RBIRadioSourceClass *klass)
193 {
194 GObjectClass *object_class = G_OBJECT_CLASS (klass);
195 RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
196 RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
197
198 object_class->dispose = rb_iradio_source_dispose;
199 object_class->constructed = rb_iradio_source_constructed;
200
201 object_class->set_property = rb_iradio_source_set_property;
202 object_class->get_property = rb_iradio_source_get_property;
203
204 page_class->show_popup = impl_show_popup;
205 page_class->get_status = impl_get_status;
206
207 source_class->impl_can_copy = (RBSourceFeatureFunc) rb_false_function;
208 source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
209 source_class->impl_can_pause = (RBSourceFeatureFunc) rb_false_function;
210 source_class->impl_delete = impl_delete;
211 source_class->impl_get_entry_view = impl_get_entry_view;
212 source_class->impl_search = impl_search;
213 source_class->impl_song_properties = impl_song_properties;
214 source_class->impl_want_uri = impl_want_uri;
215 source_class->impl_add_uri = impl_add_uri;
216 source_class->impl_reset_filters = impl_reset_filters;
217
218 g_object_class_override_property (object_class,
219 PROP_SHOW_BROWSER,
220 "show-browser");
221
222 g_type_class_add_private (klass, sizeof (RBIRadioSourcePrivate));
223 }
224
225 static void
226 rb_iradio_source_class_finalize (RBIRadioSourceClass *klass)
227 {
228 }
229
230 static void
231 rb_iradio_source_init (RBIRadioSource *source)
232 {
233 source->priv = RB_IRADIO_SOURCE_GET_PRIVATE (source);
234 }
235
236 static void
237 rb_iradio_source_dispose (GObject *object)
238 {
239 RBIRadioSource *source;
240
241 source = RB_IRADIO_SOURCE (object);
242
243 if (source->priv->dispose_has_run) {
244 /* If dispose did already run, return. */
245 return;
246 }
247 /* Make sure dispose does not run twice. */
248 source->priv->dispose_has_run = TRUE;
249
250 if (source->priv->player) {
251 g_object_unref (source->priv->player);
252 source->priv->player = NULL;
253 }
254
255 if (source->priv->db) {
256 g_object_unref (source->priv->db);
257 source->priv->db = NULL;
258 }
259
260 if (source->priv->action_group != NULL) {
261 g_object_unref (source->priv->action_group);
262 source->priv->action_group = NULL;
263 }
264
265 if (source->priv->default_search != NULL) {
266 g_object_unref (source->priv->default_search);
267 source->priv->default_search = NULL;
268 }
269
270 if (source->priv->search_query != NULL) {
271 rhythmdb_query_free (source->priv->search_query);
272 source->priv->search_query = NULL;
273 }
274
275 G_OBJECT_CLASS (rb_iradio_source_parent_class)->dispose (object);
276 }
277
278 static void
279 rb_iradio_source_constructed (GObject *object)
280 {
281 RBIRadioSource *source;
282 RBShell *shell;
283 GtkAction *action;
284 GSettings *settings;
285 GtkUIManager *ui_manager;
286 GtkWidget *grid;
287 GtkWidget *paned;
288 gint size;
289 GdkPixbuf *pixbuf;
290
291 RB_CHAIN_GOBJECT_METHOD (rb_iradio_source_parent_class, constructed, object);
292 source = RB_IRADIO_SOURCE (object);
293
294 paned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
295
296 g_object_get (source, "shell", &shell, NULL);
297 g_object_get (shell,
298 "db", &source->priv->db,
299 "shell-player", &source->priv->player,
300 "ui-manager", &ui_manager,
301 NULL);
302 g_object_unref (shell);
303
304 gtk_icon_size_lookup (RB_SOURCE_ICON_SIZE, &size, NULL);
305 pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
306 IRADIO_SOURCE_ICON,
307 size,
308 0, NULL);
309 g_object_set (source, "pixbuf", pixbuf, NULL);
310 if (pixbuf != NULL) {
311 g_object_unref (pixbuf);
312 }
313
314 settings = g_settings_new ("org.gnome.rhythmbox.plugins.iradio");
315 if (g_settings_get_boolean (settings, "initial-stations-loaded") == FALSE) {
316 GObject *plugin;
317 char *file;
318
319 g_object_get (source, "plugin", &plugin, NULL);
320 file = rb_find_plugin_data_file (plugin, "iradio-initial.xspf");
321 if (file != NULL) {
322 char *uri = g_filename_to_uri (file, NULL, NULL);
323 if (uri != NULL) {
324 rb_iradio_source_add_from_playlist (source, uri);
325 g_free (uri);
326
327 g_settings_set_boolean (settings, "initial-stations-loaded", TRUE);
328 }
329 }
330 g_free (file);
331 g_object_unref (plugin);
332 }
333
334 source->priv->action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (source),
335 "IRadioActions",
336 rb_iradio_source_actions,
337 G_N_ELEMENTS (rb_iradio_source_actions),
338 source);
339
340 action = gtk_action_group_get_action (source->priv->action_group,
341 "MusicNewInternetRadioStation");
342 /* Translators: this is the toolbar button label for
343 New Internet Radio Station action. */
344 g_object_set (action, "short-label", C_("Radio", "Add"), NULL);
345
346
347 /* set up stations view */
348 source->priv->stations = rb_entry_view_new (source->priv->db, G_OBJECT (source->priv->player),
349 FALSE, FALSE);
350
351 rb_entry_view_append_column (source->priv->stations, RB_ENTRY_VIEW_COL_TITLE, TRUE);
352 rb_entry_view_append_column (source->priv->stations, RB_ENTRY_VIEW_COL_GENRE, FALSE);
353 /* rb_entry_view_append_column (source->priv->stations, RB_ENTRY_VIEW_COL_QUALITY, FALSE); */
354 rb_entry_view_append_column (source->priv->stations, RB_ENTRY_VIEW_COL_RATING, FALSE);
355 /* rb_entry_view_append_column (source->priv->stations, RB_ENTRY_VIEW_COL_PLAY_COUNT, FALSE);*/
356 rb_entry_view_append_column (source->priv->stations, RB_ENTRY_VIEW_COL_LAST_PLAYED, FALSE);
357 g_signal_connect_object (source->priv->stations,
358 "notify::sort-order",
359 G_CALLBACK (rb_iradio_source_songs_view_sort_order_changed_cb),
360 source, 0);
361
362 /* set up drag and drop for the song tree view.
363 * we don't use RBEntryView's DnD support because it does too much.
364 * we just want to be able to drop stations in to add them.
365 */
366 g_signal_connect_object (source->priv->stations,
367 "drag_data_received",
368 G_CALLBACK (stations_view_drag_data_received_cb),
369 source, 0);
370 gtk_drag_dest_set (GTK_WIDGET (source->priv->stations),
371 GTK_DEST_DEFAULT_ALL,
372 stations_view_drag_types, 2,
373 GDK_ACTION_COPY | GDK_ACTION_MOVE);
374
375 g_signal_connect_object (source->priv->stations, "show_popup",
376 G_CALLBACK (rb_iradio_source_songs_show_popup_cb), source, 0);
377
378 /* set up genre entry view */
379 source->priv->genres = rb_property_view_new (source->priv->db,
380 RHYTHMDB_PROP_GENRE,
381 _("Genre"));
382 gtk_widget_show_all (GTK_WIDGET (source->priv->genres));
383 gtk_widget_set_no_show_all (GTK_WIDGET (source->priv->genres), TRUE);
384 g_signal_connect_object (source->priv->genres,
385 "property-selected",
386 G_CALLBACK (genre_selected_cb),
387 source, 0);
388 g_signal_connect_object (source->priv->genres,
389 "property-selection-reset",
390 G_CALLBACK (genre_selection_reset_cb),
391 source, 0);
392
393 g_object_set (source->priv->genres, "vscrollbar_policy",
394 GTK_POLICY_AUTOMATIC, NULL);
395
396 gtk_paned_pack1 (GTK_PANED (paned), GTK_WIDGET (source->priv->genres), FALSE, FALSE);
397 gtk_paned_pack2 (GTK_PANED (paned), GTK_WIDGET (source->priv->stations), TRUE, FALSE);
398
399 /* set up toolbar */
400 source->priv->toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (source), ui_manager);
401 rb_source_toolbar_add_search_entry (source->priv->toolbar, NULL, _("Search your internet radio stations"));
402
403 grid = gtk_grid_new ();
404 gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
405 gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
406 gtk_widget_set_margin_top (GTK_WIDGET (grid), 6);
407 gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (source->priv->toolbar), 0, 0, 1, 1);
408 gtk_grid_attach (GTK_GRID (grid), paned, 0, 1, 1, 1);
409
410 gtk_container_add (GTK_CONTAINER (source), grid);
411
412 rb_source_bind_settings (RB_SOURCE (source),
413 GTK_WIDGET (source->priv->stations),
414 paned,
415 GTK_WIDGET (source->priv->genres));
416
417 gtk_widget_show_all (GTK_WIDGET (source));
418
419 g_signal_connect_object (source->priv->player, "playing-source-changed",
420 G_CALLBACK (playing_source_changed_cb),
421 source, 0);
422
423 source->priv->default_search = rb_iradio_source_search_new ();
424
425 rb_iradio_source_do_query (source);
426 }
427
428 static void
429 rb_iradio_source_set_property (GObject *object,
430 guint prop_id,
431 const GValue *value,
432 GParamSpec *pspec)
433 {
434 RBIRadioSource *source = RB_IRADIO_SOURCE (object);
435
436 switch (prop_id) {
437 case PROP_SHOW_BROWSER:
438 gtk_widget_set_visible (GTK_WIDGET (source->priv->genres), g_value_get_boolean (value));
439 break;
440 default:
441 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
442 break;
443 }
444 }
445
446 static void
447 rb_iradio_source_get_property (GObject *object,
448 guint prop_id,
449 GValue *value,
450 GParamSpec *pspec)
451 {
452 RBIRadioSource *source = RB_IRADIO_SOURCE (object);
453
454 switch (prop_id) {
455 case PROP_SHOW_BROWSER:
456 g_value_set_boolean (value, gtk_widget_get_visible (GTK_WIDGET (source->priv->genres)));
457 break;
458 default:
459 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
460 break;
461 }
462 }
463
464 RBSource *
465 rb_iradio_source_new (RBShell *shell, GObject *plugin)
466 {
467 RBSource *source;
468 RhythmDBEntryType *entry_type;
469 RhythmDB *db;
470 GSettings *settings;
471
472 g_object_get (shell, "db", &db, NULL);
473
474 entry_type = rhythmdb_entry_type_get_by_name (db, "iradio");
475 if (entry_type == NULL) {
476 entry_type = g_object_new (RHYTHMDB_TYPE_ENTRY_TYPE,
477 "db", db,
478 "name", "iradio",
479 "save-to-disk", TRUE,
480 "category", RHYTHMDB_ENTRY_STREAM,
481 NULL);
482 rhythmdb_register_entry_type (db, entry_type);
483 }
484 g_object_unref (db);
485
486 settings = g_settings_new ("org.gnome.rhythmbox.plugins.iradio");
487 source = RB_SOURCE (g_object_new (RB_TYPE_IRADIO_SOURCE,
488 "name", _("Radio"),
489 "shell", shell,
490 "entry-type", entry_type,
491 "plugin", plugin,
492 "settings", g_settings_get_child (settings, "source"),
493 "toolbar-path", "/IRadioSourceToolBar",
494 NULL));
495 g_object_unref (settings);
496 rb_shell_register_entry_type_for_source (shell, source, entry_type);
497 return source;
498 }
499
500 static char *
501 guess_uri_scheme (const char *uri)
502 {
503 const char *scheme;
504
505 /* if the URI has no scheme, it might be an absolute path, or it might be
506 * host:port for HTTP.
507 */
508 scheme = strstr (uri, "://");
509 if (scheme == NULL) {
510 if (uri[0] == '/') {
511 return g_strdup_printf ("file://%s", uri);
512 } else {
513 return g_strdup_printf ("http://%s", uri);
514 }
515 }
516
517 return NULL;
518 }
519
520 void
521 rb_iradio_source_add_station (RBIRadioSource *source,
522 const char *uri,
523 const char *title,
524 const char *genre)
525 {
526 RhythmDBEntry *entry;
527 GValue val = { 0, };
528 char *real_uri = NULL;
529 char *fixed_title;
530 char *fixed_genre = NULL;
531 RhythmDBEntryType *entry_type;
532
533 real_uri = guess_uri_scheme (uri);
534 if (real_uri)
535 uri = real_uri;
536
537 entry = rhythmdb_entry_lookup_by_location (source->priv->db, uri);
538 if (entry) {
539 rb_debug ("uri %s already in db", uri);
540 g_free (real_uri);
541 return;
542 }
543
544 g_object_get (source, "entry-type", &entry_type, NULL);
545 entry = rhythmdb_entry_new (source->priv->db, entry_type, uri);
546 g_object_unref (entry_type);
547 if (entry == NULL) {
548 g_free (real_uri);
549 return;
550 }
551
552 g_value_init (&val, G_TYPE_STRING);
553 if (title) {
554 fixed_title = rb_make_valid_utf8 (title, '?');
555 } else {
556 fixed_title = g_uri_unescape_string (uri, NULL);
557 }
558 g_value_take_string (&val, fixed_title);
559
560 rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_TITLE, &val);
561 g_value_reset (&val);
562
563 if ((!genre) || (strcmp (genre, "") == 0)) {
564 genre = _("Unknown");
565 } else {
566 fixed_genre = rb_make_valid_utf8 (genre, '?');
567 genre = fixed_genre;
568 }
569
570 g_value_set_string (&val, genre);
571 rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_GENRE, &val);
572 g_value_unset (&val);
573 g_free (fixed_genre);
574
575 g_value_init (&val, G_TYPE_DOUBLE);
576 g_value_set_double (&val, 0.0);
577 rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_RATING, &val);
578 g_value_unset (&val);
579
580 rhythmdb_commit (source->priv->db);
581
582 g_free (real_uri);
583 }
584 static void
585 impl_search (RBSource *asource,
586 RBSourceSearch *search,
587 const char *cur_text,
588 const char *new_text)
589 {
590 RBIRadioSource *source = RB_IRADIO_SOURCE (asource);
591
592 if (source->priv->search_query != NULL) {
593 rhythmdb_query_free (source->priv->search_query);
594 }
595
596 if (search == NULL) {
597 search = source->priv->default_search;
598 }
599 source->priv->search_query = rb_source_search_create_query (search, source->priv->db, new_text);
600
601 rb_iradio_source_do_query (source);
602
603 rb_source_notify_filter_changed (RB_SOURCE (source));
604 }
605
606 static RBEntryView *
607 impl_get_entry_view (RBSource *asource)
608 {
609 RBIRadioSource *source = RB_IRADIO_SOURCE (asource);
610
611 return source->priv->stations;
612 }
613
614 static void
615 impl_get_status (RBDisplayPage *page,
616 char **text,
617 char **progress_text,
618 float *progress)
619 {
620 RhythmDBQueryModel *model;
621 guint num_entries;
622 RBIRadioSource *source = RB_IRADIO_SOURCE (page);
623
624 g_object_get (source, "query-model", &model, NULL);
625 num_entries = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (model), NULL);
626 g_object_unref (model);
627
628 *text = g_strdup_printf (ngettext ("%d station", "%d stations", num_entries),
629 num_entries);
630
631 rb_streaming_source_get_progress (RB_STREAMING_SOURCE (source), progress_text, progress);
632 }
633
634 static void
635 impl_delete (RBSource *asource)
636 {
637 RBIRadioSource *source = RB_IRADIO_SOURCE (asource);
638 GList *sel;
639 GList *l;
640
641 sel = rb_entry_view_get_selected_entries (source->priv->stations);
642 for (l = sel; l != NULL; l = g_list_next (l)) {
643 rhythmdb_entry_delete (source->priv->db, l->data);
644 rhythmdb_commit (source->priv->db);
645 }
646
647 g_list_foreach (sel, (GFunc)rhythmdb_entry_unref, NULL);
648 g_list_free (sel);
649 }
650
651 static void
652 impl_song_properties (RBSource *asource)
653 {
654 RBIRadioSource *source = RB_IRADIO_SOURCE (asource);
655 GObject *plugin;
656 GtkWidget *dialog;
657
658 g_object_get (source, "plugin", &plugin, NULL);
659 dialog = rb_station_properties_dialog_new (plugin, source->priv->stations);
660 g_object_unref (plugin);
661
662 rb_debug ("in song properties");
663 if (dialog)
664 gtk_widget_show_all (dialog);
665 else
666 rb_debug ("no selection!");
667 }
668
669 static guint
670 impl_want_uri (RBSource *source, const char *uri)
671 {
672 if (g_str_has_prefix (uri, "http://")) {
673 /* other entry types might have
674 * more specific guesses for HTTP
675 */
676 return 50;
677 } else if (g_str_has_prefix (uri, "pnm://") ||
678 g_str_has_prefix (uri, "rtsp://") ||
679 g_str_has_prefix (uri, "mms://") ||
680 g_str_has_prefix (uri, "mmsh://")) {
681 return 100;
682 }
683
684 return 0;
685 }
686
687 static void
688 impl_add_uri (RBSource *source,
689 const char *uri,
690 const char *title,
691 const char *genre,
692 RBSourceAddCallback callback,
693 gpointer data,
694 GDestroyNotify destroy_data)
695 {
696 if (rb_uri_is_local (uri)) {
697 rb_iradio_source_add_from_playlist (RB_IRADIO_SOURCE (source), uri);
698 } else {
699 rb_iradio_source_add_station (RB_IRADIO_SOURCE (source),
700 uri, title, genre);
701 }
702 if (callback != NULL) {
703 callback (source, uri, data);
704 if (destroy_data != NULL) {
705 destroy_data (data);
706 }
707 }
708 }
709
710 static void
711 rb_iradio_source_songs_view_sort_order_changed_cb (GObject *object,
712 GParamSpec *pspec,
713 RBIRadioSource *source)
714 {
715 rb_debug ("sort order changed");
716 rb_entry_view_resort_model (RB_ENTRY_VIEW (object));
717 }
718
719 static void
720 rb_iradio_source_songs_show_popup_cb (RBEntryView *view,
721 gboolean over_entry,
722 RBIRadioSource *source)
723 {
724 if (source == NULL) {
725 return;
726 }
727
728 if (over_entry)
729 _rb_display_page_show_popup (RB_DISPLAY_PAGE (source), "/IRadioViewPopup");
730 else
731 _rb_display_page_show_popup (RB_DISPLAY_PAGE (source), "/IRadioSourcePopup");
732 }
733
734 static void
735 genre_selected_cb (RBPropertyView *propview, const char *name,
736 RBIRadioSource *iradio_source)
737 {
738 if (iradio_source->priv->setting_new_query)
739 return;
740
741 g_free (iradio_source->priv->selected_genre);
742 iradio_source->priv->selected_genre = g_strdup (name);
743 rb_iradio_source_do_query (iradio_source);
744
745 rb_source_notify_filter_changed (RB_SOURCE (iradio_source));
746 }
747
748 static void
749 genre_selection_reset_cb (RBPropertyView *propview,
750 RBIRadioSource *iradio_source)
751 {
752 if (iradio_source->priv->setting_new_query)
753 return;
754
755 g_free (iradio_source->priv->selected_genre);
756 iradio_source->priv->selected_genre = NULL;
757
758 rb_iradio_source_do_query (iradio_source);
759
760 rb_source_notify_filter_changed (RB_SOURCE (iradio_source));
761 }
762
763 static void
764 rb_iradio_source_do_query (RBIRadioSource *source)
765 {
766 RhythmDBQueryModel *genre_query_model = NULL;
767 RhythmDBQueryModel *station_query_model = NULL;
768 RhythmDBPropertyModel *genre_model;
769 GPtrArray *query;
770 RhythmDBEntryType *entry_type;
771
772 /* don't update the selection while we're rebuilding the query */
773 source->priv->setting_new_query = TRUE;
774
775 /* construct and run the query for the search box.
776 * this is used as the model for the genre view.
777 */
778
779 g_object_get (source, "entry-type", &entry_type, NULL);
780 query = rhythmdb_query_parse (source->priv->db,
781 RHYTHMDB_QUERY_PROP_EQUALS,
782 RHYTHMDB_PROP_TYPE,
783 entry_type,
784 RHYTHMDB_QUERY_END);
785 g_object_unref (entry_type);
786
787 if (source->priv->search_query != NULL) {
788 rhythmdb_query_append (source->priv->db,
789 query,
790 RHYTHMDB_QUERY_SUBQUERY,
791 source->priv->search_query,
792 RHYTHMDB_QUERY_END);
793 }
794
795 genre_model = rb_property_view_get_model (source->priv->genres);
796
797 genre_query_model = rhythmdb_query_model_new_empty (source->priv->db);
798 g_object_set (genre_model, "query-model", genre_query_model, NULL);
799
800 rhythmdb_do_full_query_parsed (source->priv->db,
801 RHYTHMDB_QUERY_RESULTS (genre_query_model),
802 query);
803
804 rhythmdb_query_free (query);
805 query = NULL;
806
807 /* check the selected genre is still available, and if not, select 'all' */
808 if (source->priv->selected_genre != NULL) {
809 GList *sel = NULL;
810
811 if (!rhythmdb_property_model_iter_from_string (genre_model,
812 source->priv->selected_genre,
813 NULL)) {
814 g_free (source->priv->selected_genre);
815 source->priv->selected_genre = NULL;
816 }
817
818 sel = g_list_prepend (sel, source->priv->selected_genre);
819 rb_property_view_set_selection (source->priv->genres, sel);
820 g_list_free (sel);
821 }
822
823 /* if a genre is selected, construct a new query for it, and create
824 * a new model based on the search box query model. otherwise, just
825 * reuse the search box query model.
826 */
827
828 if (source->priv->selected_genre != NULL) {
829 rb_debug ("matching on genre \"%s\"", source->priv->selected_genre);
830
831 station_query_model = rhythmdb_query_model_new_empty (source->priv->db);
832 query = rhythmdb_query_parse (source->priv->db,
833 RHYTHMDB_QUERY_PROP_EQUALS,
834 RHYTHMDB_PROP_GENRE,
835 source->priv->selected_genre,
836 RHYTHMDB_QUERY_END);
837
838 g_object_set (station_query_model,
839 "query", query,
840 "base-model", genre_query_model,
841 NULL);
842
843 rhythmdb_query_free (query);
844 query = NULL;
845 } else {
846 station_query_model = g_object_ref (genre_query_model);
847 }
848
849 rb_entry_view_set_model (source->priv->stations, station_query_model);
850 g_object_set (source, "query-model", station_query_model, NULL);
851
852 g_object_unref (genre_query_model);
853 g_object_unref (station_query_model);
854
855 source->priv->setting_new_query = FALSE;
856 }
857
858 static void
859 impl_reset_filters (RBSource *asource)
860 {
861 RBIRadioSource *source = RB_IRADIO_SOURCE (asource);
862
863 if (source->priv->search_query != NULL) {
864 rhythmdb_query_free (source->priv->search_query);
865 source->priv->search_query = NULL;
866 }
867 rb_source_toolbar_clear_search_entry (source->priv->toolbar);
868
869 rb_property_view_set_selection (source->priv->genres, NULL);
870 }
871
872 static void
873 handle_playlist_entry_cb (TotemPlParser *playlist,
874 const char *uri,
875 GHashTable *metadata,
876 RBIRadioSource *source)
877 {
878 const char *title, *genre;
879
880 title = g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_TITLE);
881 genre = g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_GENRE);
882 rb_iradio_source_add_station (source, uri, title, genre);
883 }
884
885 void
886 rb_iradio_source_add_from_playlist (RBIRadioSource *source,
887 const char *uri)
888 {
889 TotemPlParser *parser = totem_pl_parser_new ();
890 char *real_uri;
891
892 real_uri = guess_uri_scheme (uri);
893 if (real_uri)
894 uri = real_uri;
895
896 g_signal_connect_object (parser, "entry-parsed",
897 G_CALLBACK (handle_playlist_entry_cb),
898 source, 0);
899 g_object_set (parser, "recurse", FALSE, NULL);
900
901 switch (totem_pl_parser_parse (parser, uri, FALSE)) {
902 case TOTEM_PL_PARSER_RESULT_UNHANDLED:
903 case TOTEM_PL_PARSER_RESULT_IGNORED:
904 /* maybe it's the actual stream URL, then */
905 rb_iradio_source_add_station (source, uri, NULL, NULL);
906 break;
907
908 default:
909 case TOTEM_PL_PARSER_RESULT_SUCCESS:
910 case TOTEM_PL_PARSER_RESULT_ERROR:
911 break;
912 }
913 g_object_unref (parser);
914 g_free (real_uri);
915 }
916
917 static void
918 stations_view_drag_data_received_cb (GtkWidget *widget,
919 GdkDragContext *dc,
920 gint x,
921 gint y,
922 GtkSelectionData *selection_data,
923 guint info,
924 guint time,
925 RBIRadioSource *source)
926 {
927 GList *uri_list, *i;
928
929 rb_debug ("parsing uri list");
930 uri_list = rb_uri_list_parse ((char *) gtk_selection_data_get_data (selection_data));
931 if (uri_list == NULL)
932 return;
933
934 for (i = uri_list; i != NULL; i = i->next) {
935 char *uri = NULL;
936
937 uri = i->data;
938 if (uri != NULL) {
939 rb_iradio_source_add_station (source, uri, NULL, NULL);
940 }
941
942 if (info == 1) {
943 /* for _NETSCAPE_URL drags, this item is the link text */
944 i = i->next;
945 }
946 }
947
948 rb_list_deep_free (uri_list);
949 return;
950 }
951
952 static gboolean
953 impl_show_popup (RBDisplayPage *page)
954 {
955 _rb_display_page_show_popup (page, "/IRadioSourcePopup");
956 return TRUE;
957 }
958
959 static void
960 new_station_location_added (RBURIDialog *dialog,
961 const char *uri,
962 RBIRadioSource *source)
963 {
964 rb_iradio_source_add_station (source, uri, NULL, NULL);
965 }
966
967 static void
968 new_station_response_cb (GtkDialog *dialog, int response, gpointer meh)
969 {
970 gtk_widget_destroy (GTK_WIDGET (dialog));
971 }
972
973 static void
974 rb_iradio_source_cmd_new_station (GtkAction *action,
975 RBIRadioSource *source)
976 {
977 GtkWidget *dialog;
978
979 rb_debug ("Got new station command");
980
981 /* should prevent multiple dialogs? going to kill this nonsense anyway soon.. */
982
983 dialog = rb_uri_dialog_new (_("New Internet Radio Station"), _("URL of internet radio station:"));
984 g_signal_connect_object (dialog, "location-added",
985 G_CALLBACK (new_station_location_added),
986 source, 0);
987 g_signal_connect (dialog, "response", G_CALLBACK (new_station_response_cb), NULL);
988
989 gtk_widget_show_all (dialog);
990 }
991
992 static gboolean
993 check_entry_type (RBIRadioSource *source, RhythmDBEntry *entry)
994 {
995 RhythmDBEntryType *entry_type;
996 gboolean matches = FALSE;
997
998 g_object_get (source, "entry-type", &entry_type, NULL);
999 if (entry != NULL && rhythmdb_entry_get_entry_type (entry) == entry_type)
1000 matches = TRUE;
1001 g_object_unref (entry_type);
1002
1003 return matches;
1004 }
1005
1006 static void
1007 info_available_cb (RBPlayer *backend,
1008 const char *uri,
1009 RBMetaDataField field,
1010 GValue *value,
1011 RBIRadioSource *source)
1012 {
1013 RhythmDBEntry *entry;
1014 RhythmDBPropType entry_field = 0;
1015 gboolean set_field = FALSE;
1016 char *str = NULL;
1017
1018 /* sanity check */
1019 if (!rb_player_opened (backend)) {
1020 rb_debug ("Got info_available but not playing");
1021 return;
1022 }
1023
1024 GDK_THREADS_ENTER ();
1025
1026 entry = rb_shell_player_get_playing_entry (source->priv->player);
1027 if (check_entry_type (source, entry) == FALSE)
1028 goto out_unlock;
1029
1030 /* validate the value */
1031 switch (field) {
1032 case RB_METADATA_FIELD_TITLE:
1033 case RB_METADATA_FIELD_ARTIST:
1034 case RB_METADATA_FIELD_GENRE:
1035 case RB_METADATA_FIELD_COMMENT:
1036 str = g_value_dup_string (value);
1037 if (!g_utf8_validate (str, -1, NULL)) {
1038 g_warning ("Invalid UTF-8 from internet radio: %s", str);
1039 g_free (str);
1040 goto out_unlock;
1041 }
1042 break;
1043 default:
1044 break;
1045 }
1046
1047
1048 switch (field) {
1049 /* streaming song information */
1050 case RB_METADATA_FIELD_TITLE:
1051 {
1052 rb_streaming_source_set_streaming_title (RB_STREAMING_SOURCE (source), str);
1053 break;
1054 }
1055 case RB_METADATA_FIELD_ARTIST:
1056 {
1057 rb_streaming_source_set_streaming_artist (RB_STREAMING_SOURCE (source), str);
1058 break;
1059 }
1060
1061 /* station information */
1062 case RB_METADATA_FIELD_GENRE:
1063 {
1064 const char *existing;
1065
1066 /* check if the db entry already has a genre; if so, don't change it */
1067 existing = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_GENRE);
1068 if ((existing == NULL) ||
1069 (strcmp (existing, "") == 0) ||
1070 (strcmp (existing, _("Unknown")) == 0)) {
1071 entry_field = RHYTHMDB_PROP_GENRE;
1072 rb_debug ("setting genre of iradio station to %s", str);
1073 set_field = TRUE;
1074 } else {
1075 rb_debug ("iradio station already has genre: %s; ignoring %s", existing, str);
1076 }
1077 break;
1078 }
1079 case RB_METADATA_FIELD_COMMENT:
1080 {
1081 const char *existing;
1082 const char *location;
1083
1084 /* check if the db entry already has a title; if so, don't change it.
1085 * consider title==URI to be the same as no title, since that's what
1086 * happens for stations imported by DnD or commandline args.
1087 * if the station title really is the same as the URI, then surely
1088 * the station title in the stream metadata will say that too..
1089 */
1090 existing = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE);
1091 location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
1092 if ((existing == NULL) ||
1093 (strcmp (existing, "") == 0) ||
1094 (strcmp (existing, location) == 0)) {
1095 entry_field = RHYTHMDB_PROP_TITLE;
1096 rb_debug ("setting title of iradio station to %s", str);
1097 set_field = TRUE;
1098 } else {
1099 rb_debug ("iradio station already has title: %s; ignoring %s", existing, str);
1100 }
1101 break;
1102 }
1103 case RB_METADATA_FIELD_BITRATE:
1104 if (!rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_BITRATE)) {
1105 gulong bitrate;
1106
1107 /* GStreamer sends us bitrate in bps, but we need it in kbps*/
1108 bitrate = g_value_get_ulong (value);
1109 g_value_set_ulong (value, bitrate/1000);
1110
1111 rb_debug ("setting bitrate of iradio station to %lu",
1112 g_value_get_ulong (value));
1113 entry_field = RHYTHMDB_PROP_BITRATE;
1114 set_field = TRUE;
1115 }
1116 break;
1117 default:
1118 break;
1119 }
1120
1121 if (set_field && entry_field != 0) {
1122 rhythmdb_entry_set (source->priv->db, entry, entry_field, value);
1123 rhythmdb_commit (source->priv->db);
1124 }
1125
1126 g_free (str);
1127 out_unlock:
1128 GDK_THREADS_LEAVE ();
1129 }
1130
1131 static void
1132 playing_source_changed_cb (RBShellPlayer *player,
1133 RBSource *source,
1134 RBIRadioSource *iradio_source)
1135 {
1136 GObject *backend;
1137
1138 g_object_get (player, "player", &backend, NULL);
1139
1140 if (source == RB_SOURCE (iradio_source) && (iradio_source->priv->info_available_id == 0)) {
1141 rb_debug ("connecting info-available signal handler");
1142 iradio_source->priv->info_available_id =
1143 g_signal_connect_object (backend, "info",
1144 G_CALLBACK (info_available_cb),
1145 iradio_source, 0);
1146 } else if (iradio_source->priv->info_available_id) {
1147 rb_debug ("disconnecting info-available signal handler");
1148 g_signal_handler_disconnect (backend,
1149 iradio_source->priv->info_available_id);
1150 iradio_source->priv->info_available_id = 0;
1151 }
1152
1153 g_object_unref (backend);
1154 }
1155
1156 void
1157 _rb_iradio_source_register_type (GTypeModule *module)
1158 {
1159 rb_iradio_entry_type_register_type (module);
1160 rb_iradio_source_register_type (module);
1161 }