1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * Implmentation of DAAP (iTunes Music Sharing) sharing
5 * Copyright (C) 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.
28 #include <glib/gi18n.h>
30 #include <libsoup/soup.h>
31 #include <libsoup/soup-address.h>
32 #include <libsoup/soup-message.h>
33 #include <libsoup/soup-uri.h>
34 #include <libsoup/soup-server.h>
35 #include <libsoup/soup-server-auth.h>
36 #include <libsoup/soup-server-message.h>
37 #include <libgnomevfs/gnome-vfs.h>
39 #include "rb-daap-share.h"
40 #include "rb-daap-structure.h"
41 #include "rb-daap-mdns-publisher.h"
42 #include "rb-daap-dialog.h"
44 #include "rb-playlist-source.h"
46 #include "eel-gconf-extensions.h"
47 #include "rb-file-helpers.h"
49 static void rb_daap_share_set_property (GObject
*object
,
53 static void rb_daap_share_get_property (GObject
*object
,
57 static void rb_daap_share_dispose (GObject
*object
);
58 static void rb_daap_share_maybe_restart (RBDAAPShare
*share
);
59 static gboolean
rb_daap_share_publish_start (RBDAAPShare
*share
);
60 static gboolean
rb_daap_share_publish_stop (RBDAAPShare
*share
);
61 static gboolean
rb_daap_share_server_start (RBDAAPShare
*share
);
62 static gboolean
rb_daap_share_server_stop (RBDAAPShare
*share
);
63 static void rb_daap_share_playlist_created (RBPlaylistManager
*mgr
,
66 static void rb_daap_share_process_playlist (RBSource
*playlist
,
68 static void rb_daap_share_playlist_destroyed (RBDAAPShare
*share
,
70 static void rb_daap_share_forget_playlist (gpointer data
,
73 #define STANDARD_DAAP_PORT 3689
75 /* HTTP chunk size used to send files to clients */
76 #define DAAP_SHARE_CHUNK_SIZE 16384
79 RB_DAAP_SHARE_AUTH_METHOD_NONE
= 0,
80 RB_DAAP_SHARE_AUTH_METHOD_NAME_AND_PASSWORD
= 1,
81 RB_DAAP_SHARE_AUTH_METHOD_PASSWORD
= 2
82 } RBDAAPShareAuthMethod
;
84 struct RBDAAPSharePrivate
{
88 RBDAAPShareAuthMethod auth_method
;
90 /* mdns/dns-sd publishing things */
91 gboolean server_active
;
93 RBDaapMdnsPublisher
*publisher
;
95 /* http server things */
97 guint revision_number
;
99 GHashTable
*session_ids
;
103 RhythmDBEntryType entry_type
;
104 gulong entry_added_id
;
105 gulong entry_deleted_id
;
106 gulong entry_changed_id
;
108 /* playlist things */
109 RBPlaylistManager
*playlist_manager
;
110 guint next_playlist_id
;
111 GList
*playlist_ids
; /* contains RBPlaylistIDs */
114 #define RB_DAAP_SHARE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_DAAP_SHARE, RBDAAPSharePrivate))
126 PROP_PLAYLIST_MANAGER
,
130 G_DEFINE_TYPE (RBDAAPShare
, rb_daap_share
, G_TYPE_OBJECT
)
133 rb_daap_share_class_init (RBDAAPShareClass
*klass
)
135 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
137 object_class
->get_property
= rb_daap_share_get_property
;
138 object_class
->set_property
= rb_daap_share_set_property
;
139 object_class
->dispose
= rb_daap_share_dispose
;
141 g_object_class_install_property (object_class
,
143 g_param_spec_string ("name",
148 g_object_class_install_property (object_class
,
150 g_param_spec_string ("password",
151 "Authentication password",
152 "Authentication password",
155 g_object_class_install_property (object_class
,
157 g_param_spec_object ("db",
161 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
162 g_object_class_install_property (object_class
,
163 PROP_PLAYLIST_MANAGER
,
164 g_param_spec_object ("playlist-manager",
166 "Playlist manager object",
167 RB_TYPE_PLAYLIST_MANAGER
,
168 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
169 g_object_class_install_property (object_class
,
171 g_param_spec_boxed ("entry-type",
173 "Type of entries to be shared",
174 RHYTHMDB_TYPE_ENTRY_TYPE
,
175 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
177 g_type_class_add_private (klass
, sizeof (RBDAAPSharePrivate
));
181 rb_daap_share_set_name (RBDAAPShare
*share
,
187 g_return_if_fail (share
!= NULL
);
189 g_free (share
->priv
->name
);
190 share
->priv
->name
= g_strdup (name
);
193 res
= rb_daap_mdns_publisher_set_name (share
->priv
->publisher
, name
, &error
);
195 g_warning ("Unable to change MDNS service name: %s", error
->message
);
196 g_error_free (error
);
201 published_cb (RBDaapMdnsPublisher
*publisher
,
205 if (share
->priv
->name
== NULL
|| name
== NULL
) {
209 if (strcmp (name
, share
->priv
->name
) == 0) {
210 rb_debug ("mDNS publish successful");
211 share
->priv
->published
= TRUE
;
216 name_collision_cb (RBDaapMdnsPublisher
*publisher
,
222 if (share
->priv
->name
== NULL
|| name
== NULL
) {
226 if (strcmp (name
, share
->priv
->name
) == 0) {
227 rb_debug ("Duplicate share name on mDNS");
229 GDK_THREADS_ENTER ();
230 new_name
= rb_daap_collision_dialog_new_run (NULL
, share
->priv
->name
);
231 GDK_THREADS_LEAVE ();
233 rb_daap_share_set_name (share
, new_name
);
241 rb_daap_share_init (RBDAAPShare
*share
)
243 share
->priv
= RB_DAAP_SHARE_GET_PRIVATE (share
);
245 share
->priv
->revision_number
= 5;
247 share
->priv
->auth_method
= RB_DAAP_SHARE_AUTH_METHOD_NONE
;
248 share
->priv
->publisher
= rb_daap_mdns_publisher_new ();
249 g_signal_connect_object (share
->priv
->publisher
,
251 G_CALLBACK (published_cb
),
253 g_signal_connect_object (share
->priv
->publisher
,
255 G_CALLBACK (name_collision_cb
),
261 rb_daap_share_set_password (RBDAAPShare
*share
,
262 const char *password
)
264 g_return_if_fail (share
!= NULL
);
266 if (share
->priv
->password
&& password
&&
267 strcmp (password
, share
->priv
->password
) == 0) {
271 g_free (share
->priv
->password
);
272 share
->priv
->password
= g_strdup (password
);
273 if (password
!= NULL
) {
274 share
->priv
->auth_method
= RB_DAAP_SHARE_AUTH_METHOD_PASSWORD
;
276 share
->priv
->auth_method
= RB_DAAP_SHARE_AUTH_METHOD_NONE
;
279 rb_daap_share_maybe_restart (share
);
283 rb_daap_share_set_db (RBDAAPShare
*share
,
286 if (share
->priv
->db
!= NULL
) {
287 g_object_unref (share
->priv
->db
);
290 share
->priv
->db
= db
;
292 if (share
->priv
->db
!= NULL
) {
293 g_object_ref (share
->priv
->db
);
298 rb_daap_share_set_playlist_manager (RBDAAPShare
*share
,
299 RBPlaylistManager
*playlist_manager
)
303 g_return_if_fail (share
!= NULL
);
305 if (share
->priv
->playlist_manager
!= NULL
) {
306 g_object_unref (share
->priv
->playlist_manager
);
307 g_signal_handlers_disconnect_by_func (share
->priv
->playlist_manager
,
308 G_CALLBACK (rb_daap_share_playlist_created
),
312 share
->priv
->playlist_manager
= playlist_manager
;
314 if (share
->priv
->playlist_manager
!= NULL
) {
315 g_object_ref (share
->priv
->playlist_manager
);
317 g_signal_connect_object (G_OBJECT (share
->priv
->playlist_manager
),
319 G_CALLBACK (rb_daap_share_playlist_created
),
322 /* Currently, there are no playlists when this object is created, but in
325 playlists
= rb_playlist_manager_get_playlists (share
->priv
->playlist_manager
);
326 g_list_foreach (playlists
, (GFunc
) rb_daap_share_process_playlist
, share
);
327 g_list_free (playlists
);
332 rb_daap_share_set_property (GObject
*object
,
337 RBDAAPShare
*share
= RB_DAAP_SHARE (object
);
341 rb_daap_share_set_name (share
, g_value_get_string (value
));
344 rb_daap_share_set_password (share
, g_value_get_string (value
));
347 rb_daap_share_set_db (share
, g_value_get_object (value
));
349 case PROP_PLAYLIST_MANAGER
:
350 rb_daap_share_set_playlist_manager (share
, g_value_get_object (value
));
352 case PROP_ENTRY_TYPE
:
353 share
->priv
->entry_type
= g_value_get_boxed (value
);
356 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
362 rb_daap_share_get_property (GObject
*object
,
367 RBDAAPShare
*share
= RB_DAAP_SHARE (object
);
371 g_value_set_string (value
, share
->priv
->name
);
374 g_value_set_string (value
, share
->priv
->password
);
377 g_value_set_object (value
, share
->priv
->db
);
379 case PROP_PLAYLIST_MANAGER
:
380 g_value_set_object (value
, share
->priv
->playlist_manager
);
382 case PROP_ENTRY_TYPE
:
383 g_value_set_boxed (value
, share
->priv
->entry_type
);
386 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
392 _find_by_id (gconstpointer a
, gconstpointer b
)
394 RBPlaylistID
*ai
= (RBPlaylistID
*)a
;
395 gint bv
= GPOINTER_TO_INT (b
);
396 return (ai
->id
- bv
);
400 _find_by_source (gconstpointer a
, gconstpointer b
)
402 RBPlaylistID
*ai
= (RBPlaylistID
*)a
;
403 RBSource
*bs
= (RBSource
*)b
;
404 return (ai
->source
- bs
);
408 rb_daap_share_playlist_created (RBPlaylistManager
*manager
,
412 rb_daap_share_process_playlist (source
, share
);
416 rb_daap_share_process_playlist (RBSource
*source
,
421 /* make sure we're not going insane.. */
422 g_assert (g_list_find_custom (share
->priv
->playlist_ids
,
424 _find_by_source
) == NULL
);
426 g_object_weak_ref (G_OBJECT (source
),
427 (GWeakNotify
) rb_daap_share_playlist_destroyed
,
429 id
= g_new0 (RBPlaylistID
, 1);
431 id
->id
= share
->priv
->next_playlist_id
++;
432 share
->priv
->playlist_ids
= g_list_append (share
->priv
->playlist_ids
, id
);
434 /* if we knew how to send updates to clients, we'd probably do something here */
438 rb_daap_share_playlist_destroyed (RBDAAPShare
*share
,
443 id
= g_list_find_custom (share
->priv
->playlist_ids
, source
, _find_by_source
);
447 share
->priv
->playlist_ids
= g_list_remove_link (share
->priv
->playlist_ids
, id
);
453 rb_daap_share_forget_playlist (gpointer data
,
456 RBPlaylistID
*id
= (RBPlaylistID
*)data
;
457 g_object_weak_unref (G_OBJECT (id
->source
),
458 (GWeakNotify
) rb_daap_share_playlist_destroyed
,
463 rb_daap_share_dispose (GObject
*object
)
465 RBDAAPShare
*share
= RB_DAAP_SHARE (object
);
467 if (share
->priv
->published
) {
468 rb_daap_share_publish_stop (share
);
471 if (share
->priv
->server_active
) {
472 rb_daap_share_server_stop (share
);
475 g_free (share
->priv
->name
);
476 g_object_unref (share
->priv
->db
);
477 g_object_unref (share
->priv
->playlist_manager
);
479 g_list_foreach (share
->priv
->playlist_ids
, (GFunc
) rb_daap_share_forget_playlist
, share
);
480 g_list_foreach (share
->priv
->playlist_ids
, (GFunc
) g_free
, NULL
);
482 if (share
->priv
->publisher
) {
483 g_object_unref (share
->priv
->publisher
);
486 G_OBJECT_CLASS (rb_daap_share_parent_class
)->dispose (object
);
490 rb_daap_share_new (const char *name
,
491 const char *password
,
493 RhythmDBEntryType entry_type
,
494 RBPlaylistManager
*playlist_manager
)
498 share
= RB_DAAP_SHARE (g_object_new (RB_TYPE_DAAP_SHARE
,
500 "password", password
,
502 "playlist-manager", playlist_manager
,
503 "entry-type", entry_type
,
506 rb_daap_share_server_start (share
);
507 rb_daap_share_publish_start (share
);
513 message_add_standard_headers (SoupMessage
*message
)
519 soup_message_add_header (message
->response_headers
, "DAAP-Server", "Rhythmbox " VERSION
);
521 soup_message_add_header (message
->response_headers
, "Content-Type", "application/x-dmap-tagged");
525 s
= g_new (gchar
, 100);
526 strftime (s
, 100, "%a, %d %b %Y %T GMT", tm
);
527 soup_message_add_header (message
->response_headers
, "Date", s
);
532 message_set_from_rb_daap_structure (SoupMessage
*message
,
538 resp
= rb_daap_structure_serialize (structure
, &length
);
541 rb_debug ("serialize gave us null?\n");
545 message
->response
.owner
= SOUP_BUFFER_SYSTEM_OWNED
;
546 message
->response
.length
= length
;
547 message
->response
.body
= resp
;
549 message_add_standard_headers (message
);
551 soup_message_set_status (message
, SOUP_STATUS_OK
);
552 soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (message
), SOUP_TRANSFER_CONTENT_LENGTH
);
555 #define DMAP_STATUS_OK 200
557 #define DMAP_VERSION 2.0
558 #define DAAP_VERSION 3.0
559 #define DMAP_TIMEOUT 1800
562 server_info_cb (RBDAAPShare
*share
,
563 SoupServerContext
*context
,
564 SoupMessage
*message
)
566 /* MSRV server info response
571 * MSAU authentication method
572 * MSLR login required
573 * MSTM timeout interval
574 * MSAL supports auto logout
575 * MSUP supports update
576 * MSPI supports persistent ids
577 * MSEX supports extensions
578 * MSBR supports browse
579 * MSQY supports query
580 * MSIX supports index
581 * MSRS supports resolve
582 * MSDC databases count
586 msrv
= rb_daap_structure_add (NULL
, RB_DAAP_CC_MSRV
);
587 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSTT
, (gint32
) DMAP_STATUS_OK
);
588 rb_daap_structure_add (msrv
, RB_DAAP_CC_MPRO
, (gdouble
) DMAP_VERSION
);
589 rb_daap_structure_add (msrv
, RB_DAAP_CC_APRO
, (gdouble
) DAAP_VERSION
);
590 /* 2/3 is for itunes 4.8 (at least). its determined by the
591 * Client-DAAP-Version header sent, but if we decide not to support
592 * older versions..? anyway
598 rb_daap_structure_add (msrv
, RB_DAAP_CC_MINM
, share
->priv
->name
);
599 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSAU
, share
->priv
->auth_method
);
600 /* authentication method
602 * 1 is name & password
605 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSLR
, 0);
606 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSTM
, (gint32
) DMAP_TIMEOUT
);
607 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSAL
, (gchar
) 0);
608 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSUP
, (gchar
) 0);
609 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSPI
, (gchar
) 0);
610 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSEX
, (gchar
) 0);
611 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSBR
, (gchar
) 0);
612 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSQY
, (gchar
) 0);
613 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSIX
, (gchar
) 0);
614 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSRS
, (gchar
) 0);
615 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSDC
, (gint32
) 1);
617 message_set_from_rb_daap_structure (message
, msrv
);
618 rb_daap_structure_destroy (msrv
);
622 content_codes_cb (RBDAAPShare
*share
,
623 SoupServerContext
*context
,
624 SoupMessage
*message
)
626 /* MCCR content codes response
629 * MCNM content codes number
630 * MCNA content codes name
631 * MCTY content codes type
635 const RBDAAPContentCodeDefinition
*defs
;
640 defs
= rb_daap_content_codes (&num_defs
);
642 mccr
= rb_daap_structure_add (NULL
, RB_DAAP_CC_MCCR
);
643 rb_daap_structure_add (mccr
, RB_DAAP_CC_MSTT
, (gint32
) DMAP_STATUS_OK
);
645 for (i
= 0; i
< num_defs
; i
++) {
648 mdcl
= rb_daap_structure_add (mccr
, RB_DAAP_CC_MDCL
);
649 rb_daap_structure_add (mdcl
, RB_DAAP_CC_MCNM
, rb_daap_content_code_string_as_int32(defs
[i
].string
));
650 rb_daap_structure_add (mdcl
, RB_DAAP_CC_MCNA
, defs
[i
].name
);
651 rb_daap_structure_add (mdcl
, RB_DAAP_CC_MCTY
, (gint32
) defs
[i
].type
);
654 message_set_from_rb_daap_structure (message
, mccr
);
655 rb_daap_structure_destroy (mccr
);
659 message_get_session_id (SoupMessage
*message
,
670 uri
= soup_message_get_uri (message
);
675 position
= strstr (uri
->query
, "session-id=");
677 if (position
== NULL
) {
678 rb_debug ("session id not found");
683 session_id
= (guint32
) strtoul (position
, NULL
, 10);
693 message_get_revision_number (SoupMessage
*message
,
698 guint revision_number
;
704 uri
= soup_message_get_uri (message
);
709 position
= strstr (uri
->query
, "revision-number=");
711 if (position
== NULL
) {
712 rb_debug ("client asked for an update without a revision number?!?\n");
717 revision_number
= atoi (position
);
720 *number
= revision_number
;
727 session_id_validate (RBDAAPShare
*share
,
728 SoupServerContext
*context
,
729 SoupMessage
*message
,
735 const char *remote_address
;
741 res
= message_get_session_id (message
, &session_id
);
743 rb_debug ("Validation failed: Unable to parse session id from message");
747 /* check hash for remote address */
748 addr
= g_hash_table_lookup (share
->priv
->session_ids
, GUINT_TO_POINTER (session_id
));
750 rb_debug ("Validation failed: Unable to lookup session id %u", session_id
);
754 remote_address
= soup_server_context_get_client_host (context
);
755 rb_debug ("Validating session id %u from %s matches %s",
756 session_id
, remote_address
, addr
);
757 if (remote_address
== NULL
|| strcmp (addr
, remote_address
) != 0) {
758 rb_debug ("Validation failed: Remote address does not match stored address");
770 session_id_generate (RBDAAPShare
*share
,
771 SoupServerContext
*context
)
775 id
= g_random_int ();
781 session_id_create (RBDAAPShare
*share
,
782 SoupServerContext
*context
)
786 char *remote_address
;
789 /* create a unique session id */
790 id
= session_id_generate (share
, context
);
791 rb_debug ("Generated session id %u", id
);
793 /* if already used, try again */
794 addr
= g_hash_table_lookup (share
->priv
->session_ids
, GUINT_TO_POINTER (id
));
795 } while (addr
!= NULL
);
797 /* store session id and remote address */
798 remote_address
= g_strdup (soup_server_context_get_client_host (context
));
799 g_hash_table_insert (share
->priv
->session_ids
, GUINT_TO_POINTER (id
), remote_address
);
805 session_id_remove (RBDAAPShare
*share
,
806 SoupServerContext
*context
,
809 g_hash_table_remove (share
->priv
->session_ids
, GUINT_TO_POINTER (id
));
813 login_cb (RBDAAPShare
*share
,
814 SoupServerContext
*context
,
815 SoupMessage
*message
)
817 /* MLOG login response
824 session_id
= session_id_create (share
, context
);
826 rb_debug ("Handling login session id %u", session_id
);
828 mlog
= rb_daap_structure_add (NULL
, RB_DAAP_CC_MLOG
);
829 rb_daap_structure_add (mlog
, RB_DAAP_CC_MSTT
, (gint32
) DMAP_STATUS_OK
);
830 rb_daap_structure_add (mlog
, RB_DAAP_CC_MLID
, session_id
);
832 message_set_from_rb_daap_structure (message
, mlog
);
833 rb_daap_structure_destroy (mlog
);
837 logout_cb (RBDAAPShare
*share
,
838 SoupServerContext
*context
,
839 SoupMessage
*message
)
844 if (session_id_validate (share
, context
, message
, &id
)) {
845 rb_debug ("Handling logout session id %u", id
);
846 session_id_remove (share
, context
, id
);
848 status
= SOUP_STATUS_NO_CONTENT
;
850 status
= SOUP_STATUS_FORBIDDEN
;
853 soup_message_set_status (message
, status
);
854 soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (message
), SOUP_TRANSFER_CONTENT_LENGTH
);
858 update_cb (RBDAAPShare
*share
,
859 SoupServerContext
*context
,
860 SoupMessage
*message
)
862 guint revision_number
;
865 res
= message_get_revision_number (message
, &revision_number
);
867 if (res
&& revision_number
!= share
->priv
->revision_number
) {
868 /* MUPD update response
870 * MUSR server revision
874 mupd
= rb_daap_structure_add (NULL
, RB_DAAP_CC_MUPD
);
875 rb_daap_structure_add (mupd
, RB_DAAP_CC_MSTT
, (gint32
) DMAP_STATUS_OK
);
876 rb_daap_structure_add (mupd
, RB_DAAP_CC_MUSR
, (gint32
) share
->priv
->revision_number
);
878 message_set_from_rb_daap_structure (message
, mupd
);
879 rb_daap_structure_destroy (mupd
);
881 g_object_ref (message
);
882 soup_message_io_pause (message
);
911 SONG_RELATIVE_VOLUME
,
923 struct DAAPMetaDataMap
{
928 struct DAAPMetaDataMap meta_data_map
[] = {
929 {"dmap.itemid", ITEM_ID
},
930 {"dmap.itemname", ITEM_NAME
},
931 {"dmap.itemkind", ITEM_KIND
},
932 {"dmap.persistentid", PERSISTENT_ID
},
933 {"dmap.containeritemid", CONTAINER_ITEM_ID
},
934 {"daap.songalbum", SONG_ALBUM
},
935 {"daap.songartist", SONG_ARTIST
},
936 {"daap.songbitrate", SONG_BITRATE
},
937 {"daap.songbeatsperminute", SONG_BPM
},
938 {"daap.songcomment", SONG_COMMENT
},
939 {"daap.songcompilation", SONG_COMPILATION
},
940 {"daap.songcomposer", SONG_COMPOSER
},
941 {"daap.songdatakind", SONG_DATA_KIND
},
942 {"daap.songdataurl", SONG_DATA_URL
},
943 {"daap.songdateadded", SONG_DATE_ADDED
},
944 {"daap.songdatemodified", SONG_DATE_MODIFIED
},
945 {"daap.songdescription", SONG_DESCRIPTION
},
946 {"daap.songdisabled", SONG_DISABLED
},
947 {"daap.songdisccount", SONG_DISC_COUNT
},
948 {"daap.songdiscnumber", SONG_DISC_NUMBER
},
949 {"daap.songeqpreset", SONG_EQ_PRESET
},
950 {"daap.songformat", SONG_FORMAT
},
951 {"daap.songgenre", SONG_GENRE
},
952 {"daap.songgrouping", SONG_GROUPING
},
953 {"daap.songrelativevolume", SONG_RELATIVE_VOLUME
},
954 {"daap.songsamplerate", SONG_SAMPLE_RATE
},
955 {"daap.songsize", SONG_SIZE
},
956 {"daap.songstarttime", SONG_START_TIME
},
957 {"daap.songstoptime", SONG_STOP_TIME
},
958 {"daap.songtime", SONG_TIME
},
959 {"daap.songtrackcount", SONG_TRACK_COUNT
},
960 {"daap.songtracknumber", SONG_TRACK_NUMBER
},
961 {"daap.songuserrating", SONG_USER_RATING
},
962 {"daap.songyear", SONG_YEAR
}};
964 typedef unsigned long long bitwise
;
973 client_requested (bitwise bits
,
976 return 0 != (bits
& (((bitwise
) 1) << field
));
979 #define DMAP_ITEM_KIND_AUDIO 2
980 #define DAAP_SONG_DATA_KIND_NONE 0
983 add_entry_to_mlcl (RhythmDB
*db
,
984 RhythmDBEntry
*entry
,
985 struct MLCL_Bits
*mb
)
990 id
= rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_ENTRY_ID
);
991 mlit
= rb_daap_structure_add (mb
->mlcl
, RB_DAAP_CC_MLIT
);
993 if (client_requested (mb
->bits
, ITEM_KIND
))
994 rb_daap_structure_add (mlit
, RB_DAAP_CC_MIKD
, (gchar
) DMAP_ITEM_KIND_AUDIO
);
995 if (client_requested (mb
->bits
, ITEM_ID
))
996 rb_daap_structure_add (mlit
, RB_DAAP_CC_MIID
, (gint32
) id
);
997 if (client_requested (mb
->bits
, ITEM_NAME
))
998 rb_daap_structure_add (mlit
, RB_DAAP_CC_MINM
, rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_TITLE
));
999 if (client_requested (mb
->bits
, PERSISTENT_ID
))
1000 rb_daap_structure_add (mlit
, RB_DAAP_CC_MPER
, (gint64
) id
);
1001 if (client_requested (mb
->bits
, CONTAINER_ITEM_ID
))
1002 rb_daap_structure_add (mlit
, RB_DAAP_CC_MCTI
, (gint32
) id
);
1003 if (client_requested (mb
->bits
, SONG_DATA_KIND
))
1004 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASDK
, (gchar
) DAAP_SONG_DATA_KIND_NONE
);
1005 if (client_requested (mb
->bits
, SONG_DATA_URL
))
1006 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASUL
, "");
1007 if (client_requested (mb
->bits
, SONG_ALBUM
))
1008 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASAL
, rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_ALBUM
));
1009 if (client_requested (mb
->bits
, SONG_GROUPING
))
1010 rb_daap_structure_add (mlit
, RB_DAAP_CC_AGRP
, "");
1011 if (client_requested (mb
->bits
, SONG_ARTIST
))
1012 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASAR
, rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_ARTIST
));
1013 if (client_requested (mb
->bits
, SONG_BITRATE
)) {
1014 gulong bitrate
= rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_BITRATE
);
1016 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASBR
, (gint32
) bitrate
);
1018 if (client_requested (mb
->bits
, SONG_BPM
))
1019 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASBT
, (gint32
) 0);
1020 if (client_requested (mb
->bits
, SONG_COMMENT
))
1021 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASCM
, "");
1022 if (client_requested (mb
->bits
, SONG_COMPILATION
))
1023 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASCO
, (gchar
) FALSE
);
1024 if (client_requested (mb
->bits
, SONG_COMPOSER
))
1025 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASCP
, "");
1026 if (client_requested (mb
->bits
, SONG_DATE_ADDED
))
1027 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASDA
, (gint32
) rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_FIRST_SEEN
));
1028 if (client_requested (mb
->bits
, SONG_DATE_MODIFIED
))
1029 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASDM
, (gint32
) rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_MTIME
));
1030 if (client_requested (mb
->bits
, SONG_DISC_COUNT
))
1031 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASDC
, (gint32
) 0);
1032 if (client_requested (mb
->bits
, SONG_DISC_NUMBER
))
1033 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASDN
, (gint32
) rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_DISC_NUMBER
));
1034 if (client_requested (mb
->bits
, SONG_DISABLED
))
1035 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASDB
, (gchar
) FALSE
);
1036 if (client_requested (mb
->bits
, SONG_EQ_PRESET
))
1037 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASEQ
, "");
1038 if (client_requested (mb
->bits
, SONG_FORMAT
)) {
1039 const gchar
*filename
;
1042 filename
= rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_LOCATION
);
1043 ext
= strrchr (filename
, '.');
1045 /* FIXME we should use RHYTHMDB_PROP_MIMETYPE instead */
1047 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASFM
, ext
);
1050 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASFM
, ext
);
1053 if (client_requested (mb
->bits
, SONG_GENRE
))
1054 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASGN
, rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_GENRE
));
1055 if (client_requested (mb
->bits
, SONG_DESCRIPTION
))
1056 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASDT
, "");
1057 if (client_requested (mb
->bits
, SONG_RELATIVE_VOLUME
))
1058 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASRV
, 0);
1059 if (client_requested (mb
->bits
, SONG_SAMPLE_RATE
))
1060 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASSR
, 0);
1061 if (client_requested (mb
->bits
, SONG_SIZE
))
1062 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASSZ
, (gint32
) rhythmdb_entry_get_uint64 (entry
, RHYTHMDB_PROP_FILE_SIZE
));
1063 if (client_requested (mb
->bits
, SONG_START_TIME
))
1064 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASST
, 0);
1065 if (client_requested (mb
->bits
, SONG_STOP_TIME
))
1066 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASSP
, 0);
1067 if (client_requested (mb
->bits
, SONG_TIME
))
1068 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASTM
, (gint32
) (1000 * rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_DURATION
)));
1069 if (client_requested (mb
->bits
, SONG_TRACK_COUNT
))
1070 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASTC
, 0);
1071 if (client_requested (mb
->bits
, SONG_TRACK_NUMBER
))
1072 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASTN
, (gint32
) rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_TRACK_NUMBER
));
1073 if (client_requested (mb
->bits
, SONG_USER_RATING
))
1074 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASUR
, 0); /* FIXME */
1075 if (client_requested (mb
->bits
, SONG_YEAR
))
1076 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASYR
, (gint32
) rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_YEAR
));
1082 add_playlist_to_mlcl (RBPlaylistID
*playlist_id
,
1085 /* MLIT listing item
1087 * MPER persistent item id
1094 RhythmDBQueryModel
*model
;
1096 g_object_get (playlist_id
->source
,
1098 "query-model", &model
,
1101 num_songs
= gtk_tree_model_iter_n_children (GTK_TREE_MODEL (model
), NULL
);
1102 g_object_unref (model
);
1104 mlit
= rb_daap_structure_add (mlcl
, RB_DAAP_CC_MLIT
);
1105 rb_daap_structure_add (mlit
, RB_DAAP_CC_MIID
, playlist_id
->id
);
1106 /* we don't have a persistant ID for playlists, unfortunately */
1107 rb_daap_structure_add (mlit
, RB_DAAP_CC_MPER
, (gint64
) playlist_id
->id
);
1108 rb_daap_structure_add (mlit
, RB_DAAP_CC_MINM
, name
);
1109 rb_daap_structure_add (mlit
, RB_DAAP_CC_MIMC
, (gint32
) num_songs
);
1117 add_playlist_entry_to_mlcl (GtkTreeModel
*model
,
1120 struct MLCL_Bits
*mb
)
1123 RhythmDBEntry
*entry
;
1126 mlit
= rb_daap_structure_add (mb
->mlcl
, RB_DAAP_CC_MLIT
);
1128 gtk_tree_model_get (model
, iter
, 0, &entry
, -1);
1130 id
= rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_ENTRY_ID
);
1132 if (client_requested (mb
->bits
, ITEM_KIND
))
1133 rb_daap_structure_add (mlit
, RB_DAAP_CC_MIKD
, (gchar
) DMAP_ITEM_KIND_AUDIO
);
1134 if (client_requested (mb
->bits
, ITEM_ID
))
1135 rb_daap_structure_add (mlit
, RB_DAAP_CC_MIID
, (gint32
) id
);
1136 if (client_requested (mb
->bits
, CONTAINER_ITEM_ID
))
1137 rb_daap_structure_add (mlit
, RB_DAAP_CC_MCTI
, (gint32
) id
);
1139 rhythmdb_entry_unref (entry
);
1145 parse_meta (const gchar
*s
)
1147 gchar
*start_of_attrs
;
1148 gchar
*end_of_attrs
;
1154 start_of_attrs
= strstr (s
, "meta=");
1155 if (start_of_attrs
== NULL
) {
1158 start_of_attrs
+= 5;
1160 end_of_attrs
= strchr (start_of_attrs
, '&');
1162 attrs
= g_strndup (start_of_attrs
, end_of_attrs
- start_of_attrs
);
1164 attrs
= g_strdup (start_of_attrs
);
1167 attrsv
= g_strsplit (attrs
,",",-1);
1169 for (i
= 0; attrsv
[i
]; i
++) {
1172 for (j
= 0; j
< G_N_ELEMENTS (meta_data_map
); j
++) {
1173 if (strcmp (meta_data_map
[j
].tag
, attrsv
[i
]) == 0) {
1174 bits
|= (((bitwise
) 1) << meta_data_map
[j
].md
);
1180 g_strfreev (attrsv
);
1186 write_next_chunk (SoupMessage
*message
, GnomeVFSHandle
*handle
)
1188 GnomeVFSFileSize read_size
;
1189 GnomeVFSResult result
;
1190 gchar
*chunk
= g_malloc (DAAP_SHARE_CHUNK_SIZE
);
1192 result
= gnome_vfs_read (handle
, chunk
, DAAP_SHARE_CHUNK_SIZE
, &read_size
);
1193 if (result
== GNOME_VFS_OK
&& read_size
> 0) {
1194 soup_message_add_chunk (message
, SOUP_BUFFER_SYSTEM_OWNED
, chunk
, read_size
);
1197 soup_message_add_final_chunk (message
);
1202 chunked_message_finished (SoupMessage
*message
, GnomeVFSHandle
*handle
)
1204 rb_debug ("finished sending chunked file");
1205 gnome_vfs_close (handle
);
1209 send_chunked_file (SoupMessage
*message
, RhythmDBEntry
*entry
, guint64 file_size
, guint64 offset
)
1211 GnomeVFSResult result
;
1212 GnomeVFSHandle
*handle
;
1213 const char *location
;
1215 location
= rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_LOCATION
);
1217 rb_debug ("sending %s chunked from offset %" G_GUINT64_FORMAT
, location
, offset
);
1218 result
= gnome_vfs_open (&handle
, location
, GNOME_VFS_OPEN_READ
);
1219 if (result
!= GNOME_VFS_OK
) {
1220 soup_message_set_status (message
, SOUP_STATUS_INTERNAL_SERVER_ERROR
);
1225 result
= gnome_vfs_seek (handle
, GNOME_VFS_SEEK_START
, offset
);
1226 if (result
!= GNOME_VFS_OK
) {
1227 g_warning ("Error seeking: %s", gnome_vfs_result_to_string (result
));
1228 gnome_vfs_close (handle
);
1229 soup_message_set_status (message
, SOUP_STATUS_INTERNAL_SERVER_ERROR
);
1232 file_size
-= offset
;
1235 soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (message
), SOUP_TRANSFER_CHUNKED
);
1237 g_signal_connect (message
, "wrote_chunk", G_CALLBACK (write_next_chunk
), handle
);
1238 g_signal_connect (message
, "finished", G_CALLBACK (chunked_message_finished
), handle
);
1239 write_next_chunk (message
, handle
);
1242 #ifdef HAVE_G_MAPPED_FILE
1244 mapped_file_message_finished (SoupMessage
*message
, GMappedFile
*file
)
1246 rb_debug ("finished sending mmapped file");
1247 g_mapped_file_free (file
);
1251 send_mapped_file (SoupMessage
*message
, RhythmDBEntry
*entry
, guint64 file_size
, guint64 offset
)
1253 GMappedFile
*mapped_file
;
1255 GError
*error
= NULL
;
1257 path
= gnome_vfs_get_local_path_from_uri (rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_LOCATION
));
1258 rb_debug ("sending file %s mmapped, from offset %" G_GUINT64_FORMAT
, path
, offset
);
1260 mapped_file
= g_mapped_file_new (path
, FALSE
, &error
);
1261 if (mapped_file
== NULL
) {
1262 g_warning ("Unable to map file %s: %s", path
, error
->message
);
1263 soup_message_set_status (message
, SOUP_STATUS_INTERNAL_SERVER_ERROR
);
1265 message
->response
.owner
= SOUP_BUFFER_USER_OWNED
;
1266 message
->response
.length
= file_size
;
1267 message
->response
.body
= g_mapped_file_get_contents (mapped_file
) + offset
;
1268 soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (message
),
1269 SOUP_TRANSFER_CONTENT_LENGTH
);
1271 g_signal_connect (message
,
1273 G_CALLBACK (mapped_file_message_finished
),
1281 databases_cb (RBDAAPShare
*share
,
1282 SoupServerContext
*context
,
1283 SoupMessage
*message
)
1286 gchar
*rest_of_path
;
1287 /*guint revision_number;*/
1289 if (! session_id_validate (share
, context
, message
, NULL
)) {
1290 soup_message_set_status (message
, SOUP_STATUS_FORBIDDEN
);
1291 soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (message
), SOUP_TRANSFER_CONTENT_LENGTH
);
1295 path
= soup_uri_to_string (soup_message_get_uri (message
), TRUE
);
1297 rest_of_path
= strchr (path
+ 1, '/');
1299 if (rest_of_path
== NULL
) {
1300 /* AVDB server databases
1303 * MTCO specified total count
1304 * MRCO returned count
1308 * MPER persistent id
1311 * MCTC container count
1317 avdb
= rb_daap_structure_add (NULL
, RB_DAAP_CC_AVDB
);
1318 rb_daap_structure_add (avdb
, RB_DAAP_CC_MSTT
, (gint32
) DMAP_STATUS_OK
);
1319 rb_daap_structure_add (avdb
, RB_DAAP_CC_MUTY
, 0);
1320 rb_daap_structure_add (avdb
, RB_DAAP_CC_MTCO
, (gint32
) 1);
1321 rb_daap_structure_add (avdb
, RB_DAAP_CC_MRCO
, (gint32
) 1);
1322 mlcl
= rb_daap_structure_add (avdb
, RB_DAAP_CC_MLCL
);
1323 mlit
= rb_daap_structure_add (mlcl
, RB_DAAP_CC_MLIT
);
1324 rb_daap_structure_add (mlit
, RB_DAAP_CC_MIID
, (gint32
) 1);
1325 rb_daap_structure_add (mlit
, RB_DAAP_CC_MPER
, (gint64
) 1);
1326 rb_daap_structure_add (mlit
, RB_DAAP_CC_MINM
, share
->priv
->name
);
1327 rb_daap_structure_add (mlit
, RB_DAAP_CC_MIMC
, (gint32
) rhythmdb_entry_count_by_type (share
->priv
->db
, share
->priv
->entry_type
));
1328 rb_daap_structure_add (mlit
, RB_DAAP_CC_MCTC
, (gint32
) 1);
1330 message_set_from_rb_daap_structure (message
, avdb
);
1331 rb_daap_structure_destroy (avdb
);
1332 } else if (g_ascii_strncasecmp ("/1/items?", rest_of_path
, 9) == 0) {
1333 /* ADBS database songs
1336 * MTCO specified total count
1337 * MRCO returned count
1345 gint32 num_songs
= rhythmdb_entry_count_by_type (share
->priv
->db
, share
->priv
->entry_type
);
1346 struct MLCL_Bits mb
= {NULL
,0};
1348 mb
.bits
= parse_meta (rest_of_path
);
1350 adbs
= rb_daap_structure_add (NULL
, RB_DAAP_CC_ADBS
);
1351 rb_daap_structure_add (adbs
, RB_DAAP_CC_MSTT
, (gint32
) DMAP_STATUS_OK
);
1352 rb_daap_structure_add (adbs
, RB_DAAP_CC_MUTY
, 0);
1353 rb_daap_structure_add (adbs
, RB_DAAP_CC_MTCO
, (gint32
) num_songs
);
1354 rb_daap_structure_add (adbs
, RB_DAAP_CC_MRCO
, (gint32
) num_songs
);
1355 mb
.mlcl
= rb_daap_structure_add (adbs
, RB_DAAP_CC_MLCL
);
1357 rhythmdb_entry_foreach_by_type (share
->priv
->db
, share
->priv
->entry_type
, (GFunc
) add_entry_to_mlcl
, &mb
);
1359 message_set_from_rb_daap_structure (message
, adbs
);
1360 rb_daap_structure_destroy (adbs
);
1362 } else if (g_ascii_strncasecmp ("/1/containers?", rest_of_path
, 14) == 0) {
1363 /* APLY database playlists
1366 * MTCO specified total count
1367 * MRCO returned count
1371 * MPER persistent item id
1374 * ABPL baseplaylist (only for base)
1382 aply
= rb_daap_structure_add (NULL
, RB_DAAP_CC_APLY
);
1383 rb_daap_structure_add (aply
, RB_DAAP_CC_MSTT
, (gint32
) DMAP_STATUS_OK
);
1384 rb_daap_structure_add (aply
, RB_DAAP_CC_MUTY
, 0);
1385 rb_daap_structure_add (aply
, RB_DAAP_CC_MTCO
, (gint32
) 1);
1386 rb_daap_structure_add (aply
, RB_DAAP_CC_MRCO
, (gint32
) 1);
1387 mlcl
= rb_daap_structure_add (aply
, RB_DAAP_CC_MLCL
);
1388 mlit
= rb_daap_structure_add (mlcl
, RB_DAAP_CC_MLIT
);
1389 rb_daap_structure_add (mlit
, RB_DAAP_CC_MIID
, (gint32
) 1);
1390 rb_daap_structure_add (mlit
, RB_DAAP_CC_MPER
, (gint64
) 1);
1391 rb_daap_structure_add (mlit
, RB_DAAP_CC_MINM
, share
->priv
->name
);
1392 rb_daap_structure_add (mlit
, RB_DAAP_CC_MIMC
, (gint32
) rhythmdb_entry_count_by_type (share
->priv
->db
, share
->priv
->entry_type
));
1393 rb_daap_structure_add (mlit
, RB_DAAP_CC_ABPL
, (gchar
) 1); /* base playlist */
1395 g_list_foreach (share
->priv
->playlist_ids
, (GFunc
) add_playlist_to_mlcl
, mlcl
);
1397 message_set_from_rb_daap_structure (message
, aply
);
1398 rb_daap_structure_destroy (aply
);
1399 } else if (g_ascii_strncasecmp ("/1/containers/", rest_of_path
, 14) == 0) {
1400 /* APSO playlist songs
1403 * MTCO specified total count
1404 * MRCO returned count
1409 * MCTI container item id
1414 struct MLCL_Bits mb
= {NULL
,0};
1415 gint pl_id
= atoi (rest_of_path
+ 14);
1417 mb
.bits
= parse_meta (rest_of_path
);
1419 apso
= rb_daap_structure_add (NULL
, RB_DAAP_CC_APSO
);
1420 rb_daap_structure_add (apso
, RB_DAAP_CC_MSTT
, (gint32
) DMAP_STATUS_OK
);
1421 rb_daap_structure_add (apso
, RB_DAAP_CC_MUTY
, 0);
1424 gint32 num_songs
= (gint32
) rhythmdb_entry_count_by_type (share
->priv
->db
, share
->priv
->entry_type
);
1425 rb_daap_structure_add (apso
, RB_DAAP_CC_MTCO
, (gint32
) num_songs
);
1426 rb_daap_structure_add (apso
, RB_DAAP_CC_MRCO
, (gint32
) num_songs
);
1427 mb
.mlcl
= rb_daap_structure_add (apso
, RB_DAAP_CC_MLCL
);
1429 rhythmdb_entry_foreach_by_type (share
->priv
->db
,
1430 share
->priv
->entry_type
,
1431 (GFunc
) add_entry_to_mlcl
,
1437 RhythmDBQueryModel
*model
;
1439 idl
= g_list_find_custom (share
->priv
->playlist_ids
,
1440 GINT_TO_POINTER (pl_id
),
1443 soup_message_set_status (message
, SOUP_STATUS_NOT_FOUND
);
1444 soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (message
),
1445 SOUP_TRANSFER_CONTENT_LENGTH
);
1446 soup_message_set_response (message
, "text/plain", SOUP_BUFFER_USER_OWNED
, "", 0);
1449 id
= (RBPlaylistID
*)idl
->data
;
1451 mb
.mlcl
= rb_daap_structure_add (apso
, RB_DAAP_CC_MLCL
);
1453 g_object_get (id
->source
, "query-model", &model
, NULL
);
1454 num_songs
= gtk_tree_model_iter_n_children (GTK_TREE_MODEL (model
), NULL
);
1456 rb_daap_structure_add (apso
, RB_DAAP_CC_MTCO
, (gint32
) num_songs
);
1457 rb_daap_structure_add (apso
, RB_DAAP_CC_MRCO
, (gint32
) num_songs
);
1459 gtk_tree_model_foreach (GTK_TREE_MODEL (model
), (GtkTreeModelForeachFunc
) add_playlist_entry_to_mlcl
, &mb
);
1460 g_object_unref (model
);
1463 message_set_from_rb_daap_structure (message
, apso
);
1464 rb_daap_structure_destroy (apso
);
1465 } else if (g_ascii_strncasecmp ("/1/items/", rest_of_path
, 9) == 0) {
1466 /* just the file :) */
1469 RhythmDBEntry
*entry
;
1470 const gchar
*location
;
1471 const gchar
*range_header
;
1475 id_str
= rest_of_path
+ 9;
1478 entry
= rhythmdb_entry_lookup_by_id (share
->priv
->db
, id
);
1479 location
= rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_LOCATION
);
1480 file_size
= rhythmdb_entry_get_uint64 (entry
, RHYTHMDB_PROP_FILE_SIZE
);
1482 message_add_standard_headers (message
);
1483 soup_message_add_header (message
->response_headers
, "Accept-Ranges", "bytes");
1485 range_header
= soup_message_get_header (message
->request_headers
, "Range");
1488 gchar
*content_range
;
1490 s
= range_header
+ 6; /* bytes= */
1493 content_range
= g_strdup_printf ("bytes %" G_GUINT64_FORMAT
"-%" G_GUINT64_FORMAT
"/%" G_GUINT64_FORMAT
, offset
, file_size
, file_size
);
1494 soup_message_add_header (message
->response_headers
, "Content-Range", content_range
);
1495 g_free (content_range
);
1497 soup_message_set_status (message
, SOUP_STATUS_PARTIAL_CONTENT
);
1498 file_size
-= offset
;
1500 soup_message_set_status (message
, SOUP_STATUS_OK
);
1503 #ifdef HAVE_G_MAPPED_FILE
1504 /* don't use chunked transfers if we can send the file mmapped,
1505 * as itunes clients can't seek properly when we do.
1507 if (rb_uri_is_local (location
)) {
1508 send_mapped_file (message
, entry
, file_size
, offset
);
1512 send_chunked_file (message
, entry
, file_size
, offset
);
1515 rb_debug ("unhandled: %s\n", path
);
1522 typedef void (* DAAPPathFunction
) (RBDAAPShare
*share
,
1523 SoupServerContext
*context
,
1524 SoupMessage
*message
);
1529 DAAPPathFunction function
;
1532 static const struct DAAPPath paths_to_functions
[] = {
1533 {"/server-info", 12, server_info_cb
},
1534 {"/content-codes", 14, content_codes_cb
},
1535 {"/login", 6, login_cb
},
1536 {"/logout", 7, logout_cb
},
1537 {"/update", 7, update_cb
},
1538 {"/databases", 10, databases_cb
}
1542 server_cb (SoupServerContext
*context
,
1543 SoupMessage
*message
,
1549 path
= soup_uri_to_string (soup_message_get_uri (message
), TRUE
);
1550 rb_debug ("request for %s", path
);
1552 for (i
= 0; i
< G_N_ELEMENTS (paths_to_functions
); i
++) {
1553 if (g_ascii_strncasecmp (paths_to_functions
[i
].path
, path
, paths_to_functions
[i
].path_length
) == 0) {
1554 paths_to_functions
[i
].function (share
, context
, message
);
1559 g_warning ("unhandled path %s\n", path
);
1565 db_entry_added_cb (RhythmDB
*db
,
1566 RhythmDBEntry
*entry
,
1569 /* TODO: update our db version number? */
1573 add_db_entry (RhythmDBEntry
*entry
,
1576 db_entry_added_cb (share
->priv
->db
, entry
, share
);
1580 db_entry_deleted_cb (RhythmDB
*db
,
1581 RhythmDBEntry
*entry
,
1584 /* TODO: update our db version number? */
1588 db_entry_changed_cb (RhythmDB
*db
,
1589 RhythmDBEntry
*entry
,
1593 if (rhythmdb_entry_get_boolean (entry
, RHYTHMDB_PROP_HIDDEN
)) {
1594 db_entry_deleted_cb (db
, entry
, share
);
1596 db_entry_added_cb (db
, entry
, share
);
1601 soup_auth_callback (SoupServerAuthContext
*auth_ctx
,
1602 SoupServerAuth
*auth
,
1603 SoupMessage
*message
,
1606 const char *username
;
1610 path
= soup_uri_to_string (soup_message_get_uri (message
), TRUE
);
1611 rb_debug ("Auth request for %s", path
);
1615 /* This is to workaround libsoup looking up handlers by directory.
1616 We require auth for "/databases?" but not "/databases/" */
1617 if (g_str_has_prefix (path
, "/databases/")) {
1622 rb_debug ("Auth DENIED: information not provided");
1627 username
= soup_server_auth_get_user (auth
);
1628 rb_debug ("Auth request for user: %s", username
);
1630 allowed
= soup_server_auth_check_passwd (auth
, share
->priv
->password
);
1631 rb_debug ("Auth request: %s", allowed
? "ALLOWED" : "DENIED");
1640 rb_daap_share_server_start (RBDAAPShare
*share
)
1642 int port
= STANDARD_DAAP_PORT
;
1643 gboolean password_required
;
1644 SoupServerAuthContext auth_ctx
= { 0 };
1646 share
->priv
->server
= soup_server_new (SOUP_SERVER_PORT
, port
, NULL
);
1647 if (share
->priv
->server
== NULL
) {
1648 rb_debug ("Unable to start music sharing server on port %d, trying any open port", port
);
1649 share
->priv
->server
= soup_server_new (SOUP_SERVER_PORT
, SOUP_ADDRESS_ANY_PORT
, NULL
);
1651 if (share
->priv
->server
== NULL
) {
1652 g_warning ("Unable to start music sharing server");
1657 share
->priv
->port
= (guint
)soup_server_get_port (share
->priv
->server
);
1658 rb_debug ("Started DAAP server on port %u", share
->priv
->port
);
1660 password_required
= (share
->priv
->auth_method
!= RB_DAAP_SHARE_AUTH_METHOD_NONE
);
1662 if (password_required
) {
1663 auth_ctx
.types
= SOUP_AUTH_TYPE_BASIC
;
1664 auth_ctx
.callback
= (SoupServerAuthCallbackFn
)soup_auth_callback
;
1665 auth_ctx
.user_data
= share
;
1666 auth_ctx
.basic_info
.realm
= "Music Sharing";
1668 soup_server_add_handler (share
->priv
->server
,
1671 (SoupServerCallbackFn
)server_cb
,
1674 soup_server_add_handler (share
->priv
->server
,
1677 (SoupServerCallbackFn
)server_cb
,
1680 soup_server_add_handler (share
->priv
->server
,
1683 (SoupServerCallbackFn
)server_cb
,
1688 soup_server_add_handler (share
->priv
->server
,
1691 (SoupServerCallbackFn
)server_cb
,
1694 soup_server_run_async (share
->priv
->server
);
1696 /* using direct since there is no g_uint_hash or g_uint_equal */
1697 share
->priv
->session_ids
= g_hash_table_new_full (g_direct_hash
, g_direct_equal
, NULL
, g_free
);
1699 share
->priv
->next_playlist_id
= 2; /* 1 already used */
1701 rhythmdb_entry_foreach (share
->priv
->db
, (GFunc
)add_db_entry
, share
);
1703 share
->priv
->entry_added_id
= g_signal_connect (G_OBJECT (share
->priv
->db
),
1705 G_CALLBACK (db_entry_added_cb
),
1707 share
->priv
->entry_deleted_id
= g_signal_connect (G_OBJECT (share
->priv
->db
),
1709 G_CALLBACK (db_entry_deleted_cb
),
1711 share
->priv
->entry_changed_id
= g_signal_connect (G_OBJECT (share
->priv
->db
),
1713 G_CALLBACK (db_entry_changed_cb
),
1716 share
->priv
->server_active
= TRUE
;
1722 rb_daap_share_server_stop (RBDAAPShare
*share
)
1724 rb_debug ("Stopping music sharing server on port %d", share
->priv
->port
);
1726 if (share
->priv
->server
) {
1727 soup_server_quit (share
->priv
->server
);
1728 g_object_unref (share
->priv
->server
);
1729 share
->priv
->server
= NULL
;
1732 if (share
->priv
->session_ids
) {
1733 g_hash_table_destroy (share
->priv
->session_ids
);
1734 share
->priv
->session_ids
= NULL
;
1737 if (share
->priv
->entry_added_id
!= 0) {
1738 g_signal_handler_disconnect (share
->priv
->db
, share
->priv
->entry_added_id
);
1739 share
->priv
->entry_added_id
= 0;
1742 if (share
->priv
->entry_deleted_id
!= 0) {
1743 g_signal_handler_disconnect (share
->priv
->db
, share
->priv
->entry_deleted_id
);
1744 share
->priv
->entry_deleted_id
= 0;
1747 if (share
->priv
->entry_changed_id
!= 0) {
1748 g_signal_handler_disconnect (share
->priv
->db
, share
->priv
->entry_changed_id
);
1749 share
->priv
->entry_changed_id
= 0;
1752 share
->priv
->server_active
= FALSE
;
1758 rb_daap_share_publish_start (RBDAAPShare
*share
)
1762 gboolean password_required
;
1764 password_required
= (share
->priv
->auth_method
!= RB_DAAP_SHARE_AUTH_METHOD_NONE
);
1767 res
= rb_daap_mdns_publisher_publish (share
->priv
->publisher
,
1774 if (error
!= NULL
) {
1775 g_warning ("Unable to notify network of music sharing: %s", error
->message
);
1776 g_error_free (error
);
1778 g_warning ("Unable to notify network of music sharing");
1782 rb_debug ("Published DAAP server information to mdns");
1789 rb_daap_share_publish_stop (RBDAAPShare
*share
)
1791 if (share
->priv
->publisher
) {
1795 res
= rb_daap_mdns_publisher_withdraw (share
->priv
->publisher
, &error
);
1796 if (error
!= NULL
) {
1797 g_warning ("Unable to withdraw music sharing service: %s", error
->message
);
1798 g_error_free (error
);
1803 share
->priv
->published
= FALSE
;
1808 rb_daap_share_restart (RBDAAPShare
*share
)
1812 rb_daap_share_server_stop (share
);
1813 res
= rb_daap_share_server_start (share
);
1815 /* To update information just publish again */
1816 rb_daap_share_publish_start (share
);
1818 rb_daap_share_publish_stop (share
);
1823 rb_daap_share_maybe_restart (RBDAAPShare
*share
)
1825 if (share
->priv
->published
) {
1826 rb_daap_share_restart (share
);