~ubuntu-branches/ubuntu/precise/rhythmbox/precise-201203091205

« back to all changes in this revision

Viewing changes to daapsharing/rb-daap-share.c

Tags: upstream-0.9.5
ImportĀ upstreamĀ versionĀ 0.9.5

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 *
17
17
 *  You should have received a copy of the GNU General Public License
18
18
 *  along with this program; if not, write to the Free Software
19
 
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
19
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
20
20
 *
21
21
 */
22
22
 
23
 
#include <config.h>
 
23
#include "config.h"
24
24
 
25
25
#include <time.h>
26
26
#include <string.h>
32
32
#include <libsoup/soup-message.h>
33
33
#include <libsoup/soup-uri.h>
34
34
#include <libsoup/soup-server.h>
 
35
#include <libsoup/soup-server-auth.h>
35
36
#include <libsoup/soup-server-message.h>
36
37
#include <libgnomevfs/gnome-vfs.h>
37
38
 
38
39
#include "rb-daap-share.h"
39
40
#include "rb-daap-structure.h"
40
 
#include "rb-daap-mdns.h"
 
41
#include "rb-daap-mdns-publisher.h"
41
42
#include "rb-daap-dialog.h"
42
43
 
43
44
#include "rb-playlist-source.h"
53
54
                                         GValue *value,
54
55
                                         GParamSpec *pspec);
55
56
static void rb_daap_share_dispose       (GObject *object);
56
 
 
57
 
static void rb_daap_share_start_publish (RBDAAPShare *share);
58
 
static void rb_daap_share_stop_publish  (RBDAAPShare *share);
 
57
static void rb_daap_share_maybe_restart (RBDAAPShare *share);
 
58
static gboolean rb_daap_share_publish_start (RBDAAPShare *share);
 
59
static gboolean rb_daap_share_publish_stop  (RBDAAPShare *share);
 
60
static gboolean rb_daap_share_server_start  (RBDAAPShare *share);
 
61
static gboolean rb_daap_share_server_stop   (RBDAAPShare *share);
59
62
static void rb_daap_share_playlist_created (RBPlaylistManager *mgr,
60
63
                                            RBSource *playlist,
61
64
                                            RBDAAPShare *share);
66
69
static void rb_daap_share_forget_playlist (gpointer data,
67
70
                                           RBDAAPShare *share);
68
71
 
69
 
 
70
 
#define CONF_NAME CONF_PREFIX "/sharing/share_name"
71
72
#define STANDARD_DAAP_PORT 3689
72
73
 
73
74
/* HTTP chunk size used to send files to clients */
74
75
#define DAAP_SHARE_CHUNK_SIZE   16384
75
76
 
 
77
typedef enum {
 
78
        RB_DAAP_SHARE_AUTH_METHOD_NONE              = 0,
 
79
        RB_DAAP_SHARE_AUTH_METHOD_NAME_AND_PASSWORD = 1,
 
80
        RB_DAAP_SHARE_AUTH_METHOD_PASSWORD          = 2
 
81
} RBDAAPShareAuthMethod;
 
82
 
76
83
struct RBDAAPSharePrivate {
77
84
        gchar *name;
78
85
        guint port;
 
86
        char *password;
 
87
        RBDAAPShareAuthMethod auth_method;
79
88
 
80
89
        /* mdns/dns-sd publishing things */
 
90
        gboolean server_active;
81
91
        gboolean published;
82
 
        RBDAAPmDNSPublisher publisher;
 
92
        RBDaapMdnsPublisher *publisher;
83
93
 
84
94
        /* http server things */
85
95
        SoupServer *server;
86
96
        guint revision_number;
87
97
 
 
98
        GHashTable *session_ids;
 
99
 
88
100
        /* db things */
89
101
        RhythmDB *db;
90
102
        gint32 next_song_id;
111
123
enum {
112
124
        PROP_0,
113
125
        PROP_NAME,
 
126
        PROP_PASSWORD,
114
127
        PROP_DB,
115
128
        PROP_PLAYLIST_MANAGER
116
129
};
136
149
                                                              NULL,
137
150
                                                              G_PARAM_READWRITE));
138
151
        g_object_class_install_property (object_class,
 
152
                                         PROP_PASSWORD,
 
153
                                         g_param_spec_string ("password",
 
154
                                                              "Authentication password",
 
155
                                                              "Authentication password",
 
156
                                                              NULL,
 
157
                                                              G_PARAM_READWRITE));
 
158
        g_object_class_install_property (object_class,
139
159
                                         PROP_DB,
140
160
                                         g_param_spec_object ("db",
141
161
                                                              "RhythmDB",
154
174
}
155
175
 
156
176
static void
 
177
rb_daap_share_set_name (RBDAAPShare *share,
 
178
                        const char  *name)
 
179
{
 
180
        GError *error;
 
181
        gboolean res;
 
182
 
 
183
        g_return_if_fail (share != NULL);
 
184
 
 
185
        g_free (share->priv->name);
 
186
        share->priv->name = g_strdup (name);
 
187
 
 
188
        error = NULL;
 
189
        res = rb_daap_mdns_publisher_set_name (share->priv->publisher, name, &error);
 
190
        if (error != NULL) {
 
191
                g_warning ("Unable to change MDNS service name: %s", error->message);
 
192
                g_error_free (error);
 
193
        }
 
194
}
 
195
 
 
196
static void
 
197
published_cb (RBDaapMdnsPublisher *publisher,
 
198
              const char          *name,
 
199
              RBDAAPShare         *share)
 
200
{
 
201
        if (share->priv->name == NULL || name == NULL) {
 
202
                return;
 
203
        }
 
204
 
 
205
        if (strcmp (name, share->priv->name) == 0) {
 
206
                rb_debug ("mDNS publish successful");
 
207
                share->priv->published = TRUE;
 
208
        }
 
209
}
 
210
 
 
211
static void
 
212
name_collision_cb (RBDaapMdnsPublisher *publisher,
 
213
                   const char          *name,
 
214
                   RBDAAPShare         *share)
 
215
{
 
216
        char *new_name;
 
217
 
 
218
        if (share->priv->name == NULL || name == NULL) {
 
219
                return;
 
220
        }
 
221
 
 
222
        if (strcmp (name, share->priv->name) == 0) {
 
223
                rb_debug ("Duplicate share name on mDNS");
 
224
                
 
225
                new_name = rb_daap_collision_dialog_new_run (NULL, share->priv->name);
 
226
 
 
227
                rb_daap_share_set_name (share, new_name);
 
228
                g_free (new_name);
 
229
        }
 
230
        
 
231
        return;
 
232
}
 
233
 
 
234
static void
157
235
rb_daap_share_init (RBDAAPShare *share)
158
236
{
159
237
        share->priv = RB_DAAP_SHARE_GET_PRIVATE (share);
160
238
 
161
239
        share->priv->revision_number = 5;
 
240
 
 
241
        share->priv->auth_method = RB_DAAP_SHARE_AUTH_METHOD_NONE;
 
242
        share->priv->publisher = rb_daap_mdns_publisher_new ();
 
243
        g_signal_connect_object (share->priv->publisher,
 
244
                                 "published",
 
245
                                 G_CALLBACK (published_cb),
 
246
                                 share, 0);
 
247
        g_signal_connect_object (share->priv->publisher,
 
248
                                 "name-collision",
 
249
                                 G_CALLBACK (name_collision_cb),
 
250
                                 share, 0);
 
251
 
 
252
}
 
253
 
 
254
static void
 
255
rb_daap_share_set_password (RBDAAPShare *share,
 
256
                            const char  *password)
 
257
{
 
258
        g_return_if_fail (share != NULL);
 
259
 
 
260
        if (share->priv->password && password &&
 
261
            strcmp (password, share->priv->password) == 0) {
 
262
                return;
 
263
        }
 
264
 
 
265
        g_free (share->priv->password);
 
266
        share->priv->password = g_strdup (password);
 
267
        if (password != NULL) {
 
268
                share->priv->auth_method = RB_DAAP_SHARE_AUTH_METHOD_PASSWORD;
 
269
        } else {
 
270
                share->priv->auth_method = RB_DAAP_SHARE_AUTH_METHOD_NONE;
 
271
        }
 
272
 
 
273
        rb_daap_share_maybe_restart (share);
 
274
}
 
275
 
 
276
static void
 
277
rb_daap_share_set_playlist_manager (RBDAAPShare       *share,
 
278
                                    RBPlaylistManager *playlist_manager)
 
279
{
 
280
        GList *playlists;
 
281
 
 
282
        g_return_if_fail (share != NULL);
 
283
 
 
284
        share->priv->playlist_manager = playlist_manager;
 
285
 
 
286
        g_signal_connect_object (G_OBJECT (share->priv->playlist_manager),
 
287
                                 "playlist_added",
 
288
                                 G_CALLBACK (rb_daap_share_playlist_created),
 
289
                                 share, 0);
 
290
 
 
291
        /* Currently, there are no playlists when this object is created, but in
 
292
         * case it changes..
 
293
         */
 
294
        playlists = rb_playlist_manager_get_playlists (share->priv->playlist_manager);
 
295
        g_list_foreach (playlists, (GFunc) rb_daap_share_process_playlist, share);
 
296
        g_list_free (playlists);
162
297
}
163
298
 
164
299
static void
170
305
        RBDAAPShare *share = RB_DAAP_SHARE (object);
171
306
 
172
307
        switch (prop_id) {
173
 
        case PROP_NAME: {
174
 
                gboolean restart_publish = FALSE;
175
 
                const char *name = g_value_get_string (value);
176
 
 
177
 
                /* check if the name hasn't really changed */
178
 
                if (share->priv->name && name &&
179
 
                    strcmp (name, share->priv->name) == 0) {
180
 
                        return;
181
 
                }
182
 
        
183
 
                if (share->priv->name) {
184
 
                        g_free (share->priv->name);
185
 
 
186
 
                        if (share->priv->published) {
187
 
                                rb_daap_share_stop_publish (share);
188
 
                                restart_publish = TRUE;
189
 
                        }
190
 
                }
191
 
 
192
 
                share->priv->name = g_strdup (name);
193
 
 
194
 
                if (restart_publish) {
195
 
                        rb_daap_share_start_publish (share);
196
 
                }
197
 
 
198
 
                break;
199
 
        }
 
308
        case PROP_NAME:
 
309
                rb_daap_share_set_name (share, g_value_get_string (value));
 
310
                break;
 
311
        case PROP_PASSWORD:
 
312
                rb_daap_share_set_password (share, g_value_get_string (value));
 
313
                break;
200
314
        case PROP_DB:
201
315
                share->priv->db = g_value_get_object (value);
202
316
                break;
203
317
        case PROP_PLAYLIST_MANAGER:
204
 
                {
205
 
                        GList *playlists;
206
 
                        share->priv->playlist_manager = g_value_get_object (value);
207
 
                        g_signal_connect_object (G_OBJECT (share->priv->playlist_manager),
208
 
                                                 "playlist_added",
209
 
                                                 G_CALLBACK (rb_daap_share_playlist_created),
210
 
                                                 share, 0);
211
 
 
212
 
                        /* Currently, there are no playlists when this object is created, but in
213
 
                         * case it changes..
214
 
                         */
215
 
                        playlists = rb_playlist_manager_get_playlists (share->priv->playlist_manager);
216
 
                        g_list_foreach (playlists, (GFunc) rb_daap_share_process_playlist, share);
217
 
                        g_list_free (playlists);
218
 
                }
 
318
                rb_daap_share_set_playlist_manager (share, g_value_get_object (value));
219
319
                break;
220
320
        default:
221
321
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
235
335
        case PROP_NAME:
236
336
                g_value_set_string (value, share->priv->name);
237
337
                break;
 
338
        case PROP_PASSWORD:
 
339
                g_value_set_string (value, share->priv->password);
 
340
                break;
238
341
        case PROP_DB:
239
342
                g_value_set_object (value, share->priv->db);
240
343
                break;
318
421
                             share);
319
422
}
320
423
 
321
 
 
322
424
static void
323
425
rb_daap_share_dispose (GObject *object)
324
426
{
325
427
        RBDAAPShare *share = RB_DAAP_SHARE (object);
326
428
 
327
429
        if (share->priv->published) {
328
 
                rb_daap_share_stop_publish (share);
329
 
        }
330
 
 
331
 
        if (share->priv) {
332
 
                g_free (share->priv->name);
333
 
                g_object_unref (share->priv->db);
334
 
                g_object_unref (share->priv->playlist_manager);
 
430
                rb_daap_share_publish_stop (share);
 
431
        }
 
432
 
 
433
        if (share->priv->server_active) {
 
434
                rb_daap_share_server_stop (share);
 
435
        }
 
436
 
 
437
        g_free (share->priv->name);
 
438
        g_object_unref (share->priv->db);
 
439
        g_object_unref (share->priv->playlist_manager);
335
440
                
336
 
                g_list_foreach (share->priv->playlist_ids, (GFunc) rb_daap_share_forget_playlist, share);
337
 
                g_list_foreach (share->priv->playlist_ids, (GFunc) g_free, NULL);
 
441
        g_list_foreach (share->priv->playlist_ids, (GFunc) rb_daap_share_forget_playlist, share);
 
442
        g_list_foreach (share->priv->playlist_ids, (GFunc) g_free, NULL);
 
443
 
 
444
        if (share->priv->publisher) {
 
445
                g_object_unref (share->priv->publisher);
338
446
        }
339
447
 
340
448
        G_OBJECT_CLASS (rb_daap_share_parent_class)->dispose (object);
342
450
 
343
451
 
344
452
RBDAAPShare *
345
 
rb_daap_share_new (const gchar *name,
 
453
rb_daap_share_new (const char *name,
 
454
                   const char *password,
346
455
                   RhythmDB *db,
347
456
                   RBPlaylistManager *playlist_manager)
348
457
{
350
459
 
351
460
        share = RB_DAAP_SHARE (g_object_new (RB_TYPE_DAAP_SHARE,
352
461
                                             "name", name,
 
462
                                             "password", password,
353
463
                                             "db", db,
354
464
                                             "playlist-manager", playlist_manager,
355
465
                                             NULL));
356
 
        rb_daap_share_start_publish (share);
 
466
 
 
467
        rb_daap_share_server_start (share);
 
468
        rb_daap_share_publish_start (share);
357
469
 
358
470
        return share;
359
471
}
387
499
        resp = rb_daap_structure_serialize (structure, &length);
388
500
 
389
501
        if (resp == NULL) {
390
 
                g_print ("serialize gave us null?\n");
 
502
                rb_debug ("serialize gave us null?\n");
391
503
                return;
392
504
        }
393
505
 
408
520
#define DMAP_TIMEOUT 1800
409
521
 
410
522
static void 
411
 
server_info_cb (RBDAAPShare *share, 
 
523
server_info_cb (RBDAAPShare *share,
 
524
                SoupServerContext *context,
412
525
                SoupMessage *message)
413
526
{
414
527
/* MSRV server info response
444
557
         * 3.0 is 2/3
445
558
         */
446
559
        rb_daap_structure_add (msrv, RB_DAAP_CC_MINM, share->priv->name);
447
 
        rb_daap_structure_add (msrv, RB_DAAP_CC_MSAU, 0);
 
560
        rb_daap_structure_add (msrv, RB_DAAP_CC_MSAU, share->priv->auth_method);
448
561
        /* authentication method
449
562
         * 0 is nothing
450
563
         * 1 is name & password
468
581
 
469
582
static void
470
583
content_codes_cb (RBDAAPShare *share,
 
584
                  SoupServerContext *context,
471
585
                  SoupMessage *message)
472
586
{
473
587
/* MCCR content codes response
502
616
        rb_daap_structure_destroy (mccr);
503
617
}
504
618
 
505
 
/* This is arbitrary.  iTunes communicates with a session id for
506
 
 * reasons relating to updates to the database and such, I think
507
 
 * Since we don't do that, and since we don't keep track of connections
508
 
 * like iTunes do, everyone can just share the same, special, arbitrary
509
 
 * session id.
510
 
 */
511
 
#define DAAP_SESSION_ID 42
 
619
static gboolean
 
620
message_get_session_id (SoupMessage *message,
 
621
                        guint32     *id)
 
622
{
 
623
        const SoupUri *uri;
 
624
        char          *position;
 
625
        guint32        session_id;
 
626
 
 
627
        if (id) {
 
628
                *id = 0;
 
629
        }
 
630
 
 
631
        uri = soup_message_get_uri (message);
 
632
        if (uri == NULL) {
 
633
                return FALSE;
 
634
        }
 
635
 
 
636
        position = strstr (uri->query, "session-id=");
 
637
 
 
638
        if (position == NULL) {
 
639
                rb_debug ("session id not found");
 
640
                return FALSE;
 
641
        }
 
642
 
 
643
        position += 11;
 
644
        session_id = (guint32) strtoul (position, NULL, 10);
 
645
 
 
646
        if (id) {
 
647
                *id = session_id;
 
648
        }
 
649
 
 
650
        return TRUE;
 
651
}
 
652
 
 
653
static gboolean
 
654
message_get_revision_number (SoupMessage *message,
 
655
                             guint       *number)
 
656
{
 
657
        const SoupUri *uri;
 
658
        char          *position;
 
659
        guint          revision_number;
 
660
 
 
661
        if (number) {
 
662
                *number = 0;
 
663
        }
 
664
 
 
665
        uri = soup_message_get_uri (message);
 
666
        if (uri == NULL) {
 
667
                return FALSE;
 
668
        }
 
669
 
 
670
        position = strstr (uri->query, "revision-number=");
 
671
 
 
672
        if (position == NULL) {
 
673
                rb_debug ("client asked for an update without a revision number?!?\n");
 
674
                return FALSE;
 
675
        }
 
676
 
 
677
        position += 16;
 
678
        revision_number = atoi (position);
 
679
 
 
680
        if (number) {
 
681
                *number = revision_number;
 
682
        }
 
683
 
 
684
        return TRUE;
 
685
}
 
686
 
 
687
static gboolean
 
688
session_id_validate (RBDAAPShare       *share,
 
689
                     SoupServerContext *context,
 
690
                     SoupMessage       *message,
 
691
                     guint32           *id)
 
692
{
 
693
        guint32     session_id;
 
694
        gboolean    res;
 
695
        const char *addr;
 
696
        const char *remote_address;
 
697
 
 
698
        if (id) {
 
699
                *id = 0;
 
700
        }
512
701
        
 
702
        res = message_get_session_id (message, &session_id);
 
703
        if (! res) {
 
704
                rb_debug ("Validation failed: Unable to parse session id from message");
 
705
                return FALSE;
 
706
        }
 
707
 
 
708
        /* check hash for remote address */
 
709
        addr = g_hash_table_lookup (share->priv->session_ids, GUINT_TO_POINTER (session_id));
 
710
        if (addr == NULL) {
 
711
                rb_debug ("Validation failed: Unable to lookup session id %u", session_id);
 
712
                return FALSE;
 
713
        }
 
714
 
 
715
        remote_address = soup_server_context_get_client_host (context);
 
716
        rb_debug ("Validating session id %u from %s matches %s",
 
717
                  session_id, remote_address, addr);
 
718
        if (remote_address == NULL || strcmp (addr, remote_address) != 0) {
 
719
                rb_debug ("Validation failed: Remote address does not match stored address");
 
720
                return FALSE;
 
721
        }
 
722
 
 
723
        if (id) {
 
724
                *id = session_id;
 
725
        }
 
726
 
 
727
        return TRUE;
 
728
}
 
729
 
 
730
static guint32
 
731
session_id_generate (RBDAAPShare       *share,
 
732
                     SoupServerContext *context)
 
733
{
 
734
        guint32 id;
 
735
 
 
736
        id = g_random_int ();
 
737
 
 
738
        return id;
 
739
}
 
740
 
 
741
static guint32
 
742
session_id_create (RBDAAPShare       *share,
 
743
                   SoupServerContext *context)
 
744
{
 
745
        guint32     id;
 
746
        const char *addr;
 
747
        char       *remote_address;
 
748
 
 
749
        do {
 
750
                /* create a unique session id */
 
751
                id = session_id_generate (share, context);
 
752
                rb_debug ("Generated session id %u", id);
 
753
 
 
754
                /* if already used, try again */
 
755
                addr = g_hash_table_lookup (share->priv->session_ids, GUINT_TO_POINTER (id));
 
756
        } while (addr != NULL);
 
757
 
 
758
        /* store session id and remote address */
 
759
        remote_address = g_strdup (soup_server_context_get_client_host (context));
 
760
        g_hash_table_insert (share->priv->session_ids, GUINT_TO_POINTER (id), remote_address);
 
761
 
 
762
        return id;
 
763
}
 
764
 
 
765
static void
 
766
session_id_remove (RBDAAPShare       *share,
 
767
                   SoupServerContext *context,
 
768
                   guint32            id)
 
769
{
 
770
        g_hash_table_remove (share->priv->session_ids, GUINT_TO_POINTER (id));
 
771
}
 
772
 
513
773
static void 
514
 
login_cb (RBDAAPShare *share, 
 
774
login_cb (RBDAAPShare *share,
 
775
          SoupServerContext *context,
515
776
          SoupMessage *message)
516
777
{
517
778
/* MLOG login response
519
780
 *      MLID session id
520
781
 */
521
782
        GNode *mlog;
 
783
        guint32 session_id;
 
784
 
 
785
        session_id = session_id_create (share, context);
 
786
 
 
787
        rb_debug ("Handling login session id %u", session_id);
522
788
 
523
789
        mlog = rb_daap_structure_add (NULL, RB_DAAP_CC_MLOG);
524
790
        rb_daap_structure_add (mlog, RB_DAAP_CC_MSTT, (gint32) DMAP_STATUS_OK);
525
 
        rb_daap_structure_add (mlog, RB_DAAP_CC_MLID, (gint32) DAAP_SESSION_ID);
 
791
        rb_daap_structure_add (mlog, RB_DAAP_CC_MLID, session_id);
526
792
 
527
793
        message_set_from_rb_daap_structure (message, mlog);
528
794
        rb_daap_structure_destroy (mlog);
529
795
}
530
796
 
 
797
static void 
 
798
logout_cb (RBDAAPShare *share, 
 
799
           SoupServerContext *context,
 
800
           SoupMessage *message)
 
801
{
 
802
        int     status;
 
803
        guint32 id;
 
804
 
 
805
        if (session_id_validate (share, context, message, &id)) {
 
806
                rb_debug ("Handling logout session id %u", id);
 
807
                session_id_remove (share, context, id);
 
808
 
 
809
                status = SOUP_STATUS_NO_CONTENT;
 
810
        } else {
 
811
                status = SOUP_STATUS_FORBIDDEN;
 
812
        }
 
813
 
 
814
        soup_message_set_status (message, status);
 
815
        soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (message), SOUP_TRANSFER_CONTENT_LENGTH);
 
816
}
 
817
 
531
818
static void
532
819
update_cb (RBDAAPShare *share,
 
820
           SoupServerContext *context,
533
821
           SoupMessage *message)
534
822
{
535
 
        gchar *path;
536
 
        gchar *revision_number_position;
537
 
        guint revision_number;
538
 
 
539
 
        path = soup_uri_to_string (soup_message_get_uri (message), TRUE);
540
 
 
541
 
        revision_number_position = strstr (path, "revision-number=");
542
 
 
543
 
        if (revision_number_position == NULL) {
544
 
                g_print ("client asked for an update without a revision number?!?\n");
545
 
                g_free (path);
546
 
                return;
547
 
        }
548
 
 
549
 
        revision_number_position += 16;
550
 
        revision_number = atoi (revision_number_position);
551
 
 
552
 
        g_free (path);
553
 
        
554
 
        if (revision_number != share->priv->revision_number) {
 
823
        guint    revision_number;
 
824
        gboolean res;
 
825
 
 
826
        res = message_get_revision_number (message, &revision_number);
 
827
 
 
828
        if (res && revision_number != share->priv->revision_number) {
555
829
                /* MUPD update response
556
830
                 *      MSTT status
557
831
                 *      MUSR server revision
772
1046
        if (client_requested (mb->bits, SONG_TRACK_NUMBER))
773
1047
                rb_daap_structure_add (mlit, RB_DAAP_CC_ASTN, (gint32) rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER));
774
1048
        if (client_requested (mb->bits, SONG_USER_RATING))
775
 
                rb_daap_structure_add (mlit, RB_DAAP_CC_ASUR, 0); // fixme
 
1049
                rb_daap_structure_add (mlit, RB_DAAP_CC_ASUR, 0); /* FIXME */
776
1050
        if (client_requested (mb->bits, SONG_YEAR))
777
1051
                rb_daap_structure_add (mlit, RB_DAAP_CC_ASYR, 0);
778
1052
        
791
1065
 */
792
1066
        GNode *mlit;
793
1067
        gchar *name;
794
 
        RBEntryView *ev;
795
1068
        guint num_songs;
796
1069
        RhythmDBQueryModel *model;
797
1070
        
798
1071
        g_object_get (G_OBJECT (playlist_id->source), "name", &name, NULL);
799
 
        
800
 
        ev = rb_source_get_entry_view (playlist_id->source);
801
 
 
802
1072
        g_object_get (G_OBJECT (playlist_id->source), "query-model", &model, NULL);
 
1073
 
803
1074
        num_songs = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (model), NULL);
804
1075
        g_object_unref (G_OBJECT (model));
805
1076
        
906
1177
 
907
1178
static void 
908
1179
databases_cb (RBDAAPShare *share, 
 
1180
              SoupServerContext *context,
909
1181
              SoupMessage *message)
910
1182
{
911
1183
        gchar *path;
912
1184
        gchar *rest_of_path;
913
 
//      guint revision_number;
 
1185
        /*guint revision_number;*/
 
1186
 
 
1187
        if (! session_id_validate (share, context, message, NULL)) {
 
1188
                soup_message_set_status (message, SOUP_STATUS_FORBIDDEN);
 
1189
                soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (message), SOUP_TRANSFER_CONTENT_LENGTH);
 
1190
                return;
 
1191
        }
914
1192
        
915
1193
        path = soup_uri_to_string (soup_message_get_uri (message), TRUE);
916
1194
 
1112
1390
                        GnomeVFSFileOffset range;
1113
1391
                        gchar *content_range;
1114
1392
                        
1115
 
                        s = range_header + 6; // bytes=
 
1393
                        s = range_header + 6; /* bytes= */
1116
1394
                        range = atoll (s);
1117
1395
                        
1118
1396
                        result = gnome_vfs_seek (handle, GNOME_VFS_SEEK_START, range);
1119
1397
                        
1120
1398
                        if (result != GNOME_VFS_OK) {
1121
 
                                g_message ("Error seeking: %s", gnome_vfs_result_to_string (result));
 
1399
                                g_warning ("Error seeking: %s", gnome_vfs_result_to_string (result));
1122
1400
                                soup_message_set_status (message, SOUP_STATUS_INTERNAL_SERVER_ERROR);
1123
1401
                                goto out;
1124
1402
                        }
1140
1418
                soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (message), SOUP_TRANSFER_CHUNKED);
1141
1419
 
1142
1420
        } else {
1143
 
                g_print ("unhandled: %s\n", path);
 
1421
                rb_debug ("unhandled: %s\n", path);
1144
1422
        }
1145
1423
        
1146
1424
out:
1147
1425
        g_free (path);
1148
1426
}
1149
1427
 
1150
 
typedef void (* DAAPPathFunction) (RBDAAPShare *share, SoupMessage *message);
 
1428
typedef void (* DAAPPathFunction) (RBDAAPShare       *share,
 
1429
                                   SoupServerContext *context,
 
1430
                                   SoupMessage       *message);
1151
1431
 
1152
1432
struct DAAPPath {
1153
1433
        const gchar *path;
1159
1439
        {"/server-info", 12, server_info_cb},
1160
1440
        {"/content-codes", 14, content_codes_cb},
1161
1441
        {"/login", 6, login_cb},
 
1442
        {"/logout", 7, logout_cb},
1162
1443
        {"/update", 7, update_cb},
1163
1444
        {"/databases", 10, databases_cb}
1164
1445
};
1172
1453
        guint i;
1173
1454
 
1174
1455
        path = soup_uri_to_string (soup_message_get_uri (message), TRUE);
 
1456
        rb_debug ("request for %s", path);
1175
1457
 
1176
1458
        for (i = 0; i < G_N_ELEMENTS (paths_to_functions); i++) {
1177
1459
                if (g_ascii_strncasecmp (paths_to_functions[i].path, path, paths_to_functions[i].path_length) == 0) {
1178
 
                        paths_to_functions[i].function (share, message);
 
1460
                        paths_to_functions[i].function (share, context, message);
1179
1461
                        return;
1180
1462
                }
1181
1463
        }
1182
1464
 
1183
 
        g_warning ("unhandled path %s\n", soup_uri_to_string (soup_message_get_uri (message), TRUE));
 
1465
        g_warning ("unhandled path %s\n", path);
1184
1466
 
1185
1467
        g_free (path);
1186
1468
}
1190
1472
                   RhythmDBEntry *entry,
1191
1473
                   RBDAAPShare *share)
1192
1474
{
1193
 
        RhythmDBEntryType type = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TYPE);
 
1475
        RhythmDBEntryType type = rhythmdb_entry_get_entry_type (entry);
1194
1476
        gboolean hidden = rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN);
1195
1477
 
1196
1478
        if (type == rhythmdb_entry_song_get_type () && !hidden && g_hash_table_lookup (share->priv->entry_to_id, entry) == NULL) {
1227
1509
                     GSList *changes,
1228
1510
                     RBDAAPShare *share)
1229
1511
{
1230
 
        if (rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN))
 
1512
        if (rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN)) {
1231
1513
                db_entry_deleted_cb (db, entry, share);
1232
 
        else
 
1514
        } else {
1233
1515
                db_entry_added_cb (db, entry, share);
 
1516
        }
1234
1517
}
1235
1518
 
1236
 
 
1237
 
static gchar *
1238
 
publish_cb (RBDAAPmDNSPublisher publisher,
1239
 
            RBDAAPmDNSPublisherStatus status,
1240
 
            RBDAAPShare *share)
 
1519
static gboolean
 
1520
soup_auth_callback (SoupServerAuthContext *auth_ctx,
 
1521
                    SoupServerAuth        *auth,
 
1522
                    SoupMessage           *message,
 
1523
                    RBDAAPShare           *share)
1241
1524
{
1242
 
        switch (status) {
1243
 
                case RB_DAAP_MDNS_PUBLISHER_STARTED:
1244
 
                        rb_debug ("mDNS publish successful");
1245
 
                        share->priv->published = TRUE;
1246
 
                        break;
1247
 
                case RB_DAAP_MDNS_PUBLISHER_COLLISION: {
1248
 
                        gchar *new_name;
1249
 
                
1250
 
                        rb_debug ("Duplicate share name on mDNS");
1251
 
                
1252
 
                        new_name = rb_daap_collision_dialog_new_run (share->priv->name);
1253
 
                        return new_name;
 
1525
        const char *username;
 
1526
        gboolean    allowed;
 
1527
        char       *path;
 
1528
 
 
1529
        path = soup_uri_to_string (soup_message_get_uri (message), TRUE);
 
1530
        rb_debug ("Auth request for %s", path);
 
1531
 
 
1532
        if (auth == NULL) {
 
1533
 
 
1534
                /* This is to workaround libsoup looking up handlers by directory.
 
1535
                   We require auth for "/databases?" but not "/databases/" */
 
1536
                if (g_str_has_prefix (path, "/databases/")) {
 
1537
                        allowed = TRUE;
 
1538
                        goto done;
1254
1539
                }
1255
 
        
 
1540
 
 
1541
                rb_debug ("Auth DENIED: information not provided");
 
1542
                allowed = FALSE;
 
1543
                goto done;
1256
1544
        }
1257
1545
 
1258
 
        return NULL;
 
1546
        username = soup_server_auth_get_user (auth);
 
1547
        rb_debug ("Auth request for user: %s", username);
 
1548
 
 
1549
        allowed = soup_server_auth_check_passwd (auth, share->priv->password);
 
1550
        rb_debug ("Auth request: %s", allowed ? "ALLOWED" : "DENIED");
 
1551
 
 
1552
 done:
 
1553
        g_free (path);
 
1554
 
 
1555
        return allowed;
1259
1556
}
1260
1557
 
1261
 
static void
1262
 
rb_daap_share_start_publish (RBDAAPShare *share)
 
1558
static gboolean
 
1559
rb_daap_share_server_start (RBDAAPShare *share)
1263
1560
{
1264
 
        gint port = STANDARD_DAAP_PORT;
1265
 
        gboolean ret;
 
1561
        int                   port = STANDARD_DAAP_PORT;
 
1562
        gboolean              password_required;
 
1563
        SoupServerAuthContext auth_ctx = { 0 };
1266
1564
 
1267
1565
        share->priv->server = soup_server_new (SOUP_SERVER_PORT, port, NULL);
1268
1566
        if (share->priv->server == NULL) {
1271
1569
 
1272
1570
                if (share->priv->server == NULL) {
1273
1571
                        g_warning ("Unable to start music sharing server");
1274
 
                        return;
 
1572
                        return FALSE;
1275
1573
                }
1276
1574
        }
1277
1575
 
1278
 
        share->priv->port = soup_server_get_port (share->priv->server);
1279
 
        rb_debug ("Started DAAP server on port %d", port);
 
1576
        share->priv->port = (guint)soup_server_get_port (share->priv->server);
 
1577
        rb_debug ("Started DAAP server on port %u", share->priv->port);
 
1578
 
 
1579
        password_required = (share->priv->auth_method != RB_DAAP_SHARE_AUTH_METHOD_NONE);
 
1580
 
 
1581
        if (password_required) {
 
1582
                auth_ctx.types = SOUP_AUTH_TYPE_BASIC;
 
1583
                auth_ctx.callback = (SoupServerAuthCallbackFn)soup_auth_callback;
 
1584
                auth_ctx.user_data = share;
 
1585
                auth_ctx.basic_info.realm = "Music Sharing";
 
1586
 
 
1587
                soup_server_add_handler (share->priv->server, 
 
1588
                                         "/login",
 
1589
                                         &auth_ctx, 
 
1590
                                         (SoupServerCallbackFn)server_cb,
 
1591
                                         NULL,
 
1592
                                         share);
 
1593
                soup_server_add_handler (share->priv->server, 
 
1594
                                         "/update",
 
1595
                                         &auth_ctx, 
 
1596
                                         (SoupServerCallbackFn)server_cb,
 
1597
                                         NULL,
 
1598
                                         share);
 
1599
                soup_server_add_handler (share->priv->server,
 
1600
                                         "/databases",
 
1601
                                         &auth_ctx, 
 
1602
                                         (SoupServerCallbackFn)server_cb,
 
1603
                                         NULL,
 
1604
                                         share);
 
1605
        }
1280
1606
 
1281
1607
        soup_server_add_handler (share->priv->server, 
1282
1608
                                 NULL, 
1286
1612
                                 share);
1287
1613
        soup_server_run_async (share->priv->server);
1288
1614
        
1289
 
        ret = rb_daap_mdns_publish (&(share->priv->publisher),
1290
 
                                    share->priv->name,
1291
 
                                    share->priv->port,
1292
 
                                    (RBDAAPmDNSPublisherCallback) publish_cb,
1293
 
                                    share);
1294
 
        
1295
 
        if (ret == FALSE) {
1296
 
                g_warning ("Unable to notify network of music sharing");
1297
 
                return;
1298
 
        }
1299
 
 
1300
 
        rb_debug ("Published DAAP server information to mdns");
1301
1615
 
1302
1616
        share->priv->id_to_entry = g_hash_table_new (NULL, NULL);
1303
1617
        share->priv->entry_to_id = g_hash_table_new (NULL, NULL);
 
1618
        /* using direct since there is no g_uint_hash or g_uint_equal */
 
1619
        share->priv->session_ids = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
 
1620
 
1304
1621
        share->priv->next_playlist_id = 2;              /* 1 already used */
1305
1622
 
1306
1623
        rhythmdb_entry_foreach (share->priv->db, (GFunc)add_db_entry, share);
1317
1634
                                                          "entry-changed",
1318
1635
                                                          G_CALLBACK (db_entry_changed_cb),
1319
1636
                                                          share);
 
1637
 
 
1638
        share->priv->server_active = TRUE;
 
1639
 
 
1640
        return TRUE;
1320
1641
}
1321
1642
 
1322
 
static void
1323
 
rb_daap_share_stop_publish (RBDAAPShare *share)
 
1643
static gboolean
 
1644
rb_daap_share_server_stop (RBDAAPShare *share)
1324
1645
{
 
1646
        rb_debug ("Stopping music sharing server on port %d", share->priv->port);
 
1647
 
1325
1648
        if (share->priv->server) {
1326
 
                /* FIXME */
1327
 
                /* This will spew:
1328
 
                 * GLib-CRITICAL **: g_main_loop_quit: assertion `loop != NULL' failed
1329
 
                 * But it doesn't seem to matter.
1330
 
                 */
1331
 
//              soup_server_quit (share->priv->server);
1332
 
                g_object_unref (G_OBJECT (share->priv->server));
 
1649
                soup_server_quit (share->priv->server);
 
1650
                g_object_unref (share->priv->server);
1333
1651
                share->priv->server = NULL;
1334
1652
        }
1335
1653
        
1338
1656
                share->priv->id_to_entry = NULL;
1339
1657
        }
1340
1658
 
 
1659
        if (share->priv->session_ids) {
 
1660
                g_hash_table_destroy (share->priv->session_ids);
 
1661
                share->priv->session_ids = NULL;
 
1662
        }
 
1663
 
1341
1664
        if (share->priv->entry_to_id) {
1342
1665
                g_hash_table_destroy (share->priv->entry_to_id);
1343
1666
                share->priv->entry_to_id = NULL;
1358
1681
                share->priv->entry_changed_id = 0;
1359
1682
        }
1360
1683
 
 
1684
        share->priv->server_active = FALSE;
 
1685
 
 
1686
        return TRUE;
 
1687
}
 
1688
 
 
1689
static gboolean
 
1690
rb_daap_share_publish_start (RBDAAPShare *share)
 
1691
{
 
1692
        GError  *error;
 
1693
        gboolean res;
 
1694
        gboolean password_required;
 
1695
 
 
1696
        password_required = (share->priv->auth_method != RB_DAAP_SHARE_AUTH_METHOD_NONE);
 
1697
 
 
1698
        error = NULL;
 
1699
        res = rb_daap_mdns_publisher_publish (share->priv->publisher,
 
1700
                                              share->priv->name,
 
1701
                                              share->priv->port,
 
1702
                                              password_required,
 
1703
                                              &error);
 
1704
        
 
1705
        if (res == FALSE) {
 
1706
                if (error != NULL) {
 
1707
                        g_warning ("Unable to notify network of music sharing: %s", error->message);
 
1708
                        g_error_free (error);
 
1709
                } else {
 
1710
                        g_warning ("Unable to notify network of music sharing");
 
1711
                }
 
1712
                return FALSE;
 
1713
        } else {
 
1714
                rb_debug ("Published DAAP server information to mdns");
 
1715
        }
 
1716
 
 
1717
        return TRUE;
 
1718
}
 
1719
 
 
1720
static gboolean
 
1721
rb_daap_share_publish_stop (RBDAAPShare *share)
 
1722
{
1361
1723
        if (share->priv->publisher) {
1362
 
                rb_daap_mdns_publish_cancel (share->priv->publisher);
1363
 
                share->priv->publisher = 0;
 
1724
                gboolean res;
 
1725
                GError  *error;
 
1726
                error = NULL;
 
1727
                res = rb_daap_mdns_publisher_withdraw (share->priv->publisher, &error);
 
1728
                if (error != NULL) {
 
1729
                        g_warning ("Unable to withdraw music sharing service: %s", error->message);
 
1730
                        g_error_free (error);
 
1731
                }
 
1732
                return res;
1364
1733
        }
1365
1734
 
1366
1735
        share->priv->published = FALSE;
 
1736
        return TRUE;
 
1737
}
 
1738
 
 
1739
static void
 
1740
rb_daap_share_restart (RBDAAPShare *share)
 
1741
{
 
1742
        gboolean res;
 
1743
 
 
1744
        rb_daap_share_server_stop (share);
 
1745
        res = rb_daap_share_server_start (share);
 
1746
        if (res) {
 
1747
                /* To update information just publish again */
 
1748
                rb_daap_share_publish_start (share);
 
1749
        } else {
 
1750
                rb_daap_share_publish_stop (share);
 
1751
        }
 
1752
}
 
1753
 
 
1754
static void
 
1755
rb_daap_share_maybe_restart (RBDAAPShare *share)
 
1756
{
 
1757
        if (share->priv->published) {
 
1758
                rb_daap_share_restart (share);
 
1759
        }
1367
1760
}