summaryrefslogtreecommitdiffstats
path: root/xbmc/network/upnp/UPnP.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
commitc04dcc2e7d834218ef2d4194331e383402495ae1 (patch)
tree7333e38d10d75386e60f336b80c2443c1166031d /xbmc/network/upnp/UPnP.cpp
parentInitial commit. (diff)
downloadkodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz
kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/network/upnp/UPnP.cpp')
-rw-r--r--xbmc/network/upnp/UPnP.cpp891
1 files changed, 891 insertions, 0 deletions
diff --git a/xbmc/network/upnp/UPnP.cpp b/xbmc/network/upnp/UPnP.cpp
new file mode 100644
index 0000000..0e6b689
--- /dev/null
+++ b/xbmc/network/upnp/UPnP.cpp
@@ -0,0 +1,891 @@
+/*
+ * 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) 2006-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 "UPnP.h"
+
+#include "FileItem.h"
+#include "GUIUserMessages.h"
+#include "ServiceBroker.h"
+#include "UPnPInternal.h"
+#include "UPnPRenderer.h"
+#include "UPnPServer.h"
+#include "UPnPSettings.h"
+#include "URL.h"
+#include "cores/playercorefactory/PlayerCoreFactory.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "messaging/ApplicationMessenger.h"
+#include "network/Network.h"
+#include "profiles/ProfileManager.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/SystemInfo.h"
+#include "utils/TimeUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+#include "video/VideoInfoTag.h"
+
+#include <memory>
+#include <mutex>
+#include <set>
+
+#include <Platinum/Source/Platinum/Platinum.h>
+
+using namespace UPNP;
+using namespace KODI::MESSAGING;
+
+#define UPNP_DEFAULT_MAX_RETURNED_ITEMS 200
+#define UPNP_DEFAULT_MIN_RETURNED_ITEMS 30
+
+/*
+# Play speed
+# 1 normal
+# 0 invalid
+DLNA_ORG_PS = 'DLNA.ORG_PS'
+DLNA_ORG_PS_VAL = '1'
+
+# Conversion Indicator
+# 1 transcoded
+# 0 not transcoded
+DLNA_ORG_CI = 'DLNA.ORG_CI'
+DLNA_ORG_CI_VAL = '0'
+
+# Operations
+# 00 not time seek range, not range
+# 01 range supported
+# 10 time seek range supported
+# 11 both supported
+DLNA_ORG_OP = 'DLNA.ORG_OP'
+DLNA_ORG_OP_VAL = '01'
+
+# Flags
+# senderPaced 80000000 31
+# lsopTimeBasedSeekSupported 40000000 30
+# lsopByteBasedSeekSupported 20000000 29
+# playcontainerSupported 10000000 28
+# s0IncreasingSupported 08000000 27
+# sNIncreasingSupported 04000000 26
+# rtspPauseSupported 02000000 25
+# streamingTransferModeSupported 01000000 24
+# interactiveTransferModeSupported 00800000 23
+# backgroundTransferModeSupported 00400000 22
+# connectionStallingSupported 00200000 21
+# dlnaVersion15Supported 00100000 20
+DLNA_ORG_FLAGS = 'DLNA.ORG_FLAGS'
+DLNA_ORG_FLAGS_VAL = '01500000000000000000000000000000'
+*/
+
+/*----------------------------------------------------------------------
+| NPT_Console::Output
++---------------------------------------------------------------------*/
+void
+NPT_Console::Output(const char* msg) { }
+
+spdlog::level::level_enum ConvertLogLevel(int nptLogLevel)
+{
+ if (nptLogLevel >= NPT_LOG_LEVEL_FATAL)
+ return spdlog::level::critical;
+ if (nptLogLevel >= NPT_LOG_LEVEL_SEVERE)
+ return spdlog::level::err;
+ if (nptLogLevel >= NPT_LOG_LEVEL_WARNING)
+ return spdlog::level::warn;
+ if (nptLogLevel >= NPT_LOG_LEVEL_FINE)
+ return spdlog::level::info;
+ if (nptLogLevel >= NPT_LOG_LEVEL_FINER)
+ return spdlog::level::debug;
+
+ return spdlog::level::trace;
+}
+
+void
+UPnPLogger(const NPT_LogRecord* record)
+{
+ static Logger logger = CServiceBroker::GetLogging().GetLogger("Platinum");
+ if (CServiceBroker::GetLogging().CanLogComponent(LOGUPNP))
+ logger->log(ConvertLogLevel(record->m_Level), "[{}]: {}", record->m_LoggerName,
+ record->m_Message);
+}
+
+namespace UPNP
+{
+
+/*----------------------------------------------------------------------
+| static
++---------------------------------------------------------------------*/
+CUPnP* CUPnP::upnp = NULL;
+static NPT_List<void*> g_UserData;
+static NPT_Mutex g_UserDataLock;
+
+/*----------------------------------------------------------------------
+| CDeviceHostReferenceHolder class
++---------------------------------------------------------------------*/
+class CDeviceHostReferenceHolder
+{
+public:
+ PLT_DeviceHostReference m_Device;
+};
+
+/*----------------------------------------------------------------------
+| CCtrlPointReferenceHolder class
++---------------------------------------------------------------------*/
+class CCtrlPointReferenceHolder
+{
+public:
+ PLT_CtrlPointReference m_CtrlPoint;
+};
+
+/*----------------------------------------------------------------------
+| CUPnPCleaner class
++---------------------------------------------------------------------*/
+class CUPnPCleaner : public NPT_Thread
+{
+public:
+ explicit CUPnPCleaner(CUPnP* upnp) : NPT_Thread(true), m_UPnP(upnp) {}
+ void Run() override {
+ delete m_UPnP;
+ }
+
+ CUPnP* m_UPnP;
+};
+
+/*----------------------------------------------------------------------
+| CMediaBrowser class
++---------------------------------------------------------------------*/
+class CMediaBrowser : public PLT_SyncMediaBrowser, public PLT_MediaContainerChangesListener
+{
+public:
+ explicit CMediaBrowser(PLT_CtrlPointReference& ctrlPoint)
+ : PLT_SyncMediaBrowser(ctrlPoint, true),
+ m_logger(CServiceBroker::GetLogging().GetLogger("UPNP::CMediaBrowser"))
+ {
+ SetContainerListener(this);
+ }
+
+ // PLT_MediaBrowser methods
+ bool OnMSAdded(PLT_DeviceDataReference& device) override
+ {
+ CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH);
+ message.SetStringParam("upnp://");
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message);
+
+ return PLT_SyncMediaBrowser::OnMSAdded(device);
+ }
+ void OnMSRemoved(PLT_DeviceDataReference& device) override
+ {
+ PLT_SyncMediaBrowser::OnMSRemoved(device);
+
+ CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH);
+ message.SetStringParam("upnp://");
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message);
+
+ PLT_SyncMediaBrowser::OnMSRemoved(device);
+ }
+
+ // PLT_MediaContainerChangesListener methods
+ void OnContainerChanged(PLT_DeviceDataReference& device,
+ const char* item_id,
+ const char* update_id) override
+ {
+ NPT_String path = "upnp://"+device->GetUUID()+"/";
+ if (!NPT_StringsEqual(item_id, "0")) {
+ std::string id(CURL::Encode(item_id));
+ URIUtils::AddSlashAtEnd(id);
+ path += id.c_str();
+ }
+
+ m_logger->debug("notified container update {}", (const char*)path);
+ CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH);
+ message.SetStringParam(path.GetChars());
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message);
+ }
+
+ bool MarkWatched(const CFileItem& item, const bool watched)
+ {
+ if (watched) {
+ CFileItem temp(item);
+ temp.SetProperty("original_listitem_url", item.GetPath());
+ return SaveFileState(temp, CBookmark(), watched);
+ }
+ else {
+ m_logger->debug("Marking video item {} as watched", item.GetPath());
+
+ std::set<std::pair<NPT_String, NPT_String> > values;
+ values.insert(std::make_pair("<upnp:playCount>1</upnp:playCount>",
+ "<upnp:playCount>0</upnp:playCount>"));
+ return InvokeUpdateObject(item.GetPath().c_str(), values);
+ }
+ }
+
+ bool SaveFileState(const CFileItem& item, const CBookmark& bookmark, const bool updatePlayCount)
+ {
+ std::string path = item.GetProperty("original_listitem_url").asString();
+ if (!item.HasVideoInfoTag() || path.empty()) {
+ return false;
+ }
+
+ std::set<std::pair<NPT_String, NPT_String> > values;
+ if (item.GetVideoInfoTag()->GetResumePoint().timeInSeconds != bookmark.timeInSeconds) {
+ m_logger->debug("Updating resume point for item {}", path);
+ long time = (long)bookmark.timeInSeconds;
+ if (time < 0)
+ time = 0;
+
+ values.insert(std::make_pair(
+ NPT_String::Format("<upnp:lastPlaybackPosition>%ld</upnp:lastPlaybackPosition>",
+ (long)item.GetVideoInfoTag()->GetResumePoint().timeInSeconds),
+ NPT_String::Format("<upnp:lastPlaybackPosition>%ld</upnp:lastPlaybackPosition>",
+ time)));
+
+ NPT_String curr_value = "<xbmc:lastPlayerState>";
+ PLT_Didl::AppendXmlEscape(curr_value, item.GetVideoInfoTag()->GetResumePoint().playerState.c_str());
+ curr_value += "</xbmc:lastPlayerState>";
+ NPT_String new_value = "<xbmc:lastPlayerState>";
+ PLT_Didl::AppendXmlEscape(new_value, bookmark.playerState.c_str());
+ new_value += "</xbmc:lastPlayerState>";
+ values.insert(std::make_pair(curr_value, new_value));
+ }
+ if (updatePlayCount) {
+ m_logger->debug("Marking video item {} as watched", path);
+ values.insert(std::make_pair("<upnp:playCount>0</upnp:playCount>",
+ "<upnp:playCount>1</upnp:playCount>"));
+ }
+
+ return InvokeUpdateObject(path.c_str(), values);
+ }
+
+ bool UpdateItem(const std::string& path, const CFileItem& item)
+ {
+ if (path.empty())
+ return false;
+
+ std::set<std::pair<NPT_String, NPT_String> > values;
+ if (item.HasVideoInfoTag())
+ {
+ // handle playcount
+ const CVideoInfoTag *details = item.GetVideoInfoTag();
+ int playcountOld = 0, playcountNew = 0;
+ if (details->GetPlayCount() <= 0)
+ playcountOld = 1;
+ else
+ playcountNew = details->GetPlayCount();
+
+ values.insert(std::make_pair(
+ NPT_String::Format("<upnp:playCount>%d</upnp:playCount>", playcountOld),
+ NPT_String::Format("<upnp:playCount>%d</upnp:playCount>", playcountNew)));
+
+ // handle lastplayed
+ CDateTime lastPlayedOld, lastPlayedNew;
+ if (!details->m_lastPlayed.IsValid())
+ lastPlayedOld = CDateTime::GetCurrentDateTime();
+ else
+ lastPlayedNew = details->m_lastPlayed;
+
+ values.insert(std::make_pair(
+ NPT_String::Format("<upnp:lastPlaybackTime>%s</upnp:lastPlaybackTime>",
+ lastPlayedOld.GetAsW3CDateTime().c_str()),
+ NPT_String::Format("<upnp:lastPlaybackTime>%s</upnp:lastPlaybackTime>",
+ lastPlayedNew.GetAsW3CDateTime().c_str())));
+
+ // handle resume point
+ long resumePointOld = 0L, resumePointNew = 0L;
+ if (details->GetResumePoint().timeInSeconds <= 0)
+ resumePointOld = 1;
+ else
+ resumePointNew = static_cast<long>(details->GetResumePoint().timeInSeconds);
+
+ values.insert(std::make_pair(
+ NPT_String::Format("<upnp:lastPlaybackPosition>%ld</upnp:lastPlaybackPosition>",
+ resumePointOld),
+ NPT_String::Format("<upnp:lastPlaybackPosition>%ld</upnp:lastPlaybackPosition>",
+ resumePointNew)));
+ }
+
+ return InvokeUpdateObject(path.c_str(), values);
+ }
+
+ bool InvokeUpdateObject(const char *id, const std::set<std::pair<NPT_String, NPT_String> >& values)
+ {
+ CURL url(id);
+ PLT_DeviceDataReference device;
+ PLT_Service* cds;
+ PLT_ActionReference action;
+ NPT_String curr_value, new_value;
+
+ m_logger->debug("attempting to invoke UpdateObject for {}", id);
+
+ // check this server supports UpdateObject action
+ NPT_CHECK_LABEL(FindServer(url.GetHostName().c_str(), device),failed);
+ NPT_CHECK_LABEL(device->FindServiceById("urn:upnp-org:serviceId:ContentDirectory", cds),failed);
+
+ NPT_CHECK_LABEL(m_CtrlPoint->CreateAction(
+ device,
+ "urn:schemas-upnp-org:service:ContentDirectory:1",
+ "UpdateObject",
+ action), failed);
+
+ NPT_CHECK_LABEL(action->SetArgumentValue("ObjectID", url.GetFileName().c_str()), failed);
+
+ // put together the current and the new value string
+ for (std::set<std::pair<NPT_String, NPT_String> >::const_iterator value = values.begin(); value != values.end(); ++value)
+ {
+ if (!curr_value.IsEmpty())
+ curr_value.Append(",");
+ if (!new_value.IsEmpty())
+ new_value.Append(",");
+
+ curr_value.Append(value->first);
+ new_value.Append(value->second);
+ }
+ NPT_CHECK_LABEL(action->SetArgumentValue("CurrentTagValue", curr_value), failed);
+ NPT_CHECK_LABEL(action->SetArgumentValue("NewTagValue", new_value), failed);
+
+ NPT_CHECK_LABEL(m_CtrlPoint->InvokeAction(action, NULL),failed);
+
+ m_logger->debug("invoked UpdateObject successfully");
+ return true;
+
+ failed:
+ m_logger->info("invoking UpdateObject failed");
+ return false;
+ }
+
+ private:
+ Logger m_logger;
+};
+
+
+/*----------------------------------------------------------------------
+| CMediaController class
++---------------------------------------------------------------------*/
+class CMediaController
+ : public PLT_MediaControllerDelegate
+ , public PLT_MediaController
+{
+public:
+ explicit CMediaController(PLT_CtrlPointReference& ctrl_point)
+ : PLT_MediaController(ctrl_point)
+ {
+ PLT_MediaController::SetDelegate(this);
+ }
+
+ ~CMediaController() override
+ {
+ for (const auto& itRenderer : m_registeredRenderers)
+ unregisterRenderer(itRenderer);
+ m_registeredRenderers.clear();
+ }
+
+#define CHECK_USERDATA_RETURN(userdata) do { \
+ if (!g_UserData.Contains(userdata)) \
+ return; \
+ } while(0)
+
+ void OnStopResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata) override
+ { CHECK_USERDATA_RETURN(userdata);
+ static_cast<PLT_MediaControllerDelegate*>(userdata)->OnStopResult(res, device, userdata);
+ }
+
+ void OnSetPlayModeResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata) override
+ { CHECK_USERDATA_RETURN(userdata);
+ static_cast<PLT_MediaControllerDelegate*>(userdata)->OnSetPlayModeResult(res, device, userdata);
+ }
+
+ void OnSetAVTransportURIResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata) override
+ { CHECK_USERDATA_RETURN(userdata);
+ static_cast<PLT_MediaControllerDelegate*>(userdata)->OnSetAVTransportURIResult(res, device, userdata);
+ }
+
+ void OnSeekResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata) override
+ { CHECK_USERDATA_RETURN(userdata);
+ static_cast<PLT_MediaControllerDelegate*>(userdata)->OnSeekResult(res, device, userdata);
+ }
+
+ void OnPreviousResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata) override
+ { CHECK_USERDATA_RETURN(userdata);
+ static_cast<PLT_MediaControllerDelegate*>(userdata)->OnPreviousResult(res, device, userdata);
+ }
+
+ void OnPlayResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata) override
+ { CHECK_USERDATA_RETURN(userdata);
+ static_cast<PLT_MediaControllerDelegate*>(userdata)->OnPlayResult(res, device, userdata);
+ }
+
+ void OnPauseResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata) override
+ { CHECK_USERDATA_RETURN(userdata);
+ static_cast<PLT_MediaControllerDelegate*>(userdata)->OnPauseResult(res, device, userdata);
+ }
+
+ void OnNextResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata) override
+ { CHECK_USERDATA_RETURN(userdata);
+ static_cast<PLT_MediaControllerDelegate*>(userdata)->OnNextResult(res, device, userdata);
+ }
+
+ void OnGetMediaInfoResult(NPT_Result res, PLT_DeviceDataReference& device, PLT_MediaInfo* info, void* userdata) override
+ { CHECK_USERDATA_RETURN(userdata);
+ static_cast<PLT_MediaControllerDelegate*>(userdata)->OnGetMediaInfoResult(res, device, info, userdata);
+ }
+
+ void OnGetPositionInfoResult(NPT_Result res, PLT_DeviceDataReference& device, PLT_PositionInfo* info, void* userdata) override
+ { CHECK_USERDATA_RETURN(userdata);
+ static_cast<PLT_MediaControllerDelegate*>(userdata)->OnGetPositionInfoResult(res, device, info, userdata);
+ }
+
+ void OnGetTransportInfoResult(NPT_Result res, PLT_DeviceDataReference& device, PLT_TransportInfo* info, void* userdata) override
+ { CHECK_USERDATA_RETURN(userdata);
+ static_cast<PLT_MediaControllerDelegate*>(userdata)->OnGetTransportInfoResult(res, device, info, userdata);
+ }
+
+ bool OnMRAdded(PLT_DeviceDataReference& device ) override
+ {
+ if (device->GetUUID().IsEmpty() || device->GetUUID().GetChars() == NULL)
+ return false;
+
+ CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory();
+
+ playerCoreFactory.OnPlayerDiscovered((const char*)device->GetUUID()
+ ,(const char*)device->GetFriendlyName());
+
+ m_registeredRenderers.insert(std::string(device->GetUUID().GetChars()));
+ return true;
+ }
+
+ void OnMRRemoved(PLT_DeviceDataReference& device ) override
+ {
+ if (device->GetUUID().IsEmpty() || device->GetUUID().GetChars() == NULL)
+ return;
+
+ std::string uuid(device->GetUUID().GetChars());
+ unregisterRenderer(uuid);
+ m_registeredRenderers.erase(uuid);
+ }
+
+private:
+ void unregisterRenderer(const std::string &deviceUUID)
+ {
+ CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory();
+
+ playerCoreFactory.OnPlayerRemoved(deviceUUID);
+ }
+
+ std::set<std::string> m_registeredRenderers;
+};
+
+/*----------------------------------------------------------------------
+| CUPnP::CUPnP
++---------------------------------------------------------------------*/
+CUPnP::CUPnP() :
+ m_MediaBrowser(NULL),
+ m_MediaController(NULL),
+ m_LogHandler(NULL),
+ m_ServerHolder(new CDeviceHostReferenceHolder()),
+ m_RendererHolder(new CRendererReferenceHolder()),
+ m_CtrlPointHolder(new CCtrlPointReferenceHolder())
+{
+ NPT_LogManager::GetDefault().Configure("plist:.level=FINE;.handlers=CustomHandler;");
+ NPT_LogHandler::Create("xbmc", "CustomHandler", m_LogHandler);
+ m_LogHandler->SetCustomHandlerFunction(&UPnPLogger);
+
+ // initialize upnp context
+ m_UPnP = new PLT_UPnP();
+
+ // keep main IP around
+ if (CServiceBroker::GetNetwork().GetFirstConnectedInterface()) {
+ m_IP = CServiceBroker::GetNetwork().GetFirstConnectedInterface()->GetCurrentIPAddress().c_str();
+ }
+ NPT_List<NPT_IpAddress> list;
+ if (NPT_SUCCEEDED(PLT_UPnPMessageHelper::GetIPAddresses(list)) && list.GetItemCount()) {
+ m_IP = (*(list.GetFirstItem())).ToString();
+ }
+ else if(m_IP.empty())
+ m_IP = "localhost";
+
+ // start upnp monitoring
+ m_UPnP->Start();
+}
+
+/*----------------------------------------------------------------------
+| CUPnP::~CUPnP
++---------------------------------------------------------------------*/
+CUPnP::~CUPnP()
+{
+ m_UPnP->Stop();
+ StopClient();
+ StopController();
+ StopServer();
+
+ delete m_UPnP;
+ delete m_LogHandler;
+ delete m_ServerHolder;
+ delete m_RendererHolder;
+ delete m_CtrlPointHolder;
+}
+
+/*----------------------------------------------------------------------
+| CUPnP::GetInstance
++---------------------------------------------------------------------*/
+CUPnP*
+CUPnP::GetInstance()
+{
+ if (!upnp) {
+ upnp = new CUPnP();
+ }
+
+ return upnp;
+}
+
+/*----------------------------------------------------------------------
+| CUPnP::ReleaseInstance
++---------------------------------------------------------------------*/
+void
+CUPnP::ReleaseInstance(bool bWait)
+{
+ if (upnp) {
+ CUPnP* _upnp = upnp;
+ upnp = NULL;
+
+ if (bWait) {
+ delete _upnp;
+ } else {
+ // since it takes a while to clean up
+ // starts a detached thread to do this
+ CUPnPCleaner* cleaner = new CUPnPCleaner(_upnp);
+ cleaner->Start();
+ }
+ }
+}
+
+/*----------------------------------------------------------------------
+| CUPnP::GetServer
++---------------------------------------------------------------------*/
+CUPnPServer* CUPnP::GetServer()
+{
+ if(upnp)
+ return static_cast<CUPnPServer*>(upnp->m_ServerHolder->m_Device.AsPointer());
+ return NULL;
+}
+
+/*----------------------------------------------------------------------
+| CUPnP::MarkWatched
++---------------------------------------------------------------------*/
+bool
+CUPnP::MarkWatched(const CFileItem& item, const bool watched)
+{
+ if (upnp && upnp->m_MediaBrowser) {
+ // dynamic_cast is safe here, avoids polluting CUPnP.h header file
+ CMediaBrowser* browser = dynamic_cast<CMediaBrowser*>(upnp->m_MediaBrowser);
+ if (browser)
+ return browser->MarkWatched(item, watched);
+ }
+ return false;
+}
+
+/*----------------------------------------------------------------------
+| CUPnP::SaveFileState
++---------------------------------------------------------------------*/
+bool
+CUPnP::SaveFileState(const CFileItem& item, const CBookmark& bookmark, const bool updatePlayCount)
+{
+ if (upnp && upnp->m_MediaBrowser) {
+ // dynamic_cast is safe here, avoids polluting CUPnP.h header file
+ CMediaBrowser* browser = dynamic_cast<CMediaBrowser*>(upnp->m_MediaBrowser);
+ if (browser)
+ return browser->SaveFileState(item, bookmark, updatePlayCount);
+ }
+ return false;
+}
+
+/*----------------------------------------------------------------------
+| CUPnP::CreateControlPoint
++---------------------------------------------------------------------*/
+void
+CUPnP::CreateControlPoint()
+{
+ if (!m_CtrlPointHolder->m_CtrlPoint.IsNull())
+ return;
+
+ // create controlpoint
+ m_CtrlPointHolder->m_CtrlPoint = new PLT_CtrlPoint();
+
+ // start it
+ m_UPnP->AddCtrlPoint(m_CtrlPointHolder->m_CtrlPoint);
+}
+
+/*----------------------------------------------------------------------
+| CUPnP::DestroyControlPoint
++---------------------------------------------------------------------*/
+void
+CUPnP::DestroyControlPoint()
+{
+ if (m_CtrlPointHolder->m_CtrlPoint.IsNull())
+ return;
+
+ m_UPnP->RemoveCtrlPoint(m_CtrlPointHolder->m_CtrlPoint);
+ m_CtrlPointHolder->m_CtrlPoint = NULL;
+}
+
+/*----------------------------------------------------------------------
+| CUPnP::UpdateItem
++---------------------------------------------------------------------*/
+bool
+CUPnP::UpdateItem(const std::string& path, const CFileItem& item)
+{
+ if (upnp && upnp->m_MediaBrowser) {
+ // dynamic_cast is safe here, avoids polluting CUPnP.h header file
+ CMediaBrowser* browser = dynamic_cast<CMediaBrowser*>(upnp->m_MediaBrowser);
+ if (browser)
+ return browser->UpdateItem(path, item);
+ }
+ return false;
+}
+
+/*----------------------------------------------------------------------
+| CUPnP::StartClient
++---------------------------------------------------------------------*/
+void
+CUPnP::StartClient()
+{
+ std::unique_lock<CCriticalSection> lock(m_lockMediaBrowser);
+ if (m_MediaBrowser != NULL)
+ return;
+
+ CreateControlPoint();
+
+ // start browser
+ m_MediaBrowser = new CMediaBrowser(m_CtrlPointHolder->m_CtrlPoint);
+}
+
+/*----------------------------------------------------------------------
+| CUPnP::StopClient
++---------------------------------------------------------------------*/
+void
+CUPnP::StopClient()
+{
+ std::unique_lock<CCriticalSection> lock(m_lockMediaBrowser);
+ if (m_MediaBrowser == NULL)
+ return;
+
+ delete m_MediaBrowser;
+ m_MediaBrowser = NULL;
+
+ if (!IsControllerStarted())
+ DestroyControlPoint();
+}
+
+/*----------------------------------------------------------------------
+| CUPnP::StartController
++---------------------------------------------------------------------*/
+void
+CUPnP::StartController()
+{
+ if (m_MediaController != NULL)
+ return;
+
+ CreateControlPoint();
+
+ m_MediaController = new CMediaController(m_CtrlPointHolder->m_CtrlPoint);
+}
+
+/*----------------------------------------------------------------------
+| CUPnP::StopController
++---------------------------------------------------------------------*/
+void
+CUPnP::StopController()
+{
+ if (m_MediaController == NULL)
+ return;
+
+ delete m_MediaController;
+ m_MediaController = NULL;
+
+ if (!IsClientStarted())
+ DestroyControlPoint();
+}
+
+/*----------------------------------------------------------------------
+| CUPnP::CreateServer
++---------------------------------------------------------------------*/
+CUPnPServer*
+CUPnP::CreateServer(int port /* = 0 */)
+{
+ CUPnPServer* device =
+ new CUPnPServer(CSysInfo::GetDeviceName().c_str(),
+ CUPnPSettings::GetInstance().GetServerUUID().length() ? CUPnPSettings::GetInstance().GetServerUUID().c_str() : NULL,
+ port);
+
+ // trying to set optional upnp values for XP UPnP UI Icons to detect us
+ // but it doesn't work anyways as it requires multicast for XP to detect us
+ device->m_PresentationURL =
+ NPT_HttpUrl(m_IP.c_str(),
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_SERVICES_WEBSERVERPORT),
+ "/").ToString();
+
+ device->m_ModelName = "Kodi";
+ device->m_ModelNumber = CSysInfo::GetVersion().c_str();
+ device->m_ModelDescription = "Kodi - Media Server";
+ device->m_ModelURL = "http://kodi.tv/";
+ device->m_Manufacturer = "XBMC Foundation";
+ device->m_ManufacturerURL = "http://kodi.tv/";
+
+ device->SetDelegate(device);
+ return device;
+}
+
+/*----------------------------------------------------------------------
+| CUPnP::StartServer
++---------------------------------------------------------------------*/
+bool
+CUPnP::StartServer()
+{
+ if (!m_ServerHolder->m_Device.IsNull()) return false;
+
+ const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
+
+ // load upnpserver.xml
+ std::string filename = URIUtils::AddFileToFolder(profileManager->GetUserDataFolder(), "upnpserver.xml");
+ CUPnPSettings::GetInstance().Load(filename);
+
+ // create the server with a XBox compatible friendlyname and UUID from upnpserver.xml if found
+ m_ServerHolder->m_Device = CreateServer(CUPnPSettings::GetInstance().GetServerPort());
+
+ // start server
+ NPT_Result res = m_UPnP->AddDevice(m_ServerHolder->m_Device);
+ if (NPT_FAILED(res)) {
+ // if the upnp device port was not 0, it could have failed because
+ // of port being in used, so restart with a random port
+ if (CUPnPSettings::GetInstance().GetServerPort() > 0) m_ServerHolder->m_Device = CreateServer(0);
+
+ res = m_UPnP->AddDevice(m_ServerHolder->m_Device);
+ }
+
+ // save port but don't overwrite saved settings if port was random
+ if (NPT_SUCCEEDED(res)) {
+ if (CUPnPSettings::GetInstance().GetServerPort() == 0) {
+ CUPnPSettings::GetInstance().SetServerPort(m_ServerHolder->m_Device->GetPort());
+ }
+ CUPnPServer::m_MaxReturnedItems = UPNP_DEFAULT_MAX_RETURNED_ITEMS;
+ if (CUPnPSettings::GetInstance().GetMaximumReturnedItems() > 0) {
+ // must be > UPNP_DEFAULT_MIN_RETURNED_ITEMS
+ CUPnPServer::m_MaxReturnedItems = std::max(UPNP_DEFAULT_MIN_RETURNED_ITEMS, CUPnPSettings::GetInstance().GetMaximumReturnedItems());
+ }
+ CUPnPSettings::GetInstance().SetMaximumReturnedItems(CUPnPServer::m_MaxReturnedItems);
+ }
+
+ // save UUID
+ CUPnPSettings::GetInstance().SetServerUUID(m_ServerHolder->m_Device->GetUUID().GetChars());
+ return CUPnPSettings::GetInstance().Save(filename);
+}
+
+/*----------------------------------------------------------------------
+| CUPnP::StopServer
++---------------------------------------------------------------------*/
+void
+CUPnP::StopServer()
+{
+ if (m_ServerHolder->m_Device.IsNull()) return;
+
+ m_UPnP->RemoveDevice(m_ServerHolder->m_Device);
+ m_ServerHolder->m_Device = NULL;
+}
+
+/*----------------------------------------------------------------------
+| CUPnP::CreateRenderer
++---------------------------------------------------------------------*/
+CUPnPRenderer*
+CUPnP::CreateRenderer(int port /* = 0 */)
+{
+ CUPnPRenderer* device =
+ new CUPnPRenderer(CSysInfo::GetDeviceName().c_str(),
+ false,
+ (CUPnPSettings::GetInstance().GetRendererUUID().length() ? CUPnPSettings::GetInstance().GetRendererUUID().c_str() : NULL),
+ port);
+
+ device->m_PresentationURL =
+ NPT_HttpUrl(m_IP.c_str(),
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_SERVICES_WEBSERVERPORT),
+ "/").ToString();
+ device->m_ModelName = "Kodi";
+ device->m_ModelNumber = CSysInfo::GetVersion().c_str();
+ device->m_ModelDescription = "Kodi - Media Renderer";
+ device->m_ModelURL = "http://kodi.tv/";
+ device->m_Manufacturer = "XBMC Foundation";
+ device->m_ManufacturerURL = "http://kodi.tv/";
+
+ return device;
+}
+
+/*----------------------------------------------------------------------
+| CUPnP::StartRenderer
++---------------------------------------------------------------------*/
+bool CUPnP::StartRenderer()
+{
+ if (!m_RendererHolder->m_Device.IsNull())
+ return false;
+
+ const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
+
+ std::string filename = URIUtils::AddFileToFolder(profileManager->GetUserDataFolder(), "upnpserver.xml");
+ CUPnPSettings::GetInstance().Load(filename);
+
+ m_RendererHolder->m_Device = CreateRenderer(CUPnPSettings::GetInstance().GetRendererPort());
+
+ NPT_Result res = m_UPnP->AddDevice(m_RendererHolder->m_Device);
+
+ // failed most likely because port is in use, try again with random port now
+ if (NPT_FAILED(res) && CUPnPSettings::GetInstance().GetRendererPort() != 0) {
+ m_RendererHolder->m_Device = CreateRenderer(0);
+
+ res = m_UPnP->AddDevice(m_RendererHolder->m_Device);
+ }
+
+ // save port but don't overwrite saved settings if random
+ if (NPT_SUCCEEDED(res) && CUPnPSettings::GetInstance().GetRendererPort() == 0) {
+ CUPnPSettings::GetInstance().SetRendererPort(m_RendererHolder->m_Device->GetPort());
+ }
+
+ // save UUID
+ CUPnPSettings::GetInstance().SetRendererUUID(m_RendererHolder->m_Device->GetUUID().GetChars());
+ return CUPnPSettings::GetInstance().Save(filename);
+}
+
+/*----------------------------------------------------------------------
+| CUPnP::StopRenderer
++---------------------------------------------------------------------*/
+void CUPnP::StopRenderer()
+{
+ if (m_RendererHolder->m_Device.IsNull()) return;
+
+ m_UPnP->RemoveDevice(m_RendererHolder->m_Device);
+ m_RendererHolder->m_Device = NULL;
+}
+
+/*----------------------------------------------------------------------
+| CUPnP::UpdateState
++---------------------------------------------------------------------*/
+void CUPnP::UpdateState()
+{
+ if (!m_RendererHolder->m_Device.IsNull())
+ static_cast<CUPnPRenderer*>(m_RendererHolder->m_Device.AsPointer())->UpdateState();
+}
+
+void CUPnP::RegisterUserdata(void* ptr)
+{
+ NPT_AutoLock lock(g_UserDataLock);
+ g_UserData.Add(ptr);
+}
+
+void CUPnP::UnregisterUserdata(void* ptr)
+{
+ NPT_AutoLock lock(g_UserDataLock);
+ g_UserData.Remove(ptr);
+}
+
+} /* namespace UPNP */