2
* Implmentation of DAAP (iTunes Music Sharing) sharing
4
* Copyright (C) 2005 Charles Schmidt <cschmidt2@emich.edu>
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; either version 2 of the License, or
9
* (at your option) any later version.
11
* This program is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program; if not, write to the Free Software
18
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
#include "rb-daap-share.h"
24
#include "rb-daap-structure.h"
25
#include "rb-daap-mdns.h"
26
#include "rb-daap-dialog.h"
28
#include "rb-playlist-source.h"
30
#include "eel-gconf-extensions.h"
32
#include <libsoup/soup.h>
33
#include <libsoup/soup-address.h>
34
#include <libsoup/soup-message.h>
35
#include <libsoup/soup-uri.h>
36
#include <libsoup/soup-server.h>
37
#include <libsoup/soup-server-message.h>
38
#include <libgnomevfs/gnome-vfs.h>
44
#include <libgnome/gnome-i18n.h>
46
static void rb_daap_share_set_property (GObject *object,
50
static void rb_daap_share_get_property (GObject *object,
54
static void rb_daap_share_dispose (GObject *object);
56
static void rb_daap_share_start_publish (RBDAAPShare *share);
57
static void rb_daap_share_stop_publish (RBDAAPShare *share);
58
static void rb_daap_share_playlist_created (RBPlaylistManager *mgr,
61
static void rb_daap_share_process_playlist (RBSource *playlist,
63
static void rb_daap_share_playlist_destroyed (RBDAAPShare *share,
65
static void rb_daap_share_forget_playlist (gpointer data,
69
#define CONF_NAME CONF_PREFIX "/sharing/share_name"
70
#define STANDARD_DAAP_PORT 3689
72
/* HTTP chunk size used to send files to clients */
73
#define DAAP_SHARE_CHUNK_SIZE 16384
75
struct RBDAAPSharePrivate {
79
/* mdns/dns-sd publishing things */
81
RBDAAPmDNSPublisher publisher;
83
/* http server things */
85
guint revision_number;
90
GHashTable *id_to_entry;
91
GHashTable *entry_to_id;
92
gulong entry_added_id;
93
gulong entry_deleted_id;
96
RBPlaylistManager *playlist_manager;
97
guint next_playlist_id;
98
GList *playlist_ids; /* contains RBPlaylistIDs */
111
PROP_PLAYLIST_MANAGER
115
G_DEFINE_TYPE (RBDAAPShare, rb_daap_share, G_TYPE_OBJECT)
119
rb_daap_share_class_init (RBDAAPShareClass *klass)
121
GObjectClass *object_class = G_OBJECT_CLASS (klass);
123
object_class->get_property = rb_daap_share_get_property;
124
object_class->set_property = rb_daap_share_set_property;
125
object_class->dispose = rb_daap_share_dispose;
127
g_object_class_install_property (object_class,
129
g_param_spec_string ("name",
134
g_object_class_install_property (object_class,
136
g_param_spec_object ("db",
140
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
141
g_object_class_install_property (object_class,
142
PROP_PLAYLIST_MANAGER,
143
g_param_spec_object ("playlist-manager",
145
"Playlist manager object",
146
RB_TYPE_PLAYLIST_MANAGER,
147
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
151
rb_daap_share_init (RBDAAPShare *share)
153
share->priv = g_new0 (RBDAAPSharePrivate, 1);
154
share->priv->revision_number = 5;
158
rb_daap_share_set_property (GObject *object,
163
RBDAAPShare *share = RB_DAAP_SHARE (object);
167
gboolean restart_publish = FALSE;
168
const char *name = g_value_get_string (value);
170
/* check if the name hasn't really changed */
171
if (share->priv->name && name &&
172
strcmp (name, share->priv->name) == 0) {
176
if (share->priv->name) {
177
g_free (share->priv->name);
179
if (share->priv->published) {
180
rb_daap_share_stop_publish (share);
181
restart_publish = TRUE;
185
share->priv->name = g_strdup (name);
187
if (restart_publish) {
188
rb_daap_share_start_publish (share);
194
share->priv->db = g_value_get_object (value);
196
case PROP_PLAYLIST_MANAGER:
199
share->priv->playlist_manager = g_value_get_object (value);
200
g_signal_connect_object (G_OBJECT (share->priv->playlist_manager),
202
G_CALLBACK (rb_daap_share_playlist_created),
205
/* Currently, there are no playlists when this object is created, but in
208
playlists = rb_playlist_manager_get_playlists (share->priv->playlist_manager);
209
g_list_foreach (playlists, (GFunc) rb_daap_share_process_playlist, share);
210
g_list_free (playlists);
214
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
220
rb_daap_share_get_property (GObject *object,
225
RBDAAPShare *share = RB_DAAP_SHARE (object);
229
g_value_set_string (value, share->priv->name);
232
g_value_set_object (value, share->priv->db);
234
case PROP_PLAYLIST_MANAGER:
235
g_value_set_object (value, share->priv->playlist_manager);
238
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
244
_find_by_id (gconstpointer a, gconstpointer b)
246
RBPlaylistID *ai = (RBPlaylistID *)a;
247
gint bv = GPOINTER_TO_INT (b);
248
return (ai->id - bv);
252
_find_by_source (gconstpointer a, gconstpointer b)
254
RBPlaylistID *ai = (RBPlaylistID *)a;
255
RBSource *bs = (RBSource *)b;
256
return (ai->source - bs);
260
rb_daap_share_playlist_created (RBPlaylistManager *manager,
264
rb_daap_share_process_playlist (source, share);
268
rb_daap_share_process_playlist (RBSource *source,
273
/* make sure we're not going insane.. */
274
g_assert (g_list_find_custom (share->priv->playlist_ids,
276
_find_by_source) == NULL);
278
g_object_weak_ref (G_OBJECT (source),
279
(GWeakNotify) rb_daap_share_playlist_destroyed,
281
id = g_new0 (RBPlaylistID, 1);
283
id->id = share->priv->next_playlist_id++;
284
share->priv->playlist_ids = g_list_append (share->priv->playlist_ids, id);
286
/* if we knew how to send updates to clients, we'd probably do something here */
290
rb_daap_share_playlist_destroyed (RBDAAPShare *share,
295
id = g_list_find_custom (share->priv->playlist_ids, source, _find_by_source);
299
share->priv->playlist_ids = g_list_remove_link (share->priv->playlist_ids, id);
305
rb_daap_share_forget_playlist (gpointer data,
308
RBPlaylistID *id = (RBPlaylistID *)data;
309
g_object_weak_unref (G_OBJECT (id->source),
310
(GWeakNotify) rb_daap_share_playlist_destroyed,
316
rb_daap_share_dispose (GObject *object)
318
RBDAAPShare *share = RB_DAAP_SHARE (object);
320
if (share->priv->published) {
321
rb_daap_share_stop_publish (share);
325
g_free (share->priv->name);
326
g_object_unref (share->priv->db);
327
g_object_unref (share->priv->playlist_manager);
329
g_list_foreach (share->priv->playlist_ids, (GFunc) rb_daap_share_forget_playlist, share);
330
g_list_foreach (share->priv->playlist_ids, (GFunc) g_free, NULL);
332
g_free (share->priv);
336
G_OBJECT_CLASS (rb_daap_share_parent_class)->dispose (object);
341
rb_daap_share_new (const gchar *name,
343
RBPlaylistManager *playlist_manager)
347
share = RB_DAAP_SHARE (g_object_new (RB_TYPE_DAAP_SHARE,
350
"playlist-manager", playlist_manager,
352
rb_daap_share_start_publish (share);
358
message_add_standard_headers (SoupMessage *message)
364
soup_message_add_header (message->response_headers, "DAAP-Server", "Rhythmbox " VERSION);
366
soup_message_add_header (message->response_headers, "Content-Type", "application/x-dmap-tagged");
370
s = g_new (gchar, 100);
371
strftime (s, 100, "%a, %d %b %Y %T GMT", tm);
372
soup_message_add_header (message->response_headers, "Date", s);
377
message_set_from_rb_daap_structure (SoupMessage *message,
383
resp = rb_daap_structure_serialize (structure, &length);
386
g_print ("serialize gave us null?\n");
390
message->response.owner = SOUP_BUFFER_SYSTEM_OWNED;
391
message->response.length = length;
392
message->response.body = resp;
394
message_add_standard_headers (message);
396
soup_message_set_status (message, SOUP_STATUS_OK);
397
soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (message), SOUP_TRANSFER_CONTENT_LENGTH);
400
#define DMAP_STATUS_OK 200
402
#define DMAP_VERSION 2.0
403
#define DAAP_VERSION 3.0
404
#define DMAP_TIMEOUT 1800
407
server_info_cb (RBDAAPShare *share,
408
SoupMessage *message)
410
/* MSRV server info response
415
* MSAU authentication method
416
* MSLR login required
417
* MSTM timeout interval
418
* MSAL supports auto logout
419
* MSUP supports update
420
* MSPI supports persistent ids
421
* MSEX supports extensions
422
* MSBR supports browse
423
* MSQY supports query
424
* MSIX supports index
425
* MSRS supports resolve
426
* MSDC databases count
430
msrv = rb_daap_structure_add (NULL, RB_DAAP_CC_MSRV);
431
rb_daap_structure_add (msrv, RB_DAAP_CC_MSTT, (gint32) DMAP_STATUS_OK);
432
rb_daap_structure_add (msrv, RB_DAAP_CC_MPRO, (gdouble) DMAP_VERSION);
433
rb_daap_structure_add (msrv, RB_DAAP_CC_APRO, (gdouble) DAAP_VERSION);
434
/* 2/3 is for itunes 4.8 (at least). its determined by the
435
* Client-DAAP-Version header sent, but if we decide not to support
436
* older versions..? anyway
442
rb_daap_structure_add (msrv, RB_DAAP_CC_MINM, share->priv->name);
443
rb_daap_structure_add (msrv, RB_DAAP_CC_MSAU, 0);
444
/* authentication method
446
* 1 is name & password
449
rb_daap_structure_add (msrv, RB_DAAP_CC_MSLR, 0);
450
rb_daap_structure_add (msrv, RB_DAAP_CC_MSTM, (gint32) DMAP_TIMEOUT);
451
rb_daap_structure_add (msrv, RB_DAAP_CC_MSAL, (gchar) 0);
452
rb_daap_structure_add (msrv, RB_DAAP_CC_MSUP, (gchar) 0);
453
rb_daap_structure_add (msrv, RB_DAAP_CC_MSPI, (gchar) 0);
454
rb_daap_structure_add (msrv, RB_DAAP_CC_MSEX, (gchar) 0);
455
rb_daap_structure_add (msrv, RB_DAAP_CC_MSBR, (gchar) 0);
456
rb_daap_structure_add (msrv, RB_DAAP_CC_MSQY, (gchar) 0);
457
rb_daap_structure_add (msrv, RB_DAAP_CC_MSIX, (gchar) 0);
458
rb_daap_structure_add (msrv, RB_DAAP_CC_MSRS, (gchar) 0);
459
rb_daap_structure_add (msrv, RB_DAAP_CC_MSDC, (gint32) 1);
461
message_set_from_rb_daap_structure (message, msrv);
462
rb_daap_structure_destroy (msrv);
466
content_codes_cb (RBDAAPShare *share,
467
SoupMessage *message)
469
/* MCCR content codes response
472
* MCNM content codes number
473
* MCNA content codes name
474
* MCTY content codes type
478
const RBDAAPContentCodeDefinition *defs;
483
defs = rb_daap_content_codes (&num_defs);
485
mccr = rb_daap_structure_add (NULL, RB_DAAP_CC_MCCR);
486
rb_daap_structure_add (mccr, RB_DAAP_CC_MSTT, (gint32) DMAP_STATUS_OK);
488
for (i = 0; i < num_defs; i++) {
491
mdcl = rb_daap_structure_add (mccr, RB_DAAP_CC_MDCL);
492
rb_daap_structure_add (mdcl, RB_DAAP_CC_MCNM, rb_daap_content_code_string_as_int32(defs[i].string));
493
rb_daap_structure_add (mdcl, RB_DAAP_CC_MCNA, defs[i].name);
494
rb_daap_structure_add (mdcl, RB_DAAP_CC_MCTY, (gint32) defs[i].type);
497
message_set_from_rb_daap_structure (message, mccr);
498
rb_daap_structure_destroy (mccr);
501
/* This is arbitrary. iTunes communicates with a session id for
502
* reasons relating to updates to the database and such, I think
503
* Since we don't do that, and since we don't keep track of connections
504
* like iTunes do, everyone can just share the same, special, arbitrary
507
#define DAAP_SESSION_ID 42
510
login_cb (RBDAAPShare *share,
511
SoupMessage *message)
513
/* MLOG login response
519
mlog = rb_daap_structure_add (NULL, RB_DAAP_CC_MLOG);
520
rb_daap_structure_add (mlog, RB_DAAP_CC_MSTT, (gint32) DMAP_STATUS_OK);
521
rb_daap_structure_add (mlog, RB_DAAP_CC_MLID, (gint32) DAAP_SESSION_ID);
523
message_set_from_rb_daap_structure (message, mlog);
524
rb_daap_structure_destroy (mlog);
528
update_cb (RBDAAPShare *share,
529
SoupMessage *message)
532
gchar *revision_number_position;
533
guint revision_number;
535
path = soup_uri_to_string (soup_message_get_uri (message), TRUE);
537
revision_number_position = strstr (path, "revision-number=");
539
if (revision_number_position == NULL) {
540
g_print ("client asked for an update without a revision number?!?\n");
545
revision_number_position += 16;
546
revision_number = atoi (revision_number_position);
550
if (revision_number != share->priv->revision_number) {
551
/* MUPD update response
553
* MUSR server revision
557
mupd = rb_daap_structure_add (NULL, RB_DAAP_CC_MUPD);
558
rb_daap_structure_add (mupd, RB_DAAP_CC_MSTT, (gint32) DMAP_STATUS_OK);
559
rb_daap_structure_add (mupd, RB_DAAP_CC_MUSR, (gint32) share->priv->revision_number);
561
message_set_from_rb_daap_structure (message, mupd);
562
rb_daap_structure_destroy (mupd);
564
g_object_ref (message);
565
soup_message_io_pause (message);
594
SONG_RELATIVE_VOLUME,
606
struct DAAPMetaDataMap {
611
struct DAAPMetaDataMap meta_data_map[] = {
612
{"dmap.itemid", ITEM_ID},
613
{"dmap.itemname", ITEM_NAME},
614
{"dmap.itemkind", ITEM_KIND},
615
{"dmap.persistentid", PERSISTENT_ID},
616
{"dmap.containeritemid", CONTAINER_ITEM_ID},
617
{"daap.songalbum", SONG_ALBUM},
618
{"daap.songartist", SONG_ARTIST},
619
{"daap.songbitrate", SONG_BITRATE},
620
{"daap.songbeatsperminute", SONG_BPM},
621
{"daap.songcomment", SONG_COMMENT},
622
{"daap.songcompilation", SONG_COMPILATION},
623
{"daap.songcomposer", SONG_COMPOSER},
624
{"daap.songdatakind", SONG_DATA_KIND},
625
{"daap.songdataurl", SONG_DATA_URL},
626
{"daap.songdateadded", SONG_DATE_ADDED},
627
{"daap.songdatemodified", SONG_DATE_MODIFIED},
628
{"daap.songdescription", SONG_DESCRIPTION},
629
{"daap.songdisabled", SONG_DISABLED},
630
{"daap.songdisccount", SONG_DISC_COUNT},
631
{"daap.songdiscnumber", SONG_DISC_NUMBER},
632
{"daap.songeqpreset", SONG_EQ_PRESET},
633
{"daap.songformat", SONG_FORMAT},
634
{"daap.songgenre", SONG_GENRE},
635
{"daap.songgrouping", SONG_GROUPING},
636
{"daap.songrelativevolume", SONG_RELATIVE_VOLUME},
637
{"daap.songsamplerate", SONG_SAMPLE_RATE},
638
{"daap.songsize", SONG_SIZE},
639
{"daap.songstarttime", SONG_START_TIME},
640
{"daap.songstoptime", SONG_STOP_TIME},
641
{"daap.songtime", SONG_TIME},
642
{"daap.songtrackcount", SONG_TRACK_COUNT},
643
{"daap.songtracknumber", SONG_TRACK_NUMBER},
644
{"daap.songuserrating", SONG_USER_RATING},
645
{"daap.songyear", SONG_YEAR}};
647
typedef unsigned long long bitwise;
656
client_requested (bitwise bits,
659
return 0 != (bits & (((bitwise) 1) << field));
662
#define DMAP_ITEM_KIND_AUDIO 2
663
#define DAAP_SONG_DATA_KIND_NONE 0
666
add_entry_to_mlcl (RhythmDBEntry *entry,
668
struct MLCL_Bits *mb)
672
mlit = rb_daap_structure_add (mb->mlcl, RB_DAAP_CC_MLIT);
674
if (client_requested (mb->bits, ITEM_KIND))
675
rb_daap_structure_add (mlit, RB_DAAP_CC_MIKD, (gchar) DMAP_ITEM_KIND_AUDIO);
676
if (client_requested (mb->bits, ITEM_ID))
677
rb_daap_structure_add (mlit, RB_DAAP_CC_MIID, (gint32) id);
678
if (client_requested (mb->bits, ITEM_NAME))
679
rb_daap_structure_add (mlit, RB_DAAP_CC_MINM, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE));
680
if (client_requested (mb->bits, PERSISTENT_ID))
681
rb_daap_structure_add (mlit, RB_DAAP_CC_MPER, (gint64) id);
682
if (client_requested (mb->bits, CONTAINER_ITEM_ID))
683
rb_daap_structure_add (mlit, RB_DAAP_CC_MCTI, (gint32) id);
684
if (client_requested (mb->bits, SONG_DATA_KIND))
685
rb_daap_structure_add (mlit, RB_DAAP_CC_ASDK, (gchar) DAAP_SONG_DATA_KIND_NONE);
686
if (client_requested (mb->bits, SONG_DATA_URL))
687
rb_daap_structure_add (mlit, RB_DAAP_CC_ASUL, "");
688
if (client_requested (mb->bits, SONG_ALBUM))
689
rb_daap_structure_add (mlit, RB_DAAP_CC_ASAL, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM));
690
if (client_requested (mb->bits, SONG_GROUPING))
691
rb_daap_structure_add (mlit, RB_DAAP_CC_AGRP, "");
692
if (client_requested (mb->bits, SONG_ARTIST))
693
rb_daap_structure_add (mlit, RB_DAAP_CC_ASAR, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST));
694
if (client_requested (mb->bits, SONG_BITRATE)) {
695
gulong bitrate = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_BITRATE);
697
if (bitrate == 0) { /* because gstreamer is stupid */
698
/* bitrate needs to be sent in kbps, kb/s
699
* a kilobit is 128 bytes
700
* if the length is L seconds,
701
* and the file is S bytes
703
* (S / 128) / L is kbps */
704
gulong length = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
705
guint64 file_size = rhythmdb_entry_get_uint64 (entry, RHYTHMDB_PROP_FILE_SIZE);
707
bitrate = (file_size / 128) / length;
710
rb_daap_structure_add (mlit, RB_DAAP_CC_ASBR, (gint32) rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_BITRATE));
712
if (client_requested (mb->bits, SONG_BPM))
713
rb_daap_structure_add (mlit, RB_DAAP_CC_ASBT, (gint32) 0);
714
if (client_requested (mb->bits, SONG_COMMENT))
715
rb_daap_structure_add (mlit, RB_DAAP_CC_ASCM, "");
716
if (client_requested (mb->bits, SONG_COMPILATION))
717
rb_daap_structure_add (mlit, RB_DAAP_CC_ASCO, (gchar) FALSE);
718
if (client_requested (mb->bits, SONG_COMPOSER))
719
rb_daap_structure_add (mlit, RB_DAAP_CC_ASCP, "");
720
if (client_requested (mb->bits, SONG_DATE_ADDED))
721
rb_daap_structure_add (mlit, RB_DAAP_CC_ASDA, (gint32) rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_FIRST_SEEN));
722
if (client_requested (mb->bits, SONG_DATE_MODIFIED))
723
rb_daap_structure_add (mlit, RB_DAAP_CC_ASDM, (gint32) rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_MTIME));
724
if (client_requested (mb->bits, SONG_DISC_COUNT))
725
rb_daap_structure_add (mlit, RB_DAAP_CC_ASDC, (gint32) 0);
726
if (client_requested (mb->bits, SONG_DISC_NUMBER))
727
rb_daap_structure_add (mlit, RB_DAAP_CC_ASDN, (gint32) rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DISC_NUMBER));
728
if (client_requested (mb->bits, SONG_DISABLED))
729
rb_daap_structure_add (mlit, RB_DAAP_CC_ASDB, (gchar) FALSE);
730
if (client_requested (mb->bits, SONG_EQ_PRESET))
731
rb_daap_structure_add (mlit, RB_DAAP_CC_ASEQ, "");
732
if (client_requested (mb->bits, SONG_FORMAT)) {
733
const gchar *filename;
736
filename = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
737
ext = strrchr (filename, '.');
739
/* FIXME we should use RHYTHMDB_PROP_MIMETYPE instead */
741
rb_daap_structure_add (mlit, RB_DAAP_CC_ASFM, ext);
744
rb_daap_structure_add (mlit, RB_DAAP_CC_ASFM, ext);
747
if (client_requested (mb->bits, SONG_GENRE))
748
rb_daap_structure_add (mlit, RB_DAAP_CC_ASGN, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_GENRE));
749
if (client_requested (mb->bits, SONG_DESCRIPTION))
750
rb_daap_structure_add (mlit, RB_DAAP_CC_ASDT, "");
751
if (client_requested (mb->bits, SONG_RELATIVE_VOLUME))
752
rb_daap_structure_add (mlit, RB_DAAP_CC_ASRV, 0);
753
if (client_requested (mb->bits, SONG_SAMPLE_RATE))
754
rb_daap_structure_add (mlit, RB_DAAP_CC_ASSR, 0);
755
if (client_requested (mb->bits, SONG_SIZE))
756
rb_daap_structure_add (mlit, RB_DAAP_CC_ASSZ, (gint32) rhythmdb_entry_get_uint64 (entry, RHYTHMDB_PROP_FILE_SIZE));
757
if (client_requested (mb->bits, SONG_START_TIME))
758
rb_daap_structure_add (mlit, RB_DAAP_CC_ASST, 0);
759
if (client_requested (mb->bits, SONG_STOP_TIME))
760
rb_daap_structure_add (mlit, RB_DAAP_CC_ASSP, 0);
761
if (client_requested (mb->bits, SONG_TIME))
762
rb_daap_structure_add (mlit, RB_DAAP_CC_ASTM, (gint32) (1000 * rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION)));
763
if (client_requested (mb->bits, SONG_TRACK_COUNT))
764
rb_daap_structure_add (mlit, RB_DAAP_CC_ASTC, 0);
765
if (client_requested (mb->bits, SONG_TRACK_NUMBER))
766
rb_daap_structure_add (mlit, RB_DAAP_CC_ASTN, (gint32) rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER));
767
if (client_requested (mb->bits, SONG_USER_RATING))
768
rb_daap_structure_add (mlit, RB_DAAP_CC_ASUR, 0); // fixme
769
if (client_requested (mb->bits, SONG_YEAR))
770
rb_daap_structure_add (mlit, RB_DAAP_CC_ASYR, 0);
776
add_playlist_to_mlcl (RBPlaylistID *playlist_id,
781
* MPER persistent item id
790
g_object_get (G_OBJECT (playlist_id->source), "name", &name, NULL);
792
ev = rb_source_get_entry_view (playlist_id->source);
793
num_songs = rb_entry_view_get_num_entries (ev);
795
mlit = rb_daap_structure_add (mlcl, RB_DAAP_CC_MLIT);
796
rb_daap_structure_add (mlit, RB_DAAP_CC_MIID, playlist_id->id);
797
/* we don't have a persistant ID for playlists, unfortunately */
798
rb_daap_structure_add (mlit, RB_DAAP_CC_MPER, (gint64) playlist_id->id);
799
rb_daap_structure_add (mlit, RB_DAAP_CC_MINM, name);
800
rb_daap_structure_add (mlit, RB_DAAP_CC_MIMC, (gint32) num_songs);
808
add_playlist_entry_to_mlcl (GtkTreeModel *model,
811
struct MLCL_Bits *mb)
814
RhythmDBEntry *entry;
817
mlit = rb_daap_structure_add (mb->mlcl, RB_DAAP_CC_MLIT);
819
gtk_tree_model_get (model, iter, 0, &entry, -1);
821
id = GPOINTER_TO_INT (g_hash_table_lookup ((GHashTable *)mb->pointer, entry));
823
if (client_requested (mb->bits, ITEM_KIND))
824
rb_daap_structure_add (mlit, RB_DAAP_CC_MIKD, (gchar) DMAP_ITEM_KIND_AUDIO);
825
if (client_requested (mb->bits, ITEM_ID))
826
rb_daap_structure_add (mlit, RB_DAAP_CC_MIID, (gint32) id);
827
if (client_requested (mb->bits, CONTAINER_ITEM_ID))
828
rb_daap_structure_add (mlit, RB_DAAP_CC_MCTI, (gint32) id);
834
parse_meta (const gchar *s)
836
gchar *start_of_attrs;
843
start_of_attrs = strstr (s, "meta=");
844
if (start_of_attrs == NULL) {
849
end_of_attrs = strchr (start_of_attrs, '&');
851
attrs = g_strndup (start_of_attrs, end_of_attrs - start_of_attrs);
853
attrs = g_strdup (start_of_attrs);
856
attrsv = g_strsplit (attrs,",",-1);
858
for (i = 0; attrsv[i]; i++) {
861
for (j = 0; j < G_N_ELEMENTS (meta_data_map); j++) {
862
if (strcmp (meta_data_map[j].tag, attrsv[i]) == 0) {
863
bits |= (((bitwise) 1) << meta_data_map[j].md);
875
write_next_chunk (SoupMessage *message, GnomeVFSHandle *handle)
877
GnomeVFSFileSize read_size;
878
GnomeVFSResult result;
879
gchar *chunk = g_malloc (DAAP_SHARE_CHUNK_SIZE);
881
result = gnome_vfs_read (handle, chunk, DAAP_SHARE_CHUNK_SIZE, &read_size);
882
if (result == GNOME_VFS_OK && read_size > 0) {
883
soup_message_add_chunk (message, SOUP_BUFFER_SYSTEM_OWNED, chunk, read_size);
886
soup_message_add_final_chunk (message);
891
message_finished (SoupMessage *message, GnomeVFSHandle *handle)
893
gnome_vfs_close (handle);
897
databases_cb (RBDAAPShare *share,
898
SoupMessage *message)
902
// guint revision_number;
904
path = soup_uri_to_string (soup_message_get_uri (message), TRUE);
906
rest_of_path = strchr (path + 1, '/');
908
if (rest_of_path == NULL) {
909
/* AVDB server databases
912
* MTCO specified total count
913
* MRCO returned count
920
* MCTC container count
926
avdb = rb_daap_structure_add (NULL, RB_DAAP_CC_AVDB);
927
rb_daap_structure_add (avdb, RB_DAAP_CC_MSTT, (gint32) DMAP_STATUS_OK);
928
rb_daap_structure_add (avdb, RB_DAAP_CC_MUTY, 0);
929
rb_daap_structure_add (avdb, RB_DAAP_CC_MTCO, (gint32) 1);
930
rb_daap_structure_add (avdb, RB_DAAP_CC_MRCO, (gint32) 1);
931
mlcl = rb_daap_structure_add (avdb, RB_DAAP_CC_MLCL);
932
mlit = rb_daap_structure_add (mlcl, RB_DAAP_CC_MLIT);
933
rb_daap_structure_add (mlit, RB_DAAP_CC_MIID, (gint32) 1);
934
rb_daap_structure_add (mlit, RB_DAAP_CC_MPER, (gint64) 1);
935
rb_daap_structure_add (mlit, RB_DAAP_CC_MINM, share->priv->name);
936
rb_daap_structure_add (mlit, RB_DAAP_CC_MIMC, (gint32)share->priv->num_songs);
937
rb_daap_structure_add (mlit, RB_DAAP_CC_MCTC, (gint32) 1);
939
message_set_from_rb_daap_structure (message, avdb);
940
rb_daap_structure_destroy (avdb);
941
} else if (g_ascii_strncasecmp ("/1/items?", rest_of_path, 9) == 0) {
942
/* ADBS database songs
945
* MTCO specified total count
946
* MRCO returned count
954
struct MLCL_Bits mb = {NULL,0};
956
mb.bits = parse_meta (rest_of_path);
958
adbs = rb_daap_structure_add (NULL, RB_DAAP_CC_ADBS);
959
rb_daap_structure_add (adbs, RB_DAAP_CC_MSTT, (gint32) DMAP_STATUS_OK);
960
rb_daap_structure_add (adbs, RB_DAAP_CC_MUTY, 0);
961
rb_daap_structure_add (adbs, RB_DAAP_CC_MTCO, (gint32) share->priv->num_songs);
962
rb_daap_structure_add (adbs, RB_DAAP_CC_MRCO, (gint32) share->priv->num_songs);
963
mb.mlcl = rb_daap_structure_add (adbs, RB_DAAP_CC_MLCL);
965
g_hash_table_foreach (share->priv->entry_to_id, (GHFunc) add_entry_to_mlcl, &mb);
967
message_set_from_rb_daap_structure (message, adbs);
968
rb_daap_structure_destroy (adbs);
970
} else if (g_ascii_strncasecmp ("/1/containers?", rest_of_path, 14) == 0) {
971
/* APLY database playlists
974
* MTCO specified total count
975
* MRCO returned count
979
* MPER persistent item id
982
* ABPL baseplaylist (only for base)
990
aply = rb_daap_structure_add (NULL, RB_DAAP_CC_APLY);
991
rb_daap_structure_add (aply, RB_DAAP_CC_MSTT, (gint32) DMAP_STATUS_OK);
992
rb_daap_structure_add (aply, RB_DAAP_CC_MUTY, 0);
993
rb_daap_structure_add (aply, RB_DAAP_CC_MTCO, (gint32) 1);
994
rb_daap_structure_add (aply, RB_DAAP_CC_MRCO, (gint32) 1);
995
mlcl = rb_daap_structure_add (aply, RB_DAAP_CC_MLCL);
996
mlit = rb_daap_structure_add (mlcl, RB_DAAP_CC_MLIT);
997
rb_daap_structure_add (mlit, RB_DAAP_CC_MIID, (gint32) 1);
998
rb_daap_structure_add (mlit, RB_DAAP_CC_MPER, (gint64) 1);
999
rb_daap_structure_add (mlit, RB_DAAP_CC_MINM, share->priv->name);
1000
rb_daap_structure_add (mlit, RB_DAAP_CC_MIMC, (gint32) share->priv->num_songs);
1001
rb_daap_structure_add (mlit, RB_DAAP_CC_ABPL, (gchar) 1); /* base playlist */
1003
g_list_foreach (share->priv->playlist_ids, (GFunc) add_playlist_to_mlcl, mlcl);
1005
message_set_from_rb_daap_structure (message, aply);
1006
rb_daap_structure_destroy (aply);
1007
} else if (g_ascii_strncasecmp ("/1/containers/", rest_of_path, 14) == 0) {
1008
/* APSO playlist songs
1011
* MTCO specified total count
1012
* MRCO returned count
1017
* MCTI container item id
1022
struct MLCL_Bits mb = {NULL,0};
1023
gint pl_id = atoi (rest_of_path + 14);
1025
mb.bits = parse_meta (rest_of_path);
1027
apso = rb_daap_structure_add (NULL, RB_DAAP_CC_APSO);
1028
rb_daap_structure_add (apso, RB_DAAP_CC_MSTT, (gint32) DMAP_STATUS_OK);
1029
rb_daap_structure_add (apso, RB_DAAP_CC_MUTY, 0);
1032
rb_daap_structure_add (apso, RB_DAAP_CC_MTCO, (gint32) share->priv->num_songs);
1033
rb_daap_structure_add (apso, RB_DAAP_CC_MRCO, (gint32) share->priv->num_songs);
1034
mb.mlcl = rb_daap_structure_add (apso, RB_DAAP_CC_MLCL);
1036
g_hash_table_foreach (share->priv->entry_to_id, (GHFunc) add_entry_to_mlcl, &mb);
1042
RhythmDBQueryModel *model;
1044
idl = g_list_find_custom (share->priv->playlist_ids,
1045
GINT_TO_POINTER (pl_id),
1048
soup_message_set_status (message, SOUP_STATUS_NOT_FOUND);
1049
soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (message),
1050
SOUP_TRANSFER_CONTENT_LENGTH);
1051
soup_message_set_response (message, "text/plain", SOUP_BUFFER_USER_OWNED, "", 0);
1054
id = (RBPlaylistID *)idl->data;
1056
mb.mlcl = rb_daap_structure_add (apso, RB_DAAP_CC_MLCL);
1058
mb.pointer = share->priv->entry_to_id;
1060
ev = rb_source_get_entry_view (id->source);
1061
num_songs = rb_entry_view_get_num_entries (ev);
1063
rb_daap_structure_add (apso, RB_DAAP_CC_MTCO, (gint32) num_songs);
1064
rb_daap_structure_add (apso, RB_DAAP_CC_MRCO, (gint32) num_songs);
1066
g_object_get (G_OBJECT (id->source), "query-model", &model, NULL);
1067
gtk_tree_model_foreach (GTK_TREE_MODEL (model), (GtkTreeModelForeachFunc) add_playlist_entry_to_mlcl, &mb);
1068
g_object_unref (model);
1071
message_set_from_rb_daap_structure (message, apso);
1072
rb_daap_structure_destroy (apso);
1073
} else if (g_ascii_strncasecmp ("/1/items/", rest_of_path, 9) == 0) {
1074
/* just the file :) */
1077
RhythmDBEntry *entry;
1078
const gchar *location;
1080
GnomeVFSResult result;
1081
GnomeVFSHandle *handle;
1082
const gchar *range_header;
1083
guint status_code = SOUP_STATUS_OK;
1085
id_str = rest_of_path + 9;
1088
entry = g_hash_table_lookup (share->priv->id_to_entry, GINT_TO_POINTER (id));
1089
location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
1090
file_size = rhythmdb_entry_get_uint64 (entry, RHYTHMDB_PROP_FILE_SIZE);
1092
result = gnome_vfs_open (&handle, location, GNOME_VFS_OPEN_READ);
1093
if (result != GNOME_VFS_OK) {
1094
soup_message_set_status (message, SOUP_STATUS_INTERNAL_SERVER_ERROR);
1098
range_header = soup_message_get_header (message->request_headers, "Range");
1101
GnomeVFSFileOffset range;
1102
gchar *content_range;
1104
s = range_header + 6; // bytes=
1107
result = gnome_vfs_seek (handle, GNOME_VFS_SEEK_START, range);
1109
if (result != GNOME_VFS_OK) {
1110
g_message ("Error seeking: %s", gnome_vfs_result_to_string (result));
1111
soup_message_set_status (message, SOUP_STATUS_INTERNAL_SERVER_ERROR);
1115
status_code = SOUP_STATUS_PARTIAL_CONTENT;
1117
content_range = g_strdup_printf ("bytes %"GNOME_VFS_OFFSET_FORMAT_STR"-%"G_GUINT64_FORMAT"/%"G_GUINT64_FORMAT, range, file_size, file_size);
1118
soup_message_add_header (message->response_headers, "Content-Range", content_range);
1119
g_free (content_range);
1124
g_signal_connect (message, "wrote_chunk", G_CALLBACK (write_next_chunk), handle);
1125
g_signal_connect (message, "finished", G_CALLBACK (message_finished), handle);
1126
write_next_chunk (message, handle);
1128
soup_message_set_status (message, status_code);
1129
soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (message), SOUP_TRANSFER_CHUNKED);
1132
g_print ("unhandled: %s\n", path);
1139
typedef void (* DAAPPathFunction) (RBDAAPShare *share, SoupMessage *message);
1144
DAAPPathFunction function;
1147
static const struct DAAPPath paths_to_functions[] = {
1148
{"/server-info", 12, server_info_cb},
1149
{"/content-codes", 14, content_codes_cb},
1150
{"/login", 6, login_cb},
1151
{"/update", 7, update_cb},
1152
{"/databases", 10, databases_cb}
1156
server_cb (SoupServerContext *context,
1157
SoupMessage *message,
1163
path = soup_uri_to_string (soup_message_get_uri (message), TRUE);
1165
for (i = 0; i < G_N_ELEMENTS (paths_to_functions); i++) {
1166
if (g_ascii_strncasecmp (paths_to_functions[i].path, path, paths_to_functions[i].path_length) == 0) {
1167
paths_to_functions[i].function (share, message);
1172
g_warning ("unhandled path %s\n", soup_uri_to_string (soup_message_get_uri (message), TRUE));
1178
add_db_entry (RhythmDBEntry *entry,
1181
RhythmDBEntryType type = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TYPE);
1183
if (type == rhythmdb_entry_song_get_type ()) {
1184
share->priv->num_songs++;
1186
g_hash_table_insert (share->priv->id_to_entry, GINT_TO_POINTER (share->priv->num_songs), entry);
1187
g_hash_table_insert (share->priv->entry_to_id, entry, GINT_TO_POINTER (share->priv->num_songs));
1192
db_entry_added_cb (RhythmDB *db,
1193
RhythmDBEntry *entry,
1196
RhythmDBEntryType type = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TYPE);
1197
gboolean hidden = rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN);
1199
if (type == rhythmdb_entry_song_get_type () && !hidden) {
1200
share->priv->num_songs++;
1202
g_hash_table_insert (share->priv->id_to_entry, GINT_TO_POINTER (share->priv->num_songs), entry);
1203
g_hash_table_insert (share->priv->entry_to_id, entry, GINT_TO_POINTER (share->priv->num_songs));
1208
db_entry_deleted_cb (RhythmDB *db,
1209
RhythmDBEntry *entry,
1214
id = g_hash_table_lookup (share->priv->entry_to_id, entry);
1216
g_hash_table_remove (share->priv->entry_to_id, entry);
1218
share->priv->num_songs--;
1224
publish_cb (RBDAAPmDNSPublisher publisher,
1225
RBDAAPmDNSPublisherStatus status,
1229
case RB_DAAP_MDNS_PUBLISHER_STARTED:
1230
rb_debug ("mDNS publish successful");
1231
share->priv->published = TRUE;
1233
case RB_DAAP_MDNS_PUBLISHER_COLLISION: {
1236
rb_debug ("Duplicate share name on mDNS");
1238
new_name = rb_daap_collision_dialog_new_run (share->priv->name);
1248
rb_daap_share_start_publish (RBDAAPShare *share)
1250
gint port = STANDARD_DAAP_PORT;
1253
share->priv->server = soup_server_new (SOUP_SERVER_PORT, port, NULL);
1254
if (share->priv->server == NULL) {
1255
rb_debug ("Unable to start music sharing server on port %d, trying any open port", port);
1256
share->priv->server = soup_server_new (SOUP_SERVER_PORT, SOUP_ADDRESS_ANY_PORT, NULL);
1258
if (share->priv->server == NULL) {
1259
g_warning ("Unable to start music sharing server");
1264
share->priv->port = soup_server_get_port (share->priv->server);
1265
rb_debug ("Started DAAP server on port %d", port);
1267
soup_server_add_handler (share->priv->server,
1270
(SoupServerCallbackFn)server_cb,
1273
soup_server_run_async (share->priv->server);
1275
ret = rb_daap_mdns_publish (&(share->priv->publisher),
1278
(RBDAAPmDNSPublisherCallback) publish_cb,
1282
g_warning ("Unable to notify network of music sharing");
1286
rb_debug ("Published DAAP server information to mdns");
1288
share->priv->id_to_entry = g_hash_table_new (NULL, NULL);
1289
share->priv->entry_to_id = g_hash_table_new (NULL, NULL);
1290
share->priv->num_songs = 0;
1291
share->priv->next_playlist_id = 2; /* 1 already used */
1293
rhythmdb_entry_foreach (share->priv->db, (GFunc)add_db_entry, share);
1295
share->priv->entry_added_id = g_signal_connect (G_OBJECT (share->priv->db),
1297
G_CALLBACK (db_entry_added_cb),
1299
share->priv->entry_deleted_id = g_signal_connect (G_OBJECT (share->priv->db),
1301
G_CALLBACK (db_entry_deleted_cb),
1306
rb_daap_share_stop_publish (RBDAAPShare *share)
1308
if (share->priv->server) {
1311
* GLib-CRITICAL **: g_main_loop_quit: assertion `loop != NULL' failed
1312
* But it doesn't seem to matter.
1314
// soup_server_quit (share->priv->server);
1315
g_object_unref (G_OBJECT (share->priv->server));
1316
share->priv->server = NULL;
1319
if (share->priv->id_to_entry) {
1320
g_hash_table_destroy (share->priv->id_to_entry);
1321
share->priv->id_to_entry = NULL;
1324
if (share->priv->entry_to_id) {
1325
g_hash_table_destroy (share->priv->entry_to_id);
1326
share->priv->entry_to_id = NULL;
1329
if (share->priv->entry_added_id != 0) {
1330
g_signal_handler_disconnect (share->priv->db, share->priv->entry_added_id);
1331
share->priv->entry_added_id = 0;
1334
if (share->priv->entry_deleted_id != 0) {
1335
g_signal_handler_disconnect (share->priv->db, share->priv->entry_deleted_id);
1336
share->priv->entry_deleted_id = 0;
1339
if (share->priv->publisher) {
1340
rb_daap_mdns_publish_cancel (share->priv->publisher);
1341
share->priv->publisher = 0;
1344
share->priv->published = FALSE;