// Place the code and data below here into the LIBXDAAP section. #ifndef __GNUC__ #pragma code_seg( "LIBXDAAP_TEXT" ) #pragma data_seg( "LIBXDAAP_DATA" ) #pragma bss_seg( "LIBXDAAP_BSS" ) #pragma const_seg( "LIBXDAAP_RD" ) #pragma comment(linker, "/merge:LIBXDAAP_TEXT=LIBXDAAP") #pragma comment(linker, "/merge:LIBXDAAP_DATA=LIBXDAAP") #pragma comment(linker, "/merge:LIBXDAAP_BSS=LIBXDAAP") #pragma comment(linker, "/merge:LIBXDAAP_RD=LIBXDAAP") #pragma comment(linker, "/section:LIBXDAAP,RWE") #endif /* client class * * Copyright (c) 2004 David Hammerton * crazney@crazney.net * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ /* * Port to XBMC by Forza @ XBMC Media Center Developers * * XBox modifications (c) 2004 Forza (Chris Barnett) * */ /* NOTE: The discovery services have been disabled due to the XBox lacking multicast packets */ #include "portability.h" #include "thread.h" #include #include #include #if defined(SYSTEM_POSIX) # include "threadpool.h" #endif #include "libXDAAP.h" #include "client.h" //#include "discover.h" #include "httpClient.h" #include "private.h" #include "daap.h" #include "daap_contentcodes.h" #include "dmap_generics.h" #include "Authentication/hasher.h" #include "debug.h" #define DEFAULT_DEBUG_CHANNEL "client" static DAAP_SClientHost *DAAP_ClientHost_Create(DAAP_SClient *parent, char *host, char *sharename); static int Priv_DAAP_ClientHost_GetDatabaseItems(DAAP_SClientHost *pCHThis, int databaseid); static int Priv_DAAP_ClientHost_GetDatabasePlaylists(DAAP_SClientHost *pCHThis, int databaseid); struct DAAP_ClientHost_FakeTAG { char sharename[1005]; char sharename_friendly[1005]; unsigned char ip[4]; unsigned short port; DAAP_ClientHost_Fake *next; int marked; }; #if !defined(WIN32) && !defined(_LINUX) static int ClientHasHost_AndMark(DAAP_SClient *pClient, char *sharename) { DAAP_ClientHost_Fake *cur_fake = pClient->fakehosts; DAAP_SClientHost *cur_real = pClient->hosts; while (cur_fake) { if (strcmp(cur_fake->sharename, sharename) == 0) { cur_fake->marked = 1; return 1; } cur_fake = cur_fake->next; } while (cur_real) { if (strcmp(cur_real->sharename, sharename) == 0) { cur_real->marked = 1; return 1; } cur_real = cur_real->next; } return 0; } static void DiscoverCB(SDiscover *disc, void *pv_pClient) { DAAP_SClient *pClient = (DAAP_SClient*)pv_pClient; int added = 0; int deleted = 0; DAAP_ClientHost_Fake *cur_fake, *prev_fake; DAAP_SClientHost *cur_real; SDiscover_HostList *hosts; ts_mutex_lock(pClient->mtObjectLock); cur_fake = pClient->fakehosts; cur_real = pClient->hosts; while (cur_fake) { cur_fake->marked = 0; cur_fake = cur_fake->next; } while (cur_real) { cur_real->marked = 0; cur_real = cur_real->next; } Discover_GetHosts(disc, &hosts); while (hosts) { if (!ClientHasHost_AndMark(pClient, hosts->sharename)) { char *hostname_buf = safe_sprintf( "%hhu.%hhu.%hhu.%hhu:%hu", hosts->ip[0], hosts->ip[1], hosts->ip[2], hosts->ip[3], hosts->port); if (!DAAP_Client_AddHost(pClient, hostname_buf, hosts->sharename, hosts->sharename_friendly)) { /* if we failed to add the host, add it to the fake * so we don't try and add it again... */ DAAP_ClientHost_Fake *new_fake = malloc(sizeof(DAAP_ClientHost_Fake)); strcpy(new_fake->sharename, hosts->sharename); strcpy(new_fake->sharename_friendly, hosts->sharename_friendly); new_fake->ip[0] = hosts->ip[0]; new_fake->ip[1] = hosts->ip[1]; new_fake->ip[2] = hosts->ip[2]; new_fake->ip[3] = hosts->ip[3]; new_fake->port = hosts->port; new_fake->next = pClient->fakehosts; new_fake->marked = 1; pClient->fakehosts = new_fake; } else { added++; } free(hostname_buf); } hosts = hosts->next; } /* delete any that arn't there... */ prev_fake = NULL; cur_fake = pClient->fakehosts; cur_real = pClient->hosts; while (cur_fake) { DAAP_ClientHost_Fake *next_fake = cur_fake->next; if (!cur_fake->marked) { if (prev_fake) prev_fake->next = cur_fake->next; else pClient->fakehosts = cur_fake->next; free(cur_fake); deleted++; } cur_fake = next_fake; } while (cur_real) { DAAP_SClientHost *next_real = cur_real->next; if (!cur_real->marked) { DAAP_SClientHost *prev = cur_real->prev; if (DAAP_ClientHost_Release(cur_real) != 0) TRACE("app still holds reference to deleted host\n"); if (prev) prev->next = next_real; else pClient->hosts = next_real; if (next_real) next_real->prev = prev; deleted++; } cur_real = next_real; } ts_mutex_unlock(pClient->mtObjectLock); if (added || deleted) { TRACE("%i added, %i deleted\n", added, deleted); pClient->pfnCallbackStatus(pClient, DAAP_STATUS_hostschanged, 0, pClient->pvCallbackStatusContext); } } #endif //WIN32 DAAP_SClient *DAAP_Client_Create(DAAP_fnClientStatus pfnCallback, void *pvCallbackContext) { DAAP_SClient *pClientNew = malloc(sizeof(DAAP_SClient)); memset(pClientNew, 0, sizeof(DAAP_SClient)); dmap_init(); pClientNew->uiRef = 1; pClientNew->pfnCallbackStatus = pfnCallback; pClientNew->pvCallbackStatusContext = pvCallbackContext; #if defined(SYSTEM_POSIX) && !defined(_LINUX) pClientNew->tp = CP_ThreadPool_Create(4); #endif #if !defined(WIN32) && !defined(_LINUX) pClientNew->discover = Discover_Create( #if defined(SYSTEM_POSIX) pClientNew->tp, #endif DiscoverCB, (void*)pClientNew); #endif pClientNew->update_watch = NULL; ts_mutex_create(pClientNew->mtObjectLock); return pClientNew; } int DAAP_Client_SetDebug(DAAP_SClient *pCThis, const char *const debug) { return daap_debug_init(debug); } unsigned int DAAP_Client_AddRef(DAAP_SClient *pCThis) { return ++pCThis->uiRef; } unsigned int DAAP_Client_Release(DAAP_SClient *pCThis) { if (--pCThis->uiRef) return pCThis->uiRef; while (pCThis->hosts) { DAAP_SClientHost *cur = pCThis->hosts; pCThis->hosts = cur->next; if (pCThis->hosts) pCThis->hosts->prev = NULL; cur->next = NULL; DAAP_ClientHost_Release(cur); } if (pCThis->update_watch) { HTTP_Client_WatchQueue_Destroy(pCThis->update_watch); } #if !defined(WIN32) && !defined(_LINUX) Discover_Release(pCThis->discover); #endif #if defined(SYSTEM_POSIX) && !defined(_LINUX) CP_ThreadPool_Release(pCThis->tp); #endif free(pCThis); dmap_deinit(); return 0; } unsigned int DAAP_Client_EnumerateHosts(DAAP_SClient *pCThis, DAAP_fnClientEnumerateHosts pfnCallback, void *context) { int count = 0; DAAP_SClientHost *cur = pCThis->hosts; ts_mutex_lock(pCThis->mtObjectLock); while(cur) { pfnCallback(pCThis, cur, context); cur = cur->next; count++; } ts_mutex_unlock(pCThis->mtObjectLock); return count; } /* hack */ DAAP_SClientHost *DAAP_Client_AddHost(DAAP_SClient *pCThis, char *host, char *sharename, char *sharename_friendly) { DAAP_SClientHost *pClientHost = DAAP_ClientHost_Create(pCThis, host, sharename_friendly); if (!pClientHost) return NULL; if (sharename) strcpy(pClientHost->sharename, sharename); if (pCThis->hosts) pCThis->hosts->prev = pClientHost; pClientHost->next = pCThis->hosts; pCThis->hosts = pClientHost; pClientHost->marked = 1; return pClientHost; } /* ClientHost */ /* this macro is used on returned http results. It will return a failure * if the http result is NULL (returns 1). * If the http status code is not 200 (OK) it will return the status code. * It will also free the http result if it returns. */ #define HTTP_RETURN_IF_FAILED(h) \ if (h == NULL) return 1; \ if (h->httpStatusCode != 200) { \ int ret = h->httpStatusCode; \ HTTP_Client_FreeResult(h); \ return ret; \ } /* private initial stuff */ static int Priv_DAAP_ClientHost_InitialTransaction(DAAP_SClientHost *pCHThis) { HTTP_GetResult *httpRes; protoParseResult_serverinfo serverinfo; protoParseResult_login logininfo; protoParseResult_update updateinfo; char hash[33] = {0}; char updateUrl[] = "/update?session-id=%i&revision-number=1"; char *buf; /* get server-info */ httpRes = HTTP_Client_Get(pCHThis->connection, "/server-info", NULL, NULL, 0); HTTP_RETURN_IF_FAILED(httpRes); serverinfo.h.expecting = QUERY_SERVERINFORESPONSE; dmap_parseProtocolData(httpRes->contentlen, httpRes->data, (protoParseResult *)&serverinfo); HTTP_Client_FreeResult(httpRes); pCHThis->version_major = serverinfo.daap_version.v1; pCHThis->version_minor = serverinfo.daap_version.v2; /* validate server info */ if (serverinfo.dmap_version.v1 != 2 && serverinfo.dmap_version.v2 != 0) { FIXME("unknown version\n"); return 1; } /* hack */ free(serverinfo.hostname); /* get content codes */ httpRes = HTTP_Client_Get(pCHThis->connection, "/content-codes", NULL, NULL, 0); HTTP_RETURN_IF_FAILED(httpRes); #if 0 { FILE *f = fopen("/tmp/content-codes", "w"); int i; for (i = 0; i < httpRes->contentlen; i++) { fputc(((unsigned char*)httpRes->data)[i], f); } fclose(f); } #endif /* send content codes to the dmap parser. don't require any result */ dmap_parseProtocolData(httpRes->contentlen, httpRes->data, NULL); HTTP_Client_FreeResult(httpRes); /* send login */ httpRes = HTTP_Client_Get(pCHThis->connection, "/login", NULL, NULL, 0); HTTP_RETURN_IF_FAILED(httpRes); logininfo.h.expecting = QUERY_LOGINRESPONSE; dmap_parseProtocolData(httpRes->contentlen, httpRes->data, (protoParseResult*)&logininfo); HTTP_Client_FreeResult(httpRes); pCHThis->sessionid = logininfo.sessionid; /* get the current revision id */ buf = safe_sprintf(updateUrl, pCHThis->sessionid); GenerateHash(pCHThis->version_major, buf, 2, hash, 0); httpRes = HTTP_Client_Get(pCHThis->connection, buf, hash, NULL, 0); free(buf); HTTP_RETURN_IF_FAILED(httpRes); updateinfo.h.expecting = QUERY_UPDATERESPONSE; dmap_parseProtocolData(httpRes->contentlen, httpRes->data, (protoParseResult*)&updateinfo); HTTP_Client_FreeResult(httpRes); pCHThis->revision_number = updateinfo.serverrevision; return 0; } static int Priv_DAAP_ClientHost_GetDatabases(DAAP_SClientHost *pCHThis) { char hash[33] = {0}; char databasesUrl[] = "/databases?session-id=%i&revision-number=%i"; char *buf; protoParseResult_genericPreListing databases; HTTP_GetResult *httpRes; int i, j; char *strpos; int sizereq; buf = safe_sprintf(databasesUrl, pCHThis->sessionid, pCHThis->revision_number); GenerateHash(pCHThis->version_major, buf, 2, hash, 0); httpRes = HTTP_Client_Get(pCHThis->connection, buf, hash, NULL, 0); free(buf); HTTP_RETURN_IF_FAILED(httpRes); databases.h.expecting = QUERY_GENERICLISTING; dmap_parseProtocolData(httpRes->contentlen, httpRes->data, (protoParseResult*)&databases); HTTP_Client_FreeResult(httpRes); if (databases.totalcount != databases.returnedcount) FIXME("didn't return all db's, need to handle split\n"); j = 0; sizereq = sizeof(DAAP_ClientHost_Database) * databases.returnedcount; for (i = 0; i < databases.returnedcount; i++) { dmapGenericContainer *item = &(databases.listitems[i]); DMAP_INT32 itemid; DMAP_STRING itemname; if (dmapGeneric_LookupContainerItem_INT32(item, dmap_l("itemid"), &itemid) != DMAP_DATATYPE_INT32) continue; if (dmapGeneric_LookupContainerItem_STRING(item, dmap_l("itemname"), &itemname) != DMAP_DATATYPE_STRING) continue; sizereq += strlen(itemname) + 1; j++; } if (pCHThis->databases) free(pCHThis->databases); pCHThis->databases_size = sizereq; pCHThis->databases = malloc(sizereq); if (pCHThis->dbitems) { int i; for (i = 0; i < pCHThis->nDatabases; i++) { free(&(pCHThis->dbitems->items[i])); } free(pCHThis->dbitems); } if (pCHThis->dbplaylists) free(pCHThis->dbplaylists); pCHThis->dbitems = malloc(sizeof(DatabaseItemContainer) * j); memset(pCHThis->dbitems, 0, sizeof(DatabaseItemContainer) * j); pCHThis->dbplaylists = malloc(sizeof(DatabasePlaylistContainer) * j); memset(pCHThis->dbplaylists, 0, sizeof(DatabasePlaylistContainer) * j); pCHThis->nDatabases = j; strpos = (char*)(((char*)pCHThis->databases) + (sizeof(DAAP_ClientHost_Database) * databases.returnedcount)); j = 0; for (i = 0; i < databases.returnedcount; i++) { dmapGenericContainer *item = &(databases.listitems[i]); DAAP_ClientHost_Database *db = &(pCHThis->databases[j]); DMAP_INT32 itemid; DMAP_STRING itemname; if (dmapGeneric_LookupContainerItem_INT32(item, dmap_l("itemid"), &itemid) != DMAP_DATATYPE_INT32) continue; if (dmapGeneric_LookupContainerItem_STRING(item, dmap_l("itemname"), &itemname) != DMAP_DATATYPE_STRING) continue; db->id = itemid; strcpy(strpos, itemname); db->name = strpos; strpos += strlen(itemname) + 1; pCHThis->dbitems[j].id = itemid; pCHThis->dbplaylists[j].id = itemid; Priv_DAAP_ClientHost_GetDatabaseItems(pCHThis, itemid); Priv_DAAP_ClientHost_GetDatabasePlaylists(pCHThis, itemid); j++; } freeGenericPreListing(&databases); return 0; } static int Priv_DAAP_ClientHost_GetDatabaseItems(DAAP_SClientHost *pCHThis, int databaseid) { char hash[33] = {0}; char itemsUrl[] = "/databases/%i/items?session-id=%i&revision-number=%i&meta=" "dmap.itemid,dmap.itemname,daap.songalbum,daap.songartist," "daap.songbeatsperminute,daap.songbitrate,daap.songdisccount," "daap.songdiscnumber,daap.songgenre,daap.songsamplerate," "daap.songsize,daap.songtime,daap.songtrackcount," "daap.songtracknumber,daap.songuserrating," "daap.songyear,daap.songformat"; char *buf; protoParseResult_genericPreListing items; DatabaseItemContainer *dbcontainer = NULL; HTTP_GetResult *httpRes; int i, j; char *strpos; int sizereq; for (i = 0; i < pCHThis->nDatabases; i++) { DatabaseItemContainer *container = &(pCHThis->dbitems[i]); if (container->id == databaseid) { dbcontainer = container; break; } } if (!dbcontainer) { ERR("container not found, returning\n"); freeGenericPreListing(&items); return 0; } buf = safe_sprintf(itemsUrl, databaseid, pCHThis->sessionid, pCHThis->revision_number); GenerateHash(pCHThis->version_major, buf, 2, hash, 0); httpRes = HTTP_Client_Get(pCHThis->connection, buf, hash, NULL, 0); free(buf); HTTP_RETURN_IF_FAILED(httpRes); items.h.expecting = QUERY_GENERICLISTING; items.returnedcount = 0; items.listitems = 0; dmap_parseProtocolData(httpRes->contentlen, httpRes->data, (protoParseResult*)&items); HTTP_Client_FreeResult(httpRes); if (items.totalcount != items.returnedcount) FIXME("didn't return all db's, need to handle split (%i vs %i)\n", items.totalcount, items.returnedcount); TRACE("returnedcount: %i\n", items.returnedcount); sizereq = sizeof(DAAP_ClientHost_DatabaseItem) * items.returnedcount; for (i = 0; i < items.returnedcount; i++) { dmapGenericContainer *item = &(items.listitems[i]); DMAP_INT32 buf32; /*DMAP_INT16 buf16; DMAP_INT8 buf8;*/ DMAP_STRING buf; if (dmapGeneric_LookupContainerItem_INT32(item, dmap_l("itemid"), &buf32) != DMAP_DATATYPE_INT32) continue; if (dmapGeneric_LookupContainerItem_STRING(item, dmap_l("itemname"), &buf) != DMAP_DATATYPE_STRING) continue; sizereq += strlen(buf) + 1; if (dmapGeneric_LookupContainerItem_STRING(item, daap_l("songalbum"), &buf) == DMAP_DATATYPE_STRING) sizereq += strlen(buf) + 1; if (dmapGeneric_LookupContainerItem_STRING(item, daap_l("songartist"), &buf) == DMAP_DATATYPE_STRING) sizereq += strlen(buf) + 1; /* * OPTIONAL - had to comment this out because mt-daapd doesn't provide all of these * all of the time. * if (dmapGeneric_LookupContainerItem_INT16(item, daap_l("songbeatsperminute"), &buf16) != DMAP_DATATYPE_INT16) continue; if (dmapGeneric_LookupContainerItem_INT16(item, daap_l("songbitrate"), &buf16) != DMAP_DATATYPE_INT16) continue; if (dmapGeneric_LookupContainerItem_INT16(item, daap_l("songdisccount"), &buf16) != DMAP_DATATYPE_INT16) continue; if (dmapGeneric_LookupContainerItem_INT16(item, daap_l("songdiscnumber"), &buf16) != DMAP_DATATYPE_INT16) continue; */ if (dmapGeneric_LookupContainerItem_STRING(item, daap_l("songgenre"), &buf) == DMAP_DATATYPE_STRING) sizereq += strlen(buf) + 1; if (dmapGeneric_LookupContainerItem_INT32(item, daap_l("songsamplerate"), &buf32) != DMAP_DATATYPE_INT32) continue; if (dmapGeneric_LookupContainerItem_INT32(item, daap_l("songsize"), &buf32) != DMAP_DATATYPE_INT32) continue; if (dmapGeneric_LookupContainerItem_INT32(item, daap_l("songtime"), &buf32) != DMAP_DATATYPE_INT32) continue; /* optional, see above re mt-daapd if (dmapGeneric_LookupContainerItem_INT16(item, daap_l("songtrackcount"), &buf16) != DMAP_DATATYPE_INT16) continue; if (dmapGeneric_LookupContainerItem_INT16(item, daap_l("songtracknumber"), &buf16) != DMAP_DATATYPE_INT16) continue; if (dmapGeneric_LookupContainerItem_INT8(item, daap_l("songuserrating"), &buf8) != DMAP_DATATYPE_INT8) continue; if (dmapGeneric_LookupContainerItem_INT16(item, daap_l("songyear"), &buf16) != DMAP_DATATYPE_INT16) continue; */ if (dmapGeneric_LookupContainerItem_STRING(item, daap_l("songformat"), &buf) != DMAP_DATATYPE_STRING) continue; sizereq += strlen(buf) + 1; } if (dbcontainer->items) free(dbcontainer->items); dbcontainer->items_size = sizereq; dbcontainer->items = malloc(sizereq); strpos = (char*)(((char*)dbcontainer->items) + (sizeof(DAAP_ClientHost_DatabaseItem) * items.returnedcount)); j = 0; for (i = 0; i < items.returnedcount; i++) { dmapGenericContainer *item = &(items.listitems[i]); DAAP_ClientHost_DatabaseItem *dbitem = &(dbcontainer->items[j]); DMAP_INT32 buf32; DMAP_INT16 buf16; DMAP_INT8 buf8; DMAP_STRING itemname, songalbum, songartist; DMAP_STRING songgenre, songformat; if (dmapGeneric_LookupContainerItem_INT32(item, dmap_l("itemid"), &buf32) != DMAP_DATATYPE_INT32) continue; dbitem->id = buf32; if (dmapGeneric_LookupContainerItem_STRING(item, daap_l("songformat"), &songformat) != DMAP_DATATYPE_STRING) continue; if (dmapGeneric_LookupContainerItem_STRING(item, dmap_l("itemname"), &itemname) != DMAP_DATATYPE_STRING) continue; if (dmapGeneric_LookupContainerItem_STRING(item, daap_l("songalbum"), &songalbum) != DMAP_DATATYPE_STRING) songalbum = NULL; if (dmapGeneric_LookupContainerItem_STRING(item, daap_l("songartist"), &songartist) != DMAP_DATATYPE_STRING) songartist = NULL; if (dmapGeneric_LookupContainerItem_INT16(item, daap_l("songbeatsperminute"), &buf16) == DMAP_DATATYPE_INT16) dbitem->songbeatsperminute = buf16; else dbitem->songbeatsperminute = 0; if (dmapGeneric_LookupContainerItem_INT16(item, daap_l("songbitrate"), &buf16) == DMAP_DATATYPE_INT16) dbitem->songbitrate = buf16; else dbitem->songbitrate = 0; if (dmapGeneric_LookupContainerItem_INT16(item, daap_l("songdisccount"), &buf16) == DMAP_DATATYPE_INT16) dbitem->songdisccount = buf16; else dbitem->songdisccount = 0; if (dmapGeneric_LookupContainerItem_INT16(item, daap_l("songdiscnumber"), &buf16) == DMAP_DATATYPE_INT16) dbitem->songdiscnumber = buf16; else dbitem->songdiscnumber = buf16; if (dmapGeneric_LookupContainerItem_STRING(item, daap_l("songgenre"), &songgenre) != DMAP_DATATYPE_STRING) songgenre = NULL; if (dmapGeneric_LookupContainerItem_INT32(item, daap_l("songsamplerate"), &buf32) != DMAP_DATATYPE_INT32) continue; dbitem->songsamplerate = buf32; if (dmapGeneric_LookupContainerItem_INT32(item, daap_l("songsize"), &buf32) != DMAP_DATATYPE_INT32) continue; dbitem->songsize = buf32; if (dmapGeneric_LookupContainerItem_INT32(item, daap_l("songtime"), &buf32) != DMAP_DATATYPE_INT32) continue; dbitem->songtime = buf32; if (dmapGeneric_LookupContainerItem_INT16(item, daap_l("songtrackcount"), &buf16) == DMAP_DATATYPE_INT16) dbitem->songtrackcount = buf16; else dbitem->songtrackcount = 0; if (dmapGeneric_LookupContainerItem_INT16(item, daap_l("songtracknumber"), &buf16) == DMAP_DATATYPE_INT16) dbitem->songtracknumber = buf16; else dbitem->songtracknumber = 0; if (dmapGeneric_LookupContainerItem_INT8(item, daap_l("songuserrating"), &buf8) == DMAP_DATATYPE_INT8) dbitem->songuserrating = buf8; else dbitem->songuserrating = 0; if (dmapGeneric_LookupContainerItem_INT16(item, daap_l("songyear"), &buf16) == DMAP_DATATYPE_INT16) dbitem->songyear = buf16; else dbitem->songyear = 0; strcpy(strpos, itemname); dbitem->itemname = strpos; strpos += strlen(strpos)+1; if (songalbum) { strcpy(strpos, songalbum); dbitem->songalbum = strpos; strpos += strlen(strpos)+1; } else dbitem->songalbum = NULL; if (songartist) { strcpy(strpos, songartist); dbitem->songartist = strpos; strpos += strlen(strpos)+1; } else dbitem->songartist = NULL; if (songgenre) { strcpy(strpos, songgenre); dbitem->songgenre = strpos; strpos += strlen(strpos)+1; } else dbitem->songgenre = NULL; strcpy(strpos, songformat); dbitem->songformat = strpos; strpos += strlen(strpos)+1; j++; } dbcontainer->nItems = j; TRACE("items: %i\n", j); freeGenericPreListing(&items); return 0; } static int Priv_DAAP_ClientHost_GetDatabasePlaylistItems(DAAP_SClientHost *pCHThis, int databaseid, int playlistid) { char hash[33] = {0}; char playlistUrl[] = "/databases/%i/containers/%i/items?session-id=%i&revision-number=%i"; char *buf; protoParseResult_genericPreListing playlist; DatabasePlaylistContainer *dbcontainer = NULL; DAAP_ClientHost_DatabasePlaylist *dbplaylist; HTTP_GetResult *httpRes; int i; //char *strpos; int sizereq; for (i = 0; i < pCHThis->nDatabases; i++) { DatabasePlaylistContainer *container = &(pCHThis->dbplaylists[i]); if (container->id == databaseid) { dbcontainer = container; break; } } if (!dbcontainer) { ERR("container not found, returning\n"); freeGenericPreListing(&playlist); return 1; } dbplaylist = NULL; for (i = 0; i < dbcontainer->nPlaylists; i++) { if (dbcontainer->playlists[i].id == playlistid) dbplaylist = &(dbcontainer->playlists[i]); } if (!dbplaylist) { ERR("playlist (%i) not found, returning\n", playlistid); freeGenericPreListing(&playlist); return 1; } buf = safe_sprintf(playlistUrl, databaseid, playlistid, pCHThis->sessionid, pCHThis->revision_number); GenerateHash(pCHThis->version_major, buf, 2, hash, 0); httpRes = HTTP_Client_Get(pCHThis->connection, buf, hash, NULL, 0); free(buf); HTTP_RETURN_IF_FAILED(httpRes); playlist.h.expecting = QUERY_GENERICLISTING; dmap_parseProtocolData(httpRes->contentlen, httpRes->data, (protoParseResult*)&playlist); HTTP_Client_FreeResult(httpRes); if (playlist.totalcount != playlist.returnedcount) FIXME("didn't return all playlists's, need to handle split\n"); TRACE("returnedcount: %i\n", playlist.returnedcount); sizereq = sizeof(DAAP_ClientHost_DatabasePlaylistItem) * playlist.returnedcount; for (i = 0; i < playlist.returnedcount; i++) { dmapGenericContainer *item = &(playlist.listitems[i]); DMAP_INT32 buf32; //DMAP_STRING buf; if (dmapGeneric_LookupContainerItem_INT32(item, dmap_l("itemid"), &buf32) != DMAP_DATATYPE_INT32) continue; } dbplaylist->items = malloc(sizereq); //items = malloc(sizereq); for (i = 0; i < playlist.returnedcount; i++) { dmapGenericContainer *item = &(playlist.listitems[i]); DAAP_ClientHost_DatabasePlaylistItem *playlistitem = &(dbplaylist->items[i]); DMAP_INT32 buf32; if (dmapGeneric_LookupContainerItem_INT32(item, dmap_l("itemid"), &buf32) != DMAP_DATATYPE_INT32) continue; playlistitem->songid = buf32; } freeGenericPreListing(&playlist); return 0; } static int Priv_DAAP_ClientHost_GetDatabasePlaylists(DAAP_SClientHost *pCHThis, int databaseid) { char hash[33] = {0}; char playlistUrl[] = "/databases/%i/containers?session-id=%i&revision-number=%i"; char *buf; protoParseResult_genericPreListing playlists; DatabasePlaylistContainer *dbcontainer = NULL; HTTP_GetResult *httpRes; int i, j; char *strpos; int sizereq; for (i = 0; i < pCHThis->nDatabases; i++) { DatabasePlaylistContainer *container = &(pCHThis->dbplaylists[i]); if (container->id == databaseid) { dbcontainer = container; break; } } if (!dbcontainer) { ERR("container not found, returning\n"); freeGenericPreListing(&playlists); } buf = safe_sprintf(playlistUrl, databaseid, pCHThis->sessionid, pCHThis->revision_number); GenerateHash(pCHThis->version_major, buf, 2, hash, 0); httpRes = HTTP_Client_Get(pCHThis->connection, buf, hash, NULL, 0); free(buf); HTTP_RETURN_IF_FAILED(httpRes); playlists.h.expecting = QUERY_GENERICLISTING; dmap_parseProtocolData(httpRes->contentlen, httpRes->data, (protoParseResult*)&playlists); HTTP_Client_FreeResult(httpRes); if (playlists.totalcount != playlists.returnedcount) FIXME("didn't return all playlists's, need to handle split\n"); TRACE("returnedcount: %i\n", playlists.returnedcount); sizereq = sizeof(DAAP_ClientHost_DatabasePlaylist) * playlists.returnedcount; for (i = 0; i < playlists.returnedcount; i++) { dmapGenericContainer *playlist = &(playlists.listitems[i]); DMAP_INT32 buf32; DMAP_STRING buf; if (dmapGeneric_LookupContainerItem_INT32(playlist, dmap_l("itemcount"), &buf32) != DMAP_DATATYPE_INT32) continue; if (dmapGeneric_LookupContainerItem_INT32(playlist, dmap_l("itemid"), &buf32) != DMAP_DATATYPE_INT32) continue; if (dmapGeneric_LookupContainerItem_STRING(playlist, dmap_l("itemname"), &buf) != DMAP_DATATYPE_STRING) continue; sizereq += strlen(buf) + 1; } if (dbcontainer->playlists) free(dbcontainer->playlists); dbcontainer->playlists_size = sizereq; dbcontainer->playlists = malloc(sizereq); strpos = (char*)(((char*)dbcontainer->playlists) + (sizeof(DAAP_ClientHost_DatabasePlaylist) * playlists.returnedcount)); j = 0; for (i = 0; i < playlists.returnedcount; i++) { dmapGenericContainer *playlist = &(playlists.listitems[i]); DAAP_ClientHost_DatabasePlaylist *dbplaylist = &(dbcontainer->playlists[j]); DMAP_INT32 buf32; DMAP_STRING itemname; if (dmapGeneric_LookupContainerItem_INT32(playlist, dmap_l("itemcount"), &buf32) != DMAP_DATATYPE_INT32) continue; dbplaylist->count = buf32; if (dmapGeneric_LookupContainerItem_INT32(playlist, dmap_l("itemid"), &buf32) != DMAP_DATATYPE_INT32) continue; dbplaylist->id = buf32; if (dmapGeneric_LookupContainerItem_STRING(playlist, dmap_l("itemname"), &itemname) != DMAP_DATATYPE_STRING) continue; strcpy(strpos, itemname); dbplaylist->itemname = strpos; strpos += strlen(strpos)+1; TRACE("(%i) got a playlist '%s', item count: %i\n", dbplaylist->id, dbplaylist->itemname, dbplaylist->count); j++; dbcontainer->nPlaylists = j; /* need to update on the go for the following to work: */ Priv_DAAP_ClientHost_GetDatabasePlaylistItems(pCHThis, databaseid, dbplaylist->id); } TRACE("playlists: %i\n", dbcontainer->nPlaylists); freeGenericPreListing(&playlists); return 0; } /* private create */ static DAAP_SClientHost *DAAP_ClientHost_Create(DAAP_SClient *parent, char *host, char *sharename) { DAAP_SClientHost *pClientHostNew = malloc(sizeof(DAAP_SClientHost)); memset(pClientHostNew, 0, sizeof(DAAP_SClientHost)); /* HACK */ pClientHostNew->interrupt = 66; pClientHostNew->uiRef = 1; /* we don't hold a reference to this.. as it will always exist * while this object exists. */ pClientHostNew->parent = parent; strncpy(pClientHostNew->sharename_friendly, sharename, sizeof(pClientHostNew->sharename_friendly) - 1); pClientHostNew->host = malloc(strlen(host) + 1); strcpy(pClientHostNew->host, host); pClientHostNew->password = NULL; pClientHostNew->prev = NULL; pClientHostNew->next = NULL; return pClientHostNew; } /* public */ unsigned int DAAP_ClientHost_AddRef(DAAP_SClientHost *pCHThis) { return ++pCHThis->uiRef; } unsigned int DAAP_ClientHost_Release(DAAP_SClientHost *pCHThis) { if (--pCHThis->uiRef) return pCHThis->uiRef; ERR("freeing (ref %i)\n", pCHThis->uiRef); if (pCHThis->connection) HTTP_Client_Close(pCHThis->connection); if (pCHThis->databases) free(pCHThis->databases); if (pCHThis->dbitems) { int i; for (i = 0; i < pCHThis->nDatabases; i++) { free(&(pCHThis->dbitems->items[i])); } free(pCHThis->dbitems); } if (pCHThis->password) free(pCHThis->password); free(pCHThis->host); free(pCHThis); return 0; } unsigned int DAAP_ClientHost_GetSharename(DAAP_SClientHost *pCHThis, char *buf, int bufsize) { int reqsize; reqsize = strlen(pCHThis->sharename_friendly) + 1; if (bufsize < reqsize) return reqsize; strcpy(buf, pCHThis->sharename_friendly); return 0; } static char *encode_base64(char *string) { static const char base64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; int out_index = 0; int outlen = ((strlen(string) * 4) / 3) + 4; char *out = malloc(outlen+1); memset(out, 0, outlen); while (string[0]) { int index; /* first 6 bits from string[0] */ index = (string[0] & 0xFC) >> 2; out[out_index++] = base64chars[index]; /* last 2 bits from string[0] and 6 bits from string[1] */ index = (string[0] & 0x3) << 4; index |= (string[1] & 0xF0) >> 4; out[out_index++] = base64chars[index]; /* but if string[1] was 0, it's the final char. fill the rest with pad */ if (!string[1]) { out[out_index++] = '='; out[out_index++] = '='; break; } /* last 4 bits from string[1] and 2 bits from string[2] */ index = (string[1] & 0x0F) << 2; index |= (string[2] & 0xC0) >> 6; out[out_index++] = base64chars[index]; /* but if string[2] was 0, it was the final char. */ if (!string[2]) { out[out_index++] = '='; break; } /* finally, last 6 bits of string[2] */ index = (string[2] & 0x3F); out[out_index++] = base64chars[index]; string += 3; } out[out_index++] = 0; return out; } void DAAP_ClientHost_SetPassword(DAAP_SClientHost *pCHThis, char *password) { char *tmppass; if (pCHThis->password) free(pCHThis->password); tmppass = malloc(strlen(password) + 2); tmppass[0] = ':'; strcpy(tmppass+1, password); pCHThis->password = encode_base64(tmppass); free(tmppass); } unsigned int DAAP_ClientHost_Connect(DAAP_SClientHost *pCHThis) { int retcode = 1; if (pCHThis->connection) { ERR("already connected? %i\n", pCHThis->interrupt); goto err; } TRACE("connecting to %s\n", pCHThis->host); pCHThis->connection = HTTP_Client_Open(pCHThis->host, pCHThis->password); if (!pCHThis->connection) { ERR("couldn't open connection to host\n"); goto err; } TRACE("connected\n"); /* do initial login etc */ if ((retcode = Priv_DAAP_ClientHost_InitialTransaction(pCHThis))) { ERR("couldn't finish initial transaction with server. [%i]\n", retcode); goto err; } if ((retcode = Priv_DAAP_ClientHost_GetDatabases(pCHThis))) { ERR("couldn't get database list [%i]\n", retcode); goto err; } return 0; err: if (pCHThis->connection) { HTTP_Client_Close(pCHThis->connection); pCHThis->connection = NULL; } return -1 * retcode; } unsigned int DAAP_ClientHost_Disconnect(DAAP_SClientHost *pCHThis) { if (!pCHThis->connection) return -1; HTTP_Client_Close(pCHThis->connection); pCHThis->connection = NULL; return 0; } unsigned int DAAP_ClientHost_GetPlaylists(DAAP_SClientHost *pCHThis, int databaseid, DAAP_ClientHost_DatabasePlaylist *buffer, int *n, int bufsize) { if (!pCHThis->connection) return -1; /* FIXME: ignoring databaseid */ if (bufsize < pCHThis->dbplaylists->playlists_size) return pCHThis->dbplaylists->playlists_size; memcpy(buffer, pCHThis->dbplaylists->playlists, pCHThis->dbplaylists->playlists_size); *n = pCHThis->dbplaylists->nPlaylists; return 0; } unsigned int DAAP_ClientHost_GetPlaylistItems(DAAP_SClientHost *pCHThis, int databaseid, int playlistid, DAAP_ClientHost_DatabasePlaylistItem *buffer, int *n, int bufsize) { int i; /* FIXME: compile time assert. * basically we need to store the playlist items in a private * container structure so we know the size of the list. * for the moment we are just presuming they are a list of ints. */ char assert[(sizeof(DAAP_ClientHost_DatabasePlaylistItem) == sizeof(int)) ? 1 : -1]; if (!pCHThis->connection) return -1; /* FIXME: ignoring databaseid */ for (i = 0; i < pCHThis->dbplaylists->nPlaylists; i++) { if (pCHThis->dbplaylists->playlists[i].id == playlistid) { if (bufsize < (pCHThis->dbplaylists->playlists[i].count * (int)sizeof(DAAP_ClientHost_DatabasePlaylistItem))) return (pCHThis->dbplaylists->playlists[i].count * sizeof(DAAP_ClientHost_DatabasePlaylistItem)); if (pCHThis->dbplaylists->playlists[i].count == 0) return 0; memcpy(buffer, pCHThis->dbplaylists->playlists[i].items, (pCHThis->dbplaylists->playlists[i].count * sizeof(DAAP_ClientHost_DatabasePlaylistItem))); *n = pCHThis->dbplaylists->playlists[i].count; return 0; } } return -1; } unsigned int DAAP_Client_GetDatabases(DAAP_SClientHost *pCHThis) { if (!Priv_DAAP_ClientHost_GetDatabases(pCHThis)) { ERR("couldn't get database list\n"); return -1; } return 0; } unsigned int DAAP_ClientHost_GetDatabases(DAAP_SClientHost *pCHThis, DAAP_ClientHost_Database *buffer, int *n, int bufsize) { if (!pCHThis->connection) return -1; if (bufsize < pCHThis->databases_size) return pCHThis->databases_size; memcpy(buffer, pCHThis->databases, pCHThis->databases_size); *n = pCHThis->nDatabases; return 0; } int DAAP_ClientHost_GetDatabaseItems(DAAP_SClientHost *pCHThis, int databaseid, DAAP_ClientHost_DatabaseItem *buffer, int *n, int bufsize) { int i; if (!pCHThis->connection) return -1; for (i = 0; i < pCHThis->nDatabases; i++) { if (pCHThis->dbitems[i].id == databaseid) { if (bufsize < pCHThis->dbitems[i].items_size) return pCHThis->dbitems[i].items_size; memcpy(buffer, pCHThis->dbitems[i].items, pCHThis->dbitems[i].items_size); *n = pCHThis->dbitems[i].nItems; return 0; } } return -1; } int DAAP_ClientHost_GetAudioFile(DAAP_SClientHost *pCHThis, int databaseid, int songid, const char *songformat, DAAP_ClientHost_Song *song) { char hash[33] = {0}; char *hashUrl; char songUrl_42[] = "/databases/%i/items/%i.%s?session-id=%i&revision-id=%i"; char songUrl_45[] = "daap://%s/databases/%i/items/%i.%s?session-id=%i"; char *buf; char requestid_45[] = "Client-DAAP-Request-ID: %u\r\n"; char *buf_45extra = NULL; int requestid = 0; HTTP_Connection *http_connection; HTTP_GetResult *httpRes; if (strlen(songformat) > 4) return -1; if (pCHThis->version_major != 3) { buf = safe_sprintf(songUrl_42, databaseid, songid, songformat, pCHThis->sessionid, pCHThis->revision_number); } else { buf = safe_sprintf(songUrl_45, pCHThis->host, databaseid, songid, songformat, pCHThis->sessionid); requestid = ++pCHThis->request_id; buf_45extra = safe_sprintf(requestid_45, requestid); } /* dodgy hack as the hash for 4.5 needs to not include daap:// */ if (!strstr(buf, "daap://")) hashUrl = buf; else hashUrl = strstr(buf, "/databases"); GenerateHash(pCHThis->version_major, hashUrl, 2, hash, requestid); /* use a seperate connection */ http_connection = HTTP_Client_Open(pCHThis->host, pCHThis->password); /* 1 = Connection: Close*/ TRACE("untested\n"); httpRes = HTTP_Client_Get(http_connection, buf, hash, requestid ? buf_45extra : NULL, 1); free(buf); free(buf_45extra); HTTP_Client_Close(http_connection); /* custom */ if (!httpRes) return -1; if (httpRes->httpStatusCode != 200) { int ret = -1 * httpRes->httpStatusCode; free(httpRes); return ret; } song->size = httpRes->contentlen; song->data = malloc(httpRes->contentlen); memcpy(song->data, httpRes->data, httpRes->contentlen); HTTP_Client_FreeResult(httpRes); return 0; } int DAAP_ClientHost_FreeAudioFile(DAAP_SClientHost *pCHThis, DAAP_ClientHost_Song *song) { free(song->data); return 0; } typedef struct { char *url; char *extra_header; int requestid; #if defined(SYSTEM_POSIX) int fileid; #elif defined(SYSTEM_WIN32) HANDLE fileid; #else FILE *fileid; #endif #if defined(WIN32) || defined(_LINUX) DAAP_fnHttpWrite callback; void *context; #endif int interrupt; } GetFile; static int httpCallback(void *pv_pCHThis, int pos) { DAAP_SClientHost *pCHThis = (DAAP_SClientHost*)pv_pCHThis; if (pCHThis->interrupt) { return 1; } if (pCHThis->parent->pfnCallbackStatus) pCHThis->parent->pfnCallbackStatus(pCHThis->parent, DAAP_STATUS_downloading, pos, pCHThis->parent->pvCallbackStatusContext); return 0; } static void AsyncGetFile(void *pv_pCHThis, void *pv_pGetFile) { GetFile *pGetFile = (GetFile*)pv_pGetFile; DAAP_SClientHost *pCHThis = (DAAP_SClientHost*)pv_pCHThis; HTTP_Connection *http_connection; int ret; char hash[33] = {0}; char *hashUrl; /* dodgy hack as the hash for 4.5 needs to not include daap:// */ if (!strstr(pGetFile->url, "daap://")) hashUrl = pGetFile->url; else hashUrl = strstr(pGetFile->url, "/databases"); pCHThis->interrupt = 0; GenerateHash(pCHThis->version_major, hashUrl, 2, hash, pGetFile->requestid); if (pCHThis->parent->pfnCallbackStatus) pCHThis->parent->pfnCallbackStatus(pCHThis->parent, DAAP_STATUS_negotiating, 0, pCHThis->parent->pvCallbackStatusContext); /* use a seperate connection */ http_connection = HTTP_Client_Open(pCHThis->host, pCHThis->password); if (!http_connection) goto err; if( pGetFile->callback ) { ret = HTTP_Client_Get_Callback(http_connection, pGetFile->url, hash, pGetFile->extra_header, 1 /* 1 = Connection: close */, pGetFile->callback, pGetFile->context); } else { ret = HTTP_Client_Get_ToFile(http_connection, pGetFile->url, hash, pGetFile->extra_header, pGetFile->fileid, /* 1 = Connection: close */ httpCallback, pv_pCHThis, 1); } /* finished prematurely, and not interrupted */ if (!ret && !pCHThis->interrupt) goto err; if( pGetFile->callback ) { /*Send null data to show we are finished*/ pGetFile->callback(NULL, 0, 0, 0, pGetFile->context); } HTTP_Client_Close(http_connection); http_connection = NULL; pCHThis->interrupt = 0; if (pCHThis->parent->pfnCallbackStatus) pCHThis->parent->pfnCallbackStatus(pCHThis->parent, DAAP_STATUS_idle, 0, pCHThis->parent->pvCallbackStatusContext); free(pGetFile->url); if (pGetFile->extra_header) free(pGetFile->extra_header); free(pGetFile); DAAP_ClientHost_Release(pCHThis); return; err: if( pGetFile->callback ) { /*Send null data with error*/ pGetFile->callback(NULL, 0, -1, 0, pGetFile->context); } if (http_connection) HTTP_Client_Close(http_connection); free(pGetFile); DAAP_ClientHost_Release(pCHThis); if (pCHThis->parent->pfnCallbackStatus) pCHThis->parent->pfnCallbackStatus(pCHThis->parent, DAAP_STATUS_error, 0, pCHThis->parent->pvCallbackStatusContext); } #if !defined(SYSTEM_POSIX) typedef struct { void *arg1, *arg2; } tsApiWrap_data; ts_thread_cb(tsApiWrap_AsyncGetFile) { tsApiWrap_data *data = arg; AsyncGetFile(data->arg1, data->arg2); free(data); return ts_thread_defaultret; } #endif int DAAP_ClientHost_AsyncGetAudioFile(DAAP_SClientHost *pCHThis, int databaseid, int songid, const char *songformat, #if defined(SYSTEM_POSIX) int fd) #elif defined(SYSTEM_WIN32) HANDLE fd) #else FILE *fd) #endif { /* FIXME: aac?? */ char songUrl_42[] = "/databases/%i/items/%i.%s?session-id=%i&revision-id=%i"; char songUrl_45[] = "daap://%s/databases/%i/items/%i.%s?session-id=%i"; char requestid_45[] = "Client-DAAP-Request-ID: %u\r\n"; GetFile *pGetFile = malloc(sizeof(GetFile)); pGetFile->fileid = fd; pGetFile->url = NULL; pGetFile->extra_header = NULL; if (pCHThis->version_major != 3) { pGetFile->url = safe_sprintf(songUrl_42, databaseid, songid, songformat, pCHThis->sessionid, pCHThis->revision_number); } else { pGetFile->url = safe_sprintf(songUrl_45, pCHThis->host, databaseid, songid, songformat, pCHThis->sessionid); pGetFile->requestid = ++pCHThis->request_id; pGetFile->extra_header = safe_sprintf(requestid_45, pGetFile->requestid); } DAAP_ClientHost_AddRef(pCHThis); #if defined(SYSTEM_POSIX) CP_ThreadPool_QueueWorkItem(pCHThis->parent->tp, AsyncGetFile, (void*)pCHThis, (void*)pGetFile); #else /*if defined(SYSTEM_WIN32) */ { ts_thread thread; tsApiWrap_data *data = malloc(sizeof(tsApiWrap_data)); data->arg1 = (void*)pCHThis; data->arg2 = (void*)pGetFile; ts_thread_create(thread, tsApiWrap_AsyncGetFile, data); #ifndef WIN32 //don't close this here since we need this handle later ts_thread_close(thread); #endif } #endif return 0; } #ifdef WIN32 int DAAP_ClientHost_AsyncGetAudioFileCallback(DAAP_SClientHost *pCHThis, int databaseid, int songid, const char *songformat, int startbyte, DAAP_fnHttpWrite callback, void* context) { /* FIXME: aac?? */ char songUrl_42[] = "/databases/%i/items/%i.%s?session-id=%i&revision-id=%i"; char songUrl_45[] = "daap://%s/databases/%i/items/%i.%s?session-id=%i"; char requestid_45[] = "Client-DAAP-Request-ID: %u\r\n"; char requestresume[] = "Range: bytes=%d-\r\n"; char *requestid = NULL; char *start = NULL; GetFile *pGetFile = malloc(sizeof(GetFile)); pGetFile->fileid = NULL; pGetFile->url = NULL; pGetFile->extra_header = NULL; pGetFile->callback = callback; pGetFile->context = context; if (pCHThis->version_major != 3) { pGetFile->url = safe_sprintf(songUrl_42, databaseid, songid, songformat, pCHThis->sessionid, pCHThis->revision_number); } else { pGetFile->url = safe_sprintf(songUrl_45, pCHThis->host, databaseid, songid, songformat, pCHThis->sessionid); pGetFile->requestid = ++pCHThis->request_id; requestid = safe_sprintf(requestid_45, pGetFile->requestid); } if( startbyte ) { start = safe_sprintf(requestresume, startbyte); } pGetFile->extra_header = safe_sprintf("%s%s", requestid,start ); if( start ) free(start); if( requestid ) free(requestid); DAAP_ClientHost_AddRef(pCHThis); #if defined(SYSTEM_POSIX) CP_ThreadPool_QueueWorkItem(pCHThis->parent->tp, AsyncGetFile, (void*)pCHThis, (void*)pGetFile); #else /*if defined(SYSTEM_WIN32) */ { ts_thread thread; tsApiWrap_data *data = malloc(sizeof(tsApiWrap_data)); data->arg1 = (void*)pCHThis; data->arg2 = (void*)pGetFile; ts_thread_create(thread, tsApiWrap_AsyncGetFile, data); ts_thread_close(thread); } #endif return 0; } #endif int DAAP_ClientHost_AsyncStop(DAAP_SClientHost *pCHThis) { pCHThis->interrupt = 1; return 0; } /********** update watcher ***********/ static void update_watch_cb(void *pv_pCHThis) { DAAP_SClientHost *pCHThis = (DAAP_SClientHost*)pv_pCHThis; FIXME("got an update from host %p (%s). Expect brokenness!\n", pCHThis, pCHThis->sharename_friendly); /* FIXME: since we don't handle updates just yet, we'll remove * ourselves from the update watch. * This will result in 505s as the iTunes times out waiting for us. */ HTTP_Client_WatchQueue_RemoveUpdateWatch(pCHThis->parent->update_watch, pCHThis->connection); } static void AsyncWaitUpdate(void *pv_pCHThis, void *unused) { DAAP_SClientHost *pCHThis = (DAAP_SClientHost*)pv_pCHThis; char hash[33] = {0}; char updateUrl[] = "/update?session-id=%i&revision-number=%i&delta=%i"; char *buf; TRACE("()\n"); buf = safe_sprintf(updateUrl, pCHThis->sessionid, pCHThis->revision_number, pCHThis->revision_number); GenerateHash(pCHThis->version_major, buf, 2, hash, 0); /* should return pretty quickly */ HTTP_Client_WatchQueue_AddUpdateWatch(pCHThis->parent->update_watch, pCHThis->connection, buf, hash, update_watch_cb, pv_pCHThis); free(buf); } static void update_watch_runloop(void *pv_pUpdateWatch, void *unused) { HTTP_ConnectionWatch *watch = (HTTP_ConnectionWatch*)pv_pUpdateWatch; HTTP_Client_WatchQueue_RunLoop(watch); } #ifndef WIN32 int DAAP_ClientHost_AsyncWaitUpdate(DAAP_SClientHost *pCHThis) { /* lazy create update_watch */ ts_mutex_lock(pCHThis->parent->mtObjectLock); if (!pCHThis->parent->update_watch) { pCHThis->parent->update_watch = HTTP_Client_WatchQueue_New(); if (!pCHThis->parent->update_watch) { ERR("couldn't create update watch\n"); return 1; } /* and now run it in a new thread */ #if defined(SYSTEM_POSIX) CP_ThreadPool_QueueWorkItem(pCHThis->parent->tp, update_watch_runloop, (void*)pCHThis->parent->update_watch, NULL); #else #error implement me #endif } ts_mutex_unlock(pCHThis->parent->mtObjectLock); TRACE("about to call async wait update\n"); #if defined(SYSTEM_POSIX) /* doesn't _really_ need to be done in a different thread, but * the HTTP GET could take a little while, and then AddUpdateWatch will return */ TRACE("calling\n"); CP_ThreadPool_QueueWorkItem(pCHThis->parent->tp, AsyncWaitUpdate, (void*)pCHThis, NULL); #else #error please implement #endif return 0; } #endif int DAAP_ClientHost_AsyncStopUpdate(DAAP_SClientHost *pCHThis) { /* that's naughty, the app called this without installing a watch */ if (!pCHThis->parent->update_watch) return 0; HTTP_Client_WatchQueue_RemoveUpdateWatch(pCHThis->parent->update_watch, pCHThis->connection); return 0; }