/* * Copyright (C) 2009-2010 Julien BLACHE * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include "db.h" #include "misc.h" #include "httpd.h" #include "logger.h" #include "dmap_common.h" /* gperf static hash, dmap_fields.gperf */ #include "dmap_fields_hash.h" const struct dmap_field * dmap_get_fields_table(int *nfields) { *nfields = sizeof(dmap_fields) / sizeof(dmap_fields[0]); return dmap_fields; } // This wrapper is so callers don't need to include dmap_fields_hash.h const struct dmap_field * dmap_find_field_wrapper(const char *str, int len) { return dmap_find_field(str, len); } void dmap_add_container(struct evbuffer *evbuf, const char *tag, int len) { unsigned char buf[4]; evbuffer_add(evbuf, tag, 4); /* Container length */ buf[0] = (len >> 24) & 0xff; buf[1] = (len >> 16) & 0xff; buf[2] = (len >> 8) & 0xff; buf[3] = len & 0xff; evbuffer_add(evbuf, buf, sizeof(buf)); } void dmap_add_long(struct evbuffer *evbuf, const char *tag, int64_t val) { unsigned char buf[12]; evbuffer_add(evbuf, tag, 4); /* Length */ buf[0] = 0; buf[1] = 0; buf[2] = 0; buf[3] = 8; /* Value */ buf[4] = (val >> 56) & 0xff; buf[5] = (val >> 48) & 0xff; buf[6] = (val >> 40) & 0xff; buf[7] = (val >> 32) & 0xff; buf[8] = (val >> 24) & 0xff; buf[9] = (val >> 16) & 0xff; buf[10] = (val >> 8) & 0xff; buf[11] = val & 0xff; evbuffer_add(evbuf, buf, sizeof(buf)); } void dmap_add_int(struct evbuffer *evbuf, const char *tag, int val) { unsigned char buf[8]; evbuffer_add(evbuf, tag, 4); /* Length */ buf[0] = 0; buf[1] = 0; buf[2] = 0; buf[3] = 4; /* Value */ buf[4] = (val >> 24) & 0xff; buf[5] = (val >> 16) & 0xff; buf[6] = (val >> 8) & 0xff; buf[7] = val & 0xff; evbuffer_add(evbuf, buf, sizeof(buf)); } void dmap_add_short(struct evbuffer *evbuf, const char *tag, short val) { unsigned char buf[6]; evbuffer_add(evbuf, tag, 4); /* Length */ buf[0] = 0; buf[1] = 0; buf[2] = 0; buf[3] = 2; /* Value */ buf[4] = (val >> 8) & 0xff; buf[5] = val & 0xff; evbuffer_add(evbuf, buf, sizeof(buf)); } void dmap_add_char(struct evbuffer *evbuf, const char *tag, char val) { unsigned char buf[5]; evbuffer_add(evbuf, tag, 4); /* Length */ buf[0] = 0; buf[1] = 0; buf[2] = 0; buf[3] = 1; /* Value */ buf[4] = val; evbuffer_add(evbuf, buf, sizeof(buf)); } void dmap_add_literal(struct evbuffer *evbuf, const char *tag, char *str, int len) { char buf[4]; evbuffer_add(evbuf, tag, 4); /* Length */ buf[0] = (len >> 24) & 0xff; buf[1] = (len >> 16) & 0xff; buf[2] = (len >> 8) & 0xff; buf[3] = len & 0xff; evbuffer_add(evbuf, buf, sizeof(buf)); if (str && (len > 0)) evbuffer_add(evbuf, str, len); } void dmap_add_raw_uint32(struct evbuffer *evbuf, uint32_t val) { unsigned char buf[4]; /* Value */ buf[0] = (val >> 24) & 0xff; buf[1] = (val >> 16) & 0xff; buf[2] = (val >> 8) & 0xff; buf[3] = val & 0xff; evbuffer_add(evbuf, buf, sizeof(buf)); } void dmap_add_string(struct evbuffer *evbuf, const char *tag, const char *str) { unsigned char buf[4]; int len; if (str) len = strlen(str); else len = 0; evbuffer_add(evbuf, tag, 4); /* String length */ buf[0] = (len >> 24) & 0xff; buf[1] = (len >> 16) & 0xff; buf[2] = (len >> 8) & 0xff; buf[3] = len & 0xff; evbuffer_add(evbuf, buf, sizeof(buf)); if (len) evbuffer_add(evbuf, str, len); } void dmap_add_field(struct evbuffer *evbuf, const struct dmap_field *df, char *strval, int32_t intval) { union { int32_t v_i32; uint32_t v_u32; int64_t v_i64; uint64_t v_u64; } val; int ret; if (strval && (df->type != DMAP_TYPE_STRING)) { switch (df->type) { case DMAP_TYPE_DATE: case DMAP_TYPE_UBYTE: case DMAP_TYPE_USHORT: case DMAP_TYPE_UINT: ret = safe_atou32(strval, &val.v_u32); if (ret < 0) val.v_u32 = 0; break; case DMAP_TYPE_BYTE: case DMAP_TYPE_SHORT: case DMAP_TYPE_INT: ret = safe_atoi32(strval, &val.v_i32); if (ret < 0) val.v_i32 = 0; break; case DMAP_TYPE_ULONG: ret = safe_atou64(strval, &val.v_u64); if (ret < 0) val.v_u64 = 0; break; case DMAP_TYPE_LONG: ret = safe_atoi64(strval, &val.v_i64); if (ret < 0) val.v_i64 = 0; break; /* DMAP_TYPE_VERSION & DMAP_TYPE_LIST not handled here */ default: DPRINTF(E_LOG, L_DAAP, "Unsupported DMAP type %d for DMAP field %s\n", df->type, df->desc); return; } } else if (!strval && (df->type != DMAP_TYPE_STRING)) { switch (df->type) { case DMAP_TYPE_DATE: case DMAP_TYPE_UBYTE: case DMAP_TYPE_USHORT: case DMAP_TYPE_UINT: val.v_u32 = intval; break; case DMAP_TYPE_BYTE: case DMAP_TYPE_SHORT: case DMAP_TYPE_INT: val.v_i32 = intval; break; case DMAP_TYPE_ULONG: val.v_u64 = intval; break; case DMAP_TYPE_LONG: val.v_i64 = intval; break; /* DMAP_TYPE_VERSION & DMAP_TYPE_LIST not handled here */ default: DPRINTF(E_LOG, L_DAAP, "Unsupported DMAP type %d for DMAP field %s\n", df->type, df->desc); return; } } switch (df->type) { case DMAP_TYPE_UBYTE: if (val.v_u32) dmap_add_char(evbuf, df->tag, val.v_u32); break; case DMAP_TYPE_BYTE: if (val.v_i32) dmap_add_char(evbuf, df->tag, val.v_i32); break; case DMAP_TYPE_USHORT: if (val.v_u32) dmap_add_short(evbuf, df->tag, val.v_u32); break; case DMAP_TYPE_SHORT: if (val.v_i32) dmap_add_short(evbuf, df->tag, val.v_i32); break; case DMAP_TYPE_DATE: case DMAP_TYPE_UINT: if (val.v_u32) dmap_add_int(evbuf, df->tag, val.v_u32); break; case DMAP_TYPE_INT: if (val.v_i32) dmap_add_int(evbuf, df->tag, val.v_i32); break; case DMAP_TYPE_ULONG: if (val.v_u64) dmap_add_long(evbuf, df->tag, val.v_u64); break; case DMAP_TYPE_LONG: if (val.v_i64) dmap_add_long(evbuf, df->tag, val.v_i64); break; case DMAP_TYPE_STRING: if (strval) dmap_add_string(evbuf, df->tag, strval); break; case DMAP_TYPE_VERSION: case DMAP_TYPE_LIST: return; } } void dmap_error_make(struct evbuffer *evbuf, const char *container, const char *errmsg) { int len; len = 12 + 8 + 8 + strlen(errmsg); CHECK_ERR(L_DMAP, evbuffer_expand(evbuf, len)); dmap_add_container(evbuf, container, len - 8); dmap_add_int(evbuf, "mstt", 500); dmap_add_string(evbuf, "msts", errmsg); } void dmap_send_error(struct evhttp_request *req, const char *container, const char *errmsg) { struct evbuffer *evbuf; if (!req) return; evbuf = evbuffer_new(); if (!evbuf) { DPRINTF(E_LOG, L_DMAP, "Could not allocate evbuffer for DMAP error\n"); httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error"); return; } dmap_error_make(evbuf, container, errmsg); httpd_send_reply(req, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP); evbuffer_free(evbuf); } int dmap_encode_file_metadata(struct evbuffer *songlist, struct evbuffer *song, struct db_media_file_info *dbmfi, const struct dmap_field **meta, int nmeta, int sort_tags, int force_wav) { const struct dmap_field_map *dfm; const struct dmap_field *df; char **strval; char *ptr; int32_t val; int want_mikd; int want_asdk; int want_ased; int i; int ret; want_mikd = 0; want_asdk = 0; want_ased = 0; i = -1; while (1) { i++; /* Specific meta tags requested (or default list) */ if (nmeta > 0) { if (i == nmeta) break; df = meta[i]; if (df->dfm) dfm = df->dfm; else break; } /* No specific meta tags requested, send out everything */ else { /* End of list */ if (i == (sizeof(dmap_fields) / sizeof(dmap_fields[0]))) break; df = &dmap_fields[i]; dfm = dmap_fields[i].dfm; } /* Extradata not in media_file_info but flag for reply */ if (dfm == &dfm_dmap_ased) { want_ased = 1; continue; } /* Not in struct media_file_info */ if (dfm->mfi_offset < 0) continue; /* Will be prepended to the list */ if (dfm == &dfm_dmap_mikd) { /* item kind */ want_mikd = 1; continue; } else if (dfm == &dfm_dmap_asdk) { /* data kind */ want_asdk = 1; continue; } DPRINTF(E_SPAM, L_DAAP, "Investigating %s\n", df->desc); strval = (char **) ((char *)dbmfi + dfm->mfi_offset); if (!(*strval) || (**strval == '\0')) continue; /* Here's one exception ... codectype (ascd) is actually an integer */ if (dfm == &dfm_dmap_ascd) { dmap_add_literal(song, df->tag, *strval, 4); continue; } val = 0; if (force_wav) { switch (dfm->mfi_offset) { case dbmfi_offsetof(type): ptr = "wav"; strval = &ptr; break; case dbmfi_offsetof(bitrate): val = 0; ret = safe_atoi32(dbmfi->samplerate, &val); if ((ret < 0) || (val == 0)) val = 1411; else val = (val * 8) / 250; ptr = NULL; strval = &ptr; break; case dbmfi_offsetof(description): ptr = "wav audio file"; strval = &ptr; break; default: break; } } dmap_add_field(song, df, *strval, val); DPRINTF(E_SPAM, L_DAAP, "Done with meta tag %s (%s)\n", df->desc, *strval); } /* Required for artwork in iTunes, set songartworkcount (asac) = 1 */ if (want_ased) { dmap_add_short(song, "ased", 1); dmap_add_short(song, "asac", 1); } if (sort_tags) { dmap_add_string(song, "assn", dbmfi->title_sort); dmap_add_string(song, "assa", dbmfi->artist_sort); dmap_add_string(song, "assu", dbmfi->album_sort); dmap_add_string(song, "assl", dbmfi->album_artist_sort); if (dbmfi->composer_sort) dmap_add_string(song, "assc", dbmfi->composer_sort); } val = 0; if (want_mikd) val += 9; if (want_asdk) val += 9; dmap_add_container(songlist, "mlit", evbuffer_get_length(song) + val); /* Prepend mikd & asdk if needed */ if (want_mikd) { /* dmap.itemkind must come first */ ret = safe_atoi32(dbmfi->item_kind, &val); if (ret < 0) val = 2; /* music by default */ dmap_add_char(songlist, "mikd", val); } if (want_asdk) { ret = safe_atoi32(dbmfi->data_kind, &val); if (ret < 0) val = 0; dmap_add_char(songlist, "asdk", val); } ret = evbuffer_add_buffer(songlist, song); if (ret < 0) { DPRINTF(E_LOG, L_DAAP, "Could not add song to song list\n"); return -1; } return 0; } int dmap_encode_queue_metadata(struct evbuffer *songlist, struct evbuffer *song, struct db_queue_item *queue_item) { int32_t val; int want_mikd; int want_asdk; int want_ased; int ret; dmap_add_int(song, "miid", queue_item->file_id); dmap_add_string(song, "minm", queue_item->title); dmap_add_long(song, "mper", queue_item->file_id); dmap_add_int(song, "mcti", queue_item->file_id); dmap_add_string(song, "asal", queue_item->album); dmap_add_long(song, "asai", queue_item->songalbumid); dmap_add_string(song, "asaa", queue_item->album_artist); dmap_add_string(song, "asar", queue_item->artist); dmap_add_int(song, "asdm", queue_item->time_modified); dmap_add_short(song, "asdn", queue_item->disc); dmap_add_string(song, "asgn", queue_item->genre); dmap_add_int(song, "astm", queue_item->song_length); dmap_add_short(song, "astn", queue_item->track); dmap_add_short(song, "asyr", queue_item->year); dmap_add_int(song, "aeMK", queue_item->media_kind); dmap_add_char(song, "aeMk", queue_item->media_kind); dmap_add_string(song, "asfm", "wav"); dmap_add_short(song, "asbr", 1411); dmap_add_string(song, "asdt", "wav audio file"); want_mikd = 1;/* Will be prepended to the list *//* item kind */ want_asdk = 1;/* Will be prepended to the list *//* data kind */ want_ased = 1;/* Extradata not in media_file_info but flag for reply */ /* Required for artwork in iTunes, set songartworkcount (asac) = 1 */ if (want_ased) { dmap_add_short(song, "ased", 1); dmap_add_short(song, "asac", 1); } val = 0; if (want_mikd) val += 9; if (want_asdk) val += 9; dmap_add_container(songlist, "mlit", evbuffer_get_length(song) + val); /* Prepend mikd & asdk if needed */ if (want_mikd) { /* dmap.itemkind must come first */ val = 2; /* music by default */ dmap_add_char(songlist, "mikd", val); } if (want_asdk) { ret = queue_item->data_kind; if (ret < 0) val = 0; dmap_add_char(songlist, "asdk", val); } ret = evbuffer_add_buffer(songlist, song); if (ret < 0) { DPRINTF(E_LOG, L_DAAP, "Could not add song to song list\n"); return -1; } return 0; }