~ubuntu-branches/ubuntu/utopic/rhythmbox/utopic-proposed

« back to all changes in this revision

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

Tags: upstream-0.9.2
ImportĀ upstreamĀ versionĀ 0.9.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *  Implementation of DAAP (iTunes Music Sharing) hashing, parsing, connection
 
3
 *
 
4
 *  Copyright (C) 2004,2005 Charles Schmidt <cschmidt2@emich.edu>
 
5
 *
 
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.
 
10
 *
 
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.
 
15
 *
 
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.
 
19
 *
 
20
 */
 
21
 
 
22
#include "rb-daap-connection.h"
 
23
#include "rb-daap-structure.h"
 
24
#include "rb-daap-dialog.h"
 
25
 
 
26
#include <libgnome/gnome-i18n.h>
 
27
#include "rb-debug.h"
 
28
 
 
29
#ifdef HAVE_LIBZ
 
30
#include <zlib.h>
 
31
#endif
 
32
 
 
33
/* hashing - based on/copied from libopendaap
 
34
 * Copyright (c) 2004 David Hammerton
 
35
 */
 
36
 
 
37
#include <stdio.h>
 
38
#include <string.h>
 
39
#include <sys/types.h>
 
40
 
 
41
typedef struct {
 
42
    guint32 buf[4];
 
43
    guint32 bits[2];
 
44
    unsigned char in[64];
 
45
    int version;
 
46
} MD5_CTX;
 
47
 
 
48
/*
 
49
* This code implements the MD5 message-digest algorithm.
 
50
* The algorithm is due to Ron Rivest.  This code was
 
51
* written by Colin Plumb in 1993, no copyright is claimed.
 
52
* This code is in the public domain; do with it what you wish.
 
53
*
 
54
* Equivalent code is available from RSA Data Security, Inc.
 
55
* This code has been tested against that, and is equivalent,
 
56
* except that you don't need to include two pages of legalese
 
57
* with every copy.
 
58
*
 
59
* To compute the message digest of a chunk of bytes, declare an MD5Context
 
60
* structure, pass it to OpenDaap_MD5Init, call OpenDaap_MD5Update as needed
 
61
* on buffers full of bytes, and then call OpenDaap_MD5Final, which will fill
 
62
* a supplied 16-byte array with the digest.
 
63
*/
 
64
static void 
 
65
MD5Transform (guint32 buf[4], 
 
66
              guint32 const in[16], 
 
67
              gint version);
 
68
/* for some reason we still have to reverse bytes on bigendian machines
 
69
 * I don't really know why... but otherwise it fails..
 
70
 * Any MD5 gurus out there know why???
 
71
 */
 
72
#if 0 //ndef WORDS_BIGENDIAN /* was: HIGHFIRST */
 
73
#define byteReverse(buf, len)     /* Nothing */
 
74
#else
 
75
static void 
 
76
byteReverse (unsigned char *buf, 
 
77
             unsigned longs);
 
78
 
 
79
#ifndef ASM_MD5
 
80
/*
 
81
* Note: this code is harmless on little-endian machines.
 
82
*/
 
83
static void 
 
84
byteReverse (unsigned char *buf, 
 
85
             unsigned longs)
 
86
{
 
87
     guint32 t;
 
88
     do {
 
89
          t = (guint32) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
 
90
               ((unsigned) buf[1] << 8 | buf[0]);
 
91
          *(guint32 *) buf = t;
 
92
          buf += 4;
 
93
     } while (--longs);
 
94
}
 
95
#endif /* ! ASM_MD5 */
 
96
#endif /* #if 0 */
 
97
 
 
98
static void 
 
99
OpenDaap_MD5Init (MD5_CTX *ctx, 
 
100
                  gint version)
 
101
{
 
102
    memset (ctx, 0, sizeof (MD5_CTX));
 
103
    ctx->buf[0] = 0x67452301;
 
104
    ctx->buf[1] = 0xefcdab89;
 
105
    ctx->buf[2] = 0x98badcfe;
 
106
    ctx->buf[3] = 0x10325476;
 
107
 
 
108
    ctx->bits[0] = 0;
 
109
    ctx->bits[1] = 0;
 
110
 
 
111
    ctx->version = version;
 
112
}
 
113
 
 
114
static void 
 
115
OpenDaap_MD5Update (MD5_CTX *ctx, 
 
116
                    unsigned char const *buf, 
 
117
                    unsigned int len)
 
118
{
 
119
    guint32 t;
 
120
 
 
121
    /* Update bitcount */
 
122
 
 
123
    t = ctx->bits[0];
 
124
    if ((ctx->bits[0] = t + ((guint32) len << 3)) < t)
 
125
        ctx->bits[1]++;          /* Carry from low to high */
 
126
    ctx->bits[1] += len >> 29;
 
127
 
 
128
    t = (t >> 3) & 0x3f;     /* Bytes already in shsInfo->data */
 
129
 
 
130
    /* Handle any leading odd-sized chunks */
 
131
 
 
132
    if (t) {
 
133
        unsigned char *p = (unsigned char *) ctx->in + t;
 
134
 
 
135
        t = 64 - t;
 
136
        if (len < t) {
 
137
            memcpy (p, buf, len);
 
138
            return;
 
139
        }
 
140
        memcpy (p, buf, t);
 
141
        byteReverse (ctx->in, 16);
 
142
        MD5Transform (ctx->buf, (guint32 *) ctx->in, ctx->version);
 
143
        buf += t;
 
144
        len -= t;
 
145
    }
 
146
    /* Process data in 64-byte chunks */
 
147
 
 
148
    while (len >= 64) {
 
149
        memcpy (ctx->in, buf, 64);
 
150
        byteReverse (ctx->in, 16);
 
151
        MD5Transform (ctx->buf, (guint32 *) ctx->in, ctx->version);
 
152
        buf += 64;
 
153
        len -= 64;
 
154
    }
 
155
 
 
156
    /* Handle any remaining bytes of data. */
 
157
 
 
158
    memcpy (ctx->in, buf, len);
 
159
 
 
160
}
 
161
 
 
162
static void 
 
163
OpenDaap_MD5Final (MD5_CTX *ctx, 
 
164
                   unsigned char digest[16])
 
165
{
 
166
    unsigned count;
 
167
    unsigned char *p;
 
168
 
 
169
    /* Compute number of bytes mod 64 */
 
170
    count = (ctx->bits[0] >> 3) & 0x3F;
 
171
 
 
172
    /* Set the first char of padding to 0x80.  This is safe since there is
 
173
    always at least one byte free */
 
174
    p = ctx->in + count;
 
175
    *p++ = 0x80;
 
176
 
 
177
    /* Bytes of padding needed to make 64 bytes */
 
178
    count = 64 - 1 - count;
 
179
 
 
180
    /* Pad out to 56 mod 64 */
 
181
    if (count < 8) {
 
182
        /* Two lots of padding:  Pad the first block to 64 bytes */
 
183
        memset (p, 0, count);
 
184
        byteReverse (ctx->in, 16);
 
185
        MD5Transform (ctx->buf, (guint32 *) ctx->in, ctx->version);
 
186
 
 
187
        /* Now fill the next block with 56 bytes */
 
188
        memset (ctx->in, 0, 56);
 
189
    } else {
 
190
        /* Pad block to 56 bytes */
 
191
        memset (p, 0, count - 8);
 
192
    }
 
193
    byteReverse (ctx->in, 14);
 
194
 
 
195
    /* Append length in bits and transform */
 
196
    ((guint32 *) ctx->in)[14] = ctx->bits[0];
 
197
    ((guint32 *) ctx->in)[15] = ctx->bits[1];
 
198
 
 
199
    MD5Transform (ctx->buf, (guint32 *) ctx->in, ctx->version);
 
200
    byteReverse ((unsigned char *) ctx->buf, 4);
 
201
    memcpy (digest, ctx->buf, 16);
 
202
    memset (ctx, 0, sizeof(ctx));     /* In case it's sensitive */
 
203
 
 
204
    return;
 
205
}
 
206
 
 
207
#ifndef ASM_MD5
 
208
 
 
209
/* The four core functions - F1 is optimized somewhat */
 
210
 
 
211
/* #define F1(x, y, z) (x & y | ~x & z) */
 
212
#define F1(x, y, z) (z ^ (x & (y ^ z)))
 
213
#define F2(x, y, z) F1(z, x, y)
 
214
#define F3(x, y, z) (x ^ y ^ z)
 
215
#define F4(x, y, z) (y ^ (x | ~z))
 
216
 
 
217
/* This is the central step in the MD5 algorithm. */
 
218
#define MD5STEP(f, w, x, y, z, data, s) \
 
219
( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )
 
220
 
 
221
/*
 
222
* The core of the MD5 algorithm, this alters an existing MD5 hash to reflect
 
223
* the addition of 16 longwords of new data.  OpenDaap_MD5Update blocks the
 
224
* data and converts bytes into longwords for this routine.
 
225
*/
 
226
static void 
 
227
MD5Transform (guint32 buf[4], 
 
228
              guint32 const in[16], 
 
229
              gint version)
 
230
{
 
231
    guint32 a, b, c, d;
 
232
 
 
233
    a = buf[0];
 
234
    b = buf[1];
 
235
    c = buf[2];
 
236
    d = buf[3];
 
237
 
 
238
    MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
 
239
    MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
 
240
    MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
 
241
    MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
 
242
    MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
 
243
    MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
 
244
    MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
 
245
    MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
 
246
    MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
 
247
    MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
 
248
    MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
 
249
    MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
 
250
    MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
 
251
    MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
 
252
    MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
 
253
    MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
 
254
 
 
255
    MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
 
256
    MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
 
257
    MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
 
258
    MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
 
259
    MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
 
260
    MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
 
261
    MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
 
262
    MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
 
263
    MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
 
264
    MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
 
265
    MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
 
266
 
 
267
    if (version == 1)
 
268
    {
 
269
        MD5STEP(F2, b, c, d, a, in[8] + 0x445a14ed, 20);
 
270
    }
 
271
    else
 
272
    {
 
273
        MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
 
274
    }
 
275
    MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
 
276
    MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
 
277
    MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
 
278
    MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
 
279
 
 
280
    MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
 
281
    MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
 
282
    MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
 
283
    MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
 
284
    MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
 
285
    MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
 
286
    MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
 
287
    MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
 
288
    MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
 
289
    MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
 
290
    MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
 
291
    MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
 
292
    MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
 
293
    MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
 
294
    MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
 
295
    MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
 
296
 
 
297
    MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
 
298
    MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
 
299
    MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
 
300
    MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
 
301
    MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
 
302
    MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
 
303
    MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
 
304
    MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
 
305
    MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
 
306
    MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
 
307
    MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
 
308
    MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
 
309
    MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
 
310
    MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
 
311
    MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
 
312
    MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
 
313
 
 
314
    buf[0] += a;
 
315
    buf[1] += b;
 
316
    buf[2] += c;
 
317
    buf[3] += d;
 
318
}
 
319
 
 
320
#endif
 
321
 
 
322
 
 
323
 
 
324
 
 
325
 
 
326
static int staticHashDone = 0;
 
327
static unsigned char staticHash_42[256*65] = {0};
 
328
static unsigned char staticHash_45[256*65] = {0};
 
329
 
 
330
static const char hexchars[] = "0123456789ABCDEF";
 
331
static char ac[] = "Dpqzsjhiu!3114!Bqqmf!Dpnqvufs-!Jod/"; /* +1 */
 
332
static gboolean ac_unfudged = FALSE;
 
333
 
 
334
static void 
 
335
DigestToString (const unsigned char *digest, 
 
336
                char *string)
 
337
{
 
338
    int i;
 
339
    for (i = 0; i < 16; i++)
 
340
    {
 
341
        unsigned char tmp = digest[i];
 
342
        string[i*2+1] = hexchars[tmp & 0x0f];
 
343
        string[i*2] = hexchars[(tmp >> 4) & 0x0f];
 
344
    }
 
345
}
 
346
 
 
347
static void 
 
348
GenerateStatic_42 ()
 
349
{
 
350
    MD5_CTX ctx;
 
351
    unsigned char *p = staticHash_42;
 
352
    int i;
 
353
    unsigned char buf[16];
 
354
 
 
355
    for (i = 0; i < 256; i++)
 
356
    {
 
357
        OpenDaap_MD5Init (&ctx, 0);
 
358
 
 
359
#define MD5_STRUPDATE(str) OpenDaap_MD5Update(&ctx, (unsigned char const *)str, strlen(str))
 
360
 
 
361
        if ((i & 0x80) != 0)
 
362
            MD5_STRUPDATE("Accept-Language");
 
363
        else
 
364
            MD5_STRUPDATE("user-agent");
 
365
 
 
366
        if ((i & 0x40) != 0)
 
367
            MD5_STRUPDATE("max-age");
 
368
        else
 
369
            MD5_STRUPDATE("Authorization");
 
370
 
 
371
        if ((i & 0x20) != 0)
 
372
            MD5_STRUPDATE("Client-DAAP-Version");
 
373
        else
 
374
            MD5_STRUPDATE("Accept-Encoding");
 
375
 
 
376
        if ((i & 0x10) != 0)
 
377
            MD5_STRUPDATE("daap.protocolversion");
 
378
        else
 
379
            MD5_STRUPDATE("daap.songartist");
 
380
 
 
381
        if ((i & 0x08) != 0)
 
382
            MD5_STRUPDATE("daap.songcomposer");
 
383
        else
 
384
            MD5_STRUPDATE("daap.songdatemodified");
 
385
 
 
386
        if ((i & 0x04) != 0)
 
387
            MD5_STRUPDATE("daap.songdiscnumber");
 
388
        else
 
389
            MD5_STRUPDATE("daap.songdisabled");
 
390
 
 
391
        if ((i & 0x02) != 0)
 
392
            MD5_STRUPDATE("playlist-item-spec");
 
393
        else
 
394
            MD5_STRUPDATE("revision-number");
 
395
 
 
396
        if ((i & 0x01) != 0)
 
397
            MD5_STRUPDATE("session-id");
 
398
        else
 
399
            MD5_STRUPDATE("content-codes");
 
400
#undef MD5_STRUPDATE
 
401
 
 
402
        OpenDaap_MD5Final (&ctx, buf);
 
403
        DigestToString (buf, (char *)p);
 
404
        p += 65;
 
405
    }
 
406
}
 
407
 
 
408
static void GenerateStatic_45()
 
409
{
 
410
    MD5_CTX ctx;
 
411
    unsigned char *p = staticHash_45;
 
412
    int i;
 
413
    unsigned char buf[16];
 
414
 
 
415
    for (i = 0; i < 256; i++)
 
416
    {
 
417
        OpenDaap_MD5Init (&ctx, 1);
 
418
 
 
419
#define MD5_STRUPDATE(str) OpenDaap_MD5Update(&ctx, (unsigned char const *)str, strlen(str))
 
420
 
 
421
        if ((i & 0x40) != 0)
 
422
            MD5_STRUPDATE("eqwsdxcqwesdc");
 
423
        else
 
424
            MD5_STRUPDATE("op[;lm,piojkmn");
 
425
 
 
426
        if ((i & 0x20) != 0)
 
427
            MD5_STRUPDATE("876trfvb 34rtgbvc");
 
428
        else
 
429
            MD5_STRUPDATE("=-0ol.,m3ewrdfv");
 
430
 
 
431
        if ((i & 0x10) != 0)
 
432
            MD5_STRUPDATE("87654323e4rgbv ");
 
433
        else
 
434
            MD5_STRUPDATE("1535753690868867974342659792");
 
435
 
 
436
        if ((i & 0x08) != 0)
 
437
            MD5_STRUPDATE("Song Name");
 
438
        else
 
439
            MD5_STRUPDATE("DAAP-CLIENT-ID:");
 
440
 
 
441
        if ((i & 0x04) != 0)
 
442
            MD5_STRUPDATE("111222333444555");
 
443
        else
 
444
            MD5_STRUPDATE("4089961010");
 
445
 
 
446
        if ((i & 0x02) != 0)
 
447
            MD5_STRUPDATE("playlist-item-spec");
 
448
        else
 
449
            MD5_STRUPDATE("revision-number");
 
450
 
 
451
        if ((i & 0x01) != 0)
 
452
            MD5_STRUPDATE("session-id");
 
453
        else
 
454
            MD5_STRUPDATE("content-codes");
 
455
 
 
456
        if ((i & 0x80) != 0)
 
457
            MD5_STRUPDATE("IUYHGFDCXWEDFGHN");
 
458
        else
 
459
            MD5_STRUPDATE("iuytgfdxwerfghjm");
 
460
 
 
461
#undef MD5_STRUPDATE
 
462
 
 
463
        OpenDaap_MD5Final (&ctx, buf);
 
464
        DigestToString (buf, (char *)p);
 
465
        p += 65;
 
466
    }
 
467
}
 
468
 
 
469
static void 
 
470
rb_daap_hash_generate (short version_major, 
 
471
                       const guchar *url, 
 
472
                       guchar hash_select, 
 
473
                       guchar *out, 
 
474
                       gint request_id)
 
475
{
 
476
    unsigned char buf[16];
 
477
    MD5_CTX ctx;
 
478
    int i;
 
479
    
 
480
    unsigned char *hashTable = (version_major == 3) ?
 
481
                      staticHash_45 : staticHash_42;
 
482
 
 
483
    if (!staticHashDone)
 
484
    {
 
485
        GenerateStatic_42 ();
 
486
        GenerateStatic_45 ();
 
487
        staticHashDone = 1;
 
488
    }
 
489
 
 
490
    OpenDaap_MD5Init (&ctx, (version_major == 3) ? 1 : 0);
 
491
 
 
492
    OpenDaap_MD5Update (&ctx, url, strlen ((const gchar*)url));
 
493
    if (ac_unfudged == FALSE) {
 
494
            for (i = 0; i < strlen (ac); i++) {
 
495
                ac[i] = ac[i]-1;
 
496
            }
 
497
            ac_unfudged = TRUE;
 
498
    }
 
499
    OpenDaap_MD5Update (&ctx, (const guchar*)ac, strlen (ac));
 
500
 
 
501
    OpenDaap_MD5Update (&ctx, &hashTable[hash_select * 65], 32);
 
502
 
 
503
    if (request_id && version_major == 3)
 
504
    {
 
505
        char scribble[20];
 
506
        sprintf (scribble, "%u", request_id);
 
507
        OpenDaap_MD5Update (&ctx, (const guchar*)scribble, strlen (scribble));
 
508
    }
 
509
 
 
510
    OpenDaap_MD5Final (&ctx, buf);
 
511
    DigestToString (buf, (char *)out);
 
512
 
 
513
    return;
 
514
}
 
515
 
 
516
/* end hashing */
 
517
 
 
518
 
 
519
/* connection */
 
520
#include <math.h>
 
521
#include <libsoup/soup.h>
 
522
#include <libsoup/soup-connection.h>
 
523
#include <libsoup/soup-session-sync.h>
 
524
 
 
525
#include <libsoup/soup-uri.h>
 
526
 
 
527
#define RB_DAAP_USER_AGENT "iTunes/4.6 (Windows; N)"
 
528
 
 
529
 
 
530
static GObject * rb_daap_connection_constructor (GType type, guint n_construct_properties,
 
531
                                                 GObjectConstructParam *construct_properties);
 
532
static void rb_daap_connection_dispose (GObject *obj);
 
533
static void rb_daap_connection_set_property (GObject *object,
 
534
                                             guint prop_id,
 
535
                                             const GValue *value,
 
536
                                             GParamSpec *pspec);
 
537
static void rb_daap_connection_get_property (GObject *object,
 
538
                                             guint prop_id,
 
539
                                             GValue *value,
 
540
                                             GParamSpec *pspec);
 
541
 
 
542
static void rb_daap_connection_do_something (RBDAAPConnection *connection);
 
543
static void rb_daap_connection_state_done (RBDAAPConnection *connection, gboolean result);
 
544
 
 
545
 
 
546
G_DEFINE_TYPE (RBDAAPConnection, rb_daap_connection, G_TYPE_OBJECT)
 
547
#define DAAP_CONNECTION_GET_PRIVATE(o)   (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_DAAP_CONNECTION, RBDaapConnectionPrivate))
 
548
 
 
549
typedef void (*RBDAAPResponseHandler) (RBDAAPConnection *connection,
 
550
                                       guint status,
 
551
                                       GNode *structure);
 
552
 
 
553
typedef struct {
 
554
        gchar *name;
 
555
        gboolean password_protected;
 
556
        gchar *password;
 
557
        char *host;
 
558
        guint port;
 
559
        
 
560
        SoupSession *session;
 
561
        SoupUri *base_uri;
 
562
        gchar *daap_base_uri;
 
563
        
 
564
        gdouble daap_version;
 
565
        gint session_id;
 
566
        gint revision_number;
 
567
 
 
568
        gint request_id;
 
569
        gint database_id;
 
570
 
 
571
        guint reading_playlist;
 
572
        GSList *playlists;
 
573
        GHashTable *item_id_to_uri;
 
574
 
 
575
        RhythmDB *db;
 
576
        RhythmDBEntryType db_type;
 
577
 
 
578
        enum {
 
579
                DAAP_GET_INFO = 0,
 
580
                DAAP_GET_PASSWORD,
 
581
                DAAP_LOGIN,
 
582
                DAAP_GET_REVISION_NUMBER,
 
583
                DAAP_GET_DB_INFO,
 
584
                DAAP_GET_SONGS,
 
585
                DAAP_GET_PLAYLISTS,
 
586
                DAAP_GET_PLAYLIST_ENTRIES,
 
587
                DAAP_LOGOUT,
 
588
                DAAP_DONE
 
589
        } state;
 
590
        RBDAAPResponseHandler response_handler;
 
591
 
 
592
        gboolean result;
 
593
        RBDAAPConnectionCallback callback;
 
594
        gpointer callback_user_data;
 
595
} RBDaapConnectionPrivate;
 
596
 
 
597
 
 
598
enum {
 
599
        PROP_0,
 
600
        PROP_DB,
 
601
        PROP_NAME,
 
602
        PROP_CALLBACK,
 
603
        PROP_CALLBACK_DATA,
 
604
        PROP_ENTRY_TYPE,
 
605
        PROP_PASSWORD_PROTECTED,
 
606
        PROP_HOST,
 
607
        PROP_PORT,
 
608
};
 
609
 
 
610
static void
 
611
rb_daap_connection_class_init (RBDAAPConnectionClass *klass)
 
612
{
 
613
        GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
614
 
 
615
        object_class->constructor = rb_daap_connection_constructor;
 
616
        object_class->dispose = rb_daap_connection_dispose;
 
617
        object_class->set_property = rb_daap_connection_set_property;
 
618
        object_class->get_property = rb_daap_connection_get_property;
 
619
 
 
620
        g_type_class_add_private (klass, sizeof (RBDaapConnectionPrivate));
 
621
 
 
622
        g_object_class_install_property (object_class,
 
623
                                         PROP_DB,
 
624
                                         g_param_spec_object ("db",
 
625
                                                              "RhythmDB",
 
626
                                                              "RhythmDB object",
 
627
                                                              RHYTHMDB_TYPE,
 
628
                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
629
        g_object_class_install_property (object_class,
 
630
                                         PROP_CALLBACK,
 
631
                                         g_param_spec_pointer ("callback",
 
632
                                                              "callback",
 
633
                                                              "callback function",
 
634
                                                               G_PARAM_READWRITE));
 
635
 
 
636
        g_object_class_install_property (object_class,
 
637
                                         PROP_CALLBACK_DATA,
 
638
                                         g_param_spec_pointer ("callback-data",
 
639
                                                              "Callback Data",
 
640
                                                              "callback user data",
 
641
                                                               G_PARAM_READWRITE));
 
642
        g_object_class_install_property (object_class,
 
643
                                         PROP_ENTRY_TYPE,
 
644
                                         g_param_spec_uint ("entry-type",
 
645
                                                            "entry type",
 
646
                                                            "RhythmDBEntryType",
 
647
                                                            0, G_MAXINT, 0,
 
648
                                                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
649
        g_object_class_install_property (object_class,
 
650
                                         PROP_PASSWORD_PROTECTED,
 
651
                                         g_param_spec_boolean ("password-protected",
 
652
                                                               "password protected",
 
653
                                                               "connection is password protected",
 
654
                                                               FALSE,
 
655
                                                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
656
        g_object_class_install_property (object_class,
 
657
                                         PROP_NAME,
 
658
                                         g_param_spec_string ("name",
 
659
                                                              "connection name",
 
660
                                                              "connection name",
 
661
                                                              NULL,
 
662
                                                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
663
        g_object_class_install_property (object_class,
 
664
                                         PROP_HOST,
 
665
                                         g_param_spec_string ("host",
 
666
                                                              "host",
 
667
                                                              "host",
 
668
                                                              NULL,
 
669
                                                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
670
        g_object_class_install_property (object_class,
 
671
                                         PROP_PORT,
 
672
                                         g_param_spec_uint ("port",
 
673
                                                            "port",
 
674
                                                            "port",
 
675
                                                            0, G_MAXINT, 0,
 
676
                                                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
677
}
 
678
 
 
679
static void
 
680
rb_daap_connection_init (RBDAAPConnection *connection)
 
681
{
 
682
 
 
683
}
 
684
 
 
685
 
 
686
static gchar *
 
687
connection_get_password (RBDAAPConnection *connection)
 
688
{
 
689
        RBDaapConnectionPrivate *priv = DAAP_CONNECTION_GET_PRIVATE (connection);
 
690
 
 
691
        return rb_daap_password_dialog_new_run (priv->name);
 
692
}
 
693
 
 
694
 
 
695
static SoupMessage * 
 
696
build_message (RBDAAPConnection *connection, 
 
697
               const gchar *path, 
 
698
               gboolean need_hash, 
 
699
               gdouble version, 
 
700
               gint req_id, 
 
701
               gboolean send_close)
 
702
{
 
703
        RBDaapConnectionPrivate *priv = DAAP_CONNECTION_GET_PRIVATE (connection);
 
704
        SoupMessage *message = NULL;
 
705
        SoupUri *uri = NULL;
 
706
        
 
707
        uri = soup_uri_new_with_base (priv->base_uri, path);
 
708
        if (uri == NULL) {
 
709
                return NULL;
 
710
        }
 
711
        
 
712
        message = soup_message_new_from_uri (SOUP_METHOD_GET, uri);
 
713
        soup_message_set_http_version (message, SOUP_HTTP_1_1);
 
714
        
 
715
        soup_message_add_header (message->request_headers, "Client-DAAP-Version",       "3.0");
 
716
        soup_message_add_header (message->request_headers, "Accept-Language",           "en-us, en;q=5.0");
 
717
#ifdef HAVE_LIBZ
 
718
        soup_message_add_header (message->request_headers, "Accept-Encoding",           "gzip");
 
719
#endif
 
720
        soup_message_add_header (message->request_headers, "Client-DAAP-Access-Index",  "2");
 
721
        if (priv->password_protected) {
 
722
                gchar *h = g_strconcat ("Basic ", priv->password, NULL);
 
723
                
 
724
                soup_message_add_header (message->request_headers, "Authorization", h);
 
725
                g_free (h);
 
726
        }
 
727
        
 
728
        if (need_hash) {
 
729
                gchar hash[33] = {0};
 
730
                gchar *no_daap_path = (gchar *)path;
 
731
                
 
732
                if (g_strncasecmp (path, "daap://", 7) == 0) {
 
733
                        no_daap_path = strstr (path, "/data");
 
734
                }
 
735
 
 
736
                rb_daap_hash_generate ((short)floor (version), (const guchar*)no_daap_path, 2, (guchar*)hash, req_id);
 
737
 
 
738
                soup_message_add_header (message->request_headers, "Client-DAAP-Validation", hash);
 
739
        }
 
740
        if (send_close) {
 
741
                soup_message_add_header (message->request_headers, "Connection", "close");
 
742
        }
 
743
 
 
744
        soup_uri_free (uri);
 
745
        
 
746
        return message;
 
747
}
 
748
 
 
749
#ifdef HAVE_LIBZ
 
750
static void *g_zalloc_wrapper (voidpf opaque, uInt items, uInt size)
 
751
{
 
752
        return g_malloc0 (items * size);
 
753
}
 
754
 
 
755
static void g_zfree_wrapper (voidpf opaque, voidpf address)
 
756
{
 
757
        g_free (address);
 
758
}
 
759
#endif
 
760
 
 
761
static void
 
762
http_response_handler (SoupMessage *message,
 
763
                       RBDAAPConnection *connection)
 
764
{
 
765
        RBDaapConnectionPrivate *priv = DAAP_CONNECTION_GET_PRIVATE (connection);
 
766
        GNode *structure = NULL;
 
767
        guint status = message->status_code;
 
768
        char *response = message->response.body;
 
769
        int response_length = message->response.length;
 
770
        const char *encoding_header = NULL;
 
771
 
 
772
        if (message->response_headers)
 
773
                encoding_header = soup_message_get_header (message->response_headers, "Content-Encoding");
 
774
 
 
775
        if (SOUP_STATUS_IS_SUCCESSFUL (status) && encoding_header && strcmp(encoding_header, "gzip") == 0) {
 
776
#ifdef HAVE_LIBZ
 
777
                z_stream stream;
 
778
                char *new_response;
 
779
                int factor = 4;
 
780
                int unc_size = response_length * factor;
 
781
 
 
782
                stream.next_in = (unsigned char *)response;
 
783
                stream.avail_in = response_length;
 
784
                stream.total_in = 0;
 
785
 
 
786
                new_response = g_malloc (unc_size + 1);
 
787
                stream.next_out = (unsigned char *)new_response;
 
788
                stream.avail_out = unc_size;
 
789
                stream.total_out = 0;
 
790
                stream.zalloc = g_zalloc_wrapper;
 
791
                stream.zfree = g_zfree_wrapper;
 
792
                stream.opaque = NULL;
 
793
 
 
794
                if (inflateInit2 (&stream, 32 /* auto-detect */ + 15 /* max */ ) != Z_OK) {
 
795
                        inflateEnd (&stream);
 
796
                        g_free (new_response);
 
797
                        rb_debug ("Unable to decompress response from http://%s:%d/%s",
 
798
                                  priv->base_uri->host,
 
799
                                  priv->base_uri->port,
 
800
                                  priv->base_uri->path);
 
801
                        status = SOUP_STATUS_MALFORMED;
 
802
                } else {
 
803
                        do {
 
804
                                int z_res = inflate (&stream, Z_FINISH);
 
805
                                if (z_res == Z_STREAM_END)
 
806
                                        break;
 
807
                                if ((z_res != Z_OK && z_res != Z_BUF_ERROR) || stream.avail_out != 0 || unc_size > 40*1000*1000) {
 
808
                                        inflateEnd (&stream);
 
809
                                        g_free (new_response);
 
810
                                        new_response = NULL;
 
811
                                        break;
 
812
                                }
 
813
 
 
814
                                factor *= 4;
 
815
                                unc_size = (response_length * factor);
 
816
                                new_response = g_realloc (new_response, unc_size + 1);
 
817
                                stream.next_out = (unsigned char *)(new_response + stream.total_out);
 
818
                                stream.avail_out = unc_size - stream.total_out;
 
819
                        } while (1);
 
820
                }
 
821
 
 
822
                if (new_response) {
 
823
                        response = new_response;
 
824
                        response_length = stream.total_out;
 
825
                }
 
826
#else
 
827
                rb_debug ("Received compressed response from http://%s:%d/%s but can't handle it",
 
828
                          priv->base_uri->host,
 
829
                          priv->base_uri->port,
 
830
                          priv->base_uri->path);
 
831
                status = SOUP_STATUS_MALFORMED;
 
832
#endif
 
833
        }
 
834
 
 
835
        if (SOUP_STATUS_IS_SUCCESSFUL (status)) {
 
836
                RBDAAPItem *item;
 
837
 
 
838
                structure = rb_daap_structure_parse (response, response_length);
 
839
                if (structure == NULL) {
 
840
                        rb_debug ("No daap structure returned from http://%s:%d/%s", 
 
841
                                  priv->base_uri->host,
 
842
                                  priv->base_uri->port,
 
843
                                  priv->base_uri->path);
 
844
                        status = SOUP_STATUS_MALFORMED;
 
845
                } else {
 
846
                        int dmap_status = 0;
 
847
                        item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MSTT);
 
848
                        if (item)
 
849
                                dmap_status = g_value_get_int (&(item->content));
 
850
 
 
851
                        if (dmap_status != 200) {
 
852
                                rb_debug ("Error, dmap.status is not 200 in response from http://%s:%d/%s",
 
853
                                          priv->base_uri->host,
 
854
                                          priv->base_uri->port,
 
855
                                          priv->base_uri->path);
 
856
                                status = SOUP_STATUS_MALFORMED;
 
857
                        }
 
858
                }
 
859
        } else {
 
860
                rb_debug ("Error getting http://%s:%d/%s: %d, %s\n", 
 
861
                          priv->base_uri->host,
 
862
                          priv->base_uri->port,
 
863
                          priv->base_uri->path, 
 
864
                          message->status_code, message->reason_phrase);
 
865
        }
 
866
 
 
867
        if (priv->response_handler) {
 
868
                RBDAAPResponseHandler h = priv->response_handler;
 
869
                priv->response_handler = NULL;
 
870
                (*h) (connection, status, structure);
 
871
        }
 
872
 
 
873
        if (structure)
 
874
                rb_daap_structure_destroy (structure);
 
875
 
 
876
        if (response != message->response.body)
 
877
                g_free (response);
 
878
}
 
879
 
 
880
static gboolean
 
881
http_get (RBDAAPConnection *connection, 
 
882
          const gchar *path, 
 
883
          gboolean need_hash, 
 
884
          gdouble version, 
 
885
          gint req_id, 
 
886
          gboolean send_close,
 
887
          RBDAAPResponseHandler handler)
 
888
{
 
889
        RBDaapConnectionPrivate *priv = DAAP_CONNECTION_GET_PRIVATE (connection);
 
890
        SoupMessage *message;
 
891
       
 
892
        message = build_message (connection, path, need_hash, version, req_id, send_close);
 
893
        if (message == NULL) {
 
894
                rb_debug ("Error building message for http://%s:%d/%s", 
 
895
                          priv->base_uri->host,
 
896
                          priv->base_uri->port,
 
897
                          path);
 
898
                return FALSE;
 
899
        }
 
900
        
 
901
        priv->response_handler = handler;
 
902
        soup_session_queue_message (priv->session, message,
 
903
                                    (SoupMessageCallbackFn) http_response_handler, 
 
904
                                    connection);
 
905
        rb_debug ("Queued message for http://%s:%d/%s",
 
906
                  priv->base_uri->host,
 
907
                  priv->base_uri->port,
 
908
                  path);
 
909
        return TRUE;
 
910
}
 
911
 
 
912
 
 
913
static void 
 
914
entry_set_string_prop (RhythmDB *db, 
 
915
                       RhythmDBEntry *entry,
 
916
                       RhythmDBPropType propid, 
 
917
                       const char *str)
 
918
{
 
919
        GValue value = {0,};
 
920
        gchar *tmp;
 
921
 
 
922
        if (str == NULL) {
 
923
                tmp = g_strdup (_("Unknown"));
 
924
        } else {
 
925
                tmp = g_strdup (str);
 
926
        }
 
927
 
 
928
        g_value_init (&value, G_TYPE_STRING);
 
929
        g_value_set_string_take_ownership (&value, tmp);
 
930
        rhythmdb_entry_set_uninserted (RHYTHMDB (db), entry, propid, &value);
 
931
        g_value_unset (&value);
 
932
}
 
933
 
 
934
static void
 
935
handle_server_info (RBDAAPConnection *connection, guint status, GNode *structure)
 
936
{
 
937
        RBDaapConnectionPrivate *priv = DAAP_CONNECTION_GET_PRIVATE (connection);
 
938
        RBDAAPItem *item = NULL;
 
939
 
 
940
        if (!SOUP_STATUS_IS_SUCCESSFUL (status) || structure == NULL) {
 
941
                rb_daap_connection_state_done (connection, FALSE);
 
942
                return;
 
943
        }
 
944
        
 
945
        /* get the daap version number */
 
946
        item = rb_daap_structure_find_item (structure, RB_DAAP_CC_APRO);
 
947
        if (item == NULL) {
 
948
                rb_daap_connection_state_done (connection, FALSE);
 
949
                return;
 
950
        }
 
951
 
 
952
        priv->daap_version = g_value_get_double (&(item->content));
 
953
        rb_daap_connection_state_done (connection, TRUE);
 
954
}
 
955
 
 
956
static void
 
957
handle_login (RBDAAPConnection *connection, guint status, GNode *structure)
 
958
{
 
959
        RBDaapConnectionPrivate *priv = DAAP_CONNECTION_GET_PRIVATE (connection);
 
960
        RBDAAPItem *item = NULL;
 
961
 
 
962
        if (status == SOUP_STATUS_UNAUTHORIZED || status == SOUP_STATUS_FORBIDDEN) {
 
963
                rb_debug ("Incorrect password");
 
964
                priv->state = DAAP_GET_PASSWORD;
 
965
                rb_daap_connection_do_something (connection);
 
966
        }
 
967
 
 
968
        if (structure == NULL || SOUP_STATUS_IS_SUCCESSFUL (status) == FALSE) {
 
969
                rb_daap_connection_state_done (connection, FALSE);
 
970
                return;
 
971
        }
 
972
 
 
973
        item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MLID);
 
974
        if (item == NULL) {
 
975
                rb_debug ("Could not find daap.sessionid item in /login");
 
976
                rb_daap_connection_state_done (connection, FALSE);
 
977
                return;
 
978
        }
 
979
 
 
980
        priv->session_id = g_value_get_int (&(item->content));
 
981
        rb_daap_connection_state_done (connection, TRUE);
 
982
}
 
983
 
 
984
static void
 
985
handle_update (RBDAAPConnection *connection, guint status, GNode *structure)
 
986
{
 
987
        RBDaapConnectionPrivate *priv = DAAP_CONNECTION_GET_PRIVATE (connection);
 
988
        RBDAAPItem *item;
 
989
 
 
990
        if (structure == NULL || SOUP_STATUS_IS_SUCCESSFUL (status) == FALSE) {
 
991
                rb_daap_connection_state_done (connection, FALSE);
 
992
                return;
 
993
        }
 
994
 
 
995
        /* get a revision number */
 
996
        item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MUSR);
 
997
        if (item == NULL) {
 
998
                rb_debug ("Could not find daap.serverrevision item in /update");
 
999
                rb_daap_connection_state_done (connection, FALSE);
 
1000
                return;
 
1001
        }
 
1002
 
 
1003
        priv->revision_number = g_value_get_int (&(item->content));
 
1004
        rb_daap_connection_state_done (connection, TRUE);
 
1005
}
 
1006
 
 
1007
static void 
 
1008
handle_database_info (RBDAAPConnection *connection, guint status, GNode *structure)
 
1009
{
 
1010
        RBDaapConnectionPrivate *priv = DAAP_CONNECTION_GET_PRIVATE (connection);
 
1011
        RBDAAPItem *item = NULL;
 
1012
        GNode *listing_node;
 
1013
        gint n_databases = 0;
 
1014
 
 
1015
        /* get a list of databases, there should be only 1 */
 
1016
 
 
1017
        if (structure == NULL || SOUP_STATUS_IS_SUCCESSFUL (status) == FALSE) {
 
1018
                rb_daap_connection_state_done (connection, FALSE);
 
1019
                return;
 
1020
        }
 
1021
 
 
1022
        item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MRCO);
 
1023
        if (item == NULL) {
 
1024
                rb_debug ("Could not find dmap.returnedcount item in /databases");
 
1025
                rb_daap_connection_state_done (connection, FALSE);
 
1026
                return;
 
1027
        }
 
1028
 
 
1029
        n_databases = g_value_get_int (&(item->content));
 
1030
        if (n_databases != 1) {
 
1031
                rb_debug ("Host seems to have more than 1 database, how strange\n");
 
1032
        }
 
1033
        
 
1034
        listing_node = rb_daap_structure_find_node (structure, RB_DAAP_CC_MLCL);
 
1035
        if (listing_node == NULL) {
 
1036
                rb_debug ("Could not find dmap.listing item in /databases");
 
1037
                rb_daap_connection_state_done (connection, FALSE);
 
1038
                return;
 
1039
        }
 
1040
 
 
1041
        item = rb_daap_structure_find_item (listing_node->children, RB_DAAP_CC_MIID);
 
1042
        if (item == NULL) {
 
1043
                rb_debug ("Could not find dmap.itemid item in /databases");
 
1044
                rb_daap_connection_state_done (connection, FALSE);
 
1045
                return;
 
1046
        }
 
1047
        
 
1048
        priv->database_id = g_value_get_int (&(item->content));
 
1049
        rb_daap_connection_state_done (connection, TRUE);
 
1050
}
 
1051
 
 
1052
static void
 
1053
handle_song_listing (RBDAAPConnection *connection, guint status, GNode *structure)
 
1054
{
 
1055
        RBDaapConnectionPrivate *priv = DAAP_CONNECTION_GET_PRIVATE (connection);
 
1056
        RBDAAPItem *item = NULL;
 
1057
        GNode *listing_node;
 
1058
        gint returned_count;
 
1059
        gint i;
 
1060
        GNode *n;
 
1061
        gint specified_total_count;
 
1062
        gboolean update_type;
 
1063
 
 
1064
        /* get the songs */
 
1065
        
 
1066
        if (structure == NULL || SOUP_STATUS_IS_SUCCESSFUL (status) == FALSE) {
 
1067
                rb_daap_connection_state_done (connection, FALSE);
 
1068
                return;
 
1069
        }
 
1070
 
 
1071
        item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MRCO);
 
1072
        if (item == NULL) {
 
1073
                rb_debug ("Could not find dmap.returnedcount item in /databases/%d/items",
 
1074
                          priv->database_id);
 
1075
                rb_daap_connection_state_done (connection, FALSE);
 
1076
                return;
 
1077
        }
 
1078
        returned_count = g_value_get_int (&(item->content));
 
1079
        
 
1080
        item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MTCO);
 
1081
        if (item == NULL) {
 
1082
                rb_debug ("Could not find dmap.specifiedtotalcount item in /databases/%d/items",
 
1083
                          priv->database_id);
 
1084
                rb_daap_connection_state_done (connection, FALSE);
 
1085
                return;
 
1086
        }
 
1087
        specified_total_count = g_value_get_int (&(item->content));
 
1088
        
 
1089
        item = rb_daap_structure_find_item (structure, RB_DAAP_CC_MUTY);
 
1090
        if (item == NULL) {
 
1091
                rb_debug ("Could not find dmap.updatetype item in /databases/%d/items",
 
1092
                          priv->database_id);
 
1093
                rb_daap_connection_state_done (connection, FALSE);
 
1094
                return;
 
1095
        }
 
1096
        update_type = g_value_get_char (&(item->content));
 
1097
 
 
1098
        listing_node = rb_daap_structure_find_node (structure, RB_DAAP_CC_MLCL);
 
1099
        if (listing_node == NULL) {
 
1100
                rb_debug ("Could not find dmap.listing item in /databases/%d/items",
 
1101
                          priv->database_id);
 
1102
                rb_daap_connection_state_done (connection, FALSE);
 
1103
                return;
 
1104
        }
 
1105
 
 
1106
        priv->item_id_to_uri = g_hash_table_new_full ((GHashFunc)g_direct_hash,(GEqualFunc)g_direct_equal, NULL, g_free);
 
1107
        
 
1108
        for (i = 0, n = listing_node->children; n; i++, n = n->next) {
 
1109
                GNode *n2;
 
1110
                RhythmDBEntry *entry = NULL;
 
1111
                GValue value = {0,};
 
1112
                gchar *uri = NULL;
 
1113
                gint item_id = 0;
 
1114
                const gchar *title = NULL;
 
1115
                const gchar *album = NULL;
 
1116
                const gchar *artist = NULL;
 
1117
                const gchar *format = NULL;
 
1118
                const gchar *genre = NULL;
 
1119
                gint length = 0;
 
1120
                gint track_number = 0;
 
1121
                gint disc_number = 0;
 
1122
                gint year = 0;
 
1123
                gint size = 0;
 
1124
                gint bitrate = 0;
 
1125
                
 
1126
                for (n2 = n->children; n2; n2 = n2->next) {
 
1127
                        RBDAAPItem *meta_item;
 
1128
                        
 
1129
                        meta_item = n2->data;
 
1130
 
 
1131
                        switch (meta_item->content_code) {
 
1132
                                case RB_DAAP_CC_MIID:
 
1133
                                        item_id = g_value_get_int (&(meta_item->content));
 
1134
                                        break;
 
1135
                                case RB_DAAP_CC_MINM:
 
1136
                                        title = g_value_get_string (&(meta_item->content));
 
1137
                                        break;
 
1138
                                case RB_DAAP_CC_ASAL:
 
1139
                                        album = g_value_get_string (&(meta_item->content));
 
1140
                                        break;
 
1141
                                case RB_DAAP_CC_ASAR:
 
1142
                                        artist = g_value_get_string (&(meta_item->content));
 
1143
                                        break;
 
1144
                                case RB_DAAP_CC_ASFM:
 
1145
                                        format = g_value_get_string (&(meta_item->content));
 
1146
                                        break;
 
1147
                                case RB_DAAP_CC_ASGN:
 
1148
                                        genre = g_value_get_string (&(meta_item->content));
 
1149
                                        break;
 
1150
                                case RB_DAAP_CC_ASTM:
 
1151
                                        length = g_value_get_int (&(meta_item->content));
 
1152
                                        break;
 
1153
                                case RB_DAAP_CC_ASTN:
 
1154
                                        track_number = g_value_get_int (&(meta_item->content));
 
1155
                                        break;
 
1156
                                case RB_DAAP_CC_ASDN:
 
1157
                                        disc_number = g_value_get_int (&(meta_item->content));
 
1158
                                        break;
 
1159
                                case RB_DAAP_CC_ASYR:
 
1160
                                        year = g_value_get_int (&(meta_item->content));
 
1161
                                        break;
 
1162
                                case RB_DAAP_CC_ASSZ:
 
1163
                                        size = g_value_get_int (&(meta_item->content));
 
1164
                                        break;
 
1165
                                case RB_DAAP_CC_ASBR:
 
1166
                                        bitrate = g_value_get_int (&(meta_item->content));
 
1167
                                        break;
 
1168
                                default:
 
1169
                                        break;
 
1170
                        }
 
1171
                }
 
1172
 
 
1173
//              if (connection->daap_version == 3.0) {
 
1174
                        uri = g_strdup_printf ("%s/databases/%d/items/%d.%s?session-id=%d", 
 
1175
                                               priv->daap_base_uri, 
 
1176
                                               priv->database_id, 
 
1177
                                               item_id, format, 
 
1178
                                               priv->session_id);
 
1179
//              } else {
 
1180
//              ??FIXME??
 
1181
//              OLD ITUNES
 
1182
                // uri should be 
 
1183
                // "/databases/%d/items/%d.%s?session-id=%d&revision-id=%d";
 
1184
                // but its not going to work cause the other parts of the code 
 
1185
                // depend on the uri to have the ip address so that the
 
1186
                // RBDAAPSource can be found to ++request_id
 
1187
                // maybe just /dont/ support older itunes.  doesn't seem 
 
1188
                // unreasonable to me, honestly
 
1189
//              }
 
1190
                entry = rhythmdb_entry_new (priv->db, priv->db_type, uri);
 
1191
                g_hash_table_insert (priv->item_id_to_uri, GINT_TO_POINTER (item_id), uri);
 
1192
 
 
1193
                 /* track number */
 
1194
                g_value_init (&value, G_TYPE_ULONG);
 
1195
                g_value_set_ulong (&value,(gulong)track_number);
 
1196
                rhythmdb_entry_set_uninserted (priv->db, entry, RHYTHMDB_PROP_TRACK_NUMBER, &value);
 
1197
                g_value_unset (&value);
 
1198
 
 
1199
                /* disc number */
 
1200
                g_value_init (&value, G_TYPE_ULONG);
 
1201
                g_value_set_ulong (&value,(gulong)disc_number);
 
1202
                rhythmdb_entry_set_uninserted (priv->db, entry, RHYTHMDB_PROP_DISC_NUMBER, &value);
 
1203
                g_value_unset (&value);
 
1204
 
 
1205
                /* bitrate */
 
1206
                g_value_init (&value, G_TYPE_ULONG);
 
1207
                g_value_set_ulong (&value,(gulong)bitrate);
 
1208
                rhythmdb_entry_set_uninserted (priv->db, entry, RHYTHMDB_PROP_BITRATE, &value);
 
1209
                g_value_unset (&value);
 
1210
                
 
1211
                /* length */
 
1212
                g_value_init (&value, G_TYPE_ULONG);
 
1213
                g_value_set_ulong (&value,(gulong)length / 1000);
 
1214
                rhythmdb_entry_set_uninserted (priv->db, entry, RHYTHMDB_PROP_DURATION, &value);
 
1215
                g_value_unset (&value);
 
1216
 
 
1217
                /* file size */
 
1218
                g_value_init (&value, G_TYPE_UINT64);
 
1219
                g_value_set_uint64(&value,(gint64)size);
 
1220
                rhythmdb_entry_set_uninserted (priv->db, entry, RHYTHMDB_PROP_FILE_SIZE, &value);
 
1221
                g_value_unset (&value);
 
1222
 
 
1223
                /* title */
 
1224
                entry_set_string_prop (priv->db, entry, RHYTHMDB_PROP_TITLE, title);
 
1225
 
 
1226
                /* album */
 
1227
                entry_set_string_prop (priv->db, entry, RHYTHMDB_PROP_ALBUM, album);
 
1228
 
 
1229
                /* artist */
 
1230
                entry_set_string_prop (priv->db, entry, RHYTHMDB_PROP_ARTIST, artist);
 
1231
 
 
1232
                /* genre */
 
1233
                entry_set_string_prop (priv->db, entry, RHYTHMDB_PROP_GENRE, genre);
 
1234
        }
 
1235
 
 
1236
        rhythmdb_commit (priv->db);
 
1237
                
 
1238
        rb_daap_connection_state_done (connection, TRUE);
 
1239
}
 
1240
 
 
1241
/* FIXME
 
1242
 * what we really should do is only get a list of playlists and their ids
 
1243
 * then when they are clicked on ('activate'd) by the user, get a list of
 
1244
 * the files that are actually in them.  This will speed up initial daap 
 
1245
 * connection times and reduce memory consumption.
 
1246
 */
 
1247
 
 
1248
static void
 
1249
handle_playlists (RBDAAPConnection *connection, guint status, GNode *structure)
 
1250
{
 
1251
        RBDaapConnectionPrivate *priv = DAAP_CONNECTION_GET_PRIVATE (connection);
 
1252
        GNode *listing_node;
 
1253
        gint i;
 
1254
        GNode *n;
 
1255
        
 
1256
        if (structure == NULL || SOUP_STATUS_IS_SUCCESSFUL (status) == FALSE) {
 
1257
                rb_daap_connection_state_done (connection, FALSE);
 
1258
                return;
 
1259
        }
 
1260
 
 
1261
        listing_node = rb_daap_structure_find_node (structure, RB_DAAP_CC_MLCL);
 
1262
        if (listing_node == NULL) {
 
1263
                rb_debug ("Could not find dmap.listing item in /databases/%d/containers", 
 
1264
                          priv->database_id);
 
1265
                rb_daap_connection_state_done (connection, FALSE);
 
1266
                return;
 
1267
        }
 
1268
 
 
1269
        for (i = 0, n = listing_node->children; n; n = n->next, i++) {
 
1270
                RBDAAPItem *item;
 
1271
                gint id;
 
1272
                gchar *name;
 
1273
                RBDAAPPlaylist *playlist;
 
1274
                
 
1275
                item = rb_daap_structure_find_item (n, RB_DAAP_CC_ABPL);
 
1276
                if (item != NULL) {
 
1277
                        continue;
 
1278
                }
 
1279
 
 
1280
                item = rb_daap_structure_find_item (n, RB_DAAP_CC_MIID);
 
1281
                if (item == NULL) {
 
1282
                        rb_debug ("Could not find dmap.itemid item in /databases/%d/containers",
 
1283
                                  priv->database_id);
 
1284
                        continue;
 
1285
                }
 
1286
                id = g_value_get_int (&(item->content));
 
1287
 
 
1288
                item = rb_daap_structure_find_item (n, RB_DAAP_CC_MINM);
 
1289
                if (item == NULL) {
 
1290
                        rb_debug ("Could not find dmap.itemname item in /databases/%d/containers",
 
1291
                                  priv->database_id);
 
1292
                        continue;
 
1293
                }
 
1294
                name = g_value_dup_string (&(item->content));
 
1295
 
 
1296
                playlist = g_new0 (RBDAAPPlaylist, 1);
 
1297
                playlist->id = id;
 
1298
                playlist->name = name;
 
1299
                rb_debug ("Got playlist %p: name %s, id %d", playlist, playlist->name, playlist->id);
 
1300
 
 
1301
                priv->playlists = g_slist_prepend (priv->playlists, playlist);
 
1302
        }
 
1303
 
 
1304
        rb_daap_connection_state_done (connection, TRUE);
 
1305
}
 
1306
 
 
1307
static void
 
1308
handle_playlist_entries (RBDAAPConnection *connection, guint status, GNode *structure)
 
1309
{
 
1310
        RBDaapConnectionPrivate *priv = DAAP_CONNECTION_GET_PRIVATE (connection);
 
1311
        RBDAAPPlaylist *playlist;
 
1312
        GNode *listing_node;
 
1313
        GNode *node;
 
1314
        gint i;
 
1315
        GList *playlist_uris = NULL;
 
1316
 
 
1317
        if (structure == NULL || SOUP_STATUS_IS_SUCCESSFUL (status) == FALSE) {
 
1318
                rb_daap_connection_state_done (connection, FALSE);
 
1319
                return;
 
1320
        }
 
1321
 
 
1322
        playlist = (RBDAAPPlaylist *)g_slist_nth_data (priv->playlists, priv->reading_playlist);
 
1323
        g_assert (playlist);
 
1324
 
 
1325
        listing_node = rb_daap_structure_find_node (structure, RB_DAAP_CC_MLCL);
 
1326
        if (listing_node == NULL) {
 
1327
                rb_debug ("Could not find dmap.listing item in /databases/%d/containers/%d/items", 
 
1328
                          priv->database_id, playlist->id);
 
1329
                rb_daap_connection_state_done (connection, FALSE);
 
1330
                return;
 
1331
        }
 
1332
 
 
1333
        for (i = 0, node = listing_node->children; node; node = node->next, i++) {
 
1334
                gchar *item_uri;
 
1335
                gint playlist_item_id;
 
1336
                RBDAAPItem *item;
 
1337
 
 
1338
                item = rb_daap_structure_find_item (node, RB_DAAP_CC_MIID);
 
1339
                if (item == NULL) {
 
1340
                        rb_debug ("Could not find dmap.itemid item in /databases/%d/containers/%d/items",
 
1341
                                  priv->database_id, playlist->id);
 
1342
                        continue;
 
1343
                }
 
1344
                playlist_item_id = g_value_get_int (&(item->content));
 
1345
        
 
1346
                item_uri = g_hash_table_lookup (priv->item_id_to_uri, GINT_TO_POINTER (playlist_item_id));
 
1347
                if (item_uri == NULL) {
 
1348
                        rb_debug ("Entry %d in playlist %s doesn't exist in the database\n", 
 
1349
                                  playlist_item_id, playlist->name);
 
1350
                        continue;
 
1351
                }
 
1352
                
 
1353
                playlist_uris = g_list_prepend (playlist_uris, g_strdup (item_uri));
 
1354
        }
 
1355
 
 
1356
        playlist->uris = playlist_uris;
 
1357
        rb_daap_connection_state_done (connection, TRUE);
 
1358
}
 
1359
 
 
1360
static void
 
1361
handle_logout (RBDAAPConnection *connection, guint status, GNode *structure)
 
1362
{
 
1363
        /* is there any point handling errors here? */
 
1364
        rb_daap_connection_state_done (connection, TRUE);
 
1365
}
 
1366
        
 
1367
RBDAAPConnection * 
 
1368
rb_daap_connection_new (const gchar *name,
 
1369
                        const gchar *host,
 
1370
                        gint port, 
 
1371
                        gboolean password_protected,
 
1372
                        RhythmDB *db, 
 
1373
                        RhythmDBEntryType type,
 
1374
                        RBDAAPConnectionCallback callback,
 
1375
                        gpointer user_data)
 
1376
{
 
1377
        return g_object_new (RB_TYPE_DAAP_CONNECTION,
 
1378
                             "name", name,
 
1379
                             "entry-type", type,
 
1380
                             "password-protected", password_protected,
 
1381
                             "callback", callback,
 
1382
                             "callback-data", user_data,
 
1383
                             "db", db,
 
1384
                             "host", host,
 
1385
                             "port", port,
 
1386
                             NULL);
 
1387
}
 
1388
 
 
1389
static GObject *
 
1390
rb_daap_connection_constructor (GType type, guint n_construct_properties,
 
1391
                                GObjectConstructParam *construct_properties)
 
1392
{
 
1393
        RBDAAPConnection *connection;
 
1394
        RBDaapConnectionPrivate *priv;
 
1395
        gchar *path = NULL;
 
1396
 
 
1397
        connection = RB_DAAP_CONNECTION (G_OBJECT_CLASS(rb_daap_connection_parent_class)->
 
1398
                        constructor (type, n_construct_properties, construct_properties));
 
1399
 
 
1400
        priv = DAAP_CONNECTION_GET_PRIVATE (connection);
 
1401
        priv->result = TRUE;
 
1402
 
 
1403
        rb_debug ("Creating new DAAP connection to %s:%d", priv->host, priv->port);
 
1404
 
 
1405
        priv->session = soup_session_async_new ();
 
1406
        path = g_strdup_printf ("http://%s:%d", priv->host, priv->port);
 
1407
        priv->base_uri = soup_uri_new (path);
 
1408
        g_free (path);
 
1409
 
 
1410
        if (priv->base_uri == NULL) {
 
1411
                rb_debug ("Error parsing http://%s:%d", priv->host, priv->port);
 
1412
                g_object_unref (G_OBJECT (connection));
 
1413
                return NULL;
 
1414
        }
 
1415
 
 
1416
        priv->daap_base_uri = g_strdup_printf ("daap://%s:%d", priv->host, priv->port);
 
1417
 
 
1418
        priv->state = DAAP_GET_INFO;
 
1419
        rb_daap_connection_do_something (connection);
 
1420
 
 
1421
        return G_OBJECT (connection);
 
1422
}
 
1423
 
 
1424
void
 
1425
rb_daap_connection_logout (RBDAAPConnection *connection,
 
1426
                           RBDAAPConnectionCallback callback,
 
1427
                           gpointer user_data)
 
1428
{
 
1429
        RBDaapConnectionPrivate *priv = DAAP_CONNECTION_GET_PRIVATE (connection);
 
1430
 
 
1431
        if (priv->state == DAAP_LOGOUT)
 
1432
                return;
 
1433
        
 
1434
        /* FIXME what to do if we get asked to log out before login has completed? */
 
1435
        g_assert (priv->state == DAAP_DONE);
 
1436
 
 
1437
        priv->callback = callback;
 
1438
        priv->callback_user_data = user_data;
 
1439
        priv->result = TRUE;
 
1440
        
 
1441
        priv->state = DAAP_LOGOUT;
 
1442
        rb_daap_connection_do_something (connection);
 
1443
}
 
1444
 
 
1445
static void
 
1446
rb_daap_connection_state_done (RBDAAPConnection *connection, gboolean result)
 
1447
{
 
1448
        RBDaapConnectionPrivate *priv = DAAP_CONNECTION_GET_PRIVATE (connection);
 
1449
 
 
1450
        if (result == FALSE) {
 
1451
                priv->state = DAAP_DONE;
 
1452
                priv->result = FALSE;
 
1453
        } else {
 
1454
                switch (priv->state) {
 
1455
                case DAAP_GET_PLAYLISTS:
 
1456
                        if (priv->playlists == NULL)
 
1457
                                priv->state = DAAP_DONE;
 
1458
                        else
 
1459
                                priv->state = DAAP_GET_PLAYLIST_ENTRIES;
 
1460
                        break;
 
1461
                case DAAP_GET_PLAYLIST_ENTRIES:
 
1462
                        /* keep reading playlists until we've got them all */
 
1463
                        if (++priv->reading_playlist >= g_slist_length (priv->playlists))
 
1464
                                priv->state = DAAP_DONE;
 
1465
                        break;
 
1466
 
 
1467
                case DAAP_LOGOUT:
 
1468
                        priv->state = DAAP_DONE;
 
1469
                        break;
 
1470
 
 
1471
                case DAAP_DONE:
 
1472
                        /* uhh.. */
 
1473
                        rb_debug ("This should never happen.");
 
1474
                        break;
 
1475
 
 
1476
                default:
 
1477
                        /* in most states, we just move on to the next */
 
1478
                        if (priv->state > DAAP_DONE) {
 
1479
                                rb_debug ("This should REALLY never happen.");
 
1480
                                return;
 
1481
                        }
 
1482
                        priv->state++;
 
1483
                        break;
 
1484
                }
 
1485
        }
 
1486
 
 
1487
        rb_daap_connection_do_something (connection);
 
1488
}
 
1489
 
 
1490
static void
 
1491
rb_daap_connection_do_something (RBDAAPConnection *connection)
 
1492
{
 
1493
        RBDaapConnectionPrivate *priv = DAAP_CONNECTION_GET_PRIVATE (connection);
 
1494
        char *path;
 
1495
 
 
1496
        switch (priv->state) {
 
1497
        case DAAP_GET_INFO:
 
1498
                rb_debug ("Getting DAAP server info");
 
1499
                if (!http_get (connection, "/server-info", FALSE, 0.0, 0, FALSE, 
 
1500
                               (RBDAAPResponseHandler) handle_server_info)) {
 
1501
                        rb_debug ("Could not get DAAP connection info");
 
1502
                        rb_daap_connection_state_done (connection, FALSE);
 
1503
                }
 
1504
                break;
 
1505
        
 
1506
        case DAAP_GET_PASSWORD:
 
1507
                if (priv->password_protected) {
 
1508
                        /* FIXME this bit is still synchronous */
 
1509
                        rb_debug ("Need a password for %s", priv->name);
 
1510
                        priv->password = connection_get_password (connection);
 
1511
                        if (priv->password == NULL || priv->password[0] == '\0') {
 
1512
                                rb_debug ("Password entry canceled");
 
1513
                                priv->result = FALSE;
 
1514
                                priv->state = DAAP_DONE;
 
1515
                                rb_daap_connection_do_something (connection);
 
1516
                                return;
 
1517
                        }
 
1518
                }
 
1519
 
 
1520
                /* otherwise, fall through */
 
1521
                priv->state = DAAP_LOGIN;
 
1522
                
 
1523
        case DAAP_LOGIN:
 
1524
                rb_debug ("Logging into DAAP server");
 
1525
                if (!http_get (connection, "/login", FALSE, 0.0, 0, FALSE, 
 
1526
                               (RBDAAPResponseHandler) handle_login)) {
 
1527
                        rb_debug ("Could not login to DAAP server");
 
1528
                        rb_daap_connection_state_done (connection, FALSE);
 
1529
                }
 
1530
                break;
 
1531
 
 
1532
        case DAAP_GET_REVISION_NUMBER:
 
1533
                rb_debug ("Getting DAAP server database revision number");
 
1534
                path = g_strdup_printf ("/update?session-id=%d&revision-number=1", priv->session_id);
 
1535
                if (!http_get (connection, path, TRUE, priv->daap_version, 0, FALSE, 
 
1536
                               (RBDAAPResponseHandler) handle_update)) {
 
1537
                        rb_debug ("Could not get server database revision number");
 
1538
                        rb_daap_connection_state_done (connection, FALSE);
 
1539
                }
 
1540
                g_free (path);
 
1541
                break;
 
1542
 
 
1543
        case DAAP_GET_DB_INFO:
 
1544
                rb_debug ("Getting DAAP database info");
 
1545
                path = g_strdup_printf ("/databases?session-id=%d&revision-number=%d", 
 
1546
                                        priv->session_id, priv->revision_number);
 
1547
                if (!http_get (connection, path, TRUE, priv->daap_version, 0, FALSE, 
 
1548
                               (RBDAAPResponseHandler) handle_database_info)) {
 
1549
                        rb_debug ("Could not get DAAP database info");
 
1550
                        rb_daap_connection_state_done (connection, FALSE);
 
1551
                }
 
1552
                g_free (path);
 
1553
                break;
 
1554
 
 
1555
        case DAAP_GET_SONGS:
 
1556
                rb_debug ("Getting DAAP song listing");
 
1557
                path = g_strdup_printf ("/databases/%i/items?session-id=%i&revision-number=%i"
 
1558
                                        "&meta=dmap.itemid,dmap.itemname,daap.songalbum,"
 
1559
                                        "daap.songartist,daap.daap.songgenre,daap.songsize,"
 
1560
                                        "daap.songtime,daap.songtrackcount,daap.songtracknumber,"
 
1561
                                        "daap.songyear,daap.songformat,daap.songgenre,"
 
1562
                                        "daap.songbitrate", 
 
1563
                                        priv->database_id, 
 
1564
                                        priv->session_id, 
 
1565
                                        priv->revision_number);
 
1566
                if (!http_get (connection, path, TRUE, priv->daap_version, 0, FALSE, 
 
1567
                               (RBDAAPResponseHandler) handle_song_listing)) {
 
1568
                        rb_debug ("Could not get DAAP song listing");
 
1569
                        rb_daap_connection_state_done (connection, FALSE);
 
1570
                }
 
1571
                g_free (path);
 
1572
                break;
 
1573
 
 
1574
        case DAAP_GET_PLAYLISTS:
 
1575
                rb_debug ("Getting DAAP playlists");
 
1576
                path = g_strdup_printf ("/databases/%d/containers?session-id=%d&revision-number=%d", 
 
1577
                                        priv->database_id, 
 
1578
                                        priv->session_id, 
 
1579
                                        priv->revision_number);
 
1580
                if (!http_get (connection, path, TRUE, priv->daap_version, 0, FALSE, 
 
1581
                               (RBDAAPResponseHandler) handle_playlists)) {
 
1582
                        rb_debug ("Could not get DAAP playlists");
 
1583
                        rb_daap_connection_state_done (connection, FALSE);
 
1584
                }
 
1585
                g_free (path);
 
1586
                break;
 
1587
 
 
1588
        case DAAP_GET_PLAYLIST_ENTRIES:
 
1589
                {
 
1590
                        RBDAAPPlaylist *playlist = 
 
1591
                                (RBDAAPPlaylist *) g_slist_nth_data (priv->playlists, 
 
1592
                                                                     priv->reading_playlist);
 
1593
                        g_assert (playlist);
 
1594
                        rb_debug ("Reading DAAP playlist %d entries", priv->reading_playlist);
 
1595
                        path = g_strdup_printf ("/databases/%d/containers/%d/items?session-id=%d&revision-number=%d&meta=dmap.itemid", 
 
1596
                                                priv->database_id, 
 
1597
                                                playlist->id,
 
1598
                                                priv->session_id, priv->revision_number);
 
1599
                        if (!http_get (connection, path, TRUE, priv->daap_version, 0, FALSE, 
 
1600
                                       (RBDAAPResponseHandler) handle_playlist_entries)) {
 
1601
                                rb_debug ("Could not get entries for DAAP playlist %d", 
 
1602
                                          priv->reading_playlist);
 
1603
                                rb_daap_connection_state_done (connection, FALSE);
 
1604
                        }
 
1605
                        g_free (path);
 
1606
                }
 
1607
                break;
 
1608
 
 
1609
        case DAAP_LOGOUT:
 
1610
                rb_debug ("Logging out of DAAP server");
 
1611
                path = g_strdup_printf ("/logout?session-id=%d", priv->session_id);
 
1612
                if (!http_get (connection, path, TRUE, priv->daap_version, 0, FALSE,
 
1613
                               (RBDAAPResponseHandler) handle_logout)) {
 
1614
                        rb_debug ("Could not log out of DAAP server");
 
1615
                        rb_daap_connection_state_done (connection, FALSE);
 
1616
                }
 
1617
                g_free (path);
 
1618
                break;
 
1619
 
 
1620
        case DAAP_DONE:
 
1621
                if (priv->callback) {
 
1622
                        /* do it this way, in case the callback sets another one or destroys the object */
 
1623
                        RBDAAPConnectionCallback callback = priv->callback;
 
1624
                        priv->callback = NULL;
 
1625
                        (*callback) (connection, priv->result, priv->callback_user_data);
 
1626
                }
 
1627
                break;
 
1628
        }
 
1629
}
 
1630
 
 
1631
gchar * 
 
1632
rb_daap_connection_get_headers (RBDAAPConnection *connection, 
 
1633
                                const gchar *uri, 
 
1634
                                gint64 bytes)
 
1635
{
 
1636
        RBDaapConnectionPrivate *priv = DAAP_CONNECTION_GET_PRIVATE (connection);
 
1637
        GString *headers;
 
1638
        gchar hash[33] = {0};
 
1639
        gchar *norb_daap_uri = (gchar *)uri;
 
1640
        gchar *s;
 
1641
        
 
1642
        priv->request_id++;
 
1643
        
 
1644
        if (g_strncasecmp (uri,"daap://",7) == 0) {
 
1645
                norb_daap_uri = strstr (uri,"/data");
 
1646
        }
 
1647
 
 
1648
        rb_daap_hash_generate ((short)floorf (priv->daap_version), 
 
1649
                               (const guchar*)norb_daap_uri, 2, 
 
1650
                               (guchar*)hash, 
 
1651
                               priv->request_id);
 
1652
 
 
1653
        headers = g_string_new ("Accept: */*\r\n"
 
1654
                                "Cache-Control: no-cache\r\n"
 
1655
                                "User-Agent: " RB_DAAP_USER_AGENT "\r\n"
 
1656
                                "Accept-Language: en-us, en;q=5.0\r\n"
 
1657
                                "Client-DAAP-Access-Index: 2\r\n"
 
1658
                                "Client-DAAP-Version: 3.0\r\n");
 
1659
        g_string_append_printf (headers, 
 
1660
                                "Client-DAAP-Validation: %s\r\n"
 
1661
                                "Client-DAAP-Request-ID: %d\r\n"
 
1662
                                "Connection: close\r\n", 
 
1663
                                hash, priv->request_id);
 
1664
        if (priv->password_protected) {
 
1665
                g_string_append_printf (headers, "Authentication: Basic %s\r\n", priv->password);
 
1666
        }
 
1667
 
 
1668
        if (bytes != 0) {
 
1669
                g_string_append_printf (headers,"Range: bytes=%"G_GINT64_FORMAT"-\r\n", bytes);
 
1670
        }
 
1671
        
 
1672
        s = headers->str;
 
1673
        g_string_free (headers, FALSE);
 
1674
 
 
1675
        return s;
 
1676
}
 
1677
 
 
1678
GSList * 
 
1679
rb_daap_connection_get_playlists (RBDAAPConnection *connection)
 
1680
{
 
1681
        RBDaapConnectionPrivate *priv = DAAP_CONNECTION_GET_PRIVATE (connection);
 
1682
 
 
1683
        return priv->playlists;
 
1684
}
 
1685
 
 
1686
 
 
1687
static void 
 
1688
rb_daap_connection_dispose (GObject *object)
 
1689
{
 
1690
        RBDaapConnectionPrivate *priv = DAAP_CONNECTION_GET_PRIVATE (object);
 
1691
        GSList *l;
 
1692
 
 
1693
        g_assert (priv->callback == NULL);
 
1694
 
 
1695
        if (priv->name) {
 
1696
                g_free (priv->name);
 
1697
                priv->name = NULL;
 
1698
        }
 
1699
        
 
1700
        if (priv->password) {
 
1701
                g_free (priv->password);
 
1702
                priv->password = NULL;
 
1703
        }
 
1704
        
 
1705
        if (priv->host) {
 
1706
                g_free (priv->host);
 
1707
                priv->host = NULL;
 
1708
        }
 
1709
        
 
1710
        if (priv->playlists) {
 
1711
                for (l = priv->playlists; l; l = l->next) {
 
1712
                        RBDAAPPlaylist *playlist = l->data;
 
1713
 
 
1714
                        g_list_foreach (playlist->uris, (GFunc)g_free, NULL);
 
1715
                        g_list_free (playlist->uris);
 
1716
                        g_free (playlist->name);
 
1717
                        g_free (playlist);
 
1718
                        l->data = NULL;
 
1719
                }
 
1720
                g_slist_free (priv->playlists);
 
1721
                priv->playlists = NULL;
 
1722
        }
 
1723
 
 
1724
        if (priv->item_id_to_uri) {
 
1725
                g_hash_table_destroy (priv->item_id_to_uri);
 
1726
                priv->item_id_to_uri = NULL;
 
1727
        }
 
1728
        
 
1729
        if (priv->session) {
 
1730
                g_object_unref (G_OBJECT (priv->session));
 
1731
                priv->session = NULL;
 
1732
        }
 
1733
 
 
1734
        if (priv->base_uri) {
 
1735
                soup_uri_free (priv->base_uri);
 
1736
                priv->base_uri = NULL;
 
1737
        }
 
1738
 
 
1739
        if (priv->daap_base_uri) {
 
1740
                g_free (priv->daap_base_uri);
 
1741
                priv->daap_base_uri = NULL;
 
1742
        }
 
1743
 
 
1744
        if (priv->db) {
 
1745
                g_object_unref (G_OBJECT (priv->db));
 
1746
                priv->db = NULL;
 
1747
        }
 
1748
        
 
1749
        G_OBJECT_CLASS (rb_daap_connection_parent_class)->dispose (object);
 
1750
}
 
1751
 
 
1752
static void
 
1753
rb_daap_connection_set_property (GObject *object,
 
1754
                                 guint prop_id,
 
1755
                                 const GValue *value,
 
1756
                                 GParamSpec *pspec)
 
1757
{
 
1758
        RBDaapConnectionPrivate *priv = DAAP_CONNECTION_GET_PRIVATE (object);
 
1759
 
 
1760
        switch (prop_id)
 
1761
        {
 
1762
        case PROP_NAME:
 
1763
                g_free (priv->name);
 
1764
                priv->name = g_value_dup_string (value);
 
1765
                break;
 
1766
        case PROP_DB:
 
1767
                priv->db = RHYTHMDB (g_value_dup_object (value));
 
1768
                break;
 
1769
        case PROP_PASSWORD_PROTECTED:
 
1770
                priv->password_protected = g_value_get_boolean (value);
 
1771
                break;
 
1772
        case PROP_ENTRY_TYPE:
 
1773
                priv->db_type = g_value_get_uint (value);
 
1774
                break;
 
1775
        case PROP_CALLBACK:
 
1776
                priv->callback = g_value_get_pointer (value);
 
1777
                break;
 
1778
        case PROP_CALLBACK_DATA:
 
1779
                priv->callback_user_data = g_value_get_pointer (value);
 
1780
                break;
 
1781
        case PROP_HOST:
 
1782
                g_free (priv->host);
 
1783
                priv->host = g_value_dup_string (value);
 
1784
                break;
 
1785
        case PROP_PORT:
 
1786
                priv->port = g_value_get_uint (value);
 
1787
                break;
 
1788
        default:
 
1789
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 
1790
                break;
 
1791
        }
 
1792
}
 
1793
 
 
1794
static void
 
1795
rb_daap_connection_get_property (GObject *object,
 
1796
                                 guint prop_id,
 
1797
                                 GValue *value,
 
1798
                                 GParamSpec *pspec)
 
1799
{
 
1800
        RBDaapConnectionPrivate *priv = DAAP_CONNECTION_GET_PRIVATE (object);
 
1801
 
 
1802
        switch (prop_id)
 
1803
        {
 
1804
        case PROP_DB:
 
1805
                g_value_set_object (value, priv->db);
 
1806
                break;
 
1807
        case PROP_NAME:
 
1808
                g_value_set_string (value, priv->name);
 
1809
                break;
 
1810
        case PROP_CALLBACK:
 
1811
                g_value_set_pointer (value, priv->callback);
 
1812
                break;
 
1813
        case PROP_CALLBACK_DATA:
 
1814
                g_value_set_pointer (value, priv->callback_user_data);
 
1815
                break;
 
1816
        case PROP_ENTRY_TYPE:
 
1817
                g_value_set_uint (value, priv->db_type);
 
1818
                break;
 
1819
        case PROP_PASSWORD_PROTECTED:
 
1820
                g_value_set_boolean (value, priv->password_protected);
 
1821
                break;
 
1822
        case PROP_HOST:
 
1823
                g_value_set_string (value, priv->host);
 
1824
                break;
 
1825
        case PROP_PORT:
 
1826
                g_value_set_uint (value, priv->port);
 
1827
                break;
 
1828
        default:
 
1829
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 
1830
                break;
 
1831
        }
 
1832
}
 
1833