2 * UPnP Support for XBMC
3 * Copyright (c) 2006 c0diq (Sylvain Rebaud)
4 * Portions Copyright (c) by the authors of libPlatinum
5 * http://www.plutinosoft.com/blog/category/platinum/
7 * Copyright (C) 2010-2018 Team Kodi
8 * This file is part of Kodi - https://kodi.tv
10 * SPDX-License-Identifier: GPL-2.0-or-later
11 * See LICENSES/README.md for more information.
13 #include "UPnPDirectory.h"
16 #include "FileItemList.h"
17 #include "ServiceBroker.h"
19 #include "network/upnp/UPnP.h"
20 #include "network/upnp/UPnPInternal.h"
21 #include "settings/Settings.h"
22 #include "settings/SettingsComponent.h"
23 #include "utils/StringUtils.h"
24 #include "utils/URIUtils.h"
25 #include "utils/log.h"
27 #include <Platinum/Source/Devices/MediaServer/PltSyncMediaBrowser.h>
28 #include <Platinum/Source/Platinum/Platinum.h>
30 using namespace MUSIC_INFO
;
31 using namespace XFILE
;
37 static std::string
GetContentMapping(NPT_String
& objectClass
)
41 const char* ObjectClass
;
44 static const SClassMapping mapping
[] = {
45 { "object.item.videoItem.videoBroadcast" , "episodes" }
46 , { "object.item.videoItem.musicVideoClip" , "musicvideos" }
47 , { "object.item.videoItem" , "movies" }
48 , { "object.item.audioItem.musicTrack" , "songs" }
49 , { "object.item.audioItem" , "songs" }
50 , { "object.item.imageItem.photo" , "photos" }
51 , { "object.item.imageItem" , "photos" }
52 , { "object.container.album.videoAlbum.videoBroadcastShow" , "tvshows" }
53 , { "object.container.album.videoAlbum.videoBroadcastSeason", "seasons" }
54 , { "object.container.album.musicAlbum" , "albums" }
55 , { "object.container.album.photoAlbum" , "photos" }
56 , { "object.container.album" , "albums" }
57 , { "object.container.person" , "artists" }
60 for(const SClassMapping
* map
= mapping
; map
->ObjectClass
; map
++)
62 if(objectClass
.StartsWith(map
->ObjectClass
, true))
71 static bool FindDeviceWait(CUPnP
* upnp
, const char* uuid
, PLT_DeviceDataReference
& device
)
73 bool client_started
= upnp
->IsClientStarted();
76 // look for device in our list
77 // (and wait for it to respond for 5 secs if we're just starting upnp client)
78 NPT_TimeStamp watchdog
;
79 NPT_System::GetCurrentTimeStamp(watchdog
);
83 if (NPT_SUCCEEDED(upnp
->m_MediaBrowser
->FindServer(uuid
, device
)) && !device
.IsNull())
86 // fail right away if device not found and upnp client was already running
90 // otherwise check if we've waited long enough without success
92 NPT_System::GetCurrentTimeStamp(now
);
96 // sleep a bit and try again
97 NPT_System::Sleep(NPT_TimeInterval((double)1));
100 return !device
.IsNull();
103 /*----------------------------------------------------------------------
104 | CUPnPDirectory::GetFriendlyName
105 +---------------------------------------------------------------------*/
106 std::string
CUPnPDirectory::GetFriendlyName(const CURL
& url
)
108 NPT_String path
= url
.Get().c_str();
109 if (!path
.EndsWith("/")) path
+= "/";
111 if (path
.Left(7).Compare("upnp://", true) != 0) {
113 } else if (path
.Compare("upnp://", true) == 0) {
114 return "UPnP Media Servers (Auto-Discover)";
117 // look for nextslash
118 int next_slash
= path
.Find('/', 7);
119 if (next_slash
== -1)
122 NPT_String uuid
= path
.SubString(7, next_slash
-7);
123 NPT_String object_id
= path
.SubString(next_slash
+1, path
.GetLength()-next_slash
-2);
126 PLT_DeviceDataReference device
;
127 if(!FindDeviceWait(CUPnP::GetInstance(), uuid
, device
))
130 return device
->GetFriendlyName().GetChars();
133 /*----------------------------------------------------------------------
134 | CUPnPDirectory::GetDirectory
135 +---------------------------------------------------------------------*/
136 bool CUPnPDirectory::GetResource(const CURL
& path
, CFileItem
&item
)
138 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_SERVICES_UPNP
))
141 if(!path
.IsProtocol("upnp"))
144 CUPnP
* upnp
= CUPnP::GetInstance();
148 const std::string
& uuid
= path
.GetHostName();
149 std::string object
= path
.GetFileName();
150 StringUtils::TrimRight(object
, "/");
151 object
= CURL::Decode(object
);
153 PLT_DeviceDataReference device
;
154 if(!FindDeviceWait(upnp
, uuid
.c_str(), device
)) {
155 CLog::Log(LOGERROR
, "CUPnPDirectory::GetResource - unable to find uuid {}", uuid
);
159 PLT_MediaObjectListReference list
;
160 if (NPT_FAILED(upnp
->m_MediaBrowser
->BrowseSync(device
, object
.c_str(), list
, true))) {
161 CLog::Log(LOGERROR
, "CUPnPDirectory::GetResource - unable to find object {}", object
);
165 if (list
.IsNull() || !list
->GetItemCount()) {
166 CLog::Log(LOGERROR
, "CUPnPDirectory::GetResource - no items returned for object {}", object
);
170 PLT_MediaObjectList::Iterator entry
= list
->GetFirstItem();
174 return UPNP::GetResource(*entry
, item
);
178 /*----------------------------------------------------------------------
179 | CUPnPDirectory::GetDirectory
180 +---------------------------------------------------------------------*/
182 CUPnPDirectory::GetDirectory(const CURL
& url
, CFileItemList
&items
)
184 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_SERVICES_UPNP
))
187 CUPnP
* upnp
= CUPnP::GetInstance();
189 /* upnp should never be cached, it has internal cache */
190 items
.SetCacheToDisc(CFileItemList::CACHE_NEVER
);
192 // We accept upnp://devuuid/[item_id/]
193 NPT_String path
= url
.Get().c_str();
194 if (!path
.StartsWith("upnp://", true)) {
198 if (path
.Compare("upnp://", true) == 0) {
201 // root -> get list of devices
202 const NPT_Lock
<PLT_DeviceDataReferenceList
>& devices
= upnp
->m_MediaBrowser
->GetMediaServers();
203 NPT_List
<PLT_DeviceDataReference
>::Iterator device
= devices
.GetFirstItem();
205 NPT_String name
= (*device
)->GetFriendlyName();
206 NPT_String uuid
= (*device
)->GetUUID();
208 CFileItemPtr
pItem(new CFileItem((const char*)name
));
209 pItem
->SetPath(std::string((const char*) "upnp://" + uuid
+ "/"));
210 pItem
->m_bIsFolder
= true;
211 pItem
->SetArt("thumb", (const char*)(*device
)->GetIconUrl("image/png"));
218 if (!path
.EndsWith("/")) path
+= "/";
220 // look for nextslash
221 int next_slash
= path
.Find('/', 7);
223 NPT_String uuid
= (next_slash
==-1)?path
.SubString(7):path
.SubString(7, next_slash
-7);
224 NPT_String object_id
= (next_slash
== -1) ? NPT_String("") : path
.SubString(next_slash
+ 1);
225 object_id
.TrimRight("/");
226 if (object_id
.GetLength()) {
227 object_id
= CURL::Decode((char*)object_id
).c_str();
230 // try to find the device with wait on startup
231 PLT_DeviceDataReference device
;
232 if (!FindDeviceWait(upnp
, uuid
, device
))
235 // issue a browse request with object_id
236 // if object_id is empty use "0" for root
237 object_id
= object_id
.IsEmpty() ? NPT_String("0") : object_id
;
239 // remember a count of object classes
240 std::map
<NPT_String
, int> classes
;
242 // just a guess as to what types of files we want
246 StringUtils::TrimLeft(m_strFileMask
, "/");
247 if (!m_strFileMask
.empty()) {
248 video
= m_strFileMask
.find(".wmv") != std::string::npos
;
249 audio
= m_strFileMask
.find(".wma") != std::string::npos
;
250 image
= m_strFileMask
.find(".jpg") != std::string::npos
;
253 // special case for Windows Media Connect and WMP11 when looking for root
254 // We can target which root subfolder we want based on directory mask
255 if (object_id
== "0" && ((device
->GetFriendlyName().Find("Windows Media Connect", 0, true) >= 0) ||
256 (device
->m_ModelName
== "Windows Media Player Sharing"))) {
258 // look for a specific type to differentiate which folder we want
259 if (audio
&& !video
&& !image
) {
262 } else if (!audio
&& video
&& !image
) {
265 } else if (!audio
&& !video
&& image
) {
271 #ifdef DISABLE_SPECIALCASE
272 // same thing but special case for XBMC
273 if (object_id
== "0" && ((device
->m_ModelName
.Find("XBMC", 0, true) >= 0) ||
274 (device
->m_ModelName
.Find("Xbox Media Center", 0, true) >= 0))) {
275 // look for a specific type to differentiate which folder we want
276 if (audio
&& !video
&& !image
) {
278 object_id
= "virtualpath://upnpmusic";
279 } else if (!audio
&& video
&& !image
) {
281 object_id
= "virtualpath://upnpvideo";
282 } else if (!audio
&& !video
&& image
) {
284 object_id
= "virtualpath://upnppictures";
289 // if error, return now, the device could have gone away
290 // this will make us go back to the sources list
291 PLT_MediaObjectListReference list
;
292 NPT_Result res
= upnp
->m_MediaBrowser
->BrowseSync(device
, object_id
, list
);
293 if (NPT_FAILED(res
)) goto failure
;
296 if (list
.IsNull()) goto cleanup
;
298 PLT_MediaObjectList::Iterator entry
= list
->GetFirstItem();
300 // disregard items with wrong class/type
301 if( (!video
&& (*entry
)->m_ObjectClass
.type
.CompareN("object.item.videoitem", 21,true) == 0)
302 || (!audio
&& (*entry
)->m_ObjectClass
.type
.CompareN("object.item.audioitem", 21,true) == 0)
303 || (!image
&& (*entry
)->m_ObjectClass
.type
.CompareN("object.item.imageitem", 21,true) == 0) )
309 // keep count of classes
310 classes
[(*entry
)->m_ObjectClass
.type
]++;
311 CFileItemPtr pItem
= BuildObject(*entry
, UPnPClient
);
318 if ((*entry
)->m_ReferenceID
.IsEmpty())
319 id
= (const char*) (*entry
)->m_ObjectID
;
321 id
= (const char*) (*entry
)->m_ReferenceID
;
323 id
= CURL::Encode(id
);
324 URIUtils::AddSlashAtEnd(id
);
325 pItem
->SetPath(std::string((const char*) "upnp://" + uuid
+ "/" + id
.c_str()));
332 NPT_String max_string
= "";
334 for (auto& it
: classes
)
336 if (it
.second
> max_count
)
338 max_string
= it
.first
;
339 max_count
= it
.second
;
342 std::string content
= GetContentMapping(max_string
);
343 items
.SetContent(content
);
344 if (content
== "unknown")
346 items
.AddSortMethod(SortByNone
, 571, LABEL_MASKS("%L", "%I", "%L", ""));
347 items
.AddSortMethod(SortByLabel
, SortAttributeIgnoreFolders
, 551, LABEL_MASKS("%L", "%I", "%L", ""));
348 items
.AddSortMethod(SortBySize
, 553, LABEL_MASKS("%L", "%I", "%L", "%I"));
349 items
.AddSortMethod(SortByDate
, 552, LABEL_MASKS("%L", "%J", "%L", "%J"));
361 bool CUPnPDirectory::Resolve(CFileItem
& item
) const
363 return GetResource(item
.GetURL(), item
);