Updated Finnish translation
[rhythmbox.git] / daapsharing / rb-daap-share.c
blob631e1110ad4695f7ffd2cc427d45da49768e35e0
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.
23 #include "config.h"
25 #include <time.h>
26 #include <string.h>
28 #include <glib/gi18n.h>
29 #include <gtk/gtk.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"
45 #include "rb-debug.h"
46 #include "eel-gconf-extensions.h"
47 #include "rb-file-helpers.h"
49 static void rb_daap_share_set_property (GObject *object,
50 guint prop_id,
51 const GValue *value,
52 GParamSpec *pspec);
53 static void rb_daap_share_get_property (GObject *object,
54 guint prop_id,
55 GValue *value,
56 GParamSpec *pspec);
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,
64 RBSource *playlist,
65 RBDAAPShare *share);
66 static void rb_daap_share_process_playlist (RBSource *playlist,
67 RBDAAPShare *share);
68 static void rb_daap_share_playlist_destroyed (RBDAAPShare *share,
69 RBSource *source);
70 static void rb_daap_share_forget_playlist (gpointer data,
71 RBDAAPShare *share);
73 #define STANDARD_DAAP_PORT 3689
75 /* HTTP chunk size used to send files to clients */
76 #define DAAP_SHARE_CHUNK_SIZE 16384
78 typedef enum {
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 {
85 gchar *name;
86 guint port;
87 char *password;
88 RBDAAPShareAuthMethod auth_method;
90 /* mdns/dns-sd publishing things */
91 gboolean server_active;
92 gboolean published;
93 RBDaapMdnsPublisher *publisher;
95 /* http server things */
96 SoupServer *server;
97 guint revision_number;
99 GHashTable *session_ids;
101 /* db things */
102 RhythmDB *db;
103 gint32 next_song_id;
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))
117 typedef struct {
118 RBSource *source;
119 gint32 id;
120 } RBPlaylistID;
122 enum {
123 PROP_0,
124 PROP_NAME,
125 PROP_PASSWORD,
126 PROP_DB,
127 PROP_PLAYLIST_MANAGER
130 G_DEFINE_TYPE (RBDAAPShare, rb_daap_share, G_TYPE_OBJECT)
132 static void
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,
142 PROP_NAME,
143 g_param_spec_string ("name",
144 "Name",
145 "Share Name",
146 NULL,
147 G_PARAM_READWRITE));
148 g_object_class_install_property (object_class,
149 PROP_PASSWORD,
150 g_param_spec_string ("password",
151 "Authentication password",
152 "Authentication password",
153 NULL,
154 G_PARAM_READWRITE));
155 g_object_class_install_property (object_class,
156 PROP_DB,
157 g_param_spec_object ("db",
158 "RhythmDB",
159 "RhythmDB object",
160 RHYTHMDB_TYPE,
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",
165 "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));
173 static void
174 rb_daap_share_set_name (RBDAAPShare *share,
175 const char *name)
177 GError *error;
178 gboolean res;
180 g_return_if_fail (share != NULL);
182 g_free (share->priv->name);
183 share->priv->name = g_strdup (name);
185 error = NULL;
186 res = rb_daap_mdns_publisher_set_name (share->priv->publisher, name, &error);
187 if (error != NULL) {
188 g_warning ("Unable to change MDNS service name: %s", error->message);
189 g_error_free (error);
193 static void
194 published_cb (RBDaapMdnsPublisher *publisher,
195 const char *name,
196 RBDAAPShare *share)
198 if (share->priv->name == NULL || name == NULL) {
199 return;
202 if (strcmp (name, share->priv->name) == 0) {
203 rb_debug ("mDNS publish successful");
204 share->priv->published = TRUE;
208 static void
209 name_collision_cb (RBDaapMdnsPublisher *publisher,
210 const char *name,
211 RBDAAPShare *share)
213 char *new_name;
215 if (share->priv->name == NULL || name == NULL) {
216 return;
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);
227 g_free (new_name);
230 return;
233 static void
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,
243 "published",
244 G_CALLBACK (published_cb),
245 share, 0);
246 g_signal_connect_object (share->priv->publisher,
247 "name-collision",
248 G_CALLBACK (name_collision_cb),
249 share, 0);
253 static void
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) {
261 return;
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;
268 } else {
269 share->priv->auth_method = RB_DAAP_SHARE_AUTH_METHOD_NONE;
272 rb_daap_share_maybe_restart (share);
275 static void
276 rb_daap_share_set_db (RBDAAPShare *share,
277 RhythmDB *db)
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);
290 static void
291 rb_daap_share_set_playlist_manager (RBDAAPShare *share,
292 RBPlaylistManager *playlist_manager)
294 GList *playlists;
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),
302 share);
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),
311 "playlist_added",
312 G_CALLBACK (rb_daap_share_playlist_created),
313 share, 0);
315 /* Currently, there are no playlists when this object is created, but in
316 * case it changes..
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);
324 static void
325 rb_daap_share_set_property (GObject *object,
326 guint prop_id,
327 const GValue *value,
328 GParamSpec *pspec)
330 RBDAAPShare *share = RB_DAAP_SHARE (object);
332 switch (prop_id) {
333 case PROP_NAME:
334 rb_daap_share_set_name (share, g_value_get_string (value));
335 break;
336 case PROP_PASSWORD:
337 rb_daap_share_set_password (share, g_value_get_string (value));
338 break;
339 case PROP_DB:
340 rb_daap_share_set_db (share, g_value_get_object (value));
341 break;
342 case PROP_PLAYLIST_MANAGER:
343 rb_daap_share_set_playlist_manager (share, g_value_get_object (value));
344 break;
345 default:
346 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
347 break;
351 static void
352 rb_daap_share_get_property (GObject *object,
353 guint prop_id,
354 GValue *value,
355 GParamSpec *pspec)
357 RBDAAPShare *share = RB_DAAP_SHARE (object);
359 switch (prop_id) {
360 case PROP_NAME:
361 g_value_set_string (value, share->priv->name);
362 break;
363 case PROP_PASSWORD:
364 g_value_set_string (value, share->priv->password);
365 break;
366 case PROP_DB:
367 g_value_set_object (value, share->priv->db);
368 break;
369 case PROP_PLAYLIST_MANAGER:
370 g_value_set_object (value, share->priv->playlist_manager);
371 break;
372 default:
373 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
374 break;
378 static gint
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);
386 static gint
387 _find_by_source (gconstpointer a, gconstpointer b)
389 RBPlaylistID *ai = (RBPlaylistID *)a;
390 RBSource *bs = (RBSource *)b;
391 return (ai->source - bs);
394 static void
395 rb_daap_share_playlist_created (RBPlaylistManager *manager,
396 RBSource *source,
397 RBDAAPShare *share)
399 rb_daap_share_process_playlist (source, share);
402 static void
403 rb_daap_share_process_playlist (RBSource *source,
404 RBDAAPShare *share)
406 RBPlaylistID *id;
408 /* make sure we're not going insane.. */
409 g_assert (g_list_find_custom (share->priv->playlist_ids,
410 source,
411 _find_by_source) == NULL);
413 g_object_weak_ref (G_OBJECT (source),
414 (GWeakNotify) rb_daap_share_playlist_destroyed,
415 share);
416 id = g_new0 (RBPlaylistID, 1);
417 id->source = source;
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 */
424 static void
425 rb_daap_share_playlist_destroyed (RBDAAPShare *share,
426 RBSource *source)
428 GList *id;
430 id = g_list_find_custom (share->priv->playlist_ids, source, _find_by_source);
431 if (!id)
432 return;
434 share->priv->playlist_ids = g_list_remove_link (share->priv->playlist_ids, id);
435 g_free (id->data);
436 g_list_free_1 (id);
439 static void
440 rb_daap_share_forget_playlist (gpointer data,
441 RBDAAPShare *share)
443 RBPlaylistID *id = (RBPlaylistID *)data;
444 g_object_weak_unref (G_OBJECT (id->source),
445 (GWeakNotify) rb_daap_share_playlist_destroyed,
446 share);
449 static void
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);
476 RBDAAPShare *
477 rb_daap_share_new (const char *name,
478 const char *password,
479 RhythmDB *db,
480 RBPlaylistManager *playlist_manager)
482 RBDAAPShare *share;
484 share = RB_DAAP_SHARE (g_object_new (RB_TYPE_DAAP_SHARE,
485 "name", name,
486 "password", password,
487 "db", db,
488 "playlist-manager", playlist_manager,
489 NULL));
491 rb_daap_share_server_start (share);
492 rb_daap_share_publish_start (share);
494 return share;
497 static void
498 message_add_standard_headers (SoupMessage *message)
500 gchar *s;
501 time_t t;
502 struct tm *tm;
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");
508 t = time (NULL);
509 tm = gmtime (&t);
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);
513 g_free (s);
516 static void
517 message_set_from_rb_daap_structure (SoupMessage *message,
518 GNode *structure)
520 gchar *resp;
521 guint length;
523 resp = rb_daap_structure_serialize (structure, &length);
525 if (resp == NULL) {
526 rb_debug ("serialize gave us null?\n");
527 return;
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
546 static void
547 server_info_cb (RBDAAPShare *share,
548 SoupServerContext *context,
549 SoupMessage *message)
551 /* MSRV server info response
552 * MSTT status
553 * MPRO dmap version
554 * APRO daap version
555 * MINM name
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
569 GNode *msrv;
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
579 * 1.0 is 1/1
580 * 2.0 is 1/2
581 * 3.0 is 2/3
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
586 * 0 is nothing
587 * 1 is name & password
588 * 2 is password only
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);
606 static void
607 content_codes_cb (RBDAAPShare *share,
608 SoupServerContext *context,
609 SoupMessage *message)
611 /* MCCR content codes response
612 * MSTT status
613 * MDCL dictionary
614 * MCNM content codes number
615 * MCNA content codes name
616 * MCTY content codes type
617 * MDCL dictionary
618 * ...
620 const RBDAAPContentCodeDefinition *defs;
621 guint num_defs = 0;
622 guint i;
623 GNode *mccr;
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++) {
631 GNode *mdcl;
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);
643 static gboolean
644 message_get_session_id (SoupMessage *message,
645 guint32 *id)
647 const SoupUri *uri;
648 char *position;
649 guint32 session_id;
651 if (id) {
652 *id = 0;
655 uri = soup_message_get_uri (message);
656 if (uri == NULL) {
657 return FALSE;
660 position = strstr (uri->query, "session-id=");
662 if (position == NULL) {
663 rb_debug ("session id not found");
664 return FALSE;
667 position += 11;
668 session_id = (guint32) strtoul (position, NULL, 10);
670 if (id) {
671 *id = session_id;
674 return TRUE;
677 static gboolean
678 message_get_revision_number (SoupMessage *message,
679 guint *number)
681 const SoupUri *uri;
682 char *position;
683 guint revision_number;
685 if (number) {
686 *number = 0;
689 uri = soup_message_get_uri (message);
690 if (uri == NULL) {
691 return FALSE;
694 position = strstr (uri->query, "revision-number=");
696 if (position == NULL) {
697 rb_debug ("client asked for an update without a revision number?!?\n");
698 return FALSE;
701 position += 16;
702 revision_number = atoi (position);
704 if (number) {
705 *number = revision_number;
708 return TRUE;
711 static gboolean
712 session_id_validate (RBDAAPShare *share,
713 SoupServerContext *context,
714 SoupMessage *message,
715 guint32 *id)
717 guint32 session_id;
718 gboolean res;
719 const char *addr;
720 const char *remote_address;
722 if (id) {
723 *id = 0;
726 res = message_get_session_id (message, &session_id);
727 if (! res) {
728 rb_debug ("Validation failed: Unable to parse session id from message");
729 return FALSE;
732 /* check hash for remote address */
733 addr = g_hash_table_lookup (share->priv->session_ids, GUINT_TO_POINTER (session_id));
734 if (addr == NULL) {
735 rb_debug ("Validation failed: Unable to lookup session id %u", session_id);
736 return FALSE;
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");
744 return FALSE;
747 if (id) {
748 *id = session_id;
751 return TRUE;
754 static guint32
755 session_id_generate (RBDAAPShare *share,
756 SoupServerContext *context)
758 guint32 id;
760 id = g_random_int ();
762 return id;
765 static guint32
766 session_id_create (RBDAAPShare *share,
767 SoupServerContext *context)
769 guint32 id;
770 const char *addr;
771 char *remote_address;
773 do {
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);
786 return id;
789 static void
790 session_id_remove (RBDAAPShare *share,
791 SoupServerContext *context,
792 guint32 id)
794 g_hash_table_remove (share->priv->session_ids, GUINT_TO_POINTER (id));
797 static void
798 login_cb (RBDAAPShare *share,
799 SoupServerContext *context,
800 SoupMessage *message)
802 /* MLOG login response
803 * MSTT status
804 * MLID session id
806 GNode *mlog;
807 guint32 session_id;
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);
821 static void
822 logout_cb (RBDAAPShare *share,
823 SoupServerContext *context,
824 SoupMessage *message)
826 int status;
827 guint32 id;
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;
834 } else {
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);
842 static void
843 update_cb (RBDAAPShare *share,
844 SoupServerContext *context,
845 SoupMessage *message)
847 guint revision_number;
848 gboolean res;
850 res = message_get_revision_number (message, &revision_number);
852 if (res && revision_number != share->priv->revision_number) {
853 /* MUPD update response
854 * MSTT status
855 * MUSR server revision
857 GNode *mupd;
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);
865 } else {
866 g_object_ref (message);
867 soup_message_io_pause (message);
871 typedef enum {
872 ITEM_ID = 0,
873 ITEM_NAME,
874 ITEM_KIND,
875 PERSISTENT_ID,
876 CONTAINER_ITEM_ID,
877 SONG_ALBUM,
878 SONG_GROUPING,
879 SONG_ARTIST,
880 SONG_BITRATE,
881 SONG_BPM,
882 SONG_COMMENT,
883 SONG_COMPILATION,
884 SONG_COMPOSER,
885 SONG_DATA_KIND,
886 SONG_DATA_URL,
887 SONG_DATE_ADDED,
888 SONG_DATE_MODIFIED,
889 SONG_DISC_COUNT,
890 SONG_DISC_NUMBER,
891 SONG_DISABLED,
892 SONG_EQ_PRESET,
893 SONG_FORMAT,
894 SONG_GENRE,
895 SONG_DESCRIPTION,
896 SONG_RELATIVE_VOLUME,
897 SONG_SAMPLE_RATE,
898 SONG_SIZE,
899 SONG_START_TIME,
900 SONG_STOP_TIME,
901 SONG_TIME,
902 SONG_TRACK_COUNT,
903 SONG_TRACK_NUMBER,
904 SONG_USER_RATING,
905 SONG_YEAR
906 } DAAPMetaData;
908 struct DAAPMetaDataMap {
909 gchar *tag;
910 DAAPMetaData md;
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;
951 struct MLCL_Bits {
952 GNode *mlcl;
953 bitwise bits;
954 gpointer pointer;
957 static gboolean
958 client_requested (bitwise bits,
959 gint field)
961 return 0 != (bits & (((bitwise) 1) << field));
964 #define DMAP_ITEM_KIND_AUDIO 2
965 #define DAAP_SONG_DATA_KIND_NONE 0
967 static void
968 add_entry_to_mlcl (gint id,
969 RhythmDBEntry *entry,
970 struct MLCL_Bits *mb)
972 GNode *mlit;
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);
998 if (bitrate != 0)
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;
1023 gchar *ext;
1025 filename = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
1026 ext = strrchr (filename, '.');
1027 if (ext == NULL) {
1028 /* FIXME we should use RHYTHMDB_PROP_MIMETYPE instead */
1029 ext = "mp3";
1030 rb_daap_structure_add (mlit, RB_DAAP_CC_ASFM, ext);
1031 } else {
1032 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));
1061 return;
1064 static void
1065 add_playlist_to_mlcl (RBPlaylistID *playlist_id,
1066 GNode *mlcl)
1068 /* MLIT listing item
1069 * MIID item id
1070 * MPER persistent item id
1071 * MINM item name
1072 * MIMC item count
1074 GNode *mlit;
1075 gchar *name;
1076 guint num_songs;
1077 RhythmDBQueryModel *model;
1079 g_object_get (playlist_id->source,
1080 "name", &name,
1081 "query-model", &model,
1082 NULL);
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);
1094 g_free (name);
1096 return;
1099 static gboolean
1100 add_playlist_entry_to_mlcl (GtkTreeModel *model,
1101 GtkTreePath *path,
1102 GtkTreeIter *iter,
1103 struct MLCL_Bits *mb)
1105 GNode *mlit;
1106 RhythmDBEntry *entry;
1107 gint id;
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);
1124 return FALSE;
1127 static bitwise
1128 parse_meta (const gchar *s)
1130 gchar *start_of_attrs;
1131 gchar *end_of_attrs;
1132 gchar *attrs;
1133 gchar **attrsv;
1134 guint i;
1135 bitwise bits = 0;
1137 start_of_attrs = strstr (s, "meta=");
1138 if (start_of_attrs == NULL) {
1139 return 0;
1141 start_of_attrs += 5;
1143 end_of_attrs = strchr (start_of_attrs, '&');
1144 if (end_of_attrs) {
1145 attrs = g_strndup (start_of_attrs, end_of_attrs - start_of_attrs);
1146 } else {
1147 attrs = g_strdup (start_of_attrs);
1150 attrsv = g_strsplit (attrs,",",-1);
1152 for (i = 0; attrsv[i]; i++) {
1153 guint j;
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);
1162 g_free (attrs);
1163 g_strfreev (attrsv);
1165 return bits;
1168 static void
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);
1178 } else {
1179 g_free (chunk);
1180 soup_message_add_final_chunk (message);
1184 static void
1185 chunked_message_finished (SoupMessage *message, GnomeVFSHandle *handle)
1187 rb_debug ("finished sending chunked file");
1188 gnome_vfs_close (handle);
1191 static void
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);
1204 return;
1207 if (offset != 0) {
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);
1213 return;
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
1226 static void
1227 mapped_file_message_finished (SoupMessage *message, GMappedFile *file)
1229 rb_debug ("finished sending mmapped file");
1230 g_mapped_file_free (file);
1233 static void
1234 send_mapped_file (SoupMessage *message, RhythmDBEntry *entry, guint64 file_size, guint64 offset)
1236 GMappedFile *mapped_file;
1237 char *path;
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);
1247 } else {
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,
1255 "finished",
1256 G_CALLBACK (mapped_file_message_finished),
1257 mapped_file);
1259 g_free (path);
1261 #endif
1263 static void
1264 databases_cb (RBDAAPShare *share,
1265 SoupServerContext *context,
1266 SoupMessage *message)
1268 gchar *path;
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);
1275 return;
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
1284 * MSTT status
1285 * MUTY update type
1286 * MTCO specified total count
1287 * MRCO returned count
1288 * MLCL listing
1289 * MLIT listing item
1290 * MIID item id
1291 * MPER persistent id
1292 * MINM item name
1293 * MIMC item count
1294 * MCTC container count
1296 GNode *avdb;
1297 GNode *mlcl;
1298 GNode *mlit;
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
1317 * MSTT status
1318 * MUTY update type
1319 * MTCO specified total count
1320 * MRCO returned count
1321 * MLCL listing
1322 * MLIT
1323 * attrs
1324 * MLIT
1325 * ...
1327 GNode *adbs;
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);
1344 adbs = NULL;
1345 } else if (g_ascii_strncasecmp ("/1/containers?", rest_of_path, 14) == 0) {
1346 /* APLY database playlists
1347 * MSTT status
1348 * MUTY update type
1349 * MTCO specified total count
1350 * MRCO returned count
1351 * MLCL listing
1352 * MLIT listing item
1353 * MIID item id
1354 * MPER persistent item id
1355 * MINM item name
1356 * MIMC item count
1357 * ABPL baseplaylist (only for base)
1358 * MLIT
1359 * ...
1361 GNode *aply;
1362 GNode *mlcl;
1363 GNode *mlit;
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
1384 * MSTT status
1385 * MUTY update type
1386 * MTCO specified total count
1387 * MRCO returned count
1388 * MLCL listing
1389 * MLIT listing item
1390 * MIKD item kind
1391 * MIID item id
1392 * MCTI container item id
1393 * MLIT
1394 * ...
1396 GNode *apso;
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);
1406 if (pl_id == 1) {
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);
1413 } else {
1414 RBPlaylistID *id;
1415 GList *idl;
1416 guint num_songs;
1417 RhythmDBQueryModel *model;
1419 idl = g_list_find_custom (share->priv->playlist_ids,
1420 GINT_TO_POINTER (pl_id),
1421 _find_by_id);
1422 if (idl == NULL) {
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);
1427 goto out;
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 :) */
1447 gchar *id_str;
1448 gint id;
1449 RhythmDBEntry *entry;
1450 const gchar *location;
1451 const gchar *range_header;
1452 guint64 file_size;
1453 guint64 offset = 0;
1455 id_str = rest_of_path + 9;
1456 id = atoi (id_str);
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");
1466 if (range_header) {
1467 const gchar *s;
1468 gchar *content_range;
1470 s = range_header + 6; /* bytes= */
1471 offset = atoll (s);
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;
1479 } else {
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);
1489 } else
1490 #endif
1492 send_chunked_file (message, entry, file_size, offset);
1494 } else {
1495 rb_debug ("unhandled: %s\n", path);
1498 out:
1499 g_free (path);
1502 typedef void (* DAAPPathFunction) (RBDAAPShare *share,
1503 SoupServerContext *context,
1504 SoupMessage *message);
1506 struct DAAPPath {
1507 const gchar *path;
1508 guint path_length;
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}
1521 static void
1522 server_cb (SoupServerContext *context,
1523 SoupMessage *message,
1524 RBDAAPShare *share)
1526 gchar *path;
1527 guint i;
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);
1535 return;
1539 g_warning ("unhandled path %s\n", path);
1541 g_free (path);
1544 static void
1545 db_entry_added_cb (RhythmDB *db,
1546 RhythmDBEntry *entry,
1547 RBDAAPShare *share)
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 () &&
1554 !hidden &&
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);
1560 static void
1561 add_db_entry (RhythmDBEntry *entry,
1562 RBDAAPShare *share)
1564 db_entry_added_cb (share->priv->db, entry, share);
1567 static void
1568 db_entry_deleted_cb (RhythmDB *db,
1569 RhythmDBEntry *entry,
1570 RBDAAPShare *share)
1572 gpointer id;
1574 id = GINT_TO_POINTER (rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_ENTRY_ID));
1575 if (id)
1576 g_hash_table_remove (share->priv->id_to_entry, id);
1579 static void
1580 db_entry_changed_cb (RhythmDB *db,
1581 RhythmDBEntry *entry,
1582 GSList *changes,
1583 RBDAAPShare *share)
1585 if (rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN)) {
1586 db_entry_deleted_cb (db, entry, share);
1587 } else {
1588 db_entry_added_cb (db, entry, share);
1592 static gboolean
1593 soup_auth_callback (SoupServerAuthContext *auth_ctx,
1594 SoupServerAuth *auth,
1595 SoupMessage *message,
1596 RBDAAPShare *share)
1598 const char *username;
1599 gboolean allowed;
1600 char *path;
1602 path = soup_uri_to_string (soup_message_get_uri (message), TRUE);
1603 rb_debug ("Auth request for %s", path);
1605 if (auth == NULL) {
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/")) {
1610 allowed = TRUE;
1611 goto done;
1614 rb_debug ("Auth DENIED: information not provided");
1615 allowed = FALSE;
1616 goto done;
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");
1625 done:
1626 g_free (path);
1628 return allowed;
1631 static gboolean
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");
1645 return FALSE;
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,
1661 "/login",
1662 &auth_ctx,
1663 (SoupServerCallbackFn)server_cb,
1664 NULL,
1665 share);
1666 soup_server_add_handler (share->priv->server,
1667 "/update",
1668 &auth_ctx,
1669 (SoupServerCallbackFn)server_cb,
1670 NULL,
1671 share);
1672 soup_server_add_handler (share->priv->server,
1673 "/databases",
1674 &auth_ctx,
1675 (SoupServerCallbackFn)server_cb,
1676 NULL,
1677 share);
1680 soup_server_add_handler (share->priv->server,
1681 NULL,
1682 NULL,
1683 (SoupServerCallbackFn)server_cb,
1684 NULL,
1685 share);
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),
1697 "entry-added",
1698 G_CALLBACK (db_entry_added_cb),
1699 share);
1700 share->priv->entry_deleted_id = g_signal_connect (G_OBJECT (share->priv->db),
1701 "entry-deleted",
1702 G_CALLBACK (db_entry_deleted_cb),
1703 share);
1704 share->priv->entry_changed_id = g_signal_connect (G_OBJECT (share->priv->db),
1705 "entry-changed",
1706 G_CALLBACK (db_entry_changed_cb),
1707 share);
1709 share->priv->server_active = TRUE;
1711 return TRUE;
1714 static gboolean
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;
1752 return TRUE;
1755 static gboolean
1756 rb_daap_share_publish_start (RBDAAPShare *share)
1758 GError *error;
1759 gboolean res;
1760 gboolean password_required;
1762 password_required = (share->priv->auth_method != RB_DAAP_SHARE_AUTH_METHOD_NONE);
1764 error = NULL;
1765 res = rb_daap_mdns_publisher_publish (share->priv->publisher,
1766 share->priv->name,
1767 share->priv->port,
1768 password_required,
1769 &error);
1771 if (res == FALSE) {
1772 if (error != NULL) {
1773 g_warning ("Unable to notify network of music sharing: %s", error->message);
1774 g_error_free (error);
1775 } else {
1776 g_warning ("Unable to notify network of music sharing");
1778 return FALSE;
1779 } else {
1780 rb_debug ("Published DAAP server information to mdns");
1783 return TRUE;
1786 static gboolean
1787 rb_daap_share_publish_stop (RBDAAPShare *share)
1789 if (share->priv->publisher) {
1790 gboolean res;
1791 GError *error;
1792 error = NULL;
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);
1798 return res;
1801 share->priv->published = FALSE;
1802 return TRUE;
1805 static void
1806 rb_daap_share_restart (RBDAAPShare *share)
1808 gboolean res;
1810 rb_daap_share_server_stop (share);
1811 res = rb_daap_share_server_start (share);
1812 if (res) {
1813 /* To update information just publish again */
1814 rb_daap_share_publish_start (share);
1815 } else {
1816 rb_daap_share_publish_stop (share);
1820 static void
1821 rb_daap_share_maybe_restart (RBDAAPShare *share)
1823 if (share->priv->published) {
1824 rb_daap_share_restart (share);