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-2013 Team XBMC
10 * This Program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2, or (at your option)
15 * This Program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with XBMC; see the file COPYING. If not, see
22 * <http://www.gnu.org/licenses/>.
25 #include "UPnPDirectory.h"
27 #include "network/upnp/UPnP.h"
28 #include "network/upnp/UPnPInternal.h"
30 #include "PltSyncMediaBrowser.h"
31 #include "video/VideoInfoTag.h"
33 #include "utils/log.h"
34 #include "utils/URIUtils.h"
35 #include "utils/StringUtils.h"
37 using namespace MUSIC_INFO;
38 using namespace XFILE;
44 static CStdString GetContentMapping(NPT_String& objectClass)
48 const char* ObjectClass;
51 static const SClassMapping mapping[] = {
52 { "object.item.videoItem.videoBroadcast", "episodes" }
53 , { "object.item.videoItem.musicVideoClip", "musicvideos" }
54 , { "object.item.videoItem" , "movies" }
55 , { "object.item.audioItem.musicTrack" , "songs" }
56 , { "object.item.audioItem" , "songs" }
57 , { "object.item.imageItem.photo" , "photos" }
58 , { "object.item.imageItem" , "photos" }
59 , { "object.container.album.videoAlbum" , "tvshows" }
60 , { "object.container.album.musicAlbum" , "albums" }
61 , { "object.container.album.photoAlbum" , "photos" }
62 , { "object.container.album" , "albums" }
63 , { "object.container.person" , "artists" }
66 for(const SClassMapping* map = mapping; map->ObjectClass; map++)
68 if(objectClass.StartsWith(map->ObjectClass, true))
77 static bool FindDeviceWait(CUPnP* upnp, const char* uuid, PLT_DeviceDataReference& device)
79 bool client_started = upnp->IsClientStarted();
82 // look for device in our list
83 // (and wait for it to respond for 5 secs if we're just starting upnp client)
84 NPT_TimeStamp watchdog;
85 NPT_System::GetCurrentTimeStamp(watchdog);
89 if (NPT_SUCCEEDED(upnp->m_MediaBrowser->FindServer(uuid, device)) && !device.IsNull())
92 // fail right away if device not found and upnp client was already running
96 // otherwise check if we've waited long enough without success
98 NPT_System::GetCurrentTimeStamp(now);
102 // sleep a bit and try again
103 NPT_System::Sleep(NPT_TimeInterval((double)1));
106 return !device.IsNull();
109 /*----------------------------------------------------------------------
110 | CUPnPDirectory::GetFriendlyName
111 +---------------------------------------------------------------------*/
113 CUPnPDirectory::GetFriendlyName(const char* url)
115 NPT_String path = url;
116 if (!path.EndsWith("/")) path += "/";
118 if (path.Left(7).Compare("upnp://", true) != 0) {
120 } else if (path.Compare("upnp://", true) == 0) {
121 return "UPnP Media Servers (Auto-Discover)";
124 // look for nextslash
125 int next_slash = path.Find('/', 7);
126 if (next_slash == -1)
129 NPT_String uuid = path.SubString(7, next_slash-7);
130 NPT_String object_id = path.SubString(next_slash+1, path.GetLength()-next_slash-2);
133 PLT_DeviceDataReference device;
134 if(!FindDeviceWait(CUPnP::GetInstance(), uuid, device))
137 return (const char*)device->GetFriendlyName();
140 /*----------------------------------------------------------------------
141 | CUPnPDirectory::GetDirectory
142 +---------------------------------------------------------------------*/
143 bool CUPnPDirectory::GetResource(const CURL& path, CFileItem &item)
145 if(path.GetProtocol() != "upnp")
148 CUPnP* upnp = CUPnP::GetInstance();
152 CStdString uuid = path.GetHostName();
153 CStdString object = path.GetFileName();
154 StringUtils::TrimRight(object, "/");
155 object = CURL::Decode(object);
157 PLT_DeviceDataReference device;
158 if(!FindDeviceWait(upnp, uuid.c_str(), device)) {
159 CLog::Log(LOGERROR, "CUPnPDirectory::GetResource - unable to find uuid %s", uuid.c_str());
163 PLT_MediaObjectListReference list;
164 if (NPT_FAILED(upnp->m_MediaBrowser->BrowseSync(device, object.c_str(), list, true))) {
165 CLog::Log(LOGERROR, "CUPnPDirectory::GetResource - unable to find object %s", object.c_str());
169 if (list.IsNull() || !list->GetItemCount()) {
170 CLog::Log(LOGERROR, "CUPnPDirectory::GetResource - no items returned for object %s", object.c_str());
174 PLT_MediaObjectList::Iterator entry = list->GetFirstItem();
178 return UPNP::GetResource(*entry, item);
182 /*----------------------------------------------------------------------
183 | CUPnPDirectory::GetDirectory
184 +---------------------------------------------------------------------*/
186 CUPnPDirectory::GetDirectory(const CStdString& strPath, CFileItemList &items)
188 CUPnP* upnp = CUPnP::GetInstance();
190 /* upnp should never be cached, it has internal cache */
191 items.SetCacheToDisc(CFileItemList::CACHE_NEVER);
193 // We accept upnp://devuuid/[item_id/]
194 NPT_String path = strPath.c_str();
195 if (!path.StartsWith("upnp://", true)) {
199 if (path.Compare("upnp://", true) == 0) {
202 // root -> get list of devices
203 const NPT_Lock<PLT_DeviceDataReferenceList>& devices = upnp->m_MediaBrowser->GetMediaServers();
204 NPT_List<PLT_DeviceDataReference>::Iterator device = devices.GetFirstItem();
206 NPT_String name = (*device)->GetFriendlyName();
207 NPT_String uuid = (*device)->GetUUID();
209 CFileItemPtr pItem(new CFileItem((const char*)name));
210 pItem->SetPath(CStdString((const char*) "upnp://" + uuid + "/"));
211 pItem->m_bIsFolder = true;
212 pItem->SetArt("thumb", (const char*)(*device)->GetIconUrl("image/png"));
219 if (!path.EndsWith("/")) path += "/";
221 // look for nextslash
222 int next_slash = path.Find('/', 7);
224 NPT_String uuid = (next_slash==-1)?path.SubString(7):path.SubString(7, next_slash-7);
225 NPT_String object_id = (next_slash==-1)?"":path.SubString(next_slash+1);
226 object_id.TrimRight("/");
227 if (object_id.GetLength()) {
228 object_id = CStdString(CURL::Decode((char*)object_id));
231 // try to find the device with wait on startup
232 PLT_DeviceDataReference device;
233 if (!FindDeviceWait(upnp, uuid, device))
236 // issue a browse request with object_id
237 // if object_id is empty use "0" for root
238 object_id = object_id.IsEmpty()?"0":object_id;
240 // remember a count of object classes
241 std::map<NPT_String, int> classes;
243 // just a guess as to what types of files we want
247 StringUtils::TrimLeft(m_strFileMask, "/");
248 if (!m_strFileMask.empty()) {
249 video = m_strFileMask.find(".wmv") != std::string::npos;
250 audio = m_strFileMask.find(".wma") != std::string::npos;
251 image = m_strFileMask.find(".jpg") != std::string::npos;
254 // special case for Windows Media Connect and WMP11 when looking for root
255 // We can target which root subfolder we want based on directory mask
256 if (object_id == "0" && ((device->GetFriendlyName().Find("Windows Media Connect", 0, true) >= 0) ||
257 (device->m_ModelName == "Windows Media Player Sharing"))) {
259 // look for a specific type to differentiate which folder we want
260 if (audio && !video && !image) {
263 } else if (!audio && video && !image) {
266 } else if (!audio && !video && image) {
272 #ifdef DISABLE_SPECIALCASE
273 // same thing but special case for XBMC
274 if (object_id == "0" && ((device->m_ModelName.Find("XBMC", 0, true) >= 0) ||
275 (device->m_ModelName.Find("Xbox Media Center", 0, true) >= 0))) {
276 // look for a specific type to differentiate which folder we want
277 if (audio && !video && !image) {
279 object_id = "virtualpath://upnpmusic";
280 } else if (!audio && video && !image) {
282 object_id = "virtualpath://upnpvideo";
283 } else if (!audio && !video && image) {
285 object_id = "virtualpath://upnppictures";
290 // if error, return now, the device could have gone away
291 // this will make us go back to the sources list
292 PLT_MediaObjectListReference list;
293 NPT_Result res = upnp->m_MediaBrowser->BrowseSync(device, object_id, list);
294 if (NPT_FAILED(res)) goto failure;
297 if (list.IsNull()) goto cleanup;
299 PLT_MediaObjectList::Iterator entry = list->GetFirstItem();
301 // disregard items with wrong class/type
302 if( (!video && (*entry)->m_ObjectClass.type.CompareN("object.item.videoitem", 21,true) == 0)
303 || (!audio && (*entry)->m_ObjectClass.type.CompareN("object.item.audioitem", 21,true) == 0)
304 || (!image && (*entry)->m_ObjectClass.type.CompareN("object.item.imageitem", 21,true) == 0) )
310 // never show empty containers in media views
311 if((*entry)->IsContainer()) {
312 if( (audio || video || image)
313 && ((PLT_MediaContainer*)(*entry))->m_ChildrenCount == 0) {
320 // keep count of classes
321 classes[(*entry)->m_ObjectClass.type]++;
322 CFileItemPtr pItem = BuildObject(*entry);
329 if ((*entry)->m_ReferenceID.IsEmpty())
330 id = (const char*) (*entry)->m_ObjectID;
332 id = (const char*) (*entry)->m_ReferenceID;
334 id = CURL::Encode(id);
335 URIUtils::AddSlashAtEnd(id);
336 pItem->SetPath(CStdString((const char*) "upnp://" + uuid + "/" + id.c_str()));
343 NPT_String max_string = "";
345 for(std::map<NPT_String, int>::iterator it = classes.begin(); it != classes.end(); it++)
347 if(it->second > max_count)
349 max_string = it->first;
350 max_count = it->second;
353 std::string content = GetContentMapping(max_string);
354 items.SetContent(content);
355 if (content == "unknown")
357 items.AddSortMethod(SortByNone, 571, LABEL_MASKS("%L", "%I", "%L", ""));
358 items.AddSortMethod(SortByLabel, SortAttributeIgnoreFolders, 551, LABEL_MASKS("%L", "%I", "%L", ""));
359 items.AddSortMethod(SortBySize, 553, LABEL_MASKS("%L", "%I", "%L", "%I"));
360 items.AddSortMethod(SortByDate, 552, LABEL_MASKS("%L", "%J", "%L", "%J"));