PageRenderTime 56ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/xbmc/network/UPnP.cpp

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

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