diff options
-rw-r--r-- | xbmc/pvr/addons/PVRClient.cpp | 419 | ||||
-rw-r--r-- | xbmc/pvr/addons/PVRClient.h | 15 |
2 files changed, 241 insertions, 193 deletions
diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index db09d2f210..700b959774 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -104,6 +104,8 @@ void CPVRClient::ResetProperties(int iClientId /* = PVR_INVALID_CLIENT_ID */) m_strClientPath = CSpecialProtocol::TranslatePath(Path()); m_bReadyToUse = false; m_bBlockAddonCalls = false; + m_iAddonCalls = 0; + m_allAddonCallsFinished.Set(); m_connectionState = PVR_CONNECTION_STATE_UNKNOWN; m_prevConnectionState = PVR_CONNECTION_STATE_UNKNOWN; m_ignoreClient = false; @@ -156,7 +158,6 @@ ADDON_STATUS CPVRClient::Create(int iClientId) if (iClientId <= PVR_INVALID_CLIENT_ID) return status; - /* reset all properties to defaults */ ResetProperties(iClientId); /* initialise the add-on */ @@ -176,16 +177,18 @@ void CPVRClient::Destroy() m_bReadyToUse = false; - /* reset 'ready to use' to false */ CLog::LogFC(LOGDEBUG, LOGPVR, "Destroying PVR add-on instance '{}'", GetFriendlyName()); - /* destroy the add-on */ + m_bBlockAddonCalls = true; + m_allAddonCallsFinished.Wait(); + DestroyInstance(); + CLog::LogFC(LOGDEBUG, LOGPVR, "PVR add-on instance '{}' destroyed", GetFriendlyName()); + if (m_menuhooks) m_menuhooks->Clear(); - /* reset all properties to defaults */ ResetProperties(); } @@ -1378,8 +1381,15 @@ PVR_ERROR CPVRClient::DoAddonCall(const char* strFunctionName, return PVR_ERROR_SERVER_ERROR; // Call. + m_allAddonCallsFinished.Reset(); + m_iAddonCalls++; + const PVR_ERROR error = function(&m_struct); + m_iAddonCalls--; + if (m_iAddonCalls == 0) + m_allAddonCallsFinished.Set(); + // Log error, if any. if (error != PVR_ERROR_NO_ERROR && error != PVR_ERROR_NOT_IMPLEMENTED) CLog::LogFunction(LOGERROR, strFunctionName, "Add-on '{}' returned an error: {}", @@ -1647,177 +1657,176 @@ int CPVRClient::GetPriority() const return m_iPriority; } -void CPVRClient::cb_transfer_channel_group(void* kodiInstance, - const ADDON_HANDLE handle, - const PVR_CHANNEL_GROUP* group) +void CPVRClient::HandleAddonCallback(const char* strFunctionName, + void* kodiInstance, + const std::function<void(CPVRClient* client)>& function, + bool bForceCall /* = false */) { - if (!handle) + // Check preconditions. + CPVRClient* client = static_cast<CPVRClient*>(kodiInstance); + if (!client) { - CLog::LogF(LOGERROR, "Invalid handler data"); + CLog::LogFunction(LOGERROR, strFunctionName, "No instance pointer given!"); return; } - CPVRChannelGroups* kodiGroups = static_cast<CPVRChannelGroups*>(handle->dataAddress); - if (!group || !kodiGroups) + if (!bForceCall && client->m_bBlockAddonCalls && client->m_iAddonCalls == 0) { - CLog::LogF(LOGERROR, "Invalid handler data"); + CLog::LogFunction(LOGWARNING, strFunctionName, LOGPVR, "Ignoring callback from PVR client '{}'", + client->GetFriendlyName()); return; } - if (strlen(group->strGroupName) == 0) - { - CLog::LogF(LOGERROR, "Empty group name"); - return; - } + // Call. + function(client); +} - /* transfer this entry to the groups container */ - CPVRChannelGroup transferGroup(*group, kodiGroups->GetGroupAll()); - kodiGroups->UpdateFromClient(transferGroup); +void CPVRClient::cb_transfer_channel_group(void* kodiInstance, + const ADDON_HANDLE handle, + const PVR_CHANNEL_GROUP* group) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!handle || !group) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } + + if (strlen(group->strGroupName) == 0) + { + CLog::LogF(LOGERROR, "Empty group name"); + return; + } + + // transfer this entry to the groups container + CPVRChannelGroups* kodiGroups = static_cast<CPVRChannelGroups*>(handle->dataAddress); + CPVRChannelGroup transferGroup(*group, kodiGroups->GetGroupAll()); + kodiGroups->UpdateFromClient(transferGroup); + }); } void CPVRClient::cb_transfer_channel_group_member(void* kodiInstance, const ADDON_HANDLE handle, const PVR_CHANNEL_GROUP_MEMBER* member) { - if (!handle) - { - CLog::LogF(LOGERROR, "Invalid handler data"); - return; - } - - CPVRClient* client = static_cast<CPVRClient*>(kodiInstance); - CPVRChannelGroup* group = static_cast<CPVRChannelGroup*>(handle->dataAddress); - if (!member || !client || !group) - { - CLog::LogF(LOGERROR, "Invalid handler data"); - return; - } + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!handle || !member) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } - std::shared_ptr<CPVRChannel> channel = - CServiceBroker::GetPVRManager().ChannelGroups()->GetByUniqueID(member->iChannelUniqueId, - client->GetID()); - if (!channel) - { - CLog::LogF(LOGERROR, "Cannot find group '{}' or channel '{}'", member->strGroupName, - member->iChannelUniqueId); - } - else if (group->IsRadio() == channel->IsRadio()) - { - /* transfer this entry to the group */ - group->AddToGroup(channel, CPVRChannelNumber(), member->iOrder, true, - CPVRChannelNumber(member->iChannelNumber, member->iSubChannelNumber)); - } + CPVRChannelGroup* group = static_cast<CPVRChannelGroup*>(handle->dataAddress); + const std::shared_ptr<CPVRChannel> channel = + CServiceBroker::GetPVRManager().ChannelGroups()->GetByUniqueID(member->iChannelUniqueId, + client->GetID()); + if (!channel) + { + CLog::LogF(LOGERROR, "Cannot find group '{}' or channel '{}'", member->strGroupName, + member->iChannelUniqueId); + } + else if (group->IsRadio() == channel->IsRadio()) + { + // add this entry to the group + group->AddToGroup(channel, CPVRChannelNumber(), member->iOrder, true, + CPVRChannelNumber(member->iChannelNumber, member->iSubChannelNumber)); + } + }); } void CPVRClient::cb_transfer_epg_entry(void* kodiInstance, const ADDON_HANDLE handle, const EPG_TAG* epgentry) { - if (!handle) - { - CLog::LogF(LOGERROR, "Invalid handler data"); - return; - } - - CPVRClient* client = static_cast<CPVRClient*>(kodiInstance); - CPVREpg* kodiEpg = static_cast<CPVREpg*>(handle->dataAddress); - if (!epgentry || !client || !kodiEpg) - { - CLog::LogF(LOGERROR, "Invalid handler data"); - return; - } + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!handle || !epgentry) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } - /* transfer this entry to the epg */ - kodiEpg->UpdateEntry(epgentry, client->GetID()); + // transfer this entry to the epg + CPVREpg* epg = static_cast<CPVREpg*>(handle->dataAddress); + epg->UpdateEntry(epgentry, client->GetID()); + }); } void CPVRClient::cb_transfer_channel_entry(void* kodiInstance, const ADDON_HANDLE handle, const PVR_CHANNEL* channel) { - if (!handle) - { - CLog::LogF(LOGERROR, "Invalid handler data"); - return; - } - - CPVRClient* client = static_cast<CPVRClient*>(kodiInstance); - CPVRChannelGroupInternal* kodiChannels = - static_cast<CPVRChannelGroupInternal*>(handle->dataAddress); - if (!channel || !client || !kodiChannels) - { - CLog::LogF(LOGERROR, "Invalid handler data"); - return; - } + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!handle || !channel) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } - /* transfer this entry to the internal channels group */ - std::shared_ptr<CPVRChannel> transferChannel(new CPVRChannel(*channel, client->GetID())); - kodiChannels->UpdateFromClient(transferChannel, CPVRChannelNumber(), channel->iOrder, - transferChannel->ClientChannelNumber()); + // transfer this entry to the internal channels group + const std::shared_ptr<CPVRChannel> transferChannel = + std::make_shared<CPVRChannel>(*channel, client->GetID()); + CPVRChannelGroupInternal* channels = + static_cast<CPVRChannelGroupInternal*>(handle->dataAddress); + channels->UpdateFromClient(transferChannel, CPVRChannelNumber(), channel->iOrder, + transferChannel->ClientChannelNumber()); + }); } void CPVRClient::cb_transfer_recording_entry(void* kodiInstance, const ADDON_HANDLE handle, const PVR_RECORDING* recording) { - if (!handle) - { - CLog::LogF(LOGERROR, "Invalid handler data"); - return; - } - - CPVRClient* client = static_cast<CPVRClient*>(kodiInstance); - CPVRRecordings* kodiRecordings = static_cast<CPVRRecordings*>(handle->dataAddress); - if (!recording || !client || !kodiRecordings) - { - CLog::LogF(LOGERROR, "Invalid handler data"); - return; - } + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!handle || !recording) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } - /* transfer this entry to the recordings container */ - std::shared_ptr<CPVRRecording> transferRecording(new CPVRRecording(*recording, client->GetID())); - kodiRecordings->UpdateFromClient(transferRecording, *client); + // transfer this entry to the recordings container + const std::shared_ptr<CPVRRecording> transferRecording = + std::make_shared<CPVRRecording>(*recording, client->GetID()); + CPVRRecordings* recordings = static_cast<CPVRRecordings*>(handle->dataAddress); + recordings->UpdateFromClient(transferRecording, *client); + }); } void CPVRClient::cb_transfer_timer_entry(void* kodiInstance, const ADDON_HANDLE handle, const PVR_TIMER* timer) { - if (!handle) - { - CLog::LogF(LOGERROR, "Invalid handler data"); - return; - } - - CPVRClient* client = static_cast<CPVRClient*>(kodiInstance); - CPVRTimersContainer* kodiTimers = static_cast<CPVRTimersContainer*>(handle->dataAddress); - if (!timer || !client || !kodiTimers) - { - CLog::LogF(LOGERROR, "Invalid handler data"); - return; - } - - /* Note: channel can be NULL here, for instance for epg-based timer rules ("record on any channel" condition). */ - std::shared_ptr<CPVRChannel> channel = - CServiceBroker::GetPVRManager().ChannelGroups()->GetByUniqueID(timer->iClientChannelUid, - client->GetID()); + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!handle || !timer) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } - /* transfer this entry to the timers container */ - std::shared_ptr<CPVRTimerInfoTag> transferTimer( - new CPVRTimerInfoTag(*timer, channel, client->GetID())); - kodiTimers->UpdateFromClient(transferTimer); + // Note: channel can be nullptr here, for instance for epg-based timer rules + // ("record on any channel" condition) + const std::shared_ptr<CPVRChannel> channel = + CServiceBroker::GetPVRManager().ChannelGroups()->GetByUniqueID(timer->iClientChannelUid, + client->GetID()); + + // transfer this entry to the timers container + const std::shared_ptr<CPVRTimerInfoTag> transferTimer = + std::make_shared<CPVRTimerInfoTag>(*timer, channel, client->GetID()); + CPVRTimersContainer* timers = static_cast<CPVRTimersContainer*>(handle->dataAddress); + timers->UpdateFromClient(transferTimer); + }); } void CPVRClient::cb_add_menu_hook(void* kodiInstance, const PVR_MENUHOOK* hook) { - CPVRClient* client = static_cast<CPVRClient*>(kodiInstance); - if (!hook || !client) - { - CLog::LogF(LOGERROR, "Invalid handler data"); - return; - } + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!hook) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } - client->GetMenuHooks()->AddHook(*hook); + client->GetMenuHooks()->AddHook(*hook); + }); } void CPVRClient::cb_recording_notification(void* kodiInstance, @@ -1825,74 +1834,89 @@ void CPVRClient::cb_recording_notification(void* kodiInstance, const char* strFileName, bool bOnOff) { - CPVRClient* client = static_cast<CPVRClient*>(kodiInstance); - if (!client || !strFileName) - { - CLog::LogF(LOGERROR, "Invalid handler data"); - return; - } - - std::string strLine1 = StringUtils::Format(g_localizeStrings.Get(bOnOff ? 19197 : 19198).c_str(), - client->Name().c_str()); - std::string strLine2; - if (strName) - strLine2 = strName; - else if (strFileName) - strLine2 = strFileName; - - /* display a notification for 5 seconds */ - CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, strLine1, strLine2, 5000, false); - CServiceBroker::GetEventLog().Add( - EventPtr(new CNotificationEvent(client->Name(), strLine1, client->Icon(), strLine2))); + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!strFileName) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } - CLog::LogFC(LOGDEBUG, LOGPVR, "Recording {} on client '{}'. name='{}' filename='{}'", - bOnOff ? "started" : "finished", client->Name(), strName, strFileName); + const std::string strLine1 = StringUtils::Format( + g_localizeStrings.Get(bOnOff ? 19197 : 19198).c_str(), client->Name().c_str()); + std::string strLine2; + if (strName) + strLine2 = strName; + else if (strFileName) + strLine2 = strFileName; + + // display a notification for 5 seconds + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, strLine1, strLine2, 5000, + false); + CServiceBroker::GetEventLog().Add( + EventPtr(new CNotificationEvent(client->Name(), strLine1, client->Icon(), strLine2))); + + CLog::LogFC(LOGDEBUG, LOGPVR, "Recording {} on client '{}'. name='{}' filename='{}'", + bOnOff ? "started" : "finished", client->Name(), strName, strFileName); + }); } void CPVRClient::cb_trigger_channel_update(void* kodiInstance) { - /* update the channels table in the next iteration of the pvrmanager's main loop */ - CServiceBroker::GetPVRManager().TriggerChannelsUpdate(); + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + // update channels in the next iteration of the pvrmanager's main loop + CServiceBroker::GetPVRManager().TriggerChannelsUpdate(); + }); } void CPVRClient::cb_trigger_timer_update(void* kodiInstance) { - /* update the timers table in the next iteration of the pvrmanager's main loop */ - CServiceBroker::GetPVRManager().TriggerTimersUpdate(); + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + // update timers in the next iteration of the pvrmanager's main loop + CServiceBroker::GetPVRManager().TriggerTimersUpdate(); + }); } void CPVRClient::cb_trigger_recording_update(void* kodiInstance) { - /* update the recordings table in the next iteration of the pvrmanager's main loop */ - CServiceBroker::GetPVRManager().TriggerRecordingsUpdate(); + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + // update recordings in the next iteration of the pvrmanager's main loop + CServiceBroker::GetPVRManager().TriggerRecordingsUpdate(); + }); } void CPVRClient::cb_trigger_channel_groups_update(void* kodiInstance) { - /* update all channel groups in the next iteration of the pvrmanager's main loop */ - CServiceBroker::GetPVRManager().TriggerChannelGroupsUpdate(); + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + // update all channel groups in the next iteration of the pvrmanager's main loop + CServiceBroker::GetPVRManager().TriggerChannelGroupsUpdate(); + }); } void CPVRClient::cb_trigger_epg_update(void* kodiInstance, unsigned int iChannelUid) { - CPVRClient* client = static_cast<CPVRClient*>(kodiInstance); - if (!client) - { - CLog::LogF(LOGERROR, "Invalid handler data"); - return; - } - - CServiceBroker::GetPVRManager().EpgContainer().UpdateRequest(client->GetID(), iChannelUid); + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + CServiceBroker::GetPVRManager().EpgContainer().UpdateRequest(client->GetID(), iChannelUid); + }); } void CPVRClient::cb_free_demux_packet(void* kodiInstance, DEMUX_PACKET* pPacket) { - CDVDDemuxUtils::FreeDemuxPacket(static_cast<DemuxPacket*>(pPacket)); + HandleAddonCallback(__func__, kodiInstance, + [&](CPVRClient* client) { + CDVDDemuxUtils::FreeDemuxPacket(static_cast<DemuxPacket*>(pPacket)); + }, + true); } DEMUX_PACKET* CPVRClient::cb_allocate_demux_packet(void* kodiInstance, int iDataSize) { - return CDVDDemuxUtils::AllocateDemuxPacket(iDataSize); + DEMUX_PACKET* result = nullptr; + + HandleAddonCallback( + __func__, kodiInstance, + [&](CPVRClient* client) { result = CDVDDemuxUtils::AllocateDemuxPacket(iDataSize); }, true); + + return result; } void CPVRClient::cb_connection_state_change(void* kodiInstance, @@ -1900,46 +1924,48 @@ void CPVRClient::cb_connection_state_change(void* kodiInstance, PVR_CONNECTION_STATE newState, const char* strMessage) { - CPVRClient* client = static_cast<CPVRClient*>(kodiInstance); - if (!client || !strConnectionString) - { - CLog::LogF(LOGERROR, "Invalid handler data"); - return; - } + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!strConnectionString) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } - const PVR_CONNECTION_STATE prevState(client->GetConnectionState()); - if (prevState == newState) - return; + const PVR_CONNECTION_STATE prevState(client->GetConnectionState()); + if (prevState == newState) + return; - CLog::LogFC(LOGDEBUG, LOGPVR, - "State for connection '{}' on client '{}' changed from '{}' to '{}'", - strConnectionString, client->Name(), prevState, newState); + CLog::LogFC(LOGDEBUG, LOGPVR, + "State for connection '{}' on client '{}' changed from '{}' to '{}'", + strConnectionString, client->Name(), prevState, newState); - client->SetConnectionState(newState); + client->SetConnectionState(newState); - std::string msg; - if (strMessage != nullptr) - msg = strMessage; + std::string msg; + if (strMessage) + msg = strMessage; - CServiceBroker::GetPVRManager().ConnectionStateChange(client, std::string(strConnectionString), - newState, msg); + CServiceBroker::GetPVRManager().ConnectionStateChange(client, std::string(strConnectionString), + newState, msg); + }); } void CPVRClient::cb_epg_event_state_change(void* kodiInstance, EPG_TAG* tag, EPG_EVENT_STATE newState) { - CPVRClient* client = static_cast<CPVRClient*>(kodiInstance); - if (!client || !tag) - { - CLog::LogF(LOGERROR, "Invalid handler data"); - return; - } + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!tag) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } - // Note: channel data and epg id may not yet be available. Tag will be fully initialized later. - const std::shared_ptr<CPVREpgInfoTag> epgTag = - std::make_shared<CPVREpgInfoTag>(*tag, client->GetID(), nullptr, -1); - CServiceBroker::GetPVRManager().EpgContainer().UpdateFromClient(epgTag, newState); + // Note: channel data and epg id may not yet be available. Tag will be fully initialized later. + const std::shared_ptr<CPVREpgInfoTag> epgTag = + std::make_shared<CPVREpgInfoTag>(*tag, client->GetID(), nullptr, -1); + CServiceBroker::GetPVRManager().EpgContainer().UpdateFromClient(epgTag, newState); + }); } class CCodecIds @@ -2006,7 +2032,14 @@ private: PVR_CODEC CPVRClient::cb_get_codec_by_name(const void* kodiInstance, const char* strCodecName) { - return CCodecIds::GetInstance().GetCodecByName(strCodecName); + PVR_CODEC result = PVR_INVALID_CODEC; + + HandleAddonCallback( + __func__, const_cast<void*>(kodiInstance), + [&](CPVRClient* client) { result = CCodecIds::GetInstance().GetCodecByName(strCodecName); }, + true); + + return result; } CPVRClientCapabilities::CPVRClientCapabilities(const CPVRClientCapabilities& other) diff --git a/xbmc/pvr/addons/PVRClient.h b/xbmc/pvr/addons/PVRClient.h index a724cffd43..d7a085212c 100644 --- a/xbmc/pvr/addons/PVRClient.h +++ b/xbmc/pvr/addons/PVRClient.h @@ -10,6 +10,7 @@ #include "addons/binary-addons/AddonInstanceHandler.h" #include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h" +#include "threads/Event.h" #include <atomic> #include <functional> @@ -1079,6 +1080,18 @@ private: bool bCheckReadyToUse = true) const; /*! + * @brief Wraps an addon callback function call in order to do common pre and post function invocation actions. + * @param strFunctionName The function name, for logging purposes. + * @param kodiInstance The addon instance pointer. + * @param function The function to wrap. It must take one parameter of type CPVRClient*. + * @param bForceCall If true, make the call, ignoring client's state. + */ + static void HandleAddonCallback(const char* strFunctionName, + void* kodiInstance, + const std::function<void(CPVRClient* client)>& function, + bool bForceCall = false); + + /*! * @brief Callback functions from addon to kodi */ //@{ @@ -1240,6 +1253,8 @@ private: std::atomic<bool> m_bReadyToUse; /*!< true if this add-on is initialised (ADDON_Create returned true), false otherwise */ std::atomic<bool> m_bBlockAddonCalls; /*!< true if no add-on API calls are allowed */ + mutable std::atomic<int> m_iAddonCalls; /*!< number of in-progress addon calls */ + mutable CEvent m_allAddonCallsFinished; /*!< fires after last in-progress addon call finished */ PVR_CONNECTION_STATE m_connectionState; /*!< the backend connection state */ PVR_CONNECTION_STATE m_prevConnectionState; /*!< the previous backend connection state */ bool |