diff options
-rw-r--r-- | language/English/strings.po | 4 | ||||
-rw-r--r-- | lib/libUPnP/Platinum/Source/Core/PltService.cpp | 11 | ||||
-rw-r--r-- | lib/libUPnP/Platinum/Source/Core/PltService.h | 3 | ||||
-rw-r--r-- | lib/libUPnP/Platinum/Source/Core/PltStateVariable.cpp | 16 | ||||
-rw-r--r-- | lib/libUPnP/Platinum/Source/Core/PltStateVariable.h | 10 | ||||
-rw-r--r-- | xbmc/music/MusicDatabase.cpp | 6 | ||||
-rw-r--r-- | xbmc/network/upnp/UPnP.cpp | 1 | ||||
-rw-r--r-- | xbmc/network/upnp/UPnPServer.cpp | 199 | ||||
-rw-r--r-- | xbmc/network/upnp/UPnPServer.h | 17 | ||||
-rw-r--r-- | xbmc/settings/GUISettings.cpp | 1 | ||||
-rw-r--r-- | xbmc/settings/GUIWindowSettingsCategory.cpp | 5 | ||||
-rw-r--r-- | xbmc/video/VideoDatabase.cpp | 24 |
12 files changed, 269 insertions, 28 deletions
diff --git a/language/English/strings.po b/language/English/strings.po index 424dc943d1..80d00bc22d 100644 --- a/language/English/strings.po +++ b/language/English/strings.po @@ -8257,7 +8257,9 @@ msgctxt "#20187" msgid "UPnP" msgstr "" -#empty string with id 20188 +msgctxt "#20188" +msgid "Announce library updates via UPnP" +msgstr "" msgctxt "#20189" msgid "Enable auto scrolling for plot & review" diff --git a/lib/libUPnP/Platinum/Source/Core/PltService.cpp b/lib/libUPnP/Platinum/Source/Core/PltService.cpp index 3280b150ab..62bdc49741 100644 --- a/lib/libUPnP/Platinum/Source/Core/PltService.cpp +++ b/lib/libUPnP/Platinum/Source/Core/PltService.cpp @@ -460,14 +460,14 @@ PLT_Service::IsSubscribable() | PLT_Service::SetStateVariable +---------------------------------------------------------------------*/ NPT_Result -PLT_Service::SetStateVariable(const char* name, const char* value) +PLT_Service::SetStateVariable(const char* name, const char* value, const bool clearonsend /*=false*/) { PLT_StateVariable* stateVariable = NULL; NPT_ContainerFind(m_StateVars, PLT_StateVariableNameFinder(name), stateVariable); if (stateVariable == NULL) return NPT_FAILURE; - return stateVariable->SetValue(value); + return stateVariable->SetValue(value, clearonsend); } /*---------------------------------------------------------------------- @@ -838,6 +838,13 @@ PLT_Service::NotifyChanged() delete sub; } + // some state variables must be cleared immediatly after sending + iter = vars_ready.GetFirstItem(); + while (iter) { + PLT_StateVariable* var = *iter; + var->OnSendCompleted(); + ++iter; + } return NPT_SUCCESS; } diff --git a/lib/libUPnP/Platinum/Source/Core/PltService.h b/lib/libUPnP/Platinum/Source/Core/PltService.h index c03a552f67..9bd4d1268f 100644 --- a/lib/libUPnP/Platinum/Source/Core/PltService.h +++ b/lib/libUPnP/Platinum/Source/Core/PltService.h @@ -216,8 +216,9 @@ public: when necessary. @param name state variable name @param value new State Variable value. + @param clearonsend whether the State Variable should clear immediatly in ::OnSendingCompleted */ - NPT_Result SetStateVariable(const char* name, const char* value); + NPT_Result SetStateVariable(const char* name, const char* value, const bool clearonsend = false); /** Certain state variables notifications must not be sent faster than a certain diff --git a/lib/libUPnP/Platinum/Source/Core/PltStateVariable.cpp b/lib/libUPnP/Platinum/Source/Core/PltStateVariable.cpp index 229e3044dd..47aeb5a453 100644 --- a/lib/libUPnP/Platinum/Source/Core/PltStateVariable.cpp +++ b/lib/libUPnP/Platinum/Source/Core/PltStateVariable.cpp @@ -48,7 +48,8 @@ NPT_SET_LOCAL_LOGGER("platinum.core.statevariable") PLT_StateVariable::PLT_StateVariable(PLT_Service* service) : m_Service(service), m_AllowedValueRange(NULL), - m_IsSendingEventsIndirectly(true) + m_IsSendingEventsIndirectly(true), + m_ShouldClearOnSend(false) { } @@ -145,7 +146,7 @@ PLT_StateVariable::SetRate(NPT_TimeInterval rate) | PLT_StateVariable::SetValue +---------------------------------------------------------------------*/ NPT_Result -PLT_StateVariable::SetValue(const char* value) +PLT_StateVariable::SetValue(const char* value, const bool clearonsend /*=false*/) { if (value == NULL) { return NPT_FAILURE; @@ -159,6 +160,7 @@ PLT_StateVariable::SetValue(const char* value) } m_Value = value; + m_ShouldClearOnSend = clearonsend; m_Service->AddChanged(this); } @@ -183,6 +185,16 @@ PLT_StateVariable::IsReadyToPublish() } /*---------------------------------------------------------------------- +| PLT_StateVariable::OnSendCompleted ++---------------------------------------------------------------------*/ +void +PLT_StateVariable::OnSendCompleted() +{ + if(m_ShouldClearOnSend) + m_Value = m_DefaultValue; +} + +/*---------------------------------------------------------------------- | PLT_StateVariable::ValidateValue +---------------------------------------------------------------------*/ NPT_Result diff --git a/lib/libUPnP/Platinum/Source/Core/PltStateVariable.h b/lib/libUPnP/Platinum/Source/Core/PltStateVariable.h index 46ec9e95a3..465e95cfeb 100644 --- a/lib/libUPnP/Platinum/Source/Core/PltStateVariable.h +++ b/lib/libUPnP/Platinum/Source/Core/PltStateVariable.h @@ -115,8 +115,9 @@ public: it is an allowed value. Once the value is validated, it is marked for eventing by calling the PLT_Service AddChanged function. @param value new state variable value. Can be a comma separated list of values. + @param clearonsend whether the statevariable should be cleared immediatly after sending */ - NPT_Result SetValue(const char* value); + NPT_Result SetValue(const char* value, const bool clearonsend = false); /** Validate the new value of the state variable. @@ -173,6 +174,12 @@ protected: bool IsReadyToPublish(); /** + * If this statevariable should clear after sending to all subscribers, clears the value without + * eventing the change + */ + void OnSendCompleted(); + + /** Serialize the state variable into xml. */ NPT_Result Serialize(NPT_XmlElementNode& node); @@ -189,6 +196,7 @@ protected: NPT_String m_DefaultValue; bool m_IsSendingEvents; bool m_IsSendingEventsIndirectly; + bool m_ShouldClearOnSend; NPT_TimeInterval m_Rate; NPT_TimeStamp m_LastEvent; NPT_Array<NPT_String*> m_AllowedValues; diff --git a/xbmc/music/MusicDatabase.cpp b/xbmc/music/MusicDatabase.cpp index c69e74cea8..2617ab14c0 100644 --- a/xbmc/music/MusicDatabase.cpp +++ b/xbmc/music/MusicDatabase.cpp @@ -4183,6 +4183,10 @@ bool CMusicDatabase::RemoveSongsFromPath(const CStdString &path1, CSongMap &song m_pDS->close(); + //TODO: move this below the m_pDS->exec block, once UPnP doesn't rely on this anymore + for (unsigned int i = 0; i < ids.size(); i++) + AnnounceRemove("song", ids[i]); + // and delete all songs, and anything linked to them sql = "delete from song where idSong in " + songIds; m_pDS->exec(sql.c_str()); @@ -4193,8 +4197,6 @@ bool CMusicDatabase::RemoveSongsFromPath(const CStdString &path1, CSongMap &song sql = "delete from karaokedata where idSong in " + songIds; m_pDS->exec(sql.c_str()); - for (unsigned int i = 0; i < ids.size(); i++) - AnnounceRemove("song", ids[i]); } // and remove the path as well (it'll be re-added later on with the new hash if it's non-empty) sql = "delete from path" + where; diff --git a/xbmc/network/upnp/UPnP.cpp b/xbmc/network/upnp/UPnP.cpp index e011e04921..6081415340 100644 --- a/xbmc/network/upnp/UPnP.cpp +++ b/xbmc/network/upnp/UPnP.cpp @@ -183,6 +183,7 @@ public: path += "/"; } + CLog::Log(LOGDEBUG, "UPNP: notfified container update %s", (const char*)path); CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH); message.SetStringParam(path.GetChars()); g_windowManager.SendThreadMessage(message); diff --git a/xbmc/network/upnp/UPnPServer.cpp b/xbmc/network/upnp/UPnPServer.cpp index ada9d7fd66..ee21e4bad9 100644 --- a/xbmc/network/upnp/UPnPServer.cpp +++ b/xbmc/network/upnp/UPnPServer.cpp @@ -1,13 +1,16 @@ #include "UPnPServer.h" #include "UPnPInternal.h" +#include "Application.h" #include "GUIViewState.h" #include "Platinum.h" #include "ThumbLoader.h" +#include "interfaces/AnnouncementManager.h" #include "filesystem/Directory.h" #include "filesystem/MusicDatabaseDirectory.h" #include "filesystem/VideoDatabaseDirectory.h" #include "guilib/Key.h" #include "music/tags/MusicInfoTag.h" +#include "settings/GUISettings.h" #include "utils/log.h" #include "utils/md5.h" #include "utils/StringUtils.h" @@ -16,6 +19,7 @@ #include "video/VideoDatabase.h" using namespace std; +using namespace ANNOUNCEMENT; using namespace XFILE; namespace UPNP @@ -23,6 +27,28 @@ namespace UPNP NPT_UInt32 CUPnPServer::m_MaxReturnedItems = 0; +const char* audio_containers[] = { "musicdb://1/", "musicdb://2/", "musicdb://3/", + "musicdb://4/", "musicdb://6/", "musicdb://9/", + "musicdb://10/" }; + +const char* video_containers[] = { "videodb://1/2/", "videodb://2/2/", "videodb://4/", + "videodb://5/" }; + +/*---------------------------------------------------------------------- +| CUPnPServer::CUPnPServer ++---------------------------------------------------------------------*/ +CUPnPServer::CUPnPServer(const char* friendly_name, const char* uuid /*= NULL*/, int port /*= 0*/) : + PLT_MediaConnect(friendly_name, false, uuid, port), + PLT_FileMediaConnectDelegate("/", "/"), + m_scanning(g_application.IsMusicScanning() || g_application.IsVideoScanning()) +{ +} + +CUPnPServer::~CUPnPServer() +{ + ANNOUNCEMENT::CAnnouncementManager::RemoveAnnouncer(this); +} + /*---------------------------------------------------------------------- | CUPnPServer::ProcessGetSCPD +---------------------------------------------------------------------*/ @@ -42,12 +68,100 @@ CUPnPServer::ProcessGetSCPD(PLT_Service* service, NPT_Result CUPnPServer::SetupServices() { - PLT_MediaConnect::SetupServices(); - PLT_Service* service = NULL; - NPT_Result result = FindServiceById("urn:upnp-org:serviceId:ContentDirectory", service); - if (service) - service->SetStateVariable("SortCapabilities", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating"); - return result; + PLT_MediaConnect::SetupServices(); + PLT_Service* service = NULL; + NPT_Result result = FindServiceById("urn:upnp-org:serviceId:ContentDirectory", service); + if (service) + service->SetStateVariable("SortCapabilities", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating"); + + m_scanning = true; + OnScanCompleted(AudioLibrary); + m_scanning = true; + OnScanCompleted(VideoLibrary); + + // now safe to start passing on new notifications + ANNOUNCEMENT::CAnnouncementManager::AddAnnouncer(this); + + return result; +} + +/*---------------------------------------------------------------------- +| CUPnPServer::OnScanCompleted ++---------------------------------------------------------------------*/ +void +CUPnPServer::OnScanCompleted(int type) +{ + if (type == AudioLibrary) { + for (size_t i = 0; i < sizeof(audio_containers)/sizeof(audio_containers[0]); i++) + UpdateContainer(audio_containers[i]); + } + else if (type == VideoLibrary) { + for (size_t i = 0; i < sizeof(video_containers)/sizeof(video_containers[0]); i++) + UpdateContainer(video_containers[i]); + } + else + return; + m_scanning = false; + PropagateUpdates(); +} + +/*---------------------------------------------------------------------- +| CUPnPServer::UpdateContainer ++---------------------------------------------------------------------*/ +void +CUPnPServer::UpdateContainer(const string& id) +{ + map<string,pair<bool, unsigned long> >::iterator itr = m_UpdateIDs.find(id); + unsigned long count = 0; + if (itr != m_UpdateIDs.end()) + count = ++itr->second.second; + m_UpdateIDs[id] = make_pair(true, count); + PropagateUpdates(); +} + +/*---------------------------------------------------------------------- +| CUPnPServer::PropagateUpdates ++---------------------------------------------------------------------*/ +void +CUPnPServer::PropagateUpdates() +{ + PLT_Service* service = NULL; + NPT_String current_ids; + string buffer; + map<string,pair<bool, unsigned long> >::iterator itr; + + if (m_scanning || !g_guiSettings.GetBool("services.upnpannounce")) + return; + + NPT_CHECK_LABEL(FindServiceById("urn:upnp-org:serviceId:ContentDirectory", service), failed); + + // we pause, and we must retain any changes which have not been + // broadcast yet + NPT_CHECK_LABEL(service->PauseEventing(), failed); + NPT_CHECK_LABEL(service->GetStateVariableValue("ContainerUpdateIDs", current_ids), failed); + buffer = (const char*)current_ids; + if (!buffer.empty()) + buffer.append(","); + + // only broadcast ids with modified bit set + for (itr = m_UpdateIDs.begin(); itr != m_UpdateIDs.end(); ++itr) { + if (itr->second.first) { + buffer.append(StringUtils::Format("%s,%ld,", itr->first.c_str(), itr->second.second).c_str()); + itr->second.first = false; + } + } + + // set the value, Platinum will clear ContainerUpdateIDs after sending + NPT_CHECK_LABEL(service->SetStateVariable("ContainerUpdateIDs", buffer.substr(0,buffer.size()-1).c_str(), true), failed); + NPT_CHECK_LABEL(service->IncStateVariable("SystemUpdateID"), failed); + + service->PauseEventing(false); + return; + +failed: + // should attempt to start eventing on a failure + if (service) service->PauseEventing(false); + CLog::Log(LOGERROR, "UPNP: Unable to propagate updates"); } /*---------------------------------------------------------------------- @@ -202,6 +316,79 @@ failure: } /*---------------------------------------------------------------------- +| CUPnPServer::Announce ++---------------------------------------------------------------------*/ +void +CUPnPServer::Announce(AnnouncementFlag flag, const char *sender, const char *message, const CVariant &data) +{ + NPT_String path; + int item_id; + string item_type; + + if (strcmp(sender, "xbmc")) + return; + + if (strcmp(message, "OnUpdate") && strcmp(message, "OnRemove") + && strcmp(message, "OnScanStarted") && strcmp(message, "OnScanFinished")) + return; + + if (data.isNull()) { + if (!strcmp(message, "OnScanStarted")) { + m_scanning = true; + } + else if (!strcmp(message, "OnScanFinished")) { + OnScanCompleted(flag); + } + } + else { + // handle both updates & removals + if (!data["item"].isNull()) { + item_id = data["item"]["id"].asInteger(); + item_type = data["item"]["type"].asString(); + } + else { + item_id = data["id"].asInteger(); + item_type = data["type"].asString(); + } + + // we always update 'recently added' nodes along with the specific container, + // as we don't differentiate 'updates' from 'adds' in RPC interface + if (flag == VideoLibrary) { + if(item_type == "episode") { + CVideoDatabase db; + if (!db.Open()) return; + int show_id = db.GetTvShowForEpisode(item_id); + UpdateContainer(StringUtils::Format("videodb://2/2/%d/", show_id)); + UpdateContainer("videodb://5/"); + } + else if(item_type == "tvshow") { + UpdateContainer("videodb://2/2/"); + UpdateContainer("videodb://5/"); + } + else if(item_type == "movie") { + UpdateContainer("videodb://1/2/"); + UpdateContainer("videodb://4/"); + } + else if(item_type == "musicvideo") { + UpdateContainer("videodb://4/"); + } + } + else if (flag == AudioLibrary && item_type == "song") { + // we also update the 'songs' container is maybe a performance drop too + // high? would need to check if slow clients even cache at all anyway + CMusicDatabase db; + CAlbum album; + if (!db.Open()) return; + if (db.GetAlbumFromSong(item_id, album)) { + UpdateContainer(StringUtils::Format("musicdb://3/%ld", album.idAlbum)); + UpdateContainer("musicdb://4/"); + UpdateContainer("musicdb://6/"); + } + } + } +} + +/*---------------------------------------------------------------------- | TranslateWMPObjectId +---------------------------------------------------------------------*/ static NPT_String TranslateWMPObjectId(NPT_String id) diff --git a/xbmc/network/upnp/UPnPServer.h b/xbmc/network/upnp/UPnPServer.h index dff30c4188..b4055e1d97 100644 --- a/xbmc/network/upnp/UPnPServer.h +++ b/xbmc/network/upnp/UPnPServer.h @@ -19,6 +19,7 @@ * */ #include "PltMediaConnect.h" +#include "interfaces/IAnnouncer.h" #include "FileItem.h" class CThumbLoader; @@ -29,13 +30,13 @@ namespace UPNP { class CUPnPServer : public PLT_MediaConnect, - public PLT_FileMediaConnectDelegate + public PLT_FileMediaConnectDelegate, + public ANNOUNCEMENT::IAnnouncer { public: - CUPnPServer(const char* friendly_name, const char* uuid = NULL, int port = 0) : - PLT_MediaConnect(friendly_name, false, uuid, port), - PLT_FileMediaConnectDelegate("/", "/") { - } + CUPnPServer(const char* friendly_name, const char* uuid = NULL, int port = 0); + ~CUPnPServer(); + virtual void Announce(ANNOUNCEMENT::AnnouncementFlag flag, const char *sender, const char *message, const CVariant &data); // PLT_MediaServer methods virtual NPT_Result OnBrowseMetadata(PLT_ActionReference& action, @@ -90,6 +91,10 @@ public: private: + void OnScanCompleted(int type); + void UpdateContainer(const std::string& id); + void PropagateUpdates(); + PLT_MediaObject* Build(CFileItemPtr item, bool with_count, const PLT_HttpRequestContext& context, @@ -117,6 +122,8 @@ private: NPT_Mutex m_FileMutex; NPT_Map<NPT_String, NPT_String> m_FileMap; + std::map<std::string, std::pair<bool, unsigned long> > m_UpdateIDs; + bool m_scanning; public: // class members static NPT_UInt32 m_MaxReturnedItems; diff --git a/xbmc/settings/GUISettings.cpp b/xbmc/settings/GUISettings.cpp index 87107e5428..44af36f574 100644 --- a/xbmc/settings/GUISettings.cpp +++ b/xbmc/settings/GUISettings.cpp @@ -829,6 +829,7 @@ void CGUISettings::Initialize() CSettingsCategory* srvUpnp = AddCategory(SETTINGS_SERVICE, "upnp", 20187); AddBool(srvUpnp, "services.upnpserver", 21360, false); + AddBool(srvUpnp, "services.upnpannounce", 20188, true); AddBool(srvUpnp, "services.upnprenderer", 21881, false); #ifdef HAS_WEB_SERVER diff --git a/xbmc/settings/GUIWindowSettingsCategory.cpp b/xbmc/settings/GUIWindowSettingsCategory.cpp index ac58939533..5ce74c6b05 100644 --- a/xbmc/settings/GUIWindowSettingsCategory.cpp +++ b/xbmc/settings/GUIWindowSettingsCategory.cpp @@ -737,6 +737,11 @@ void CGUIWindowSettingsCategory::UpdateSettings() CGUIControl *pControl = (CGUIControl *)GetControl(pSettingControl->GetID()); if (pControl) pControl->SetEnabled(g_guiSettings.GetBool("services.esenabled")); } + else if (strSetting.Equals("services.upnpannounce")) + { + CGUIControl *pControl = (CGUIControl *)GetControl(pSettingControl->GetID()); + pControl->SetEnabled(g_guiSettings.GetBool("services.upnpserver")); + } else if (strSetting.Equals("audiocds.quality")) { // only visible if we are doing non-WAV ripping CGUIControl *pControl = (CGUIControl *)GetControl(pSettingControl->GetID()); diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index 0b56060396..c8b687e97f 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -2731,13 +2731,15 @@ void CVideoDatabase::DeleteMovie(const CStdString& strFilenameAndPath, bool bKee m_pDS->exec(strSQL.c_str()); } + //TODO: move this below CommitTransaction() once UPnP doesn't rely on this anymore + if (!bKeepId) + AnnounceRemove("movie", idMovie); + CStdString strPath, strFileName; SplitPath(strFilenameAndPath,strPath,strFileName); InvalidatePathHash(strPath); CommitTransaction(); - if (!bKeepId) - AnnounceRemove("movie", idMovie); } catch (...) { @@ -2801,12 +2803,14 @@ void CVideoDatabase::DeleteTvShow(const CStdString& strPath, bool bKeepId /* = f m_pDS->exec(strSQL.c_str()); } + //TODO: move this below CommitTransaction() once UPnP doesn't rely on this anymore + if (!bKeepId) + AnnounceRemove("tvshow", idTvShow); + InvalidatePathHash(strPath); CommitTransaction(); - if (!bKeepId) - AnnounceRemove("tvshow", idTvShow); } catch (...) { @@ -2840,6 +2844,10 @@ void CVideoDatabase::DeleteEpisode(const CStdString& strFilenameAndPath, int idE } } + //TODO: move this below CommitTransaction() once UPnP doesn't rely on this anymore + if (!bKeepId) + AnnounceRemove("episode", idEpisode); + CStdString strSQL; strSQL=PrepareSQL("delete from actorlinkepisode where idEpisode=%i", idEpisode); m_pDS->exec(strSQL.c_str()); @@ -2862,8 +2870,6 @@ void CVideoDatabase::DeleteEpisode(const CStdString& strFilenameAndPath, int idE m_pDS->exec(strSQL.c_str()); } - if (!bKeepId) - AnnounceRemove("episode", idEpisode); } catch (...) { @@ -2922,13 +2928,15 @@ void CVideoDatabase::DeleteMusicVideo(const CStdString& strFilenameAndPath, bool m_pDS->exec(strSQL.c_str()); } + //TODO: move this below CommitTransaction() once UPnP doesn't rely on this anymore + if (!bKeepId) + AnnounceRemove("musicvideo", idMVideo); + CStdString strPath, strFileName; SplitPath(strFilenameAndPath,strPath,strFileName); InvalidatePathHash(strPath); CommitTransaction(); - if (!bKeepId) - AnnounceRemove("musicvideo", idMVideo); } catch (...) { |