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.
27
#include <sys/types.h>
33
#include <glib/gi18n.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"
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,
56
static void rb_daap_connection_get_property (GObject *object,
61
static gboolean rb_daap_connection_do_something (RBDAAPConnection *connection);
62
static void rb_daap_connection_state_done (RBDAAPConnection *connection,
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,
75
struct RBDAAPConnectionPrivate {
77
gboolean password_protected;
83
gboolean is_connected;
84
gboolean is_connecting;
97
guint reading_playlist;
99
GHashTable *item_id_to_uri;
102
RhythmDBEntryType db_type;
104
RBDAAPConnectionState state;
105
RBDAAPResponseHandler response_handler;
106
gboolean use_response_handler_thread;
109
guint emit_progress_id;
110
guint do_something_id;
113
char *last_error_message;
121
PROP_PASSWORD_PROTECTED,
135
static guint signals [LAST_SIGNAL] = { 0, };
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);
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,
168
g_param_spec_object ("db",
172
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
173
g_object_class_install_property (object_class,
175
g_param_spec_boxed ("entry-type",
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",
187
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
188
g_object_class_install_property (object_class,
190
g_param_spec_string ("name",
194
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
195
g_object_class_install_property (object_class,
197
g_param_spec_string ("host",
201
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
202
g_object_class_install_property (object_class,
204
g_param_spec_uint ("port",
208
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
210
signals [AUTHENTICATE] = g_signal_new ("authenticate",
211
G_TYPE_FROM_CLASS (object_class),
213
G_STRUCT_OFFSET (RBDAAPConnectionClass, authenticate),
216
rb_marshal_STRING__STRING,
219
signals [CONNECTING] = g_signal_new ("connecting",
220
G_TYPE_FROM_CLASS (object_class),
222
G_STRUCT_OFFSET (RBDAAPConnectionClass, connecting),
225
rb_marshal_VOID__ULONG_FLOAT,
227
2, G_TYPE_ULONG, G_TYPE_FLOAT);
228
signals [CONNECTED] = g_signal_new ("connected",
229
G_TYPE_FROM_CLASS (object_class),
231
G_STRUCT_OFFSET (RBDAAPConnectionClass, connected),
234
g_cclosure_marshal_VOID__VOID,
237
signals [DISCONNECTED] = g_signal_new ("disconnected",
238
G_TYPE_FROM_CLASS (object_class),
240
G_STRUCT_OFFSET (RBDAAPConnectionClass, disconnected),
243
g_cclosure_marshal_VOID__VOID,
246
signals [OPERATION_DONE] = g_signal_new ("operation-done",
247
G_TYPE_FROM_CLASS (object_class),
249
G_STRUCT_OFFSET (RBDAAPConnectionClass, operation_done),
252
g_cclosure_marshal_VOID__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;
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,
277
GDK_THREADS_LEAVE ();
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,
293
GDK_THREADS_LEAVE ();
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 ();
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 ();
323
build_message (RBDAAPConnection *connection,
330
RBDAAPConnectionPrivate *priv = connection->priv;
331
SoupMessage *message = NULL;
334
uri = soup_uri_new_with_base (priv->base_uri, path);
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");
345
soup_message_add_header (message->request_headers, "Accept-Encoding", "gzip");
347
soup_message_add_header (message->request_headers, "Client-DAAP-Access-Index", "2");
349
if (priv->password_protected) {
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);
361
soup_message_add_header (message->request_headers, "Authorization", h);
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);
378
soup_message_add_header (message->request_headers, "Connection", "close");
388
*g_zalloc_wrapper (voidpf opaque, uInt items, uInt size)
390
if ((items != 0) && (size >= G_MAXUINT/items)) {
393
if ((size != 0) && (items >= G_MAXUINT/size)) {
396
return g_malloc0 (items * size);
400
g_zfree_wrapper (voidpf opaque, voidpf address)
407
connection_set_error_message (RBDAAPConnection *connection,
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);
418
SoupMessage *message;
420
RBDAAPConnection *connection;
424
actual_http_response_handler (DAAPResponseData *data)
426
RBDAAPConnectionPrivate *priv;
429
const char *encoding_header;
433
priv = data->connection->priv;
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",
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) {
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;
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",
476
data->status = SOUP_STATUS_MALFORMED;
477
rb_profile_end ("decompressing DAAP response (failed)");
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)");
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);
492
rb_profile_end ("attempting inflate (error)");
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
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)");
508
rb_profile_end ("decompressing DAAP response (successful)");
511
response = new_response;
512
response_length = stream.total_out;
515
rb_debug ("Received compressed response from %s but can't handle it",
517
data->status = SOUP_STATUS_MALFORMED;
521
if (SOUP_STATUS_IS_SUCCESSFUL (data->status)) {
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",
537
data->status = SOUP_STATUS_MALFORMED;
538
rb_profile_end ("parsing DAAP response (failed)");
541
item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MSTT);
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",
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);
561
rb_debug ("Error getting %s: %d, %s\n",
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);
575
rb_daap_structure_destroy (structure);
578
if (response != data->message->response.body) {
582
g_free (message_path);
583
g_object_unref (G_OBJECT (data->connection));
584
g_object_unref (G_OBJECT (data->message));
589
http_response_handler (SoupMessage *message,
590
RBDAAPConnection *connection)
592
DAAPResponseData *data;
595
if (message->status_code == SOUP_STATUS_CANCELLED) {
596
rb_debug ("Message cancelled");
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,
629
actual_http_response_handler (data);
634
http_get (RBDAAPConnection *connection,
640
RBDAAPResponseHandler handler,
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,
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,
660
rb_debug ("Queued message for http://%s:%d/%s",
661
priv->base_uri->host,
662
priv->base_uri->port,
668
entry_set_string_prop (RhythmDB *db,
669
RhythmDBEntry *entry,
670
RhythmDBPropType propid,
676
if (str == NULL || *str == '\0' || !g_utf8_validate (str, -1, NULL)) {
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);
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 ();
703
handle_server_info (RBDAAPConnection *connection,
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);
715
/* get the daap version number */
716
item = rb_daap_structure_find_item (structure, RB_DAAP_CC_APRO);
718
rb_daap_connection_state_done (connection, FALSE);
722
priv->daap_version = g_value_get_double (&(item->content));
723
rb_daap_connection_state_done (connection, TRUE);
727
handle_login (RBDAAPConnection *connection,
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);
744
if (structure == NULL || SOUP_STATUS_IS_SUCCESSFUL (status) == FALSE) {
745
rb_daap_connection_state_done (connection, FALSE);
749
item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MLID);
751
rb_debug ("Could not find daap.sessionid item in /login");
752
rb_daap_connection_state_done (connection, FALSE);
756
priv->session_id = (guint32) g_value_get_int (&(item->content));
758
connection_connected (connection);
760
rb_daap_connection_state_done (connection, TRUE);
764
handle_update (RBDAAPConnection *connection,
768
RBDAAPConnectionPrivate *priv = connection->priv;
771
if (structure == NULL || SOUP_STATUS_IS_SUCCESSFUL (status) == FALSE) {
772
rb_daap_connection_state_done (connection, FALSE);
776
/* get a revision number */
777
item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MUSR);
779
rb_debug ("Could not find daap.serverrevision item in /update");
780
rb_daap_connection_state_done (connection, FALSE);
784
priv->revision_number = g_value_get_int (&(item->content));
785
rb_daap_connection_state_done (connection, TRUE);
789
handle_database_info (RBDAAPConnection *connection,
793
RBDAAPConnectionPrivate *priv = connection->priv;
794
RBDAAPItem *item = NULL;
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);
805
item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MRCO);
807
rb_debug ("Could not find dmap.returnedcount item in /databases");
808
rb_daap_connection_state_done (connection, FALSE);
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);
824
item = rb_daap_structure_find_item (listing_node->children, RB_DAAP_CC_MIID);
826
rb_debug ("Could not find dmap.itemid item in /databases");
827
rb_daap_connection_state_done (connection, FALSE);
831
priv->database_id = g_value_get_int (&(item->content));
832
rb_daap_connection_state_done (connection, TRUE);
836
handle_song_listing (RBDAAPConnection *connection,
840
RBDAAPConnectionPrivate *priv = connection->priv;
841
RBDAAPItem *item = NULL;
846
gint specified_total_count;
847
gboolean update_type;
852
if (structure == NULL || SOUP_STATUS_IS_SUCCESSFUL (status) == FALSE) {
853
rb_daap_connection_state_done (connection, FALSE);
857
item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MRCO);
859
rb_debug ("Could not find dmap.returnedcount item in /databases/%d/items",
861
rb_daap_connection_state_done (connection, FALSE);
864
returned_count = g_value_get_int (&(item->content));
865
if (returned_count > 20) {
866
commit_batch = returned_count / 20;
871
item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MTCO);
873
rb_debug ("Could not find dmap.specifiedtotalcount item in /databases/%d/items",
875
rb_daap_connection_state_done (connection, FALSE);
878
specified_total_count = g_value_get_int (&(item->content));
880
item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MUTY);
882
rb_debug ("Could not find dmap.updatetype item in /databases/%d/items",
884
rb_daap_connection_state_done (connection, FALSE);
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",
893
rb_daap_connection_state_done (connection, FALSE);
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) {
908
RhythmDBEntry *entry = NULL;
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;
918
gint track_number = 0;
919
gint disc_number = 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));
933
case RB_DAAP_CC_MINM:
934
title = g_value_get_string (&(meta_item->content));
936
case RB_DAAP_CC_ASAL:
937
album = g_value_get_string (&(meta_item->content));
939
case RB_DAAP_CC_ASAR:
940
artist = g_value_get_string (&(meta_item->content));
942
case RB_DAAP_CC_ASFM:
943
format = g_value_get_string (&(meta_item->content));
945
case RB_DAAP_CC_ASGN:
946
genre = g_value_get_string (&(meta_item->content));
948
case RB_DAAP_CC_ASTM:
949
length = g_value_get_int (&(meta_item->content));
951
case RB_DAAP_CC_ASTN:
952
track_number = g_value_get_int (&(meta_item->content));
954
case RB_DAAP_CC_ASDN:
955
disc_number = g_value_get_int (&(meta_item->content));
957
case RB_DAAP_CC_ASYR:
958
year = g_value_get_int (&(meta_item->content));
960
case RB_DAAP_CC_ASSZ:
961
size = g_value_get_int (&(meta_item->content));
963
case RB_DAAP_CC_ASBR:
964
bitrate = g_value_get_int (&(meta_item->content));
971
/*if (connection->daap_version == 3.0) {*/
972
uri = g_strdup_printf ("%s/databases/%d/items/%d.%s?session-id=%u",
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
987
entry = rhythmdb_entry_new (priv->db, priv->db_type, uri);
989
rb_debug ("cannot create entry for daap track %s", uri);
992
g_hash_table_insert (priv->item_id_to_uri, GINT_TO_POINTER (item_id), rb_refstring_new (uri));
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);
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);
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);
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);
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);
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);
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);
1042
entry_set_string_prop (priv->db, entry, RHYTHMDB_PROP_TITLE, title);
1045
entry_set_string_prop (priv->db, entry, RHYTHMDB_PROP_ALBUM, album);
1048
entry_set_string_prop (priv->db, entry, RHYTHMDB_PROP_ARTIST, artist);
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);
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.
1076
handle_playlists (RBDAAPConnection *connection,
1080
RBDAAPConnectionPrivate *priv = connection->priv;
1081
GNode *listing_node;
1085
if (structure == NULL || SOUP_STATUS_IS_SUCCESSFUL (status) == FALSE) {
1086
rb_daap_connection_state_done (connection, FALSE);
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",
1094
rb_daap_connection_state_done (connection, FALSE);
1098
for (i = 0, n = listing_node->children; n; n = n->next, i++) {
1102
RBDAAPPlaylist *playlist;
1104
item = rb_daap_structure_find_item (n, RB_DAAP_CC_ABPL);
1109
item = rb_daap_structure_find_item (n, RB_DAAP_CC_MIID);
1111
rb_debug ("Could not find dmap.itemid item in /databases/%d/containers",
1115
id = g_value_get_int (&(item->content));
1117
item = rb_daap_structure_find_item (n, RB_DAAP_CC_MINM);
1119
rb_debug ("Could not find dmap.itemname item in /databases/%d/containers",
1123
name = g_value_dup_string (&(item->content));
1125
playlist = g_new0 (RBDAAPPlaylist, 1);
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);
1138
handle_playlist_entries (RBDAAPConnection *connection,
1142
RBDAAPConnectionPrivate *priv = connection->priv;
1143
RBDAAPPlaylist *playlist;
1144
GNode *listing_node;
1147
GList *playlist_uris = NULL;
1149
if (structure == NULL || SOUP_STATUS_IS_SUCCESSFUL (status) == FALSE) {
1150
rb_daap_connection_state_done (connection, FALSE);
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);
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;
1171
item = rb_daap_structure_find_item (node, RB_DAAP_CC_MIID);
1173
rb_debug ("Could not find dmap.itemid item in /databases/%d/containers/%d/items",
1174
priv->database_id, playlist->id);
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);
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);
1195
handle_logout (RBDAAPConnection *connection,
1199
connection_disconnected (connection);
1201
/* is there any point handling errors here? */
1202
rb_daap_connection_state_done (connection, TRUE);
1206
rb_daap_connection_new (const char *name,
1209
gboolean password_protected,
1211
RhythmDBEntryType type)
1213
return g_object_new (RB_TYPE_DAAP_CONNECTION,
1216
"password-protected", password_protected,
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;
1232
RBDAAPConnection *connection;
1233
RBDAAPConnectionCallback callback;
1235
GDestroyNotify destroy;
1236
} ConnectionResponseData;
1239
connection_response_data_free (gpointer data)
1241
ConnectionResponseData *rdata = data;
1243
g_object_unref (rdata->connection);
1248
connected_cb (RBDAAPConnection *connection,
1249
ConnectionResponseData *rdata)
1253
rb_debug ("Connected callback");
1255
connection->priv->is_connecting = FALSE;
1257
g_signal_handlers_disconnect_by_func (connection,
1258
G_CALLBACK (connected_cb),
1261
/* if connected then we succeeded */
1262
result = rb_daap_connection_is_connected (connection);
1264
if (rdata->callback) {
1265
rdata->callback (rdata->connection,
1267
rdata->connection->priv->last_error_message,
1271
if (rdata->destroy) {
1272
rdata->destroy (rdata);
1277
rb_daap_connection_connect (RBDAAPConnection *connection,
1278
RBDAAPConnectionCallback callback,
1281
ConnectionResponseData *rdata;
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);
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 */
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);
1319
disconnected_cb (RBDAAPConnection *connection,
1320
ConnectionResponseData *rdata)
1324
rb_debug ("Disconnected callback");
1326
g_signal_handlers_disconnect_by_func (connection,
1327
G_CALLBACK (disconnected_cb),
1330
/* if not connected then we succeeded */
1331
result = ! rb_daap_connection_is_connected (connection);
1333
if (rdata->callback) {
1334
rdata->callback (rdata->connection,
1336
rdata->connection->priv->last_error_message,
1340
if (rdata->destroy) {
1341
rdata->destroy (rdata);
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);
1358
rb_daap_connection_disconnect (RBDAAPConnection *connection,
1359
RBDAAPConnectionCallback callback,
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
1373
priv->state = DAAP_DONE;
1374
GDK_THREADS_LEAVE ();
1375
rb_daap_connection_finish (connection);
1376
GDK_THREADS_ENTER ();
1379
rdata = g_new (ConnectionResponseData, 1);
1380
rdata->connection = g_object_ref (connection);
1381
rdata->callback = callback;
1382
rdata->data = user_data;
1383
rdata->destroy = connection_response_data_free;
1385
g_signal_connect (connection, "operation-done", G_CALLBACK (disconnected_cb), rdata);
1387
if (priv->do_something_id != 0) {
1388
g_source_remove (priv->do_something_id);
1391
if (! connection->priv->is_connected) {
1392
priv->state = DAAP_DONE;
1393
GDK_THREADS_LEAVE ();
1394
rb_daap_connection_finish (connection);
1395
GDK_THREADS_ENTER ();
1397
priv->state = DAAP_LOGOUT;
1399
priv->do_something_id = g_idle_add ((GSourceFunc) rb_daap_connection_do_something, connection);
1404
rb_daap_connection_state_done (RBDAAPConnection *connection,
1407
RBDAAPConnectionPrivate *priv = connection->priv;
1409
rb_debug ("Transitioning to next state from %d", priv->state);
1411
if (result == FALSE) {
1412
priv->state = DAAP_DONE;
1413
priv->result = FALSE;
1415
switch (priv->state) {
1416
case DAAP_GET_PLAYLISTS:
1417
if (priv->playlists == NULL)
1418
priv->state = DAAP_DONE;
1420
priv->state = DAAP_GET_PLAYLIST_ENTRIES;
1422
case DAAP_GET_PLAYLIST_ENTRIES:
1423
/* keep reading playlists until we've got them all */
1424
if (++priv->reading_playlist >= g_slist_length (priv->playlists))
1425
priv->state = DAAP_DONE;
1429
priv->state = DAAP_DONE;
1434
rb_debug ("This should never happen.");
1438
/* in most states, we just move on to the next */
1439
if (priv->state > DAAP_DONE) {
1440
rb_debug ("This should REALLY never happen.");
1447
priv->progress = 1.0f;
1448
if (connection->priv->emit_progress_id != 0) {
1449
g_source_remove (connection->priv->emit_progress_id);
1451
connection->priv->emit_progress_id = g_idle_add ((GSourceFunc) emit_progress_idle, connection);
1454
if (priv->do_something_id != 0) {
1455
g_source_remove (priv->do_something_id);
1457
priv->do_something_id = g_idle_add ((GSourceFunc) rb_daap_connection_do_something, connection);
1461
rb_daap_connection_do_something (RBDAAPConnection *connection)
1463
RBDAAPConnectionPrivate *priv = connection->priv;
1466
rb_debug ("Doing something for state: %d", priv->state);
1468
priv->do_something_id = 0;
1470
switch (priv->state) {
1472
rb_debug ("Getting DAAP server info");
1473
if (! http_get (connection, "/server-info", FALSE, 0.0, 0, FALSE,
1474
(RBDAAPResponseHandler) handle_server_info, FALSE)) {
1475
rb_debug ("Could not get DAAP connection info");
1476
rb_daap_connection_state_done (connection, FALSE);
1480
case DAAP_GET_PASSWORD:
1481
if (priv->password_protected) {
1482
/* FIXME this bit is still synchronous */
1483
rb_debug ("Need a password for %s", priv->name);
1484
g_free (priv->password);
1485
priv->password = connection_get_password (connection);
1487
if (priv->password == NULL || priv->password[0] == '\0') {
1488
rb_debug ("Password entry cancelled");
1489
priv->result = FALSE;
1490
priv->state = DAAP_DONE;
1491
rb_daap_connection_do_something (connection);
1495
/* If the share went away while we were asking for the password,
1496
* don't bother trying to log in.
1498
if (priv->state != DAAP_GET_PASSWORD) {
1503
/* otherwise, fall through */
1504
priv->state = DAAP_LOGIN;
1507
rb_debug ("Logging into DAAP server");
1508
if (! http_get (connection, "/login", FALSE, 0.0, 0, FALSE,
1509
(RBDAAPResponseHandler) handle_login, FALSE)) {
1510
rb_debug ("Could not login to DAAP server");
1511
/* FIXME: set state back to GET_PASSWORD to try again */
1512
rb_daap_connection_state_done (connection, FALSE);
1517
case DAAP_GET_REVISION_NUMBER:
1518
rb_debug ("Getting DAAP server database revision number");
1519
path = g_strdup_printf ("/update?session-id=%u&revision-number=1", priv->session_id);
1520
if (! http_get (connection, path, TRUE, priv->daap_version, 0, FALSE,
1521
(RBDAAPResponseHandler) handle_update, FALSE)) {
1522
rb_debug ("Could not get server database revision number");
1523
rb_daap_connection_state_done (connection, FALSE);
1528
case DAAP_GET_DB_INFO:
1529
rb_debug ("Getting DAAP database info");
1530
path = g_strdup_printf ("/databases?session-id=%u&revision-number=%d",
1531
priv->session_id, priv->revision_number);
1532
if (! http_get (connection, path, TRUE, priv->daap_version, 0, FALSE,
1533
(RBDAAPResponseHandler) handle_database_info, FALSE)) {
1534
rb_debug ("Could not get DAAP database info");
1535
rb_daap_connection_state_done (connection, FALSE);
1540
case DAAP_GET_SONGS:
1541
rb_debug ("Getting DAAP song listing");
1542
path = g_strdup_printf ("/databases/%i/items?session-id=%u&revision-number=%i"
1543
"&meta=dmap.itemid,dmap.itemname,daap.songalbum,"
1544
"daap.songartist,daap.daap.songgenre,daap.songsize,"
1545
"daap.songtime,daap.songtrackcount,daap.songtracknumber,"
1546
"daap.songyear,daap.songformat,daap.songgenre,"
1547
"daap.songbitrate,daap.songdiscnumber",
1550
priv->revision_number);
1551
if (! http_get (connection, path, TRUE, priv->daap_version, 0, FALSE,
1552
(RBDAAPResponseHandler) handle_song_listing, TRUE)) {
1553
rb_debug ("Could not get DAAP song listing");
1554
rb_daap_connection_state_done (connection, FALSE);
1559
case DAAP_GET_PLAYLISTS:
1560
rb_debug ("Getting DAAP playlists");
1561
path = g_strdup_printf ("/databases/%d/containers?session-id=%u&revision-number=%d",
1564
priv->revision_number);
1565
if (! http_get (connection, path, TRUE, priv->daap_version, 0, FALSE,
1566
(RBDAAPResponseHandler) handle_playlists, TRUE)) {
1567
rb_debug ("Could not get DAAP playlists");
1568
rb_daap_connection_state_done (connection, FALSE);
1573
case DAAP_GET_PLAYLIST_ENTRIES:
1575
RBDAAPPlaylist *playlist =
1576
(RBDAAPPlaylist *) g_slist_nth_data (priv->playlists,
1577
priv->reading_playlist);
1578
g_assert (playlist);
1579
rb_debug ("Reading DAAP playlist %d entries", priv->reading_playlist);
1580
path = g_strdup_printf ("/databases/%d/containers/%d/items?session-id=%u&revision-number=%d&meta=dmap.itemid",
1583
priv->session_id, priv->revision_number);
1584
if (! http_get (connection, path, TRUE, priv->daap_version, 0, FALSE,
1585
(RBDAAPResponseHandler) handle_playlist_entries, TRUE)) {
1586
rb_debug ("Could not get entries for DAAP playlist %d",
1587
priv->reading_playlist);
1588
rb_daap_connection_state_done (connection, FALSE);
1595
rb_debug ("Logging out of DAAP server");
1596
path = g_strdup_printf ("/logout?session-id=%u", priv->session_id);
1597
if (! http_get (connection, path, TRUE, priv->daap_version, 0, FALSE,
1598
(RBDAAPResponseHandler) handle_logout, FALSE)) {
1599
rb_debug ("Could not log out of DAAP server");
1600
rb_daap_connection_state_done (connection, FALSE);
1607
rb_debug ("DAAP done");
1609
rb_daap_connection_finish (connection);
1618
rb_daap_connection_get_headers (RBDAAPConnection *connection,
1622
RBDAAPConnectionPrivate *priv = connection->priv;
1624
char hash[33] = {0};
1625
char *norb_daap_uri = (char *)uri;
1630
if (g_strncasecmp (uri, "daap://", 7) == 0) {
1631
norb_daap_uri = strstr (uri, "/data");
1634
rb_daap_hash_generate ((short)floorf (priv->daap_version),
1635
(const guchar*)norb_daap_uri, 2,
1639
headers = g_string_new ("Accept: */*\r\n"
1640
"Cache-Control: no-cache\r\n"
1641
"User-Agent: " RB_DAAP_USER_AGENT "\r\n"
1642
"Accept-Language: en-us, en;q=5.0\r\n"
1643
"Client-DAAP-Access-Index: 2\r\n"
1644
"Client-DAAP-Version: 3.0\r\n");
1645
g_string_append_printf (headers,
1646
"Client-DAAP-Validation: %s\r\n"
1647
"Client-DAAP-Request-ID: %d\r\n"
1648
"Connection: close\r\n",
1649
hash, priv->request_id);
1651
if (priv->password_protected) {
1655
user_pass = g_strdup_printf ("%s:%s", priv->username, priv->password);
1656
token = soup_base64_encode (user_pass, strlen (user_pass));
1657
g_string_append_printf (headers, "Authentication: Basic %s\r\n", token);
1663
g_string_append_printf (headers,"Range: bytes=%"G_GINT64_FORMAT"-\r\n", bytes);
1667
g_string_free (headers, FALSE);
1673
rb_daap_connection_get_playlists (RBDAAPConnection *connection)
1675
return connection->priv->playlists;
1679
rb_daap_connection_dispose (GObject *object)
1681
RBDAAPConnectionPrivate *priv = RB_DAAP_CONNECTION (object)->priv;
1684
rb_debug ("DAAP connection dispose");
1686
if (priv->emit_progress_id != 0) {
1687
g_source_remove (priv->emit_progress_id);
1688
priv->emit_progress_id = 0;
1691
if (priv->do_something_id != 0) {
1692
g_source_remove (priv->do_something_id);
1693
priv->do_something_id = 0;
1697
g_free (priv->name);
1701
if (priv->username) {
1702
g_free (priv->username);
1703
priv->username = NULL;
1706
if (priv->password) {
1707
g_free (priv->password);
1708
priv->password = NULL;
1712
g_free (priv->host);
1716
if (priv->playlists) {
1717
for (l = priv->playlists; l; l = l->next) {
1718
RBDAAPPlaylist *playlist = l->data;
1720
g_list_foreach (playlist->uris, (GFunc)rb_refstring_unref, NULL);
1721
g_list_free (playlist->uris);
1722
g_free (playlist->name);
1726
g_slist_free (priv->playlists);
1727
priv->playlists = NULL;
1730
if (priv->item_id_to_uri) {
1731
g_hash_table_destroy (priv->item_id_to_uri);
1732
priv->item_id_to_uri = NULL;
1735
if (priv->session) {
1736
rb_debug ("Aborting all pending requests");
1737
soup_session_abort (priv->session);
1738
g_object_unref (G_OBJECT (priv->session));
1739
priv->session = NULL;
1742
if (priv->base_uri) {
1743
soup_uri_free (priv->base_uri);
1744
priv->base_uri = NULL;
1747
if (priv->daap_base_uri) {
1748
g_free (priv->daap_base_uri);
1749
priv->daap_base_uri = NULL;
1753
g_object_unref (G_OBJECT (priv->db));
1757
if (priv->last_error_message != NULL) {
1758
g_free (priv->last_error_message);
1759
priv->last_error_message = NULL;
1762
G_OBJECT_CLASS (rb_daap_connection_parent_class)->dispose (object);
1766
rb_daap_connection_set_property (GObject *object,
1768
const GValue *value,
1771
RBDAAPConnectionPrivate *priv = RB_DAAP_CONNECTION (object)->priv;
1775
g_free (priv->name);
1776
priv->name = g_value_dup_string (value);
1779
if (priv->db != NULL) {
1780
g_object_unref (priv->db);
1782
priv->db = RHYTHMDB (g_value_dup_object (value));
1784
case PROP_PASSWORD_PROTECTED:
1785
priv->password_protected = g_value_get_boolean (value);
1787
case PROP_ENTRY_TYPE:
1788
priv->db_type = g_value_get_boxed (value);
1791
g_free (priv->host);
1792
priv->host = g_value_dup_string (value);
1795
priv->port = g_value_get_uint (value);
1798
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1804
rb_daap_connection_get_property (GObject *object,
1809
RBDAAPConnectionPrivate *priv = RB_DAAP_CONNECTION (object)->priv;
1813
g_value_set_object (value, priv->db);
1816
g_value_set_string (value, priv->name);
1818
case PROP_ENTRY_TYPE:
1819
g_value_set_boxed (value, priv->db_type);
1821
case PROP_PASSWORD_PROTECTED:
1822
g_value_set_boolean (value, priv->password_protected);
1825
g_value_set_string (value, priv->host);
1828
g_value_set_uint (value, priv->port);
1831
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);