daap-share.c 28 KB
Newer Older
1
/* Implmentation of DAAP (e.g., iTunes Music) sharing
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
 *
 * Copyright (C) 2005 Charles Schmidt <cschmidt2@emich.edu>
 *
 * Modifications Copyright (C) 2008 W. Michael Petullo <mike@flyn.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "config.h"

#include <time.h>
#include <string.h>
#include <stdlib.h>

#include <glib.h>

#include <libsoup/soup.h>
#include <libsoup/soup-address.h>
#include <libsoup/soup-message.h>
#include <libsoup/soup-uri.h>
#include <libsoup/soup-server.h>

37
#include <libdmapsharing/dmap.h>
38
#include <libdmapsharing/dmap-structure.h>
39
#include <libdmapsharing/dmap-private-utils.h>
40
#include <libdmapsharing/dmap-utils.h>
41
42

#ifdef HAVE_GSTREAMERAPP
43
#include <libdmapsharing/dmap-gst-input-stream.h>
44
45
#endif /* HAVE_GSTREAMERAPP */

W. Michael Petullo's avatar
W. Michael Petullo committed
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
static void daap_share_set_property (GObject * object,
				     guint prop_id,
				     const GValue * value,
				     GParamSpec * pspec);
static void daap_share_get_property (GObject * object,
				     guint prop_id,
				     GValue * value, GParamSpec * pspec);
static void daap_share_dispose (GObject * object);
guint daap_share_get_desired_port (DMAPShare * share);
const char *daap_share_get_type_of_service (DMAPShare * share);
void daap_share_server_info (DMAPShare * share,
			     SoupServer * server,
			     SoupMessage * message,
			     const char *path,
			     GHashTable * query, SoupClientContext * context);
void daap_share_message_add_standard_headers (DMAPShare * share,
					      SoupMessage * message);
static void databases_browse_xxx (DMAPShare * share,
				  SoupServer * server,
				  SoupMessage * msg,
66
				  const char *path,
W. Michael Petullo's avatar
W. Michael Petullo committed
67
68
69
70
71
				  GHashTable * query,
				  SoupClientContext * context);
static void databases_items_xxx (DMAPShare * share,
				 SoupServer * server,
				 SoupMessage * msg,
72
				 const char *path,
W. Michael Petullo's avatar
W. Michael Petullo committed
73
74
75
76
				 GHashTable * query,
				 SoupClientContext * context);
static struct DMAPMetaDataMap *get_meta_data_map (DMAPShare * share);
static void add_entry_to_mlcl (gpointer id, DMAPRecord * record, gpointer mb);
77
78
79
80

#define DAAP_TYPE_OF_SERVICE "_daap._tcp"
#define DAAP_PORT 3689

W. Michael Petullo's avatar
W. Michael Petullo committed
81
82
struct DAAPSharePrivate
{
83
	gchar unused;
84
85
};

86
#define DAAP_SHARE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), DAAP_TYPE_SHARE, DAAPSharePrivate))
87

W. Michael Petullo's avatar
W. Michael Petullo committed
88
89
enum
{
90
91
92
	PROP_0,
};

93
G_DEFINE_TYPE (DAAPShare, daap_share, DMAP_TYPE_SHARE);
94

95
96
static void
daap_share_class_init (DAAPShareClass * klass)
97
98
99
100
101
102
103
104
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	DMAPShareClass *parent_class = DMAP_SHARE_CLASS (object_class);

	object_class->get_property = daap_share_get_property;
	object_class->set_property = daap_share_set_property;
	object_class->dispose = daap_share_dispose;

W. Michael Petullo's avatar
W. Michael Petullo committed
105
106
107
108
109
110
	parent_class->get_desired_port = daap_share_get_desired_port;
	parent_class->get_type_of_service = daap_share_get_type_of_service;
	parent_class->message_add_standard_headers =
		daap_share_message_add_standard_headers;
	parent_class->get_meta_data_map = get_meta_data_map;
	parent_class->add_entry_to_mlcl = add_entry_to_mlcl;
111
	parent_class->databases_browse_xxx = databases_browse_xxx;
W. Michael Petullo's avatar
W. Michael Petullo committed
112
113
	parent_class->databases_items_xxx = databases_items_xxx;
	parent_class->server_info = daap_share_server_info;
114
115
116
117
118

	g_type_class_add_private (klass, sizeof (DAAPSharePrivate));
}

static void
W. Michael Petullo's avatar
W. Michael Petullo committed
119
daap_share_init (DAAPShare * share)
120
121
122
123
124
125
{
	share->priv = DAAP_SHARE_GET_PRIVATE (share);
	/* FIXME: do I need to manually call parent _init? */
}

static void
W. Michael Petullo's avatar
W. Michael Petullo committed
126
127
128
daap_share_set_property (GObject * object,
			 guint prop_id,
			 const GValue * value, GParamSpec * pspec)
129
{
W. Michael Petullo's avatar
W. Michael Petullo committed
130
	// DAAPShare *share = DAAP_SHARE (object);
131
132
133
134
135
136
137
138
139

	switch (prop_id) {
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

static void
W. Michael Petullo's avatar
W. Michael Petullo committed
140
141
daap_share_get_property (GObject * object,
			 guint prop_id, GValue * value, GParamSpec * pspec)
142
{
W. Michael Petullo's avatar
W. Michael Petullo committed
143
	// DAAPShare *share = DAAP_SHARE (object);
144
145
146
147
148
149
150
151
152

	switch (prop_id) {
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

static void
W. Michael Petullo's avatar
W. Michael Petullo committed
153
daap_share_dispose (GObject * object)
154
155
156
157
158
159
160
{
	/* FIXME: implement in parent */
}

DAAPShare *
daap_share_new (const char *name,
		const char *password,
W. Michael Petullo's avatar
W. Michael Petullo committed
161
162
		DMAPDb * db,
		DMAPContainerDb * container_db, gchar * transcode_mimetype)
163
164
165
{
	DAAPShare *share;

W. Michael Petullo's avatar
W. Michael Petullo committed
166
167
168
	g_object_ref (db);
	g_object_ref (container_db);

169
	share = DAAP_SHARE (g_object_new (DAAP_TYPE_SHARE,
W. Michael Petullo's avatar
W. Michael Petullo committed
170
171
172
173
174
175
					  "name", name,
					  "password", password,
					  "db", db,
					  "container-db", container_db,
					  "transcode-mimetype",
					  transcode_mimetype, NULL));
176

177
178
	_dmap_share_server_start (DMAP_SHARE (share));
	_dmap_share_publish_start (DMAP_SHARE (share));
179
180
181
182
183

	return share;
}

void
W. Michael Petullo's avatar
W. Michael Petullo committed
184
185
daap_share_message_add_standard_headers (DMAPShare * share,
					 SoupMessage * message)
186
{
W. Michael Petullo's avatar
W. Michael Petullo committed
187
	soup_message_headers_append (message->response_headers, "DMAP-Server",
188
				     "libdmapsharing" "VERSION");
189
190
191
192
193
194
195
}

#define DMAP_VERSION 2.0
#define DAAP_VERSION 3.0
#define DAAP_TIMEOUT 1800

guint
W. Michael Petullo's avatar
W. Michael Petullo committed
196
daap_share_get_desired_port (DMAPShare * share)
197
198
199
200
201
{
	return DAAP_PORT;
}

const char *
W. Michael Petullo's avatar
W. Michael Petullo committed
202
daap_share_get_type_of_service (DMAPShare * share)
203
204
205
206
207
{
	return DAAP_TYPE_OF_SERVICE;
}

void
W. Michael Petullo's avatar
W. Michael Petullo committed
208
209
210
211
212
daap_share_server_info (DMAPShare * share,
			SoupServer * server,
			SoupMessage * message,
			const char *path,
			GHashTable * query, SoupClientContext * context)
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
{
/* MSRV	server info response
 * 	MSTT status
 * 	MPRO daap version
 * 	APRO daap version
 * 	MINM name
 * 	MSAU authentication method
 * 	MSLR login required
 * 	MSTM timeout interval
 * 	MSAL supports auto logout
 * 	MSUP supports update
 * 	MSPI supports persistent ids
 * 	MSEX supports extensions
 * 	MSBR supports browse
 * 	MSQY supports query
 * 	MSIX supports index
 * 	MSRS supports resolve
 * 	MSDC databases count
 */
	gchar *nameprop;
	GNode *msrv;

	g_debug ("Path is %s.", path);

	g_object_get ((gpointer) share, "name", &nameprop, NULL);

	msrv = dmap_structure_add (NULL, DMAP_CC_MSRV);
240
	dmap_structure_add (msrv, DMAP_CC_MSTT, (gint32) DMAP_STATUS_OK);
241
242
243
244
245
246
247
248
249
250
251
	dmap_structure_add (msrv, DMAP_CC_MPRO, (gdouble) DAAP_VERSION);
	dmap_structure_add (msrv, DMAP_CC_APRO, (gdouble) DAAP_VERSION);
	/* 2/3 is for itunes 4.8 (at least).  its determined by the
	 * Client-DAAP-Version header sent, but if we decide not to support
	 * older versions..? anyway
	 *
	 * 1.0 is 1/1
	 * 2.0 is 1/2
	 * 3.0 is 2/3
	 */
	dmap_structure_add (msrv, DMAP_CC_MINM, nameprop);
W. Michael Petullo's avatar
W. Michael Petullo committed
252
253
	dmap_structure_add (msrv, DMAP_CC_MSAU,
			    _dmap_share_get_auth_method (share));
254
255
256
257
258
259
260
261
	/* authentication method
	 * 0 is nothing
	 * 1 is name & password
	 * 2 is password only
	 */
	dmap_structure_add (msrv, DMAP_CC_MSLR, 0);
	dmap_structure_add (msrv, DMAP_CC_MSTM, (gint32) DAAP_TIMEOUT);
	dmap_structure_add (msrv, DMAP_CC_MSAL, (gchar) 0);
262
	dmap_structure_add (msrv, DMAP_CC_MSUP, (gchar) 1);
263
264
265
266
267
268
269
270
	dmap_structure_add (msrv, DMAP_CC_MSPI, (gchar) 0);
	dmap_structure_add (msrv, DMAP_CC_MSEX, (gchar) 0);
	dmap_structure_add (msrv, DMAP_CC_MSBR, (gchar) 0);
	dmap_structure_add (msrv, DMAP_CC_MSQY, (gchar) 0);
	dmap_structure_add (msrv, DMAP_CC_MSIX, (gchar) 0);
	dmap_structure_add (msrv, DMAP_CC_MSRS, (gchar) 0);
	dmap_structure_add (msrv, DMAP_CC_MSDC, (gint32) 1);

271
	_dmap_share_message_set_from_dmap_structure (share, message, msrv);
272
273
274
275
276
	dmap_structure_destroy (msrv);

	g_free (nameprop);
}

W. Michael Petullo's avatar
W. Michael Petullo committed
277
278
typedef enum
{
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
	ITEM_ID = 0,
	ITEM_NAME,
	ITEM_KIND,
	PERSISTENT_ID,
	CONTAINER_ITEM_ID,
	SONG_ALBUM,
	SONG_GROUPING,
	SONG_ARTIST,
	SONG_BITRATE,
	SONG_BPM,
	SONG_COMMENT,
	SONG_COMPILATION,
	SONG_COMPOSER,
	SONG_DATA_KIND,
	SONG_DATA_URL,
	SONG_DATE_ADDED,
	SONG_DATE_MODIFIED,
	SONG_DISC_COUNT,
	SONG_DISC_NUMBER,
	SONG_DISABLED,
	SONG_EQ_PRESET,
	SONG_FORMAT,
	SONG_GENRE,
	SONG_DESCRIPTION,
	SONG_RELATIVE_VOLUME,
	SONG_SAMPLE_RATE,
	SONG_SIZE,
306
307
	SONG_SORT_ALBUM,
	SONG_SORT_ARTIST,
308
309
310
311
312
313
314
	SONG_START_TIME,
	SONG_STOP_TIME,
	SONG_TIME,
	SONG_TRACK_COUNT,
	SONG_TRACK_NUMBER,
	SONG_USER_RATING,
	SONG_YEAR,
W. Michael Petullo's avatar
W. Michael Petullo committed
315
316
317
318
319
320
321
322
	SONG_HAS_VIDEO,
	SONG_SMART_PLAYLIST,
	SONG_IS_PODCAST_PLAYLIST,
	SONG_SPECIAL_PLAYLIST,
	SONG_SAVED_GENIUS,
	SONG_MEDIAKIND,
	HAS_CHILD_CONTAINERS,
	PARENT_CONTAINER_ID
323
324
325
} DAAPMetaData;

static struct DMAPMetaDataMap meta_data_map[] = {
W. Michael Petullo's avatar
W. Michael Petullo committed
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
	{"dmap.itemid", ITEM_ID},
	{"dmap.itemname", ITEM_NAME},
	{"dmap.itemkind", ITEM_KIND},
	{"dmap.persistentid", PERSISTENT_ID},
	{"dmap.containeritemid", CONTAINER_ITEM_ID},
	{"daap.songalbum", SONG_ALBUM},
	{"daap.songartist", SONG_ARTIST},
	{"daap.songbitrate", SONG_BITRATE},
	{"daap.songbeatsperminute", SONG_BPM},
	{"daap.songcomment", SONG_COMMENT},
	{"daap.songcompilation", SONG_COMPILATION},
	{"daap.songcomposer", SONG_COMPOSER},
	{"daap.songdatakind", SONG_DATA_KIND},
	{"daap.songdataurl", SONG_DATA_URL},
	{"daap.songdateadded", SONG_DATE_ADDED},
	{"daap.songdatemodified", SONG_DATE_MODIFIED},
	{"daap.songdescription", SONG_DESCRIPTION},
	{"daap.songdisabled", SONG_DISABLED},
	{"daap.songdisccount", SONG_DISC_COUNT},
	{"daap.songdiscnumber", SONG_DISC_NUMBER},
	{"daap.songeqpreset", SONG_EQ_PRESET},
	{"daap.songformat", SONG_FORMAT},
	{"daap.songgenre", SONG_GENRE},
	{"daap.songgrouping", SONG_GROUPING},
	{"daap.songrelativevolume", SONG_RELATIVE_VOLUME},
	{"daap.songsamplerate", SONG_SAMPLE_RATE},
	{"daap.songsize", SONG_SIZE},
	{"daap.songstarttime", SONG_START_TIME},
	{"daap.songstoptime", SONG_STOP_TIME},
	{"daap.songtime", SONG_TIME},
	{"daap.songtrackcount", SONG_TRACK_COUNT},
	{"daap.songtracknumber", SONG_TRACK_NUMBER},
	{"daap.songuserrating", SONG_USER_RATING},
	{"daap.songyear", SONG_YEAR},
	{"daap.sortalbum", SONG_SORT_ALBUM},
	{"daap.sortartist", SONG_SORT_ARTIST},
	{"com.apple.itunes.has-video", SONG_HAS_VIDEO},
	{"com.apple.itunes.smart-playlist", SONG_SMART_PLAYLIST},
	{"com.apple.itunes.is-podcast-playlist", SONG_IS_PODCAST_PLAYLIST},
	{"com.apple.itunes.special-playlist", SONG_SPECIAL_PLAYLIST},
	{"com.apple.itunes.saved-genius", SONG_SAVED_GENIUS},
	{"com.apple.itunes.mediakind", SONG_MEDIAKIND},
	{"dmap.haschildcontainers", HAS_CHILD_CONTAINERS},
	{"dmap.parentcontainerid", PARENT_CONTAINER_ID},
	{NULL, 0}
};
372
373
374
375

#define DAAP_ITEM_KIND_AUDIO 2
#define DAAP_SONG_DATA_KIND_NONE 0

W. Michael Petullo's avatar
W. Michael Petullo committed
376
377
static gboolean should_transcode (const gchar *format, const gboolean has_video, const gchar *transcode_mimetype)
{
378
	gboolean fnval = FALSE;
W. Michael Petullo's avatar
W. Michael Petullo committed
379
380
381
	char *format2 = NULL;

	// Not presently transcoding videos (see also same comments elsewhere).
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
	if (TRUE == has_video) {
		goto done;
	}

	if (NULL == transcode_mimetype) {
		goto done;
	}

	format2 = dmap_mime_to_format (transcode_mimetype);
	if (NULL == format2) {
		g_warning ("Configured to transcode, but target format is bad");
		goto done;
	}

	if (strcmp (format, format2)) {
		fnval = TRUE;
	}
W. Michael Petullo's avatar
W. Michael Petullo committed
399

400
done:
401
	g_debug ("    Should%s transcode %s to %s", fnval ? "" : " not", format, format2 ? format2 : "[no target format]");
W. Michael Petullo's avatar
W. Michael Petullo committed
402
403
404
405

	return fnval;
}

406
static void
W. Michael Petullo's avatar
W. Michael Petullo committed
407
408
409
send_chunked_file (SoupServer * server, SoupMessage * message,
		   DAAPRecord * record, guint64 filesize, guint64 offset,
		   const gchar * transcode_mimetype)
410
{
411
	gchar *format = NULL;
412
413
	gchar *location = NULL;
	GInputStream *stream = NULL;
414
415
	gboolean has_video;
	GError *error = NULL;
416
417
418
419
420
421
422
	ChunkData *cd = NULL;

	cd = g_new (ChunkData, 1);
	if (NULL == cd) {
		g_warning ("Error allocating chunk\n");
		goto _error;
	}
423

424
	g_object_get (record, "location", &location, "has-video", &has_video, NULL);
425
426
427
428
	if (NULL == location) {
		g_warning ("Error getting location from record\n");
		goto _error;
	}
429
430

	/* FIXME: This crashes on powerpc-440fp-linux-gnu:
431
	 * g_debug ("Sending %s chunked from offset %" G_GUINT64_FORMAT ".", location, offset);
432
433
434
435
436
437
438
	 */

	cd->server = server;

	stream = G_INPUT_STREAM (daap_record_read (record, &error));
	if (error != NULL) {
		g_warning ("Couldn't open %s: %s.", location, error->message);
439
		goto _error;
440
441
	}

442
	g_object_get (record, "format", &format, NULL);
443
444
445
446
447
	if (NULL == format) {
		g_warning ("Error getting format from record\n");
		goto _error;
	}

448
	// Not presently transcoding videos (see also same comments elsewhere).
W. Michael Petullo's avatar
W. Michael Petullo committed
449
	if (should_transcode (format, has_video, transcode_mimetype)) {
450
#ifdef HAVE_GSTREAMERAPP
W. Michael Petullo's avatar
W. Michael Petullo committed
451
		cd->stream = dmap_gst_input_stream_new (transcode_mimetype, stream);
452
#else
W. Michael Petullo's avatar
W. Michael Petullo committed
453
454
455
		g_warning ("Transcode format %s not supported", transcode_mimetype);
		cd->stream = stream;
#endif /* HAVE_GSTREAMERAPP */
456
	} else {
W. Michael Petullo's avatar
W. Michael Petullo committed
457
		g_debug ("Not transcoding %s", location);
458
459
460
		cd->stream = stream;
	}

461
462
	if (cd->stream == NULL) {
		g_warning ("Could not set up input stream");
463
		goto _error;
464
465
	}

466
	if (offset != 0) {
467
		if (g_seekable_seek (G_SEEKABLE (cd->stream), offset, G_SEEK_SET, NULL, &error) == FALSE) {
468
			g_warning ("Error seeking: %s.", error->message);
469
			goto _error;
W. Michael Petullo's avatar
W. Michael Petullo committed
470
		}
471
472
473
474
475
476
		filesize -= offset;
	}

	/* Free memory after each chunk sent out over network. */
	soup_message_body_set_accumulate (message->response_body, FALSE);

W. Michael Petullo's avatar
W. Michael Petullo committed
477
478
479
480
481
	if (! should_transcode (format, has_video, transcode_mimetype)) {
	        /* NOTE: iTunes seems to require this or it stops reading 
	         * video data after about 2.5MB. Perhaps this is so iTunes
	         * knows how much data to buffer.
	         */
482
		g_debug ("Using HTTP 1.1 content length encoding.");
W. Michael Petullo's avatar
W. Michael Petullo committed
483
484
485
486
487
488
489
		soup_message_headers_set_encoding (message->response_headers, SOUP_ENCODING_CONTENT_LENGTH);

	        /* NOTE: iTunes 8 (and other versions?) will not seek
	         * properly without a Content-Length header.
	         */
		g_debug ("Content length is %" G_GUINT64_FORMAT ".", filesize);
		soup_message_headers_set_content_length (message->response_headers, filesize);
490
491
492
493
	} else if (soup_message_get_http_version (message) == SOUP_HTTP_1_0) {
		/* NOTE: Roku clients support only HTTP 1.0. */
#ifdef HAVE_ENCODING_EOF
		g_debug ("Using HTTP 1.0 encoding.");
W. Michael Petullo's avatar
W. Michael Petullo committed
494
		soup_message_headers_set_encoding (message->response_headers, SOUP_ENCODING_EOF);
495
#else
W. Michael Petullo's avatar
W. Michael Petullo committed
496
497
		g_warning ("Received HTTP 1.0 request, but not built with HTTP 1.0 support");
		soup_message_headers_set_encoding (message->response_headers, SOUP_ENCODING_CHUNKED);
498
499
500
501
502
503
#endif
	} else {
		/* NOTE: Can not provide Content-Length when performing
		 * real-time transcoding.
		 */
		g_debug ("Using HTTP 1.1 chunked encoding.");
W. Michael Petullo's avatar
W. Michael Petullo committed
504
		soup_message_headers_set_encoding (message->response_headers, SOUP_ENCODING_CHUNKED);
505
506
	}

W. Michael Petullo's avatar
W. Michael Petullo committed
507
508
509
510
511
512
513
514
515
516
517
518
	soup_message_headers_append (message->response_headers, "Connection",
				     "Close");
	soup_message_headers_append (message->response_headers,
				     "Content-Type",
				     "application/x-dmap-tagged");

	g_signal_connect (message, "wrote_headers",
			  G_CALLBACK (dmap_write_next_chunk), cd);
	g_signal_connect (message, "wrote_chunk",
			  G_CALLBACK (dmap_write_next_chunk), cd);
	g_signal_connect (message, "finished",
			  G_CALLBACK (dmap_chunked_message_finished), cd);
519
	/* NOTE: cd g_free'd by chunked_message_finished(). */
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545

	return;
_error:
	soup_message_set_status (message, SOUP_STATUS_INTERNAL_SERVER_ERROR);

	if (NULL != cd) {
		g_free (cd);
	}

	if (NULL != format) {
		g_free (format);
	}

	if (NULL != location) {
		g_free (location);
	}

	if (NULL != error) {
		g_error_free (error);
	}
	
	if (NULL != cd->stream) {
		g_input_stream_close (cd->stream, NULL, NULL);
	}
	
	if (NULL != stream) {
546
		g_input_stream_close (stream, NULL, NULL);
547
548
549
	}

	return;
550
551
}

552
static void
W. Michael Petullo's avatar
W. Michael Petullo committed
553
add_entry_to_mlcl (gpointer id, DMAPRecord * record, gpointer _mb)
554
555
{
	GNode *mlit;
556
	gboolean has_video = 0;
557
	struct MLCL_Bits *mb = (struct MLCL_Bits *) _mb;
W. Michael Petullo's avatar
W. Michael Petullo committed
558

559
	mlit = dmap_structure_add (mb->mlcl, DMAP_CC_MLIT);
560
	g_object_get (record, "has-video", &has_video, NULL);
561

562
	if (_dmap_share_client_requested (mb->bits, ITEM_KIND))
W. Michael Petullo's avatar
W. Michael Petullo committed
563
564
		dmap_structure_add (mlit, DMAP_CC_MIKD,
				    (gchar) DAAP_ITEM_KIND_AUDIO);
565
	if (_dmap_share_client_requested (mb->bits, ITEM_ID))
W. Michael Petullo's avatar
W. Michael Petullo committed
566
567
		dmap_structure_add (mlit, DMAP_CC_MIID,
				    GPOINTER_TO_UINT (id));
568
	if (_dmap_share_client_requested (mb->bits, ITEM_NAME)) {
569
		gchar *title = NULL;
W. Michael Petullo's avatar
W. Michael Petullo committed
570

571
		g_object_get (record, "title", &title, NULL);
572
573
574
575
		if (title) {
			dmap_structure_add (mlit, DMAP_CC_MINM, title);
			g_free (title);
		} else
576
			g_debug ("Title requested but not available");
577
	}
578
	if (_dmap_share_client_requested (mb->bits, PERSISTENT_ID))
W. Michael Petullo's avatar
W. Michael Petullo committed
579
580
		dmap_structure_add (mlit, DMAP_CC_MPER,
				    GPOINTER_TO_UINT (id));
581
	if (_dmap_share_client_requested (mb->bits, CONTAINER_ITEM_ID))
W. Michael Petullo's avatar
W. Michael Petullo committed
582
583
		dmap_structure_add (mlit, DMAP_CC_MCTI,
				    GPOINTER_TO_UINT (id));
584
	if (_dmap_share_client_requested (mb->bits, SONG_DATA_KIND))
W. Michael Petullo's avatar
W. Michael Petullo committed
585
586
		dmap_structure_add (mlit, DMAP_CC_ASDK,
				    (gchar) DAAP_SONG_DATA_KIND_NONE);
587
	/* FIXME: Any use for this?
W. Michael Petullo's avatar
W. Michael Petullo committed
588
589
590
	 * if (_dmap_share_client_requested (mb->bits, SONG_DATA_URL))
	 * dmap_structure_add (mlit, DMAP_CC_ASUL, "daap://192.168.0.100:%u/databases/1/items/%d.%s?session-id=%s", data->port, *id, daap_record_get_format (DAAP_RECORD (record)), data->session_id);
	 */
591
	if (_dmap_share_client_requested (mb->bits, SONG_ALBUM)) {
592
		gchar *album = NULL;
W. Michael Petullo's avatar
W. Michael Petullo committed
593

594
		g_object_get (record, "songalbum", &album, NULL);
595
596
597
598
		if (album) {
			dmap_structure_add (mlit, DMAP_CC_ASAL, album);
			g_free (album);
		} else
599
			g_debug ("Album requested but not available");
600
	}
601
	if (_dmap_share_client_requested (mb->bits, SONG_GROUPING))
602
		dmap_structure_add (mlit, DMAP_CC_AGRP, "");
603
	if (_dmap_share_client_requested (mb->bits, SONG_ARTIST)) {
604
		gchar *artist = NULL;
W. Michael Petullo's avatar
W. Michael Petullo committed
605

606
		g_object_get (record, "songartist", &artist, NULL);
607
608
609
610
		if (artist) {
			dmap_structure_add (mlit, DMAP_CC_ASAR, artist);
			g_free (artist);
		} else
611
			g_debug ("Artist requested but not available");
612
	}
613
	if (_dmap_share_client_requested (mb->bits, SONG_BITRATE)) {
614
		gint32 bitrate = 0;
W. Michael Petullo's avatar
W. Michael Petullo committed
615

616
617
		g_object_get (record, "bitrate", &bitrate, NULL);
		if (bitrate != 0)
W. Michael Petullo's avatar
W. Michael Petullo committed
618
619
			dmap_structure_add (mlit, DMAP_CC_ASBR,
					    (gint32) bitrate);
620
	}
621
	if (_dmap_share_client_requested (mb->bits, SONG_BPM))
622
		dmap_structure_add (mlit, DMAP_CC_ASBT, (gint32) 0);
623
	if (_dmap_share_client_requested (mb->bits, SONG_COMMENT))
624
		dmap_structure_add (mlit, DMAP_CC_ASCM, "");
625
	if (_dmap_share_client_requested (mb->bits, SONG_COMPILATION))
626
		dmap_structure_add (mlit, DMAP_CC_ASCO, (gchar) FALSE);
627
	if (_dmap_share_client_requested (mb->bits, SONG_COMPOSER))
628
		dmap_structure_add (mlit, DMAP_CC_ASCP, "");
629
	if (_dmap_share_client_requested (mb->bits, SONG_DATE_ADDED)) {
630
		gint32 firstseen = 0;
W. Michael Petullo's avatar
W. Michael Petullo committed
631

632
633
634
		g_object_get (record, "firstseen", &firstseen, NULL);
		dmap_structure_add (mlit, DMAP_CC_ASDA, firstseen);
	}
635
	if (_dmap_share_client_requested (mb->bits, SONG_DATE_MODIFIED)) {
636
		gint32 mtime = 0;
W. Michael Petullo's avatar
W. Michael Petullo committed
637

638
639
640
		g_object_get (record, "mtime", &mtime, NULL);
		dmap_structure_add (mlit, DMAP_CC_ASDM, mtime);
	}
641
	if (_dmap_share_client_requested (mb->bits, SONG_DISC_COUNT))
642
		dmap_structure_add (mlit, DMAP_CC_ASDC, (gint32) 0);
643
	if (_dmap_share_client_requested (mb->bits, SONG_DISC_NUMBER)) {
644
		gint32 disc = 0;
W. Michael Petullo's avatar
W. Michael Petullo committed
645

646
647
648
		g_object_get (record, "disc", &disc, NULL);
		dmap_structure_add (mlit, DMAP_CC_ASDN, disc);
	}
649
	if (_dmap_share_client_requested (mb->bits, SONG_DISABLED))
650
		dmap_structure_add (mlit, DMAP_CC_ASDB, (gchar) FALSE);
651
	if (_dmap_share_client_requested (mb->bits, SONG_EQ_PRESET))
652
		dmap_structure_add (mlit, DMAP_CC_ASEQ, "");
653
	if (_dmap_share_client_requested (mb->bits, SONG_FORMAT)) {
654
		gchar *format = NULL;
655
		gchar *transcode_mimetype = NULL;
W. Michael Petullo's avatar
W. Michael Petullo committed
656

657
		g_object_get (mb->share, "transcode-mimetype",
W. Michael Petullo's avatar
W. Michael Petullo committed
658
			      &transcode_mimetype, NULL);
659
660
		// Not presently transcoding videos (see also same comments elsewhere).
		if (! has_video && transcode_mimetype) {
661
			format = g_strdup (dmap_mime_to_format
W. Michael Petullo's avatar
W. Michael Petullo committed
662
					   (transcode_mimetype));
663
664
			g_free (transcode_mimetype);
		} else {
665
			g_object_get (record, "format", &format, NULL);
666
		}
667
668
669
670
		if (format) {
			dmap_structure_add (mlit, DMAP_CC_ASFM, format);
			g_free (format);
		} else
671
			g_debug ("Format requested but not available");
672
	}
673
	if (_dmap_share_client_requested (mb->bits, SONG_GENRE)) {
674
		gchar *genre = NULL;
W. Michael Petullo's avatar
W. Michael Petullo committed
675

676
		g_object_get (record, "songgenre", &genre, NULL);
677
678
679
680
		if (genre) {
			dmap_structure_add (mlit, DMAP_CC_ASGN, genre);
			g_free (genre);
		} else
681
			g_debug ("Genre requested but not available");
682
	}
683
	if (_dmap_share_client_requested (mb->bits, SONG_DESCRIPTION))
W. Michael Petullo's avatar
W. Michael Petullo committed
684
		dmap_structure_add (mlit, DMAP_CC_ASDT, "");	/* FIXME: e.g., wav audio file */
685
	if (_dmap_share_client_requested (mb->bits, SONG_RELATIVE_VOLUME))
686
		dmap_structure_add (mlit, DMAP_CC_ASRV, 0);
687
	if (_dmap_share_client_requested (mb->bits, SONG_SAMPLE_RATE))
688
		dmap_structure_add (mlit, DMAP_CC_ASSR, 0);
689
	if (_dmap_share_client_requested (mb->bits, SONG_SIZE)) {
690
		guint64 filesize = 0;
W. Michael Petullo's avatar
W. Michael Petullo committed
691

692
693
694
		g_object_get (record, "filesize", &filesize, NULL);
		dmap_structure_add (mlit, DMAP_CC_ASSZ, (gint32) filesize);
	}
695
	if (_dmap_share_client_requested (mb->bits, SONG_START_TIME))
696
		dmap_structure_add (mlit, DMAP_CC_ASST, 0);
697
	if (_dmap_share_client_requested (mb->bits, SONG_STOP_TIME))
698
		dmap_structure_add (mlit, DMAP_CC_ASSP, 0);
699
	if (_dmap_share_client_requested (mb->bits, SONG_TIME)) {
700
		gint32 duration;
W. Michael Petullo's avatar
W. Michael Petullo committed
701

702
703
704
		g_object_get (record, "duration", &duration, NULL);
		dmap_structure_add (mlit, DMAP_CC_ASTM, (1000 * duration));
	}
705
	if (_dmap_share_client_requested (mb->bits, SONG_TRACK_COUNT))
706
		dmap_structure_add (mlit, DMAP_CC_ASTC, 0);
707
	if (_dmap_share_client_requested (mb->bits, SONG_TRACK_NUMBER)) {
708
		gint32 track = 0;
W. Michael Petullo's avatar
W. Michael Petullo committed
709

710
711
712
		g_object_get (record, "track", &track, NULL);
		dmap_structure_add (mlit, DMAP_CC_ASTN, track);
	}
713
	if (_dmap_share_client_requested (mb->bits, SONG_USER_RATING)) {
714
		gint32 rating = 0;
W. Michael Petullo's avatar
W. Michael Petullo committed
715

716
717
718
		g_object_get (record, "rating", &rating, NULL);
		dmap_structure_add (mlit, DMAP_CC_ASUR, rating);
	}
719
	if (_dmap_share_client_requested (mb->bits, SONG_YEAR)) {
720
		gint32 year = 0;
W. Michael Petullo's avatar
W. Michael Petullo committed
721

722
723
724
		g_object_get (record, "year", &year, NULL);
		dmap_structure_add (mlit, DMAP_CC_ASYR, year);
	}
725
	if (_dmap_share_client_requested (mb->bits, SONG_HAS_VIDEO)) {
726
727
		dmap_structure_add (mlit, DMAP_CC_AEHV, has_video);
	}
728
	if (_dmap_share_client_requested (mb->bits, SONG_SORT_ARTIST)) {
729
		gchar *sort_artist = NULL;
W. Michael Petullo's avatar
W. Michael Petullo committed
730

731
		g_object_get (record, "sort-artist", &sort_artist, NULL);
732
		if (sort_artist) {
733
			dmap_structure_add (mlit, DMAP_CC_ASSA, sort_artist);
734
735
			g_free (sort_artist);
		} else {
736
			g_debug ("Sort artist requested but not available");
737
		}
738
739
	}
	if (_dmap_share_client_requested (mb->bits, SONG_SORT_ALBUM)) {
740
		gchar *sort_album = NULL;
W. Michael Petullo's avatar
W. Michael Petullo committed
741

742
		g_object_get (record, "sort-album", &sort_album, NULL);
743
		if (sort_album) {
744
			dmap_structure_add (mlit, DMAP_CC_ASSU, sort_album);
745
746
			g_free (sort_album);
		} else {
747
			g_debug ("Sort album requested but not available");
748
		}
749
	}
W. Michael Petullo's avatar
W. Michael Petullo committed
750
	if (_dmap_share_client_requested (mb->bits, SONG_MEDIAKIND)) {
751
		gint mediakind = 0;
W. Michael Petullo's avatar
W. Michael Petullo committed
752

W. Michael Petullo's avatar
W. Michael Petullo committed
753
754
755
		g_object_get (record, "mediakind", &mediakind, NULL);
		dmap_structure_add (mlit, DMAP_CC_AEMK, mediakind);
	}
756
757
758
}

static void
W. Michael Petullo's avatar
W. Michael Petullo committed
759
genre_tabulator (gpointer id, DMAPRecord * record, GHashTable * ht)
760
761
{
	const gchar *genre;
W. Michael Petullo's avatar
W. Michael Petullo committed
762

763
764
765
	g_object_get (record, "songgenre", &genre, NULL);
	if (!genre)
		return;
W. Michael Petullo's avatar
W. Michael Petullo committed
766
	if (!g_hash_table_lookup (ht, genre))
767
768
769
770
		g_hash_table_insert (ht, (gchar *) genre, NULL);
}

static void
W. Michael Petullo's avatar
W. Michael Petullo committed
771
artist_tabulator (gpointer id, DMAPRecord * record, GHashTable * ht)
772
773
{
	const gchar *artist;
W. Michael Petullo's avatar
W. Michael Petullo committed
774

775
776
777
	g_object_get (record, "songartist", &artist, NULL);
	if (!artist)
		return;
W. Michael Petullo's avatar
W. Michael Petullo committed
778
	if (!g_hash_table_lookup (ht, artist))
779
780
781
782
		g_hash_table_insert (ht, (gchar *) artist, NULL);
}

static void
W. Michael Petullo's avatar
W. Michael Petullo committed
783
album_tabulator (gpointer id, DMAPRecord * record, GHashTable * ht)
784
785
{
	const gchar *album;
W. Michael Petullo's avatar
W. Michael Petullo committed
786

787
788
789
	g_object_get (record, "songalbum", &album, NULL);
	if (!album)
		return;
W. Michael Petullo's avatar
W. Michael Petullo committed
790
	if (!g_hash_table_lookup (ht, album))
791
792
793
		g_hash_table_insert (ht, (gchar *) album, NULL);
}

794
static void
795
add_to_category_listing (gpointer key, gpointer user_data)
796
797
798
799
800
801
802
803
{
	GNode *mlit;
	GNode *node = (GNode *) user_data;

	mlit = dmap_structure_add (node, DMAP_CC_MLIT);
	dmap_structure_add (mlit, DMAP_RAW, (char *) key);
}

804
static void
W. Michael Petullo's avatar
W. Michael Petullo committed
805
806
807
databases_browse_xxx (DMAPShare * share,
		      SoupServer * server,
		      SoupMessage * msg,
808
		      const char *path,
W. Michael Petullo's avatar
W. Michael Petullo committed
809
		      GHashTable * query, SoupClientContext * context)
810
811
{
	/* ABRO database browse
W. Michael Petullo's avatar
W. Michael Petullo committed
812
813
814
815
816
817
818
819
	 *      MSTT status
	 *      MUTY update type
	 *      MTCO specified total count
	 *      MRCO returned count
	 *      ABGN genre listing
	 *              MLIT listing item
	 *              ...
	 */
820
821
822
823
824
825
826
827
828
829
	DMAPDb *db;
	const gchar *rest_of_path;
	GNode *abro, *node;
	gchar *filter;
	GSList *filter_def;
	GHashTable *filtered;
	guint num_genre;
	const gchar *browse_category;
	GHashTable *category_items;
	DMAPContentCode category_cc;
830
	GList *values;
831

832
833
834
835
836
	rest_of_path = strchr (path + 1, '/');
	browse_category = rest_of_path + 10;
	category_items = g_hash_table_new (g_str_hash, g_str_equal);

	filter = g_hash_table_lookup (query, "filter");
W. Michael Petullo's avatar
W. Michael Petullo committed
837
	filter_def = _dmap_share_build_filter (filter);
838
839
840
841
	g_object_get (share, "db", &db, NULL);
	filtered = dmap_db_apply_filter (db, filter_def);

	if (g_ascii_strcasecmp (browse_category, "genres") == 0) {
W. Michael Petullo's avatar
W. Michael Petullo committed
842
843
		g_hash_table_foreach (filtered, (GHFunc) genre_tabulator,
				      category_items);
844
845
		category_cc = DMAP_CC_ABGN;
	} else if (g_ascii_strcasecmp (browse_category, "artists") == 0) {
W. Michael Petullo's avatar
W. Michael Petullo committed
846
847
		g_hash_table_foreach (filtered, (GHFunc) artist_tabulator,
				      category_items);
848
849
		category_cc = DMAP_CC_ABAR;
	} else if (g_ascii_strcasecmp (browse_category, "albums") == 0) {
W. Michael Petullo's avatar
W. Michael Petullo committed
850
851
		g_hash_table_foreach (filtered, (GHFunc) album_tabulator,
				      category_items);
852
853
854
855
856
857
		category_cc = DMAP_CC_ABAL;
	} else {
		g_warning ("Unsupported browse category: %s",
			   browse_category);
		goto _bad_category;
	}
858

859
860
861
	abro = dmap_structure_add (NULL, DMAP_CC_ABRO);
	dmap_structure_add (abro, DMAP_CC_MSTT, (gint32) DMAP_STATUS_OK);
	dmap_structure_add (abro, DMAP_CC_MUTY, 0);
862

863
864
865
866
867
	num_genre = g_hash_table_size (category_items);
	dmap_structure_add (abro, DMAP_CC_MTCO, (gint32) num_genre);
	dmap_structure_add (abro, DMAP_CC_MRCO, (gint32) num_genre);

	node = dmap_structure_add (abro, category_cc);
868

869
	values = g_hash_table_get_keys (category_items);
870
	if (values && g_hash_table_lookup (query, "include-sort-headers")) {
871
		g_debug ("Sorting...");
W. Michael Petullo's avatar
W. Michael Petullo committed
872
873
		values = g_list_sort (values,
				      (GCompareFunc) g_ascii_strcasecmp);
874
875
876
	}

	g_list_foreach (values, add_to_category_listing, node);
W. Michael Petullo's avatar
W. Michael Petullo committed
877

878
	g_list_free (values);
879

880
881
	_dmap_share_message_set_from_dmap_structure (share, msg, abro);
	dmap_structure_destroy (abro);
W. Michael Petullo's avatar
W. Michael Petullo committed
882
      _bad_category:
883
	dmap_share_free_filter (filter_def);
884
885
886
887
	/* Free's hash table but not data (points into real DB): */
	g_hash_table_destroy (filtered);
	g_hash_table_destroy (category_items);
}
888

889
static void
W. Michael Petullo's avatar
W. Michael Petullo committed
890
891
892
databases_items_xxx (DMAPShare * share,
		     SoupServer * server,
		     SoupMessage * msg,
893
		     const char *path,
W. Michael Petullo's avatar
W. Michael Petullo committed
894
		     GHashTable * query, SoupClientContext * context)
895
896
897
898
899
{
	DMAPDb *db;
	const gchar *transcode_mimetype;
	const gchar *rest_of_path;
	const gchar *id_str;
900
	guint id;
901
902
903
904
	const gchar *range_header;
	guint64 filesize;
	guint64 offset = 0;
	DAAPRecord *record;
905

906
907
	rest_of_path = strchr (path + 1, '/');
	id_str = rest_of_path + 9;
908
	id = strtoul (id_str, NULL, 10);
909
910
911
912
913
914

	g_object_get (share, "db", &db, NULL);
	record = DAAP_RECORD (dmap_db_lookup_by_id (db, id));
	g_object_get (record, "filesize", &filesize, NULL);

	DMAP_SHARE_GET_CLASS (share)->message_add_standard_headers
W. Michael Petullo's avatar
W. Michael Petullo committed
915
916
917
		(share, msg);
	soup_message_headers_append (msg->response_headers, "Accept-Ranges",
				     "bytes");
918

W. Michael Petullo's avatar
W. Michael Petullo committed
919
	range_header =
920
		soup_message_headers_get_one (msg->request_headers, "Range");
921
922
923
924
	if (range_header) {
		const gchar *s;
		gchar *content_range;

925
926
927
928
929
930
931
		if (!g_ascii_strncasecmp (range_header, "bytes=", strlen("bytes="))) {
			/* Not starting with "bytes=" ? */
			offset = 0;
		} else {
			s = range_header + strlen ("bytes=");	/* bytes= */
			offset = atoll (s);
		}
932

W. Michael Petullo's avatar
W. Michael Petullo committed
933
934
935
936
937
938
939
		content_range =
			g_strdup_printf ("bytes %" G_GUINT64_FORMAT "-%"
					 G_GUINT64_FORMAT "/%"
					 G_GUINT64_FORMAT, offset, filesize,
					 filesize);
		soup_message_headers_append (msg->response_headers,
					     "Content-Range", content_range);
940
941
942
		g_debug ("Content range is %s.", content_range);
		g_free (content_range);
		soup_message_set_status (msg, SOUP_STATUS_PARTIAL_CONTENT);
943
	} else {
944
		soup_message_set_status (msg, SOUP_STATUS_OK);
945
	}
946
	g_object_get (share, "transcode-mimetype", &transcode_mimetype, NULL);
W. Michael Petullo's avatar
W. Michael Petullo committed
947
948
949
	send_chunked_file (server, msg, record, filesize, offset,
			   transcode_mimetype);

950
951
952
953
	g_object_unref (record);
}

static struct DMAPMetaDataMap *
W. Michael Petullo's avatar
W. Michael Petullo committed
954
get_meta_data_map (DMAPShare * share)
955
956
{
	return meta_data_map;
957
}