Updated Finnish translation
[rhythmbox.git] / daapsharing / rb-daap-connection.c
blobd0fe14406795e29c0af9da671a96e048356ae5ca
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 GDK_THREADS_ENTER ();
290 g_signal_emit (connection,
291 signals [CONNECTED],
293 GDK_THREADS_LEAVE ();
296 static void
297 connection_disconnected (RBDAAPConnection *connection)
299 rb_debug ("Emitting disconnected");
301 connection->priv->is_connected = FALSE;
303 GDK_THREADS_ENTER ();
304 g_signal_emit (connection,
305 signals [DISCONNECTED],
307 GDK_THREADS_LEAVE ();
310 static void
311 connection_operation_done (RBDAAPConnection *connection)
313 rb_debug ("Emitting operation done");
315 GDK_THREADS_ENTER ();
316 g_signal_emit (connection,
317 signals [OPERATION_DONE],
319 GDK_THREADS_LEAVE ();
322 static SoupMessage *
323 build_message (RBDAAPConnection *connection,
324 const char *path,
325 gboolean need_hash,
326 gdouble version,
327 gint req_id,
328 gboolean send_close)
330 RBDAAPConnectionPrivate *priv = connection->priv;
331 SoupMessage *message = NULL;
332 SoupUri *uri = NULL;
334 uri = soup_uri_new_with_base (priv->base_uri, path);
335 if (uri == NULL) {
336 return NULL;
339 message = soup_message_new_from_uri (SOUP_METHOD_GET, uri);
340 soup_message_set_http_version (message, SOUP_HTTP_1_1);
342 soup_message_add_header (message->request_headers, "Client-DAAP-Version", "3.0");
343 soup_message_add_header (message->request_headers, "Accept-Language", "en-us, en;q=5.0");
344 #ifdef HAVE_LIBZ
345 soup_message_add_header (message->request_headers, "Accept-Encoding", "gzip");
346 #endif
347 soup_message_add_header (message->request_headers, "Client-DAAP-Access-Index", "2");
349 if (priv->password_protected) {
350 char *h;
351 char *user_pass;
352 char *token;
354 user_pass = g_strdup_printf ("%s:%s", priv->username, priv->password);
355 token = soup_base64_encode (user_pass, strlen (user_pass));
356 h = g_strdup_printf ("Basic %s", token);
358 g_free (token);
359 g_free (user_pass);
361 soup_message_add_header (message->request_headers, "Authorization", h);
362 g_free (h);
365 if (need_hash) {
366 gchar hash[33] = {0};
367 gchar *no_daap_path = (gchar *)path;
369 if (g_strncasecmp (path, "daap://", 7) == 0) {
370 no_daap_path = strstr (path, "/data");
373 rb_daap_hash_generate ((short)floor (version), (const guchar*)no_daap_path, 2, (guchar*)hash, req_id);
375 soup_message_add_header (message->request_headers, "Client-DAAP-Validation", hash);
377 if (send_close) {
378 soup_message_add_header (message->request_headers, "Connection", "close");
381 soup_uri_free (uri);
383 return message;
386 #ifdef HAVE_LIBZ
387 static void
388 *g_zalloc_wrapper (voidpf opaque, uInt items, uInt size)
390 if ((items != 0) && (size >= G_MAXUINT/items)) {
391 return Z_NULL;
393 if ((size != 0) && (items >= G_MAXUINT/size)) {
394 return Z_NULL;
396 return g_malloc0 (items * size);
399 static void
400 g_zfree_wrapper (voidpf opaque, voidpf address)
402 g_free (address);
404 #endif
406 static void
407 connection_set_error_message (RBDAAPConnection *connection,
408 const char *message)
410 /* FIXME: obtain a lock */
411 if (connection->priv->last_error_message != NULL) {
412 g_free (connection->priv->last_error_message);
414 connection->priv->last_error_message = g_strdup (message);
417 typedef struct {
418 SoupMessage *message;
419 int status;
420 RBDAAPConnection *connection;
421 } DAAPResponseData;
423 static void
424 actual_http_response_handler (DAAPResponseData *data)
426 RBDAAPConnectionPrivate *priv;
427 GNode *structure;
428 char *response;
429 const char *encoding_header;
430 char *message_path;
431 int response_length;
433 priv = data->connection->priv;
434 structure = NULL;
435 response = data->message->response.body;
436 encoding_header = NULL;
437 response_length = data->message->response.length;
439 message_path = soup_uri_to_string (soup_message_get_uri (data->message), FALSE);
441 rb_debug ("Received response from %s: %d, %s\n",
442 message_path,
443 data->message->status_code,
444 data->message->reason_phrase);
446 if (data->message->response_headers) {
447 encoding_header = soup_message_get_header (data->message->response_headers, "Content-Encoding");
450 if (SOUP_STATUS_IS_SUCCESSFUL (data->status) && encoding_header && strcmp (encoding_header, "gzip") == 0) {
451 #ifdef HAVE_LIBZ
452 z_stream stream;
453 char *new_response;
454 unsigned int factor = 4;
455 unsigned int unc_size = response_length * factor;
457 stream.next_in = (unsigned char *)response;
458 stream.avail_in = response_length;
459 stream.total_in = 0;
461 new_response = g_malloc (unc_size + 1);
462 stream.next_out = (unsigned char *)new_response;
463 stream.avail_out = unc_size;
464 stream.total_out = 0;
465 stream.zalloc = g_zalloc_wrapper;
466 stream.zfree = g_zfree_wrapper;
467 stream.opaque = NULL;
469 rb_profile_start ("decompressing DAAP response");
471 if (inflateInit2 (&stream, 32 /* auto-detect */ + 15 /* max */ ) != Z_OK) {
472 inflateEnd (&stream);
473 g_free (new_response);
474 rb_debug ("Unable to decompress response from %s",
475 message_path);
476 data->status = SOUP_STATUS_MALFORMED;
477 rb_profile_end ("decompressing DAAP response (failed)");
478 } else {
479 do {
480 int z_res;
482 rb_profile_start ("attempting inflate");
483 z_res = inflate (&stream, Z_FINISH);
484 if (z_res == Z_STREAM_END) {
485 rb_profile_end ("attempting inflate (done)");
486 break;
488 if ((z_res != Z_OK && z_res != Z_BUF_ERROR) || stream.avail_out != 0 || unc_size > 40*1000*1000) {
489 inflateEnd (&stream);
490 g_free (new_response);
491 new_response = NULL;
492 rb_profile_end ("attempting inflate (error)");
493 break;
496 factor *= 4;
497 unc_size = (response_length * factor);
498 /* unc_size can't grow bigger than 40MB, so
499 * unc_size can't overflow, and this realloc
500 * call is safe
502 new_response = g_realloc (new_response, unc_size + 1);
503 stream.next_out = (unsigned char *)(new_response + stream.total_out);
504 stream.avail_out = unc_size - stream.total_out;
505 rb_profile_end ("attempting inflate (incomplete)");
506 } while (1);
508 rb_profile_end ("decompressing DAAP response (successful)");
510 if (new_response) {
511 response = new_response;
512 response_length = stream.total_out;
514 #else
515 rb_debug ("Received compressed response from %s but can't handle it",
516 message_path);
517 data->status = SOUP_STATUS_MALFORMED;
518 #endif
521 if (SOUP_STATUS_IS_SUCCESSFUL (data->status)) {
522 RBDAAPItem *item;
524 if (!rb_is_main_thread ()) {
525 priv->progress = -1.0f;
526 if (priv->emit_progress_id != 0) {
527 g_source_remove (priv->emit_progress_id);
529 priv->emit_progress_id = g_idle_add ((GSourceFunc) emit_progress_idle, data->connection);
531 rb_profile_start ("parsing DAAP response");
532 structure = rb_daap_structure_parse (response, response_length);
533 if (structure == NULL) {
534 rb_debug ("No daap structure returned from %s",
535 message_path);
537 data->status = SOUP_STATUS_MALFORMED;
538 rb_profile_end ("parsing DAAP response (failed)");
539 } else {
540 int dmap_status = 0;
541 item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MSTT);
542 if (item)
543 dmap_status = g_value_get_int (&(item->content));
545 if (dmap_status != 200) {
546 rb_debug ("Error, dmap.status is not 200 in response from %s",
547 message_path);
549 data->status = SOUP_STATUS_MALFORMED;
551 rb_profile_end ("parsing DAAP response (successful)");
553 if (! rb_is_main_thread ()) {
554 priv->progress = 1.0f;
555 if (priv->emit_progress_id != 0) {
556 g_source_remove (priv->emit_progress_id);
558 priv->emit_progress_id = g_idle_add ((GSourceFunc) emit_progress_idle, data->connection);
560 } else {
561 rb_debug ("Error getting %s: %d, %s\n",
562 message_path,
563 data->message->status_code,
564 data->message->reason_phrase);
565 connection_set_error_message (data->connection, data->message->reason_phrase);
568 if (priv->response_handler) {
569 RBDAAPResponseHandler h = priv->response_handler;
570 priv->response_handler = NULL;
571 (*h) (data->connection, data->status, structure);
574 if (structure) {
575 rb_daap_structure_destroy (structure);
578 if (response != data->message->response.body) {
579 g_free (response);
582 g_free (message_path);
583 g_object_unref (G_OBJECT (data->connection));
584 g_object_unref (G_OBJECT (data->message));
585 g_free (data);
588 static void
589 http_response_handler (SoupMessage *message,
590 RBDAAPConnection *connection)
592 DAAPResponseData *data;
593 int response_length;
595 if (message->status_code == SOUP_STATUS_CANCELLED) {
596 rb_debug ("Message cancelled");
597 return;
600 data = g_new0 (DAAPResponseData, 1);
601 data->status = message->status_code;
602 response_length = message->response.length;
604 g_object_ref (G_OBJECT (connection));
605 data->connection = connection;
607 g_object_ref (G_OBJECT (message));
608 data->message = message;
610 if (response_length >= G_MAXUINT/4 - 1) {
611 /* If response_length is too big,
612 * the g_malloc (unc_size + 1) below would overflow
614 data->status = SOUP_STATUS_MALFORMED;
617 /* to avoid blocking the UI, handle big responses in a separate thread */
618 if (SOUP_STATUS_IS_SUCCESSFUL (data->status) && connection->priv->use_response_handler_thread) {
619 GError *error = NULL;
620 rb_debug ("creating thread to handle daap response");
621 g_thread_create ((GThreadFunc) actual_http_response_handler,
622 data,
623 FALSE,
624 &error);
625 if (error) {
626 g_warning ("fuck");
628 } else {
629 actual_http_response_handler (data);
633 static gboolean
634 http_get (RBDAAPConnection *connection,
635 const char *path,
636 gboolean need_hash,
637 gdouble version,
638 gint req_id,
639 gboolean send_close,
640 RBDAAPResponseHandler handler,
641 gboolean use_thread)
643 RBDAAPConnectionPrivate *priv = connection->priv;
644 SoupMessage *message;
646 message = build_message (connection, path, need_hash, version, req_id, send_close);
647 if (message == NULL) {
648 rb_debug ("Error building message for http://%s:%d/%s",
649 priv->base_uri->host,
650 priv->base_uri->port,
651 path);
652 return FALSE;
655 priv->use_response_handler_thread = use_thread;
656 priv->response_handler = handler;
657 soup_session_queue_message (priv->session, message,
658 (SoupMessageCallbackFn) http_response_handler,
659 connection);
660 rb_debug ("Queued message for http://%s:%d/%s",
661 priv->base_uri->host,
662 priv->base_uri->port,
663 path);
664 return TRUE;
667 static void
668 entry_set_string_prop (RhythmDB *db,
669 RhythmDBEntry *entry,
670 RhythmDBPropType propid,
671 const char *str)
673 GValue value = {0,};
674 const gchar *tmp;
676 if (str == NULL || *str == '\0' || !g_utf8_validate (str, -1, NULL)) {
677 tmp = _("Unknown");
678 } else {
679 tmp = str;
682 g_value_init (&value, G_TYPE_STRING);
683 g_value_set_string (&value, tmp);
684 rhythmdb_entry_set (RHYTHMDB (db), entry, propid, &value);
685 g_value_unset (&value);
688 static gboolean
689 emit_progress_idle (RBDAAPConnection *connection)
691 rb_debug ("Emitting progress");
693 GDK_THREADS_ENTER ();
694 g_signal_emit (G_OBJECT (connection), signals[CONNECTING], 0,
695 connection->priv->state,
696 connection->priv->progress);
697 connection->priv->emit_progress_id = 0;
698 GDK_THREADS_LEAVE ();
699 return FALSE;
702 static void
703 handle_server_info (RBDAAPConnection *connection,
704 guint status,
705 GNode *structure)
707 RBDAAPConnectionPrivate *priv = connection->priv;
708 RBDAAPItem *item = NULL;
710 if (!SOUP_STATUS_IS_SUCCESSFUL (status) || structure == NULL) {
711 rb_daap_connection_state_done (connection, FALSE);
712 return;
715 /* get the daap version number */
716 item = rb_daap_structure_find_item (structure, RB_DAAP_CC_APRO);
717 if (item == NULL) {
718 rb_daap_connection_state_done (connection, FALSE);
719 return;
722 priv->daap_version = g_value_get_double (&(item->content));
723 rb_daap_connection_state_done (connection, TRUE);
726 static void
727 handle_login (RBDAAPConnection *connection,
728 guint status,
729 GNode *structure)
731 RBDAAPConnectionPrivate *priv = connection->priv;
732 RBDAAPItem *item = NULL;
734 if (status == SOUP_STATUS_UNAUTHORIZED || status == SOUP_STATUS_FORBIDDEN) {
735 rb_debug ("Incorrect password");
736 priv->state = DAAP_GET_PASSWORD;
737 if (priv->do_something_id != 0) {
738 g_source_remove (priv->do_something_id);
740 priv->do_something_id = g_idle_add ((GSourceFunc) rb_daap_connection_do_something, connection);
741 return;
744 if (structure == NULL || SOUP_STATUS_IS_SUCCESSFUL (status) == FALSE) {
745 rb_daap_connection_state_done (connection, FALSE);
746 return;
749 item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MLID);
750 if (item == NULL) {
751 rb_debug ("Could not find daap.sessionid item in /login");
752 rb_daap_connection_state_done (connection, FALSE);
753 return;
756 priv->session_id = (guint32) g_value_get_int (&(item->content));
758 connection_connected (connection);
760 rb_daap_connection_state_done (connection, TRUE);
763 static void
764 handle_update (RBDAAPConnection *connection,
765 guint status,
766 GNode *structure)
768 RBDAAPConnectionPrivate *priv = connection->priv;
769 RBDAAPItem *item;
771 if (structure == NULL || SOUP_STATUS_IS_SUCCESSFUL (status) == FALSE) {
772 rb_daap_connection_state_done (connection, FALSE);
773 return;
776 /* get a revision number */
777 item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MUSR);
778 if (item == NULL) {
779 rb_debug ("Could not find daap.serverrevision item in /update");
780 rb_daap_connection_state_done (connection, FALSE);
781 return;
784 priv->revision_number = g_value_get_int (&(item->content));
785 rb_daap_connection_state_done (connection, TRUE);
788 static void
789 handle_database_info (RBDAAPConnection *connection,
790 guint status,
791 GNode *structure)
793 RBDAAPConnectionPrivate *priv = connection->priv;
794 RBDAAPItem *item = NULL;
795 GNode *listing_node;
796 gint n_databases = 0;
798 /* get a list of databases, there should be only 1 */
800 if (structure == NULL || SOUP_STATUS_IS_SUCCESSFUL (status) == FALSE) {
801 rb_daap_connection_state_done (connection, FALSE);
802 return;
805 item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MRCO);
806 if (item == NULL) {
807 rb_debug ("Could not find dmap.returnedcount item in /databases");
808 rb_daap_connection_state_done (connection, FALSE);
809 return;
812 n_databases = g_value_get_int (&(item->content));
813 if (n_databases != 1) {
814 rb_debug ("Host seems to have more than 1 database, how strange\n");
817 listing_node = rb_daap_structure_find_node (structure, RB_DAAP_CC_MLCL);
818 if (listing_node == NULL) {
819 rb_debug ("Could not find dmap.listing item in /databases");
820 rb_daap_connection_state_done (connection, FALSE);
821 return;
824 item = rb_daap_structure_find_item (listing_node->children, RB_DAAP_CC_MIID);
825 if (item == NULL) {
826 rb_debug ("Could not find dmap.itemid item in /databases");
827 rb_daap_connection_state_done (connection, FALSE);
828 return;
831 priv->database_id = g_value_get_int (&(item->content));
832 rb_daap_connection_state_done (connection, TRUE);
835 static void
836 handle_song_listing (RBDAAPConnection *connection,
837 guint status,
838 GNode *structure)
840 RBDAAPConnectionPrivate *priv = connection->priv;
841 RBDAAPItem *item = NULL;
842 GNode *listing_node;
843 gint returned_count;
844 gint i;
845 GNode *n;
846 gint specified_total_count;
847 gboolean update_type;
848 gint commit_batch;
850 /* get the songs */
852 if (structure == NULL || SOUP_STATUS_IS_SUCCESSFUL (status) == FALSE) {
853 rb_daap_connection_state_done (connection, FALSE);
854 return;
857 item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MRCO);
858 if (item == NULL) {
859 rb_debug ("Could not find dmap.returnedcount item in /databases/%d/items",
860 priv->database_id);
861 rb_daap_connection_state_done (connection, FALSE);
862 return;
864 returned_count = g_value_get_int (&(item->content));
865 if (returned_count > 20) {
866 commit_batch = returned_count / 20;
867 } else {
868 commit_batch = 1;
871 item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MTCO);
872 if (item == NULL) {
873 rb_debug ("Could not find dmap.specifiedtotalcount item in /databases/%d/items",
874 priv->database_id);
875 rb_daap_connection_state_done (connection, FALSE);
876 return;
878 specified_total_count = g_value_get_int (&(item->content));
880 item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MUTY);
881 if (item == NULL) {
882 rb_debug ("Could not find dmap.updatetype item in /databases/%d/items",
883 priv->database_id);
884 rb_daap_connection_state_done (connection, FALSE);
885 return;
887 update_type = g_value_get_char (&(item->content));
889 listing_node = rb_daap_structure_find_node (structure, RB_DAAP_CC_MLCL);
890 if (listing_node == NULL) {
891 rb_debug ("Could not find dmap.listing item in /databases/%d/items",
892 priv->database_id);
893 rb_daap_connection_state_done (connection, FALSE);
894 return;
897 priv->item_id_to_uri = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)rb_refstring_unref);
899 rb_profile_start ("handling song listing");
900 priv->progress = 0.0f;
901 if (priv->emit_progress_id != 0) {
902 g_source_remove (priv->emit_progress_id);
904 connection->priv->emit_progress_id = g_idle_add ((GSourceFunc) emit_progress_idle, connection);
906 for (i = 0, n = listing_node->children; n; i++, n = n->next) {
907 GNode *n2;
908 RhythmDBEntry *entry = NULL;
909 GValue value = {0,};
910 gchar *uri = NULL;
911 gint item_id = 0;
912 const gchar *title = NULL;
913 const gchar *album = NULL;
914 const gchar *artist = NULL;
915 const gchar *format = NULL;
916 const gchar *genre = NULL;
917 gint length = 0;
918 gint track_number = 0;
919 gint disc_number = 0;
920 gint year = 0;
921 gint size = 0;
922 gint bitrate = 0;
924 for (n2 = n->children; n2; n2 = n2->next) {
925 RBDAAPItem *meta_item;
927 meta_item = n2->data;
929 switch (meta_item->content_code) {
930 case RB_DAAP_CC_MIID:
931 item_id = g_value_get_int (&(meta_item->content));
932 break;
933 case RB_DAAP_CC_MINM:
934 title = g_value_get_string (&(meta_item->content));
935 break;
936 case RB_DAAP_CC_ASAL:
937 album = g_value_get_string (&(meta_item->content));
938 break;
939 case RB_DAAP_CC_ASAR:
940 artist = g_value_get_string (&(meta_item->content));
941 break;
942 case RB_DAAP_CC_ASFM:
943 format = g_value_get_string (&(meta_item->content));
944 break;
945 case RB_DAAP_CC_ASGN:
946 genre = g_value_get_string (&(meta_item->content));
947 break;
948 case RB_DAAP_CC_ASTM:
949 length = g_value_get_int (&(meta_item->content));
950 break;
951 case RB_DAAP_CC_ASTN:
952 track_number = g_value_get_int (&(meta_item->content));
953 break;
954 case RB_DAAP_CC_ASDN:
955 disc_number = g_value_get_int (&(meta_item->content));
956 break;
957 case RB_DAAP_CC_ASYR:
958 year = g_value_get_int (&(meta_item->content));
959 break;
960 case RB_DAAP_CC_ASSZ:
961 size = g_value_get_int (&(meta_item->content));
962 break;
963 case RB_DAAP_CC_ASBR:
964 bitrate = g_value_get_int (&(meta_item->content));
965 break;
966 default:
967 break;
971 /*if (connection->daap_version == 3.0) {*/
972 uri = g_strdup_printf ("%s/databases/%d/items/%d.%s?session-id=%u",
973 priv->daap_base_uri,
974 priv->database_id,
975 item_id, format,
976 priv->session_id);
977 /*} else {*/
978 /* uri should be
979 * "/databases/%d/items/%d.%s?session-id=%u&revision-id=%d";
980 * but its not going to work cause the other parts of the code
981 * depend on the uri to have the ip address so that the
982 * RBDAAPSource can be found to ++request_id
983 * maybe just /dont/ support older itunes. doesn't seem
984 * unreasonable to me, honestly
986 /*}*/
987 entry = rhythmdb_entry_new (priv->db, priv->db_type, uri);
988 if (entry == NULL) {
989 rb_debug ("cannot create entry for daap track %s", uri);
990 continue;
992 g_hash_table_insert (priv->item_id_to_uri, GINT_TO_POINTER (item_id), rb_refstring_new (uri));
993 g_free (uri);
995 /* year */
996 if (year != 0) {
997 GDate *date;
998 gulong julian;
1000 /* create dummy date with given year */
1001 date = g_date_new_dmy (1, G_DATE_JANUARY, year);
1002 julian = g_date_get_julian (date);
1003 g_date_free (date);
1005 g_value_init (&value, G_TYPE_ULONG);
1006 g_value_set_ulong (&value,julian);
1007 rhythmdb_entry_set (priv->db, entry, RHYTHMDB_PROP_DATE, &value);
1008 g_value_unset (&value);
1011 /* track number */
1012 g_value_init (&value, G_TYPE_ULONG);
1013 g_value_set_ulong (&value,(gulong)track_number);
1014 rhythmdb_entry_set (priv->db, entry, RHYTHMDB_PROP_TRACK_NUMBER, &value);
1015 g_value_unset (&value);
1017 /* disc number */
1018 g_value_init (&value, G_TYPE_ULONG);
1019 g_value_set_ulong (&value,(gulong)disc_number);
1020 rhythmdb_entry_set (priv->db, entry, RHYTHMDB_PROP_DISC_NUMBER, &value);
1021 g_value_unset (&value);
1023 /* bitrate */
1024 g_value_init (&value, G_TYPE_ULONG);
1025 g_value_set_ulong (&value,(gulong)bitrate);
1026 rhythmdb_entry_set (priv->db, entry, RHYTHMDB_PROP_BITRATE, &value);
1027 g_value_unset (&value);
1029 /* length */
1030 g_value_init (&value, G_TYPE_ULONG);
1031 g_value_set_ulong (&value,(gulong)length / 1000);
1032 rhythmdb_entry_set (priv->db, entry, RHYTHMDB_PROP_DURATION, &value);
1033 g_value_unset (&value);
1035 /* file size */
1036 g_value_init (&value, G_TYPE_UINT64);
1037 g_value_set_uint64(&value,(gint64)size);
1038 rhythmdb_entry_set (priv->db, entry, RHYTHMDB_PROP_FILE_SIZE, &value);
1039 g_value_unset (&value);
1041 /* title */
1042 entry_set_string_prop (priv->db, entry, RHYTHMDB_PROP_TITLE, title);
1044 /* album */
1045 entry_set_string_prop (priv->db, entry, RHYTHMDB_PROP_ALBUM, album);
1047 /* artist */
1048 entry_set_string_prop (priv->db, entry, RHYTHMDB_PROP_ARTIST, artist);
1050 /* genre */
1051 entry_set_string_prop (priv->db, entry, RHYTHMDB_PROP_GENRE, genre);
1053 if (i % commit_batch == 0) {
1054 connection->priv->progress = ((float)i / (float)returned_count);
1055 if (priv->emit_progress_id != 0) {
1056 g_source_remove (connection->priv->emit_progress_id);
1058 connection->priv->emit_progress_id = g_idle_add ((GSourceFunc) emit_progress_idle, connection);
1059 rhythmdb_commit (priv->db);
1062 rhythmdb_commit (priv->db);
1063 rb_profile_end ("handling song listing");
1065 rb_daap_connection_state_done (connection, TRUE);
1068 /* FIXME
1069 * what we really should do is only get a list of playlists and their ids
1070 * then when they are clicked on ('activate'd) by the user, get a list of
1071 * the files that are actually in them. This will speed up initial daap
1072 * connection times and reduce memory consumption.
1075 static void
1076 handle_playlists (RBDAAPConnection *connection,
1077 guint status,
1078 GNode *structure)
1080 RBDAAPConnectionPrivate *priv = connection->priv;
1081 GNode *listing_node;
1082 gint i;
1083 GNode *n;
1085 if (structure == NULL || SOUP_STATUS_IS_SUCCESSFUL (status) == FALSE) {
1086 rb_daap_connection_state_done (connection, FALSE);
1087 return;
1090 listing_node = rb_daap_structure_find_node (structure, RB_DAAP_CC_MLCL);
1091 if (listing_node == NULL) {
1092 rb_debug ("Could not find dmap.listing item in /databases/%d/containers",
1093 priv->database_id);
1094 rb_daap_connection_state_done (connection, FALSE);
1095 return;
1098 for (i = 0, n = listing_node->children; n; n = n->next, i++) {
1099 RBDAAPItem *item;
1100 gint id;
1101 gchar *name;
1102 RBDAAPPlaylist *playlist;
1104 item = rb_daap_structure_find_item (n, RB_DAAP_CC_ABPL);
1105 if (item != NULL) {
1106 continue;
1109 item = rb_daap_structure_find_item (n, RB_DAAP_CC_MIID);
1110 if (item == NULL) {
1111 rb_debug ("Could not find dmap.itemid item in /databases/%d/containers",
1112 priv->database_id);
1113 continue;
1115 id = g_value_get_int (&(item->content));
1117 item = rb_daap_structure_find_item (n, RB_DAAP_CC_MINM);
1118 if (item == NULL) {
1119 rb_debug ("Could not find dmap.itemname item in /databases/%d/containers",
1120 priv->database_id);
1121 continue;
1123 name = g_value_dup_string (&(item->content));
1125 playlist = g_new0 (RBDAAPPlaylist, 1);
1126 playlist->id = id;
1127 playlist->name = name;
1128 rb_debug ("Got playlist %p: name %s, id %d", playlist, playlist->name, playlist->id);
1130 priv->playlists = g_slist_prepend (priv->playlists, playlist);
1132 priv->playlists = g_slist_reverse (priv->playlists);
1134 rb_daap_connection_state_done (connection, TRUE);
1137 static void
1138 handle_playlist_entries (RBDAAPConnection *connection,
1139 guint status,
1140 GNode *structure)
1142 RBDAAPConnectionPrivate *priv = connection->priv;
1143 RBDAAPPlaylist *playlist;
1144 GNode *listing_node;
1145 GNode *node;
1146 gint i;
1147 GList *playlist_uris = NULL;
1149 if (structure == NULL || SOUP_STATUS_IS_SUCCESSFUL (status) == FALSE) {
1150 rb_daap_connection_state_done (connection, FALSE);
1151 return;
1154 playlist = (RBDAAPPlaylist *)g_slist_nth_data (priv->playlists, priv->reading_playlist);
1155 g_assert (playlist);
1157 listing_node = rb_daap_structure_find_node (structure, RB_DAAP_CC_MLCL);
1158 if (listing_node == NULL) {
1159 rb_debug ("Could not find dmap.listing item in /databases/%d/containers/%d/items",
1160 priv->database_id, playlist->id);
1161 rb_daap_connection_state_done (connection, FALSE);
1162 return;
1165 rb_profile_start ("handling playlist entries");
1166 for (i = 0, node = listing_node->children; node; node = node->next, i++) {
1167 RBRefString *item_uri;
1168 gint playlist_item_id;
1169 RBDAAPItem *item;
1171 item = rb_daap_structure_find_item (node, RB_DAAP_CC_MIID);
1172 if (item == NULL) {
1173 rb_debug ("Could not find dmap.itemid item in /databases/%d/containers/%d/items",
1174 priv->database_id, playlist->id);
1175 continue;
1177 playlist_item_id = g_value_get_int (&(item->content));
1179 item_uri = g_hash_table_lookup (priv->item_id_to_uri, GINT_TO_POINTER (playlist_item_id));
1180 if (item_uri == NULL) {
1181 rb_debug ("Entry %d in playlist %s doesn't exist in the database\n",
1182 playlist_item_id, playlist->name);
1183 continue;
1186 playlist_uris = g_list_prepend (playlist_uris, rb_refstring_ref (item_uri));
1188 rb_profile_end ("handling playlist entries");
1190 playlist->uris = playlist_uris;
1191 rb_daap_connection_state_done (connection, TRUE);
1194 static void
1195 handle_logout (RBDAAPConnection *connection,
1196 guint status,
1197 GNode *structure)
1199 connection_disconnected (connection);
1201 /* is there any point handling errors here? */
1202 rb_daap_connection_state_done (connection, TRUE);
1205 RBDAAPConnection *
1206 rb_daap_connection_new (const char *name,
1207 const char *host,
1208 int port,
1209 gboolean password_protected,
1210 RhythmDB *db,
1211 RhythmDBEntryType type)
1213 return g_object_new (RB_TYPE_DAAP_CONNECTION,
1214 "name", name,
1215 "entry-type", type,
1216 "password-protected", password_protected,
1217 "db", db,
1218 "host", host,
1219 "port", port,
1220 NULL);
1223 gboolean
1224 rb_daap_connection_is_connected (RBDAAPConnection *connection)
1226 g_return_val_if_fail (RB_IS_DAAP_CONNECTION (connection), FALSE);
1228 return connection->priv->is_connected;
1231 typedef struct {
1232 RBDAAPConnection *connection;
1233 RBDAAPConnectionCallback callback;
1234 gpointer data;
1235 GDestroyNotify destroy;
1236 } ConnectionResponseData;
1238 static void
1239 connection_response_data_free (gpointer data)
1241 ConnectionResponseData *rdata = data;
1243 g_object_unref (rdata->connection);
1244 g_free (rdata);
1247 static void
1248 connected_cb (RBDAAPConnection *connection,
1249 ConnectionResponseData *rdata)
1251 gboolean result;
1253 rb_debug ("Connected callback");
1255 connection->priv->is_connecting = FALSE;
1257 g_signal_handlers_disconnect_by_func (connection,
1258 G_CALLBACK (connected_cb),
1259 rdata);
1261 /* if connected then we succeeded */
1262 result = rb_daap_connection_is_connected (connection);
1264 if (rdata->callback) {
1265 rdata->callback (rdata->connection,
1266 result,
1267 rdata->connection->priv->last_error_message,
1268 rdata->data);
1271 if (rdata->destroy) {
1272 rdata->destroy (rdata);
1276 void
1277 rb_daap_connection_connect (RBDAAPConnection *connection,
1278 RBDAAPConnectionCallback callback,
1279 gpointer user_data)
1281 ConnectionResponseData *rdata;
1282 char *path;
1284 g_return_if_fail (RB_IS_DAAP_CONNECTION (connection));
1285 g_return_if_fail (connection->priv->state == DAAP_GET_INFO);
1287 rb_debug ("Creating new DAAP connection to %s:%d", connection->priv->host, connection->priv->port);
1289 connection->priv->session = soup_session_async_new ();
1291 path = g_strdup_printf ("http://%s:%d", connection->priv->host, connection->priv->port);
1292 connection->priv->base_uri = soup_uri_new (path);
1293 g_free (path);
1295 if (connection->priv->base_uri == NULL) {
1296 rb_debug ("Error parsing http://%s:%d", connection->priv->host, connection->priv->port);
1297 /* FIXME: do callback */
1298 return;
1301 connection->priv->daap_base_uri = g_strdup_printf ("daap://%s:%d", connection->priv->host, connection->priv->port);
1303 rdata = g_new (ConnectionResponseData, 1);
1304 rdata->connection = g_object_ref (connection);
1305 rdata->callback = callback;
1306 rdata->data = user_data;
1307 rdata->destroy = connection_response_data_free;
1308 g_signal_connect (connection, "operation-done", G_CALLBACK (connected_cb), rdata);
1310 if (connection->priv->do_something_id != 0) {
1311 g_source_remove (connection->priv->do_something_id);
1314 connection->priv->is_connecting = TRUE;
1315 connection->priv->do_something_id = g_idle_add ((GSourceFunc) rb_daap_connection_do_something, connection);
1318 static void
1319 disconnected_cb (RBDAAPConnection *connection,
1320 ConnectionResponseData *rdata)
1322 gboolean result;
1324 rb_debug ("Disconnected callback");
1326 g_signal_handlers_disconnect_by_func (connection,
1327 G_CALLBACK (disconnected_cb),
1328 rdata);
1330 /* if not connected then we succeeded */
1331 result = ! rb_daap_connection_is_connected (connection);
1333 if (rdata->callback) {
1334 rdata->callback (rdata->connection,
1335 result,
1336 rdata->connection->priv->last_error_message,
1337 rdata->data);
1340 if (rdata->destroy) {
1341 rdata->destroy (rdata);
1345 static void
1346 rb_daap_connection_finish (RBDAAPConnection *connection)
1348 g_return_if_fail (RB_IS_DAAP_CONNECTION (connection));
1350 rb_debug ("DAAP finish");
1351 connection->priv->state = DAAP_DONE;
1352 connection->priv->progress = 1.0f;
1354 connection_operation_done (connection);
1357 void
1358 rb_daap_connection_disconnect (RBDAAPConnection *connection,
1359 RBDAAPConnectionCallback callback,
1360 gpointer user_data)
1362 RBDAAPConnectionPrivate *priv = connection->priv;
1363 ConnectionResponseData *rdata;
1365 g_return_if_fail (RB_IS_DAAP_CONNECTION (connection));
1367 rb_debug ("Disconnecting");
1369 if (connection->priv->is_connecting) {
1370 /* this is a special case where the async connection
1371 hasn't returned yet so we need to force the connection
1372 to finish */
1373 priv->state = DAAP_DONE;
1374 rb_daap_connection_finish (connection);
1377 rdata = g_new (ConnectionResponseData, 1);
1378 rdata->connection = g_object_ref (connection);
1379 rdata->callback = callback;
1380 rdata->data = user_data;
1381 rdata->destroy = connection_response_data_free;
1383 g_signal_connect (connection, "operation-done", G_CALLBACK (disconnected_cb), rdata);
1385 if (priv->do_something_id != 0) {
1386 g_source_remove (priv->do_something_id);
1389 if (! connection->priv->is_connected) {
1390 priv->state = DAAP_DONE;
1391 rb_daap_connection_finish (connection);
1392 } else {
1393 priv->state = DAAP_LOGOUT;
1395 priv->do_something_id = g_idle_add ((GSourceFunc) rb_daap_connection_do_something, connection);
1399 static void
1400 rb_daap_connection_state_done (RBDAAPConnection *connection,
1401 gboolean result)
1403 RBDAAPConnectionPrivate *priv = connection->priv;
1405 rb_debug ("Transitioning to next state from %d", priv->state);
1407 if (result == FALSE) {
1408 priv->state = DAAP_DONE;
1409 priv->result = FALSE;
1410 } else {
1411 switch (priv->state) {
1412 case DAAP_GET_PLAYLISTS:
1413 if (priv->playlists == NULL)
1414 priv->state = DAAP_DONE;
1415 else
1416 priv->state = DAAP_GET_PLAYLIST_ENTRIES;
1417 break;
1418 case DAAP_GET_PLAYLIST_ENTRIES:
1419 /* keep reading playlists until we've got them all */
1420 if (++priv->reading_playlist >= g_slist_length (priv->playlists))
1421 priv->state = DAAP_DONE;
1422 break;
1424 case DAAP_LOGOUT:
1425 priv->state = DAAP_DONE;
1426 break;
1428 case DAAP_DONE:
1429 /* uhh.. */
1430 rb_debug ("This should never happen.");
1431 break;
1433 default:
1434 /* in most states, we just move on to the next */
1435 if (priv->state > DAAP_DONE) {
1436 rb_debug ("This should REALLY never happen.");
1437 return;
1439 priv->state++;
1440 break;
1443 priv->progress = 1.0f;
1444 if (connection->priv->emit_progress_id != 0) {
1445 g_source_remove (connection->priv->emit_progress_id);
1447 connection->priv->emit_progress_id = g_idle_add ((GSourceFunc) emit_progress_idle, connection);
1450 if (priv->do_something_id != 0) {
1451 g_source_remove (priv->do_something_id);
1453 priv->do_something_id = g_idle_add ((GSourceFunc) rb_daap_connection_do_something, connection);
1456 static gboolean
1457 rb_daap_connection_do_something (RBDAAPConnection *connection)
1459 RBDAAPConnectionPrivate *priv = connection->priv;
1460 char *path;
1462 rb_debug ("Doing something for state: %d", priv->state);
1464 priv->do_something_id = 0;
1466 switch (priv->state) {
1467 case DAAP_GET_INFO:
1468 rb_debug ("Getting DAAP server info");
1469 if (! http_get (connection, "/server-info", FALSE, 0.0, 0, FALSE,
1470 (RBDAAPResponseHandler) handle_server_info, FALSE)) {
1471 rb_debug ("Could not get DAAP connection info");
1472 rb_daap_connection_state_done (connection, FALSE);
1474 break;
1476 case DAAP_GET_PASSWORD:
1477 if (priv->password_protected) {
1478 /* FIXME this bit is still synchronous */
1479 rb_debug ("Need a password for %s", priv->name);
1480 g_free (priv->password);
1481 priv->password = connection_get_password (connection);
1483 if (priv->password == NULL || priv->password[0] == '\0') {
1484 rb_debug ("Password entry cancelled");
1485 priv->result = FALSE;
1486 priv->state = DAAP_DONE;
1487 rb_daap_connection_do_something (connection);
1488 return FALSE;
1491 /* If the share went away while we were asking for the password,
1492 * don't bother trying to log in.
1494 if (priv->state != DAAP_GET_PASSWORD) {
1495 return FALSE;
1499 /* otherwise, fall through */
1500 priv->state = DAAP_LOGIN;
1502 case DAAP_LOGIN:
1503 rb_debug ("Logging into DAAP server");
1504 if (! http_get (connection, "/login", FALSE, 0.0, 0, FALSE,
1505 (RBDAAPResponseHandler) handle_login, FALSE)) {
1506 rb_debug ("Could not login to DAAP server");
1507 /* FIXME: set state back to GET_PASSWORD to try again */
1508 rb_daap_connection_state_done (connection, FALSE);
1511 break;
1513 case DAAP_GET_REVISION_NUMBER:
1514 rb_debug ("Getting DAAP server database revision number");
1515 path = g_strdup_printf ("/update?session-id=%u&revision-number=1", priv->session_id);
1516 if (! http_get (connection, path, TRUE, priv->daap_version, 0, FALSE,
1517 (RBDAAPResponseHandler) handle_update, FALSE)) {
1518 rb_debug ("Could not get server database revision number");
1519 rb_daap_connection_state_done (connection, FALSE);
1521 g_free (path);
1522 break;
1524 case DAAP_GET_DB_INFO:
1525 rb_debug ("Getting DAAP database info");
1526 path = g_strdup_printf ("/databases?session-id=%u&revision-number=%d",
1527 priv->session_id, priv->revision_number);
1528 if (! http_get (connection, path, TRUE, priv->daap_version, 0, FALSE,
1529 (RBDAAPResponseHandler) handle_database_info, FALSE)) {
1530 rb_debug ("Could not get DAAP database info");
1531 rb_daap_connection_state_done (connection, FALSE);
1533 g_free (path);
1534 break;
1536 case DAAP_GET_SONGS:
1537 rb_debug ("Getting DAAP song listing");
1538 path = g_strdup_printf ("/databases/%i/items?session-id=%u&revision-number=%i"
1539 "&meta=dmap.itemid,dmap.itemname,daap.songalbum,"
1540 "daap.songartist,daap.daap.songgenre,daap.songsize,"
1541 "daap.songtime,daap.songtrackcount,daap.songtracknumber,"
1542 "daap.songyear,daap.songformat,daap.songgenre,"
1543 "daap.songbitrate",
1544 priv->database_id,
1545 priv->session_id,
1546 priv->revision_number);
1547 if (! http_get (connection, path, TRUE, priv->daap_version, 0, FALSE,
1548 (RBDAAPResponseHandler) handle_song_listing, TRUE)) {
1549 rb_debug ("Could not get DAAP song listing");
1550 rb_daap_connection_state_done (connection, FALSE);
1552 g_free (path);
1553 break;
1555 case DAAP_GET_PLAYLISTS:
1556 rb_debug ("Getting DAAP playlists");
1557 path = g_strdup_printf ("/databases/%d/containers?session-id=%u&revision-number=%d",
1558 priv->database_id,
1559 priv->session_id,
1560 priv->revision_number);
1561 if (! http_get (connection, path, TRUE, priv->daap_version, 0, FALSE,
1562 (RBDAAPResponseHandler) handle_playlists, TRUE)) {
1563 rb_debug ("Could not get DAAP playlists");
1564 rb_daap_connection_state_done (connection, FALSE);
1566 g_free (path);
1567 break;
1569 case DAAP_GET_PLAYLIST_ENTRIES:
1571 RBDAAPPlaylist *playlist =
1572 (RBDAAPPlaylist *) g_slist_nth_data (priv->playlists,
1573 priv->reading_playlist);
1574 g_assert (playlist);
1575 rb_debug ("Reading DAAP playlist %d entries", priv->reading_playlist);
1576 path = g_strdup_printf ("/databases/%d/containers/%d/items?session-id=%u&revision-number=%d&meta=dmap.itemid",
1577 priv->database_id,
1578 playlist->id,
1579 priv->session_id, priv->revision_number);
1580 if (! http_get (connection, path, TRUE, priv->daap_version, 0, FALSE,
1581 (RBDAAPResponseHandler) handle_playlist_entries, TRUE)) {
1582 rb_debug ("Could not get entries for DAAP playlist %d",
1583 priv->reading_playlist);
1584 rb_daap_connection_state_done (connection, FALSE);
1586 g_free (path);
1588 break;
1590 case DAAP_LOGOUT:
1591 rb_debug ("Logging out of DAAP server");
1592 path = g_strdup_printf ("/logout?session-id=%u", priv->session_id);
1593 if (! http_get (connection, path, TRUE, priv->daap_version, 0, FALSE,
1594 (RBDAAPResponseHandler) handle_logout, FALSE)) {
1595 rb_debug ("Could not log out of DAAP server");
1596 rb_daap_connection_state_done (connection, FALSE);
1599 g_free (path);
1600 break;
1602 case DAAP_DONE:
1603 rb_debug ("DAAP done");
1605 rb_daap_connection_finish (connection);
1607 break;
1610 return FALSE;
1613 char *
1614 rb_daap_connection_get_headers (RBDAAPConnection *connection,
1615 const gchar *uri,
1616 gint64 bytes)
1618 RBDAAPConnectionPrivate *priv = connection->priv;
1619 GString *headers;
1620 char hash[33] = {0};
1621 char *norb_daap_uri = (char *)uri;
1622 char *s;
1624 priv->request_id++;
1626 if (g_strncasecmp (uri, "daap://", 7) == 0) {
1627 norb_daap_uri = strstr (uri, "/data");
1630 rb_daap_hash_generate ((short)floorf (priv->daap_version),
1631 (const guchar*)norb_daap_uri, 2,
1632 (guchar*)hash,
1633 priv->request_id);
1635 headers = g_string_new ("Accept: */*\r\n"
1636 "Cache-Control: no-cache\r\n"
1637 "User-Agent: " RB_DAAP_USER_AGENT "\r\n"
1638 "Accept-Language: en-us, en;q=5.0\r\n"
1639 "Client-DAAP-Access-Index: 2\r\n"
1640 "Client-DAAP-Version: 3.0\r\n");
1641 g_string_append_printf (headers,
1642 "Client-DAAP-Validation: %s\r\n"
1643 "Client-DAAP-Request-ID: %d\r\n"
1644 "Connection: close\r\n",
1645 hash, priv->request_id);
1647 if (priv->password_protected) {
1648 char *user_pass;
1649 char *token;
1651 user_pass = g_strdup_printf ("%s:%s", priv->username, priv->password);
1652 token = soup_base64_encode (user_pass, strlen (user_pass));
1653 g_string_append_printf (headers, "Authentication: Basic %s\r\n", token);
1654 g_free (token);
1655 g_free (user_pass);
1658 if (bytes != 0) {
1659 g_string_append_printf (headers,"Range: bytes=%"G_GINT64_FORMAT"-\r\n", bytes);
1662 s = headers->str;
1663 g_string_free (headers, FALSE);
1665 return s;
1668 GSList *
1669 rb_daap_connection_get_playlists (RBDAAPConnection *connection)
1671 return connection->priv->playlists;
1674 static void
1675 rb_daap_connection_dispose (GObject *object)
1677 RBDAAPConnectionPrivate *priv = RB_DAAP_CONNECTION (object)->priv;
1678 GSList *l;
1680 rb_debug ("DAAP connection dispose");
1682 if (priv->emit_progress_id != 0) {
1683 g_source_remove (priv->emit_progress_id);
1684 priv->emit_progress_id = 0;
1687 if (priv->do_something_id != 0) {
1688 g_source_remove (priv->do_something_id);
1689 priv->do_something_id = 0;
1692 if (priv->name) {
1693 g_free (priv->name);
1694 priv->name = NULL;
1697 if (priv->username) {
1698 g_free (priv->username);
1699 priv->username = NULL;
1702 if (priv->password) {
1703 g_free (priv->password);
1704 priv->password = NULL;
1707 if (priv->host) {
1708 g_free (priv->host);
1709 priv->host = NULL;
1712 if (priv->playlists) {
1713 for (l = priv->playlists; l; l = l->next) {
1714 RBDAAPPlaylist *playlist = l->data;
1716 g_list_foreach (playlist->uris, (GFunc)rb_refstring_unref, NULL);
1717 g_list_free (playlist->uris);
1718 g_free (playlist->name);
1719 g_free (playlist);
1720 l->data = NULL;
1722 g_slist_free (priv->playlists);
1723 priv->playlists = NULL;
1726 if (priv->item_id_to_uri) {
1727 g_hash_table_destroy (priv->item_id_to_uri);
1728 priv->item_id_to_uri = NULL;
1731 if (priv->session) {
1732 rb_debug ("Aborting all pending requests");
1733 soup_session_abort (priv->session);
1734 g_object_unref (G_OBJECT (priv->session));
1735 priv->session = NULL;
1738 if (priv->base_uri) {
1739 soup_uri_free (priv->base_uri);
1740 priv->base_uri = NULL;
1743 if (priv->daap_base_uri) {
1744 g_free (priv->daap_base_uri);
1745 priv->daap_base_uri = NULL;
1748 if (priv->db) {
1749 g_object_unref (G_OBJECT (priv->db));
1750 priv->db = NULL;
1753 if (priv->last_error_message != NULL) {
1754 g_free (priv->last_error_message);
1755 priv->last_error_message = NULL;
1758 G_OBJECT_CLASS (rb_daap_connection_parent_class)->dispose (object);
1761 static void
1762 rb_daap_connection_set_property (GObject *object,
1763 guint prop_id,
1764 const GValue *value,
1765 GParamSpec *pspec)
1767 RBDAAPConnectionPrivate *priv = RB_DAAP_CONNECTION (object)->priv;
1769 switch (prop_id) {
1770 case PROP_NAME:
1771 g_free (priv->name);
1772 priv->name = g_value_dup_string (value);
1773 break;
1774 case PROP_DB:
1775 if (priv->db != NULL) {
1776 g_object_unref (priv->db);
1778 priv->db = RHYTHMDB (g_value_dup_object (value));
1779 break;
1780 case PROP_PASSWORD_PROTECTED:
1781 priv->password_protected = g_value_get_boolean (value);
1782 break;
1783 case PROP_ENTRY_TYPE:
1784 priv->db_type = g_value_get_boxed (value);
1785 break;
1786 case PROP_HOST:
1787 g_free (priv->host);
1788 priv->host = g_value_dup_string (value);
1789 break;
1790 case PROP_PORT:
1791 priv->port = g_value_get_uint (value);
1792 break;
1793 default:
1794 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1795 break;
1799 static void
1800 rb_daap_connection_get_property (GObject *object,
1801 guint prop_id,
1802 GValue *value,
1803 GParamSpec *pspec)
1805 RBDAAPConnectionPrivate *priv = RB_DAAP_CONNECTION (object)->priv;
1807 switch (prop_id) {
1808 case PROP_DB:
1809 g_value_set_object (value, priv->db);
1810 break;
1811 case PROP_NAME:
1812 g_value_set_string (value, priv->name);
1813 break;
1814 case PROP_ENTRY_TYPE:
1815 g_value_set_boxed (value, priv->db_type);
1816 break;
1817 case PROP_PASSWORD_PROTECTED:
1818 g_value_set_boolean (value, priv->password_protected);
1819 break;
1820 case PROP_HOST:
1821 g_value_set_string (value, priv->host);
1822 break;
1823 case PROP_PORT:
1824 g_value_set_uint (value, priv->port);
1825 break;
1826 default:
1827 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1828 break;