1 /* Implmentation of DPAP (e.g., iPhoto Picture) sharing
2  *
3  * Copyright (C) 2005 Charles Schmidt <cschmidt2@emich.edu>
4  *
5  * Modifications Copyright (C) 2008 W. Michael Petullo <mike@flyn.org>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  *
21  */
22 
23 #include "config.h"
24 
25 #include <time.h>
26 #include <string.h>
27 #include <stdlib.h>
28 
29 #include <glib/gi18n.h>
30 #include <glib.h>
31 
32 #include <unistd.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <fcntl.h>
36 
37 #include <libsoup/soup.h>
38 #include <libsoup/soup-address.h>
39 #include <libsoup/soup-message.h>
40 #include <libsoup/soup-uri.h>
41 #include <libsoup/soup-server.h>
42 
43 #include <libdmapsharing/dmap.h>
44 #include <libdmapsharing/dmap-private-utils.h>
45 #include <libdmapsharing/dmap-structure.h>
46 
47 static void dpap_share_set_property (GObject * object,
48 				     guint prop_id,
49 				     const GValue * value,
50 				     GParamSpec * pspec);
51 static void dpap_share_get_property (GObject * object,
52 				     guint prop_id,
53 				     GValue * value, GParamSpec * pspec);
54 static void dpap_share_dispose (GObject * object);
55 guint dpap_share_get_desired_port (DMAPShare * share);
56 const char *dpap_share_get_type_of_service (DMAPShare * share);
57 void dpap_share_server_info (DMAPShare * share,
58 			     SoupServer * server,
59 			     SoupMessage * message,
60 			     const char *path,
61 			     GHashTable * query, SoupClientContext * context);
62 void dpap_share_message_add_standard_headers (DMAPShare * share,
63 					      SoupMessage * message);
64 static void databases_browse_xxx (DMAPShare * share,
65 				  SoupServer * server,
66 				  SoupMessage * msg,
67 				  const char *path,
68 				  GHashTable * query,
69 				  SoupClientContext * context);
70 static void databases_items_xxx (DMAPShare * share,
71 				 SoupServer * server,
72 				 SoupMessage * msg,
73 				 const char *path,
74 				 GHashTable * query,
75 				 SoupClientContext * context);
76 static struct DMAPMetaDataMap *get_meta_data_map (DMAPShare * share);
77 static void add_entry_to_mlcl (gpointer id, DMAPRecord * record, gpointer mb);
78 
79 #define DPAP_TYPE_OF_SERVICE "_dpap._tcp"
80 #define DPAP_PORT 8770
81 
82 struct DPAPSharePrivate
83 {
84 	gchar unused;
85 };
86 
87 /* Mmap'ed full image file. Global so that it may be free'ed in a different
88  * function call that the one that set it up.
89  */
90 static GMappedFile *mapped_file = NULL;
91 
92 enum
93 {
94 	PROP_0,
95 };
96 
97 G_DEFINE_TYPE_WITH_PRIVATE (DPAPShare, dpap_share, DMAP_TYPE_SHARE);
98 
99 static void
dpap_share_class_init(DPAPShareClass * klass)100 dpap_share_class_init (DPAPShareClass * klass)
101 {
102 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
103 	DMAPShareClass *parent_class = DMAP_SHARE_CLASS (object_class);
104 
105 	object_class->get_property = dpap_share_get_property;
106 	object_class->set_property = dpap_share_set_property;
107 	object_class->dispose = dpap_share_dispose;
108 
109 	parent_class->get_desired_port = dpap_share_get_desired_port;
110 	parent_class->get_type_of_service = dpap_share_get_type_of_service;
111 	parent_class->message_add_standard_headers =
112 		dpap_share_message_add_standard_headers;
113 	parent_class->get_meta_data_map = get_meta_data_map;
114 	parent_class->add_entry_to_mlcl = add_entry_to_mlcl;
115 	parent_class->databases_browse_xxx = databases_browse_xxx;
116 	parent_class->databases_items_xxx = databases_items_xxx;
117 	parent_class->server_info = dpap_share_server_info;
118 }
119 
120 static void
dpap_share_init(DPAPShare * share)121 dpap_share_init (DPAPShare * share)
122 {
123 	/* FIXME: do I need to manually call parent _init? */
124 	share->priv = dpap_share_get_instance_private(share);
125 }
126 
127 static void
dpap_share_set_property(GObject * object,guint prop_id,G_GNUC_UNUSED const GValue * value,GParamSpec * pspec)128 dpap_share_set_property (GObject * object,
129 			 guint prop_id,
130 			 G_GNUC_UNUSED const GValue * value,
131                          GParamSpec * pspec)
132 {
133 	// DPAPShare *share = DPAP_SHARE (object);
134 
135 	switch (prop_id) {
136 		/* FIXME: */
137 	default:
138 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
139 		break;
140 	}
141 }
142 
143 static void
dpap_share_get_property(GObject * object,guint prop_id,G_GNUC_UNUSED GValue * value,GParamSpec * pspec)144 dpap_share_get_property (GObject * object, guint prop_id,
145                          G_GNUC_UNUSED GValue * value, GParamSpec * pspec)
146 {
147 	// DPAPShare *share = DPAP_SHARE (object);
148 
149 	switch (prop_id) {
150 	default:
151 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
152 		break;
153 	}
154 }
155 
156 static void
dpap_share_dispose(G_GNUC_UNUSED GObject * object)157 dpap_share_dispose (G_GNUC_UNUSED GObject * object)
158 {
159 	/* FIXME: implement in parent */
160 }
161 
162 /* FIXME: trancode_mimetype currently not used for DPAP, only DAAP.
163  *        Threrfore, it is not passed to g_object_new.
164  */
165 DPAPShare *
dpap_share_new(const char * name,const char * password,gpointer db,gpointer container_db,G_GNUC_UNUSED gchar * transcode_mimetype)166 dpap_share_new (const char *name,
167 		const char *password,
168 		gpointer db,
169 		gpointer container_db,
170                 G_GNUC_UNUSED gchar * transcode_mimetype)
171 {
172 	DPAPShare *share;
173 
174 	share = DPAP_SHARE (g_object_new (DPAP_TYPE_SHARE,
175 					  "name", name,
176 					  "password", password,
177 					  "db", db,
178 					  "container-db", container_db,
179 					  NULL));
180 
181 	_dmap_share_server_start (DMAP_SHARE (share));
182 	_dmap_share_publish_start (DMAP_SHARE (share));
183 
184 	return share;
185 }
186 
187 void
dpap_share_message_add_standard_headers(G_GNUC_UNUSED DMAPShare * share,SoupMessage * message)188 dpap_share_message_add_standard_headers (G_GNUC_UNUSED DMAPShare * share,
189 					 SoupMessage * message)
190 {
191 	soup_message_headers_append (message->response_headers, "DPAP-Server",
192 				     "libdmapsharing" VERSION);
193 }
194 
195 #define DMAP_VERSION 2.0
196 #define DPAP_VERSION 1.1
197 #define DPAP_TIMEOUT 1800
198 
199 guint
dpap_share_get_desired_port(G_GNUC_UNUSED DMAPShare * share)200 dpap_share_get_desired_port (G_GNUC_UNUSED DMAPShare * share)
201 {
202 	return DPAP_PORT;
203 }
204 
205 const char *
dpap_share_get_type_of_service(G_GNUC_UNUSED DMAPShare * share)206 dpap_share_get_type_of_service (G_GNUC_UNUSED DMAPShare * share)
207 {
208 	return DPAP_TYPE_OF_SERVICE;
209 }
210 
211 void
dpap_share_server_info(DMAPShare * share,G_GNUC_UNUSED SoupServer * server,SoupMessage * message,const char * path,G_GNUC_UNUSED GHashTable * query,G_GNUC_UNUSED SoupClientContext * context)212 dpap_share_server_info (DMAPShare * share,
213 			G_GNUC_UNUSED SoupServer * server,
214 			SoupMessage * message,
215 			const char *path,
216 			G_GNUC_UNUSED GHashTable * query,
217                         G_GNUC_UNUSED SoupClientContext * context)
218 {
219 /* MSRV	server info response
220  * 	MSTT status
221  * 	MPRO dpap version
222  * 	PPRO dpap version
223  * 	MINM name
224  * 	MSAU authentication method
225  * 	MSLR login required
226  * 	MSTM timeout interval
227  * 	MSAL supports auto logout
228  * 	MSUP supports update
229  * 	MSPI supports persistent ids
230  * 	MSEX supports extensions
231  * 	MSBR supports browse
232  * 	MSQY supports query
233  * 	MSIX supports index
234  * 	MSRS supports resolve
235  * 	MSDC databases count
236  */
237 	gchar *nameprop;
238 	GNode *msrv;
239 
240 	g_debug ("Path is %s.", path);
241 
242 	g_object_get ((gpointer) share, "name", &nameprop, NULL);
243 
244 	msrv = dmap_structure_add (NULL, DMAP_CC_MSRV);
245 	dmap_structure_add (msrv, DMAP_CC_MSTT, (gint32) DMAP_STATUS_OK);
246 	dmap_structure_add (msrv, DMAP_CC_MPRO, (gdouble) DMAP_VERSION);
247 	dmap_structure_add (msrv, DMAP_CC_PPRO, (gdouble) DPAP_VERSION);
248 	dmap_structure_add (msrv, DMAP_CC_MINM, nameprop);
249 	/*dmap_structure_add (msrv, DMAP_CC_MSAU, _dmap_share_get_auth_method (share)); */
250 	/* authentication method
251 	 * 0 is nothing
252 	 * 1 is name & password
253 	 * 2 is password only
254 	 */
255 	dmap_structure_add (msrv, DMAP_CC_MSLR, 0);
256 	dmap_structure_add (msrv, DMAP_CC_MSTM, (gint32) DPAP_TIMEOUT);
257 	dmap_structure_add (msrv, DMAP_CC_MSAL, (gchar) 0);
258 	/*dmap_structure_add (msrv, DMAP_CC_MSUP, (gchar) 1);
259 	 *dmap_structure_add (msrv, DMAP_CC_MSPI, (gchar) 0);
260 	 *dmap_structure_add (msrv, DMAP_CC_MSEX, (gchar) 0);
261 	 *dmap_structure_add (msrv, DMAP_CC_MSBR, (gchar) 0);
262 	 *dmap_structure_add (msrv, DMAP_CC_MSQY, (gchar) 0); */
263 	dmap_structure_add (msrv, DMAP_CC_MSIX, (gchar) 0);
264 	/* dmap_structure_add (msrv, DMAP_CC_MSRS, (gchar) 0); */
265 	dmap_structure_add (msrv, DMAP_CC_MSDC, (gint32) 1);
266 
267 	_dmap_share_message_set_from_dmap_structure (share, message, msrv);
268 	dmap_structure_destroy (msrv);
269 
270 	g_free (nameprop);
271 }
272 
273 typedef enum
274 {
275 	ITEM_ID = 0,
276 	ITEM_NAME,
277 	ITEM_KIND,
278 	PERSISTENT_ID,
279 	CONTAINER_ITEM_ID,
280 	PHOTO_ASPECTRATIO,
281 	PHOTO_CREATIONDATE,
282 	PHOTO_IMAGEFILENAME,
283 	PHOTO_IMAGEFORMAT,
284 	PHOTO_IMAGEFILESIZE,
285 	PHOTO_IMAGELARGEFILESIZE,
286 	PHOTO_IMAGEPIXELHEIGHT,
287 	PHOTO_IMAGEPIXELWIDTH,
288 	PHOTO_IMAGERATING,
289 	PHOTO_HIRES,
290 	PHOTO_THUMB,
291 	PHOTO_FILEDATA,
292 	PHOTO_IMAGECOMMENTS
293 } DPAPMetaData;
294 
295 static struct DMAPMetaDataMap meta_data_map[] = {
296 	{"dmap.itemid", ITEM_ID},
297 	{"dmap.itemname", ITEM_NAME},
298 	{"dmap.itemkind", ITEM_KIND},
299 	{"dmap.persistentid", PERSISTENT_ID},
300 	{"dmap.containeritemid", CONTAINER_ITEM_ID},
301 	{"dpap.aspectratio", PHOTO_ASPECTRATIO},
302 	{"dpap.creationdate", PHOTO_CREATIONDATE},
303 	{"dpap.imagefilename", PHOTO_IMAGEFILENAME},
304 	{"dpap.imageformat", PHOTO_IMAGEFORMAT},
305 	{"dpap.imagefilesize", PHOTO_IMAGEFILESIZE},
306 	{"dpap.imagelargefilesize", PHOTO_IMAGELARGEFILESIZE},
307 	{"dpap.imagepixelheight", PHOTO_IMAGEPIXELHEIGHT},
308 	{"dpap.imagepixelwidth", PHOTO_IMAGEPIXELWIDTH},
309 	{"dpap.imagerating", PHOTO_IMAGERATING},
310 	{"dpap.thumb", PHOTO_THUMB},
311 	{"dpap.hires", PHOTO_HIRES},
312 	{"dpap.filedata", PHOTO_FILEDATA},
313 	{"dpap.imagecomments", PHOTO_IMAGECOMMENTS},
314 	{NULL, 0}
315 };
316 
317 #define DPAP_ITEM_KIND_PHOTO 3	/* This is the constant that dpap-sharp uses. */
318 
319 static GMappedFile *
file_to_mmap(const char * location)320 file_to_mmap (const char *location)
321 {
322 	GFile *file;
323 	GMappedFile *mapped_file = NULL;
324 	char *path;
325 	GError *error = NULL;
326 
327 	file = g_file_new_for_uri (location);
328 	/* NOTE: this is broken if original filename contains "%20" etc. This
329 	 * is because g_file_get_path() will translate this to " ", etc. But
330 	 * the filename really may have used "%20" (not " ").
331 	 */
332 	path = g_file_get_path (file);
333 	if (path == NULL) {
334 		g_warning ("Couldn't mmap %s: couldn't get path", path);
335 		g_object_unref (file);
336 		return mapped_file;
337 	}
338 	g_object_unref (file);
339 
340 	mapped_file = g_mapped_file_new (path, FALSE, &error);
341 	if (mapped_file == NULL) {
342 		g_warning ("Unable to map file %s: %s", path, error->message);
343 	}
344 
345 	g_free (path);
346 	return mapped_file;
347 }
348 
349 static void
add_entry_to_mlcl(gpointer id,DMAPRecord * record,gpointer _mb)350 add_entry_to_mlcl (gpointer id, DMAPRecord * record, gpointer _mb)
351 {
352 	GNode *mlit;
353 	struct MLCL_Bits *mb = (struct MLCL_Bits *) _mb;
354 
355 	mlit = dmap_structure_add (mb->mlcl, DMAP_CC_MLIT);
356 
357 	if (_dmap_share_client_requested (mb->bits, ITEM_KIND))
358 		dmap_structure_add (mlit, DMAP_CC_MIKD,
359 				    (gchar) DPAP_ITEM_KIND_PHOTO);
360 	if (_dmap_share_client_requested (mb->bits, ITEM_ID))
361 		dmap_structure_add (mlit, DMAP_CC_MIID,
362 				    GPOINTER_TO_UINT (id));
363 	if (_dmap_share_client_requested (mb->bits, ITEM_NAME)) {
364 		gchar *filename = NULL;
365 
366 		g_object_get (record, "filename", &filename, NULL);
367 		if (filename) {
368 			dmap_structure_add (mlit, DMAP_CC_MINM, filename);
369 			g_free (filename);
370 		} else
371 			g_debug ("Filename requested but not available");
372 	}
373 	if (_dmap_share_client_requested (mb->bits, PERSISTENT_ID))
374 		dmap_structure_add (mlit, DMAP_CC_MPER,
375 				    GPOINTER_TO_UINT (id));
376 	if (TRUE) {
377 		/* dpap-sharp claims iPhoto '08 will not show thumbnails without PASP
378 		 * and this does seem to be the case when testing. */
379 		gchar *aspect_ratio = NULL;
380 
381 		g_object_get (record, "aspect-ratio", &aspect_ratio, NULL);
382 		if (aspect_ratio) {
383 			dmap_structure_add (mlit, DMAP_CC_PASP, aspect_ratio);
384 			g_free (aspect_ratio);
385 		} else
386 			g_debug
387 				("Aspect ratio requested but not available");
388 	}
389 	if (_dmap_share_client_requested (mb->bits, PHOTO_CREATIONDATE)) {
390 		gint creation_date = 0;
391 
392 		g_object_get (record, "creation-date", &creation_date, NULL);
393 		dmap_structure_add (mlit, DMAP_CC_PICD, creation_date);
394 	}
395 	if (_dmap_share_client_requested (mb->bits, PHOTO_IMAGEFILENAME)) {
396 		gchar *filename = NULL;
397 
398 		g_object_get (record, "filename", &filename, NULL);
399 		if (filename) {
400 			dmap_structure_add (mlit, DMAP_CC_PIMF, filename);
401 			g_free (filename);
402 		} else
403 			g_debug ("Filename requested but not available");
404 	}
405 	if (_dmap_share_client_requested (mb->bits, PHOTO_IMAGEFORMAT)) {
406 		gchar *format = NULL;
407 
408 		g_object_get (record, "format", &format, NULL);
409 		if (format) {
410 			dmap_structure_add (mlit, DMAP_CC_PFMT, format);
411 			g_free (format);
412 		} else
413 			g_debug ("Format requested but not available");
414 	}
415 	if (_dmap_share_client_requested (mb->bits, PHOTO_IMAGEFILESIZE)) {
416 		GByteArray *thumbnail = NULL;
417 
418 		g_object_get (record, "thumbnail", &thumbnail, NULL);
419 		dmap_structure_add (mlit, DMAP_CC_PIFS,
420 				    thumbnail ? thumbnail->len : 0);
421 	}
422 	if (_dmap_share_client_requested (mb->bits, PHOTO_IMAGELARGEFILESIZE)) {
423 		gint large_filesize = 0;
424 
425 		g_object_get (record, "large-filesize", &large_filesize,
426 			      NULL);
427 		dmap_structure_add (mlit, DMAP_CC_PLSZ, large_filesize);
428 	}
429 	if (_dmap_share_client_requested (mb->bits, PHOTO_IMAGEPIXELHEIGHT)) {
430 		gint pixel_height = 0;
431 
432 		g_object_get (record, "pixel-height", &pixel_height, NULL);
433 		dmap_structure_add (mlit, DMAP_CC_PHGT, pixel_height);
434 	}
435 	if (_dmap_share_client_requested (mb->bits, PHOTO_IMAGEPIXELWIDTH)) {
436 		gint pixel_width = 0;
437 
438 		g_object_get (record, "pixel-width", &pixel_width, NULL);
439 		dmap_structure_add (mlit, DMAP_CC_PWTH, pixel_width);
440 	}
441 	if (_dmap_share_client_requested (mb->bits, PHOTO_IMAGERATING)) {
442 		gint rating = 0;
443 
444 		g_object_get (record, "rating", &rating, NULL);
445 		dmap_structure_add (mlit, DMAP_CC_PRAT, rating);
446 	}
447 	if (_dmap_share_client_requested (mb->bits, PHOTO_IMAGECOMMENTS)) {
448 		gchar *comments = NULL;
449 
450 		g_object_get (record, "comments", &comments, NULL);
451 		if (comments) {
452 			dmap_structure_add (mlit, DMAP_CC_PCMT, comments);
453 			g_free (comments);
454 		} else
455 			g_debug ("Comments requested but not available");
456 	}
457 	if (_dmap_share_client_requested (mb->bits, PHOTO_FILEDATA)) {
458 		size_t size = 0;
459 		unsigned char *data = NULL;
460 		GByteArray *thumbnail = NULL;
461 
462 		if (_dmap_share_client_requested (mb->bits, PHOTO_THUMB)) {
463 			g_object_get (record, "thumbnail", &thumbnail, NULL);
464 			if (thumbnail) {
465 				data = thumbnail->data;
466 				size = thumbnail->len;
467 			} else {
468 				data = NULL;
469 				size = 0;
470 			}
471 		} else {
472 			/* Should be PHOTO_HIRES */
473 			char *location = NULL;
474 
475 			g_object_get (record, "location", &location, NULL);
476 			if (mapped_file) {
477 				/* Free any previously mapped image */
478 				g_mapped_file_unref (mapped_file);
479 				mapped_file = NULL;
480 			}
481 
482 			mapped_file = file_to_mmap (location);
483 			if (mapped_file == NULL) {
484 				g_warning ("Error opening %s", location);
485 				data = NULL;
486 				size = 0;
487 			} else {
488 				data = (unsigned char *)
489 					g_mapped_file_get_contents
490 					(mapped_file);
491 				size = g_mapped_file_get_length (mapped_file);
492 			}
493 			g_free (location);
494 		}
495 		dmap_structure_add (mlit, DMAP_CC_PFDT, data, size);
496 	}
497 }
498 
499 static void
databases_browse_xxx(G_GNUC_UNUSED DMAPShare * share,G_GNUC_UNUSED SoupServer * server,G_GNUC_UNUSED SoupMessage * msg,const char * path,G_GNUC_UNUSED GHashTable * query,G_GNUC_UNUSED SoupClientContext * context)500 databases_browse_xxx (G_GNUC_UNUSED DMAPShare * share,
501 		      G_GNUC_UNUSED SoupServer * server,
502 		      G_GNUC_UNUSED SoupMessage * msg,
503 		      const char *path,
504 		      G_GNUC_UNUSED GHashTable * query,
505                       G_GNUC_UNUSED SoupClientContext * context)
506 {
507 	g_warning ("Unhandled: %s\n", path);
508 }
509 
510 static void
send_chunked_file(SoupServer * server,SoupMessage * message,DPAPRecord * record,guint64 filesize)511 send_chunked_file (SoupServer * server, SoupMessage * message,
512 		   DPAPRecord * record, guint64 filesize)
513 {
514 	GInputStream *stream;
515 	const char *location;
516 	GError *error = NULL;
517 	ChunkData *cd = g_new0 (ChunkData, 1);
518 
519 	g_object_get (record, "location", &location, NULL);
520 
521 	cd->server = server;
522 
523 	stream = G_INPUT_STREAM (dpap_record_read (record, &error));
524 
525 	if (error != NULL) {
526 		g_warning ("Couldn't open %s: %s.", location, error->message);
527 		g_error_free (error);
528 		soup_message_set_status (message,
529 					 SOUP_STATUS_INTERNAL_SERVER_ERROR);
530 		g_free (cd);
531 		return;
532 	}
533 
534 	cd->stream = stream;
535 
536 	if (cd->stream == NULL) {
537 		g_warning ("Could not set up input stream");
538 		g_free (cd);
539 		return;
540 	}
541 
542 	soup_message_headers_set_encoding (message->response_headers,
543 					   SOUP_ENCODING_CONTENT_LENGTH);
544 	soup_message_headers_set_content_length (message->response_headers,
545 						 filesize);
546 
547 	soup_message_headers_append (message->response_headers, "Connection",
548 				     "Close");
549 	soup_message_headers_append (message->response_headers,
550 				     "Content-Type",
551 				     "application/x-dmap-tagged");
552 
553 	g_signal_connect (message, "wrote_headers",
554 			  G_CALLBACK (dmap_write_next_chunk), cd);
555 	g_signal_connect (message, "wrote_chunk",
556 			  G_CALLBACK (dmap_write_next_chunk), cd);
557 	g_signal_connect (message, "finished",
558 			  G_CALLBACK (dmap_chunked_message_finished), cd);
559 	/* NOTE: cd g_free'd by chunked_message_finished(). */
560 }
561 
562 static void
databases_items_xxx(DMAPShare * share,SoupServer * server,SoupMessage * msg,const char * path,G_GNUC_UNUSED GHashTable * query,G_GNUC_UNUSED SoupClientContext * context)563 databases_items_xxx (DMAPShare * share,
564 		     SoupServer * server,
565 		     SoupMessage * msg,
566 		     const char *path,
567 		     G_GNUC_UNUSED GHashTable * query,
568                      G_GNUC_UNUSED SoupClientContext * context)
569 {
570 	DMAPDb *db;
571 	const gchar *rest_of_path;
572 	const gchar *id_str;
573 	guint id;
574 	guint64 filesize;
575 	DPAPRecord *record;
576 
577 	rest_of_path = strchr (path + 1, '/');
578 	id_str = rest_of_path + 9;
579 	id = strtoul (id_str, NULL, 10);
580 
581 	g_object_get (share, "db", &db, NULL);
582 	record = DPAP_RECORD (dmap_db_lookup_by_id (db, id));
583 	g_object_get (record, "large-filesize", &filesize, NULL);
584 
585 	DMAP_SHARE_GET_CLASS (share)->message_add_standard_headers
586 		(share, msg);
587 	soup_message_set_status (msg, SOUP_STATUS_OK);
588 
589 	send_chunked_file (server, msg, record, filesize);
590 
591 	g_object_unref (record);
592 }
593 
594 static struct DMAPMetaDataMap *
get_meta_data_map(G_GNUC_UNUSED DMAPShare * share)595 get_meta_data_map (G_GNUC_UNUSED DMAPShare * share)
596 {
597 	return meta_data_map;
598 }
599