PageRenderTime 67ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 1ms

/xbmc/network/UPnP.cpp

https://github.com/nemphys/xbmc
C++ | 2449 lines | 1780 code | 302 blank | 367 comment | 395 complexity | e2302b5d026b5e2ade2556e301c20a1e 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 "ApplicationMessenger.h"
  27. #include "Network.h"
  28. #include "utils/log.h"
  29. #include "filesystem/MusicDatabaseDirectory.h"
  30. #include "filesystem/VideoDatabaseDirectory.h"
  31. #include "music/MusicDatabase.h"
  32. #include "video/VideoDatabase.h"
  33. #include "filesystem/VideoDatabaseDirectory/DirectoryNode.h"
  34. #include "filesystem/VideoDatabaseDirectory/QueryParams.h"
  35. #include "filesystem/File.h"
  36. #include "NptStrings.h"
  37. #include "Platinum.h"
  38. #include "PltMediaConnect.h"
  39. #include "PltMediaRenderer.h"
  40. #include "PltSyncMediaBrowser.h"
  41. #include "PltDidl.h"
  42. #include "NptNetwork.h"
  43. #include "NptConsole.h"
  44. #include "music/tags/MusicInfoTag.h"
  45. #include "pictures/PictureInfoTag.h"
  46. #include "pictures/GUIWindowSlideShow.h"
  47. #include "filesystem/Directory.h"
  48. #include "URL.h"
  49. #include "settings/GUISettings.h"
  50. #include "GUIUserMessages.h"
  51. #include "settings/Settings.h"
  52. #include "settings/AdvancedSettings.h"
  53. #include "utils/StringUtils.h"
  54. #include "FileItem.h"
  55. #include "guilib/GUIWindowManager.h"
  56. #include "GUIInfoManager.h"
  57. #include "utils/TimeUtils.h"
  58. #include "utils/md5.h"
  59. #include "guilib/Key.h"
  60. #include "ThumbLoader.h"
  61. #include "Util.h"
  62. using namespace std;
  63. using namespace MUSIC_INFO;
  64. using namespace XFILE;
  65. NPT_SET_LOCAL_LOGGER("xbmc.upnp")
  66. #define UPNP_DEFAULT_MAX_RETURNED_ITEMS 200
  67. #define UPNP_DEFAULT_MIN_RETURNED_ITEMS 30
  68. /*
  69. # Play speed
  70. # 1 normal
  71. # 0 invalid
  72. DLNA_ORG_PS = 'DLNA.ORG_PS'
  73. DLNA_ORG_PS_VAL = '1'
  74. # Convertion Indicator
  75. # 1 transcoded
  76. # 0 not transcoded
  77. DLNA_ORG_CI = 'DLNA.ORG_CI'
  78. DLNA_ORG_CI_VAL = '0'
  79. # Operations
  80. # 00 not time seek range, not range
  81. # 01 range supported
  82. # 10 time seek range supported
  83. # 11 both supported
  84. DLNA_ORG_OP = 'DLNA.ORG_OP'
  85. DLNA_ORG_OP_VAL = '01'
  86. # Flags
  87. # senderPaced 80000000 31
  88. # lsopTimeBasedSeekSupported 40000000 30
  89. # lsopByteBasedSeekSupported 20000000 29
  90. # playcontainerSupported 10000000 28
  91. # s0IncreasingSupported 08000000 27
  92. # sNIncreasingSupported 04000000 26
  93. # rtspPauseSupported 02000000 25
  94. # streamingTransferModeSupported 01000000 24
  95. # interactiveTransferModeSupported 00800000 23
  96. # backgroundTransferModeSupported 00400000 22
  97. # connectionStallingSupported 00200000 21
  98. # dlnaVersion15Supported 00100000 20
  99. DLNA_ORG_FLAGS = 'DLNA.ORG_FLAGS'
  100. DLNA_ORG_FLAGS_VAL = '01500000000000000000000000000000'
  101. */
  102. /*----------------------------------------------------------------------
  103. | static
  104. +---------------------------------------------------------------------*/
  105. CUPnP* CUPnP::upnp = NULL;
  106. // change to false for XBMC_PC if you want real UPnP functionality
  107. // otherwise keep to true for xbox as it doesn't support multicast
  108. // don't change unless you know what you're doing!
  109. bool CUPnP::broadcast = true;
  110. namespace
  111. {
  112. static const NPT_String JoinString(const NPT_List<NPT_String>& array, const NPT_String& delimiter)
  113. {
  114. NPT_String result;
  115. for(NPT_List<NPT_String>::Iterator it = array.GetFirstItem(); it; it++ )
  116. result += delimiter + (*it);
  117. if(result.IsEmpty())
  118. return "";
  119. else
  120. return result.SubString(delimiter.GetLength());
  121. }
  122. enum EClientQuirks
  123. {
  124. ECLIENTQUIRKS_NONE = 0x0
  125. /* Client requires folder's to be marked as storageFolers as verndor type (360)*/
  126. , ECLIENTQUIRKS_ONLYSTORAGEFOLDER = 0x01
  127. /* Client can't handle subtypes for videoItems (360) */
  128. , ECLIENTQUIRKS_BASICVIDEOCLASS = 0x02
  129. /* Client requires album to be set to [Unknown Series] to show title (WMP) */
  130. , ECLIENTQUIRKS_UNKNOWNSERIES = 0x04
  131. };
  132. static EClientQuirks GetClientQuirks(const PLT_HttpRequestContext* context)
  133. {
  134. if(context == NULL)
  135. return ECLIENTQUIRKS_NONE;
  136. unsigned int quirks = 0;
  137. const NPT_String* user_agent = context->GetRequest().GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_USER_AGENT);
  138. const NPT_String* server = context->GetRequest().GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_SERVER);
  139. if (user_agent) {
  140. if (user_agent->Find("XBox", 0, true) >= 0 ||
  141. user_agent->Find("Xenon", 0, true) >= 0)
  142. quirks |= ECLIENTQUIRKS_ONLYSTORAGEFOLDER | ECLIENTQUIRKS_BASICVIDEOCLASS;
  143. if (user_agent->Find("Windows-Media-Player", 0, true) >= 0)
  144. quirks |= ECLIENTQUIRKS_UNKNOWNSERIES;
  145. }
  146. if (server) {
  147. if (server->Find("Xbox", 0, true) >= 0)
  148. quirks |= ECLIENTQUIRKS_ONLYSTORAGEFOLDER | ECLIENTQUIRKS_BASICVIDEOCLASS;
  149. }
  150. return (EClientQuirks)quirks;
  151. }
  152. }
  153. /*----------------------------------------------------------------------
  154. | NPT_Console::Output
  155. +---------------------------------------------------------------------*/
  156. void
  157. NPT_Console::Output(const char* message)
  158. {
  159. CLog::Log(LOGDEBUG, "%s", message);
  160. }
  161. /*----------------------------------------------------------------------
  162. | CDeviceHostReferenceHolder class
  163. +---------------------------------------------------------------------*/
  164. class CDeviceHostReferenceHolder
  165. {
  166. public:
  167. PLT_DeviceHostReference m_Device;
  168. };
  169. /*----------------------------------------------------------------------
  170. | CCtrlPointReferenceHolder class
  171. +---------------------------------------------------------------------*/
  172. class CCtrlPointReferenceHolder
  173. {
  174. public:
  175. PLT_CtrlPointReference m_CtrlPoint;
  176. };
  177. /*----------------------------------------------------------------------
  178. | CUPnPCleaner class
  179. +---------------------------------------------------------------------*/
  180. class CUPnPCleaner : public NPT_Thread
  181. {
  182. public:
  183. CUPnPCleaner(CUPnP* upnp) : NPT_Thread(true), m_UPnP(upnp) {}
  184. void Run() {
  185. delete m_UPnP;
  186. }
  187. CUPnP* m_UPnP;
  188. };
  189. /*----------------------------------------------------------------------
  190. | CUPnP::CUPnP
  191. +---------------------------------------------------------------------*/
  192. class CUPnPServer : public PLT_MediaConnect
  193. {
  194. public:
  195. CUPnPServer(const char* friendly_name, const char* uuid = NULL, int port = 0) :
  196. PLT_MediaConnect("", friendly_name, false, uuid, port) {
  197. // hack: override path to make sure it's empty
  198. // urls will contain full paths to local files
  199. m_Path = "";
  200. }
  201. // PLT_MediaServer methods
  202. virtual NPT_Result OnBrowseMetadata(PLT_ActionReference& action,
  203. const char* object_id,
  204. const char* filter,
  205. NPT_UInt32 starting_index,
  206. NPT_UInt32 requested_count,
  207. const NPT_List<NPT_String>& sort_criteria,
  208. const PLT_HttpRequestContext& context);
  209. virtual NPT_Result OnBrowseDirectChildren(PLT_ActionReference& action,
  210. const char* object_id,
  211. const char* filter,
  212. NPT_UInt32 starting_index,
  213. NPT_UInt32 requested_count,
  214. const NPT_List<NPT_String>& sort_criteria,
  215. const PLT_HttpRequestContext& context);
  216. virtual NPT_Result OnSearchContainer(PLT_ActionReference& action,
  217. const char* container_id,
  218. const char* search_criteria,
  219. const char* filter,
  220. NPT_UInt32 starting_index,
  221. NPT_UInt32 requested_count,
  222. const NPT_List<NPT_String>& sort_criteria,
  223. const PLT_HttpRequestContext& context);
  224. // PLT_FileMediaServer methods
  225. virtual NPT_Result ServeFile(NPT_HttpRequest& request,
  226. const NPT_HttpRequestContext& context,
  227. NPT_HttpResponse& response,
  228. const NPT_String& file_path);
  229. // class methods
  230. static NPT_Result PopulateObjectFromTag(CMusicInfoTag& tag,
  231. PLT_MediaObject& object,
  232. NPT_String* file_path,
  233. PLT_MediaItemResource* resource,
  234. EClientQuirks quirks);
  235. static NPT_Result PopulateObjectFromTag(CVideoInfoTag& tag,
  236. PLT_MediaObject& object,
  237. NPT_String* file_path,
  238. PLT_MediaItemResource* resource,
  239. EClientQuirks quirks);
  240. static PLT_MediaObject* BuildObject(const CFileItem& item,
  241. NPT_String& file_path,
  242. bool with_count,
  243. const PLT_HttpRequestContext* context = NULL,
  244. CUPnPServer* upnp_server = NULL);
  245. NPT_String BuildSafeResourceUri(const char* host,
  246. const char* file_path);
  247. void AddSafeResourceUri(PLT_MediaObject* object, NPT_List<NPT_IpAddress> ips, const char* file_path, const NPT_String& info)
  248. {
  249. PLT_MediaItemResource res;
  250. for(NPT_List<NPT_IpAddress>::Iterator ip = ips.GetFirstItem(); ip; ++ip) {
  251. res.m_ProtocolInfo = PLT_ProtocolInfo(info);
  252. res.m_Uri = BuildSafeResourceUri((*ip).ToString(), file_path);
  253. object->m_Resources.Add(res);
  254. }
  255. }
  256. static const char* GetMimeTypeFromExtension(const char* extension, const PLT_HttpRequestContext* context = NULL);
  257. static NPT_String GetMimeType(const CFileItem& item, const PLT_HttpRequestContext* context = NULL);
  258. static NPT_String GetMimeType(const char* filename, const PLT_HttpRequestContext* context = NULL);
  259. static const CStdString& CorrectAllItemsSortHack(const CStdString &item);
  260. private:
  261. PLT_MediaObject* Build(CFileItemPtr item,
  262. bool with_count,
  263. const PLT_HttpRequestContext& context,
  264. const char* parent_id = NULL);
  265. NPT_Result BuildResponse(PLT_ActionReference& action,
  266. CFileItemList& items,
  267. const char* filter,
  268. NPT_UInt32 starting_index,
  269. NPT_UInt32 requested_count,
  270. const NPT_List<NPT_String>& sort_criteria,
  271. const PLT_HttpRequestContext& context,
  272. const char* parent_id /* = NULL */);
  273. // class methods
  274. static NPT_String GetParentFolder(NPT_String file_path) {
  275. int index = file_path.ReverseFind("\\");
  276. if (index == -1) return "";
  277. return file_path.Left(index);
  278. }
  279. static const NPT_String GetProtocolInfo(const CFileItem& item,
  280. const char* protocol,
  281. const PLT_HttpRequestContext* context = NULL);
  282. NPT_Mutex m_FileMutex;
  283. NPT_Map<NPT_String, NPT_String> m_FileMap;
  284. public:
  285. // class members
  286. static NPT_UInt32 m_MaxReturnedItems;
  287. };
  288. NPT_UInt32 CUPnPServer::m_MaxReturnedItems = 0;
  289. /*----------------------------------------------------------------------
  290. | CUPnPServer::BuildSafeResourceUri
  291. +---------------------------------------------------------------------*/
  292. NPT_String CUPnPServer::BuildSafeResourceUri(const char* host,
  293. const char* file_path)
  294. {
  295. CStdString md5;
  296. XBMC::XBMC_MD5 md5state;
  297. md5state.append(file_path);
  298. md5state.getDigest(md5);
  299. md5 += "/" + URIUtils::GetFileName(file_path);
  300. { NPT_AutoLock lock(m_FileMutex);
  301. NPT_CHECK(m_FileMap.Put(md5.c_str(), file_path));
  302. }
  303. return PLT_FileMediaServer::BuildSafeResourceUri(m_FileBaseUri, host, md5);
  304. }
  305. /*----------------------------------------------------------------------
  306. | CUPnPServer::GetMimeType
  307. +---------------------------------------------------------------------*/
  308. NPT_String
  309. CUPnPServer::GetMimeType(const char* filename,
  310. const PLT_HttpRequestContext* context /* = NULL */)
  311. {
  312. NPT_String ext = URIUtils::GetExtension(filename).c_str();
  313. ext.TrimLeft('.');
  314. ext = ext.ToLowercase();
  315. return PLT_MediaObject::GetMimeTypeFromExtension(ext, context);
  316. }
  317. /*----------------------------------------------------------------------
  318. | CUPnPServer::GetMimeType
  319. +---------------------------------------------------------------------*/
  320. NPT_String
  321. CUPnPServer::GetMimeType(const CFileItem& item,
  322. const PLT_HttpRequestContext* context /* = NULL */)
  323. {
  324. CStdString path = item.GetPath();
  325. if (item.HasVideoInfoTag() && !item.GetVideoInfoTag()->GetPath().IsEmpty()) {
  326. path = item.GetVideoInfoTag()->GetPath();
  327. } else if (item.HasMusicInfoTag() && !item.GetMusicInfoTag()->GetURL().IsEmpty()) {
  328. path = item.GetMusicInfoTag()->GetURL();
  329. }
  330. if(path.Left(8).Equals("stack://"))
  331. return "audio/x-mpegurl";
  332. NPT_String ext = URIUtils::GetExtension(path).c_str();
  333. ext.TrimLeft('.');
  334. ext = ext.ToLowercase();
  335. NPT_String mime;
  336. /* We always use Platinum mime type first
  337. as it is defined to map extension to DLNA compliant mime type
  338. or custom according to context (who asked for it) */
  339. if (!ext.IsEmpty()) {
  340. mime = PLT_MediaObject::GetMimeTypeFromExtension(ext, context);
  341. if (mime == "application/octet-stream") mime = "";
  342. }
  343. /* if Platinum couldn't map it, default to XBMC mapping */
  344. if (mime.IsEmpty()) {
  345. NPT_String mime = item.GetMimeType().c_str();
  346. if (mime == "application/octet-stream") mime = "";
  347. }
  348. /* fallback to generic mime type if not found */
  349. if (mime.IsEmpty()) {
  350. if (item.IsVideo() || item.IsVideoDb() )
  351. mime = "video/" + ext;
  352. else if (item.IsAudio() || item.IsMusicDb() )
  353. mime = "audio/" + ext;
  354. else if (item.IsPicture() )
  355. mime = "image/" + ext;
  356. }
  357. /* nothing we can figure out */
  358. if (mime.IsEmpty()) {
  359. mime = "application/octet-stream";
  360. }
  361. return mime;
  362. }
  363. /*----------------------------------------------------------------------
  364. | CUPnPServer::GetProtocolInfo
  365. +---------------------------------------------------------------------*/
  366. const NPT_String
  367. CUPnPServer::GetProtocolInfo(const CFileItem& item,
  368. const char* protocol,
  369. const PLT_HttpRequestContext* context /* = NULL */)
  370. {
  371. NPT_String proto = protocol;
  372. /* fixup the protocol just in case nothing was passed */
  373. if (proto.IsEmpty()) {
  374. proto = item.GetAsUrl().GetProtocol();
  375. }
  376. /*
  377. map protocol to right prefix and use xbmc-get for
  378. unsupported UPnP protocols for other xbmc clients
  379. TODO: add rtsp ?
  380. */
  381. if (proto == "http") {
  382. proto = "http-get";
  383. } else {
  384. proto = "xbmc-get";
  385. }
  386. /* we need a valid extension to retrieve the mimetype for the protocol info */
  387. NPT_String mime = GetMimeType(item, context);
  388. proto += ":*:" + mime + ":" + PLT_MediaObject::GetDlnaExtension(mime, context);
  389. return proto;
  390. }
  391. /*----------------------------------------------------------------------
  392. | CUPnPServer::PopulateObjectFromTag
  393. +---------------------------------------------------------------------*/
  394. NPT_Result
  395. CUPnPServer::PopulateObjectFromTag(CMusicInfoTag& tag,
  396. PLT_MediaObject& object,
  397. NPT_String* file_path, /* = NULL */
  398. PLT_MediaItemResource* resource, /* = NULL */
  399. EClientQuirks quirks)
  400. {
  401. if (!tag.GetURL().IsEmpty() && file_path)
  402. *file_path = tag.GetURL();
  403. std::vector<std::string> genres = tag.GetGenre();
  404. for (unsigned int index = 0; index < genres.size(); index++)
  405. object.m_Affiliation.genre.Add(genres.at(index).c_str());
  406. object.m_Title = tag.GetTitle();
  407. object.m_Affiliation.album = tag.GetAlbum();
  408. for (unsigned int index = 0; index < tag.GetArtist().size(); index++)
  409. {
  410. object.m_People.artists.Add(tag.GetArtist().at(index).c_str());
  411. object.m_People.artists.Add(tag.GetArtist().at(index).c_str(), "Performer");
  412. }
  413. object.m_People.artists.Add(StringUtils::Join(!tag.GetAlbumArtist().empty() ? tag.GetAlbumArtist() : tag.GetArtist(), g_advancedSettings.m_musicItemSeparator).c_str(), "AlbumArtist");
  414. if(tag.GetAlbumArtist().empty())
  415. object.m_Creator = StringUtils::Join(tag.GetArtist(), g_advancedSettings.m_musicItemSeparator);
  416. else
  417. object.m_Creator = StringUtils::Join(tag.GetAlbumArtist(), g_advancedSettings.m_musicItemSeparator);
  418. object.m_MiscInfo.original_track_number = tag.GetTrackNumber();
  419. if(tag.GetDatabaseId() >= 0) {
  420. object.m_ReferenceID = NPT_String::Format("musicdb://4/%i%s", tag.GetDatabaseId(), URIUtils::GetExtension(tag.GetURL()).c_str());
  421. }
  422. if (object.m_ReferenceID == object.m_ObjectID)
  423. object.m_ReferenceID = "";
  424. if (resource) resource->m_Duration = tag.GetDuration();
  425. return NPT_SUCCESS;
  426. }
  427. /*----------------------------------------------------------------------
  428. | CUPnPServer::PopulateObjectFromTag
  429. +---------------------------------------------------------------------*/
  430. NPT_Result
  431. CUPnPServer::PopulateObjectFromTag(CVideoInfoTag& tag,
  432. PLT_MediaObject& object,
  433. NPT_String* file_path, /* = NULL */
  434. PLT_MediaItemResource* resource, /* = NULL */
  435. EClientQuirks quirks)
  436. {
  437. // some usefull buffers
  438. CStdStringArray strings;
  439. if (!tag.m_strFileNameAndPath.IsEmpty() && file_path)
  440. *file_path = tag.m_strFileNameAndPath;
  441. if (tag.m_iDbId != -1 ) {
  442. if (!tag.m_artist.empty()) {
  443. object.m_ObjectClass.type = "object.item.videoItem.musicVideoClip";
  444. object.m_Creator = StringUtils::Join(tag.m_artist, g_advancedSettings.m_videoItemSeparator);
  445. object.m_Title = tag.m_strTitle;
  446. object.m_ReferenceID = NPT_String::Format("videodb://3/2/%i", tag.m_iDbId);
  447. } else if (!tag.m_strShowTitle.IsEmpty()) {
  448. object.m_ObjectClass.type = "object.item.videoItem.videoBroadcast";
  449. object.m_Recorded.program_title = "S" + ("0" + NPT_String::FromInteger(tag.m_iSeason)).Right(2);
  450. object.m_Recorded.program_title += "E" + ("0" + NPT_String::FromInteger(tag.m_iEpisode)).Right(2);
  451. object.m_Recorded.program_title += " : " + tag.m_strTitle;
  452. object.m_Recorded.series_title = tag.m_strShowTitle;
  453. object.m_Recorded.episode_number = tag.m_iSeason * 100 + tag.m_iEpisode;
  454. object.m_Title = object.m_Recorded.series_title + " - " + object.m_Recorded.program_title;
  455. object.m_Date = tag.m_firstAired.GetAsLocalizedDate();
  456. if(tag.m_iSeason != -1)
  457. object.m_ReferenceID = NPT_String::Format("videodb://2/0/%i", tag.m_iDbId);
  458. } else {
  459. object.m_ObjectClass.type = "object.item.videoItem.movie";
  460. object.m_Title = tag.m_strTitle;
  461. object.m_Date = NPT_String::FromInteger(tag.m_iYear) + "-01-01";
  462. object.m_ReferenceID = NPT_String::Format("videodb://1/2/%i", tag.m_iDbId);
  463. }
  464. }
  465. if(quirks & ECLIENTQUIRKS_BASICVIDEOCLASS)
  466. object.m_ObjectClass.type = "object.item.videoItem";
  467. if(object.m_ReferenceID == object.m_ObjectID)
  468. object.m_ReferenceID = "";
  469. for (unsigned int index = 0; index < tag.m_genre.size(); index++)
  470. object.m_Affiliation.genre.Add(tag.m_genre.at(index).c_str());
  471. for(CVideoInfoTag::iCast it = tag.m_cast.begin();it != tag.m_cast.end();it++) {
  472. object.m_People.actors.Add(it->strName.c_str(), it->strRole.c_str());
  473. }
  474. object.m_People.director = StringUtils::Join(tag.m_director, g_advancedSettings.m_videoItemSeparator);
  475. for (unsigned int index = 0; index < tag.m_writingCredits.size(); index++)
  476. object.m_People.authors.Add(tag.m_writingCredits[index].c_str());
  477. object.m_Description.description = tag.m_strTagLine;
  478. object.m_Description.long_description = tag.m_strPlot;
  479. if (resource) resource->m_Duration = tag.m_streamDetails.GetVideoDuration();
  480. if (resource) resource->m_Resolution = NPT_String::FromInteger(tag.m_streamDetails.GetVideoWidth()) + "x" + NPT_String::FromInteger(tag.m_streamDetails.GetVideoHeight());
  481. return NPT_SUCCESS;
  482. }
  483. /*----------------------------------------------------------------------
  484. | CUPnPServer::CorrectAllItemsSortHack
  485. +---------------------------------------------------------------------*/
  486. const CStdString&
  487. CUPnPServer::CorrectAllItemsSortHack(const CStdString &item)
  488. {
  489. // This is required as in order for the "* All Albums" etc. items to sort
  490. // correctly, they must have fake artist/album etc. information generated.
  491. // This looks nasty if we attempt to render it to the GUI, thus this (further)
  492. // workaround
  493. if ((item.size() == 1 && item[0] == 0x01) || (item.size() > 1 && ((unsigned char) item[1]) == 0xff))
  494. return StringUtils::EmptyString;
  495. return item;
  496. }
  497. /*----------------------------------------------------------------------
  498. | CUPnPServer::BuildObject
  499. +---------------------------------------------------------------------*/
  500. PLT_MediaObject*
  501. CUPnPServer::BuildObject(const CFileItem& item,
  502. NPT_String& file_path,
  503. bool with_count,
  504. const PLT_HttpRequestContext* context /* = NULL */,
  505. CUPnPServer* upnp_server /* = NULL */)
  506. {
  507. PLT_MediaItemResource resource;
  508. PLT_MediaObject* object = NULL;
  509. CLog::Log(LOGDEBUG, "Building didl for object '%s'", (const char*)item.GetPath());
  510. EClientQuirks quirks = GetClientQuirks(context);
  511. // get list of ip addresses
  512. NPT_List<NPT_IpAddress> ips;
  513. NPT_CHECK_LABEL(PLT_UPnPMessageHelper::GetIPAddresses(ips), failure);
  514. // if we're passed an interface where we received the request from
  515. // move the ip to the top
  516. if (context && context->GetLocalAddress().GetIpAddress().ToString() != "0.0.0.0") {
  517. ips.Remove(context->GetLocalAddress().GetIpAddress());
  518. ips.Insert(ips.GetFirstItem(), context->GetLocalAddress().GetIpAddress());
  519. }
  520. if (!item.m_bIsFolder) {
  521. object = new PLT_MediaItem();
  522. object->m_ObjectID = item.GetPath();
  523. /* Setup object type */
  524. if (item.IsMusicDb() || item.IsAudio()) {
  525. object->m_ObjectClass.type = "object.item.audioItem.musicTrack";
  526. if (item.HasMusicInfoTag()) {
  527. CMusicInfoTag *tag = (CMusicInfoTag*)item.GetMusicInfoTag();
  528. PopulateObjectFromTag(*tag, *object, &file_path, &resource, quirks);
  529. }
  530. } else if (item.IsVideoDb() || item.IsVideo()) {
  531. object->m_ObjectClass.type = "object.item.videoItem";
  532. if(quirks & ECLIENTQUIRKS_UNKNOWNSERIES)
  533. object->m_Affiliation.album = "[Unknown Series]";
  534. if (item.HasVideoInfoTag()) {
  535. CVideoInfoTag *tag = (CVideoInfoTag*)item.GetVideoInfoTag();
  536. PopulateObjectFromTag(*tag, *object, &file_path, &resource, quirks);
  537. }
  538. } else if (item.IsPicture()) {
  539. object->m_ObjectClass.type = "object.item.imageItem.photo";
  540. } else {
  541. object->m_ObjectClass.type = "object.item";
  542. }
  543. // duration of zero is invalid
  544. if (resource.m_Duration == 0) resource.m_Duration = -1;
  545. // Set the resource file size
  546. resource.m_Size = item.m_dwSize;
  547. if (resource.m_Size == 0) {
  548. struct __stat64 info;
  549. if(CFile::Stat((const char*)file_path, &info) == 0 && info.st_size >= 0)
  550. resource.m_Size = info.st_size;
  551. }
  552. if(resource.m_Size == 0)
  553. resource.m_Size = (NPT_LargeSize)-1;
  554. // set date
  555. if (object->m_Date.IsEmpty() && item.m_dateTime.IsValid()) {
  556. object->m_Date = item.m_dateTime.GetAsLocalizedDate();
  557. }
  558. if (upnp_server) {
  559. upnp_server->AddSafeResourceUri(object, ips, file_path, GetProtocolInfo(item, "http", context));
  560. }
  561. // if the item is remote, add a direct link to the item
  562. if (URIUtils::IsRemote((const char*)file_path)) {
  563. resource.m_ProtocolInfo = PLT_ProtocolInfo(CUPnPServer::GetProtocolInfo(item, item.GetAsUrl().GetProtocol(), context));
  564. resource.m_Uri = file_path;
  565. // if the direct link can be served directly using http, then push it in front
  566. // otherwise keep the xbmc-get resource last and let a compatible client look for it
  567. if (resource.m_ProtocolInfo.ToString().StartsWith("xbmc", true)) {
  568. object->m_Resources.Add(resource);
  569. } else {
  570. object->m_Resources.Insert(object->m_Resources.GetFirstItem(), resource);
  571. }
  572. }
  573. // Some upnp clients expect all audio items to have parent root id 4
  574. #ifdef WMP_ID_MAPPING
  575. object->m_ParentID = "4";
  576. #endif
  577. } else {
  578. PLT_MediaContainer* container = new PLT_MediaContainer;
  579. object = container;
  580. /* Assign a title and id for this container */
  581. container->m_ObjectID = item.GetPath();
  582. container->m_ObjectClass.type = "object.container";
  583. container->m_ChildrenCount = -1;
  584. CStdStringArray strings;
  585. /* this might be overkill, but hey */
  586. if (item.IsMusicDb()) {
  587. MUSICDATABASEDIRECTORY::NODE_TYPE node = CMusicDatabaseDirectory::GetDirectoryType(item.GetPath());
  588. switch(node) {
  589. case MUSICDATABASEDIRECTORY::NODE_TYPE_ARTIST: {
  590. container->m_ObjectClass.type += ".person.musicArtist";
  591. CMusicInfoTag *tag = (CMusicInfoTag*)item.GetMusicInfoTag();
  592. if (tag) {
  593. container->m_People.artists.Add(
  594. CorrectAllItemsSortHack(StringUtils::Join(tag->GetArtist(), g_advancedSettings.m_musicItemSeparator)).c_str(), "Performer");
  595. container->m_People.artists.Add(
  596. CorrectAllItemsSortHack(StringUtils::Join(!tag->GetAlbumArtist().empty() ? tag->GetAlbumArtist() : tag->GetArtist(), g_advancedSettings.m_musicItemSeparator)).c_str(), "AlbumArtist");
  597. }
  598. #ifdef WMP_ID_MAPPING
  599. // Some upnp clients expect all artists to have parent root id 107
  600. container->m_ParentID = "107";
  601. #endif
  602. }
  603. break;
  604. case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM:
  605. case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM_COMPILATIONS:
  606. case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM_RECENTLY_ADDED:
  607. case MUSICDATABASEDIRECTORY::NODE_TYPE_YEAR_ALBUM: {
  608. container->m_ObjectClass.type += ".album.musicAlbum";
  609. // for Sonos to be happy
  610. CMusicInfoTag *tag = (CMusicInfoTag*)item.GetMusicInfoTag();
  611. if (tag) {
  612. container->m_People.artists.Add(
  613. CorrectAllItemsSortHack(StringUtils::Join(tag->GetArtist(), g_advancedSettings.m_musicItemSeparator)).c_str(), "Performer");
  614. container->m_People.artists.Add(
  615. CorrectAllItemsSortHack(StringUtils::Join(!tag->GetAlbumArtist().empty() ? tag->GetAlbumArtist() : tag->GetArtist(), g_advancedSettings.m_musicItemSeparator)).c_str(), "AlbumArtist");
  616. container->m_Affiliation.album = CorrectAllItemsSortHack(tag->GetAlbum()).c_str();
  617. }
  618. #ifdef WMP_ID_MAPPING
  619. // Some upnp clients expect all albums to have parent root id 7
  620. container->m_ParentID = "7";
  621. #endif
  622. }
  623. break;
  624. case MUSICDATABASEDIRECTORY::NODE_TYPE_GENRE:
  625. container->m_ObjectClass.type += ".genre.musicGenre";
  626. break;
  627. default:
  628. break;
  629. }
  630. } else if (item.IsVideoDb()) {
  631. VIDEODATABASEDIRECTORY::NODE_TYPE node = CVideoDatabaseDirectory::GetDirectoryType(item.GetPath());
  632. CVideoInfoTag &tag = *(CVideoInfoTag*)item.GetVideoInfoTag();
  633. switch(node) {
  634. case VIDEODATABASEDIRECTORY::NODE_TYPE_GENRE:
  635. container->m_ObjectClass.type += ".genre.movieGenre";
  636. break;
  637. case VIDEODATABASEDIRECTORY::NODE_TYPE_ACTOR:
  638. container->m_ObjectClass.type += ".person.videoArtist";
  639. container->m_Creator = StringUtils::Join(tag.m_artist, g_advancedSettings.m_videoItemSeparator);
  640. container->m_Title = tag.m_strTitle;
  641. break;
  642. case VIDEODATABASEDIRECTORY::NODE_TYPE_TITLE_TVSHOWS:
  643. container->m_ObjectClass.type += ".album.videoAlbum";
  644. container->m_Recorded.program_title = "S" + ("0" + NPT_String::FromInteger(tag.m_iSeason)).Right(2);
  645. container->m_Recorded.program_title += "E" + ("0" + NPT_String::FromInteger(tag.m_iEpisode)).Right(2);
  646. container->m_Recorded.program_title += " : " + tag.m_strTitle;
  647. container->m_Recorded.series_title = tag.m_strShowTitle;
  648. container->m_Recorded.episode_number = tag.m_iSeason * 100 + tag.m_iEpisode;
  649. container->m_Title = container->m_Recorded.series_title + " - " + container->m_Recorded.program_title;
  650. container->m_Title = tag.m_strTitle;
  651. if(!tag.m_firstAired.IsValid() && tag.m_iYear)
  652. container->m_Date = NPT_String::FromInteger(tag.m_iYear) + "-01-01";
  653. else
  654. container->m_Date = tag.m_firstAired.GetAsLocalizedDate();
  655. for (unsigned int index = 0; index < tag.m_genre.size(); index++)
  656. container->m_Affiliation.genre.Add(tag.m_genre.at(index).c_str());
  657. for(CVideoInfoTag::iCast it = tag.m_cast.begin();it != tag.m_cast.end();it++) {
  658. container->m_People.actors.Add(it->strName.c_str(), it->strRole.c_str());
  659. }
  660. container->m_People.director = StringUtils::Join(tag.m_director, g_advancedSettings.m_videoItemSeparator);;
  661. for (unsigned int index = 0; index < tag.m_writingCredits.size(); index++)
  662. container->m_People.authors.Add(tag.m_writingCredits[index].c_str());
  663. container->m_Description.description = tag.m_strTagLine;
  664. container->m_Description.long_description = tag.m_strPlot;
  665. break;
  666. default:
  667. container->m_ObjectClass.type += ".storageFolder";
  668. break;
  669. }
  670. } else if (item.IsPlayList()) {
  671. container->m_ObjectClass.type += ".playlistContainer";
  672. }
  673. if(quirks & ECLIENTQUIRKS_ONLYSTORAGEFOLDER) {
  674. container->m_ObjectClass.type = "object.container.storageFolder";
  675. }
  676. /* Get the number of children for this container */
  677. if (with_count && upnp_server) {
  678. if (object->m_ObjectID.StartsWith("virtualpath://")) {
  679. NPT_Cardinal count = 0;
  680. NPT_CHECK_LABEL(NPT_File::GetCount(file_path, count), failure);
  681. container->m_ChildrenCount = count;
  682. } else {
  683. /* this should be a standard path */
  684. // TODO - get file count of this directory
  685. }
  686. }
  687. }
  688. // set a title for the object
  689. if (object->m_Title.IsEmpty()) {
  690. if (!item.GetLabel().IsEmpty()) {
  691. CStdString title = item.GetLabel();
  692. if (item.IsPlayList() || !item.m_bIsFolder) URIUtils::RemoveExtension(title);
  693. object->m_Title = title;
  694. } else {
  695. CStdString title, volumeNumber;
  696. CUtil::GetVolumeFromFileName(item.GetPath(), title, volumeNumber);
  697. if (!item.m_bIsFolder) URIUtils::RemoveExtension(title);
  698. object->m_Title = title;
  699. }
  700. }
  701. // set a thumbnail if we have one
  702. if (item.HasThumbnail() && upnp_server) {
  703. object->m_ExtraInfo.album_art_uri = upnp_server->BuildSafeResourceUri(
  704. (*ips.GetFirstItem()).ToString(),
  705. item.GetThumbnailImage());
  706. // Set DLNA profileID by extension, defaulting to JPEG.
  707. NPT_String ext = URIUtils::GetExtension(item.GetThumbnailImage()).c_str();
  708. if (strcmp(ext, ".png") == 0) {
  709. object->m_ExtraInfo.album_art_uri_dlna_profile = "PNG_TN";
  710. } else {
  711. object->m_ExtraInfo.album_art_uri_dlna_profile = "JPEG_TN";
  712. }
  713. }
  714. if (upnp_server) {
  715. if (item.HasProperty("fanart_image")) {
  716. upnp_server->AddSafeResourceUri(object, ips, item.GetProperty("fanart_image").asString().c_str(), "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->SetThumbnailImage(CThumbLoader::GetCachedImage(*item, "thumb"));
  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] == '\"') {

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