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
;
104 GHashTable
*id_to_entry
;
105 gulong entry_added_id
;
106 gulong entry_deleted_id
;
107 gulong entry_changed_id
;
109 /* playlist things */
110 RBPlaylistManager
*playlist_manager
;
111 guint next_playlist_id
;
112 GList
*playlist_ids
; /* contains RBPlaylistIDs */
115 #define RB_DAAP_SHARE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_DAAP_SHARE, RBDAAPSharePrivate))
127 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
));
170 g_type_class_add_private (klass
, sizeof (RBDAAPSharePrivate
));
174 rb_daap_share_set_name (RBDAAPShare
*share
,
180 g_return_if_fail (share
!= NULL
);
182 g_free (share
->priv
->name
);
183 share
->priv
->name
= g_strdup (name
);
186 res
= rb_daap_mdns_publisher_set_name (share
->priv
->publisher
, name
, &error
);
188 g_warning ("Unable to change MDNS service name: %s", error
->message
);
189 g_error_free (error
);
194 published_cb (RBDaapMdnsPublisher
*publisher
,
198 if (share
->priv
->name
== NULL
|| name
== NULL
) {
202 if (strcmp (name
, share
->priv
->name
) == 0) {
203 rb_debug ("mDNS publish successful");
204 share
->priv
->published
= TRUE
;
209 name_collision_cb (RBDaapMdnsPublisher
*publisher
,
215 if (share
->priv
->name
== NULL
|| name
== NULL
) {
219 if (strcmp (name
, share
->priv
->name
) == 0) {
220 rb_debug ("Duplicate share name on mDNS");
222 GDK_THREADS_ENTER ();
223 new_name
= rb_daap_collision_dialog_new_run (NULL
, share
->priv
->name
);
224 GDK_THREADS_LEAVE ();
226 rb_daap_share_set_name (share
, new_name
);
234 rb_daap_share_init (RBDAAPShare
*share
)
236 share
->priv
= RB_DAAP_SHARE_GET_PRIVATE (share
);
238 share
->priv
->revision_number
= 5;
240 share
->priv
->auth_method
= RB_DAAP_SHARE_AUTH_METHOD_NONE
;
241 share
->priv
->publisher
= rb_daap_mdns_publisher_new ();
242 g_signal_connect_object (share
->priv
->publisher
,
244 G_CALLBACK (published_cb
),
246 g_signal_connect_object (share
->priv
->publisher
,
248 G_CALLBACK (name_collision_cb
),
254 rb_daap_share_set_password (RBDAAPShare
*share
,
255 const char *password
)
257 g_return_if_fail (share
!= NULL
);
259 if (share
->priv
->password
&& password
&&
260 strcmp (password
, share
->priv
->password
) == 0) {
264 g_free (share
->priv
->password
);
265 share
->priv
->password
= g_strdup (password
);
266 if (password
!= NULL
) {
267 share
->priv
->auth_method
= RB_DAAP_SHARE_AUTH_METHOD_PASSWORD
;
269 share
->priv
->auth_method
= RB_DAAP_SHARE_AUTH_METHOD_NONE
;
272 rb_daap_share_maybe_restart (share
);
276 rb_daap_share_set_db (RBDAAPShare
*share
,
279 if (share
->priv
->db
!= NULL
) {
280 g_object_unref (share
->priv
->db
);
283 share
->priv
->db
= db
;
285 if (share
->priv
->db
!= NULL
) {
286 g_object_ref (share
->priv
->db
);
291 rb_daap_share_set_playlist_manager (RBDAAPShare
*share
,
292 RBPlaylistManager
*playlist_manager
)
296 g_return_if_fail (share
!= NULL
);
298 if (share
->priv
->playlist_manager
!= NULL
) {
299 g_object_unref (share
->priv
->playlist_manager
);
300 g_signal_handlers_disconnect_by_func (share
->priv
->playlist_manager
,
301 G_CALLBACK (rb_daap_share_playlist_created
),
305 share
->priv
->playlist_manager
= playlist_manager
;
307 if (share
->priv
->playlist_manager
!= NULL
) {
308 g_object_ref (share
->priv
->playlist_manager
);
310 g_signal_connect_object (G_OBJECT (share
->priv
->playlist_manager
),
312 G_CALLBACK (rb_daap_share_playlist_created
),
315 /* Currently, there are no playlists when this object is created, but in
318 playlists
= rb_playlist_manager_get_playlists (share
->priv
->playlist_manager
);
319 g_list_foreach (playlists
, (GFunc
) rb_daap_share_process_playlist
, share
);
320 g_list_free (playlists
);
325 rb_daap_share_set_property (GObject
*object
,
330 RBDAAPShare
*share
= RB_DAAP_SHARE (object
);
334 rb_daap_share_set_name (share
, g_value_get_string (value
));
337 rb_daap_share_set_password (share
, g_value_get_string (value
));
340 rb_daap_share_set_db (share
, g_value_get_object (value
));
342 case PROP_PLAYLIST_MANAGER
:
343 rb_daap_share_set_playlist_manager (share
, g_value_get_object (value
));
346 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
352 rb_daap_share_get_property (GObject
*object
,
357 RBDAAPShare
*share
= RB_DAAP_SHARE (object
);
361 g_value_set_string (value
, share
->priv
->name
);
364 g_value_set_string (value
, share
->priv
->password
);
367 g_value_set_object (value
, share
->priv
->db
);
369 case PROP_PLAYLIST_MANAGER
:
370 g_value_set_object (value
, share
->priv
->playlist_manager
);
373 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
379 _find_by_id (gconstpointer a
, gconstpointer b
)
381 RBPlaylistID
*ai
= (RBPlaylistID
*)a
;
382 gint bv
= GPOINTER_TO_INT (b
);
383 return (ai
->id
- bv
);
387 _find_by_source (gconstpointer a
, gconstpointer b
)
389 RBPlaylistID
*ai
= (RBPlaylistID
*)a
;
390 RBSource
*bs
= (RBSource
*)b
;
391 return (ai
->source
- bs
);
395 rb_daap_share_playlist_created (RBPlaylistManager
*manager
,
399 rb_daap_share_process_playlist (source
, share
);
403 rb_daap_share_process_playlist (RBSource
*source
,
408 /* make sure we're not going insane.. */
409 g_assert (g_list_find_custom (share
->priv
->playlist_ids
,
411 _find_by_source
) == NULL
);
413 g_object_weak_ref (G_OBJECT (source
),
414 (GWeakNotify
) rb_daap_share_playlist_destroyed
,
416 id
= g_new0 (RBPlaylistID
, 1);
418 id
->id
= share
->priv
->next_playlist_id
++;
419 share
->priv
->playlist_ids
= g_list_append (share
->priv
->playlist_ids
, id
);
421 /* if we knew how to send updates to clients, we'd probably do something here */
425 rb_daap_share_playlist_destroyed (RBDAAPShare
*share
,
430 id
= g_list_find_custom (share
->priv
->playlist_ids
, source
, _find_by_source
);
434 share
->priv
->playlist_ids
= g_list_remove_link (share
->priv
->playlist_ids
, id
);
440 rb_daap_share_forget_playlist (gpointer data
,
443 RBPlaylistID
*id
= (RBPlaylistID
*)data
;
444 g_object_weak_unref (G_OBJECT (id
->source
),
445 (GWeakNotify
) rb_daap_share_playlist_destroyed
,
450 rb_daap_share_dispose (GObject
*object
)
452 RBDAAPShare
*share
= RB_DAAP_SHARE (object
);
454 if (share
->priv
->published
) {
455 rb_daap_share_publish_stop (share
);
458 if (share
->priv
->server_active
) {
459 rb_daap_share_server_stop (share
);
462 g_free (share
->priv
->name
);
463 g_object_unref (share
->priv
->db
);
464 g_object_unref (share
->priv
->playlist_manager
);
466 g_list_foreach (share
->priv
->playlist_ids
, (GFunc
) rb_daap_share_forget_playlist
, share
);
467 g_list_foreach (share
->priv
->playlist_ids
, (GFunc
) g_free
, NULL
);
469 if (share
->priv
->publisher
) {
470 g_object_unref (share
->priv
->publisher
);
473 G_OBJECT_CLASS (rb_daap_share_parent_class
)->dispose (object
);
477 rb_daap_share_new (const char *name
,
478 const char *password
,
480 RBPlaylistManager
*playlist_manager
)
484 share
= RB_DAAP_SHARE (g_object_new (RB_TYPE_DAAP_SHARE
,
486 "password", password
,
488 "playlist-manager", playlist_manager
,
491 rb_daap_share_server_start (share
);
492 rb_daap_share_publish_start (share
);
498 message_add_standard_headers (SoupMessage
*message
)
504 soup_message_add_header (message
->response_headers
, "DAAP-Server", "Rhythmbox " VERSION
);
506 soup_message_add_header (message
->response_headers
, "Content-Type", "application/x-dmap-tagged");
510 s
= g_new (gchar
, 100);
511 strftime (s
, 100, "%a, %d %b %Y %T GMT", tm
);
512 soup_message_add_header (message
->response_headers
, "Date", s
);
517 message_set_from_rb_daap_structure (SoupMessage
*message
,
523 resp
= rb_daap_structure_serialize (structure
, &length
);
526 rb_debug ("serialize gave us null?\n");
530 message
->response
.owner
= SOUP_BUFFER_SYSTEM_OWNED
;
531 message
->response
.length
= length
;
532 message
->response
.body
= resp
;
534 message_add_standard_headers (message
);
536 soup_message_set_status (message
, SOUP_STATUS_OK
);
537 soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (message
), SOUP_TRANSFER_CONTENT_LENGTH
);
540 #define DMAP_STATUS_OK 200
542 #define DMAP_VERSION 2.0
543 #define DAAP_VERSION 3.0
544 #define DMAP_TIMEOUT 1800
547 server_info_cb (RBDAAPShare
*share
,
548 SoupServerContext
*context
,
549 SoupMessage
*message
)
551 /* MSRV server info response
556 * MSAU authentication method
557 * MSLR login required
558 * MSTM timeout interval
559 * MSAL supports auto logout
560 * MSUP supports update
561 * MSPI supports persistent ids
562 * MSEX supports extensions
563 * MSBR supports browse
564 * MSQY supports query
565 * MSIX supports index
566 * MSRS supports resolve
567 * MSDC databases count
571 msrv
= rb_daap_structure_add (NULL
, RB_DAAP_CC_MSRV
);
572 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSTT
, (gint32
) DMAP_STATUS_OK
);
573 rb_daap_structure_add (msrv
, RB_DAAP_CC_MPRO
, (gdouble
) DMAP_VERSION
);
574 rb_daap_structure_add (msrv
, RB_DAAP_CC_APRO
, (gdouble
) DAAP_VERSION
);
575 /* 2/3 is for itunes 4.8 (at least). its determined by the
576 * Client-DAAP-Version header sent, but if we decide not to support
577 * older versions..? anyway
583 rb_daap_structure_add (msrv
, RB_DAAP_CC_MINM
, share
->priv
->name
);
584 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSAU
, share
->priv
->auth_method
);
585 /* authentication method
587 * 1 is name & password
590 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSLR
, 0);
591 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSTM
, (gint32
) DMAP_TIMEOUT
);
592 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSAL
, (gchar
) 0);
593 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSUP
, (gchar
) 0);
594 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSPI
, (gchar
) 0);
595 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSEX
, (gchar
) 0);
596 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSBR
, (gchar
) 0);
597 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSQY
, (gchar
) 0);
598 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSIX
, (gchar
) 0);
599 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSRS
, (gchar
) 0);
600 rb_daap_structure_add (msrv
, RB_DAAP_CC_MSDC
, (gint32
) 1);
602 message_set_from_rb_daap_structure (message
, msrv
);
603 rb_daap_structure_destroy (msrv
);
607 content_codes_cb (RBDAAPShare
*share
,
608 SoupServerContext
*context
,
609 SoupMessage
*message
)
611 /* MCCR content codes response
614 * MCNM content codes number
615 * MCNA content codes name
616 * MCTY content codes type
620 const RBDAAPContentCodeDefinition
*defs
;
625 defs
= rb_daap_content_codes (&num_defs
);
627 mccr
= rb_daap_structure_add (NULL
, RB_DAAP_CC_MCCR
);
628 rb_daap_structure_add (mccr
, RB_DAAP_CC_MSTT
, (gint32
) DMAP_STATUS_OK
);
630 for (i
= 0; i
< num_defs
; i
++) {
633 mdcl
= rb_daap_structure_add (mccr
, RB_DAAP_CC_MDCL
);
634 rb_daap_structure_add (mdcl
, RB_DAAP_CC_MCNM
, rb_daap_content_code_string_as_int32(defs
[i
].string
));
635 rb_daap_structure_add (mdcl
, RB_DAAP_CC_MCNA
, defs
[i
].name
);
636 rb_daap_structure_add (mdcl
, RB_DAAP_CC_MCTY
, (gint32
) defs
[i
].type
);
639 message_set_from_rb_daap_structure (message
, mccr
);
640 rb_daap_structure_destroy (mccr
);
644 message_get_session_id (SoupMessage
*message
,
655 uri
= soup_message_get_uri (message
);
660 position
= strstr (uri
->query
, "session-id=");
662 if (position
== NULL
) {
663 rb_debug ("session id not found");
668 session_id
= (guint32
) strtoul (position
, NULL
, 10);
678 message_get_revision_number (SoupMessage
*message
,
683 guint revision_number
;
689 uri
= soup_message_get_uri (message
);
694 position
= strstr (uri
->query
, "revision-number=");
696 if (position
== NULL
) {
697 rb_debug ("client asked for an update without a revision number?!?\n");
702 revision_number
= atoi (position
);
705 *number
= revision_number
;
712 session_id_validate (RBDAAPShare
*share
,
713 SoupServerContext
*context
,
714 SoupMessage
*message
,
720 const char *remote_address
;
726 res
= message_get_session_id (message
, &session_id
);
728 rb_debug ("Validation failed: Unable to parse session id from message");
732 /* check hash for remote address */
733 addr
= g_hash_table_lookup (share
->priv
->session_ids
, GUINT_TO_POINTER (session_id
));
735 rb_debug ("Validation failed: Unable to lookup session id %u", session_id
);
739 remote_address
= soup_server_context_get_client_host (context
);
740 rb_debug ("Validating session id %u from %s matches %s",
741 session_id
, remote_address
, addr
);
742 if (remote_address
== NULL
|| strcmp (addr
, remote_address
) != 0) {
743 rb_debug ("Validation failed: Remote address does not match stored address");
755 session_id_generate (RBDAAPShare
*share
,
756 SoupServerContext
*context
)
760 id
= g_random_int ();
766 session_id_create (RBDAAPShare
*share
,
767 SoupServerContext
*context
)
771 char *remote_address
;
774 /* create a unique session id */
775 id
= session_id_generate (share
, context
);
776 rb_debug ("Generated session id %u", id
);
778 /* if already used, try again */
779 addr
= g_hash_table_lookup (share
->priv
->session_ids
, GUINT_TO_POINTER (id
));
780 } while (addr
!= NULL
);
782 /* store session id and remote address */
783 remote_address
= g_strdup (soup_server_context_get_client_host (context
));
784 g_hash_table_insert (share
->priv
->session_ids
, GUINT_TO_POINTER (id
), remote_address
);
790 session_id_remove (RBDAAPShare
*share
,
791 SoupServerContext
*context
,
794 g_hash_table_remove (share
->priv
->session_ids
, GUINT_TO_POINTER (id
));
798 login_cb (RBDAAPShare
*share
,
799 SoupServerContext
*context
,
800 SoupMessage
*message
)
802 /* MLOG login response
809 session_id
= session_id_create (share
, context
);
811 rb_debug ("Handling login session id %u", session_id
);
813 mlog
= rb_daap_structure_add (NULL
, RB_DAAP_CC_MLOG
);
814 rb_daap_structure_add (mlog
, RB_DAAP_CC_MSTT
, (gint32
) DMAP_STATUS_OK
);
815 rb_daap_structure_add (mlog
, RB_DAAP_CC_MLID
, session_id
);
817 message_set_from_rb_daap_structure (message
, mlog
);
818 rb_daap_structure_destroy (mlog
);
822 logout_cb (RBDAAPShare
*share
,
823 SoupServerContext
*context
,
824 SoupMessage
*message
)
829 if (session_id_validate (share
, context
, message
, &id
)) {
830 rb_debug ("Handling logout session id %u", id
);
831 session_id_remove (share
, context
, id
);
833 status
= SOUP_STATUS_NO_CONTENT
;
835 status
= SOUP_STATUS_FORBIDDEN
;
838 soup_message_set_status (message
, status
);
839 soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (message
), SOUP_TRANSFER_CONTENT_LENGTH
);
843 update_cb (RBDAAPShare
*share
,
844 SoupServerContext
*context
,
845 SoupMessage
*message
)
847 guint revision_number
;
850 res
= message_get_revision_number (message
, &revision_number
);
852 if (res
&& revision_number
!= share
->priv
->revision_number
) {
853 /* MUPD update response
855 * MUSR server revision
859 mupd
= rb_daap_structure_add (NULL
, RB_DAAP_CC_MUPD
);
860 rb_daap_structure_add (mupd
, RB_DAAP_CC_MSTT
, (gint32
) DMAP_STATUS_OK
);
861 rb_daap_structure_add (mupd
, RB_DAAP_CC_MUSR
, (gint32
) share
->priv
->revision_number
);
863 message_set_from_rb_daap_structure (message
, mupd
);
864 rb_daap_structure_destroy (mupd
);
866 g_object_ref (message
);
867 soup_message_io_pause (message
);
896 SONG_RELATIVE_VOLUME
,
908 struct DAAPMetaDataMap
{
913 struct DAAPMetaDataMap meta_data_map
[] = {
914 {"dmap.itemid", ITEM_ID
},
915 {"dmap.itemname", ITEM_NAME
},
916 {"dmap.itemkind", ITEM_KIND
},
917 {"dmap.persistentid", PERSISTENT_ID
},
918 {"dmap.containeritemid", CONTAINER_ITEM_ID
},
919 {"daap.songalbum", SONG_ALBUM
},
920 {"daap.songartist", SONG_ARTIST
},
921 {"daap.songbitrate", SONG_BITRATE
},
922 {"daap.songbeatsperminute", SONG_BPM
},
923 {"daap.songcomment", SONG_COMMENT
},
924 {"daap.songcompilation", SONG_COMPILATION
},
925 {"daap.songcomposer", SONG_COMPOSER
},
926 {"daap.songdatakind", SONG_DATA_KIND
},
927 {"daap.songdataurl", SONG_DATA_URL
},
928 {"daap.songdateadded", SONG_DATE_ADDED
},
929 {"daap.songdatemodified", SONG_DATE_MODIFIED
},
930 {"daap.songdescription", SONG_DESCRIPTION
},
931 {"daap.songdisabled", SONG_DISABLED
},
932 {"daap.songdisccount", SONG_DISC_COUNT
},
933 {"daap.songdiscnumber", SONG_DISC_NUMBER
},
934 {"daap.songeqpreset", SONG_EQ_PRESET
},
935 {"daap.songformat", SONG_FORMAT
},
936 {"daap.songgenre", SONG_GENRE
},
937 {"daap.songgrouping", SONG_GROUPING
},
938 {"daap.songrelativevolume", SONG_RELATIVE_VOLUME
},
939 {"daap.songsamplerate", SONG_SAMPLE_RATE
},
940 {"daap.songsize", SONG_SIZE
},
941 {"daap.songstarttime", SONG_START_TIME
},
942 {"daap.songstoptime", SONG_STOP_TIME
},
943 {"daap.songtime", SONG_TIME
},
944 {"daap.songtrackcount", SONG_TRACK_COUNT
},
945 {"daap.songtracknumber", SONG_TRACK_NUMBER
},
946 {"daap.songuserrating", SONG_USER_RATING
},
947 {"daap.songyear", SONG_YEAR
}};
949 typedef unsigned long long bitwise
;
958 client_requested (bitwise bits
,
961 return 0 != (bits
& (((bitwise
) 1) << field
));
964 #define DMAP_ITEM_KIND_AUDIO 2
965 #define DAAP_SONG_DATA_KIND_NONE 0
968 add_entry_to_mlcl (gint id
,
969 RhythmDBEntry
*entry
,
970 struct MLCL_Bits
*mb
)
974 mlit
= rb_daap_structure_add (mb
->mlcl
, RB_DAAP_CC_MLIT
);
976 if (client_requested (mb
->bits
, ITEM_KIND
))
977 rb_daap_structure_add (mlit
, RB_DAAP_CC_MIKD
, (gchar
) DMAP_ITEM_KIND_AUDIO
);
978 if (client_requested (mb
->bits
, ITEM_ID
))
979 rb_daap_structure_add (mlit
, RB_DAAP_CC_MIID
, (gint32
) id
);
980 if (client_requested (mb
->bits
, ITEM_NAME
))
981 rb_daap_structure_add (mlit
, RB_DAAP_CC_MINM
, rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_TITLE
));
982 if (client_requested (mb
->bits
, PERSISTENT_ID
))
983 rb_daap_structure_add (mlit
, RB_DAAP_CC_MPER
, (gint64
) id
);
984 if (client_requested (mb
->bits
, CONTAINER_ITEM_ID
))
985 rb_daap_structure_add (mlit
, RB_DAAP_CC_MCTI
, (gint32
) id
);
986 if (client_requested (mb
->bits
, SONG_DATA_KIND
))
987 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASDK
, (gchar
) DAAP_SONG_DATA_KIND_NONE
);
988 if (client_requested (mb
->bits
, SONG_DATA_URL
))
989 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASUL
, "");
990 if (client_requested (mb
->bits
, SONG_ALBUM
))
991 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASAL
, rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_ALBUM
));
992 if (client_requested (mb
->bits
, SONG_GROUPING
))
993 rb_daap_structure_add (mlit
, RB_DAAP_CC_AGRP
, "");
994 if (client_requested (mb
->bits
, SONG_ARTIST
))
995 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASAR
, rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_ARTIST
));
996 if (client_requested (mb
->bits
, SONG_BITRATE
)) {
997 gulong bitrate
= rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_BITRATE
);
999 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASBR
, (gint32
) bitrate
);
1001 if (client_requested (mb
->bits
, SONG_BPM
))
1002 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASBT
, (gint32
) 0);
1003 if (client_requested (mb
->bits
, SONG_COMMENT
))
1004 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASCM
, "");
1005 if (client_requested (mb
->bits
, SONG_COMPILATION
))
1006 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASCO
, (gchar
) FALSE
);
1007 if (client_requested (mb
->bits
, SONG_COMPOSER
))
1008 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASCP
, "");
1009 if (client_requested (mb
->bits
, SONG_DATE_ADDED
))
1010 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASDA
, (gint32
) rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_FIRST_SEEN
));
1011 if (client_requested (mb
->bits
, SONG_DATE_MODIFIED
))
1012 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASDM
, (gint32
) rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_MTIME
));
1013 if (client_requested (mb
->bits
, SONG_DISC_COUNT
))
1014 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASDC
, (gint32
) 0);
1015 if (client_requested (mb
->bits
, SONG_DISC_NUMBER
))
1016 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASDN
, (gint32
) rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_DISC_NUMBER
));
1017 if (client_requested (mb
->bits
, SONG_DISABLED
))
1018 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASDB
, (gchar
) FALSE
);
1019 if (client_requested (mb
->bits
, SONG_EQ_PRESET
))
1020 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASEQ
, "");
1021 if (client_requested (mb
->bits
, SONG_FORMAT
)) {
1022 const gchar
*filename
;
1025 filename
= rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_LOCATION
);
1026 ext
= strrchr (filename
, '.');
1028 /* FIXME we should use RHYTHMDB_PROP_MIMETYPE instead */
1030 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASFM
, ext
);
1033 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASFM
, ext
);
1036 if (client_requested (mb
->bits
, SONG_GENRE
))
1037 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASGN
, rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_GENRE
));
1038 if (client_requested (mb
->bits
, SONG_DESCRIPTION
))
1039 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASDT
, "");
1040 if (client_requested (mb
->bits
, SONG_RELATIVE_VOLUME
))
1041 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASRV
, 0);
1042 if (client_requested (mb
->bits
, SONG_SAMPLE_RATE
))
1043 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASSR
, 0);
1044 if (client_requested (mb
->bits
, SONG_SIZE
))
1045 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASSZ
, (gint32
) rhythmdb_entry_get_uint64 (entry
, RHYTHMDB_PROP_FILE_SIZE
));
1046 if (client_requested (mb
->bits
, SONG_START_TIME
))
1047 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASST
, 0);
1048 if (client_requested (mb
->bits
, SONG_STOP_TIME
))
1049 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASSP
, 0);
1050 if (client_requested (mb
->bits
, SONG_TIME
))
1051 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASTM
, (gint32
) (1000 * rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_DURATION
)));
1052 if (client_requested (mb
->bits
, SONG_TRACK_COUNT
))
1053 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASTC
, 0);
1054 if (client_requested (mb
->bits
, SONG_TRACK_NUMBER
))
1055 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASTN
, (gint32
) rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_TRACK_NUMBER
));
1056 if (client_requested (mb
->bits
, SONG_USER_RATING
))
1057 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASUR
, 0); /* FIXME */
1058 if (client_requested (mb
->bits
, SONG_YEAR
))
1059 rb_daap_structure_add (mlit
, RB_DAAP_CC_ASYR
, (gint32
) rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_YEAR
));
1065 add_playlist_to_mlcl (RBPlaylistID
*playlist_id
,
1068 /* MLIT listing item
1070 * MPER persistent item id
1077 RhythmDBQueryModel
*model
;
1079 g_object_get (playlist_id
->source
,
1081 "query-model", &model
,
1084 num_songs
= gtk_tree_model_iter_n_children (GTK_TREE_MODEL (model
), NULL
);
1085 g_object_unref (model
);
1087 mlit
= rb_daap_structure_add (mlcl
, RB_DAAP_CC_MLIT
);
1088 rb_daap_structure_add (mlit
, RB_DAAP_CC_MIID
, playlist_id
->id
);
1089 /* we don't have a persistant ID for playlists, unfortunately */
1090 rb_daap_structure_add (mlit
, RB_DAAP_CC_MPER
, (gint64
) playlist_id
->id
);
1091 rb_daap_structure_add (mlit
, RB_DAAP_CC_MINM
, name
);
1092 rb_daap_structure_add (mlit
, RB_DAAP_CC_MIMC
, (gint32
) num_songs
);
1100 add_playlist_entry_to_mlcl (GtkTreeModel
*model
,
1103 struct MLCL_Bits
*mb
)
1106 RhythmDBEntry
*entry
;
1109 mlit
= rb_daap_structure_add (mb
->mlcl
, RB_DAAP_CC_MLIT
);
1111 gtk_tree_model_get (model
, iter
, 0, &entry
, -1);
1113 id
= rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_ENTRY_ID
);
1115 if (client_requested (mb
->bits
, ITEM_KIND
))
1116 rb_daap_structure_add (mlit
, RB_DAAP_CC_MIKD
, (gchar
) DMAP_ITEM_KIND_AUDIO
);
1117 if (client_requested (mb
->bits
, ITEM_ID
))
1118 rb_daap_structure_add (mlit
, RB_DAAP_CC_MIID
, (gint32
) id
);
1119 if (client_requested (mb
->bits
, CONTAINER_ITEM_ID
))
1120 rb_daap_structure_add (mlit
, RB_DAAP_CC_MCTI
, (gint32
) id
);
1122 rhythmdb_entry_unref (entry
);
1128 parse_meta (const gchar
*s
)
1130 gchar
*start_of_attrs
;
1131 gchar
*end_of_attrs
;
1137 start_of_attrs
= strstr (s
, "meta=");
1138 if (start_of_attrs
== NULL
) {
1141 start_of_attrs
+= 5;
1143 end_of_attrs
= strchr (start_of_attrs
, '&');
1145 attrs
= g_strndup (start_of_attrs
, end_of_attrs
- start_of_attrs
);
1147 attrs
= g_strdup (start_of_attrs
);
1150 attrsv
= g_strsplit (attrs
,",",-1);
1152 for (i
= 0; attrsv
[i
]; i
++) {
1155 for (j
= 0; j
< G_N_ELEMENTS (meta_data_map
); j
++) {
1156 if (strcmp (meta_data_map
[j
].tag
, attrsv
[i
]) == 0) {
1157 bits
|= (((bitwise
) 1) << meta_data_map
[j
].md
);
1163 g_strfreev (attrsv
);
1169 write_next_chunk (SoupMessage
*message
, GnomeVFSHandle
*handle
)
1171 GnomeVFSFileSize read_size
;
1172 GnomeVFSResult result
;
1173 gchar
*chunk
= g_malloc (DAAP_SHARE_CHUNK_SIZE
);
1175 result
= gnome_vfs_read (handle
, chunk
, DAAP_SHARE_CHUNK_SIZE
, &read_size
);
1176 if (result
== GNOME_VFS_OK
&& read_size
> 0) {
1177 soup_message_add_chunk (message
, SOUP_BUFFER_SYSTEM_OWNED
, chunk
, read_size
);
1180 soup_message_add_final_chunk (message
);
1185 chunked_message_finished (SoupMessage
*message
, GnomeVFSHandle
*handle
)
1187 rb_debug ("finished sending chunked file");
1188 gnome_vfs_close (handle
);
1192 send_chunked_file (SoupMessage
*message
, RhythmDBEntry
*entry
, guint64 file_size
, guint64 offset
)
1194 GnomeVFSResult result
;
1195 GnomeVFSHandle
*handle
;
1196 const char *location
;
1198 location
= rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_LOCATION
);
1200 rb_debug ("sending %s chunked from offset %" G_GUINT64_FORMAT
, location
, offset
);
1201 result
= gnome_vfs_open (&handle
, location
, GNOME_VFS_OPEN_READ
);
1202 if (result
!= GNOME_VFS_OK
) {
1203 soup_message_set_status (message
, SOUP_STATUS_INTERNAL_SERVER_ERROR
);
1208 result
= gnome_vfs_seek (handle
, GNOME_VFS_SEEK_START
, offset
);
1209 if (result
!= GNOME_VFS_OK
) {
1210 g_warning ("Error seeking: %s", gnome_vfs_result_to_string (result
));
1211 gnome_vfs_close (handle
);
1212 soup_message_set_status (message
, SOUP_STATUS_INTERNAL_SERVER_ERROR
);
1215 file_size
-= offset
;
1218 soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (message
), SOUP_TRANSFER_CHUNKED
);
1220 g_signal_connect (message
, "wrote_chunk", G_CALLBACK (write_next_chunk
), handle
);
1221 g_signal_connect (message
, "finished", G_CALLBACK (chunked_message_finished
), handle
);
1222 write_next_chunk (message
, handle
);
1225 #ifdef HAVE_G_MAPPED_FILE
1227 mapped_file_message_finished (SoupMessage
*message
, GMappedFile
*file
)
1229 rb_debug ("finished sending mmapped file");
1230 g_mapped_file_free (file
);
1234 send_mapped_file (SoupMessage
*message
, RhythmDBEntry
*entry
, guint64 file_size
, guint64 offset
)
1236 GMappedFile
*mapped_file
;
1238 GError
*error
= NULL
;
1240 path
= gnome_vfs_get_local_path_from_uri (rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_LOCATION
));
1241 rb_debug ("sending file %s mmapped, from offset %" G_GUINT64_FORMAT
, path
, offset
);
1243 mapped_file
= g_mapped_file_new (path
, FALSE
, &error
);
1244 if (mapped_file
== NULL
) {
1245 g_warning ("Unable to map file %s: %s", path
, error
->message
);
1246 soup_message_set_status (message
, SOUP_STATUS_INTERNAL_SERVER_ERROR
);
1248 message
->response
.owner
= SOUP_BUFFER_USER_OWNED
;
1249 message
->response
.length
= file_size
;
1250 message
->response
.body
= g_mapped_file_get_contents (mapped_file
) + offset
;
1251 soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (message
),
1252 SOUP_TRANSFER_CONTENT_LENGTH
);
1254 g_signal_connect (message
,
1256 G_CALLBACK (mapped_file_message_finished
),
1264 databases_cb (RBDAAPShare
*share
,
1265 SoupServerContext
*context
,
1266 SoupMessage
*message
)
1269 gchar
*rest_of_path
;
1270 /*guint revision_number;*/
1272 if (! session_id_validate (share
, context
, message
, NULL
)) {
1273 soup_message_set_status (message
, SOUP_STATUS_FORBIDDEN
);
1274 soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (message
), SOUP_TRANSFER_CONTENT_LENGTH
);
1278 path
= soup_uri_to_string (soup_message_get_uri (message
), TRUE
);
1280 rest_of_path
= strchr (path
+ 1, '/');
1282 if (rest_of_path
== NULL
) {
1283 /* AVDB server databases
1286 * MTCO specified total count
1287 * MRCO returned count
1291 * MPER persistent id
1294 * MCTC container count
1300 avdb
= rb_daap_structure_add (NULL
, RB_DAAP_CC_AVDB
);
1301 rb_daap_structure_add (avdb
, RB_DAAP_CC_MSTT
, (gint32
) DMAP_STATUS_OK
);
1302 rb_daap_structure_add (avdb
, RB_DAAP_CC_MUTY
, 0);
1303 rb_daap_structure_add (avdb
, RB_DAAP_CC_MTCO
, (gint32
) 1);
1304 rb_daap_structure_add (avdb
, RB_DAAP_CC_MRCO
, (gint32
) 1);
1305 mlcl
= rb_daap_structure_add (avdb
, RB_DAAP_CC_MLCL
);
1306 mlit
= rb_daap_structure_add (mlcl
, RB_DAAP_CC_MLIT
);
1307 rb_daap_structure_add (mlit
, RB_DAAP_CC_MIID
, (gint32
) 1);
1308 rb_daap_structure_add (mlit
, RB_DAAP_CC_MPER
, (gint64
) 1);
1309 rb_daap_structure_add (mlit
, RB_DAAP_CC_MINM
, share
->priv
->name
);
1310 rb_daap_structure_add (mlit
, RB_DAAP_CC_MIMC
, (gint32
) g_hash_table_size (share
->priv
->id_to_entry
));
1311 rb_daap_structure_add (mlit
, RB_DAAP_CC_MCTC
, (gint32
) 1);
1313 message_set_from_rb_daap_structure (message
, avdb
);
1314 rb_daap_structure_destroy (avdb
);
1315 } else if (g_ascii_strncasecmp ("/1/items?", rest_of_path
, 9) == 0) {
1316 /* ADBS database songs
1319 * MTCO specified total count
1320 * MRCO returned count
1328 gint32 num_songs
= (gint32
)g_hash_table_size (share
->priv
->id_to_entry
);
1329 struct MLCL_Bits mb
= {NULL
,0};
1331 mb
.bits
= parse_meta (rest_of_path
);
1333 adbs
= rb_daap_structure_add (NULL
, RB_DAAP_CC_ADBS
);
1334 rb_daap_structure_add (adbs
, RB_DAAP_CC_MSTT
, (gint32
) DMAP_STATUS_OK
);
1335 rb_daap_structure_add (adbs
, RB_DAAP_CC_MUTY
, 0);
1336 rb_daap_structure_add (adbs
, RB_DAAP_CC_MTCO
, (gint32
) num_songs
);
1337 rb_daap_structure_add (adbs
, RB_DAAP_CC_MRCO
, (gint32
) num_songs
);
1338 mb
.mlcl
= rb_daap_structure_add (adbs
, RB_DAAP_CC_MLCL
);
1340 g_hash_table_foreach (share
->priv
->id_to_entry
, (GHFunc
) add_entry_to_mlcl
, &mb
);
1342 message_set_from_rb_daap_structure (message
, adbs
);
1343 rb_daap_structure_destroy (adbs
);
1345 } else if (g_ascii_strncasecmp ("/1/containers?", rest_of_path
, 14) == 0) {
1346 /* APLY database playlists
1349 * MTCO specified total count
1350 * MRCO returned count
1354 * MPER persistent item id
1357 * ABPL baseplaylist (only for base)
1365 aply
= rb_daap_structure_add (NULL
, RB_DAAP_CC_APLY
);
1366 rb_daap_structure_add (aply
, RB_DAAP_CC_MSTT
, (gint32
) DMAP_STATUS_OK
);
1367 rb_daap_structure_add (aply
, RB_DAAP_CC_MUTY
, 0);
1368 rb_daap_structure_add (aply
, RB_DAAP_CC_MTCO
, (gint32
) 1);
1369 rb_daap_structure_add (aply
, RB_DAAP_CC_MRCO
, (gint32
) 1);
1370 mlcl
= rb_daap_structure_add (aply
, RB_DAAP_CC_MLCL
);
1371 mlit
= rb_daap_structure_add (mlcl
, RB_DAAP_CC_MLIT
);
1372 rb_daap_structure_add (mlit
, RB_DAAP_CC_MIID
, (gint32
) 1);
1373 rb_daap_structure_add (mlit
, RB_DAAP_CC_MPER
, (gint64
) 1);
1374 rb_daap_structure_add (mlit
, RB_DAAP_CC_MINM
, share
->priv
->name
);
1375 rb_daap_structure_add (mlit
, RB_DAAP_CC_MIMC
, (gint32
) g_hash_table_size (share
->priv
->id_to_entry
));
1376 rb_daap_structure_add (mlit
, RB_DAAP_CC_ABPL
, (gchar
) 1); /* base playlist */
1378 g_list_foreach (share
->priv
->playlist_ids
, (GFunc
) add_playlist_to_mlcl
, mlcl
);
1380 message_set_from_rb_daap_structure (message
, aply
);
1381 rb_daap_structure_destroy (aply
);
1382 } else if (g_ascii_strncasecmp ("/1/containers/", rest_of_path
, 14) == 0) {
1383 /* APSO playlist songs
1386 * MTCO specified total count
1387 * MRCO returned count
1392 * MCTI container item id
1397 struct MLCL_Bits mb
= {NULL
,0};
1398 gint pl_id
= atoi (rest_of_path
+ 14);
1400 mb
.bits
= parse_meta (rest_of_path
);
1402 apso
= rb_daap_structure_add (NULL
, RB_DAAP_CC_APSO
);
1403 rb_daap_structure_add (apso
, RB_DAAP_CC_MSTT
, (gint32
) DMAP_STATUS_OK
);
1404 rb_daap_structure_add (apso
, RB_DAAP_CC_MUTY
, 0);
1407 gint32 num_songs
= (gint32
) g_hash_table_size (share
->priv
->id_to_entry
);
1408 rb_daap_structure_add (apso
, RB_DAAP_CC_MTCO
, (gint32
) num_songs
);
1409 rb_daap_structure_add (apso
, RB_DAAP_CC_MRCO
, (gint32
) num_songs
);
1410 mb
.mlcl
= rb_daap_structure_add (apso
, RB_DAAP_CC_MLCL
);
1412 g_hash_table_foreach (share
->priv
->id_to_entry
, (GHFunc
) add_entry_to_mlcl
, &mb
);
1417 RhythmDBQueryModel
*model
;
1419 idl
= g_list_find_custom (share
->priv
->playlist_ids
,
1420 GINT_TO_POINTER (pl_id
),
1423 soup_message_set_status (message
, SOUP_STATUS_NOT_FOUND
);
1424 soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (message
),
1425 SOUP_TRANSFER_CONTENT_LENGTH
);
1426 soup_message_set_response (message
, "text/plain", SOUP_BUFFER_USER_OWNED
, "", 0);
1429 id
= (RBPlaylistID
*)idl
->data
;
1431 mb
.mlcl
= rb_daap_structure_add (apso
, RB_DAAP_CC_MLCL
);
1433 g_object_get (id
->source
, "query-model", &model
, NULL
);
1434 num_songs
= gtk_tree_model_iter_n_children (GTK_TREE_MODEL (model
), NULL
);
1436 rb_daap_structure_add (apso
, RB_DAAP_CC_MTCO
, (gint32
) num_songs
);
1437 rb_daap_structure_add (apso
, RB_DAAP_CC_MRCO
, (gint32
) num_songs
);
1439 gtk_tree_model_foreach (GTK_TREE_MODEL (model
), (GtkTreeModelForeachFunc
) add_playlist_entry_to_mlcl
, &mb
);
1440 g_object_unref (model
);
1443 message_set_from_rb_daap_structure (message
, apso
);
1444 rb_daap_structure_destroy (apso
);
1445 } else if (g_ascii_strncasecmp ("/1/items/", rest_of_path
, 9) == 0) {
1446 /* just the file :) */
1449 RhythmDBEntry
*entry
;
1450 const gchar
*location
;
1451 const gchar
*range_header
;
1455 id_str
= rest_of_path
+ 9;
1458 entry
= g_hash_table_lookup (share
->priv
->id_to_entry
, GINT_TO_POINTER (id
));
1459 location
= rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_LOCATION
);
1460 file_size
= rhythmdb_entry_get_uint64 (entry
, RHYTHMDB_PROP_FILE_SIZE
);
1462 message_add_standard_headers (message
);
1463 soup_message_add_header (message
->response_headers
, "Accept-Ranges", "bytes");
1465 range_header
= soup_message_get_header (message
->request_headers
, "Range");
1468 gchar
*content_range
;
1470 s
= range_header
+ 6; /* bytes= */
1473 content_range
= g_strdup_printf ("bytes %" G_GUINT64_FORMAT
"-%" G_GUINT64_FORMAT
"/%" G_GUINT64_FORMAT
, offset
, file_size
, file_size
);
1474 soup_message_add_header (message
->response_headers
, "Content-Range", content_range
);
1475 g_free (content_range
);
1477 soup_message_set_status (message
, SOUP_STATUS_PARTIAL_CONTENT
);
1478 file_size
-= offset
;
1480 soup_message_set_status (message
, SOUP_STATUS_OK
);
1483 #ifdef HAVE_G_MAPPED_FILE
1484 /* don't use chunked transfers if we can send the file mmapped,
1485 * as itunes clients can't seek properly when we do.
1487 if (rb_uri_is_local (location
)) {
1488 send_mapped_file (message
, entry
, file_size
, offset
);
1492 send_chunked_file (message
, entry
, file_size
, offset
);
1495 rb_debug ("unhandled: %s\n", path
);
1502 typedef void (* DAAPPathFunction
) (RBDAAPShare
*share
,
1503 SoupServerContext
*context
,
1504 SoupMessage
*message
);
1509 DAAPPathFunction function
;
1512 static const struct DAAPPath paths_to_functions
[] = {
1513 {"/server-info", 12, server_info_cb
},
1514 {"/content-codes", 14, content_codes_cb
},
1515 {"/login", 6, login_cb
},
1516 {"/logout", 7, logout_cb
},
1517 {"/update", 7, update_cb
},
1518 {"/databases", 10, databases_cb
}
1522 server_cb (SoupServerContext
*context
,
1523 SoupMessage
*message
,
1529 path
= soup_uri_to_string (soup_message_get_uri (message
), TRUE
);
1530 rb_debug ("request for %s", path
);
1532 for (i
= 0; i
< G_N_ELEMENTS (paths_to_functions
); i
++) {
1533 if (g_ascii_strncasecmp (paths_to_functions
[i
].path
, path
, paths_to_functions
[i
].path_length
) == 0) {
1534 paths_to_functions
[i
].function (share
, context
, message
);
1539 g_warning ("unhandled path %s\n", path
);
1545 db_entry_added_cb (RhythmDB
*db
,
1546 RhythmDBEntry
*entry
,
1549 RhythmDBEntryType type
= rhythmdb_entry_get_entry_type (entry
);
1550 gboolean hidden
= rhythmdb_entry_get_boolean (entry
, RHYTHMDB_PROP_HIDDEN
);
1551 gulong id
= rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_ENTRY_ID
);
1553 if (type
== rhythmdb_entry_song_get_type () &&
1555 g_hash_table_lookup (share
->priv
->id_to_entry
, GINT_TO_POINTER (id
)) == NULL
) {
1556 g_hash_table_insert (share
->priv
->id_to_entry
, GINT_TO_POINTER (id
), entry
);
1561 add_db_entry (RhythmDBEntry
*entry
,
1564 db_entry_added_cb (share
->priv
->db
, entry
, share
);
1568 db_entry_deleted_cb (RhythmDB
*db
,
1569 RhythmDBEntry
*entry
,
1574 id
= GINT_TO_POINTER (rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_ENTRY_ID
));
1576 g_hash_table_remove (share
->priv
->id_to_entry
, id
);
1580 db_entry_changed_cb (RhythmDB
*db
,
1581 RhythmDBEntry
*entry
,
1585 if (rhythmdb_entry_get_boolean (entry
, RHYTHMDB_PROP_HIDDEN
)) {
1586 db_entry_deleted_cb (db
, entry
, share
);
1588 db_entry_added_cb (db
, entry
, share
);
1593 soup_auth_callback (SoupServerAuthContext
*auth_ctx
,
1594 SoupServerAuth
*auth
,
1595 SoupMessage
*message
,
1598 const char *username
;
1602 path
= soup_uri_to_string (soup_message_get_uri (message
), TRUE
);
1603 rb_debug ("Auth request for %s", path
);
1607 /* This is to workaround libsoup looking up handlers by directory.
1608 We require auth for "/databases?" but not "/databases/" */
1609 if (g_str_has_prefix (path
, "/databases/")) {
1614 rb_debug ("Auth DENIED: information not provided");
1619 username
= soup_server_auth_get_user (auth
);
1620 rb_debug ("Auth request for user: %s", username
);
1622 allowed
= soup_server_auth_check_passwd (auth
, share
->priv
->password
);
1623 rb_debug ("Auth request: %s", allowed
? "ALLOWED" : "DENIED");
1632 rb_daap_share_server_start (RBDAAPShare
*share
)
1634 int port
= STANDARD_DAAP_PORT
;
1635 gboolean password_required
;
1636 SoupServerAuthContext auth_ctx
= { 0 };
1638 share
->priv
->server
= soup_server_new (SOUP_SERVER_PORT
, port
, NULL
);
1639 if (share
->priv
->server
== NULL
) {
1640 rb_debug ("Unable to start music sharing server on port %d, trying any open port", port
);
1641 share
->priv
->server
= soup_server_new (SOUP_SERVER_PORT
, SOUP_ADDRESS_ANY_PORT
, NULL
);
1643 if (share
->priv
->server
== NULL
) {
1644 g_warning ("Unable to start music sharing server");
1649 share
->priv
->port
= (guint
)soup_server_get_port (share
->priv
->server
);
1650 rb_debug ("Started DAAP server on port %u", share
->priv
->port
);
1652 password_required
= (share
->priv
->auth_method
!= RB_DAAP_SHARE_AUTH_METHOD_NONE
);
1654 if (password_required
) {
1655 auth_ctx
.types
= SOUP_AUTH_TYPE_BASIC
;
1656 auth_ctx
.callback
= (SoupServerAuthCallbackFn
)soup_auth_callback
;
1657 auth_ctx
.user_data
= share
;
1658 auth_ctx
.basic_info
.realm
= "Music Sharing";
1660 soup_server_add_handler (share
->priv
->server
,
1663 (SoupServerCallbackFn
)server_cb
,
1666 soup_server_add_handler (share
->priv
->server
,
1669 (SoupServerCallbackFn
)server_cb
,
1672 soup_server_add_handler (share
->priv
->server
,
1675 (SoupServerCallbackFn
)server_cb
,
1680 soup_server_add_handler (share
->priv
->server
,
1683 (SoupServerCallbackFn
)server_cb
,
1686 soup_server_run_async (share
->priv
->server
);
1688 share
->priv
->id_to_entry
= g_hash_table_new (NULL
, NULL
);
1689 /* using direct since there is no g_uint_hash or g_uint_equal */
1690 share
->priv
->session_ids
= g_hash_table_new_full (g_direct_hash
, g_direct_equal
, NULL
, g_free
);
1692 share
->priv
->next_playlist_id
= 2; /* 1 already used */
1694 rhythmdb_entry_foreach (share
->priv
->db
, (GFunc
)add_db_entry
, share
);
1696 share
->priv
->entry_added_id
= g_signal_connect (G_OBJECT (share
->priv
->db
),
1698 G_CALLBACK (db_entry_added_cb
),
1700 share
->priv
->entry_deleted_id
= g_signal_connect (G_OBJECT (share
->priv
->db
),
1702 G_CALLBACK (db_entry_deleted_cb
),
1704 share
->priv
->entry_changed_id
= g_signal_connect (G_OBJECT (share
->priv
->db
),
1706 G_CALLBACK (db_entry_changed_cb
),
1709 share
->priv
->server_active
= TRUE
;
1715 rb_daap_share_server_stop (RBDAAPShare
*share
)
1717 rb_debug ("Stopping music sharing server on port %d", share
->priv
->port
);
1719 if (share
->priv
->server
) {
1720 soup_server_quit (share
->priv
->server
);
1721 g_object_unref (share
->priv
->server
);
1722 share
->priv
->server
= NULL
;
1725 if (share
->priv
->id_to_entry
) {
1726 g_hash_table_destroy (share
->priv
->id_to_entry
);
1727 share
->priv
->id_to_entry
= NULL
;
1730 if (share
->priv
->session_ids
) {
1731 g_hash_table_destroy (share
->priv
->session_ids
);
1732 share
->priv
->session_ids
= NULL
;
1735 if (share
->priv
->entry_added_id
!= 0) {
1736 g_signal_handler_disconnect (share
->priv
->db
, share
->priv
->entry_added_id
);
1737 share
->priv
->entry_added_id
= 0;
1740 if (share
->priv
->entry_deleted_id
!= 0) {
1741 g_signal_handler_disconnect (share
->priv
->db
, share
->priv
->entry_deleted_id
);
1742 share
->priv
->entry_deleted_id
= 0;
1745 if (share
->priv
->entry_changed_id
!= 0) {
1746 g_signal_handler_disconnect (share
->priv
->db
, share
->priv
->entry_changed_id
);
1747 share
->priv
->entry_changed_id
= 0;
1750 share
->priv
->server_active
= FALSE
;
1756 rb_daap_share_publish_start (RBDAAPShare
*share
)
1760 gboolean password_required
;
1762 password_required
= (share
->priv
->auth_method
!= RB_DAAP_SHARE_AUTH_METHOD_NONE
);
1765 res
= rb_daap_mdns_publisher_publish (share
->priv
->publisher
,
1772 if (error
!= NULL
) {
1773 g_warning ("Unable to notify network of music sharing: %s", error
->message
);
1774 g_error_free (error
);
1776 g_warning ("Unable to notify network of music sharing");
1780 rb_debug ("Published DAAP server information to mdns");
1787 rb_daap_share_publish_stop (RBDAAPShare
*share
)
1789 if (share
->priv
->publisher
) {
1793 res
= rb_daap_mdns_publisher_withdraw (share
->priv
->publisher
, &error
);
1794 if (error
!= NULL
) {
1795 g_warning ("Unable to withdraw music sharing service: %s", error
->message
);
1796 g_error_free (error
);
1801 share
->priv
->published
= FALSE
;
1806 rb_daap_share_restart (RBDAAPShare
*share
)
1810 rb_daap_share_server_stop (share
);
1811 res
= rb_daap_share_server_start (share
);
1813 /* To update information just publish again */
1814 rb_daap_share_publish_start (share
);
1816 rb_daap_share_publish_stop (share
);
1821 rb_daap_share_maybe_restart (RBDAAPShare
*share
)
1823 if (share
->priv
->published
) {
1824 rb_daap_share_restart (share
);