PageRenderTime 57ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/xbmc/network/UPnP.cpp

https://github.com/opdenkamp/xbmc
C++ | 2440 lines | 1769 code | 304 blank | 367 comment | 388 complexity | 7f3d63ae39cbec19ef12c51716d720f6 MD5 | raw file
Possible License(s): GPL-2.0, AGPL-1.0

Large files files are truncated, but you can click here to view the full file

  1. /*
  2. * UPnP Support for XBMC
  3. * Copyright (c) 2006 c0diq (Sylvain Rebaud)
  4. * Portions Copyright (c) by the authors of libPlatinum
  5. *
  6. * http://www.plutinosoft.com/blog/category/platinum/
  7. *
  8. * This program is free software; you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation; either version 2 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, write to the Free Software
  20. * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  21. */
  22. #include "threads/SystemClock.h"
  23. #include "UPnP.h"
  24. #include "utils/URIUtils.h"
  25. #include "Application.h"
  26. #include "Network.h"
  27. #include "utils/log.h"
  28. #include "filesystem/MusicDatabaseDirectory.h"
  29. #include "filesystem/VideoDatabaseDirectory.h"
  30. #include "music/MusicDatabase.h"
  31. #include "video/VideoDatabase.h"
  32. #include "filesystem/VideoDatabaseDirectory/DirectoryNode.h"
  33. #include "filesystem/VideoDatabaseDirectory/QueryParams.h"
  34. #include "filesystem/File.h"
  35. #include "NptStrings.h"
  36. #include "Platinum.h"
  37. #include "PltMediaConnect.h"
  38. #include "PltMediaRenderer.h"
  39. #include "PltSyncMediaBrowser.h"
  40. #include "PltDidl.h"
  41. #include "NptNetwork.h"
  42. #include "NptConsole.h"
  43. #include "music/tags/MusicInfoTag.h"
  44. #include "pictures/PictureInfoTag.h"
  45. #include "pictures/GUIWindowSlideShow.h"
  46. #include "filesystem/Directory.h"
  47. #include "URL.h"
  48. #include "settings/GUISettings.h"
  49. #include "GUIUserMessages.h"
  50. #include "settings/Settings.h"
  51. #include "utils/StringUtils.h"
  52. #include "FileItem.h"
  53. #include "guilib/GUIWindowManager.h"
  54. #include "GUIInfoManager.h"
  55. #include "utils/TimeUtils.h"
  56. #include "utils/md5.h"
  57. #include "guilib/Key.h"
  58. using namespace std;
  59. using namespace MUSIC_INFO;
  60. using namespace XFILE;
  61. extern CGUIInfoManager g_infoManager;
  62. NPT_SET_LOCAL_LOGGER("xbmc.upnp")
  63. #define UPNP_DEFAULT_MAX_RETURNED_ITEMS 200
  64. #define UPNP_DEFAULT_MIN_RETURNED_ITEMS 30
  65. /*
  66. # Play speed
  67. # 1 normal
  68. # 0 invalid
  69. DLNA_ORG_PS = 'DLNA.ORG_PS'
  70. DLNA_ORG_PS_VAL = '1'
  71. # Convertion Indicator
  72. # 1 transcoded
  73. # 0 not transcoded
  74. DLNA_ORG_CI = 'DLNA.ORG_CI'
  75. DLNA_ORG_CI_VAL = '0'
  76. # Operations
  77. # 00 not time seek range, not range
  78. # 01 range supported
  79. # 10 time seek range supported
  80. # 11 both supported
  81. DLNA_ORG_OP = 'DLNA.ORG_OP'
  82. DLNA_ORG_OP_VAL = '01'
  83. # Flags
  84. # senderPaced 80000000 31
  85. # lsopTimeBasedSeekSupported 40000000 30
  86. # lsopByteBasedSeekSupported 20000000 29
  87. # playcontainerSupported 10000000 28
  88. # s0IncreasingSupported 08000000 27
  89. # sNIncreasingSupported 04000000 26
  90. # rtspPauseSupported 02000000 25
  91. # streamingTransferModeSupported 01000000 24
  92. # interactiveTransferModeSupported 00800000 23
  93. # backgroundTransferModeSupported 00400000 22
  94. # connectionStallingSupported 00200000 21
  95. # dlnaVersion15Supported 00100000 20
  96. DLNA_ORG_FLAGS = 'DLNA.ORG_FLAGS'
  97. DLNA_ORG_FLAGS_VAL = '01500000000000000000000000000000'
  98. */
  99. /*----------------------------------------------------------------------
  100. | static
  101. +---------------------------------------------------------------------*/
  102. CUPnP* CUPnP::upnp = NULL;
  103. // change to false for XBMC_PC if you want real UPnP functionality
  104. // otherwise keep to true for xbox as it doesn't support multicast
  105. // don't change unless you know what you're doing!
  106. bool CUPnP::broadcast = true;
  107. namespace
  108. {
  109. static const NPT_String JoinString(const NPT_List<NPT_String>& array, const NPT_String& delimiter)
  110. {
  111. NPT_String result;
  112. for(NPT_List<NPT_String>::Iterator it = array.GetFirstItem(); it; it++ )
  113. result += delimiter + (*it);
  114. if(result.IsEmpty())
  115. return "";
  116. else
  117. return result.SubString(delimiter.GetLength());
  118. }
  119. enum EClientQuirks
  120. {
  121. ECLIENTQUIRKS_NONE = 0x0
  122. /* Client requires folder's to be marked as storageFolers as verndor type (360)*/
  123. , ECLIENTQUIRKS_ONLYSTORAGEFOLDER = 0x01
  124. /* Client can't handle subtypes for videoItems (360) */
  125. , ECLIENTQUIRKS_BASICVIDEOCLASS = 0x02
  126. /* Client requires album to be set to [Unknown Series] to show title (WMP) */
  127. , ECLIENTQUIRKS_UNKNOWNSERIES = 0x04
  128. };
  129. static EClientQuirks GetClientQuirks(const PLT_HttpRequestContext* context)
  130. {
  131. if(context == NULL)
  132. return ECLIENTQUIRKS_NONE;
  133. unsigned int quirks = 0;
  134. const NPT_String* user_agent = context->GetRequest().GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_USER_AGENT);
  135. const NPT_String* server = context->GetRequest().GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_SERVER);
  136. if (user_agent) {
  137. if (user_agent->Find("XBox", 0, true) >= 0 ||
  138. user_agent->Find("Xenon", 0, true) >= 0)
  139. quirks |= ECLIENTQUIRKS_ONLYSTORAGEFOLDER | ECLIENTQUIRKS_BASICVIDEOCLASS;
  140. if (user_agent->Find("Windows-Media-Player", 0, true) >= 0)
  141. quirks |= ECLIENTQUIRKS_UNKNOWNSERIES;
  142. }
  143. if (server) {
  144. if (server->Find("Xbox", 0, true) >= 0)
  145. quirks |= ECLIENTQUIRKS_ONLYSTORAGEFOLDER | ECLIENTQUIRKS_BASICVIDEOCLASS;
  146. }
  147. return (EClientQuirks)quirks;
  148. }
  149. }
  150. /*----------------------------------------------------------------------
  151. | NPT_Console::Output
  152. +---------------------------------------------------------------------*/
  153. void
  154. NPT_Console::Output(const char* message)
  155. {
  156. CLog::Log(LOGDEBUG, "%s", message);
  157. }
  158. /*----------------------------------------------------------------------
  159. | CDeviceHostReferenceHolder class
  160. +---------------------------------------------------------------------*/
  161. class CDeviceHostReferenceHolder
  162. {
  163. public:
  164. PLT_DeviceHostReference m_Device;
  165. };
  166. /*----------------------------------------------------------------------
  167. | CCtrlPointReferenceHolder class
  168. +---------------------------------------------------------------------*/
  169. class CCtrlPointReferenceHolder
  170. {
  171. public:
  172. PLT_CtrlPointReference m_CtrlPoint;
  173. };
  174. /*----------------------------------------------------------------------
  175. | CUPnPCleaner class
  176. +---------------------------------------------------------------------*/
  177. class CUPnPCleaner : public NPT_Thread
  178. {
  179. public:
  180. CUPnPCleaner(CUPnP* upnp) : NPT_Thread(true), m_UPnP(upnp) {}
  181. void Run() {
  182. delete m_UPnP;
  183. }
  184. CUPnP* m_UPnP;
  185. };
  186. /*----------------------------------------------------------------------
  187. | CUPnP::CUPnP
  188. +---------------------------------------------------------------------*/
  189. class CUPnPServer : public PLT_MediaConnect
  190. {
  191. public:
  192. CUPnPServer(const char* friendly_name, const char* uuid = NULL, int port = 0) :
  193. PLT_MediaConnect("", friendly_name, false, uuid, port) {
  194. // hack: override path to make sure it's empty
  195. // urls will contain full paths to local files
  196. m_Path = "";
  197. }
  198. // PLT_MediaServer methods
  199. virtual NPT_Result OnBrowseMetadata(PLT_ActionReference& action,
  200. const char* object_id,
  201. const char* filter,
  202. NPT_UInt32 starting_index,
  203. NPT_UInt32 requested_count,
  204. const NPT_List<NPT_String>& sort_criteria,
  205. const PLT_HttpRequestContext& context);
  206. virtual NPT_Result OnBrowseDirectChildren(PLT_ActionReference& action,
  207. const char* object_id,
  208. const char* filter,
  209. NPT_UInt32 starting_index,
  210. NPT_UInt32 requested_count,
  211. const NPT_List<NPT_String>& sort_criteria,
  212. const PLT_HttpRequestContext& context);
  213. virtual NPT_Result OnSearchContainer(PLT_ActionReference& action,
  214. const char* container_id,
  215. const char* search_criteria,
  216. const char* filter,
  217. NPT_UInt32 starting_index,
  218. NPT_UInt32 requested_count,
  219. const NPT_List<NPT_String>& sort_criteria,
  220. const PLT_HttpRequestContext& context);
  221. // PLT_FileMediaServer methods
  222. virtual NPT_Result ServeFile(NPT_HttpRequest& request,
  223. const NPT_HttpRequestContext& context,
  224. NPT_HttpResponse& response,
  225. const NPT_String& file_path);
  226. // class methods
  227. static NPT_Result PopulateObjectFromTag(CMusicInfoTag& tag,
  228. PLT_MediaObject& object,
  229. NPT_String* file_path,
  230. PLT_MediaItemResource* resource,
  231. EClientQuirks quirks);
  232. static NPT_Result PopulateObjectFromTag(CVideoInfoTag& tag,
  233. PLT_MediaObject& object,
  234. NPT_String* file_path,
  235. PLT_MediaItemResource* resource,
  236. EClientQuirks quirks);
  237. static PLT_MediaObject* BuildObject(const CFileItem& item,
  238. NPT_String& file_path,
  239. bool with_count,
  240. const PLT_HttpRequestContext* context = NULL,
  241. CUPnPServer* upnp_server = NULL);
  242. NPT_String BuildSafeResourceUri(const char* host,
  243. const char* file_path);
  244. void AddSafeResourceUri(PLT_MediaObject* object, NPT_List<NPT_IpAddress> ips, const char* file_path, const NPT_String& info)
  245. {
  246. PLT_MediaItemResource res;
  247. for(NPT_List<NPT_IpAddress>::Iterator ip = ips.GetFirstItem(); ip; ++ip) {
  248. res.m_ProtocolInfo = PLT_ProtocolInfo(info);
  249. res.m_Uri = BuildSafeResourceUri((*ip).ToString(), file_path);
  250. object->m_Resources.Add(res);
  251. }
  252. }
  253. static const char* GetMimeTypeFromExtension(const char* extension, const PLT_HttpRequestContext* context = NULL);
  254. static NPT_String GetMimeType(const CFileItem& item, const PLT_HttpRequestContext* context = NULL);
  255. static NPT_String GetMimeType(const char* filename, const PLT_HttpRequestContext* context = NULL);
  256. static const CStdString& CorrectAllItemsSortHack(const CStdString &item);
  257. private:
  258. PLT_MediaObject* Build(CFileItemPtr item,
  259. bool with_count,
  260. const PLT_HttpRequestContext& context,
  261. const char* parent_id = NULL);
  262. NPT_Result BuildResponse(PLT_ActionReference& action,
  263. CFileItemList& items,
  264. const char* filter,
  265. NPT_UInt32 starting_index,
  266. NPT_UInt32 requested_count,
  267. const NPT_List<NPT_String>& sort_criteria,
  268. const PLT_HttpRequestContext& context,
  269. const char* parent_id /* = NULL */);
  270. // class methods
  271. static NPT_String GetParentFolder(NPT_String file_path) {
  272. int index = file_path.ReverseFind("\\");
  273. if (index == -1) return "";
  274. return file_path.Left(index);
  275. }
  276. static const NPT_String GetProtocolInfo(const CFileItem& item,
  277. const char* protocol,
  278. const PLT_HttpRequestContext* context = NULL);
  279. NPT_Mutex m_FileMutex;
  280. NPT_Map<NPT_String, NPT_String> m_FileMap;
  281. public:
  282. // class members
  283. static NPT_UInt32 m_MaxReturnedItems;
  284. };
  285. NPT_UInt32 CUPnPServer::m_MaxReturnedItems = 0;
  286. /*----------------------------------------------------------------------
  287. | CUPnPServer::BuildSafeResourceUri
  288. +---------------------------------------------------------------------*/
  289. NPT_String CUPnPServer::BuildSafeResourceUri(const char* host,
  290. const char* file_path)
  291. {
  292. CStdString md5;
  293. XBMC::XBMC_MD5 md5state;
  294. md5state.append(file_path);
  295. md5state.getDigest(md5);
  296. md5 += "/" + URIUtils::GetFileName(file_path);
  297. { NPT_AutoLock lock(m_FileMutex);
  298. NPT_CHECK(m_FileMap.Put(md5.c_str(), file_path));
  299. }
  300. return PLT_FileMediaServer::BuildSafeResourceUri(m_FileBaseUri, host, md5);
  301. }
  302. /*----------------------------------------------------------------------
  303. | CUPnPServer::GetMimeType
  304. +---------------------------------------------------------------------*/
  305. NPT_String
  306. CUPnPServer::GetMimeType(const char* filename,
  307. const PLT_HttpRequestContext* context /* = NULL */)
  308. {
  309. NPT_String ext = URIUtils::GetExtension(filename).c_str();
  310. ext.TrimLeft('.');
  311. ext = ext.ToLowercase();
  312. return PLT_MediaObject::GetMimeTypeFromExtension(ext, context);
  313. }
  314. /*----------------------------------------------------------------------
  315. | CUPnPServer::GetMimeType
  316. +---------------------------------------------------------------------*/
  317. NPT_String
  318. CUPnPServer::GetMimeType(const CFileItem& item,
  319. const PLT_HttpRequestContext* context /* = NULL */)
  320. {
  321. CStdString path = item.GetPath();
  322. if (item.HasVideoInfoTag() && !item.GetVideoInfoTag()->GetPath().IsEmpty()) {
  323. path = item.GetVideoInfoTag()->GetPath();
  324. } else if (item.HasMusicInfoTag() && !item.GetMusicInfoTag()->GetURL().IsEmpty()) {
  325. path = item.GetMusicInfoTag()->GetURL();
  326. }
  327. if(path.Left(8).Equals("stack://"))
  328. return "audio/x-mpegurl";
  329. NPT_String ext = URIUtils::GetExtension(path).c_str();
  330. ext.TrimLeft('.');
  331. ext = ext.ToLowercase();
  332. NPT_String mime;
  333. /* We always use Platinum mime type first
  334. as it is defined to map extension to DLNA compliant mime type
  335. or custom according to context (who asked for it) */
  336. if (!ext.IsEmpty()) {
  337. mime = PLT_MediaObject::GetMimeTypeFromExtension(ext, context);
  338. if (mime == "application/octet-stream") mime = "";
  339. }
  340. /* if Platinum couldn't map it, default to XBMC mapping */
  341. if (mime.IsEmpty()) {
  342. NPT_String mime = item.GetMimeType().c_str();
  343. if (mime == "application/octet-stream") mime = "";
  344. }
  345. /* fallback to generic mime type if not found */
  346. if (mime.IsEmpty()) {
  347. if (item.IsVideo() || item.IsVideoDb() )
  348. mime = "video/" + ext;
  349. else if (item.IsAudio() || item.IsMusicDb() )
  350. mime = "audio/" + ext;
  351. else if (item.IsPicture() )
  352. mime = "image/" + ext;
  353. }
  354. /* nothing we can figure out */
  355. if (mime.IsEmpty()) {
  356. mime = "application/octet-stream";
  357. }
  358. return mime;
  359. }
  360. /*----------------------------------------------------------------------
  361. | CUPnPServer::GetProtocolInfo
  362. +---------------------------------------------------------------------*/
  363. const NPT_String
  364. CUPnPServer::GetProtocolInfo(const CFileItem& item,
  365. const char* protocol,
  366. const PLT_HttpRequestContext* context /* = NULL */)
  367. {
  368. NPT_String proto = protocol;
  369. /* fixup the protocol just in case nothing was passed */
  370. if (proto.IsEmpty()) {
  371. proto = item.GetAsUrl().GetProtocol();
  372. }
  373. /*
  374. map protocol to right prefix and use xbmc-get for
  375. unsupported UPnP protocols for other xbmc clients
  376. TODO: add rtsp ?
  377. */
  378. if (proto == "http") {
  379. proto = "http-get";
  380. } else {
  381. proto = "xbmc-get";
  382. }
  383. /* we need a valid extension to retrieve the mimetype for the protocol info */
  384. NPT_String mime = GetMimeType(item, context);
  385. proto += ":*:" + mime + ":" + PLT_MediaObject::GetDlnaExtension(mime, context);
  386. return proto;
  387. }
  388. /*----------------------------------------------------------------------
  389. | CUPnPServer::PopulateObjectFromTag
  390. +---------------------------------------------------------------------*/
  391. NPT_Result
  392. CUPnPServer::PopulateObjectFromTag(CMusicInfoTag& tag,
  393. PLT_MediaObject& object,
  394. NPT_String* file_path, /* = NULL */
  395. PLT_MediaItemResource* resource, /* = NULL */
  396. EClientQuirks quirks)
  397. {
  398. if (!tag.GetURL().IsEmpty() && file_path)
  399. *file_path = tag.GetURL();
  400. object.m_Affiliation.genre = NPT_String(tag.GetGenre().c_str()).Split(" / ");
  401. object.m_Title = tag.GetTitle();
  402. object.m_Affiliation.album = tag.GetAlbum();
  403. object.m_People.artists.Add(tag.GetArtist().c_str());
  404. object.m_People.artists.Add(tag.GetArtist().c_str(), "Performer");
  405. object.m_People.artists.Add(!tag.GetAlbumArtist().empty()?tag.GetAlbumArtist().c_str():tag.GetArtist().c_str(), "AlbumArtist");
  406. if(tag.GetAlbumArtist().IsEmpty())
  407. object.m_Creator = tag.GetArtist();
  408. else
  409. object.m_Creator = tag.GetAlbumArtist();
  410. object.m_MiscInfo.original_track_number = tag.GetTrackNumber();
  411. if(tag.GetDatabaseId() >= 0) {
  412. object.m_ReferenceID = NPT_String::Format("musicdb://4/%i%s", tag.GetDatabaseId(), URIUtils::GetExtension(tag.GetURL()).c_str());
  413. }
  414. if (object.m_ReferenceID == object.m_ObjectID)
  415. object.m_ReferenceID = "";
  416. if (resource) resource->m_Duration = tag.GetDuration();
  417. return NPT_SUCCESS;
  418. }
  419. /*----------------------------------------------------------------------
  420. | CUPnPServer::PopulateObjectFromTag
  421. +---------------------------------------------------------------------*/
  422. NPT_Result
  423. CUPnPServer::PopulateObjectFromTag(CVideoInfoTag& tag,
  424. PLT_MediaObject& object,
  425. NPT_String* file_path, /* = NULL */
  426. PLT_MediaItemResource* resource, /* = NULL */
  427. EClientQuirks quirks)
  428. {
  429. // some usefull buffers
  430. CStdStringArray strings;
  431. if (!tag.m_strFileNameAndPath.IsEmpty() && file_path)
  432. *file_path = tag.m_strFileNameAndPath;
  433. if (tag.m_iDbId != -1 ) {
  434. if (!tag.m_strArtist.IsEmpty()) {
  435. object.m_ObjectClass.type = "object.item.videoItem.musicVideoClip";
  436. object.m_Creator = tag.m_strArtist;
  437. object.m_Title = tag.m_strTitle;
  438. object.m_ReferenceID = NPT_String::Format("videodb://3/2/%i", tag.m_iDbId);
  439. } else if (!tag.m_strShowTitle.IsEmpty()) {
  440. object.m_ObjectClass.type = "object.item.videoItem.videoBroadcast";
  441. object.m_Recorded.program_title = "S" + ("0" + NPT_String::FromInteger(tag.m_iSeason)).Right(2);
  442. object.m_Recorded.program_title += "E" + ("0" + NPT_String::FromInteger(tag.m_iEpisode)).Right(2);
  443. object.m_Recorded.program_title += " : " + tag.m_strTitle;
  444. object.m_Recorded.series_title = tag.m_strShowTitle;
  445. object.m_Recorded.episode_number = tag.m_iSeason * 100 + tag.m_iEpisode;
  446. object.m_Title = object.m_Recorded.series_title + " - " + object.m_Recorded.program_title;
  447. object.m_Date = tag.m_strFirstAired;
  448. if(tag.m_iSeason != -1)
  449. object.m_ReferenceID = NPT_String::Format("videodb://2/0/%i", tag.m_iDbId);
  450. } else {
  451. object.m_ObjectClass.type = "object.item.videoItem.movie";
  452. object.m_Title = tag.m_strTitle;
  453. object.m_Date = NPT_String::FromInteger(tag.m_iYear) + "-01-01";
  454. object.m_ReferenceID = NPT_String::Format("videodb://1/2/%i", tag.m_iDbId);
  455. }
  456. }
  457. if(quirks & ECLIENTQUIRKS_BASICVIDEOCLASS)
  458. object.m_ObjectClass.type = "object.item.videoItem";
  459. if(object.m_ReferenceID == object.m_ObjectID)
  460. object.m_ReferenceID = "";
  461. object.m_Affiliation.genre = NPT_String(tag.m_strGenre.c_str()).Split(" / ");
  462. for(CVideoInfoTag::iCast it = tag.m_cast.begin();it != tag.m_cast.end();it++) {
  463. object.m_People.actors.Add(it->strName.c_str(), it->strRole.c_str());
  464. }
  465. object.m_People.director = tag.m_strDirector;
  466. object.m_People.authors.Add(tag.m_strWritingCredits.c_str());
  467. object.m_Description.description = tag.m_strTagLine;
  468. object.m_Description.long_description = tag.m_strPlot;
  469. if (resource) resource->m_Duration = tag.m_streamDetails.GetVideoDuration();
  470. if (resource) resource->m_Resolution = NPT_String::FromInteger(tag.m_streamDetails.GetVideoWidth()) + "x" + NPT_String::FromInteger(tag.m_streamDetails.GetVideoHeight());
  471. return NPT_SUCCESS;
  472. }
  473. /*----------------------------------------------------------------------
  474. | CUPnPServer::CorrectAllItemsSortHack
  475. +---------------------------------------------------------------------*/
  476. const CStdString&
  477. CUPnPServer::CorrectAllItemsSortHack(const CStdString &item)
  478. {
  479. // This is required as in order for the "* All Albums" etc. items to sort
  480. // correctly, they must have fake artist/album etc. information generated.
  481. // This looks nasty if we attempt to render it to the GUI, thus this (further)
  482. // workaround
  483. if ((item.size() == 1 && item[0] == 0x01) || (item.size() > 1 && ((unsigned char) item[1]) == 0xff))
  484. return StringUtils::EmptyString;
  485. return item;
  486. }
  487. /*----------------------------------------------------------------------
  488. | CUPnPServer::BuildObject
  489. +---------------------------------------------------------------------*/
  490. PLT_MediaObject*
  491. CUPnPServer::BuildObject(const CFileItem& item,
  492. NPT_String& file_path,
  493. bool with_count,
  494. const PLT_HttpRequestContext* context /* = NULL */,
  495. CUPnPServer* upnp_server /* = NULL */)
  496. {
  497. PLT_MediaItemResource resource;
  498. PLT_MediaObject* object = NULL;
  499. CLog::Log(LOGDEBUG, "Building didl for object '%s'", (const char*)item.GetPath());
  500. EClientQuirks quirks = GetClientQuirks(context);
  501. // get list of ip addresses
  502. NPT_List<NPT_IpAddress> ips;
  503. NPT_CHECK_LABEL(PLT_UPnPMessageHelper::GetIPAddresses(ips), failure);
  504. // if we're passed an interface where we received the request from
  505. // move the ip to the top
  506. if (context && context->GetLocalAddress().GetIpAddress().ToString() != "0.0.0.0") {
  507. ips.Remove(context->GetLocalAddress().GetIpAddress());
  508. ips.Insert(ips.GetFirstItem(), context->GetLocalAddress().GetIpAddress());
  509. }
  510. if (!item.m_bIsFolder) {
  511. object = new PLT_MediaItem();
  512. object->m_ObjectID = item.GetPath();
  513. /* Setup object type */
  514. if (item.IsMusicDb() || item.IsAudio()) {
  515. object->m_ObjectClass.type = "object.item.audioItem.musicTrack";
  516. if (item.HasMusicInfoTag()) {
  517. CMusicInfoTag *tag = (CMusicInfoTag*)item.GetMusicInfoTag();
  518. PopulateObjectFromTag(*tag, *object, &file_path, &resource, quirks);
  519. }
  520. } else if (item.IsVideoDb() || item.IsVideo()) {
  521. object->m_ObjectClass.type = "object.item.videoItem";
  522. if(quirks & ECLIENTQUIRKS_UNKNOWNSERIES)
  523. object->m_Affiliation.album = "[Unknown Series]";
  524. if (item.HasVideoInfoTag()) {
  525. CVideoInfoTag *tag = (CVideoInfoTag*)item.GetVideoInfoTag();
  526. PopulateObjectFromTag(*tag, *object, &file_path, &resource, quirks);
  527. }
  528. } else if (item.IsPicture()) {
  529. object->m_ObjectClass.type = "object.item.imageItem.photo";
  530. } else {
  531. object->m_ObjectClass.type = "object.item";
  532. }
  533. // duration of zero is invalid
  534. if (resource.m_Duration == 0) resource.m_Duration = -1;
  535. // Set the resource file size
  536. resource.m_Size = item.m_dwSize;
  537. if (resource.m_Size == 0) {
  538. struct __stat64 info;
  539. if(CFile::Stat((const char*)file_path, &info) == 0 && info.st_size >= 0)
  540. resource.m_Size = info.st_size;
  541. }
  542. if(resource.m_Size == 0)
  543. resource.m_Size = (NPT_LargeSize)-1;
  544. // set date
  545. if (object->m_Date.IsEmpty() && item.m_dateTime.IsValid()) {
  546. object->m_Date = item.m_dateTime.GetAsLocalizedDate();
  547. }
  548. if (upnp_server) {
  549. upnp_server->AddSafeResourceUri(object, ips, file_path, GetProtocolInfo(item, "http", context));
  550. }
  551. // if the item is remote, add a direct link to the item
  552. if (URIUtils::IsRemote((const char*)file_path)) {
  553. resource.m_ProtocolInfo = PLT_ProtocolInfo(CUPnPServer::GetProtocolInfo(item, item.GetAsUrl().GetProtocol(), context));
  554. resource.m_Uri = file_path;
  555. // if the direct link can be served directly using http, then push it in front
  556. // otherwise keep the xbmc-get resource last and let a compatible client look for it
  557. if (resource.m_ProtocolInfo.ToString().StartsWith("xbmc", true)) {
  558. object->m_Resources.Add(resource);
  559. } else {
  560. object->m_Resources.Insert(object->m_Resources.GetFirstItem(), resource);
  561. }
  562. }
  563. // Some upnp clients expect all audio items to have parent root id 4
  564. #ifdef WMP_ID_MAPPING
  565. object->m_ParentID = "4";
  566. #endif
  567. } else {
  568. PLT_MediaContainer* container = new PLT_MediaContainer;
  569. object = container;
  570. /* Assign a title and id for this container */
  571. container->m_ObjectID = item.GetPath();
  572. container->m_ObjectClass.type = "object.container";
  573. container->m_ChildrenCount = -1;
  574. CStdStringArray strings;
  575. /* this might be overkill, but hey */
  576. if (item.IsMusicDb()) {
  577. MUSICDATABASEDIRECTORY::NODE_TYPE node = CMusicDatabaseDirectory::GetDirectoryType(item.GetPath());
  578. switch(node) {
  579. case MUSICDATABASEDIRECTORY::NODE_TYPE_ARTIST: {
  580. container->m_ObjectClass.type += ".person.musicArtist";
  581. CMusicInfoTag *tag = (CMusicInfoTag*)item.GetMusicInfoTag();
  582. if (tag) {
  583. container->m_People.artists.Add(
  584. CorrectAllItemsSortHack(tag->GetArtist()).c_str(), "Performer");
  585. container->m_People.artists.Add(
  586. CorrectAllItemsSortHack(!tag->GetAlbumArtist().empty()?tag->GetAlbumArtist():tag->GetArtist()).c_str(), "AlbumArtist");
  587. }
  588. #ifdef WMP_ID_MAPPING
  589. // Some upnp clients expect all artists to have parent root id 107
  590. container->m_ParentID = "107";
  591. #endif
  592. }
  593. break;
  594. case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM:
  595. case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM_COMPILATIONS:
  596. case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM_RECENTLY_ADDED:
  597. case MUSICDATABASEDIRECTORY::NODE_TYPE_YEAR_ALBUM: {
  598. container->m_ObjectClass.type += ".album.musicAlbum";
  599. // for Sonos to be happy
  600. CMusicInfoTag *tag = (CMusicInfoTag*)item.GetMusicInfoTag();
  601. if (tag) {
  602. container->m_People.artists.Add(
  603. CorrectAllItemsSortHack(tag->GetArtist()).c_str(), "Performer");
  604. container->m_People.artists.Add(
  605. CorrectAllItemsSortHack(!tag->GetAlbumArtist().empty()?tag->GetAlbumArtist():tag->GetArtist()).c_str(), "AlbumArtist");
  606. container->m_Affiliation.album = CorrectAllItemsSortHack(tag->GetAlbum()).c_str();
  607. }
  608. #ifdef WMP_ID_MAPPING
  609. // Some upnp clients expect all albums to have parent root id 7
  610. container->m_ParentID = "7";
  611. #endif
  612. }
  613. break;
  614. case MUSICDATABASEDIRECTORY::NODE_TYPE_GENRE:
  615. container->m_ObjectClass.type += ".genre.musicGenre";
  616. break;
  617. default:
  618. break;
  619. }
  620. } else if (item.IsVideoDb()) {
  621. VIDEODATABASEDIRECTORY::NODE_TYPE node = CVideoDatabaseDirectory::GetDirectoryType(item.GetPath());
  622. CVideoInfoTag &tag = *(CVideoInfoTag*)item.GetVideoInfoTag();
  623. switch(node) {
  624. case VIDEODATABASEDIRECTORY::NODE_TYPE_GENRE:
  625. container->m_ObjectClass.type += ".genre.movieGenre";
  626. break;
  627. case VIDEODATABASEDIRECTORY::NODE_TYPE_ACTOR:
  628. container->m_ObjectClass.type += ".person.videoArtist";
  629. container->m_Creator = tag.m_strArtist;
  630. container->m_Title = tag.m_strTitle;
  631. break;
  632. case VIDEODATABASEDIRECTORY::NODE_TYPE_TITLE_TVSHOWS:
  633. container->m_ObjectClass.type += ".album.videoAlbum";
  634. container->m_Recorded.program_title = "S" + ("0" + NPT_String::FromInteger(tag.m_iSeason)).Right(2);
  635. container->m_Recorded.program_title += "E" + ("0" + NPT_String::FromInteger(tag.m_iEpisode)).Right(2);
  636. container->m_Recorded.program_title += " : " + tag.m_strTitle;
  637. container->m_Recorded.series_title = tag.m_strShowTitle;
  638. container->m_Recorded.episode_number = tag.m_iSeason * 100 + tag.m_iEpisode;
  639. container->m_Title = container->m_Recorded.series_title + " - " + container->m_Recorded.program_title;
  640. container->m_Title = tag.m_strTitle;
  641. if(tag.m_strFirstAired.IsEmpty() && tag.m_iYear)
  642. container->m_Date = NPT_String::FromInteger(tag.m_iYear) + "-01-01";
  643. else
  644. container->m_Date = tag.m_strFirstAired;
  645. container->m_Affiliation.genre = NPT_String(tag.m_strGenre.c_str()).Split(" / ");
  646. for(CVideoInfoTag::iCast it = tag.m_cast.begin();it != tag.m_cast.end();it++) {
  647. container->m_People.actors.Add(it->strName.c_str(), it->strRole.c_str());
  648. }
  649. container->m_People.director = tag.m_strDirector;
  650. container->m_People.authors.Add(tag.m_strWritingCredits.c_str());
  651. container->m_Description.description = tag.m_strTagLine;
  652. container->m_Description.long_description = tag.m_strPlot;
  653. break;
  654. default:
  655. container->m_ObjectClass.type += ".storageFolder";
  656. break;
  657. }
  658. } else if (item.IsPlayList()) {
  659. container->m_ObjectClass.type += ".playlistContainer";
  660. }
  661. if(quirks & ECLIENTQUIRKS_ONLYSTORAGEFOLDER) {
  662. container->m_ObjectClass.type = "object.container.storageFolder";
  663. }
  664. /* Get the number of children for this container */
  665. if (with_count && upnp_server) {
  666. if (object->m_ObjectID.StartsWith("virtualpath://")) {
  667. NPT_Cardinal count = 0;
  668. NPT_CHECK_LABEL(NPT_File::GetCount(file_path, count), failure);
  669. container->m_ChildrenCount = count;
  670. } else {
  671. /* this should be a standard path */
  672. // TODO - get file count of this directory
  673. }
  674. }
  675. }
  676. // set a title for the object
  677. if (object->m_Title.IsEmpty()) {
  678. if (!item.GetLabel().IsEmpty()) {
  679. CStdString title = item.GetLabel();
  680. if (item.IsPlayList() || !item.m_bIsFolder) URIUtils::RemoveExtension(title);
  681. object->m_Title = title;
  682. } else {
  683. CStdString title, volumeNumber;
  684. CUtil::GetVolumeFromFileName(item.GetPath(), title, volumeNumber);
  685. if (!item.m_bIsFolder) URIUtils::RemoveExtension(title);
  686. object->m_Title = title;
  687. }
  688. }
  689. // set a thumbnail if we have one
  690. if (item.HasThumbnail() && upnp_server) {
  691. object->m_ExtraInfo.album_art_uri = upnp_server->BuildSafeResourceUri(
  692. (*ips.GetFirstItem()).ToString(),
  693. item.GetThumbnailImage());
  694. // Set DLNA profileID by extension, defaulting to JPEG.
  695. NPT_String ext = URIUtils::GetExtension(item.GetThumbnailImage()).c_str();
  696. if (strcmp(ext, ".png") == 0) {
  697. object->m_ExtraInfo.album_art_uri_dlna_profile = "PNG_TN";
  698. } else {
  699. object->m_ExtraInfo.album_art_uri_dlna_profile = "JPEG_TN";
  700. }
  701. }
  702. if (upnp_server) {
  703. CStdString fanart(item.GetCachedFanart());
  704. if (CFile::Exists(fanart)) {
  705. upnp_server->AddSafeResourceUri(object, ips, fanart, "xbmc.org:*:fanart:*");
  706. }
  707. }
  708. return object;
  709. failure:
  710. delete object;
  711. return NULL;
  712. }
  713. /*----------------------------------------------------------------------
  714. | CUPnPServer::Build
  715. +---------------------------------------------------------------------*/
  716. PLT_MediaObject*
  717. CUPnPServer::Build(CFileItemPtr item,
  718. bool with_count,
  719. const PLT_HttpRequestContext& context,
  720. const char* parent_id /* = NULL */)
  721. {
  722. PLT_MediaObject* object = NULL;
  723. NPT_String path = item->GetPath().c_str();
  724. //HACK: temporary disabling count as it thrashes HDD
  725. with_count = false;
  726. CLog::Log(LOGDEBUG, "Preparing upnp object for item '%s'", (const char*)path);
  727. if (path == "virtualpath://upnproot") {
  728. path.TrimRight("/");
  729. if (path.StartsWith("virtualpath://")) {
  730. object = new PLT_MediaContainer;
  731. object->m_Title = item->GetLabel();
  732. object->m_ObjectClass.type = "object.container";
  733. object->m_ObjectID = path;
  734. // root
  735. object->m_ObjectID = "0";
  736. object->m_ParentID = "-1";
  737. // root has 5 children
  738. if (with_count) {
  739. ((PLT_MediaContainer*)object)->m_ChildrenCount = 5;
  740. }
  741. } else {
  742. goto failure;
  743. }
  744. } else {
  745. // db path handling
  746. NPT_String file_path, share_name;
  747. file_path = item->GetPath();
  748. share_name = "";
  749. if (path.StartsWith("musicdb://")) {
  750. if (path == "musicdb://" ) {
  751. item->SetLabel("Music Library");
  752. item->SetLabelPreformated(true);
  753. } else {
  754. if (!item->HasMusicInfoTag() || !item->GetMusicInfoTag()->Loaded() )
  755. item->LoadMusicTag();
  756. if (!item->HasThumbnail() )
  757. item->SetCachedMusicThumb();
  758. if (item->GetLabel().IsEmpty()) {
  759. /* if no label try to grab it from node type */
  760. CStdString label;
  761. if (CMusicDatabaseDirectory::GetLabel((const char*)path, label)) {
  762. item->SetLabel(label);
  763. item->SetLabelPreformated(true);
  764. }
  765. }
  766. }
  767. } else if (file_path.StartsWith("videodb://")) {
  768. if (path == "videodb://" ) {
  769. item->SetLabel("Video Library");
  770. item->SetLabelPreformated(true);
  771. } else {
  772. if (!item->HasVideoInfoTag()) {
  773. XFILE::VIDEODATABASEDIRECTORY::CQueryParams params;
  774. XFILE::VIDEODATABASEDIRECTORY::CDirectoryNode::GetDatabaseInfo((const char*)path, params);
  775. CVideoDatabase db;
  776. if (!db.Open() ) return NULL;
  777. if (params.GetMovieId() >= 0 )
  778. db.GetMovieInfo((const char*)path, *item->GetVideoInfoTag(), params.GetMovieId());
  779. else if (params.GetEpisodeId() >= 0 )
  780. db.GetEpisodeInfo((const char*)path, *item->GetVideoInfoTag(), params.GetEpisodeId());
  781. else if (params.GetTvShowId() >= 0 )
  782. db.GetTvShowInfo((const char*)path, *item->GetVideoInfoTag(), params.GetTvShowId());
  783. }
  784. // try to grab title from tag
  785. if (item->HasVideoInfoTag() && !item->GetVideoInfoTag()->m_strTitle.IsEmpty()) {
  786. item->SetLabel(item->GetVideoInfoTag()->m_strTitle);
  787. item->SetLabelPreformated(true);
  788. }
  789. // try to grab it from the folder
  790. if (item->GetLabel().IsEmpty()) {
  791. CStdString label;
  792. if (CVideoDatabaseDirectory::GetLabel((const char*)path, label)) {
  793. item->SetLabel(label);
  794. item->SetLabelPreformated(true);
  795. }
  796. }
  797. if (!item->HasThumbnail() )
  798. item->SetCachedVideoThumb();
  799. }
  800. }
  801. // not a virtual path directory, new system
  802. object = BuildObject(*item.get(), file_path, with_count, &context, this);
  803. // set parent id if passed, otherwise it should have been determined
  804. if (object && parent_id) {
  805. object->m_ParentID = parent_id;
  806. }
  807. }
  808. if (object) {
  809. // remap Root virtualpath://upnproot/ to id "0"
  810. if (object->m_ObjectID == "virtualpath://upnproot/")
  811. object->m_ObjectID = "0";
  812. // remap Parent Root virtualpath://upnproot/ to id "0"
  813. if (object->m_ParentID == "virtualpath://upnproot/")
  814. object->m_ParentID = "0";
  815. }
  816. return object;
  817. failure:
  818. delete object;
  819. return NULL;
  820. }
  821. /*----------------------------------------------------------------------
  822. | TranslateWMPObjectId
  823. +---------------------------------------------------------------------*/
  824. static NPT_String TranslateWMPObjectId(NPT_String id)
  825. {
  826. if (id == "0") {
  827. id = "virtualpath://upnproot/";
  828. } else if (id == "15") {
  829. // Xbox 360 asking for videos
  830. id = "videodb://";
  831. } else if (id == "16") {
  832. // Xbox 360 asking for photos
  833. } else if (id == "107") {
  834. // Sonos uses 107 for artists root container id
  835. id = "musicdb://2/";
  836. } else if (id == "7") {
  837. // Sonos uses 7 for albums root container id
  838. id = "musicdb://3/";
  839. } else if (id == "4") {
  840. // Sonos uses 4 for tracks root container id
  841. id = "musicdb://4/";
  842. }
  843. CLog::Log(LOGDEBUG, "UPnP Translated id to '%s'", (const char*)id);
  844. return id;
  845. }
  846. /*----------------------------------------------------------------------
  847. | CUPnPServer::OnBrowseMetadata
  848. +---------------------------------------------------------------------*/
  849. NPT_Result
  850. CUPnPServer::OnBrowseMetadata(PLT_ActionReference& action,
  851. const char* object_id,
  852. const char* filter,
  853. NPT_UInt32 starting_index,
  854. NPT_UInt32 requested_count,
  855. const NPT_List<NPT_String>& sort_criteria,
  856. const PLT_HttpRequestContext& context)
  857. {
  858. NPT_COMPILER_UNUSED(sort_criteria);
  859. NPT_COMPILER_UNUSED(requested_count);
  860. NPT_COMPILER_UNUSED(starting_index);
  861. NPT_String didl;
  862. NPT_Reference<PLT_MediaObject> object;
  863. NPT_String id = TranslateWMPObjectId(object_id);
  864. vector<CStdString> paths;
  865. CFileItemPtr item;
  866. CLog::Log(LOGINFO, "Received UPnP Browse Metadata request for object '%s'", (const char*)object_id);
  867. if (id.StartsWith("virtualpath://")) {
  868. id.TrimRight("/");
  869. if (id == "virtualpath://upnproot") {
  870. id += "/";
  871. item.reset(new CFileItem((const char*)id, true));
  872. item->SetLabel("Root");
  873. item->SetLabelPreformated(true);
  874. object = Build(item, true, context);
  875. } else {
  876. return NPT_FAILURE;
  877. }
  878. } else {
  879. // determine if it's a container by calling CDirectory::Exists
  880. item.reset(new CFileItem((const char*)id, CDirectory::Exists((const char*)id)));
  881. // determine parent id for shared paths only
  882. // otherwise let db find out
  883. CStdString parent;
  884. if (!URIUtils::GetParentPath((const char*)id, parent)) parent = "0";
  885. //#ifdef WMP_ID_MAPPING
  886. // if (!id.StartsWith("musicdb://") && !id.StartsWith("videodb://")) {
  887. // parent = "";
  888. // }
  889. //#endif
  890. object = Build(item, true, context, parent.empty()?NULL:parent.c_str());
  891. }
  892. if (object.IsNull()) {
  893. /* error */
  894. NPT_LOG_WARNING_1("CUPnPServer::OnBrowseMetadata - Object null (%s)", object_id);
  895. action->SetError(701, "No Such Object.");
  896. return NPT_FAILURE;
  897. }
  898. NPT_String tmp;
  899. NPT_CHECK(PLT_Didl::ToDidl(*object.AsPointer(), filter, tmp));
  900. /* add didl header and footer */
  901. didl = didl_header + tmp + didl_footer;
  902. NPT_CHECK(action->SetArgumentValue("Result", didl));
  903. NPT_CHECK(action->SetArgumentValue("NumberReturned", "1"));
  904. NPT_CHECK(action->SetArgumentValue("TotalMatches", "1"));
  905. // update ID may be wrong here, it should be the one of the container?
  906. NPT_CHECK(action->SetArgumentValue("UpdateId", "0"));
  907. // TODO: We need to keep track of the overall SystemUpdateID of the CDS
  908. return NPT_SUCCESS;
  909. }
  910. /*----------------------------------------------------------------------
  911. | CUPnPServer::OnBrowseDirectChildren
  912. +---------------------------------------------------------------------*/
  913. NPT_Result
  914. CUPnPServer::OnBrowseDirectChildren(PLT_ActionReference& action,
  915. const char* object_id,
  916. const char* filter,
  917. NPT_UInt32 starting_index,
  918. NPT_UInt32 requested_count,
  919. const NPT_List<NPT_String>& sort_criteria,
  920. const PLT_HttpRequestContext& context)
  921. {
  922. CFileItemList items;
  923. NPT_String parent_id = TranslateWMPObjectId(object_id);
  924. CLog::Log(LOGINFO, "Received UPnP Browse DirectChildren request for object '%s'", (const char*)object_id);
  925. items.SetPath(CStdString(parent_id));
  926. if (!items.Load()) {
  927. // cache anything that takes more than a second to retrieve
  928. unsigned int time = XbmcThreads::SystemClockMillis();
  929. if (parent_id.StartsWith("virtualpath://upnproot")) {
  930. CFileItemPtr item;
  931. // music library
  932. item.reset(new CFileItem("musicdb://", true));
  933. item->SetLabel("Music Library");
  934. item->SetLabelPreformated(true);
  935. items.Add(item);
  936. // video library
  937. item.reset(new CFileItem("videodb://", true));
  938. item->SetLabel("Video Library");
  939. item->SetLabelPreformated(true);
  940. items.Add(item);
  941. } else {
  942. CDirectory::GetDirectory((const char*)parent_id, items);
  943. }
  944. if (items.CacheToDiscAlways() || (items.CacheToDiscIfSlow() && (XbmcThreads::SystemClockMillis() - time) > 1000 )) {
  945. items.Save();
  946. }
  947. }
  948. // Always sort by label
  949. items.Sort(SORT_METHOD_LABEL, SORT_ORDER_ASC);
  950. // Don't pass parent_id if action is Search not BrowseDirectChildren, as
  951. // we want the engine to determine the best parent id, not necessarily the one
  952. // passed
  953. NPT_String action_name = action->GetActionDesc().GetName();
  954. return BuildResponse(
  955. action,
  956. items,
  957. filter,
  958. starting_index,
  959. requested_count,
  960. sort_criteria,
  961. context,
  962. (action_name.Compare("Search", true)==0)?NULL:parent_id.GetChars());
  963. }
  964. /*----------------------------------------------------------------------
  965. | CUPnPServer::BuildResponse
  966. +---------------------------------------------------------------------*/
  967. NPT_Result
  968. CUPnPServer::BuildResponse(PLT_ActionReference& action,
  969. CFileItemList& items,
  970. const char* filter,
  971. NPT_UInt32 starting_index,
  972. NPT_UInt32 requested_count,
  973. const NPT_List<NPT_String>& sort_criteria,
  974. const PLT_HttpRequestContext& context,
  975. const char* parent_id /* = NULL */)
  976. {
  977. NPT_COMPILER_UNUSED(sort_criteria);
  978. CLog::Log(LOGDEBUG, "Building UPnP response with filter '%s', starting @ %d with %d requested",
  979. (const char*)filter,
  980. starting_index,
  981. requested_count);
  982. // won't return more than UPNP_MAX_RETURNED_ITEMS items at a time to keep things smooth
  983. // 0 requested means as many as possible
  984. NPT_UInt32 max_count = (requested_count == 0)?m_MaxReturnedItems:min((unsigned long)requested_count, (unsigned long)m_MaxReturnedItems);
  985. NPT_UInt32 stop_index = min((unsigned long)(starting_index + max_count), (unsigned long)items.Size()); // don't return more than we can
  986. NPT_Cardinal count = 0;
  987. NPT_String didl = didl_header;
  988. PLT_MediaObjectReference object;
  989. for (unsigned long i=starting_index; i<stop_index; ++i) {
  990. object = Build(items[i], true, context, parent_id);
  991. if (object.IsNull()) {
  992. continue;
  993. }
  994. NPT_String tmp;
  995. NPT_CHECK(PLT_Didl::ToDidl(*object.AsPointer(), filter, tmp));
  996. // Neptunes string growing is dead slow for small additions
  997. if (didl.GetCapacity() < tmp.GetLength() + didl.GetLength()) {
  998. didl.Reserve((tmp.GetLength() + didl.GetLength())*2);
  999. }
  1000. didl += tmp;
  1001. ++count;
  1002. }
  1003. didl += didl_footer;
  1004. CLog::Log(LOGDEBUG, "Returning UPnP response with %d items out of %d total matches",
  1005. count,
  1006. items.Size());
  1007. NPT_CHECK(action->SetArgumentValue("Result", didl));
  1008. NPT_CHECK(action->SetArgumentValue("NumberReturned", NPT_String::FromInteger(count)));
  1009. NPT_CHECK(action->SetArgumentValue("TotalMatches", NPT_String::FromInteger(items.Size())));
  1010. NPT_CHECK(action->SetArgumentValue("UpdateId", "0"));
  1011. return NPT_SUCCESS;
  1012. }
  1013. /*----------------------------------------------------------------------
  1014. | FindSubCriteria
  1015. +---------------------------------------------------------------------*/
  1016. static
  1017. NPT_String
  1018. FindSubCriteria(NPT_String criteria, const char* name)
  1019. {
  1020. NPT_String result;
  1021. int search = criteria.Find(name);
  1022. if (search >= 0) {
  1023. criteria = criteria.Right(criteria.GetLength() - search - NPT_StringLength(name));
  1024. criteria.TrimLeft(" ");
  1025. if (criteria.GetLength()>0 && criteria[0] == '=') {
  1026. criteria.TrimLeft("= ");
  1027. if (criteria.GetLength()>0 && criteria[0] == '\"') {
  1028. search = criteria.Find("\"", 1);
  1029. if (search > 0) result = criteria.SubString(1, search-1);
  1030. }
  1031. }
  1032. }
  1033. return result;
  1034. }
  1035. /*----------------------------------------------------------------------
  1036. | CUPnPServer::OnSearchContainer
  1037. +---------------------------------------------------------------------*/
  1038. NPT_Result
  1039. CUPnPServer::OnSearchContainer(PLT_ActionReference& action,
  1040. const char* object_id,
  1041. const char* search_criteria,
  1042. const char* filter,
  1043. NPT_UInt32 starting_index,
  1044. NPT_UInt32 requested_count,
  1045. const NPT_List<NPT_String>& sort_criteria,
  1046. const PLT_HttpRequestContext& context)
  1047. {
  1048. CLog::Log(LOGDEBUG, "Received Search request for object '%s' with search '%s'",
  1049. (const char*)object_id,
  1050. (const char*)search_criteria);
  1051. NPT_String id = object_id;
  1052. if (id.StartsWith("musicdb://")) {
  1053. // we browse for all tracks given a genre, artist or album
  1054. if (NPT_String(search_criteria).Find("object.item.audioItem") >= 0) {
  1055. if (!id.EndsWith("/")) id += "/";

Large files files are truncated, but you can click here to view the full file