2
* Implementation of DAAP (iTunes Music Sharing) source object
4
* Copyright (C) 2005 Charles Schmidt <cschmidt2@emich.edu>
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; either version 2 of the License, or
9
* (at your option) any later version.
11
* This program is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program; if not, write to the Free Software
18
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24
#include <gtk/gtktreeview.h>
25
#include <gtk/gtkicontheme.h>
26
#include <gtk/gtkiconfactory.h>
30
#include <libgnome/gnome-i18n.h>
31
#include "eel-gconf-extensions.h"
32
#include "rb-daap-source.h"
33
#include "rb-stock-icons.h"
36
#include "rb-dialog.h"
37
#include "rb-preferences.h"
39
#include "rb-daap-connection.h"
40
#include "rb-daap-mdns.h"
41
#include "rb-daap-src.h"
43
#include "rb-playlist-source.h"
46
static void rb_daap_source_dispose (GObject *object);
47
static void rb_daap_source_set_property (GObject *object,
51
static void rb_daap_source_get_property (GObject *object,
55
static void rb_daap_source_activate (RBSource *source);
56
static void rb_daap_source_connection_cb (RBDAAPConnection *connection,
59
static gboolean rb_daap_source_disconnect (RBSource *source);
60
static gboolean rb_daap_source_show_popup (RBSource *source);
61
static const gchar * rb_daap_source_get_browser_key (RBSource *source);
62
static const gchar * rb_daap_source_get_paned_key (RBLibrarySource *source);
65
#define CONF_ENABLE_BROWSING CONF_PREFIX "/sharing/enable_browsing"
66
#define CONF_STATE_SORTING CONF_PREFIX "/state/daap/sorting"
67
#define CONF_STATE_PANED_POSITION CONF_PREFIX "/state/daap/paned_position"
68
#define CONF_STATE_SHOW_BROWSER CONF_PREFIX "/state/daap/show_browser"
71
static RBDAAPmDNSBrowser browser = 0;
72
static GHashTable *service_name_to_resolver = NULL;
73
static GSList *sources = NULL;
74
static guint enable_browsing_notify_id = EEL_GCONF_UNDEFINED_CONNECTION;
77
struct RBDAAPSourcePrivate
82
gboolean password_protected;
84
RBDAAPConnection *connection;
85
GSList *playlist_sources;
94
PROP_PASSWORD_PROTECTED
97
G_DEFINE_TYPE (RBDAAPSource, rb_daap_source, RB_TYPE_LIBRARY_SOURCE)
100
static RhythmDBEntryType
101
rhythmdb_entry_daap_type_new (void)
103
return rhythmdb_entry_register_type ();
107
rb_daap_source_class_init (RBDAAPSourceClass *klass)
109
GObjectClass *object_class = G_OBJECT_CLASS (klass);
110
RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
111
RBLibrarySourceClass *library_source_class = RB_LIBRARY_SOURCE_CLASS (klass);
113
object_class->dispose = rb_daap_source_dispose;
114
object_class->get_property = rb_daap_source_get_property;
115
object_class->set_property = rb_daap_source_set_property;
117
source_class->impl_activate = rb_daap_source_activate;
118
source_class->impl_disconnect = rb_daap_source_disconnect;
119
source_class->impl_can_search = (RBSourceFeatureFunc) rb_true_function;
120
source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
121
source_class->impl_can_copy = (RBSourceFeatureFunc) rb_false_function;
122
source_class->impl_can_delete = (RBSourceFeatureFunc) rb_false_function;
123
source_class->impl_paste = NULL;
124
source_class->impl_receive_drag = NULL;
125
source_class->impl_delete = NULL;
126
source_class->impl_show_popup = rb_daap_source_show_popup;
127
source_class->impl_get_config_widget = NULL;
128
source_class->impl_get_browser_key = rb_daap_source_get_browser_key;
130
library_source_class->impl_get_paned_key = rb_daap_source_get_paned_key;
131
library_source_class->impl_has_first_added_column = (RBLibrarySourceFeatureFunc) rb_false_function;
132
library_source_class->impl_has_drop_support = (RBLibrarySourceFeatureFunc) rb_false_function;
135
g_object_class_install_property (object_class,
137
g_param_spec_string ("service-name",
139
"mDNS/DNS-SD service name of the share",
141
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
143
g_object_class_install_property (object_class,
145
g_param_spec_string ("host",
151
g_object_class_install_property (object_class,
153
g_param_spec_uint ("port",
155
"Port of DAAP server on host",
160
g_object_class_install_property (object_class,
161
PROP_PASSWORD_PROTECTED,
162
g_param_spec_boolean ("password-protected",
163
"Password Protected",
164
"Whether the share is password protected",
170
rb_daap_source_init (RBDAAPSource *source)
172
source->priv = g_new0 (RBDAAPSourcePrivate, 1);
177
rb_daap_source_dispose (GObject *object)
179
RBDAAPSource *source = RB_DAAP_SOURCE (object);
182
/* we should already have been disconnected */
183
g_assert (source->priv->connection == NULL);
185
g_free (source->priv->service_name);
186
g_free (source->priv->host);
187
g_free (source->priv);
191
G_OBJECT_CLASS (rb_daap_source_parent_class)->dispose (object);
195
rb_daap_source_set_property (GObject *object,
200
RBDAAPSource *source = RB_DAAP_SOURCE (object);
203
case PROP_SERVICE_NAME:
204
source->priv->service_name = g_value_dup_string (value);
207
if (source->priv->host) {
208
g_free (source->priv->host);
210
source->priv->host = g_value_dup_string (value);
211
/* FIXME what do we do if its already connected and we
215
source->priv->port = g_value_get_uint (value);
217
case PROP_PASSWORD_PROTECTED:
218
source->priv->password_protected = g_value_get_boolean (value);
221
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
227
rb_daap_source_get_property (GObject *object,
232
RBDAAPSource *source = RB_DAAP_SOURCE (object);
235
case PROP_SERVICE_NAME:
236
g_value_set_string (value, source->priv->service_name);
239
g_value_set_string (value, source->priv->host);
242
g_value_set_uint (value, source->priv->port);
244
case PROP_PASSWORD_PROTECTED:
245
g_value_set_boolean (value, source->priv->password_protected);
248
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
254
rb_daap_get_icon (void)
260
theme = gtk_icon_theme_get_default ();
261
gtk_icon_size_lookup (GTK_ICON_SIZE_LARGE_TOOLBAR, &size, NULL);
262
icon = gtk_icon_theme_load_icon (theme, "gnome-fs-network", size, 0, NULL);
268
rb_daap_source_new (RBShell *shell,
269
const gchar *service_name,
273
gboolean password_protected)
276
RhythmDBEntryType type;
278
type = rhythmdb_entry_daap_type_new ();
280
source = RB_SOURCE (g_object_new (RB_TYPE_DAAP_SOURCE,
281
"service-name", service_name,
286
"icon", rb_daap_get_icon (),
289
"sorting-key", CONF_STATE_SORTING,
290
"password-protected", password_protected,
293
rb_shell_register_entry_type_for_source (shell, source,
301
find_source_by_service_name (const gchar *service_name)
305
for (l = sources; l != NULL; l = l->next) {
306
RBSource *source = l->data;
308
if (strcmp (service_name, RB_DAAP_SOURCE (source)->priv->service_name) == 0) {
317
resolve_cb (RBDAAPmDNSResolver resolver,
318
RBDAAPmDNSResolverStatus status,
319
const gchar *service_name,
323
gboolean password_protected,
326
if (status == RB_DAAP_MDNS_RESOLVER_FOUND) {
327
RBSource *source = find_source_by_service_name (service_name);
329
if (source == NULL) {
330
source = rb_daap_source_new (shell, service_name, name, host, port, password_protected);
331
sources = g_slist_prepend (sources, source);
332
rb_shell_append_source (shell, source, NULL);
334
g_object_set (G_OBJECT (source),
338
"password-protected", password_protected,
341
} else if (status == RB_DAAP_MDNS_RESOLVER_TIMEOUT) {
342
g_warning ("Unable to resolve %s", service_name);
348
browse_cb (RBDAAPmDNSBrowser b,
349
RBDAAPmDNSBrowserStatus status,
350
const gchar *service_name,
353
if (status == RB_DAAP_MDNS_BROWSER_ADD_SERVICE) {
355
RBDAAPmDNSResolver *resolver;
357
if (find_source_by_service_name (service_name)) {
358
rb_debug ("Ignoring duplicate DAAP source");
362
rb_debug ("New DAAP (music sharing) source '%s' discovered", service_name);
364
/* the resolver takes care of ignoring our own shares,
365
* if this is us, the callback won't fire
367
resolver = g_new0 (RBDAAPmDNSResolver, 1);
368
g_hash_table_insert (service_name_to_resolver, g_strdup (service_name), resolver);
369
ret = rb_daap_mdns_resolve (resolver,
371
(RBDAAPmDNSResolverCallback) resolve_cb,
374
g_warning ("could not start resolving host");
377
} else if (status == RB_DAAP_MDNS_BROWSER_REMOVE_SERVICE) {
378
RBSource *source = find_source_by_service_name (service_name);
380
rb_debug ("DAAP source '%s' went away", service_name);
381
if (source == NULL) {
382
/* if this happens, its because the user's own share
383
* went away. since that one doesnt resolve,
384
* it doesnt get added to the sources list
385
* it does have a resolver tho, so we should remove
388
g_hash_table_remove (service_name_to_resolver, service_name);
393
g_hash_table_remove (service_name_to_resolver, service_name);
394
sources = g_slist_remove (sources, source);
396
rb_daap_source_disconnect (source);
397
rb_source_delete_thyself (source);
398
/* unref is done via gtk in rb_shell_source_delete_cb at
399
* gtk_notebook_remove_page */
404
stop_resolver (RBDAAPmDNSResolver *resolver)
406
rb_daap_mdns_resolve_cancel (*resolver);
414
start_browsing (RBShell *shell)
416
if (service_name_to_resolver != NULL)
419
gboolean ret = rb_daap_mdns_browse (&browser,
420
(RBDAAPmDNSBrowserCallback) browse_cb,
424
g_warning ("Unable to start mDNS browsing");
428
service_name_to_resolver = g_hash_table_new_full ((GHashFunc)g_str_hash,
429
(GEqualFunc)g_str_equal,
430
(GDestroyNotify)g_free,
431
(GDestroyNotify)stop_resolver);
437
stop_browsing (RBShell *shell)
441
if (service_name_to_resolver == NULL)
444
g_hash_table_destroy (service_name_to_resolver);
445
service_name_to_resolver = NULL;
447
for (l = sources; l != NULL; l = l->next) {
448
RBSource *source = l->data;
450
rb_daap_source_disconnect (source);
451
rb_source_delete_thyself (source);
454
g_slist_free (sources);
458
rb_daap_mdns_browse_cancel (browser);
462
rb_daap_src_shutdown ();
466
enable_browsing_changed_cb (GConfClient *client,
471
gboolean enabled = eel_gconf_get_boolean (CONF_ENABLE_BROWSING);
474
start_browsing (shell);
476
stop_browsing (shell);
481
rb_daap_sources_init (RBShell *shell)
483
gboolean enabled = TRUE;
485
GConfClient *client = eel_gconf_client_get_global ();
487
value = gconf_client_get_without_default (client,
488
CONF_ENABLE_BROWSING, NULL);
490
enabled = gconf_value_get_bool (value);
491
gconf_value_free (value);
494
g_object_ref (shell);
497
start_browsing (shell);
500
enable_browsing_notify_id =
501
eel_gconf_notification_add (CONF_ENABLE_BROWSING,
502
(GConfClientNotifyFunc) enable_browsing_changed_cb,
509
rb_daap_sources_shutdown (RBShell *shell)
512
stop_browsing (shell);
515
if (enable_browsing_notify_id != EEL_GCONF_UNDEFINED_CONNECTION) {
516
eel_gconf_notification_remove (enable_browsing_notify_id);
517
enable_browsing_notify_id = EEL_GCONF_UNDEFINED_CONNECTION;
520
g_object_unref (shell);
525
rb_daap_source_activate (RBSource *source)
527
RBDAAPSource *daap_source = RB_DAAP_SOURCE (source);
528
RBShell *shell = NULL;
531
RhythmDBEntryType type;
533
if (daap_source->priv->connection != NULL)
536
g_object_get (G_OBJECT (daap_source),
541
g_object_get (G_OBJECT (shell), "db", &db, NULL);
543
daap_source->priv->connection =
544
rb_daap_connection_new (name,
545
daap_source->priv->host,
546
daap_source->priv->port,
547
daap_source->priv->password_protected,
550
(RBDAAPConnectionCallback) rb_daap_source_connection_cb,
552
g_object_unref (G_OBJECT (db));
553
g_object_unref (G_OBJECT (shell));
554
if (daap_source->priv->connection == NULL) {
555
/* XXX can this still happen? */
556
daap_source->priv->playlist_sources = NULL;
562
rb_daap_source_connection_cb (RBDAAPConnection *connection,
566
RBDAAPSource *daap_source = RB_DAAP_SOURCE (source);
567
RBShell *shell = NULL;
570
RhythmDBEntryType entry_type;
572
if (result == FALSE) {
573
/* FIXME display error? should get more info from the connection.. */
577
g_object_get (G_OBJECT (daap_source),
579
"entry-type", &entry_type,
581
playlists = rb_daap_connection_get_playlists (daap_source->priv->connection);
582
for (l = playlists; l != NULL; l = g_slist_next (l)) {
583
RBDAAPPlaylist *playlist = l->data;
584
RBSource *playlist_source;
586
playlist_source = RB_SOURCE (g_object_new (RB_TYPE_PLAYLIST_SOURCE,
587
"name", playlist->name,
591
"entry-type", entry_type,
593
/* this is set here instead of in construction so that
594
* rb_playlist_source_constructor has a chance to be run to set things up */
595
rb_playlist_source_add_locations (RB_PLAYLIST_SOURCE (playlist_source), playlist->uris);
597
rb_shell_append_source (shell, playlist_source, RB_SOURCE (daap_source));
598
daap_source->priv->playlist_sources = g_slist_prepend (daap_source->priv->playlist_sources, playlist_source);
600
g_object_unref (G_OBJECT (shell));
604
rb_daap_source_disconnect_cb (RBDAAPConnection *connection,
608
RBDAAPSource *daap_source = RB_DAAP_SOURCE (source);
609
g_object_unref (G_OBJECT (connection));
610
daap_source->priv->connection = NULL;
611
g_object_unref (source);
615
rb_daap_source_disconnect (RBSource *source)
617
RBDAAPSource *daap_source = RB_DAAP_SOURCE (source);
619
if (daap_source->priv->connection) {
623
RhythmDBEntryType type;
625
rb_debug ("Disconnecting source");
627
g_object_get (G_OBJECT (source), "shell", &shell, "entry-type", &type, NULL);
628
g_object_get (G_OBJECT (shell), "db", &db, NULL);
629
g_object_unref (G_OBJECT (shell));
631
rhythmdb_entry_delete_by_type (db, type);
632
rhythmdb_commit (db);
633
g_object_unref (G_OBJECT (db));
635
for (l = daap_source->priv->playlist_sources; l!= NULL; l = l->next) {
636
RBSource *playlist_source = RB_SOURCE (l->data);
638
rb_source_delete_thyself (playlist_source);
641
g_slist_free (daap_source->priv->playlist_sources);
642
daap_source->priv->playlist_sources = NULL;
644
/* keep the source alive until the disconnect completes */
645
g_object_ref (daap_source);
646
rb_daap_connection_logout (daap_source->priv->connection,
647
(RBDAAPConnectionCallback) rb_daap_source_disconnect_cb,
655
rb_daap_source_show_popup (RBSource *source)
657
_rb_source_show_popup (RB_SOURCE (source), "/DAAPSourcePopup");
662
rb_daap_source_find_for_uri (const gchar *uri)
667
RBDAAPSource *found_source = NULL;
669
ip = strdup (uri + 7); // daap://
670
s = strchr (ip, ':');
673
for (l = sources; l != NULL; l = l->next) {
674
RBDAAPSource *source = l->data;
676
if (strcmp (ip, source->priv->host) == 0) {
677
found_source = source;
689
rb_daap_source_get_headers (RBDAAPSource *source,
699
RhythmDBEntry *entry;
702
g_object_get (G_OBJECT (source), "shell", &shell, NULL);
703
g_object_get (G_OBJECT (shell), "db", &db, NULL);
705
entry = rhythmdb_entry_lookup_by_location (db, uri);
707
g_object_unref (G_OBJECT (shell));
708
g_object_unref (G_OBJECT (db));
710
bitrate = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_BITRATE);
711
// bitrate is kilobits per second
712
// a kilobit is 128 bytes
713
bytes = (time * bitrate) * 128;
717
return rb_daap_connection_get_headers (source->priv->connection, uri, bytes);
722
rb_daap_source_get_browser_key (RBSource *source)
724
return CONF_STATE_SHOW_BROWSER;
728
rb_daap_source_get_paned_key (RBLibrarySource *source)
730
return CONF_STATE_PANED_POSITION;