diff -ruN /tmp/gmediaserver-0.9.0/src/contentdir.c ./src/contentdir.c --- /tmp/gmediaserver-0.9.0/src/contentdir.c 2005-12-11 01:19:48.000000000 +0100 +++ ./src/contentdir.c 2006-01-11 22:16:46.000000000 +0100 @@ -36,9 +36,14 @@ #include "search-parser.h" #include "search-lexer.h" +#define DEFAULT_ENTRIES_SIZE 512 + extern int yyparse(yyscan_t scanner, SearchCriteriaParseData *data); static uint32_t update_id = 0; +static Entry **search_result; +static uint32_t search_result_count = 0; +static uint32_t search_result_size = 0; static ServiceVariable * find_variable(const char *name) @@ -140,7 +145,7 @@ } static char * -get_entry_property(Entry *entry, char *property) +get_entry_property(Entry *entry, char *property, char *value) { EntryDetail *detail; @@ -162,6 +167,15 @@ } if (strcmp(property, "dc:title") == 0) { + if (detail != NULL && detail->data.tag.artist != NULL && strcmp(value, "object.container.person.musicArtist") == 0) + return convert_string(detail->data.tag.artist); + + if (detail != NULL && detail->data.tag.album != NULL && strcmp(value, "object.container.album.musicAlbum") ==0) + return convert_string(detail->data.tag.album); + + if (detail != NULL && detail->data.tag.genre != NULL && strcmp(value, "object.container.genre.musicGenre") == 0) + return convert_string(detail->data.tag.genre); + if (detail != NULL && detail->data.tag.title != NULL && strcmp(detail->data.tag.title, "") != 0) return convert_string(detail->data.tag.title); return convert_string(entry->name); @@ -172,6 +186,15 @@ return convert_string("object.container.storageFolder"); /*if (has_entry_detail(entry, DETAIL_URL)) return convert_string("object.item.audioItem.audioBroadcast");*/ + if(strcmp(value, "object.container.person.musicArtist") == 0 && detail->data.tag.artist != NULL) + return convert_string("object.container.person.musicArtist"); + + if(strcmp(value, "object.container.album.musicAlbum") == 0 && detail->data.tag.album != NULL) + return convert_string("object.container.album.musicAlbum"); + + if(strcmp(value, "object.container.genre.musicGenre") == 0 && detail->data.tag.genre != NULL) + return convert_string("object.container.genre.musicGenre"); + return convert_string("object.item.audioItem.musicTrack"); } @@ -225,8 +248,30 @@ return false; } +static char* +find_search_expr_value(SearchExpr *expr, Entry *entry, char *property) +{ + char *value = NULL; + + switch(expr->type) { + case T_AND: + case T_OR: + value = find_search_expr_value(expr->u.logical.expr1, entry, property); + if(value == NULL) + value = find_search_expr_value(expr->u.logical.expr2, entry, property); + break; + + default: + if(strcmp(expr->u.binary.property, property) == 0) + value = expr->u.binary.value; + } + if(value == NULL) + value = ""; + return value; +} + static void -append_entry(StrBuf *result, Entry *entry, const char *filter) +append_entry(StrBuf *result, Entry *entry, const char *filter, SearchCriteria *criteria) { EntryDetail *detail; char *value; @@ -234,15 +279,22 @@ if (has_entry_detail(entry, DETAIL_CHILDREN)) { detail = get_entry_detail(entry, DETAIL_CHILDREN); strbuf_appendf(result, - "", + "", entry->id, entry->parent, detail->data.children.count); } else { + if(strcmp(find_search_expr_value(criteria->expr, entry, "upnp:class"), "object.container.album.musicAlbum") == 0) { + strbuf_appendf(result, + "", + entry->parent, + get_entry_by_id(entry->parent)->parent); + } else { strbuf_appendf(result, - "", + "", entry->id, entry->parent); + } if (filter_includes_any(filter, "res", "@protocolInfo", "@size", "address@hidden", "address@hidden", NULL)) { detail = get_entry_detail(entry, DETAIL_FILE); if (detail != NULL) { @@ -252,7 +304,7 @@ /*if (detail->data.file.size != 0)*/ if (filter_includes_any(filter, "res", "@size", "address@hidden", NULL)) strbuf_appendf(result, " size=\"%" PRIu32 "\"", detail->data.file.size); - strbuf_appendf(result, ">http://%s:%d/files/%d", + strbuf_appendf(result, ">http://%s:%d/files/%d.mp3", UpnpGetServerIpAddress(), UpnpGetServerPort(), entry->id); @@ -261,7 +313,7 @@ strbuf_append(result, "http://%s:%d/files/%d", + strbuf_appendf(result, ">http://%s:%d/files/%d.mp3", UpnpGetServerIpAddress(), UpnpGetServerPort(), entry->id); @@ -270,30 +322,30 @@ } /* upnp:class is required and cannot be filtered out. */ - value = get_entry_property(entry, "upnp:class"); + value = get_entry_property(entry, "upnp:class", ""); strbuf_appendf(result, "%s", value); free(value); /* dc:title is required and cannot be filtered out. */ - value = get_entry_property(entry, "dc:title"); + value = get_entry_property(entry, "dc:title", find_search_expr_value(criteria->expr, entry, "upnp:class")); if (value != NULL) append_escaped_xml(result, "dc:title", value); free(value); /* XXX: dc:creator instead or in combination with upnp:artist? */ if (filter_includes(filter, "upnp:artist")) { - value = get_entry_property(entry, "upnp:artist"); + value = get_entry_property(entry, "upnp:artist", ""); if (value != NULL) append_escaped_xml(result, "upnp:artist", value); free(value); } if (filter_includes(filter, "upnp:album")) { - value = get_entry_property(entry, "upnp:album"); + value = get_entry_property(entry, "upnp:album", ""); if (value != NULL) append_escaped_xml(result, "upnp:album", value); free(value); } if (filter_includes(filter, "upnp:genre")) { - value = get_entry_property(entry, "upnp:genre"); + value = get_entry_property(entry, "upnp:genre", ""); if (value != NULL) append_escaped_xml(result, "upnp:genre", value); free(value); @@ -397,47 +449,47 @@ return match_search_expr(expr->u.logical.expr1, entry) || match_search_expr(expr->u.logical.expr2, entry); case T_EXISTS: - value = get_entry_property(entry, expr->u.exists.property); + value = get_entry_property(entry, expr->u.exists.property, ""); result = (value != NULL) == (expr->u.exists.must_exist); free(value); return result; case T_EQ: - value = get_entry_property(entry, expr->u.binary.property); + value = get_entry_property(entry, expr->u.binary.property, expr->u.binary.value); result = strcmp(value, expr->u.binary.value) == 0; free(value); return result; case T_NE: - value = get_entry_property(entry, expr->u.binary.property); + value = get_entry_property(entry, expr->u.binary.property, ""); result = strcmp(value, expr->u.binary.value) != 0; free(value); return result; case T_LT: - value = get_entry_property(entry, expr->u.binary.property); + value = get_entry_property(entry, expr->u.binary.property, ""); result = strcmp(value, expr->u.binary.value) > 0; free(value); return result; case T_LE: - value = get_entry_property(entry, expr->u.binary.property); + value = get_entry_property(entry, expr->u.binary.property, ""); result = strcmp(value, expr->u.binary.value) >= 0; free(value); return result; case T_GT: - value = get_entry_property(entry, expr->u.binary.property); + value = get_entry_property(entry, expr->u.binary.property, ""); result = strcmp(value, expr->u.binary.value) < 0; free(value); return result; case T_GE: - value = get_entry_property(entry, expr->u.binary.property); + value = get_entry_property(entry, expr->u.binary.property, ""); result = strcmp(value, expr->u.binary.value) <= 0; free(value); return result; case T_CONTAINS: - value = get_entry_property(entry, expr->u.binary.property); + value = get_entry_property(entry, expr->u.binary.property, ""); result = strstr(value, expr->u.binary.value) != NULL; free(value); return result; case T_DOES_NOT_CONTAIN: - value = get_entry_property(entry, expr->u.binary.property); + value = get_entry_property(entry, expr->u.binary.property, ""); result = strstr(value, expr->u.binary.value) == NULL; free(value); return result; @@ -474,10 +526,20 @@ return match_search_expr(criteria->expr, entry); } +static void +add_search_result_hit(Entry *entry) +{ + if(search_result_count >= search_result_size) { + search_result_size *= 2; + search_result = xrealloc(search_result, search_result_size * sizeof(Entry *)); + } + search_result[search_result_count++] = entry; +} + static uint32_t search_container(SearchCriteria *criteria, Entry *entry, uint32_t *skip, uint32_t max_count, - const char *filter, StrBuf *result) + const char *filter) { EntryDetail *detail; uint32_t c; @@ -495,7 +557,8 @@ (*skip)--; } else { /*if (result != NULL)*/ - append_entry(result, child, filter); + // twi append_entry(result, child, filter, criteria); + add_search_result_hit(child); match_count++; if (match_count > max_count) return match_count; @@ -508,7 +571,7 @@ * can return more than UINT32_MAX results anyway * (the return value would overflow). */ - match_count += search_container(criteria, child, skip, max_count-match_count, filter, result); + match_count += search_container(criteria, child, skip, max_count-match_count, filter); if (match_count > max_count) return match_count; } @@ -549,11 +612,11 @@ count = UINT32_MAX; /* SortCriteria not supported at the moment */ - if (strcmp(sort_criteria, "") != 0) { + /*if (strcmp(sort_criteria, "") != 0) { upnp_set_error(event, UPNP_CONTENTDIR_E_BAD_SORT_CRITERIA, _("Sorting not supported")); return false; - } + }*/ /* Check SearchCriteria */ criteria = parse_search_criteria(search_criteria, &error); @@ -568,34 +631,123 @@ } /* Check ContainerID argument */ - lock_metadata(); - entry = get_entry_by_id(id); - if (entry == NULL) { - upnp_set_error(event, UPNP_CONTENTDIR_E_NO_CONTAINER, - _("No such container")); - unlock_metadata(); - return false; - } - detail = get_entry_detail(entry, DETAIL_CHILDREN); - if (detail == NULL) { - upnp_set_error(event, UPNP_CONTENTDIR_E_NO_CONTAINER, - _("Not a container")); - unlock_metadata(); - return false; + if(id < 100) { + /*if(strcmp(criteria->expr->u.binary.value, convert_string("object.container.person.musicArtist")) == 0) { + say(4, "--- musicArtist match\n");*/ + lock_metadata(); + entry = get_entry_by_id(100); +/* } else if(strcmp(criteria->expr->u.binary.value, convert_string("object.container.album.musicAlbum")) == 0) { + say(4, "--- musicAlbum match\n"); + upnp_set_error(event, UPNP_CONTENTDIR_E_NO_CONTAINER, + _("No such container")); + return false; + } else if(strcmp(criteria->expr->u.binary.value, convert_string("object.container.playlistContainer")) == 0) { + say(4, "--- playlist match\n"); + upnp_set_error(event, UPNP_CONTENTDIR_E_NO_CONTAINER, + _("No such container")); + return false; + } else if(strcmp(criteria->expr->u.binary.value, convert_string("object.item.audioItem")) == 0) { + lock_metadata(); + entry = get_entry_by_id(100); + } else if(strcmp(criteria->expr->u.binary.value, convert_string("object.container.genre.musicGenre")) == 0) { + say(4, "--- musicGenre match\n"); + upnp_set_error(event, UPNP_CONTENTDIR_E_NO_CONTAINER, + _("No such container")); + return false; + }*/ + } else { + lock_metadata(); + entry = get_entry_by_id(id); + if (entry == NULL) { + upnp_set_error(event, UPNP_CONTENTDIR_E_NO_CONTAINER, + _("No such container")); + unlock_metadata(); + return false; + } + detail = get_entry_detail(entry, DETAIL_CHILDREN); + if (detail == NULL) { + upnp_set_error(event, UPNP_CONTENTDIR_E_NO_CONTAINER, + _("Not a container")); + unlock_metadata(); + return false; + } } /* Do the actual searching */ + search_result_size = DEFAULT_ENTRIES_SIZE; + search_result_count = 0; + search_result = xmalloc(sizeof(Entry *) * search_result_size); + match_count = search_container(criteria, entry, &index, count, filter); + + Entry **unique_titles; + unique_titles = xmalloc(sizeof(Entry *) * DEFAULT_ENTRIES_SIZE); + bool not_unique = false; + uint32_t unique_titles_count = 0; + + uint32_t total_matches = 0; + result = strbuf_new(); begin_result(result); - match_count = search_container(criteria, entry, &index, count, filter, result); + say(4, "--- search result count: %d\n", search_result_count); + // XXX result_entries -> result + uint32_t i, j; + char *upnp_class; + for(i=0; iexpr, entry, "upnp:class"); + if(strcmp(upnp_class, "object.container.person.musicArtist") == 0 || + strcmp(upnp_class, "object.container.album.musicAlbum") == 0 || + strcmp(upnp_class, "object.container.genre.musicGenre") == 0) { + for(j=0; jdata.tag.artist, + get_entry_detail(search_result[i], DETAIL_TAG)->data.tag.artist) == 0) { + not_unique = true; + break; + } + + if(strcmp(upnp_class, "object.container.album.musicAlbum") == 0 && + strcmp(get_entry_detail(unique_titles[j], DETAIL_TAG)->data.tag.album, + get_entry_detail(search_result[i], DETAIL_TAG)->data.tag.album) == 0) { + not_unique = true; + break; + } + + if(strcmp(upnp_class, "object.container.genre.musicGenre") == 0 && + strcmp(get_entry_detail(unique_titles[j], DETAIL_TAG)->data.tag.genre, + get_entry_detail(search_result[i], DETAIL_TAG)->data.tag.genre) == 0) { + not_unique = true; + break; + } + + + } + if(not_unique) { + not_unique=false; + continue; + } + unique_titles[unique_titles_count++] = search_result[i]; + append_entry(result, search_result[i], filter, criteria); + total_matches++; + + } else { + append_entry(result, search_result[i], filter, criteria); + total_matches = match_count; + } + } end_result(result); + + /* Make response. */ upnp_add_response(event, "Result", strbuf_buffer(result)); upnp_add_response(event, "NumberReturned", int32_str(match_count)); - upnp_add_response(event, "TotalMatches", "0"); + upnp_add_response(event, "TotalMatches", int32_str(total_matches)); upnp_add_response(event, "UpdateID", uint32_str(id == 0 ? update_id : 0)); strbuf_free(result); + + free(unique_titles); + free(search_result); unlock_metadata(); return true; @@ -620,6 +772,7 @@ /* ObjectID is a string according to ContentDir specification, but we use int32. */ /* XXX: is this OK? maybe we should use a more appropriate error response if not int32. */ id = upnp_get_i4(event, "ObjectID"); + if(id < 100) id = 100; filter = upnp_get_string(event, "Filter"); strreplace(filter, ' ', ','); /* XXX: This is a hack. Better remove all spaces. */ flag = upnp_get_string(event, "BrowseFlag"); @@ -665,7 +818,8 @@ result = strbuf_new(); begin_result(result); - append_entry(result, entry, filter); + // twi append_entry(result, entry, filter, NULL); + add_search_result_hit(entry); end_result(result); upnp_add_response(event, "Result", strbuf_buffer(result)); upnp_add_response(event, "NumberReturned", "1"); @@ -702,7 +856,7 @@ end_index = MIN(index+count, end_index); for (c = index; c < end_index; c++) { Entry *child = get_entry_by_id(detail->data.children.list[c]); - append_entry(result, child, filter); + append_entry(result, child, filter, NULL); result_count++; } end_result(result); @@ -731,6 +885,8 @@ free(find_variable("SystemUpdateID")->value); } + + /* XXX: In the future, ServiceVariable should allow different * data types (and point to real variables, e.g. {"SystemUpdateID", UINT32_TYPE, &update_id}). */ diff -ruN /tmp/gmediaserver-0.9.0/src/gmediaserver.h ./src/gmediaserver.h --- /tmp/gmediaserver-0.9.0/src/gmediaserver.h 2005-10-10 08:10:37.000000000 +0200 +++ ./src/gmediaserver.h 2006-01-06 20:03:16.000000000 +0100 @@ -257,6 +257,13 @@ extern ServiceAction connectmgr_service_actions[]; extern ServiceVariable connectmgr_service_variables[]; +/* mediarecreg.c */ +#define MEDIARECREG_SERVICE_ID "urn:microsoft.com:serviceId:X_MS_MediaReceiverRegistrar" +extern ServiceAction mediarecreg_service_actions[]; +extern ServiceVariable mediarecreg_service_variables[]; + + + /* url.c */ void sgmlescape(const char *str, char *target, uint32_t *length); char *xsgmlescape(const char *str); diff -ruN /tmp/gmediaserver-0.9.0/src/Makefile.in ./src/Makefile.in --- /tmp/gmediaserver-0.9.0/src/Makefile.in 2005-12-13 21:50:45.000000000 +0100 +++ ./src/Makefile.in 2006-01-06 18:01:56.000000000 +0100 @@ -83,7 +83,7 @@ am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(man1dir)" binPROGRAMS_INSTALL = $(INSTALL_PROGRAM) PROGRAMS = $(bin_PROGRAMS) -am_gmediaserver_OBJECTS = connectmgr.$(OBJEXT) contentdir.$(OBJEXT) \ +am_gmediaserver_OBJECTS = connectmgr.$(OBJEXT) contentdir.$(OBJEXT) mediarecreg.${OBJEXT} \ interface.$(OBJEXT) logging.$(OBJEXT) upnp.$(OBJEXT) \ webserver.$(OBJEXT) webclient.$(OBJEXT) main.$(OBJEXT) \ metadata.$(OBJEXT) url.$(OBJEXT) search-lexer.$(OBJEXT) \ @@ -262,6 +262,7 @@ gmediaserver_SOURCES = \ connectmgr.c \ contentdir.c \ + mediarecreg.c \ interface.c \ logging.c \ upnp.c \ @@ -370,6 +371,7 @@ @AMDEP_TRUE@@am__include@ @address@hidden/$(DEPDIR)/address@hidden@ @AMDEP_TRUE@@am__include@ @address@hidden/$(DEPDIR)/address@hidden@ address@hidden@@am__include@ @address@hidden/$(DEPDIR)/address@hidden@ @AMDEP_TRUE@@am__include@ @address@hidden/$(DEPDIR)/address@hidden@ @AMDEP_TRUE@@am__include@ @address@hidden/$(DEPDIR)/address@hidden@ @AMDEP_TRUE@@am__include@ @address@hidden/$(DEPDIR)/address@hidden@ diff -ruN /tmp/gmediaserver-0.9.0/src/mediarecreg.c ./src/mediarecreg.c --- /tmp/gmediaserver-0.9.0/src/mediarecreg.c 1970-01-01 01:00:00.000000000 +0100 +++ ./src/mediarecreg.c 2006-01-06 20:06:36.000000000 +0100 @@ -0,0 +1,82 @@ +/* contentdir.c - Implementation of UPnP ContentDirectory + * + * Copyright (C) 2005 Oskar Liljeblad + * + * 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 Library 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include +#include /* Gnulib/C99 */ +#include /* ? */ +#include "gettext.h" /* Gnulib/gettext */ +#define _(s) gettext(s) +#define N_(s) gettext_noop(s) +#include "xvasprintf.h" /* Gnulib */ +#include "minmax.h" /* Gnulib */ +#include "xalloc.h" /* Gnulib */ +#include "quotearg.h" /* Gnulib */ +#include "dirname.h" /* Gnulib */ +#include "strbuf.h" +#include "intutil.h" +#include "strutil.h" +#include "gmediaserver.h" +#include "search-parser.h" +#include "search-lexer.h" + +extern int yyparse(yyscan_t scanner, SearchCriteriaParseData *data); + +static uint32_t update_id = 0; + +static ServiceVariable * +find_variable(const char *name) +{ + int c; + for (c = 0; contentdir_service_variables[c].name != NULL; c++) { + if (strcmp(contentdir_service_variables[c].name, name) == 0) + return &contentdir_service_variables[c]; + } + assert(0); /* Shouldn't get here */ +} + +static bool +mediarecreg_is_authorized(ActionEvent *event) +{ + upnp_add_response(event, "Result", "1"); + return event->status; +} + +static bool +mediarecreg_is_validated(ActionEvent *event) +{ + upnp_add_response(event, "Result", "1"); + return event->status; +} + +/* XXX: In the future, ServiceVariable should allow different + * data types (and point to real variables, e.g. {"SystemUpdateID", UINT32_TYPE, &update_id}). + */ +ServiceVariable mediarecreg_service_variables[] = { + /*{ "TransferIDs", NULL },*/ + { "SystemUpdateID", NULL }, + /*{ "ContainerUpdateIDs", NULL },*/ + { 0, } +}; + +ServiceAction mediarecreg_service_actions[] = { + { "IsAuthorized", mediarecreg_is_authorized }, + { "IsValidated", mediarecreg_is_validated }, + { 0, } +}; diff -ruN /tmp/gmediaserver-0.9.0/src/metadata.c ./src/metadata.c --- /tmp/gmediaserver-0.9.0/src/metadata.c 2005-09-11 09:22:26.000000000 +0200 +++ ./src/metadata.c 2006-01-10 20:04:36.000000000 +0100 @@ -110,7 +110,7 @@ static Entry *scan_playlist_file(const char *fullpath, const char *name, int32_t parent, FileType type); static Entry *root_entry; -static uint32_t entry_count = 0; +static uint32_t entry_count = 100; static uint32_t entries_size = 0; static Entry **entries; char *file_types = DEFAULT_FILE_TYPES; @@ -664,7 +664,7 @@ free(entries[c]); } - entry_count = 0; + entry_count = 100; } void diff -ruN /tmp/gmediaserver-0.9.0/src/schemas/MediaServer.xml.in ./src/schemas/MediaServer.xml.in --- /tmp/gmediaserver-0.9.0/src/schemas/MediaServer.xml.in 2005-06-17 14:05:11.000000000 +0200 +++ ./src/schemas/MediaServer.xml.in 2006-01-06 17:39:42.000000000 +0100 @@ -9,7 +9,7 @@ @DEVICE_UDN@ %s Oskar Liljeblad - @PACKAGE_NAME@ + Windows Media Connect @PACKAGE_VERSION@ 0000001 @@ -30,6 +30,13 @@ /ConnectionManager/Event /ConnectionManager/Control + + urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1 + urn:microsoft.com:serviceId:X_MS_MediaReceiverRegistrar + MediaReceiverRegistrar.xml + MediaReceiverRegistrar/Event + MediaReceiverRegistrar/Control + diff -ruN /tmp/gmediaserver-0.9.0/src/upnp.c ./src/upnp.c --- /tmp/gmediaserver-0.9.0/src/upnp.c 2005-12-11 16:09:28.000000000 +0100 +++ ./src/upnp.c 2006-01-06 17:55:28.000000000 +0100 @@ -53,6 +53,12 @@ connectmgr_service_actions, connectmgr_service_variables, }, + { + MEDIARECREG_SERVICE_ID, + "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1", + mediarecreg_service_actions, + mediarecreg_service_variables, + }, { 0, } }; diff -ruN /tmp/gmediaserver-0.9.0/src/webserver.c ./src/webserver.c --- /tmp/gmediaserver-0.9.0/src/webserver.c 2005-08-29 17:06:21.000000000 +0200 +++ ./src/webserver.c 2006-01-10 20:08:20.000000000 +0100 @@ -62,10 +62,14 @@ } WebServerFile; static Entry * -get_entry_from_url(const char *filename) +get_entry_from_url(char *filename) { int32_t id; - + char * dotpos; + + dotpos=strchr(filename, '.'); + if(dotpos!=NULL) + filename[dotpos-filename]='\0'; if (filename[0] != '/') return NULL; filename = strchr(filename+1, '/'); @@ -76,7 +80,8 @@ return NULL; if (!parse_int32(filename, &id)) return NULL; - + if(id<100) + return NULL; return get_entry_by_id(id); }