2006-08-02 James Livingston <doclivingston@gmail.com>
[rhythmbox.git] / daapsharing / rb-daap-connection.c
blob4a1436647cb7f625c7ce2a5cca19d84143bebd22
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * Implementation of DAAP (iTunes Music Sharing) hashing, parsing, connection
5 * Copyright (C) 2004-2005 Charles Schmidt <cschmidt2@emich.edu>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include "config.h"
25 #include <stdio.h>
26 #include <string.h>
27 #include <sys/types.h>
28 #include <math.h>
29 #ifdef HAVE_LIBZ
30 #include <zlib.h>
31 #endif
33 #include <glib/gi18n.h>
34 #include <gdk/gdk.h>
36 #include <libsoup/soup.h>
37 #include <libsoup/soup-connection.h>
38 #include <libsoup/soup-session-sync.h>
39 #include <libsoup/soup-uri.h>
41 #include "rb-daap-hash.h"
42 #include "rb-daap-connection.h"
43 #include "rb-daap-structure.h"
44 #include "rb-marshal.h"
46 #include "rb-debug.h"
47 #include "rb-util.h"
49 #define RB_DAAP_USER_AGENT "iTunes/4.6 (Windows; N)"
51 static void rb_daap_connection_dispose (GObject *obj);
52 static void rb_daap_connection_set_property (GObject *object,
53 guint prop_id,
54 const GValue *value,
55 GParamSpec *pspec);
56 static void rb_daap_connection_get_property (GObject *object,
57 guint prop_id,
58 GValue *value,
59 GParamSpec *pspec);
61 static gboolean rb_daap_connection_do_something (RBDAAPConnection *connection);
62 static void rb_daap_connection_state_done (RBDAAPConnection *connection,
63 gboolean result);
65 static gboolean emit_progress_idle (RBDAAPConnection *connection);
67 G_DEFINE_TYPE (RBDAAPConnection, rb_daap_connection, G_TYPE_OBJECT)
69 #define RB_DAAP_CONNECTION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_DAAP_CONNECTION, RBDAAPConnectionPrivate))
71 typedef void (* RBDAAPResponseHandler) (RBDAAPConnection *connection,
72 guint status,
73 GNode *structure);
75 struct RBDAAPConnectionPrivate {
76 char *name;
77 gboolean password_protected;
78 char *username;
79 char *password;
80 char *host;
81 guint port;
83 gboolean is_connected;
84 gboolean is_connecting;
86 SoupSession *session;
87 SoupUri *base_uri;
88 gchar *daap_base_uri;
90 gdouble daap_version;
91 guint32 session_id;
92 gint revision_number;
94 gint request_id;
95 gint database_id;
97 guint reading_playlist;
98 GSList *playlists;
99 GHashTable *item_id_to_uri;
101 RhythmDB *db;
102 RhythmDBEntryType db_type;
104 RBDAAPConnectionState state;
105 RBDAAPResponseHandler response_handler;
106 gboolean use_response_handler_thread;
107 float progress;
109 guint emit_progress_id;
110 guint do_something_id;
112 gboolean result;
113 char *last_error_message;
116 enum {
117 PROP_0,
118 PROP_DB,
119 PROP_NAME,
120 PROP_ENTRY_TYPE,
121 PROP_PASSWORD_PROTECTED,
122 PROP_HOST,
123 PROP_PORT,
126 enum {
127 AUTHENTICATE,
128 CONNECTING,
129 CONNECTED,
130 DISCONNECTED,
131 OPERATION_DONE,
132 LAST_SIGNAL
135 static guint signals [LAST_SIGNAL] = { 0, };
137 static void
138 rb_daap_connection_finalize (GObject *object)
140 RBDAAPConnection *connection;
142 g_return_if_fail (object != NULL);
143 g_return_if_fail (RB_IS_DAAP_CONNECTION (object));
145 connection = RB_DAAP_CONNECTION (object);
147 g_return_if_fail (connection->priv != NULL);
149 rb_debug ("Finalize");
151 G_OBJECT_CLASS (rb_daap_connection_parent_class)->finalize (object);
154 static void
155 rb_daap_connection_class_init (RBDAAPConnectionClass *klass)
157 GObjectClass *object_class = G_OBJECT_CLASS (klass);
159 object_class->finalize = rb_daap_connection_finalize;
160 object_class->dispose = rb_daap_connection_dispose;
161 object_class->set_property = rb_daap_connection_set_property;
162 object_class->get_property = rb_daap_connection_get_property;
164 g_type_class_add_private (klass, sizeof (RBDAAPConnectionPrivate));
166 g_object_class_install_property (object_class,
167 PROP_DB,
168 g_param_spec_object ("db",
169 "RhythmDB",
170 "RhythmDB object",
171 RHYTHMDB_TYPE,
172 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
173 g_object_class_install_property (object_class,
174 PROP_ENTRY_TYPE,
175 g_param_spec_boxed ("entry-type",
176 "entry type",
177 "RhythmDBEntryType",
178 RHYTHMDB_TYPE_ENTRY_TYPE,
179 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
181 g_object_class_install_property (object_class,
182 PROP_PASSWORD_PROTECTED,
183 g_param_spec_boolean ("password-protected",
184 "password protected",
185 "connection is password protected",
186 FALSE,
187 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
188 g_object_class_install_property (object_class,
189 PROP_NAME,
190 g_param_spec_string ("name",
191 "connection name",
192 "connection name",
193 NULL,
194 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
195 g_object_class_install_property (object_class,
196 PROP_HOST,
197 g_param_spec_string ("host",
198 "host",
199 "host",
200 NULL,
201 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
202 g_object_class_install_property (object_class,
203 PROP_PORT,
204 g_param_spec_uint ("port",
205 "port",
206 "port",
207 0, G_MAXINT, 0,
208 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
210 signals [AUTHENTICATE] = g_signal_new ("authenticate",
211 G_TYPE_FROM_CLASS (object_class),
212 G_SIGNAL_RUN_LAST,
213 G_STRUCT_OFFSET (RBDAAPConnectionClass, authenticate),
214 NULL,
215 NULL,
216 rb_marshal_STRING__STRING,
217 G_TYPE_STRING,
218 1, G_TYPE_STRING);
219 signals [CONNECTING] = g_signal_new ("connecting",
220 G_TYPE_FROM_CLASS (object_class),
221 G_SIGNAL_RUN_LAST,
222 G_STRUCT_OFFSET (RBDAAPConnectionClass, connecting),
223 NULL,
224 NULL,
225 rb_marshal_VOID__ULONG_FLOAT,
226 G_TYPE_NONE,
227 2, G_TYPE_ULONG, G_TYPE_FLOAT);
228 signals [CONNECTED] = g_signal_new ("connected",
229 G_TYPE_FROM_CLASS (object_class),
230 G_SIGNAL_RUN_LAST,
231 G_STRUCT_OFFSET (RBDAAPConnectionClass, connected),
232 NULL,
233 NULL,
234 g_cclosure_marshal_VOID__VOID,
235 G_TYPE_NONE,
237 signals [DISCONNECTED] = g_signal_new ("disconnected",
238 G_TYPE_FROM_CLASS (object_class),
239 G_SIGNAL_RUN_LAST,
240 G_STRUCT_OFFSET (RBDAAPConnectionClass, disconnected),
241 NULL,
242 NULL,
243 g_cclosure_marshal_VOID__VOID,
244 G_TYPE_NONE,
246 signals [OPERATION_DONE] = g_signal_new ("operation-done",
247 G_TYPE_FROM_CLASS (object_class),
248 G_SIGNAL_RUN_FIRST,
249 G_STRUCT_OFFSET (RBDAAPConnectionClass, operation_done),
250 NULL,
251 NULL,
252 g_cclosure_marshal_VOID__VOID,
253 G_TYPE_NONE,
257 static void
258 rb_daap_connection_init (RBDAAPConnection *connection)
260 connection->priv = RB_DAAP_CONNECTION_GET_PRIVATE (connection);
262 connection->priv->username = g_strdup_printf ("Rhythmbox_%s", VERSION);
263 connection->priv->db_type = RHYTHMDB_ENTRY_TYPE_INVALID;
266 static char *
267 connection_get_password (RBDAAPConnection *connection)
269 char *password = NULL;;
271 GDK_THREADS_ENTER ();
272 g_signal_emit (connection,
273 signals [AUTHENTICATE],
275 connection->priv->name,
276 &password);
277 GDK_THREADS_LEAVE ();
279 return password;
282 static void
283 connection_connected (RBDAAPConnection *connection)
285 rb_debug ("Emitting connected");
287 connection->priv->is_connected = TRUE;
289 g_signal_emit (connection,
290 signals [CONNECTED],
294 static void
295 connection_disconnected (RBDAAPConnection *connection)
297 rb_debug ("Emitting disconnected");
299 connection->priv->is_connected = FALSE;
301 g_signal_emit (connection,
302 signals [DISCONNECTED],
306 static void
307 connection_operation_done (RBDAAPConnection *connection)
309 rb_debug ("Emitting operation done");
311 g_signal_emit (connection,
312 signals [OPERATION_DONE],
316 static SoupMessage *
317 build_message (RBDAAPConnection *connection,
318 const char *path,
319 gboolean need_hash,
320 gdouble version,
321 gint req_id,
322 gboolean send_close)
324 RBDAAPConnectionPrivate *priv = connection->priv;
325 SoupMessage *message = NULL;
326 SoupUri *uri = NULL;
328 uri = soup_uri_new_with_base (priv->base_uri, path);
329 if (uri == NULL) {
330 return NULL;
333 message = soup_message_new_from_uri (SOUP_METHOD_GET, uri);
334 soup_message_set_http_version (message, SOUP_HTTP_1_1);
336 soup_message_add_header (message->request_headers, "Client-DAAP-Version", "3.0");
337 soup_message_add_header (message->request_headers, "Accept-Language", "en-us, en;q=5.0");
338 #ifdef HAVE_LIBZ
339 soup_message_add_header (message->request_headers, "Accept-Encoding", "gzip");
340 #endif
341 soup_message_add_header (message->request_headers, "Client-DAAP-Access-Index", "2");
343 if (priv->password_protected) {
344 char *h;
345 char *user_pass;
346 char *token;
348 user_pass = g_strdup_printf ("%s:%s", priv->username, priv->password);
349 token = soup_base64_encode (user_pass, strlen (user_pass));
350 h = g_strdup_printf ("Basic %s", token);
352 g_free (token);
353 g_free (user_pass);
355 soup_message_add_header (message->request_headers, "Authorization", h);
356 g_free (h);
359 if (need_hash) {
360 gchar hash[33] = {0};
361 gchar *no_daap_path = (gchar *)path;
363 if (g_strncasecmp (path, "daap://", 7) == 0) {
364 no_daap_path = strstr (path, "/data");
367 rb_daap_hash_generate ((short)floor (version), (const guchar*)no_daap_path, 2, (guchar*)hash, req_id);
369 soup_message_add_header (message->request_headers, "Client-DAAP-Validation", hash);
371 if (send_close) {
372 soup_message_add_header (message->request_headers, "Connection", "close");
375 soup_uri_free (uri);
377 return message;
380 #ifdef HAVE_LIBZ
381 static void
382 *g_zalloc_wrapper (voidpf opaque, uInt items, uInt size)
384 if ((items != 0) && (size >= G_MAXUINT/items)) {
385 return Z_NULL;
387 if ((size != 0) && (items >= G_MAXUINT/size)) {
388 return Z_NULL;
390 return g_malloc0 (items * size);
393 static void
394 g_zfree_wrapper (voidpf opaque, voidpf address)
396 g_free (address);
398 #endif
400 static void
401 connection_set_error_message (RBDAAPConnection *connection,
402 const char *message)
404 /* FIXME: obtain a lock */
405 if (connection->priv->last_error_message != NULL) {
406 g_free (connection->priv->last_error_message);
408 connection->priv->last_error_message = g_strdup (message);
411 typedef struct {
412 SoupMessage *message;
413 int status;
414 RBDAAPConnection *connection;
415 } DAAPResponseData;
417 static void
418 actual_http_response_handler (DAAPResponseData *data)
420 RBDAAPConnectionPrivate *priv;
421 GNode *structure;
422 char *response;
423 const char *encoding_header;
424 char *message_path;
425 int response_length;
427 priv = data->connection->priv;
428 structure = NULL;
429 response = data->message->response.body;
430 encoding_header = NULL;
431 response_length = data->message->response.length;
433 message_path = soup_uri_to_string (soup_message_get_uri (data->message), FALSE);
435 rb_debug ("Received response from %s: %d, %s\n",
436 message_path,
437 data->message->status_code,
438 data->message->reason_phrase);
440 if (data->message->response_headers) {
441 encoding_header = soup_message_get_header (data->message->response_headers, "Content-Encoding");
444 if (SOUP_STATUS_IS_SUCCESSFUL (data->status) && encoding_header && strcmp (encoding_header, "gzip") == 0) {
445 #ifdef HAVE_LIBZ
446 z_stream stream;
447 char *new_response;
448 unsigned int factor = 4;
449 unsigned int unc_size = response_length * factor;
451 stream.next_in = (unsigned char *)response;
452 stream.avail_in = response_length;
453 stream.total_in = 0;
455 new_response = g_malloc (unc_size + 1);
456 stream.next_out = (unsigned char *)new_response;
457 stream.avail_out = unc_size;
458 stream.total_out = 0;
459 stream.zalloc = g_zalloc_wrapper;
460 stream.zfree = g_zfree_wrapper;
461 stream.opaque = NULL;
463 rb_profile_start ("decompressing DAAP response");
465 if (inflateInit2 (&stream, 32 /* auto-detect */ + 15 /* max */ ) != Z_OK) {
466 inflateEnd (&stream);
467 g_free (new_response);
468 rb_debug ("Unable to decompress response from %s",
469 message_path);
470 data->status = SOUP_STATUS_MALFORMED;
471 rb_profile_end ("decompressing DAAP response (failed)");
472 } else {
473 do {
474 int z_res;
476 rb_profile_start ("attempting inflate");
477 z_res = inflate (&stream, Z_FINISH);
478 if (z_res == Z_STREAM_END) {
479 rb_profile_end ("attempting inflate (done)");
480 break;
482 if ((z_res != Z_OK && z_res != Z_BUF_ERROR) || stream.avail_out != 0 || unc_size > 40*1000*1000) {
483 inflateEnd (&stream);
484 g_free (new_response);
485 new_response = NULL;
486 rb_profile_end ("attempting inflate (error)");
487 break;
490 factor *= 4;
491 unc_size = (response_length * factor);
492 /* unc_size can't grow bigger than 40MB, so
493 * unc_size can't overflow, and this realloc
494 * call is safe
496 new_response = g_realloc (new_response, unc_size + 1);
497 stream.next_out = (unsigned char *)(new_response + stream.total_out);
498 stream.avail_out = unc_size - stream.total_out;
499 rb_profile_end ("attempting inflate (incomplete)");
500 } while (1);
502 rb_profile_end ("decompressing DAAP response (successful)");
504 if (new_response) {
505 response = new_response;
506 response_length = stream.total_out;
508 #else
509 rb_debug ("Received compressed response from %s but can't handle it",
510 message_path);
511 data->status = SOUP_STATUS_MALFORMED;
512 #endif
515 if (SOUP_STATUS_IS_SUCCESSFUL (data->status)) {
516 RBDAAPItem *item;
518 if (!rb_is_main_thread ()) {
519 priv->progress = -1.0f;
520 if (priv->emit_progress_id != 0) {
521 g_source_remove (priv->emit_progress_id);
523 priv->emit_progress_id = g_idle_add ((GSourceFunc) emit_progress_idle, data->connection);
525 rb_profile_start ("parsing DAAP response");
526 structure = rb_daap_structure_parse (response, response_length);
527 if (structure == NULL) {
528 rb_debug ("No daap structure returned from %s",
529 message_path);
531 data->status = SOUP_STATUS_MALFORMED;
532 rb_profile_end ("parsing DAAP response (failed)");
533 } else {
534 int dmap_status = 0;
535 item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MSTT);
536 if (item)
537 dmap_status = g_value_get_int (&(item->content));
539 if (dmap_status != 200) {
540 rb_debug ("Error, dmap.status is not 200 in response from %s",
541 message_path);
543 data->status = SOUP_STATUS_MALFORMED;
545 rb_profile_end ("parsing DAAP response (successful)");
547 if (! rb_is_main_thread ()) {
548 priv->progress = 1.0f;
549 if (priv->emit_progress_id != 0) {
550 g_source_remove (priv->emit_progress_id);
552 priv->emit_progress_id = g_idle_add ((GSourceFunc) emit_progress_idle, data->connection);
554 } else {
555 rb_debug ("Error getting %s: %d, %s\n",
556 message_path,
557 data->message->status_code,
558 data->message->reason_phrase);
559 connection_set_error_message (data->connection, data->message->reason_phrase);
562 if (priv->response_handler) {
563 RBDAAPResponseHandler h = priv->response_handler;
564 priv->response_handler = NULL;
565 (*h) (data->connection, data->status, structure);
568 if (structure) {
569 rb_daap_structure_destroy (structure);
572 if (response != data->message->response.body) {
573 g_free (response);
576 g_free (message_path);
577 g_object_unref (G_OBJECT (data->connection));
578 g_object_unref (G_OBJECT (data->message));
579 g_free (data);
582 static void
583 http_response_handler (SoupMessage *message,
584 RBDAAPConnection *connection)
586 DAAPResponseData *data;
587 int response_length;
589 if (message->status_code == SOUP_STATUS_CANCELLED) {
590 rb_debug ("Message cancelled");
591 return;
594 data = g_new0 (DAAPResponseData, 1);
595 data->status = message->status_code;
596 response_length = message->response.length;
598 g_object_ref (G_OBJECT (connection));
599 data->connection = connection;
601 g_object_ref (G_OBJECT (message));
602 data->message = message;
604 if (response_length >= G_MAXUINT/4 - 1) {
605 /* If response_length is too big,
606 * the g_malloc (unc_size + 1) below would overflow
608 data->status = SOUP_STATUS_MALFORMED;
611 /* to avoid blocking the UI, handle big responses in a separate thread */
612 if (SOUP_STATUS_IS_SUCCESSFUL (data->status) && connection->priv->use_response_handler_thread) {
613 GError *error = NULL;
614 rb_debug ("creating thread to handle daap response");
615 g_thread_create ((GThreadFunc) actual_http_response_handler,
616 data,
617 FALSE,
618 &error);
619 if (error) {
620 g_warning ("fuck");
622 } else {
623 actual_http_response_handler (data);
627 static gboolean
628 http_get (RBDAAPConnection *connection,
629 const char *path,
630 gboolean need_hash,
631 gdouble version,
632 gint req_id,
633 gboolean send_close,
634 RBDAAPResponseHandler handler,
635 gboolean use_thread)
637 RBDAAPConnectionPrivate *priv = connection->priv;
638 SoupMessage *message;
640 message = build_message (connection, path, need_hash, version, req_id, send_close);
641 if (message == NULL) {
642 rb_debug ("Error building message for http://%s:%d/%s",
643 priv->base_uri->host,
644 priv->base_uri->port,
645 path);
646 return FALSE;
649 priv->use_response_handler_thread = use_thread;
650 priv->response_handler = handler;
651 soup_session_queue_message (priv->session, message,
652 (SoupMessageCallbackFn) http_response_handler,
653 connection);
654 rb_debug ("Queued message for http://%s:%d/%s",
655 priv->base_uri->host,
656 priv->base_uri->port,
657 path);
658 return TRUE;
661 static void
662 entry_set_string_prop (RhythmDB *db,
663 RhythmDBEntry *entry,
664 RhythmDBPropType propid,
665 const char *str)
667 GValue value = {0,};
668 const gchar *tmp;
670 if (str == NULL || *str == '\0' || !g_utf8_validate (str, -1, NULL)) {
671 tmp = _("Unknown");
672 } else {
673 tmp = str;
676 g_value_init (&value, G_TYPE_STRING);
677 g_value_set_string (&value, tmp);
678 rhythmdb_entry_set (RHYTHMDB (db), entry, propid, &value);
679 g_value_unset (&value);
682 static gboolean
683 emit_progress_idle (RBDAAPConnection *connection)
685 rb_debug ("Emitting progress");
687 GDK_THREADS_ENTER ();
688 g_signal_emit (G_OBJECT (connection), signals[CONNECTING], 0,
689 connection->priv->state,
690 connection->priv->progress);
691 connection->priv->emit_progress_id = 0;
692 GDK_THREADS_LEAVE ();
693 return FALSE;
696 static void
697 handle_server_info (RBDAAPConnection *connection,
698 guint status,
699 GNode *structure)
701 RBDAAPConnectionPrivate *priv = connection->priv;
702 RBDAAPItem *item = NULL;
704 if (!SOUP_STATUS_IS_SUCCESSFUL (status) || structure == NULL) {
705 rb_daap_connection_state_done (connection, FALSE);
706 return;
709 /* get the daap version number */
710 item = rb_daap_structure_find_item (structure, RB_DAAP_CC_APRO);
711 if (item == NULL) {
712 rb_daap_connection_state_done (connection, FALSE);
713 return;
716 priv->daap_version = g_value_get_double (&(item->content));
717 rb_daap_connection_state_done (connection, TRUE);
720 static void
721 handle_login (RBDAAPConnection *connection,
722 guint status,
723 GNode *structure)
725 RBDAAPConnectionPrivate *priv = connection->priv;
726 RBDAAPItem *item = NULL;
728 if (status == SOUP_STATUS_UNAUTHORIZED || status == SOUP_STATUS_FORBIDDEN) {
729 rb_debug ("Incorrect password");
730 priv->state = DAAP_GET_PASSWORD;
731 if (priv->do_something_id != 0) {
732 g_source_remove (priv->do_something_id);
734 priv->do_something_id = g_idle_add ((GSourceFunc) rb_daap_connection_do_something, connection);
735 return;
738 if (structure == NULL || SOUP_STATUS_IS_SUCCESSFUL (status) == FALSE) {
739 rb_daap_connection_state_done (connection, FALSE);
740 return;
743 item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MLID);
744 if (item == NULL) {
745 rb_debug ("Could not find daap.sessionid item in /login");
746 rb_daap_connection_state_done (connection, FALSE);
747 return;
750 priv->session_id = (guint32) g_value_get_int (&(item->content));
752 connection_connected (connection);
754 rb_daap_connection_state_done (connection, TRUE);
757 static void
758 handle_update (RBDAAPConnection *connection,
759 guint status,
760 GNode *structure)
762 RBDAAPConnectionPrivate *priv = connection->priv;
763 RBDAAPItem *item;
765 if (structure == NULL || SOUP_STATUS_IS_SUCCESSFUL (status) == FALSE) {
766 rb_daap_connection_state_done (connection, FALSE);
767 return;
770 /* get a revision number */
771 item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MUSR);
772 if (item == NULL) {
773 rb_debug ("Could not find daap.serverrevision item in /update");
774 rb_daap_connection_state_done (connection, FALSE);
775 return;
778 priv->revision_number = g_value_get_int (&(item->content));
779 rb_daap_connection_state_done (connection, TRUE);
782 static void
783 handle_database_info (RBDAAPConnection *connection,
784 guint status,
785 GNode *structure)
787 RBDAAPConnectionPrivate *priv = connection->priv;
788 RBDAAPItem *item = NULL;
789 GNode *listing_node;
790 gint n_databases = 0;
792 /* get a list of databases, there should be only 1 */
794 if (structure == NULL || SOUP_STATUS_IS_SUCCESSFUL (status) == FALSE) {
795 rb_daap_connection_state_done (connection, FALSE);
796 return;
799 item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MRCO);
800 if (item == NULL) {
801 rb_debug ("Could not find dmap.returnedcount item in /databases");
802 rb_daap_connection_state_done (connection, FALSE);
803 return;
806 n_databases = g_value_get_int (&(item->content));
807 if (n_databases != 1) {
808 rb_debug ("Host seems to have more than 1 database, how strange\n");
811 listing_node = rb_daap_structure_find_node (structure, RB_DAAP_CC_MLCL);
812 if (listing_node == NULL) {
813 rb_debug ("Could not find dmap.listing item in /databases");
814 rb_daap_connection_state_done (connection, FALSE);
815 return;
818 item = rb_daap_structure_find_item (listing_node->children, RB_DAAP_CC_MIID);
819 if (item == NULL) {
820 rb_debug ("Could not find dmap.itemid item in /databases");
821 rb_daap_connection_state_done (connection, FALSE);
822 return;
825 priv->database_id = g_value_get_int (&(item->content));
826 rb_daap_connection_state_done (connection, TRUE);
829 static void
830 handle_song_listing (RBDAAPConnection *connection,
831 guint status,
832 GNode *structure)
834 RBDAAPConnectionPrivate *priv = connection->priv;
835 RBDAAPItem *item = NULL;
836 GNode *listing_node;
837 gint returned_count;
838 gint i;
839 GNode *n;
840 gint specified_total_count;
841 gboolean update_type;
842 gint commit_batch;
844 /* get the songs */
846 if (structure == NULL || SOUP_STATUS_IS_SUCCESSFUL (status) == FALSE) {
847 rb_daap_connection_state_done (connection, FALSE);
848 return;
851 item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MRCO);
852 if (item == NULL) {
853 rb_debug ("Could not find dmap.returnedcount item in /databases/%d/items",
854 priv->database_id);
855 rb_daap_connection_state_done (connection, FALSE);
856 return;
858 returned_count = g_value_get_int (&(item->content));
859 if (returned_count > 20) {
860 commit_batch = returned_count / 20;
861 } else {
862 commit_batch = 1;
865 item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MTCO);
866 if (item == NULL) {
867 rb_debug ("Could not find dmap.specifiedtotalcount item in /databases/%d/items",
868 priv->database_id);
869 rb_daap_connection_state_done (connection, FALSE);
870 return;
872 specified_total_count = g_value_get_int (&(item->content));
874 item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MUTY);
875 if (item == NULL) {
876 rb_debug ("Could not find dmap.updatetype item in /databases/%d/items",
877 priv->database_id);
878 rb_daap_connection_state_done (connection, FALSE);
879 return;
881 update_type = g_value_get_char (&(item->content));
883 listing_node = rb_daap_structure_find_node (structure, RB_DAAP_CC_MLCL);
884 if (listing_node == NULL) {
885 rb_debug ("Could not find dmap.listing item in /databases/%d/items",
886 priv->database_id);
887 rb_daap_connection_state_done (connection, FALSE);
888 return;
891 priv->item_id_to_uri = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)rb_refstring_unref);
893 rb_profile_start ("handling song listing");
894 priv->progress = 0.0f;
895 if (priv->emit_progress_id != 0) {
896 g_source_remove (priv->emit_progress_id);
898 connection->priv->emit_progress_id = g_idle_add ((GSourceFunc) emit_progress_idle, connection);
900 for (i = 0, n = listing_node->children; n; i++, n = n->next) {
901 GNode *n2;
902 RhythmDBEntry *entry = NULL;
903 GValue value = {0,};
904 gchar *uri = NULL;
905 gint item_id = 0;
906 const gchar *title = NULL;
907 const gchar *album = NULL;
908 const gchar *artist = NULL;
909 const gchar *format = NULL;
910 const gchar *genre = NULL;
911 gint length = 0;
912 gint track_number = 0;
913 gint disc_number = 0;
914 gint year = 0;
915 gint size = 0;
916 gint bitrate = 0;
918 for (n2 = n->children; n2; n2 = n2->next) {
919 RBDAAPItem *meta_item;
921 meta_item = n2->data;
923 switch (meta_item->content_code) {
924 case RB_DAAP_CC_MIID:
925 item_id = g_value_get_int (&(meta_item->content));
926 break;
927 case RB_DAAP_CC_MINM:
928 title = g_value_get_string (&(meta_item->content));
929 break;
930 case RB_DAAP_CC_ASAL:
931 album = g_value_get_string (&(meta_item->content));
932 break;
933 case RB_DAAP_CC_ASAR:
934 artist = g_value_get_string (&(meta_item->content));
935 break;
936 case RB_DAAP_CC_ASFM:
937 format = g_value_get_string (&(meta_item->content));
938 break;
939 case RB_DAAP_CC_ASGN:
940 genre = g_value_get_string (&(meta_item->content));
941 break;
942 case RB_DAAP_CC_ASTM:
943 length = g_value_get_int (&(meta_item->content));
944 break;
945 case RB_DAAP_CC_ASTN:
946 track_number = g_value_get_int (&(meta_item->content));
947 break;
948 case RB_DAAP_CC_ASDN:
949 disc_number = g_value_get_int (&(meta_item->content));
950 break;
951 case RB_DAAP_CC_ASYR:
952 year = g_value_get_int (&(meta_item->content));
953 break;
954 case RB_DAAP_CC_ASSZ:
955 size = g_value_get_int (&(meta_item->content));
956 break;
957 case RB_DAAP_CC_ASBR:
958 bitrate = g_value_get_int (&(meta_item->content));
959 break;
960 default:
961 break;
965 /*if (connection->daap_version == 3.0) {*/
966 uri = g_strdup_printf ("%s/databases/%d/items/%d.%s?session-id=%u",
967 priv->daap_base_uri,
968 priv->database_id,
969 item_id, format,
970 priv->session_id);
971 /*} else {*/
972 /* uri should be
973 * "/databases/%d/items/%d.%s?session-id=%u&revision-id=%d";
974 * but its not going to work cause the other parts of the code
975 * depend on the uri to have the ip address so that the
976 * RBDAAPSource can be found to ++request_id
977 * maybe just /dont/ support older itunes. doesn't seem
978 * unreasonable to me, honestly
980 /*}*/
981 entry = rhythmdb_entry_new (priv->db, priv->db_type, uri);
982 if (entry == NULL) {
983 rb_debug ("cannot create entry for daap track %s", uri);
984 continue;
986 g_hash_table_insert (priv->item_id_to_uri, GINT_TO_POINTER (item_id), rb_refstring_new (uri));
987 g_free (uri);
989 /* year */
990 if (year != 0) {
991 GDate *date;
992 gulong julian;
994 /* create dummy date with given year */
995 date = g_date_new_dmy (1, G_DATE_JANUARY, year);
996 julian = g_date_get_julian (date);
997 g_date_free (date);
999 g_value_init (&value, G_TYPE_ULONG);
1000 g_value_set_ulong (&value,julian);
1001 rhythmdb_entry_set (priv->db, entry, RHYTHMDB_PROP_DATE, &value);
1002 g_value_unset (&value);
1005 /* track number */
1006 g_value_init (&value, G_TYPE_ULONG);
1007 g_value_set_ulong (&value,(gulong)track_number);
1008 rhythmdb_entry_set (priv->db, entry, RHYTHMDB_PROP_TRACK_NUMBER, &value);
1009 g_value_unset (&value);
1011 /* disc number */
1012 g_value_init (&value, G_TYPE_ULONG);
1013 g_value_set_ulong (&value,(gulong)disc_number);
1014 rhythmdb_entry_set (priv->db, entry, RHYTHMDB_PROP_DISC_NUMBER, &value);
1015 g_value_unset (&value);
1017 /* bitrate */
1018 g_value_init (&value, G_TYPE_ULONG);
1019 g_value_set_ulong (&value,(gulong)bitrate);
1020 rhythmdb_entry_set (priv->db, entry, RHYTHMDB_PROP_BITRATE, &value);
1021 g_value_unset (&value);
1023 /* length */
1024 g_value_init (&value, G_TYPE_ULONG);
1025 g_value_set_ulong (&value,(gulong)length / 1000);
1026 rhythmdb_entry_set (priv->db, entry, RHYTHMDB_PROP_DURATION, &value);
1027 g_value_unset (&value);
1029 /* file size */
1030 g_value_init (&value, G_TYPE_UINT64);
1031 g_value_set_uint64(&value,(gint64)size);
1032 rhythmdb_entry_set (priv->db, entry, RHYTHMDB_PROP_FILE_SIZE, &value);
1033 g_value_unset (&value);
1035 /* title */
1036 entry_set_string_prop (priv->db, entry, RHYTHMDB_PROP_TITLE, title);
1038 /* album */
1039 entry_set_string_prop (priv->db, entry, RHYTHMDB_PROP_ALBUM, album);
1041 /* artist */
1042 entry_set_string_prop (priv->db, entry, RHYTHMDB_PROP_ARTIST, artist);
1044 /* genre */
1045 entry_set_string_prop (priv->db, entry, RHYTHMDB_PROP_GENRE, genre);
1047 if (i % commit_batch == 0) {
1048 connection->priv->progress = ((float)i / (float)returned_count);
1049 if (priv->emit_progress_id != 0) {
1050 g_source_remove (connection->priv->emit_progress_id);
1052 connection->priv->emit_progress_id = g_idle_add ((GSourceFunc) emit_progress_idle, connection);
1053 rhythmdb_commit (priv->db);
1056 rb_profile_end ("handling song listing");
1058 rb_daap_connection_state_done (connection, TRUE);
1061 /* FIXME
1062 * what we really should do is only get a list of playlists and their ids
1063 * then when they are clicked on ('activate'd) by the user, get a list of
1064 * the files that are actually in them. This will speed up initial daap
1065 * connection times and reduce memory consumption.
1068 static void
1069 handle_playlists (RBDAAPConnection *connection,
1070 guint status,
1071 GNode *structure)
1073 RBDAAPConnectionPrivate *priv = connection->priv;
1074 GNode *listing_node;
1075 gint i;
1076 GNode *n;
1078 if (structure == NULL || SOUP_STATUS_IS_SUCCESSFUL (status) == FALSE) {
1079 rb_daap_connection_state_done (connection, FALSE);
1080 return;
1083 listing_node = rb_daap_structure_find_node (structure, RB_DAAP_CC_MLCL);
1084 if (listing_node == NULL) {
1085 rb_debug ("Could not find dmap.listing item in /databases/%d/containers",
1086 priv->database_id);
1087 rb_daap_connection_state_done (connection, FALSE);
1088 return;
1091 for (i = 0, n = listing_node->children; n; n = n->next, i++) {
1092 RBDAAPItem *item;
1093 gint id;
1094 gchar *name;
1095 RBDAAPPlaylist *playlist;
1097 item = rb_daap_structure_find_item (n, RB_DAAP_CC_ABPL);
1098 if (item != NULL) {
1099 continue;
1102 item = rb_daap_structure_find_item (n, RB_DAAP_CC_MIID);
1103 if (item == NULL) {
1104 rb_debug ("Could not find dmap.itemid item in /databases/%d/containers",
1105 priv->database_id);
1106 continue;
1108 id = g_value_get_int (&(item->content));
1110 item = rb_daap_structure_find_item (n, RB_DAAP_CC_MINM);
1111 if (item == NULL) {
1112 rb_debug ("Could not find dmap.itemname item in /databases/%d/containers",
1113 priv->database_id);
1114 continue;
1116 name = g_value_dup_string (&(item->content));
1118 playlist = g_new0 (RBDAAPPlaylist, 1);
1119 playlist->id = id;
1120 playlist->name = name;
1121 rb_debug ("Got playlist %p: name %s, id %d", playlist, playlist->name, playlist->id);
1123 priv->playlists = g_slist_prepend (priv->playlists, playlist);
1126 rb_daap_connection_state_done (connection, TRUE);
1129 static void
1130 handle_playlist_entries (RBDAAPConnection *connection,
1131 guint status,
1132 GNode *structure)
1134 RBDAAPConnectionPrivate *priv = connection->priv;
1135 RBDAAPPlaylist *playlist;
1136 GNode *listing_node;
1137 GNode *node;
1138 gint i;
1139 GList *playlist_uris = NULL;
1141 if (structure == NULL || SOUP_STATUS_IS_SUCCESSFUL (status) == FALSE) {
1142 rb_daap_connection_state_done (connection, FALSE);
1143 return;
1146 playlist = (RBDAAPPlaylist *)g_slist_nth_data (priv->playlists, priv->reading_playlist);
1147 g_assert (playlist);
1149 listing_node = rb_daap_structure_find_node (structure, RB_DAAP_CC_MLCL);
1150 if (listing_node == NULL) {
1151 rb_debug ("Could not find dmap.listing item in /databases/%d/containers/%d/items",
1152 priv->database_id, playlist->id);
1153 rb_daap_connection_state_done (connection, FALSE);
1154 return;
1157 rb_profile_start ("handling playlist entries");
1158 for (i = 0, node = listing_node->children; node; node = node->next, i++) {
1159 RBRefString *item_uri;
1160 gint playlist_item_id;
1161 RBDAAPItem *item;
1163 item = rb_daap_structure_find_item (node, RB_DAAP_CC_MIID);
1164 if (item == NULL) {
1165 rb_debug ("Could not find dmap.itemid item in /databases/%d/containers/%d/items",
1166 priv->database_id, playlist->id);
1167 continue;
1169 playlist_item_id = g_value_get_int (&(item->content));
1171 item_uri = g_hash_table_lookup (priv->item_id_to_uri, GINT_TO_POINTER (playlist_item_id));
1172 if (item_uri == NULL) {
1173 rb_debug ("Entry %d in playlist %s doesn't exist in the database\n",
1174 playlist_item_id, playlist->name);
1175 continue;
1178 playlist_uris = g_list_prepend (playlist_uris, rb_refstring_ref (item_uri));
1180 rb_profile_end ("handling playlist entries");
1182 playlist->uris = playlist_uris;
1183 rb_daap_connection_state_done (connection, TRUE);
1186 static void
1187 handle_logout (RBDAAPConnection *connection,
1188 guint status,
1189 GNode *structure)
1191 connection_disconnected (connection);
1193 /* is there any point handling errors here? */
1194 rb_daap_connection_state_done (connection, TRUE);
1197 RBDAAPConnection *
1198 rb_daap_connection_new (const char *name,
1199 const char *host,
1200 int port,
1201 gboolean password_protected,
1202 RhythmDB *db,
1203 RhythmDBEntryType type)
1205 return g_object_new (RB_TYPE_DAAP_CONNECTION,
1206 "name", name,
1207 "entry-type", type,
1208 "password-protected", password_protected,
1209 "db", db,
1210 "host", host,
1211 "port", port,
1212 NULL);
1215 gboolean
1216 rb_daap_connection_is_connected (RBDAAPConnection *connection)
1218 g_return_val_if_fail (RB_IS_DAAP_CONNECTION (connection), FALSE);
1220 return connection->priv->is_connected;
1223 typedef struct {
1224 RBDAAPConnection *connection;
1225 RBDAAPConnectionCallback callback;
1226 gpointer data;
1227 GDestroyNotify destroy;
1228 } ConnectionResponseData;
1230 static void
1231 connection_response_data_free (gpointer data)
1233 ConnectionResponseData *rdata = data;
1235 g_object_unref (rdata->connection);
1236 g_free (rdata);
1239 static void
1240 connected_cb (RBDAAPConnection *connection,
1241 ConnectionResponseData *rdata)
1243 gboolean result;
1245 rb_debug ("Connected callback");
1247 connection->priv->is_connecting = FALSE;
1249 g_signal_handlers_disconnect_by_func (connection,
1250 G_CALLBACK (connected_cb),
1251 rdata);
1253 /* if connected then we succeeded */
1254 result = rb_daap_connection_is_connected (connection);
1256 if (rdata->callback) {
1257 rdata->callback (rdata->connection,
1258 result,
1259 rdata->connection->priv->last_error_message,
1260 rdata->data);
1263 if (rdata->destroy) {
1264 rdata->destroy (rdata);
1268 void
1269 rb_daap_connection_connect (RBDAAPConnection *connection,
1270 RBDAAPConnectionCallback callback,
1271 gpointer user_data)
1273 ConnectionResponseData *rdata;
1274 char *path;
1276 g_return_if_fail (RB_IS_DAAP_CONNECTION (connection));
1277 g_return_if_fail (connection->priv->state == DAAP_GET_INFO);
1279 rb_debug ("Creating new DAAP connection to %s:%d", connection->priv->host, connection->priv->port);
1281 connection->priv->session = soup_session_async_new ();
1283 path = g_strdup_printf ("http://%s:%d", connection->priv->host, connection->priv->port);
1284 connection->priv->base_uri = soup_uri_new (path);
1285 g_free (path);
1287 if (connection->priv->base_uri == NULL) {
1288 rb_debug ("Error parsing http://%s:%d", connection->priv->host, connection->priv->port);
1289 /* FIXME: do callback */
1290 return;
1293 connection->priv->daap_base_uri = g_strdup_printf ("daap://%s:%d", connection->priv->host, connection->priv->port);
1295 rdata = g_new (ConnectionResponseData, 1);
1296 rdata->connection = g_object_ref (connection);
1297 rdata->callback = callback;
1298 rdata->data = user_data;
1299 rdata->destroy = connection_response_data_free;
1300 g_signal_connect (connection, "operation-done", G_CALLBACK (connected_cb), rdata);
1302 if (connection->priv->do_something_id != 0) {
1303 g_source_remove (connection->priv->do_something_id);
1306 connection->priv->is_connecting = TRUE;
1307 connection->priv->do_something_id = g_idle_add ((GSourceFunc) rb_daap_connection_do_something, connection);
1310 static void
1311 disconnected_cb (RBDAAPConnection *connection,
1312 ConnectionResponseData *rdata)
1314 gboolean result;
1316 rb_debug ("Disconnected callback");
1318 g_signal_handlers_disconnect_by_func (connection,
1319 G_CALLBACK (disconnected_cb),
1320 rdata);
1322 /* if not connected then we succeeded */
1323 result = ! rb_daap_connection_is_connected (connection);
1325 if (rdata->callback) {
1326 rdata->callback (rdata->connection,
1327 result,
1328 rdata->connection->priv->last_error_message,
1329 rdata->data);
1332 if (rdata->destroy) {
1333 rdata->destroy (rdata);
1337 static void
1338 rb_daap_connection_finish (RBDAAPConnection *connection)
1340 g_return_if_fail (RB_IS_DAAP_CONNECTION (connection));
1342 rb_debug ("DAAP finish");
1343 connection->priv->state = DAAP_DONE;
1344 connection->priv->progress = 1.0f;
1346 connection_operation_done (connection);
1349 void
1350 rb_daap_connection_disconnect (RBDAAPConnection *connection,
1351 RBDAAPConnectionCallback callback,
1352 gpointer user_data)
1354 RBDAAPConnectionPrivate *priv = connection->priv;
1355 ConnectionResponseData *rdata;
1357 g_return_if_fail (RB_IS_DAAP_CONNECTION (connection));
1359 rb_debug ("Disconnecting");
1361 if (connection->priv->is_connecting) {
1362 /* this is a special case where the async connection
1363 hasn't returned yet so we need to force the connection
1364 to finish */
1365 priv->state = DAAP_DONE;
1366 rb_daap_connection_finish (connection);
1369 rdata = g_new (ConnectionResponseData, 1);
1370 rdata->connection = g_object_ref (connection);
1371 rdata->callback = callback;
1372 rdata->data = user_data;
1373 rdata->destroy = connection_response_data_free;
1375 g_signal_connect (connection, "operation-done", G_CALLBACK (disconnected_cb), rdata);
1377 if (priv->do_something_id != 0) {
1378 g_source_remove (priv->do_something_id);
1381 if (! connection->priv->is_connected) {
1382 priv->state = DAAP_DONE;
1383 rb_daap_connection_finish (connection);
1384 } else {
1385 priv->state = DAAP_LOGOUT;
1387 priv->do_something_id = g_idle_add ((GSourceFunc) rb_daap_connection_do_something, connection);
1391 static void
1392 rb_daap_connection_state_done (RBDAAPConnection *connection,
1393 gboolean result)
1395 RBDAAPConnectionPrivate *priv = connection->priv;
1397 rb_debug ("Transitioning to next state from %d", priv->state);
1399 if (result == FALSE) {
1400 priv->state = DAAP_DONE;
1401 priv->result = FALSE;
1402 } else {
1403 switch (priv->state) {
1404 case DAAP_GET_PLAYLISTS:
1405 if (priv->playlists == NULL)
1406 priv->state = DAAP_DONE;
1407 else
1408 priv->state = DAAP_GET_PLAYLIST_ENTRIES;
1409 break;
1410 case DAAP_GET_PLAYLIST_ENTRIES:
1411 /* keep reading playlists until we've got them all */
1412 if (++priv->reading_playlist >= g_slist_length (priv->playlists))
1413 priv->state = DAAP_DONE;
1414 break;
1416 case DAAP_LOGOUT:
1417 priv->state = DAAP_DONE;
1418 break;
1420 case DAAP_DONE:
1421 /* uhh.. */
1422 rb_debug ("This should never happen.");
1423 break;
1425 default:
1426 /* in most states, we just move on to the next */
1427 if (priv->state > DAAP_DONE) {
1428 rb_debug ("This should REALLY never happen.");
1429 return;
1431 priv->state++;
1432 break;
1435 priv->progress = 1.0f;
1436 if (connection->priv->emit_progress_id != 0) {
1437 g_source_remove (connection->priv->emit_progress_id);
1439 connection->priv->emit_progress_id = g_idle_add ((GSourceFunc) emit_progress_idle, connection);
1442 if (priv->do_something_id != 0) {
1443 g_source_remove (priv->do_something_id);
1445 priv->do_something_id = g_idle_add ((GSourceFunc) rb_daap_connection_do_something, connection);
1448 static gboolean
1449 rb_daap_connection_do_something (RBDAAPConnection *connection)
1451 RBDAAPConnectionPrivate *priv = connection->priv;
1452 char *path;
1454 rb_debug ("Doing something for state: %d", priv->state);
1456 priv->do_something_id = 0;
1458 switch (priv->state) {
1459 case DAAP_GET_INFO:
1460 rb_debug ("Getting DAAP server info");
1461 if (! http_get (connection, "/server-info", FALSE, 0.0, 0, FALSE,
1462 (RBDAAPResponseHandler) handle_server_info, FALSE)) {
1463 rb_debug ("Could not get DAAP connection info");
1464 rb_daap_connection_state_done (connection, FALSE);
1466 break;
1468 case DAAP_GET_PASSWORD:
1469 if (priv->password_protected) {
1470 /* FIXME this bit is still synchronous */
1471 rb_debug ("Need a password for %s", priv->name);
1472 g_free (priv->password);
1473 priv->password = connection_get_password (connection);
1475 if (priv->password == NULL || priv->password[0] == '\0') {
1476 rb_debug ("Password entry cancelled");
1477 priv->result = FALSE;
1478 priv->state = DAAP_DONE;
1479 rb_daap_connection_do_something (connection);
1480 return FALSE;
1483 /* If the share went away while we were asking for the password,
1484 * don't bother trying to log in.
1486 if (priv->state != DAAP_GET_PASSWORD) {
1487 return FALSE;
1491 /* otherwise, fall through */
1492 priv->state = DAAP_LOGIN;
1494 case DAAP_LOGIN:
1495 rb_debug ("Logging into DAAP server");
1496 if (! http_get (connection, "/login", FALSE, 0.0, 0, FALSE,
1497 (RBDAAPResponseHandler) handle_login, FALSE)) {
1498 rb_debug ("Could not login to DAAP server");
1499 /* FIXME: set state back to GET_PASSWORD to try again */
1500 rb_daap_connection_state_done (connection, FALSE);
1503 break;
1505 case DAAP_GET_REVISION_NUMBER:
1506 rb_debug ("Getting DAAP server database revision number");
1507 path = g_strdup_printf ("/update?session-id=%u&revision-number=1", priv->session_id);
1508 if (! http_get (connection, path, TRUE, priv->daap_version, 0, FALSE,
1509 (RBDAAPResponseHandler) handle_update, FALSE)) {
1510 rb_debug ("Could not get server database revision number");
1511 rb_daap_connection_state_done (connection, FALSE);
1513 g_free (path);
1514 break;
1516 case DAAP_GET_DB_INFO:
1517 rb_debug ("Getting DAAP database info");
1518 path = g_strdup_printf ("/databases?session-id=%u&revision-number=%d",
1519 priv->session_id, priv->revision_number);
1520 if (! http_get (connection, path, TRUE, priv->daap_version, 0, FALSE,
1521 (RBDAAPResponseHandler) handle_database_info, FALSE)) {
1522 rb_debug ("Could not get DAAP database info");
1523 rb_daap_connection_state_done (connection, FALSE);
1525 g_free (path);
1526 break;
1528 case DAAP_GET_SONGS:
1529 rb_debug ("Getting DAAP song listing");
1530 path = g_strdup_printf ("/databases/%i/items?session-id=%u&revision-number=%i"
1531 "&meta=dmap.itemid,dmap.itemname,daap.songalbum,"
1532 "daap.songartist,daap.daap.songgenre,daap.songsize,"
1533 "daap.songtime,daap.songtrackcount,daap.songtracknumber,"
1534 "daap.songyear,daap.songformat,daap.songgenre,"
1535 "daap.songbitrate",
1536 priv->database_id,
1537 priv->session_id,
1538 priv->revision_number);
1539 if (! http_get (connection, path, TRUE, priv->daap_version, 0, FALSE,
1540 (RBDAAPResponseHandler) handle_song_listing, TRUE)) {
1541 rb_debug ("Could not get DAAP song listing");
1542 rb_daap_connection_state_done (connection, FALSE);
1544 g_free (path);
1545 break;
1547 case DAAP_GET_PLAYLISTS:
1548 rb_debug ("Getting DAAP playlists");
1549 path = g_strdup_printf ("/databases/%d/containers?session-id=%u&revision-number=%d",
1550 priv->database_id,
1551 priv->session_id,
1552 priv->revision_number);
1553 if (! http_get (connection, path, TRUE, priv->daap_version, 0, FALSE,
1554 (RBDAAPResponseHandler) handle_playlists, TRUE)) {
1555 rb_debug ("Could not get DAAP playlists");
1556 rb_daap_connection_state_done (connection, FALSE);
1558 g_free (path);
1559 break;
1561 case DAAP_GET_PLAYLIST_ENTRIES:
1563 RBDAAPPlaylist *playlist =
1564 (RBDAAPPlaylist *) g_slist_nth_data (priv->playlists,
1565 priv->reading_playlist);
1566 g_assert (playlist);
1567 rb_debug ("Reading DAAP playlist %d entries", priv->reading_playlist);
1568 path = g_strdup_printf ("/databases/%d/containers/%d/items?session-id=%u&revision-number=%d&meta=dmap.itemid",
1569 priv->database_id,
1570 playlist->id,
1571 priv->session_id, priv->revision_number);
1572 if (! http_get (connection, path, TRUE, priv->daap_version, 0, FALSE,
1573 (RBDAAPResponseHandler) handle_playlist_entries, TRUE)) {
1574 rb_debug ("Could not get entries for DAAP playlist %d",
1575 priv->reading_playlist);
1576 rb_daap_connection_state_done (connection, FALSE);
1578 g_free (path);
1580 break;
1582 case DAAP_LOGOUT:
1583 rb_debug ("Logging out of DAAP server");
1584 path = g_strdup_printf ("/logout?session-id=%u", priv->session_id);
1585 if (! http_get (connection, path, TRUE, priv->daap_version, 0, FALSE,
1586 (RBDAAPResponseHandler) handle_logout, FALSE)) {
1587 rb_debug ("Could not log out of DAAP server");
1588 rb_daap_connection_state_done (connection, FALSE);
1591 g_free (path);
1592 break;
1594 case DAAP_DONE:
1595 rb_debug ("DAAP done");
1597 rb_daap_connection_finish (connection);
1599 break;
1602 return FALSE;
1605 char *
1606 rb_daap_connection_get_headers (RBDAAPConnection *connection,
1607 const gchar *uri,
1608 gint64 bytes)
1610 RBDAAPConnectionPrivate *priv = connection->priv;
1611 GString *headers;
1612 char hash[33] = {0};
1613 char *norb_daap_uri = (char *)uri;
1614 char *s;
1616 priv->request_id++;
1618 if (g_strncasecmp (uri, "daap://", 7) == 0) {
1619 norb_daap_uri = strstr (uri, "/data");
1622 rb_daap_hash_generate ((short)floorf (priv->daap_version),
1623 (const guchar*)norb_daap_uri, 2,
1624 (guchar*)hash,
1625 priv->request_id);
1627 headers = g_string_new ("Accept: */*\r\n"
1628 "Cache-Control: no-cache\r\n"
1629 "User-Agent: " RB_DAAP_USER_AGENT "\r\n"
1630 "Accept-Language: en-us, en;q=5.0\r\n"
1631 "Client-DAAP-Access-Index: 2\r\n"
1632 "Client-DAAP-Version: 3.0\r\n");
1633 g_string_append_printf (headers,
1634 "Client-DAAP-Validation: %s\r\n"
1635 "Client-DAAP-Request-ID: %d\r\n"
1636 "Connection: close\r\n",
1637 hash, priv->request_id);
1639 if (priv->password_protected) {
1640 char *user_pass;
1641 char *token;
1643 user_pass = g_strdup_printf ("%s:%s", priv->username, priv->password);
1644 token = soup_base64_encode (user_pass, strlen (user_pass));
1645 g_string_append_printf (headers, "Authentication: Basic %s\r\n", token);
1646 g_free (token);
1647 g_free (user_pass);
1650 if (bytes != 0) {
1651 g_string_append_printf (headers,"Range: bytes=%"G_GINT64_FORMAT"-\r\n", bytes);
1654 s = headers->str;
1655 g_string_free (headers, FALSE);
1657 return s;
1660 GSList *
1661 rb_daap_connection_get_playlists (RBDAAPConnection *connection)
1663 return connection->priv->playlists;
1666 static void
1667 rb_daap_connection_dispose (GObject *object)
1669 RBDAAPConnectionPrivate *priv = RB_DAAP_CONNECTION (object)->priv;
1670 GSList *l;
1672 rb_debug ("DAAP connection dispose");
1674 if (priv->emit_progress_id != 0) {
1675 g_source_remove (priv->emit_progress_id);
1676 priv->emit_progress_id = 0;
1679 if (priv->do_something_id != 0) {
1680 g_source_remove (priv->do_something_id);
1681 priv->do_something_id = 0;
1684 if (priv->name) {
1685 g_free (priv->name);
1686 priv->name = NULL;
1689 if (priv->username) {
1690 g_free (priv->username);
1691 priv->username = NULL;
1694 if (priv->password) {
1695 g_free (priv->password);
1696 priv->password = NULL;
1699 if (priv->host) {
1700 g_free (priv->host);
1701 priv->host = NULL;
1704 if (priv->playlists) {
1705 for (l = priv->playlists; l; l = l->next) {
1706 RBDAAPPlaylist *playlist = l->data;
1708 g_list_foreach (playlist->uris, (GFunc)rb_refstring_unref, NULL);
1709 g_list_free (playlist->uris);
1710 g_free (playlist->name);
1711 g_free (playlist);
1712 l->data = NULL;
1714 g_slist_free (priv->playlists);
1715 priv->playlists = NULL;
1718 if (priv->item_id_to_uri) {
1719 g_hash_table_destroy (priv->item_id_to_uri);
1720 priv->item_id_to_uri = NULL;
1723 if (priv->session) {
1724 rb_debug ("Aborting all pending requests");
1725 soup_session_abort (priv->session);
1726 g_object_unref (G_OBJECT (priv->session));
1727 priv->session = NULL;
1730 if (priv->base_uri) {
1731 soup_uri_free (priv->base_uri);
1732 priv->base_uri = NULL;
1735 if (priv->daap_base_uri) {
1736 g_free (priv->daap_base_uri);
1737 priv->daap_base_uri = NULL;
1740 if (priv->db) {
1741 g_object_unref (G_OBJECT (priv->db));
1742 priv->db = NULL;
1745 if (priv->last_error_message != NULL) {
1746 g_free (priv->last_error_message);
1747 priv->last_error_message = NULL;
1750 G_OBJECT_CLASS (rb_daap_connection_parent_class)->dispose (object);
1753 static void
1754 rb_daap_connection_set_property (GObject *object,
1755 guint prop_id,
1756 const GValue *value,
1757 GParamSpec *pspec)
1759 RBDAAPConnectionPrivate *priv = RB_DAAP_CONNECTION (object)->priv;
1761 switch (prop_id) {
1762 case PROP_NAME:
1763 g_free (priv->name);
1764 priv->name = g_value_dup_string (value);
1765 break;
1766 case PROP_DB:
1767 if (priv->db != NULL) {
1768 g_object_unref (priv->db);
1770 priv->db = RHYTHMDB (g_value_dup_object (value));
1771 break;
1772 case PROP_PASSWORD_PROTECTED:
1773 priv->password_protected = g_value_get_boolean (value);
1774 break;
1775 case PROP_ENTRY_TYPE:
1776 priv->db_type = g_value_get_boxed (value);
1777 break;
1778 case PROP_HOST:
1779 g_free (priv->host);
1780 priv->host = g_value_dup_string (value);
1781 break;
1782 case PROP_PORT:
1783 priv->port = g_value_get_uint (value);
1784 break;
1785 default:
1786 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1787 break;
1791 static void
1792 rb_daap_connection_get_property (GObject *object,
1793 guint prop_id,
1794 GValue *value,
1795 GParamSpec *pspec)
1797 RBDAAPConnectionPrivate *priv = RB_DAAP_CONNECTION (object)->priv;
1799 switch (prop_id) {
1800 case PROP_DB:
1801 g_value_set_object (value, priv->db);
1802 break;
1803 case PROP_NAME:
1804 g_value_set_string (value, priv->name);
1805 break;
1806 case PROP_ENTRY_TYPE:
1807 g_value_set_boxed (value, priv->db_type);
1808 break;
1809 case PROP_PASSWORD_PROTECTED:
1810 g_value_set_boolean (value, priv->password_protected);
1811 break;
1812 case PROP_HOST:
1813 g_value_set_string (value, priv->host);
1814 break;
1815 case PROP_PORT:
1816 g_value_set_uint (value, priv->port);
1817 break;
1818 default:
1819 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1820 break;