summaryrefslogtreecommitdiffstats
path: root/xbmc/filesystem/UPnPDirectory.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/filesystem/UPnPDirectory.cpp')
-rw-r--r--xbmc/filesystem/UPnPDirectory.cpp358
1 files changed, 358 insertions, 0 deletions
diff --git a/xbmc/filesystem/UPnPDirectory.cpp b/xbmc/filesystem/UPnPDirectory.cpp
new file mode 100644
index 0000000..b93b6bf
--- /dev/null
+++ b/xbmc/filesystem/UPnPDirectory.cpp
@@ -0,0 +1,358 @@
+/*
+ * UPnP Support for XBMC
+ * Copyright (c) 2006 c0diq (Sylvain Rebaud)
+ * Portions Copyright (c) by the authors of libPlatinum
+ * http://www.plutinosoft.com/blog/category/platinum/
+ *
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+#include "UPnPDirectory.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "network/upnp/UPnP.h"
+#include "network/upnp/UPnPInternal.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#include <Platinum/Source/Devices/MediaServer/PltSyncMediaBrowser.h>
+#include <Platinum/Source/Platinum/Platinum.h>
+
+using namespace MUSIC_INFO;
+using namespace XFILE;
+using namespace UPNP;
+
+namespace XFILE
+{
+
+static std::string GetContentMapping(NPT_String& objectClass)
+{
+ struct SClassMapping
+ {
+ const char* ObjectClass;
+ const char* Content;
+ };
+ static const SClassMapping mapping[] = {
+ { "object.item.videoItem.videoBroadcast" , "episodes" }
+ , { "object.item.videoItem.musicVideoClip" , "musicvideos" }
+ , { "object.item.videoItem" , "movies" }
+ , { "object.item.audioItem.musicTrack" , "songs" }
+ , { "object.item.audioItem" , "songs" }
+ , { "object.item.imageItem.photo" , "photos" }
+ , { "object.item.imageItem" , "photos" }
+ , { "object.container.album.videoAlbum.videoBroadcastShow" , "tvshows" }
+ , { "object.container.album.videoAlbum.videoBroadcastSeason", "seasons" }
+ , { "object.container.album.musicAlbum" , "albums" }
+ , { "object.container.album.photoAlbum" , "photos" }
+ , { "object.container.album" , "albums" }
+ , { "object.container.person" , "artists" }
+ , { NULL , NULL }
+ };
+ for(const SClassMapping* map = mapping; map->ObjectClass; map++)
+ {
+ if(objectClass.StartsWith(map->ObjectClass, true))
+ {
+ return map->Content;
+ break;
+ }
+ }
+ return "unknown";
+}
+
+static bool FindDeviceWait(CUPnP* upnp, const char* uuid, PLT_DeviceDataReference& device)
+{
+ bool client_started = upnp->IsClientStarted();
+ upnp->StartClient();
+
+ // look for device in our list
+ // (and wait for it to respond for 5 secs if we're just starting upnp client)
+ NPT_TimeStamp watchdog;
+ NPT_System::GetCurrentTimeStamp(watchdog);
+ watchdog += 5.0;
+
+ for (;;) {
+ if (NPT_SUCCEEDED(upnp->m_MediaBrowser->FindServer(uuid, device)) && !device.IsNull())
+ break;
+
+ // fail right away if device not found and upnp client was already running
+ if (client_started)
+ return false;
+
+ // otherwise check if we've waited long enough without success
+ NPT_TimeStamp now;
+ NPT_System::GetCurrentTimeStamp(now);
+ if (now > watchdog)
+ return false;
+
+ // sleep a bit and try again
+ NPT_System::Sleep(NPT_TimeInterval((double)1));
+ }
+
+ return !device.IsNull();
+}
+
+/*----------------------------------------------------------------------
+| CUPnPDirectory::GetFriendlyName
++---------------------------------------------------------------------*/
+std::string CUPnPDirectory::GetFriendlyName(const CURL& url)
+{
+ NPT_String path = url.Get().c_str();
+ if (!path.EndsWith("/")) path += "/";
+
+ if (path.Left(7).Compare("upnp://", true) != 0) {
+ return {};
+ } else if (path.Compare("upnp://", true) == 0) {
+ return "UPnP Media Servers (Auto-Discover)";
+ }
+
+ // look for nextslash
+ int next_slash = path.Find('/', 7);
+ if (next_slash == -1)
+ return {};
+
+ NPT_String uuid = path.SubString(7, next_slash-7);
+ NPT_String object_id = path.SubString(next_slash+1, path.GetLength()-next_slash-2);
+
+ // look for device
+ PLT_DeviceDataReference device;
+ if(!FindDeviceWait(CUPnP::GetInstance(), uuid, device))
+ return {};
+
+ return device->GetFriendlyName().GetChars();
+}
+
+/*----------------------------------------------------------------------
+| CUPnPDirectory::GetDirectory
++---------------------------------------------------------------------*/
+bool CUPnPDirectory::GetResource(const CURL& path, CFileItem &item)
+{
+ if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_SERVICES_UPNP))
+ return false;
+
+ if(!path.IsProtocol("upnp"))
+ return false;
+
+ CUPnP* upnp = CUPnP::GetInstance();
+ if(!upnp)
+ return false;
+
+ const std::string& uuid = path.GetHostName();
+ std::string object = path.GetFileName();
+ StringUtils::TrimRight(object, "/");
+ object = CURL::Decode(object);
+
+ PLT_DeviceDataReference device;
+ if(!FindDeviceWait(upnp, uuid.c_str(), device)) {
+ CLog::Log(LOGERROR, "CUPnPDirectory::GetResource - unable to find uuid {}", uuid);
+ return false;
+ }
+
+ PLT_MediaObjectListReference list;
+ if (NPT_FAILED(upnp->m_MediaBrowser->BrowseSync(device, object.c_str(), list, true))) {
+ CLog::Log(LOGERROR, "CUPnPDirectory::GetResource - unable to find object {}", object);
+ return false;
+ }
+
+ if (list.IsNull() || !list->GetItemCount()) {
+ CLog::Log(LOGERROR, "CUPnPDirectory::GetResource - no items returned for object {}", object);
+ return false;
+ }
+
+ PLT_MediaObjectList::Iterator entry = list->GetFirstItem();
+ if (entry == 0)
+ return false;
+
+ return UPNP::GetResource(*entry, item);
+}
+
+
+/*----------------------------------------------------------------------
+| CUPnPDirectory::GetDirectory
++---------------------------------------------------------------------*/
+bool
+CUPnPDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_SERVICES_UPNP))
+ return false;
+
+ CUPnP* upnp = CUPnP::GetInstance();
+
+ /* upnp should never be cached, it has internal cache */
+ items.SetCacheToDisc(CFileItemList::CACHE_NEVER);
+
+ // We accept upnp://devuuid/[item_id/]
+ NPT_String path = url.Get().c_str();
+ if (!path.StartsWith("upnp://", true)) {
+ return false;
+ }
+
+ if (path.Compare("upnp://", true) == 0) {
+ upnp->StartClient();
+
+ // root -> get list of devices
+ const NPT_Lock<PLT_DeviceDataReferenceList>& devices = upnp->m_MediaBrowser->GetMediaServers();
+ NPT_List<PLT_DeviceDataReference>::Iterator device = devices.GetFirstItem();
+ while (device) {
+ NPT_String name = (*device)->GetFriendlyName();
+ NPT_String uuid = (*device)->GetUUID();
+
+ CFileItemPtr pItem(new CFileItem((const char*)name));
+ pItem->SetPath(std::string((const char*) "upnp://" + uuid + "/"));
+ pItem->m_bIsFolder = true;
+ pItem->SetArt("thumb", (const char*)(*device)->GetIconUrl("image/png"));
+
+ items.Add(pItem);
+
+ ++device;
+ }
+ } else {
+ if (!path.EndsWith("/")) path += "/";
+
+ // look for nextslash
+ int next_slash = path.Find('/', 7);
+
+ NPT_String uuid = (next_slash==-1)?path.SubString(7):path.SubString(7, next_slash-7);
+ NPT_String object_id = (next_slash == -1) ? NPT_String("") : path.SubString(next_slash + 1);
+ object_id.TrimRight("/");
+ if (object_id.GetLength()) {
+ object_id = CURL::Decode((char*)object_id).c_str();
+ }
+
+ // try to find the device with wait on startup
+ PLT_DeviceDataReference device;
+ if (!FindDeviceWait(upnp, uuid, device))
+ goto failure;
+
+ // issue a browse request with object_id
+ // if object_id is empty use "0" for root
+ object_id = object_id.IsEmpty() ? NPT_String("0") : object_id;
+
+ // remember a count of object classes
+ std::map<NPT_String, int> classes;
+
+ // just a guess as to what types of files we want
+ bool video = true;
+ bool audio = true;
+ bool image = true;
+ StringUtils::TrimLeft(m_strFileMask, "/");
+ if (!m_strFileMask.empty()) {
+ video = m_strFileMask.find(".wmv") != std::string::npos;
+ audio = m_strFileMask.find(".wma") != std::string::npos;
+ image = m_strFileMask.find(".jpg") != std::string::npos;
+ }
+
+ // special case for Windows Media Connect and WMP11 when looking for root
+ // We can target which root subfolder we want based on directory mask
+ if (object_id == "0" && ((device->GetFriendlyName().Find("Windows Media Connect", 0, true) >= 0) ||
+ (device->m_ModelName == "Windows Media Player Sharing"))) {
+
+ // look for a specific type to differentiate which folder we want
+ if (audio && !video && !image) {
+ // music
+ object_id = "1";
+ } else if (!audio && video && !image) {
+ // video
+ object_id = "2";
+ } else if (!audio && !video && image) {
+ // pictures
+ object_id = "3";
+ }
+ }
+
+#ifdef DISABLE_SPECIALCASE
+ // same thing but special case for XBMC
+ if (object_id == "0" && ((device->m_ModelName.Find("XBMC", 0, true) >= 0) ||
+ (device->m_ModelName.Find("Xbox Media Center", 0, true) >= 0))) {
+ // look for a specific type to differentiate which folder we want
+ if (audio && !video && !image) {
+ // music
+ object_id = "virtualpath://upnpmusic";
+ } else if (!audio && video && !image) {
+ // video
+ object_id = "virtualpath://upnpvideo";
+ } else if (!audio && !video && image) {
+ // pictures
+ object_id = "virtualpath://upnppictures";
+ }
+ }
+#endif
+
+ // if error, return now, the device could have gone away
+ // this will make us go back to the sources list
+ PLT_MediaObjectListReference list;
+ NPT_Result res = upnp->m_MediaBrowser->BrowseSync(device, object_id, list);
+ if (NPT_FAILED(res)) goto failure;
+
+ // empty list is ok
+ if (list.IsNull()) goto cleanup;
+
+ PLT_MediaObjectList::Iterator entry = list->GetFirstItem();
+ while (entry) {
+ // disregard items with wrong class/type
+ if( (!video && (*entry)->m_ObjectClass.type.CompareN("object.item.videoitem", 21,true) == 0)
+ || (!audio && (*entry)->m_ObjectClass.type.CompareN("object.item.audioitem", 21,true) == 0)
+ || (!image && (*entry)->m_ObjectClass.type.CompareN("object.item.imageitem", 21,true) == 0) )
+ {
+ ++entry;
+ continue;
+ }
+
+ // keep count of classes
+ classes[(*entry)->m_ObjectClass.type]++;
+ CFileItemPtr pItem = BuildObject(*entry, UPnPClient);
+ if(!pItem) {
+ ++entry;
+ continue;
+ }
+
+ std::string id;
+ if ((*entry)->m_ReferenceID.IsEmpty())
+ id = (const char*) (*entry)->m_ObjectID;
+ else
+ id = (const char*) (*entry)->m_ReferenceID;
+
+ id = CURL::Encode(id);
+ URIUtils::AddSlashAtEnd(id);
+ pItem->SetPath(std::string((const char*) "upnp://" + uuid + "/" + id.c_str()));
+
+ items.Add(pItem);
+
+ ++entry;
+ }
+
+ NPT_String max_string = "";
+ int max_count = 0;
+ for (auto& it : classes)
+ {
+ if (it.second > max_count)
+ {
+ max_string = it.first;
+ max_count = it.second;
+ }
+ }
+ std::string content = GetContentMapping(max_string);
+ items.SetContent(content);
+ if (content == "unknown")
+ {
+ items.AddSortMethod(SortByNone, 571, LABEL_MASKS("%L", "%I", "%L", ""));
+ items.AddSortMethod(SortByLabel, SortAttributeIgnoreFolders, 551, LABEL_MASKS("%L", "%I", "%L", ""));
+ items.AddSortMethod(SortBySize, 553, LABEL_MASKS("%L", "%I", "%L", "%I"));
+ items.AddSortMethod(SortByDate, 552, LABEL_MASKS("%L", "%J", "%L", "%J"));
+ }
+ }
+
+cleanup:
+ return true;
+
+failure:
+ return false;
+}
+}