diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
commit | c04dcc2e7d834218ef2d4194331e383402495ae1 (patch) | |
tree | 7333e38d10d75386e60f336b80c2443c1166031d /xbmc/pvr/addons | |
parent | Initial commit. (diff) | |
download | kodi-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/pvr/addons')
-rw-r--r-- | xbmc/pvr/addons/CMakeLists.txt | 13 | ||||
-rw-r--r-- | xbmc/pvr/addons/PVRClient.cpp | 2023 | ||||
-rw-r--r-- | xbmc/pvr/addons/PVRClient.h | 1062 | ||||
-rw-r--r-- | xbmc/pvr/addons/PVRClientCapabilities.cpp | 85 | ||||
-rw-r--r-- | xbmc/pvr/addons/PVRClientCapabilities.h | 277 | ||||
-rw-r--r-- | xbmc/pvr/addons/PVRClientMenuHooks.cpp | 185 | ||||
-rw-r--r-- | xbmc/pvr/addons/PVRClientMenuHooks.h | 73 | ||||
-rw-r--r-- | xbmc/pvr/addons/PVRClientUID.cpp | 33 | ||||
-rw-r--r-- | xbmc/pvr/addons/PVRClientUID.h | 42 | ||||
-rw-r--r-- | xbmc/pvr/addons/PVRClients.cpp | 977 | ||||
-rw-r--r-- | xbmc/pvr/addons/PVRClients.h | 485 |
11 files changed, 5255 insertions, 0 deletions
diff --git a/xbmc/pvr/addons/CMakeLists.txt b/xbmc/pvr/addons/CMakeLists.txt new file mode 100644 index 0000000..e8a0a51 --- /dev/null +++ b/xbmc/pvr/addons/CMakeLists.txt @@ -0,0 +1,13 @@ +set(SOURCES PVRClient.cpp + PVRClientCapabilities.cpp + PVRClientMenuHooks.cpp + PVRClientUID.cpp + PVRClients.cpp) + +set(HEADERS PVRClient.h + PVRClientCapabilities.h + PVRClientMenuHooks.h + PVRClientUID.h + PVRClients.h) + +core_add_library(pvr_addons) diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp new file mode 100644 index 0000000..2a09277 --- /dev/null +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -0,0 +1,2023 @@ +/* + * Copyright (C) 2012-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 "PVRClient.h" + +#include "ServiceBroker.h" +#include "addons/AddonManager.h" +#include "addons/binary-addons/AddonDll.h" +#include "cores/VideoPlayer/DVDDemuxers/DVDDemuxUtils.h" +#include "dialogs/GUIDialogKaiToast.h" //! @todo get rid of GUI in core +#include "events/EventLog.h" +#include "events/NotificationEvent.h" +#include "filesystem/SpecialProtocol.h" +#include "guilib/LocalizeStrings.h" +#include "pvr/PVRDatabase.h" +#include "pvr/PVRManager.h" +#include "pvr/PVRStreamProperties.h" +#include "pvr/addons/PVRClientMenuHooks.h" +#include "pvr/addons/PVRClients.h" +#include "pvr/channels/PVRChannel.h" +#include "pvr/channels/PVRChannelGroup.h" +#include "pvr/channels/PVRChannelGroupInternal.h" +#include "pvr/channels/PVRChannelGroupMember.h" +#include "pvr/channels/PVRChannelGroups.h" +#include "pvr/channels/PVRChannelGroupsContainer.h" +#include "pvr/epg/Epg.h" +#include "pvr/epg/EpgContainer.h" +#include "pvr/epg/EpgInfoTag.h" +#include "pvr/providers/PVRProvider.h" +#include "pvr/providers/PVRProviders.h" +#include "pvr/recordings/PVRRecording.h" +#include "pvr/recordings/PVRRecordings.h" +#include "pvr/timers/PVRTimerInfoTag.h" +#include "pvr/timers/PVRTimerType.h" +#include "pvr/timers/PVRTimers.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <map> +#include <memory> +#include <mutex> +#include <string> +#include <utility> + +extern "C" +{ +#include <libavcodec/avcodec.h> +} + +using namespace ADDON; + +namespace PVR +{ + +#define DEFAULT_INFO_STRING_VALUE "unknown" + +CPVRClient::CPVRClient(const ADDON::AddonInfoPtr& addonInfo, + ADDON::AddonInstanceId instanceId, + int clientId) + : IAddonInstanceHandler(ADDON_INSTANCE_PVR, addonInfo, instanceId), m_iClientId(clientId) +{ + // Create all interface parts independent to make API changes easier if + // something is added + m_ifc.pvr = new AddonInstance_PVR; + m_ifc.pvr->props = new AddonProperties_PVR(); + m_ifc.pvr->toKodi = new AddonToKodiFuncTable_PVR(); + m_ifc.pvr->toAddon = new KodiToAddonFuncTable_PVR(); + + ResetProperties(); +} + +CPVRClient::~CPVRClient() +{ + Destroy(); + + if (m_ifc.pvr) + { + delete m_ifc.pvr->props; + delete m_ifc.pvr->toKodi; + delete m_ifc.pvr->toAddon; + } + delete m_ifc.pvr; +} + +void CPVRClient::StopRunningInstance() +{ + // stop the pvr manager and stop and unload the running pvr addon. pvr manager will be restarted on demand. + CServiceBroker::GetPVRManager().Stop(); + CServiceBroker::GetPVRManager().Clients()->StopClient(m_iClientId, false); +} + +void CPVRClient::OnPreInstall() +{ + // note: this method is also called on update; thus stop and unload possibly running instance + StopRunningInstance(); +} + +void CPVRClient::OnPreUnInstall() +{ + StopRunningInstance(); +} + +void CPVRClient::ResetProperties() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + /* initialise members */ + m_strUserPath = CSpecialProtocol::TranslatePath(Profile()); + 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; + m_iPriority = 0; + m_bPriorityFetched = false; + m_strBackendVersion = DEFAULT_INFO_STRING_VALUE; + m_strConnectionString = DEFAULT_INFO_STRING_VALUE; + m_strBackendName = DEFAULT_INFO_STRING_VALUE; + m_strBackendHostname.clear(); + m_menuhooks.reset(); + m_timertypes.clear(); + m_clientCapabilities.clear(); + + m_ifc.pvr->props->strUserPath = m_strUserPath.c_str(); + m_ifc.pvr->props->strClientPath = m_strClientPath.c_str(); + m_ifc.pvr->props->iEpgMaxPastDays = + CServiceBroker::GetPVRManager().EpgContainer().GetPastDaysToDisplay(); + m_ifc.pvr->props->iEpgMaxFutureDays = + CServiceBroker::GetPVRManager().EpgContainer().GetFutureDaysToDisplay(); + + m_ifc.pvr->toKodi->kodiInstance = this; + m_ifc.pvr->toKodi->TransferEpgEntry = cb_transfer_epg_entry; + m_ifc.pvr->toKodi->TransferChannelEntry = cb_transfer_channel_entry; + m_ifc.pvr->toKodi->TransferProviderEntry = cb_transfer_provider_entry; + m_ifc.pvr->toKodi->TransferTimerEntry = cb_transfer_timer_entry; + m_ifc.pvr->toKodi->TransferRecordingEntry = cb_transfer_recording_entry; + m_ifc.pvr->toKodi->AddMenuHook = cb_add_menu_hook; + m_ifc.pvr->toKodi->RecordingNotification = cb_recording_notification; + m_ifc.pvr->toKodi->TriggerChannelUpdate = cb_trigger_channel_update; + m_ifc.pvr->toKodi->TriggerProvidersUpdate = cb_trigger_provider_update; + m_ifc.pvr->toKodi->TriggerChannelGroupsUpdate = cb_trigger_channel_groups_update; + m_ifc.pvr->toKodi->TriggerTimerUpdate = cb_trigger_timer_update; + m_ifc.pvr->toKodi->TriggerRecordingUpdate = cb_trigger_recording_update; + m_ifc.pvr->toKodi->TriggerEpgUpdate = cb_trigger_epg_update; + m_ifc.pvr->toKodi->FreeDemuxPacket = cb_free_demux_packet; + m_ifc.pvr->toKodi->AllocateDemuxPacket = cb_allocate_demux_packet; + m_ifc.pvr->toKodi->TransferChannelGroup = cb_transfer_channel_group; + m_ifc.pvr->toKodi->TransferChannelGroupMember = cb_transfer_channel_group_member; + m_ifc.pvr->toKodi->ConnectionStateChange = cb_connection_state_change; + m_ifc.pvr->toKodi->EpgEventStateChange = cb_epg_event_state_change; + m_ifc.pvr->toKodi->GetCodecByName = cb_get_codec_by_name; + + // Clear function addresses to have NULL if not set by addon + memset(m_ifc.pvr->toAddon, 0, sizeof(KodiToAddonFuncTable_PVR)); +} + +ADDON_STATUS CPVRClient::Create() +{ + ADDON_STATUS status(ADDON_STATUS_UNKNOWN); + + ResetProperties(); + + /* initialise the add-on */ + CLog::LogFC(LOGDEBUG, LOGPVR, "Creating PVR add-on instance '{}'", ID()); + + bool bReadyToUse = false; + if ((status = CreateInstance()) == ADDON_STATUS_OK) + bReadyToUse = GetAddonProperties(); + + CLog::LogFC(LOGDEBUG, LOGPVR, "Created PVR add-on instance '{}'. readytouse={} ", ID(), + bReadyToUse); + + m_bReadyToUse = bReadyToUse; + return status; +} + +void CPVRClient::Destroy() +{ + if (!m_bReadyToUse) + return; + + m_bReadyToUse = false; + + CLog::LogFC(LOGDEBUG, LOGPVR, "Destroying PVR add-on instance '{}'", ID()); + + m_bBlockAddonCalls = true; + m_allAddonCallsFinished.Wait(); + + DestroyInstance(); + + CLog::LogFC(LOGDEBUG, LOGPVR, "Destroyed PVR add-on instance '{}'", ID()); + + if (m_menuhooks) + m_menuhooks->Clear(); + + ResetProperties(); +} + +void CPVRClient::Stop() +{ + m_bBlockAddonCalls = true; + m_bPriorityFetched = false; +} + +void CPVRClient::Continue() +{ + m_bBlockAddonCalls = false; +} + +void CPVRClient::ReCreate() +{ + Destroy(); + Create(); +} + +bool CPVRClient::ReadyToUse() const +{ + return m_bReadyToUse; +} + +PVR_CONNECTION_STATE CPVRClient::GetConnectionState() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_connectionState; +} + +void CPVRClient::SetConnectionState(PVR_CONNECTION_STATE state) +{ + if (state == PVR_CONNECTION_STATE_CONNECTED) + { + // update properties - some will only be available after add-on is connected to backend + if (!GetAddonProperties()) + CLog::LogF(LOGERROR, "Error reading PVR client properties"); + } + else + { + if (!GetAddonNameStringProperties()) + CLog::LogF(LOGERROR, "Cannot read PVR client name string properties"); + } + + std::unique_lock<CCriticalSection> lock(m_critSection); + + m_prevConnectionState = m_connectionState; + m_connectionState = state; + + if (m_connectionState == PVR_CONNECTION_STATE_CONNECTED) + m_ignoreClient = false; + else if (m_connectionState == PVR_CONNECTION_STATE_CONNECTING && + m_prevConnectionState == PVR_CONNECTION_STATE_UNKNOWN) + m_ignoreClient = true; // ignore until connected +} + +PVR_CONNECTION_STATE CPVRClient::GetPreviousConnectionState() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_prevConnectionState; +} + +bool CPVRClient::IgnoreClient() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_ignoreClient; +} + +bool CPVRClient::IsEnabled() const +{ + if (InstanceId() == ADDON_SINGLETON_INSTANCE_ID) + { + return !CServiceBroker::GetAddonMgr().IsAddonDisabled(ID()); + } + else + { + bool instanceEnabled{false}; + Addon()->ReloadSettings(InstanceId()); + Addon()->GetSettingBool(ADDON_SETTING_INSTANCE_ENABLED_VALUE, instanceEnabled, InstanceId()); + return instanceEnabled; + } +} + +int CPVRClient::GetID() const +{ + return m_iClientId; +} + +bool CPVRClient::GetAddonProperties() +{ + if (!GetAddonNameStringProperties()) + return false; + + PVR_ADDON_CAPABILITIES addonCapabilities = {}; + std::vector<std::shared_ptr<CPVRTimerType>> timerTypes; + + /* get the capabilities */ + PVR_ERROR retVal = DoAddonCall( + __func__, + [&addonCapabilities](const AddonInstance* addon) { + return addon->toAddon->GetCapabilities(addon, &addonCapabilities); + }, + true, false); + + if (retVal != PVR_ERROR_NO_ERROR) + return false; + + /* timer types */ + retVal = DoAddonCall( + __func__, + [this, &addonCapabilities, &timerTypes](const AddonInstance* addon) { + std::unique_ptr<PVR_TIMER_TYPE[]> types_array( + new PVR_TIMER_TYPE[PVR_ADDON_TIMERTYPE_ARRAY_SIZE]); + int size = PVR_ADDON_TIMERTYPE_ARRAY_SIZE; + + PVR_ERROR retval = addon->toAddon->GetTimerTypes(addon, types_array.get(), &size); + + if (retval == PVR_ERROR_NOT_IMPLEMENTED) + { + // begin compat section + CLog::LogF(LOGWARNING, + "Add-on {} does not support timer types. It will work, but not benefit from " + "the timer features introduced with PVR Addon API 2.0.0", + GetFriendlyName()); + + // Create standard timer types (mostly) matching the timer functionality available in Isengard. + // This is for migration only and does not make changes to the addons obsolete. Addons should + // work and benefit from some UI changes (e.g. some of the timer settings dialog enhancements), + // but all old problems/bugs due to static attributes and values will remain the same as in + // Isengard. Also, new features (like epg search) are not available to addons automatically. + // This code can be removed once all addons actually support the respective PVR Addon API version. + + size = 0; + // manual one time + memset(&types_array[size], 0, sizeof(types_array[size])); + types_array[size].iId = size + 1; + types_array[size].iAttributes = + PVR_TIMER_TYPE_IS_MANUAL | PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE | + PVR_TIMER_TYPE_SUPPORTS_CHANNELS | PVR_TIMER_TYPE_SUPPORTS_START_TIME | + PVR_TIMER_TYPE_SUPPORTS_END_TIME | PVR_TIMER_TYPE_SUPPORTS_PRIORITY | + PVR_TIMER_TYPE_SUPPORTS_LIFETIME | PVR_TIMER_TYPE_SUPPORTS_RECORDING_FOLDERS; + ++size; + + // manual timer rule + memset(&types_array[size], 0, sizeof(types_array[size])); + types_array[size].iId = size + 1; + types_array[size].iAttributes = + PVR_TIMER_TYPE_IS_MANUAL | PVR_TIMER_TYPE_IS_REPEATING | + PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE | PVR_TIMER_TYPE_SUPPORTS_CHANNELS | + PVR_TIMER_TYPE_SUPPORTS_START_TIME | PVR_TIMER_TYPE_SUPPORTS_END_TIME | + PVR_TIMER_TYPE_SUPPORTS_PRIORITY | PVR_TIMER_TYPE_SUPPORTS_LIFETIME | + PVR_TIMER_TYPE_SUPPORTS_FIRST_DAY | PVR_TIMER_TYPE_SUPPORTS_WEEKDAYS | + PVR_TIMER_TYPE_SUPPORTS_RECORDING_FOLDERS; + ++size; + + if (addonCapabilities.bSupportsEPG) + { + // One-shot epg-based + memset(&types_array[size], 0, sizeof(types_array[size])); + types_array[size].iId = size + 1; + types_array[size].iAttributes = + PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE | PVR_TIMER_TYPE_REQUIRES_EPG_TAG_ON_CREATE | + PVR_TIMER_TYPE_SUPPORTS_CHANNELS | PVR_TIMER_TYPE_SUPPORTS_START_TIME | + PVR_TIMER_TYPE_SUPPORTS_END_TIME | PVR_TIMER_TYPE_SUPPORTS_PRIORITY | + PVR_TIMER_TYPE_SUPPORTS_LIFETIME | PVR_TIMER_TYPE_SUPPORTS_RECORDING_FOLDERS; + ++size; + } + + retval = PVR_ERROR_NO_ERROR; + // end compat section + } + + if (retval == PVR_ERROR_NO_ERROR) + { + timerTypes.reserve(size); + for (int i = 0; i < size; ++i) + { + if (types_array[i].iId == PVR_TIMER_TYPE_NONE) + { + CLog::LogF(LOGERROR, "Invalid timer type supplied by add-on '{}'.", ID()); + continue; + } + timerTypes.emplace_back( + std::shared_ptr<CPVRTimerType>(new CPVRTimerType(types_array[i], m_iClientId))); + } + } + return retval; + }, + addonCapabilities.bSupportsTimers, false); + + if (retVal == PVR_ERROR_NOT_IMPLEMENTED) + retVal = PVR_ERROR_NO_ERROR; // timer support is optional. + + /* update the members */ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_clientCapabilities = addonCapabilities; + m_timertypes = timerTypes; + + return retVal == PVR_ERROR_NO_ERROR; +} + +bool CPVRClient::GetAddonNameStringProperties() +{ + char strBackendName[PVR_ADDON_NAME_STRING_LENGTH] = {}; + char strConnectionString[PVR_ADDON_NAME_STRING_LENGTH] = {}; + char strBackendVersion[PVR_ADDON_NAME_STRING_LENGTH] = {}; + char strBackendHostname[PVR_ADDON_NAME_STRING_LENGTH] = {}; + + /* get the name of the backend */ + PVR_ERROR retVal = DoAddonCall( + __func__, + [&strBackendName](const AddonInstance* addon) { + return addon->toAddon->GetBackendName(addon, strBackendName, sizeof(strBackendName)); + }, + true, false); + + if (retVal != PVR_ERROR_NO_ERROR) + return false; + + /* get the connection string */ + retVal = DoAddonCall( + __func__, + [&strConnectionString](const AddonInstance* addon) { + return addon->toAddon->GetConnectionString(addon, strConnectionString, + sizeof(strConnectionString)); + }, + true, false); + + if (retVal != PVR_ERROR_NO_ERROR && retVal != PVR_ERROR_NOT_IMPLEMENTED) + return false; + + /* backend version number */ + retVal = DoAddonCall( + __func__, + [&strBackendVersion](const AddonInstance* addon) { + return addon->toAddon->GetBackendVersion(addon, strBackendVersion, + sizeof(strBackendVersion)); + }, + true, false); + + if (retVal != PVR_ERROR_NO_ERROR) + return false; + + /* backend hostname */ + retVal = DoAddonCall( + __func__, + [&strBackendHostname](const AddonInstance* addon) { + return addon->toAddon->GetBackendHostname(addon, strBackendHostname, + sizeof(strBackendHostname)); + }, + true, false); + + if (retVal != PVR_ERROR_NO_ERROR && retVal != PVR_ERROR_NOT_IMPLEMENTED) + return false; + + /* update the members */ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strBackendName = strBackendName; + m_strConnectionString = strConnectionString; + m_strBackendVersion = strBackendVersion; + m_strBackendHostname = strBackendHostname; + + return true; +} + +const std::string& CPVRClient::GetBackendName() const +{ + return m_strBackendName; +} + +const std::string& CPVRClient::GetBackendVersion() const +{ + return m_strBackendVersion; +} + +const std::string& CPVRClient::GetBackendHostname() const +{ + return m_strBackendHostname; +} + +const std::string& CPVRClient::GetConnectionString() const +{ + return m_strConnectionString; +} + +const std::string CPVRClient::GetFriendlyName() const +{ + + if (Addon()->SupportsInstanceSettings()) + { + std::string instanceName; + Addon()->GetSettingString(ADDON_SETTING_INSTANCE_NAME_VALUE, instanceName, InstanceId()); + if (!instanceName.empty()) + return StringUtils::Format("{} ({})", Name(), instanceName); + } + return Name(); +} + +PVR_ERROR CPVRClient::GetDriveSpace(uint64_t& iTotal, uint64_t& iUsed) +{ + /* default to 0 in case of error */ + iTotal = 0; + iUsed = 0; + + return DoAddonCall(__func__, [&iTotal, &iUsed](const AddonInstance* addon) { + uint64_t iTotalSpace = 0; + uint64_t iUsedSpace = 0; + PVR_ERROR error = addon->toAddon->GetDriveSpace(addon, &iTotalSpace, &iUsedSpace); + if (error == PVR_ERROR_NO_ERROR) + { + iTotal = iTotalSpace; + iUsed = iUsedSpace; + } + return error; + }); +} + +PVR_ERROR CPVRClient::StartChannelScan() +{ + return DoAddonCall( + __func__, + [](const AddonInstance* addon) { return addon->toAddon->OpenDialogChannelScan(addon); }, + m_clientCapabilities.SupportsChannelScan()); +} + +PVR_ERROR CPVRClient::OpenDialogChannelAdd(const std::shared_ptr<CPVRChannel>& channel) +{ + return DoAddonCall( + __func__, + [channel](const AddonInstance* addon) { + PVR_CHANNEL addonChannel; + channel->FillAddonData(addonChannel); + return addon->toAddon->OpenDialogChannelAdd(addon, &addonChannel); + }, + m_clientCapabilities.SupportsChannelSettings()); +} + +PVR_ERROR CPVRClient::OpenDialogChannelSettings(const std::shared_ptr<CPVRChannel>& channel) +{ + return DoAddonCall( + __func__, + [channel](const AddonInstance* addon) { + PVR_CHANNEL addonChannel; + channel->FillAddonData(addonChannel); + return addon->toAddon->OpenDialogChannelSettings(addon, &addonChannel); + }, + m_clientCapabilities.SupportsChannelSettings()); +} + +PVR_ERROR CPVRClient::DeleteChannel(const std::shared_ptr<CPVRChannel>& channel) +{ + return DoAddonCall( + __func__, + [channel](const AddonInstance* addon) { + PVR_CHANNEL addonChannel; + channel->FillAddonData(addonChannel); + return addon->toAddon->DeleteChannel(addon, &addonChannel); + }, + m_clientCapabilities.SupportsChannelSettings()); +} + +PVR_ERROR CPVRClient::RenameChannel(const std::shared_ptr<CPVRChannel>& channel) +{ + return DoAddonCall( + __func__, + [channel](const AddonInstance* addon) { + PVR_CHANNEL addonChannel; + channel->FillAddonData(addonChannel); + strncpy(addonChannel.strChannelName, channel->ChannelName().c_str(), + sizeof(addonChannel.strChannelName) - 1); + return addon->toAddon->RenameChannel(addon, &addonChannel); + }, + m_clientCapabilities.SupportsChannelSettings()); +} + +PVR_ERROR CPVRClient::GetEPGForChannel(int iChannelUid, CPVREpg* epg, time_t start, time_t end) +{ + return DoAddonCall( + __func__, + [this, iChannelUid, epg, start, end](const AddonInstance* addon) { + PVR_HANDLE_STRUCT handle = {}; + handle.callerAddress = this; + handle.dataAddress = epg; + + int iPVRTimeCorrection = + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iPVRTimeCorrection; + + return addon->toAddon->GetEPGForChannel(addon, &handle, iChannelUid, + start ? start - iPVRTimeCorrection : 0, + end ? end - iPVRTimeCorrection : 0); + }, + m_clientCapabilities.SupportsEPG()); +} + +PVR_ERROR CPVRClient::SetEPGMaxPastDays(int iPastDays) +{ + return DoAddonCall( + __func__, + [iPastDays](const AddonInstance* addon) { + return addon->toAddon->SetEPGMaxPastDays(addon, iPastDays); + }, + m_clientCapabilities.SupportsEPG()); +} + +PVR_ERROR CPVRClient::SetEPGMaxFutureDays(int iFutureDays) +{ + return DoAddonCall( + __func__, + [iFutureDays](const AddonInstance* addon) { + return addon->toAddon->SetEPGMaxFutureDays(addon, iFutureDays); + }, + m_clientCapabilities.SupportsEPG()); +} + +// This class wraps an EPG_TAG (PVR Addon API struct) to ensure that the string members of +// that struct, which are const char pointers, stay valid until the EPG_TAG gets destructed. +// Please note that this struct is also used to transfer huge amount of EPG_TAGs from +// addon to Kodi. Thus, changing the struct to contain char arrays is not recommended, +// because this would lead to huge amount of string copies when transferring epg data +// from addon to Kodi. +class CAddonEpgTag : public EPG_TAG +{ +public: + CAddonEpgTag() = delete; + explicit CAddonEpgTag(const std::shared_ptr<const CPVREpgInfoTag>& kodiTag) + : m_strTitle(kodiTag->Title()), + m_strPlotOutline(kodiTag->PlotOutline()), + m_strPlot(kodiTag->Plot()), + m_strOriginalTitle(kodiTag->OriginalTitle()), + m_strCast(kodiTag->DeTokenize(kodiTag->Cast())), + m_strDirector(kodiTag->DeTokenize(kodiTag->Directors())), + m_strWriter(kodiTag->DeTokenize(kodiTag->Writers())), + m_strIMDBNumber(kodiTag->IMDBNumber()), + m_strEpisodeName(kodiTag->EpisodeName()), + m_strIconPath(kodiTag->ClientIconPath()), + m_strSeriesLink(kodiTag->SeriesLink()), + m_strGenreDescription(kodiTag->GenreDescription()), + m_strParentalRatingCode(kodiTag->ParentalRatingCode()) + { + time_t t; + kodiTag->StartAsUTC().GetAsTime(t); + startTime = t; + kodiTag->EndAsUTC().GetAsTime(t); + endTime = t; + + const CDateTime firstAired = kodiTag->FirstAired(); + if (firstAired.IsValid()) + m_strFirstAired = firstAired.GetAsW3CDate(); + + iUniqueBroadcastId = kodiTag->UniqueBroadcastID(); + iUniqueChannelId = kodiTag->UniqueChannelID(); + iParentalRating = kodiTag->ParentalRating(); + iSeriesNumber = kodiTag->SeriesNumber(); + iEpisodeNumber = kodiTag->EpisodeNumber(); + iEpisodePartNumber = kodiTag->EpisodePart(); + iStarRating = kodiTag->StarRating(); + iYear = kodiTag->Year(); + iFlags = kodiTag->Flags(); + iGenreType = kodiTag->GenreType(); + iGenreSubType = kodiTag->GenreSubType(); + strTitle = m_strTitle.c_str(); + strPlotOutline = m_strPlotOutline.c_str(); + strPlot = m_strPlot.c_str(); + strOriginalTitle = m_strOriginalTitle.c_str(); + strCast = m_strCast.c_str(); + strDirector = m_strDirector.c_str(); + strWriter = m_strWriter.c_str(); + strIMDBNumber = m_strIMDBNumber.c_str(); + strEpisodeName = m_strEpisodeName.c_str(); + strIconPath = m_strIconPath.c_str(); + strSeriesLink = m_strSeriesLink.c_str(); + strGenreDescription = m_strGenreDescription.c_str(); + strFirstAired = m_strFirstAired.c_str(); + strParentalRatingCode = m_strParentalRatingCode.c_str(); + } + + virtual ~CAddonEpgTag() = default; + +private: + std::string m_strTitle; + std::string m_strPlotOutline; + std::string m_strPlot; + std::string m_strOriginalTitle; + std::string m_strCast; + std::string m_strDirector; + std::string m_strWriter; + std::string m_strIMDBNumber; + std::string m_strEpisodeName; + std::string m_strIconPath; + std::string m_strSeriesLink; + std::string m_strGenreDescription; + std::string m_strFirstAired; + std::string m_strParentalRatingCode; +}; + +PVR_ERROR CPVRClient::IsRecordable(const std::shared_ptr<const CPVREpgInfoTag>& tag, + bool& bIsRecordable) const +{ + return DoAddonCall( + __func__, + [tag, &bIsRecordable](const AddonInstance* addon) { + CAddonEpgTag addonTag(tag); + return addon->toAddon->IsEPGTagRecordable(addon, &addonTag, &bIsRecordable); + }, + m_clientCapabilities.SupportsRecordings() && m_clientCapabilities.SupportsEPG()); +} + +PVR_ERROR CPVRClient::IsPlayable(const std::shared_ptr<const CPVREpgInfoTag>& tag, + bool& bIsPlayable) const +{ + return DoAddonCall( + __func__, + [tag, &bIsPlayable](const AddonInstance* addon) { + CAddonEpgTag addonTag(tag); + return addon->toAddon->IsEPGTagPlayable(addon, &addonTag, &bIsPlayable); + }, + m_clientCapabilities.SupportsEPG()); +} + +void CPVRClient::WriteStreamProperties(const PVR_NAMED_VALUE* properties, + unsigned int iPropertyCount, + CPVRStreamProperties& props) +{ + for (unsigned int i = 0; i < iPropertyCount; ++i) + { + props.emplace_back(std::make_pair(properties[i].strName, properties[i].strValue)); + } +} + +PVR_ERROR CPVRClient::GetEpgTagStreamProperties(const std::shared_ptr<CPVREpgInfoTag>& tag, + CPVRStreamProperties& props) +{ + return DoAddonCall(__func__, [&tag, &props](const AddonInstance* addon) { + CAddonEpgTag addonTag(tag); + + unsigned int iPropertyCount = STREAM_MAX_PROPERTY_COUNT; + std::unique_ptr<PVR_NAMED_VALUE[]> properties(new PVR_NAMED_VALUE[iPropertyCount]); + memset(properties.get(), 0, iPropertyCount * sizeof(PVR_NAMED_VALUE)); + + PVR_ERROR error = addon->toAddon->GetEPGTagStreamProperties(addon, &addonTag, properties.get(), + &iPropertyCount); + if (error == PVR_ERROR_NO_ERROR) + WriteStreamProperties(properties.get(), iPropertyCount, props); + + return error; + }); +} + +PVR_ERROR CPVRClient::GetEpgTagEdl(const std::shared_ptr<const CPVREpgInfoTag>& epgTag, + std::vector<PVR_EDL_ENTRY>& edls) +{ + edls.clear(); + return DoAddonCall( + __func__, + [&epgTag, &edls](const AddonInstance* addon) { + CAddonEpgTag addonTag(epgTag); + + PVR_EDL_ENTRY edl_array[PVR_ADDON_EDL_LENGTH]; + int size = PVR_ADDON_EDL_LENGTH; + PVR_ERROR error = addon->toAddon->GetEPGTagEdl(addon, &addonTag, edl_array, &size); + if (error == PVR_ERROR_NO_ERROR) + { + edls.reserve(size); + for (int i = 0; i < size; ++i) + edls.emplace_back(edl_array[i]); + } + return error; + }, + m_clientCapabilities.SupportsEpgTagEdl()); +} + +PVR_ERROR CPVRClient::GetChannelGroupsAmount(int& iGroups) +{ + iGroups = -1; + return DoAddonCall( + __func__, + [&iGroups](const AddonInstance* addon) { + return addon->toAddon->GetChannelGroupsAmount(addon, &iGroups); + }, + m_clientCapabilities.SupportsChannelGroups()); +} + +PVR_ERROR CPVRClient::GetChannelGroups(CPVRChannelGroups* groups) +{ + return DoAddonCall(__func__, + [this, groups](const AddonInstance* addon) { + PVR_HANDLE_STRUCT handle = {}; + handle.callerAddress = this; + handle.dataAddress = groups; + return addon->toAddon->GetChannelGroups(addon, &handle, groups->IsRadio()); + }, + m_clientCapabilities.SupportsChannelGroups()); +} + +PVR_ERROR CPVRClient::GetChannelGroupMembers( + CPVRChannelGroup* group, std::vector<std::shared_ptr<CPVRChannelGroupMember>>& groupMembers) +{ + return DoAddonCall(__func__, + [this, group, &groupMembers](const AddonInstance* addon) { + PVR_HANDLE_STRUCT handle = {}; + handle.callerAddress = this; + handle.dataAddress = &groupMembers; + + PVR_CHANNEL_GROUP tag; + group->FillAddonData(tag); + return addon->toAddon->GetChannelGroupMembers(addon, &handle, &tag); + }, + m_clientCapabilities.SupportsChannelGroups()); +} + +PVR_ERROR CPVRClient::GetProvidersAmount(int& iProviders) +{ + iProviders = -1; + return DoAddonCall(__func__, [&iProviders](const AddonInstance* addon) { + return addon->toAddon->GetProvidersAmount(addon, &iProviders); + }); +} + +PVR_ERROR CPVRClient::GetProviders(CPVRProvidersContainer& providers) +{ + return DoAddonCall(__func__, + [this, &providers](const AddonInstance* addon) { + PVR_HANDLE_STRUCT handle = {}; + handle.callerAddress = this; + handle.dataAddress = &providers; + return addon->toAddon->GetProviders(addon, &handle); + }, + m_clientCapabilities.SupportsProviders()); +} + +PVR_ERROR CPVRClient::GetChannelsAmount(int& iChannels) +{ + iChannels = -1; + return DoAddonCall(__func__, [&iChannels](const AddonInstance* addon) { + return addon->toAddon->GetChannelsAmount(addon, &iChannels); + }); +} + +PVR_ERROR CPVRClient::GetChannels(bool radio, std::vector<std::shared_ptr<CPVRChannel>>& channels) +{ + return DoAddonCall(__func__, + [this, radio, &channels](const AddonInstance* addon) { + PVR_HANDLE_STRUCT handle = {}; + handle.callerAddress = this; + handle.dataAddress = &channels; + return addon->toAddon->GetChannels(addon, &handle, radio); + }, + (radio && m_clientCapabilities.SupportsRadio()) || + (!radio && m_clientCapabilities.SupportsTV())); +} + +PVR_ERROR CPVRClient::GetRecordingsAmount(bool deleted, int& iRecordings) +{ + iRecordings = -1; + return DoAddonCall( + __func__, + [deleted, &iRecordings](const AddonInstance* addon) { + return addon->toAddon->GetRecordingsAmount(addon, deleted, &iRecordings); + }, + m_clientCapabilities.SupportsRecordings() && + (!deleted || m_clientCapabilities.SupportsRecordingsUndelete())); +} + +PVR_ERROR CPVRClient::GetRecordings(CPVRRecordings* results, bool deleted) +{ + return DoAddonCall(__func__, + [this, results, deleted](const AddonInstance* addon) { + PVR_HANDLE_STRUCT handle = {}; + handle.callerAddress = this; + handle.dataAddress = results; + return addon->toAddon->GetRecordings(addon, &handle, deleted); + }, + m_clientCapabilities.SupportsRecordings() && + (!deleted || m_clientCapabilities.SupportsRecordingsUndelete())); +} + +PVR_ERROR CPVRClient::DeleteRecording(const CPVRRecording& recording) +{ + return DoAddonCall( + __func__, + [&recording](const AddonInstance* addon) { + PVR_RECORDING tag; + recording.FillAddonData(tag); + return addon->toAddon->DeleteRecording(addon, &tag); + }, + m_clientCapabilities.SupportsRecordings() && m_clientCapabilities.SupportsRecordingsDelete()); +} + +PVR_ERROR CPVRClient::UndeleteRecording(const CPVRRecording& recording) +{ + return DoAddonCall( + __func__, + [&recording](const AddonInstance* addon) { + PVR_RECORDING tag; + recording.FillAddonData(tag); + return addon->toAddon->UndeleteRecording(addon, &tag); + }, + m_clientCapabilities.SupportsRecordingsUndelete()); +} + +PVR_ERROR CPVRClient::DeleteAllRecordingsFromTrash() +{ + return DoAddonCall( + __func__, + [](const AddonInstance* addon) { + return addon->toAddon->DeleteAllRecordingsFromTrash(addon); + }, + m_clientCapabilities.SupportsRecordingsUndelete()); +} + +PVR_ERROR CPVRClient::RenameRecording(const CPVRRecording& recording) +{ + return DoAddonCall( + __func__, + [&recording](const AddonInstance* addon) { + PVR_RECORDING tag; + recording.FillAddonData(tag); + return addon->toAddon->RenameRecording(addon, &tag); + }, + m_clientCapabilities.SupportsRecordings()); +} + +PVR_ERROR CPVRClient::SetRecordingLifetime(const CPVRRecording& recording) +{ + return DoAddonCall( + __func__, + [&recording](const AddonInstance* addon) { + PVR_RECORDING tag; + recording.FillAddonData(tag); + return addon->toAddon->SetRecordingLifetime(addon, &tag); + }, + m_clientCapabilities.SupportsRecordingsLifetimeChange()); +} + +PVR_ERROR CPVRClient::SetRecordingPlayCount(const CPVRRecording& recording, int count) +{ + return DoAddonCall( + __func__, + [&recording, count](const AddonInstance* addon) { + PVR_RECORDING tag; + recording.FillAddonData(tag); + return addon->toAddon->SetRecordingPlayCount(addon, &tag, count); + }, + m_clientCapabilities.SupportsRecordingsPlayCount()); +} + +PVR_ERROR CPVRClient::SetRecordingLastPlayedPosition(const CPVRRecording& recording, + int lastplayedposition) +{ + return DoAddonCall( + __func__, + [&recording, lastplayedposition](const AddonInstance* addon) { + PVR_RECORDING tag; + recording.FillAddonData(tag); + return addon->toAddon->SetRecordingLastPlayedPosition(addon, &tag, lastplayedposition); + }, + m_clientCapabilities.SupportsRecordingsLastPlayedPosition()); +} + +PVR_ERROR CPVRClient::GetRecordingLastPlayedPosition(const CPVRRecording& recording, int& iPosition) +{ + iPosition = -1; + return DoAddonCall( + __func__, + [&recording, &iPosition](const AddonInstance* addon) { + PVR_RECORDING tag; + recording.FillAddonData(tag); + return addon->toAddon->GetRecordingLastPlayedPosition(addon, &tag, &iPosition); + }, + m_clientCapabilities.SupportsRecordingsLastPlayedPosition()); +} + +PVR_ERROR CPVRClient::GetRecordingEdl(const CPVRRecording& recording, + std::vector<PVR_EDL_ENTRY>& edls) +{ + edls.clear(); + return DoAddonCall( + __func__, + [&recording, &edls](const AddonInstance* addon) { + PVR_RECORDING tag; + recording.FillAddonData(tag); + + PVR_EDL_ENTRY edl_array[PVR_ADDON_EDL_LENGTH]; + int size = PVR_ADDON_EDL_LENGTH; + PVR_ERROR error = addon->toAddon->GetRecordingEdl(addon, &tag, edl_array, &size); + if (error == PVR_ERROR_NO_ERROR) + { + edls.reserve(size); + for (int i = 0; i < size; ++i) + edls.emplace_back(edl_array[i]); + } + return error; + }, + m_clientCapabilities.SupportsRecordingsEdl()); +} + +PVR_ERROR CPVRClient::GetRecordingSize(const CPVRRecording& recording, int64_t& sizeInBytes) +{ + return DoAddonCall( + __func__, + [&recording, &sizeInBytes](const AddonInstance* addon) { + PVR_RECORDING tag; + recording.FillAddonData(tag); + return addon->toAddon->GetRecordingSize(addon, &tag, &sizeInBytes); + }, + m_clientCapabilities.SupportsRecordingsSize()); +} + +PVR_ERROR CPVRClient::GetTimersAmount(int& iTimers) +{ + iTimers = -1; + return DoAddonCall( + __func__, + [&iTimers](const AddonInstance* addon) { + return addon->toAddon->GetTimersAmount(addon, &iTimers); + }, + m_clientCapabilities.SupportsTimers()); +} + +PVR_ERROR CPVRClient::GetTimers(CPVRTimersContainer* results) +{ + return DoAddonCall(__func__, + [this, results](const AddonInstance* addon) { + PVR_HANDLE_STRUCT handle = {}; + handle.callerAddress = this; + handle.dataAddress = results; + return addon->toAddon->GetTimers(addon, &handle); + }, + m_clientCapabilities.SupportsTimers()); +} + +PVR_ERROR CPVRClient::AddTimer(const CPVRTimerInfoTag& timer) +{ + return DoAddonCall( + __func__, + [&timer](const AddonInstance* addon) { + PVR_TIMER tag; + timer.FillAddonData(tag); + return addon->toAddon->AddTimer(addon, &tag); + }, + m_clientCapabilities.SupportsTimers()); +} + +PVR_ERROR CPVRClient::DeleteTimer(const CPVRTimerInfoTag& timer, bool bForce /* = false */) +{ + return DoAddonCall( + __func__, + [&timer, bForce](const AddonInstance* addon) { + PVR_TIMER tag; + timer.FillAddonData(tag); + return addon->toAddon->DeleteTimer(addon, &tag, bForce); + }, + m_clientCapabilities.SupportsTimers()); +} + +PVR_ERROR CPVRClient::UpdateTimer(const CPVRTimerInfoTag& timer) +{ + return DoAddonCall( + __func__, + [&timer](const AddonInstance* addon) { + PVR_TIMER tag; + timer.FillAddonData(tag); + return addon->toAddon->UpdateTimer(addon, &tag); + }, + m_clientCapabilities.SupportsTimers()); +} + +PVR_ERROR CPVRClient::GetTimerTypes(std::vector<std::shared_ptr<CPVRTimerType>>& results) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + results = m_timertypes; + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR CPVRClient::GetStreamReadChunkSize(int& iChunkSize) +{ + return DoAddonCall( + __func__, + [&iChunkSize](const AddonInstance* addon) { + return addon->toAddon->GetStreamReadChunkSize(addon, &iChunkSize); + }, + m_clientCapabilities.SupportsRecordings() || m_clientCapabilities.HandlesInputStream()); +} + +PVR_ERROR CPVRClient::ReadLiveStream(void* lpBuf, int64_t uiBufSize, int& iRead) +{ + iRead = -1; + return DoAddonCall(__func__, [&lpBuf, uiBufSize, &iRead](const AddonInstance* addon) { + iRead = addon->toAddon->ReadLiveStream(addon, static_cast<unsigned char*>(lpBuf), + static_cast<int>(uiBufSize)); + return (iRead == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::ReadRecordedStream(void* lpBuf, int64_t uiBufSize, int& iRead) +{ + iRead = -1; + return DoAddonCall(__func__, [&lpBuf, uiBufSize, &iRead](const AddonInstance* addon) { + iRead = addon->toAddon->ReadRecordedStream(addon, static_cast<unsigned char*>(lpBuf), + static_cast<int>(uiBufSize)); + return (iRead == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::SeekLiveStream(int64_t iFilePosition, int iWhence, int64_t& iPosition) +{ + iPosition = -1; + return DoAddonCall(__func__, [iFilePosition, iWhence, &iPosition](const AddonInstance* addon) { + iPosition = addon->toAddon->SeekLiveStream(addon, iFilePosition, iWhence); + return (iPosition == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::SeekRecordedStream(int64_t iFilePosition, int iWhence, int64_t& iPosition) +{ + iPosition = -1; + return DoAddonCall(__func__, [iFilePosition, iWhence, &iPosition](const AddonInstance* addon) { + iPosition = addon->toAddon->SeekRecordedStream(addon, iFilePosition, iWhence); + return (iPosition == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::SeekTime(double time, bool backwards, double* startpts) +{ + return DoAddonCall(__func__, [time, backwards, &startpts](const AddonInstance* addon) { + return addon->toAddon->SeekTime(addon, time, backwards, startpts) ? PVR_ERROR_NO_ERROR + : PVR_ERROR_NOT_IMPLEMENTED; + }); +} + +PVR_ERROR CPVRClient::GetLiveStreamLength(int64_t& iLength) +{ + iLength = -1; + return DoAddonCall(__func__, [&iLength](const AddonInstance* addon) { + iLength = addon->toAddon->LengthLiveStream(addon); + return (iLength == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::GetRecordedStreamLength(int64_t& iLength) +{ + iLength = -1; + return DoAddonCall(__func__, [&iLength](const AddonInstance* addon) { + iLength = addon->toAddon->LengthRecordedStream(addon); + return (iLength == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::SignalQuality(int channelUid, PVR_SIGNAL_STATUS& qualityinfo) +{ + return DoAddonCall(__func__, [channelUid, &qualityinfo](const AddonInstance* addon) { + return addon->toAddon->GetSignalStatus(addon, channelUid, &qualityinfo); + }); +} + +PVR_ERROR CPVRClient::GetDescrambleInfo(int channelUid, PVR_DESCRAMBLE_INFO& descrambleinfo) const +{ + return DoAddonCall( + __func__, + [channelUid, &descrambleinfo](const AddonInstance* addon) { + return addon->toAddon->GetDescrambleInfo(addon, channelUid, &descrambleinfo); + }, + m_clientCapabilities.SupportsDescrambleInfo()); +} + +PVR_ERROR CPVRClient::GetChannelStreamProperties(const std::shared_ptr<CPVRChannel>& channel, + CPVRStreamProperties& props) +{ + return DoAddonCall(__func__, [this, &channel, &props](const AddonInstance* addon) { + if (!CanPlayChannel(channel)) + return PVR_ERROR_NO_ERROR; // no error, but no need to obtain the values from the addon + + PVR_CHANNEL tag = {}; + channel->FillAddonData(tag); + + unsigned int iPropertyCount = STREAM_MAX_PROPERTY_COUNT; + std::unique_ptr<PVR_NAMED_VALUE[]> properties(new PVR_NAMED_VALUE[iPropertyCount]); + memset(properties.get(), 0, iPropertyCount * sizeof(PVR_NAMED_VALUE)); + + PVR_ERROR error = + addon->toAddon->GetChannelStreamProperties(addon, &tag, properties.get(), &iPropertyCount); + if (error == PVR_ERROR_NO_ERROR) + WriteStreamProperties(properties.get(), iPropertyCount, props); + + return error; + }); +} + +PVR_ERROR CPVRClient::GetRecordingStreamProperties(const std::shared_ptr<CPVRRecording>& recording, + CPVRStreamProperties& props) +{ + return DoAddonCall(__func__, [this, &recording, &props](const AddonInstance* addon) { + if (!m_clientCapabilities.SupportsRecordings()) + return PVR_ERROR_NO_ERROR; // no error, but no need to obtain the values from the addon + + PVR_RECORDING tag = {}; + recording->FillAddonData(tag); + + unsigned int iPropertyCount = STREAM_MAX_PROPERTY_COUNT; + std::unique_ptr<PVR_NAMED_VALUE[]> properties(new PVR_NAMED_VALUE[iPropertyCount]); + memset(properties.get(), 0, iPropertyCount * sizeof(PVR_NAMED_VALUE)); + + PVR_ERROR error = addon->toAddon->GetRecordingStreamProperties(addon, &tag, properties.get(), + &iPropertyCount); + if (error == PVR_ERROR_NO_ERROR) + WriteStreamProperties(properties.get(), iPropertyCount, props); + + return error; + }); +} + +PVR_ERROR CPVRClient::GetStreamProperties(PVR_STREAM_PROPERTIES* props) +{ + return DoAddonCall(__func__, [&props](const AddonInstance* addon) { + return addon->toAddon->GetStreamProperties(addon, props); + }); +} + +PVR_ERROR CPVRClient::DemuxReset() +{ + return DoAddonCall( + __func__, + [](const AddonInstance* addon) { + addon->toAddon->DemuxReset(addon); + return PVR_ERROR_NO_ERROR; + }, + m_clientCapabilities.HandlesDemuxing()); +} + +PVR_ERROR CPVRClient::DemuxAbort() +{ + return DoAddonCall( + __func__, + [](const AddonInstance* addon) { + addon->toAddon->DemuxAbort(addon); + return PVR_ERROR_NO_ERROR; + }, + m_clientCapabilities.HandlesDemuxing()); +} + +PVR_ERROR CPVRClient::DemuxFlush() +{ + return DoAddonCall( + __func__, + [](const AddonInstance* addon) { + addon->toAddon->DemuxFlush(addon); + return PVR_ERROR_NO_ERROR; + }, + m_clientCapabilities.HandlesDemuxing()); +} + +PVR_ERROR CPVRClient::DemuxRead(DemuxPacket*& packet) +{ + return DoAddonCall( + __func__, + [&packet](const AddonInstance* addon) { + packet = static_cast<DemuxPacket*>(addon->toAddon->DemuxRead(addon)); + return packet ? PVR_ERROR_NO_ERROR : PVR_ERROR_NOT_IMPLEMENTED; + }, + m_clientCapabilities.HandlesDemuxing()); +} + +const char* CPVRClient::ToString(const PVR_ERROR error) +{ + switch (error) + { + case PVR_ERROR_NO_ERROR: + return "no error"; + case PVR_ERROR_NOT_IMPLEMENTED: + return "not implemented"; + case PVR_ERROR_SERVER_ERROR: + return "server error"; + case PVR_ERROR_SERVER_TIMEOUT: + return "server timeout"; + case PVR_ERROR_RECORDING_RUNNING: + return "recording already running"; + case PVR_ERROR_ALREADY_PRESENT: + return "already present"; + case PVR_ERROR_REJECTED: + return "rejected by the backend"; + case PVR_ERROR_INVALID_PARAMETERS: + return "invalid parameters for this method"; + case PVR_ERROR_FAILED: + return "the command failed"; + case PVR_ERROR_UNKNOWN: + default: + return "unknown error"; + } +} + +PVR_ERROR CPVRClient::DoAddonCall(const char* strFunctionName, + const std::function<PVR_ERROR(const AddonInstance*)>& function, + bool bIsImplemented /* = true */, + bool bCheckReadyToUse /* = true */) const +{ + // Check preconditions. + if (!bIsImplemented) + return PVR_ERROR_NOT_IMPLEMENTED; + + if (m_bBlockAddonCalls) + { + CLog::Log(LOGWARNING, "{}: Blocking call to add-on '{}'.", strFunctionName, ID()); + return PVR_ERROR_SERVER_ERROR; + } + + if (bCheckReadyToUse && IgnoreClient()) + { + CLog::Log(LOGWARNING, "{}: Blocking call to add-on '{}'. Add-on not (yet) connected.", + strFunctionName, ID()); + return PVR_ERROR_SERVER_ERROR; + } + + if (bCheckReadyToUse && !ReadyToUse()) + { + CLog::Log(LOGWARNING, "{}: Blocking call to add-on '{}'. Add-on not ready to use.", + strFunctionName, ID()); + return PVR_ERROR_SERVER_ERROR; + } + + // Call. + m_allAddonCallsFinished.Reset(); + m_iAddonCalls++; + + const PVR_ERROR error = function(m_ifc.pvr); + + 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::Log(LOGERROR, "{}: Add-on '{}' returned an error: {}", strFunctionName, ID(), + ToString(error)); + + return error; +} + +bool CPVRClient::CanPlayChannel(const std::shared_ptr<CPVRChannel>& channel) const +{ + return (m_bReadyToUse && ((m_clientCapabilities.SupportsTV() && !channel->IsRadio()) || + (m_clientCapabilities.SupportsRadio() && channel->IsRadio()))); +} + +PVR_ERROR CPVRClient::OpenLiveStream(const std::shared_ptr<CPVRChannel>& channel) +{ + if (!channel) + return PVR_ERROR_INVALID_PARAMETERS; + + return DoAddonCall(__func__, [this, channel](const AddonInstance* addon) { + CloseLiveStream(); + + if (!CanPlayChannel(channel)) + { + CLog::LogFC(LOGDEBUG, LOGPVR, "Add-on '{}' can not play channel '{}'", ID(), + channel->ChannelName()); + return PVR_ERROR_SERVER_ERROR; + } + else + { + CLog::LogFC(LOGDEBUG, LOGPVR, "Opening live stream for channel '{}'", channel->ChannelName()); + PVR_CHANNEL tag; + channel->FillAddonData(tag); + return addon->toAddon->OpenLiveStream(addon, &tag) ? PVR_ERROR_NO_ERROR + : PVR_ERROR_NOT_IMPLEMENTED; + } + }); +} + +PVR_ERROR CPVRClient::OpenRecordedStream(const std::shared_ptr<CPVRRecording>& recording) +{ + if (!recording) + return PVR_ERROR_INVALID_PARAMETERS; + + return DoAddonCall( + __func__, + [this, recording](const AddonInstance* addon) { + CloseRecordedStream(); + + PVR_RECORDING tag; + recording->FillAddonData(tag); + CLog::LogFC(LOGDEBUG, LOGPVR, "Opening stream for recording '{}'", recording->m_strTitle); + return addon->toAddon->OpenRecordedStream(addon, &tag) ? PVR_ERROR_NO_ERROR + : PVR_ERROR_NOT_IMPLEMENTED; + }, + m_clientCapabilities.SupportsRecordings()); +} + +PVR_ERROR CPVRClient::CloseLiveStream() +{ + return DoAddonCall(__func__, [](const AddonInstance* addon) { + addon->toAddon->CloseLiveStream(addon); + return PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::CloseRecordedStream() +{ + return DoAddonCall(__func__, [](const AddonInstance* addon) { + addon->toAddon->CloseRecordedStream(addon); + return PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::PauseStream(bool bPaused) +{ + return DoAddonCall(__func__, [bPaused](const AddonInstance* addon) { + addon->toAddon->PauseStream(addon, bPaused); + return PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::SetSpeed(int speed) +{ + return DoAddonCall(__func__, [speed](const AddonInstance* addon) { + addon->toAddon->SetSpeed(addon, speed); + return PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::FillBuffer(bool mode) +{ + return DoAddonCall(__func__, [mode](const AddonInstance* addon) { + addon->toAddon->FillBuffer(addon, mode); + return PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::CanPauseStream(bool& bCanPause) const +{ + bCanPause = false; + return DoAddonCall(__func__, [&bCanPause](const AddonInstance* addon) { + bCanPause = addon->toAddon->CanPauseStream(addon); + return PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::CanSeekStream(bool& bCanSeek) const +{ + bCanSeek = false; + return DoAddonCall(__func__, [&bCanSeek](const AddonInstance* addon) { + bCanSeek = addon->toAddon->CanSeekStream(addon); + return PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::GetStreamTimes(PVR_STREAM_TIMES* times) +{ + return DoAddonCall(__func__, [×](const AddonInstance* addon) { + return addon->toAddon->GetStreamTimes(addon, times); + }); +} + +PVR_ERROR CPVRClient::IsRealTimeStream(bool& bRealTime) const +{ + bRealTime = false; + return DoAddonCall(__func__, [&bRealTime](const AddonInstance* addon) { + bRealTime = addon->toAddon->IsRealTimeStream(addon); + return PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::OnSystemSleep() +{ + return DoAddonCall( + __func__, [](const AddonInstance* addon) { return addon->toAddon->OnSystemSleep(addon); }); +} + +PVR_ERROR CPVRClient::OnSystemWake() +{ + return DoAddonCall( + __func__, [](const AddonInstance* addon) { return addon->toAddon->OnSystemWake(addon); }); +} + +PVR_ERROR CPVRClient::OnPowerSavingActivated() +{ + return DoAddonCall(__func__, [](const AddonInstance* addon) { + return addon->toAddon->OnPowerSavingActivated(addon); + }); +} + +PVR_ERROR CPVRClient::OnPowerSavingDeactivated() +{ + return DoAddonCall(__func__, [](const AddonInstance* addon) { + return addon->toAddon->OnPowerSavingDeactivated(addon); + }); +} + +std::shared_ptr<CPVRClientMenuHooks> CPVRClient::GetMenuHooks() +{ + if (!m_menuhooks) + m_menuhooks.reset(new CPVRClientMenuHooks(ID())); + + return m_menuhooks; +} + +PVR_ERROR CPVRClient::CallEpgTagMenuHook(const CPVRClientMenuHook& hook, + const std::shared_ptr<CPVREpgInfoTag>& tag) +{ + return DoAddonCall(__func__, [&hook, &tag](const AddonInstance* addon) { + CAddonEpgTag addonTag(tag); + + PVR_MENUHOOK menuHook; + menuHook.category = PVR_MENUHOOK_EPG; + menuHook.iHookId = hook.GetId(); + menuHook.iLocalizedStringId = hook.GetLabelId(); + + return addon->toAddon->CallEPGMenuHook(addon, &menuHook, &addonTag); + }); +} + +PVR_ERROR CPVRClient::CallChannelMenuHook(const CPVRClientMenuHook& hook, + const std::shared_ptr<CPVRChannel>& channel) +{ + return DoAddonCall(__func__, [&hook, &channel](const AddonInstance* addon) { + PVR_CHANNEL tag; + channel->FillAddonData(tag); + + PVR_MENUHOOK menuHook; + menuHook.category = PVR_MENUHOOK_CHANNEL; + menuHook.iHookId = hook.GetId(); + menuHook.iLocalizedStringId = hook.GetLabelId(); + + return addon->toAddon->CallChannelMenuHook(addon, &menuHook, &tag); + }); +} + +PVR_ERROR CPVRClient::CallRecordingMenuHook(const CPVRClientMenuHook& hook, + const std::shared_ptr<CPVRRecording>& recording, + bool bDeleted) +{ + return DoAddonCall(__func__, [&hook, &recording, &bDeleted](const AddonInstance* addon) { + PVR_RECORDING tag; + recording->FillAddonData(tag); + + PVR_MENUHOOK menuHook; + menuHook.category = bDeleted ? PVR_MENUHOOK_DELETED_RECORDING : PVR_MENUHOOK_RECORDING; + menuHook.iHookId = hook.GetId(); + menuHook.iLocalizedStringId = hook.GetLabelId(); + + return addon->toAddon->CallRecordingMenuHook(addon, &menuHook, &tag); + }); +} + +PVR_ERROR CPVRClient::CallTimerMenuHook(const CPVRClientMenuHook& hook, + const std::shared_ptr<CPVRTimerInfoTag>& timer) +{ + return DoAddonCall(__func__, [&hook, &timer](const AddonInstance* addon) { + PVR_TIMER tag; + timer->FillAddonData(tag); + + PVR_MENUHOOK menuHook; + menuHook.category = PVR_MENUHOOK_TIMER; + menuHook.iHookId = hook.GetId(); + menuHook.iLocalizedStringId = hook.GetLabelId(); + + return addon->toAddon->CallTimerMenuHook(addon, &menuHook, &tag); + }); +} + +PVR_ERROR CPVRClient::CallSettingsMenuHook(const CPVRClientMenuHook& hook) +{ + return DoAddonCall(__func__, [&hook](const AddonInstance* addon) { + PVR_MENUHOOK menuHook; + menuHook.category = PVR_MENUHOOK_SETTING; + menuHook.iHookId = hook.GetId(); + menuHook.iLocalizedStringId = hook.GetLabelId(); + + return addon->toAddon->CallSettingsMenuHook(addon, &menuHook); + }); +} + +void CPVRClient::SetPriority(int iPriority) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (m_iPriority != iPriority) + { + m_iPriority = iPriority; + if (m_iClientId > PVR_INVALID_CLIENT_ID) + { + CServiceBroker::GetPVRManager().GetTVDatabase()->Persist(*this); + m_bPriorityFetched = true; + } + } +} + +int CPVRClient::GetPriority() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (!m_bPriorityFetched && m_iClientId > PVR_INVALID_CLIENT_ID) + { + m_iPriority = CServiceBroker::GetPVRManager().GetTVDatabase()->GetPriority(*this); + m_bPriorityFetched = true; + } + return m_iPriority; +} + +void CPVRClient::HandleAddonCallback(const char* strFunctionName, + void* kodiInstance, + const std::function<void(CPVRClient* client)>& function, + bool bForceCall /* = false */) +{ + // Check preconditions. + CPVRClient* client = static_cast<CPVRClient*>(kodiInstance); + if (!client) + { + CLog::Log(LOGERROR, "{}: No instance pointer given!", strFunctionName); + return; + } + + if (!bForceCall && client->m_bBlockAddonCalls && client->m_iAddonCalls == 0) + { + CLog::Log(LOGWARNING, LOGPVR, "{}: Ignoring callback from PVR client '{}'", strFunctionName, + client->ID()); + return; + } + + // Call. + function(client); +} + +void CPVRClient::cb_transfer_channel_group(void* kodiInstance, + const PVR_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); + const auto transferGroup = + std::make_shared<CPVRChannelGroup>(*group, kodiGroups->GetGroupAll()); + kodiGroups->UpdateFromClient(transferGroup); + }); +} + +void CPVRClient::cb_transfer_channel_group_member(void* kodiInstance, + const PVR_HANDLE handle, + const PVR_CHANNEL_GROUP_MEMBER* member) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!handle || !member) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } + + 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 + { + auto* groupMembers = + static_cast<std::vector<std::shared_ptr<CPVRChannelGroupMember>>*>(handle->dataAddress); + groupMembers->emplace_back( + std::make_shared<CPVRChannelGroupMember>(member->strGroupName, member->iOrder, channel)); + } + }); +} + +void CPVRClient::cb_transfer_epg_entry(void* kodiInstance, + const PVR_HANDLE handle, + const EPG_TAG* epgentry) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!handle || !epgentry) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } + + // transfer this entry to the epg + CPVREpg* epg = static_cast<CPVREpg*>(handle->dataAddress); + epg->UpdateEntry(epgentry, client->GetID()); + }); +} + +void CPVRClient::cb_transfer_provider_entry(void* kodiInstance, + const PVR_HANDLE handle, + const PVR_PROVIDER* provider) +{ + if (!handle) + { + CLog::LogF(LOGERROR, "Invalid handler data"); + return; + } + + CPVRClient* client = static_cast<CPVRClient*>(kodiInstance); + CPVRProvidersContainer* kodiProviders = static_cast<CPVRProvidersContainer*>(handle->dataAddress); + if (!provider || !client || !kodiProviders) + { + CLog::LogF(LOGERROR, "Invalid handler data"); + return; + } + + /* transfer this entry to the internal channels group */ + std::shared_ptr<CPVRProvider> transferProvider( + std::make_shared<CPVRProvider>(*provider, client->GetID())); + kodiProviders->UpdateFromClient(transferProvider); +} + +void CPVRClient::cb_transfer_channel_entry(void* kodiInstance, + const PVR_HANDLE handle, + const PVR_CHANNEL* channel) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!handle || !channel) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } + + auto* channels = static_cast<std::vector<std::shared_ptr<CPVRChannel>>*>(handle->dataAddress); + channels->emplace_back(std::make_shared<CPVRChannel>(*channel, client->GetID())); + }); +} + +void CPVRClient::cb_transfer_recording_entry(void* kodiInstance, + const PVR_HANDLE handle, + const PVR_RECORDING* recording) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!handle || !recording) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } + + // 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 PVR_HANDLE handle, + const PVR_TIMER* timer) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!handle || !timer) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } + + // 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) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!hook) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } + + client->GetMenuHooks()->AddHook(*hook); + }); +} + +void CPVRClient::cb_recording_notification(void* kodiInstance, + const char* strName, + const char* strFileName, + bool bOnOff) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!strFileName) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } + + const std::string strLine1 = StringUtils::Format(g_localizeStrings.Get(bOnOff ? 19197 : 19198), + client->GetFriendlyName()); + std::string strLine2; + if (strName) + strLine2 = strName; + else + strLine2 = strFileName; + + // display a notification for 5 seconds + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, strLine1, strLine2, 5000, + false); + auto eventLog = CServiceBroker::GetEventLog(); + if (eventLog) + eventLog->Add(EventPtr( + new CNotificationEvent(client->GetFriendlyName(), strLine1, client->Icon(), strLine2))); + + CLog::LogFC(LOGDEBUG, LOGPVR, "Recording {} on client '{}'. name='{}' filename='{}'", + bOnOff ? "started" : "finished", client->ID(), strName, strFileName); + }); +} + +void CPVRClient::cb_trigger_channel_update(void* kodiInstance) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + // update channels in the next iteration of the pvrmanager's main loop + CServiceBroker::GetPVRManager().TriggerChannelsUpdate(client->GetID()); + }); +} + +void CPVRClient::cb_trigger_provider_update(void* kodiInstance) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + /* update the providers table in the next iteration of the pvrmanager's main loop */ + CServiceBroker::GetPVRManager().TriggerProvidersUpdate(client->GetID()); + }); +} + +void CPVRClient::cb_trigger_timer_update(void* kodiInstance) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + // update timers in the next iteration of the pvrmanager's main loop + CServiceBroker::GetPVRManager().TriggerTimersUpdate(client->GetID()); + }); +} + +void CPVRClient::cb_trigger_recording_update(void* kodiInstance) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + // update recordings in the next iteration of the pvrmanager's main loop + CServiceBroker::GetPVRManager().TriggerRecordingsUpdate(client->GetID()); + }); +} + +void CPVRClient::cb_trigger_channel_groups_update(void* kodiInstance) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + // update all channel groups in the next iteration of the pvrmanager's main loop + CServiceBroker::GetPVRManager().TriggerChannelGroupsUpdate(client->GetID()); + }); +} + +void CPVRClient::cb_trigger_epg_update(void* kodiInstance, unsigned int iChannelUid) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + CServiceBroker::GetPVRManager().EpgContainer().UpdateRequest(client->GetID(), iChannelUid); + }); +} + +void CPVRClient::cb_free_demux_packet(void* kodiInstance, DEMUX_PACKET* pPacket) +{ + HandleAddonCallback(__func__, kodiInstance, + [&](CPVRClient* client) { + CDVDDemuxUtils::FreeDemuxPacket(static_cast<DemuxPacket*>(pPacket)); + }, + true); +} + +DEMUX_PACKET* CPVRClient::cb_allocate_demux_packet(void* kodiInstance, int 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, + const char* strConnectionString, + PVR_CONNECTION_STATE newState, + const char* strMessage) +{ + 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; + + CLog::LogFC(LOGDEBUG, LOGPVR, + "State for connection '{}' on client '{}' changed from '{}' to '{}'", + strConnectionString, client->ID(), prevState, newState); + + client->SetConnectionState(newState); + + std::string msg; + if (strMessage) + msg = strMessage; + + 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) +{ + 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); + }); +} + +class CCodecIds +{ +public: + virtual ~CCodecIds() = default; + + static CCodecIds& GetInstance() + { + static CCodecIds _instance; + return _instance; + } + + PVR_CODEC GetCodecByName(const char* strCodecName) + { + PVR_CODEC retVal = PVR_INVALID_CODEC; + if (strlen(strCodecName) == 0) + return retVal; + + std::string strUpperCodecName = strCodecName; + StringUtils::ToUpper(strUpperCodecName); + + std::map<std::string, PVR_CODEC>::const_iterator it = m_lookup.find(strUpperCodecName); + if (it != m_lookup.end()) + retVal = it->second; + + return retVal; + } + +private: + CCodecIds() + { + // get ids and names + const AVCodec* codec = nullptr; + void* i = nullptr; + PVR_CODEC tmp; + while ((codec = av_codec_iterate(&i))) + { + if (av_codec_is_decoder(codec)) + { + tmp.codec_type = static_cast<PVR_CODEC_TYPE>(codec->type); + tmp.codec_id = codec->id; + + std::string strUpperCodecName = codec->name; + StringUtils::ToUpper(strUpperCodecName); + + m_lookup.insert(std::make_pair(strUpperCodecName, tmp)); + } + } + + // teletext is not returned by av_codec_next. we got our own decoder + tmp.codec_type = PVR_CODEC_TYPE_SUBTITLE; + tmp.codec_id = AV_CODEC_ID_DVB_TELETEXT; + m_lookup.insert(std::make_pair("TELETEXT", tmp)); + + // rds is not returned by av_codec_next. we got our own decoder + tmp.codec_type = PVR_CODEC_TYPE_RDS; + tmp.codec_id = AV_CODEC_ID_NONE; + m_lookup.insert(std::make_pair("RDS", tmp)); + + // ID3 is not returned by av_codec_next. we got our own decoder + tmp.codec_type = PVR_CODEC_TYPE_ID3; + tmp.codec_id = AV_CODEC_ID_NONE; + m_lookup.insert({"ID3", tmp}); + } + + std::map<std::string, PVR_CODEC> m_lookup; +}; + +PVR_CODEC CPVRClient::cb_get_codec_by_name(const void* kodiInstance, const char* strCodecName) +{ + PVR_CODEC result = PVR_INVALID_CODEC; + + HandleAddonCallback( + __func__, const_cast<void*>(kodiInstance), + [&](CPVRClient* client) { result = CCodecIds::GetInstance().GetCodecByName(strCodecName); }, + true); + + return result; +} + +} // namespace PVR diff --git a/xbmc/pvr/addons/PVRClient.h b/xbmc/pvr/addons/PVRClient.h new file mode 100644 index 0000000..10203cf --- /dev/null +++ b/xbmc/pvr/addons/PVRClient.h @@ -0,0 +1,1062 @@ +/* + * Copyright (C) 2012-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. + */ + +#pragma once + +#include "addons/binary-addons/AddonInstanceHandler.h" +#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h" +#include "pvr/addons/PVRClientCapabilities.h" +#include "threads/Event.h" + +#include <atomic> +#include <functional> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +struct DemuxPacket; + +namespace PVR +{ +class CPVRChannel; +class CPVRChannelGroup; +class CPVRChannelGroupMember; +class CPVRChannelGroups; +class CPVRProvider; +class CPVRProvidersContainer; +class CPVRClientMenuHook; +class CPVRClientMenuHooks; +class CPVREpg; +class CPVREpgInfoTag; +class CPVRRecording; +class CPVRRecordings; +class CPVRStreamProperties; +class CPVRTimerInfoTag; +class CPVRTimerType; +class CPVRTimersContainer; + +#define PVR_INVALID_CLIENT_ID (-2) + +/*! + * Interface from Kodi to a PVR add-on. + * + * Also translates Kodi's C++ structures to the add-on's C structures. + */ +class CPVRClient : public ADDON::IAddonInstanceHandler +{ +public: + CPVRClient(const ADDON::AddonInfoPtr& addonInfo, ADDON::AddonInstanceId instanceId, int clientId); + ~CPVRClient() override; + + void OnPreInstall() override; + void OnPreUnInstall() override; + + /** @name PVR add-on methods */ + //@{ + + /*! + * @brief Initialise the instance of this add-on. + */ + ADDON_STATUS Create(); + + /*! + * @brief Stop this add-on instance. No more client add-on access after this call. + */ + void Stop(); + + /*! + * @brief Continue this add-on instance. Client add-on access is okay again after this call. + */ + void Continue(); + + /*! + * @brief Destroy the instance of this add-on. + */ + void Destroy(); + + /*! + * @brief Destroy and recreate this add-on. + */ + void ReCreate(); + + /*! + * @return True if this instance is initialised (ADDON_Create returned true), false otherwise. + */ + bool ReadyToUse() const; + + /*! + * @brief Gets the backend connection state. + * @return the backend connection state. + */ + PVR_CONNECTION_STATE GetConnectionState() const; + + /*! + * @brief Sets the backend connection state. + * @param state the new backend connection state. + */ + void SetConnectionState(PVR_CONNECTION_STATE state); + + /*! + * @brief Gets the backend's previous connection state. + * @return the backend's previous connection state. + */ + PVR_CONNECTION_STATE GetPreviousConnectionState() const; + + /*! + * @brief Check whether this client should be ignored. + * @return True if this client should be ignored, false otherwise. + */ + bool IgnoreClient() const; + + /*! + * @brief Check whether this client is enabled, according to its instance/add-on configuration. + * @return True if this client is enabled, false otherwise. + */ + bool IsEnabled() const; + + /*! + * @return The ID of this instance. + */ + int GetID() const; + + //@} + /** @name PVR server methods */ + //@{ + + /*! + * @brief Query this add-on's capabilities. + * @return The add-on's capabilities. + */ + const CPVRClientCapabilities& GetClientCapabilities() const { return m_clientCapabilities; } + + /*! + * @brief Get the stream properties of the stream that's currently being read. + * @param pProperties The properties. + * @return PVR_ERROR_NO_ERROR if the properties have been fetched successfully. + */ + PVR_ERROR GetStreamProperties(PVR_STREAM_PROPERTIES* pProperties); + + /*! + * @return The name reported by the backend. + */ + const std::string& GetBackendName() const; + + /*! + * @return The version string reported by the backend. + */ + const std::string& GetBackendVersion() const; + + /*! + * @brief the ip address or alias of the pvr backend server + */ + const std::string& GetBackendHostname() const; + + /*! + * @return The connection string reported by the backend. + */ + const std::string& GetConnectionString() const; + + /*! + * @brief A friendly name used to uniquely identify the addon instance + * @return string that can be used in log messages and the GUI. + */ + const std::string GetFriendlyName() const; + + /*! + * @brief Get the disk space reported by the server. + * @param iTotal The total disk space. + * @param iUsed The used disk space. + * @return PVR_ERROR_NO_ERROR if the drive space has been fetched successfully. + */ + PVR_ERROR GetDriveSpace(uint64_t& iTotal, uint64_t& iUsed); + + /*! + * @brief Start a channel scan on the server. + * @return PVR_ERROR_NO_ERROR if the channel scan has been started successfully. + */ + PVR_ERROR StartChannelScan(); + + /*! + * @brief Request the client to open dialog about given channel to add + * @param channel The channel to add + * @return PVR_ERROR_NO_ERROR if the add has been fetched successfully. + */ + PVR_ERROR OpenDialogChannelAdd(const std::shared_ptr<CPVRChannel>& channel); + + /*! + * @brief Request the client to open dialog about given channel settings + * @param channel The channel to edit + * @return PVR_ERROR_NO_ERROR if the edit has been fetched successfully. + */ + PVR_ERROR OpenDialogChannelSettings(const std::shared_ptr<CPVRChannel>& channel); + + /*! + * @brief Request the client to delete given channel + * @param channel The channel to delete + * @return PVR_ERROR_NO_ERROR if the delete has been fetched successfully. + */ + PVR_ERROR DeleteChannel(const std::shared_ptr<CPVRChannel>& channel); + + /*! + * @brief Request the client to rename given channel + * @param channel The channel to rename + * @return PVR_ERROR_NO_ERROR if the rename has been fetched successfully. + */ + PVR_ERROR RenameChannel(const std::shared_ptr<CPVRChannel>& channel); + + /* + * @brief Check if an epg tag can be recorded + * @param tag The epg tag + * @param bIsRecordable Set to true if the tag can be recorded + * @return PVR_ERROR_NO_ERROR if bIsRecordable has been set successfully. + */ + PVR_ERROR IsRecordable(const std::shared_ptr<const CPVREpgInfoTag>& tag, + bool& bIsRecordable) const; + + /* + * @brief Check if an epg tag can be played + * @param tag The epg tag + * @param bIsPlayable Set to true if the tag can be played + * @return PVR_ERROR_NO_ERROR if bIsPlayable has been set successfully. + */ + PVR_ERROR IsPlayable(const std::shared_ptr<const CPVREpgInfoTag>& tag, bool& bIsPlayable) const; + + /*! + * @brief Fill the given container with the properties required for playback + * of the given EPG tag. Values are obtained from the PVR backend. + * + * @param tag The EPG tag. + * @param props The container to be filled with the stream properties. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR GetEpgTagStreamProperties(const std::shared_ptr<CPVREpgInfoTag>& tag, + CPVRStreamProperties& props); + + //@} + /** @name PVR EPG methods */ + //@{ + + /*! + * @brief Request an EPG table for a channel from the client. + * @param iChannelUid The UID of the channel to get the EPG table for. + * @param epg The table to write the data to. + * @param start The start time to use. + * @param end The end time to use. + * @return PVR_ERROR_NO_ERROR if the table has been fetched successfully. + */ + PVR_ERROR GetEPGForChannel(int iChannelUid, CPVREpg* epg, time_t start, time_t end); + + /*! + * @brief Tell the client the past time frame to use when notifying epg events back + * to Kodi. + * + * The client might push epg events asynchronously to Kodi using the callback + * function EpgEventStateChange. To be able to only push events that are + * actually of interest for Kodi, client needs to know about the past epg time + * frame Kodi uses. + * + * @param[in] iPastDays number of days before "now". + @ref EPG_TIMEFRAME_UNLIMITED means that Kodi is interested in all epg events, + regardless of event times. + * @return PVR_ERROR_NO_ERROR if new value was successfully set. + */ + PVR_ERROR SetEPGMaxPastDays(int iPastDays); + + /*! + * @brief Tell the client the future time frame to use when notifying epg events back + * to Kodi. + * + * The client might push epg events asynchronously to Kodi using the callback + * function EpgEventStateChange. To be able to only push events that are + * actually of interest for Kodi, client needs to know about the future epg time + * frame Kodi uses. + * + * @param[in] iFutureDays number of days after "now". + @ref EPG_TIMEFRAME_UNLIMITED means that Kodi is interested in all epg events, + regardless of event times. + * @return PVR_ERROR_NO_ERROR if new value was successfully set. + */ + PVR_ERROR SetEPGMaxFutureDays(int iFutureDays); + + //@} + /** @name PVR channel group methods */ + //@{ + + /*! + * @brief Get the total amount of channel groups from the backend. + * @param iGroups The total amount of channel groups on the server or -1 on error. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR GetChannelGroupsAmount(int& iGroups); + + /*! + * @brief Request the list of all channel groups from the backend. + * @param groups The groups container to get the groups for. + * @return PVR_ERROR_NO_ERROR if the list has been fetched successfully. + */ + PVR_ERROR GetChannelGroups(CPVRChannelGroups* groups); + + /*! + * @brief Request the list of all group members from the backend. + * @param group The group to get the members for. + * @param groupMembers The container for the group members. + * @return PVR_ERROR_NO_ERROR if the list has been fetched successfully. + */ + PVR_ERROR GetChannelGroupMembers( + CPVRChannelGroup* group, std::vector<std::shared_ptr<CPVRChannelGroupMember>>& groupMembers); + + //@} + /** @name PVR channel methods */ + //@{ + + /*! + * @brief Get the total amount of channels from the backend. + * @param iChannels The total amount of channels on the server or -1 on error. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR GetChannelsAmount(int& iChannels); + + /*! + * @brief Request the list of all channels from the backend. + * @param bRadio True to get the radio channels, false to get the TV channels. + * @param channels The container for the channels. + * @return PVR_ERROR_NO_ERROR if the list has been fetched successfully. + */ + PVR_ERROR GetChannels(bool bRadio, std::vector<std::shared_ptr<CPVRChannel>>& channels); + + /*! + * @brief Get the total amount of providers from the backend. + * @param iChannels The total amount of channels on the server or -1 on error. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR GetProvidersAmount(int& iProviders); + + /*! + * @brief Request the list of all providers from the backend. + * @param providers The providers list to add the providers to. + * @return PVR_ERROR_NO_ERROR if the list has been fetched successfully. + */ + PVR_ERROR GetProviders(CPVRProvidersContainer& providers); + + //@} + /** @name PVR recording methods */ + //@{ + + /*! + * @brief Get the total amount of recordings from the backend. + * @param deleted True to return deleted recordings. + * @param iRecordings The total amount of recordings on the server or -1 on error. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR GetRecordingsAmount(bool deleted, int& iRecordings); + + /*! + * @brief Request the list of all recordings from the backend. + * @param results The container to add the recordings to. + * @param deleted True to return deleted recordings. + * @return PVR_ERROR_NO_ERROR if the list has been fetched successfully. + */ + PVR_ERROR GetRecordings(CPVRRecordings* results, bool deleted); + + /*! + * @brief Delete a recording on the backend. + * @param recording The recording to delete. + * @return PVR_ERROR_NO_ERROR if the recording has been deleted successfully. + */ + PVR_ERROR DeleteRecording(const CPVRRecording& recording); + + /*! + * @brief Undelete a recording on the backend. + * @param recording The recording to undelete. + * @return PVR_ERROR_NO_ERROR if the recording has been undeleted successfully. + */ + PVR_ERROR UndeleteRecording(const CPVRRecording& recording); + + /*! + * @brief Delete all recordings permanent which in the deleted folder on the backend. + * @return PVR_ERROR_NO_ERROR if the recordings has been deleted successfully. + */ + PVR_ERROR DeleteAllRecordingsFromTrash(); + + /*! + * @brief Rename a recording on the backend. + * @param recording The recording to rename. + * @return PVR_ERROR_NO_ERROR if the recording has been renamed successfully. + */ + PVR_ERROR RenameRecording(const CPVRRecording& recording); + + /*! + * @brief Set the lifetime of a recording on the backend. + * @param recording The recording to set the lifetime for. recording.m_iLifetime contains the new lifetime value. + * @return PVR_ERROR_NO_ERROR if the recording's lifetime has been set successfully. + */ + PVR_ERROR SetRecordingLifetime(const CPVRRecording& recording); + + /*! + * @brief Set the play count of a recording on the backend. + * @param recording The recording to set the play count. + * @param count Play count. + * @return PVR_ERROR_NO_ERROR if the recording's play count has been set successfully. + */ + PVR_ERROR SetRecordingPlayCount(const CPVRRecording& recording, int count); + + /*! + * @brief Set the last watched position of a recording on the backend. + * @param recording The recording. + * @param lastplayedposition The last watched position in seconds + * @return PVR_ERROR_NO_ERROR if the position has been stored successfully. + */ + PVR_ERROR SetRecordingLastPlayedPosition(const CPVRRecording& recording, int lastplayedposition); + + /*! + * @brief Retrieve the last watched position of a recording on the backend. + * @param recording The recording. + * @param iPosition The last watched position in seconds or -1 on error + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR GetRecordingLastPlayedPosition(const CPVRRecording& recording, int& iPosition); + + /*! + * @brief Retrieve the edit decision list (EDL) from the backend. + * @param recording The recording. + * @param edls The edit decision list (empty on error). + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR GetRecordingEdl(const CPVRRecording& recording, std::vector<PVR_EDL_ENTRY>& edls); + + /*! + * @brief Retrieve the size of a recording on the backend. + * @param recording The recording. + * @param sizeInBytes The size in bytes + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR GetRecordingSize(const CPVRRecording& recording, int64_t& sizeInBytes); + + /*! + * @brief Retrieve the edit decision list (EDL) from the backend. + * @param epgTag The EPG tag. + * @param edls The edit decision list (empty on error). + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR GetEpgTagEdl(const std::shared_ptr<const CPVREpgInfoTag>& epgTag, + std::vector<PVR_EDL_ENTRY>& edls); + + //@} + /** @name PVR timer methods */ + //@{ + + /*! + * @brief Get the total amount of timers from the backend. + * @param iTimers The total amount of timers on the backend or -1 on error. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR GetTimersAmount(int& iTimers); + + /*! + * @brief Request the list of all timers from the backend. + * @param results The container to store the result in. + * @return PVR_ERROR_NO_ERROR if the list has been fetched successfully. + */ + PVR_ERROR GetTimers(CPVRTimersContainer* results); + + /*! + * @brief Add a timer on the backend. + * @param timer The timer to add. + * @return PVR_ERROR_NO_ERROR if the timer has been added successfully. + */ + PVR_ERROR AddTimer(const CPVRTimerInfoTag& timer); + + /*! + * @brief Delete a timer on the backend. + * @param timer The timer to delete. + * @param bForce Set to true to delete a timer that is currently recording a program. + * @return PVR_ERROR_NO_ERROR if the timer has been deleted successfully. + */ + PVR_ERROR DeleteTimer(const CPVRTimerInfoTag& timer, bool bForce = false); + + /*! + * @brief Update the timer information on the server. + * @param timer The timer to update. + * @return PVR_ERROR_NO_ERROR if the timer has been updated successfully. + */ + PVR_ERROR UpdateTimer(const CPVRTimerInfoTag& timer); + + /*! + * @brief Get all timer types supported by the backend. + * @param results The container to store the result in. + * @return PVR_ERROR_NO_ERROR if the list has been fetched successfully. + */ + PVR_ERROR GetTimerTypes(std::vector<std::shared_ptr<CPVRTimerType>>& results) const; + + //@} + /** @name PVR live stream methods */ + //@{ + + /*! + * @brief Open a live stream on the server. + * @param channel The channel to stream. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR OpenLiveStream(const std::shared_ptr<CPVRChannel>& channel); + + /*! + * @brief Close an open live stream. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR CloseLiveStream(); + + /*! + * @brief Read from an open live stream. + * @param lpBuf The buffer to store the data in. + * @param uiBufSize The amount of bytes to read. + * @param iRead The amount of bytes that were actually read from the stream. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR ReadLiveStream(void* lpBuf, int64_t uiBufSize, int& iRead); + + /*! + * @brief Seek in a live stream on a backend. + * @param iFilePosition The position to seek to. + * @param iWhence ? + * @param iPosition The new position or -1 on error. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR SeekLiveStream(int64_t iFilePosition, int iWhence, int64_t& iPosition); + + /*! + * @brief Get the length of the currently playing live stream, if any. + * @param iLength The total length of the stream that's currently being read or -1 on error. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR GetLiveStreamLength(int64_t& iLength); + + /*! + * @brief (Un)Pause a stream. + * @param bPaused True to pause the stream, false to unpause. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR PauseStream(bool bPaused); + + /*! + * @brief Get the signal quality of the stream that's currently open. + * @param channelUid Channel unique identifier + * @param qualityinfo The signal quality. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR SignalQuality(int channelUid, PVR_SIGNAL_STATUS& qualityinfo); + + /*! + * @brief Get the descramble information of the stream that's currently open. + * @param channelUid Channel unique identifier + * @param descrambleinfo The descramble information. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR GetDescrambleInfo(int channelUid, PVR_DESCRAMBLE_INFO& descrambleinfo) const; + + /*! + * @brief Fill the given container with the properties required for playback of the given channel. Values are obtained from the PVR backend. + * @param channel The channel. + * @param props The container to be filled with the stream properties. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR GetChannelStreamProperties(const std::shared_ptr<CPVRChannel>& channel, + CPVRStreamProperties& props); + + /*! + * @brief Check whether PVR backend supports pausing the currently playing stream + * @param bCanPause True if the stream can be paused, false otherwise. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR CanPauseStream(bool& bCanPause) const; + + /*! + * @brief Check whether PVR backend supports seeking for the currently playing stream + * @param bCanSeek True if the stream can be seeked, false otherwise. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR CanSeekStream(bool& bCanSeek) const; + + /*! + * @brief Notify the pvr addon/demuxer that Kodi wishes to seek the stream by time + * @param time The absolute time since stream start + * @param backwards True to seek to keyframe BEFORE time, else AFTER + * @param startpts can be updated to point to where display should start + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + * @remarks Optional, and only used if addon has its own demuxer. + */ + PVR_ERROR SeekTime(double time, bool backwards, double* startpts); + + /*! + * @brief Notify the pvr addon/demuxer that Kodi wishes to change playback speed + * @param speed The requested playback speed + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + * @remarks Optional, and only used if addon has its own demuxer. + */ + PVR_ERROR SetSpeed(int speed); + + /*! + * @brief Notify the pvr addon/demuxer that Kodi wishes to fill demux queue + * @param mode for setting on/off + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + * @remarks Optional, and only used if addon has its own demuxer. + */ + PVR_ERROR FillBuffer(bool mode); + + //@} + /** @name PVR recording stream methods */ + //@{ + + /*! + * @brief Open a recording on the server. + * @param recording The recording to open. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR OpenRecordedStream(const std::shared_ptr<CPVRRecording>& recording); + + /*! + * @brief Close an open recording stream. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR CloseRecordedStream(); + + /*! + * @brief Read from an open recording stream. + * @param lpBuf The buffer to store the data in. + * @param uiBufSize The amount of bytes to read. + * @param iRead The amount of bytes that were actually read from the stream. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR ReadRecordedStream(void* lpBuf, int64_t uiBufSize, int& iRead); + + /*! + * @brief Seek in a recording stream on a backend. + * @param iFilePosition The position to seek to. + * @param iWhence ? + * @param iPosition The new position or -1 on error. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR SeekRecordedStream(int64_t iFilePosition, int iWhence, int64_t& iPosition); + + /*! + * @brief Get the length of the currently playing recording stream, if any. + * @param iLength The total length of the stream that's currently being read or -1 on error. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR GetRecordedStreamLength(int64_t& iLength); + + /*! + * @brief Fill the given container with the properties required for playback of the given recording. Values are obtained from the PVR backend. + * @param recording The recording. + * @param props The container to be filled with the stream properties. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR GetRecordingStreamProperties(const std::shared_ptr<CPVRRecording>& recording, + CPVRStreamProperties& props); + + //@} + /** @name PVR demultiplexer methods */ + //@{ + + /*! + * @brief Reset the demultiplexer in the add-on. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR DemuxReset(); + + /*! + * @brief Abort the demultiplexer thread in the add-on. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR DemuxAbort(); + + /*! + * @brief Flush all data that's currently in the demultiplexer buffer in the add-on. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR DemuxFlush(); + + /*! + * @brief Read a packet from the demultiplexer. + * @param packet The packet read. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR DemuxRead(DemuxPacket*& packet); + + static const char* ToString(const PVR_ERROR error); + + /*! + * @brief Check whether the currently playing stream, if any, is a real-time stream. + * @param bRealTime True if real-time, false otherwise. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR IsRealTimeStream(bool& bRealTime) const; + + /*! + * @brief Get Stream times for the currently playing stream, if any (will be moved to inputstream). + * @param times The stream times. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR GetStreamTimes(PVR_STREAM_TIMES* times); + + /*! + * @brief Get the client's menu hooks. + * @return The hooks. Guaranteed never to be nullptr. + */ + std::shared_ptr<CPVRClientMenuHooks> GetMenuHooks(); + + /*! + * @brief Call one of the EPG tag menu hooks of the client. + * @param hook The hook to call. + * @param tag The EPG tag associated with the hook to be called. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR CallEpgTagMenuHook(const CPVRClientMenuHook& hook, + const std::shared_ptr<CPVREpgInfoTag>& tag); + + /*! + * @brief Call one of the channel menu hooks of the client. + * @param hook The hook to call. + * @param tag The channel associated with the hook to be called. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR CallChannelMenuHook(const CPVRClientMenuHook& hook, + const std::shared_ptr<CPVRChannel>& channel); + + /*! + * @brief Call one of the recording menu hooks of the client. + * @param hook The hook to call. + * @param tag The recording associated with the hook to be called. + * @param bDeleted True, if the recording is deleted (trashed), false otherwise + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR CallRecordingMenuHook(const CPVRClientMenuHook& hook, + const std::shared_ptr<CPVRRecording>& recording, + bool bDeleted); + + /*! + * @brief Call one of the timer menu hooks of the client. + * @param hook The hook to call. + * @param tag The timer associated with the hook to be called. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR CallTimerMenuHook(const CPVRClientMenuHook& hook, + const std::shared_ptr<CPVRTimerInfoTag>& timer); + + /*! + * @brief Call one of the settings menu hooks of the client. + * @param hook The hook to call. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR CallSettingsMenuHook(const CPVRClientMenuHook& hook); + + /*! + * @brief Propagate power management events to this add-on + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR OnSystemSleep(); + PVR_ERROR OnSystemWake(); + PVR_ERROR OnPowerSavingActivated(); + PVR_ERROR OnPowerSavingDeactivated(); + + /*! + * @brief Get the priority of this client. Larger value means higher priority. + * @return The priority. + */ + int GetPriority() const; + + /*! + * @brief Set a new priority for this client. + * @param iPriority The new priority. + */ + void SetPriority(int iPriority); + + /*! + * @brief Obtain the chunk size to use when reading streams. + * @param iChunkSize the chunk size in bytes. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR GetStreamReadChunkSize(int& iChunkSize); + + /*! + * @brief Get the interface table used between addon and Kodi. + * @todo This function will be removed after old callback library system is removed. + */ + AddonInstance_PVR* GetInstanceInterface() { return m_ifc.pvr; } + +private: + /*! + * @brief Resets all class members to their defaults, accept the client id. + */ + void ResetProperties(); + + /*! + * @brief reads the client's properties. + * @return True on success, false otherwise. + */ + bool GetAddonProperties(); + + /*! + * @brief reads the client's name string properties + * @return True on success, false otherwise. + */ + bool GetAddonNameStringProperties(); + + /*! + * @brief Write the given addon properties to the given properties container. + * @param properties Pointer to an array of addon properties. + * @param iPropertyCount The number of properties contained in the addon properties array. + * @param props The container the addon properties shall be written to. + */ + static void WriteStreamProperties(const PVR_NAMED_VALUE* properties, + unsigned int iPropertyCount, + CPVRStreamProperties& props); + + /*! + * @brief Whether a channel can be played by this add-on + * @param channel The channel to check. + * @return True when it can be played, false otherwise. + */ + bool CanPlayChannel(const std::shared_ptr<CPVRChannel>& channel) const; + + /*! + * @brief Stop this instance, if it is currently running. + */ + void StopRunningInstance(); + + /*! + * @brief Wraps an addon function call in order to do common pre and post function invocation actions. + * @param strFunctionName The function name, for logging purposes. + * @param function The function to wrap. It has to have return type PVR_ERROR and must take one parameter of type const AddonInstance*. + * @param bIsImplemented If false, this method will return PVR_ERROR_NOT_IMPLEMENTED. + * @param bCheckReadyToUse If true, this method will check whether this instance is ready for use and return PVR_ERROR_SERVER_ERROR if it is not. + * @return PVR_ERROR_NO_ERROR on success, any other PVR_ERROR_* value otherwise. + */ + typedef AddonInstance_PVR AddonInstance; + PVR_ERROR DoAddonCall(const char* strFunctionName, + const std::function<PVR_ERROR(const AddonInstance*)>& function, + bool bIsImplemented = true, + 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 + */ + //@{ + + /*! + * @brief Transfer a channel group from the add-on to Kodi. The group will be created if it doesn't exist. + * @param kodiInstance Pointer to Kodi's CPVRClient class + * @param handle The handle parameter that Kodi used when requesting the channel groups list + * @param entry The entry to transfer to Kodi + */ + static void cb_transfer_channel_group(void* kodiInstance, + const PVR_HANDLE handle, + const PVR_CHANNEL_GROUP* entry); + + /*! + * @brief Transfer a channel group member entry from the add-on to Kodi. The channel will be added to the group if the group can be found. + * @param kodiInstance Pointer to Kodi's CPVRClient class + * @param handle The handle parameter that Kodi used when requesting the channel group members list + * @param entry The entry to transfer to Kodi + */ + static void cb_transfer_channel_group_member(void* kodiInstance, + const PVR_HANDLE handle, + const PVR_CHANNEL_GROUP_MEMBER* entry); + + /*! + * @brief Transfer an EPG tag from the add-on to Kodi + * @param kodiInstance Pointer to Kodi's CPVRClient class + * @param handle The handle parameter that Kodi used when requesting the EPG data + * @param entry The entry to transfer to Kodi + */ + static void cb_transfer_epg_entry(void* kodiInstance, + const PVR_HANDLE handle, + const EPG_TAG* entry); + + /*! + * @brief Transfer a channel entry from the add-on to Kodi + * @param kodiInstance Pointer to Kodi's CPVRClient class + * @param handle The handle parameter that Kodi used when requesting the channel list + * @param entry The entry to transfer to Kodi + */ + static void cb_transfer_channel_entry(void* kodiInstance, + const PVR_HANDLE handle, + const PVR_CHANNEL* entry); + + /*! + * @brief Transfer a provider entry from the add-on to Kodi + * @param kodiInstance Pointer to Kodi's CPVRClient class + * @param handle The handle parameter that Kodi used when requesting the channel list + * @param entry The entry to transfer to Kodi + */ + static void cb_transfer_provider_entry(void* kodiInstance, + const PVR_HANDLE handle, + const PVR_PROVIDER* entry); + + /*! + * @brief Transfer a timer entry from the add-on to Kodi + * @param kodiInstance Pointer to Kodi's CPVRClient class + * @param handle The handle parameter that Kodi used when requesting the timers list + * @param entry The entry to transfer to Kodi + */ + static void cb_transfer_timer_entry(void* kodiInstance, + const PVR_HANDLE handle, + const PVR_TIMER* entry); + + /*! + * @brief Transfer a recording entry from the add-on to Kodi + * @param kodiInstance Pointer to Kodi's CPVRClient class + * @param handle The handle parameter that Kodi used when requesting the recordings list + * @param entry The entry to transfer to Kodi + */ + static void cb_transfer_recording_entry(void* kodiInstance, + const PVR_HANDLE handle, + const PVR_RECORDING* entry); + + /*! + * @brief Add or replace a menu hook for the context menu for this add-on + * @param kodiInstance Pointer to Kodi's CPVRClient class + * @param hook The hook to add. + */ + static void cb_add_menu_hook(void* kodiInstance, const PVR_MENUHOOK* hook); + + /*! + * @brief Display a notification in Kodi that a recording started or stopped on the server + * @param kodiInstance Pointer to Kodi's CPVRClient class + * @param strName The name of the recording to display + * @param strFileName The filename of the recording + * @param bOnOff True when recording started, false when it stopped + */ + static void cb_recording_notification(void* kodiInstance, + const char* strName, + const char* strFileName, + bool bOnOff); + + /*! + * @brief Request Kodi to update it's list of channels + * @param kodiInstance Pointer to Kodi's CPVRClient class + */ + static void cb_trigger_channel_update(void* kodiInstance); + + /*! + * @brief Request Kodi to update it's list of providers + * @param kodiInstance Pointer to Kodi's CPVRClient class + */ + static void cb_trigger_provider_update(void* kodiInstance); + + /*! + * @brief Request Kodi to update it's list of timers + * @param kodiInstance Pointer to Kodi's CPVRClient class + */ + static void cb_trigger_timer_update(void* kodiInstance); + + /*! + * @brief Request Kodi to update it's list of recordings + * @param kodiInstance Pointer to Kodi's CPVRClient class + */ + static void cb_trigger_recording_update(void* kodiInstance); + + /*! + * @brief Request Kodi to update it's list of channel groups + * @param kodiInstance Pointer to Kodi's CPVRClient class + */ + static void cb_trigger_channel_groups_update(void* kodiInstance); + + /*! + * @brief Schedule an EPG update for the given channel channel + * @param kodiInstance A pointer to the add-on + * @param iChannelUid The unique id of the channel for this add-on + */ + static void cb_trigger_epg_update(void* kodiInstance, unsigned int iChannelUid); + + /*! + * @brief Free a packet that was allocated with AllocateDemuxPacket + * @param kodiInstance Pointer to Kodi's CPVRClient class + * @param pPacket The packet to free. + */ + static void cb_free_demux_packet(void* kodiInstance, DEMUX_PACKET* pPacket); + + /*! + * @brief Allocate a demux packet. Free with FreeDemuxPacket + * @param kodiInstance Pointer to Kodi's CPVRClient class. + * @param iDataSize The size of the data that will go into the packet + * @return The allocated packet. + */ + static DEMUX_PACKET* cb_allocate_demux_packet(void* kodiInstance, int iDataSize = 0); + + /*! + * @brief Notify a state change for a PVR backend connection + * @param kodiInstance Pointer to Kodi's CPVRClient class + * @param strConnectionString The connection string reported by the backend that can be displayed in the UI. + * @param newState The new state. + * @param strMessage A localized addon-defined string representing the new state, that can be displayed + * in the UI or NULL if the Kodi-defined default string for the new state shall be displayed. + */ + static void cb_connection_state_change(void* kodiInstance, + const char* strConnectionString, + PVR_CONNECTION_STATE newState, + const char* strMessage); + + /*! + * @brief Notify a state change for an EPG event + * @param kodiInstance Pointer to Kodi's CPVRClient class + * @param tag The EPG event. + * @param newState The new state. + * @param newState The new state. For EPG_EVENT_CREATED and EPG_EVENT_UPDATED, tag must be filled with all available + * event data, not just a delta. For EPG_EVENT_DELETED, it is sufficient to fill EPG_TAG.iUniqueBroadcastId + */ + static void cb_epg_event_state_change(void* kodiInstance, EPG_TAG* tag, EPG_EVENT_STATE newState); + + /*! @todo remove the use complete from them, or add as generl function?! + * Returns the ffmpeg codec id from given ffmpeg codec string name + */ + static PVR_CODEC cb_get_codec_by_name(const void* kodiInstance, const char* strCodecName); + //@} + + const int m_iClientId; /*!< unique ID of the client */ + 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 + m_ignoreClient; /*!< signals to PVRManager to ignore this client until it has been connected */ + std::vector<std::shared_ptr<CPVRTimerType>> + m_timertypes; /*!< timer types supported by this backend */ + mutable int m_iPriority; /*!< priority of the client */ + mutable bool m_bPriorityFetched; + + /* cached data */ + std::string m_strBackendName; /*!< the cached backend version */ + std::string m_strBackendVersion; /*!< the cached backend version */ + std::string m_strConnectionString; /*!< the cached connection string */ + std::string m_strBackendHostname; /*!< the cached backend hostname */ + CPVRClientCapabilities m_clientCapabilities; /*!< the cached add-on's capabilities */ + std::shared_ptr<CPVRClientMenuHooks> m_menuhooks; /*!< the menu hooks for this add-on */ + + /* stored strings to make sure const char* members in AddonProperties_PVR stay valid */ + std::string m_strUserPath; /*!< @brief translated path to the user profile */ + std::string m_strClientPath; /*!< @brief translated path to this add-on */ + + mutable CCriticalSection m_critSection; +}; +} // namespace PVR diff --git a/xbmc/pvr/addons/PVRClientCapabilities.cpp b/xbmc/pvr/addons/PVRClientCapabilities.cpp new file mode 100644 index 0000000..8650465 --- /dev/null +++ b/xbmc/pvr/addons/PVRClientCapabilities.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2012-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 "PVRClientCapabilities.h" + +#include "guilib/LocalizeStrings.h" +#include "utils/StringUtils.h" + +#include <algorithm> +#include <iterator> + +using namespace PVR; + +CPVRClientCapabilities::CPVRClientCapabilities(const CPVRClientCapabilities& other) +{ + if (other.m_addonCapabilities) + m_addonCapabilities.reset(new PVR_ADDON_CAPABILITIES(*other.m_addonCapabilities)); + InitRecordingsLifetimeValues(); +} + +const CPVRClientCapabilities& CPVRClientCapabilities::operator=(const CPVRClientCapabilities& other) +{ + if (other.m_addonCapabilities) + m_addonCapabilities.reset(new PVR_ADDON_CAPABILITIES(*other.m_addonCapabilities)); + InitRecordingsLifetimeValues(); + return *this; +} + +const CPVRClientCapabilities& CPVRClientCapabilities::operator=( + const PVR_ADDON_CAPABILITIES& addonCapabilities) +{ + m_addonCapabilities.reset(new PVR_ADDON_CAPABILITIES(addonCapabilities)); + InitRecordingsLifetimeValues(); + return *this; +} + +void CPVRClientCapabilities::clear() +{ + m_recordingsLifetimeValues.clear(); + m_addonCapabilities.reset(); +} + +void CPVRClientCapabilities::InitRecordingsLifetimeValues() +{ + m_recordingsLifetimeValues.clear(); + if (m_addonCapabilities && m_addonCapabilities->iRecordingsLifetimesSize > 0) + { + for (unsigned int i = 0; i < m_addonCapabilities->iRecordingsLifetimesSize; ++i) + { + int iValue = m_addonCapabilities->recordingsLifetimeValues[i].iValue; + std::string strDescr(m_addonCapabilities->recordingsLifetimeValues[i].strDescription); + if (strDescr.empty()) + { + // No description given by addon. Create one from value. + strDescr = std::to_string(iValue); + } + m_recordingsLifetimeValues.emplace_back(strDescr, iValue); + } + } + else if (SupportsRecordingsLifetimeChange()) + { + // No values given by addon, but lifetime supported. Use default values 1..365 + for (int i = 1; i < 366; ++i) + { + m_recordingsLifetimeValues.emplace_back(StringUtils::Format(g_localizeStrings.Get(17999), i), + i); // "{} days" + } + } + else + { + // No lifetime supported. + } +} + +void CPVRClientCapabilities::GetRecordingsLifetimeValues( + std::vector<std::pair<std::string, int>>& list) const +{ + std::copy(m_recordingsLifetimeValues.cbegin(), m_recordingsLifetimeValues.cend(), + std::back_inserter(list)); +} diff --git a/xbmc/pvr/addons/PVRClientCapabilities.h b/xbmc/pvr/addons/PVRClientCapabilities.h new file mode 100644 index 0000000..edf2090 --- /dev/null +++ b/xbmc/pvr/addons/PVRClientCapabilities.h @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2012-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. + */ + +#pragma once + +#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h" + +#include <memory> +#include <string> +#include <utility> +#include <vector> + +namespace PVR +{ + +class CPVRClientCapabilities +{ +public: + CPVRClientCapabilities() = default; + virtual ~CPVRClientCapabilities() = default; + + CPVRClientCapabilities(const CPVRClientCapabilities& other); + const CPVRClientCapabilities& operator=(const CPVRClientCapabilities& other); + + const CPVRClientCapabilities& operator=(const PVR_ADDON_CAPABILITIES& addonCapabilities); + + void clear(); + + ///////////////////////////////////////////////////////////////////////////////// + // + // Channels + // + ///////////////////////////////////////////////////////////////////////////////// + + /*! + * @brief Check whether this add-on supports TV channels. + * @return True if supported, false otherwise. + */ + bool SupportsTV() const { return m_addonCapabilities && m_addonCapabilities->bSupportsTV; } + + /*! + * @brief Check whether this add-on supports radio channels. + * @return True if supported, false otherwise. + */ + bool SupportsRadio() const { return m_addonCapabilities && m_addonCapabilities->bSupportsRadio; } + + /*! + * @brief Check whether this add-on supports providers. + * @return True if supported, false otherwise. + */ + bool SupportsProviders() const + { + return m_addonCapabilities && m_addonCapabilities->bSupportsProviders; + } + + /*! + * @brief Check whether this add-on supports channel groups. + * @return True if supported, false otherwise. + */ + bool SupportsChannelGroups() const + { + return m_addonCapabilities && m_addonCapabilities->bSupportsChannelGroups; + } + + /*! + * @brief Check whether this add-on supports scanning for new channels on the backend. + * @return True if supported, false otherwise. + */ + bool SupportsChannelScan() const + { + return m_addonCapabilities && m_addonCapabilities->bSupportsChannelScan; + } + + /*! + * @brief Check whether this add-on supports the following functions: + * DeleteChannel, RenameChannel, DialogChannelSettings and DialogAddChannel. + * + * @return True if supported, false otherwise. + */ + bool SupportsChannelSettings() const + { + return m_addonCapabilities && m_addonCapabilities->bSupportsChannelSettings; + } + + /*! + * @brief Check whether this add-on supports descramble information for playing channels. + * @return True if supported, false otherwise. + */ + bool SupportsDescrambleInfo() const + { + return m_addonCapabilities && m_addonCapabilities->bSupportsDescrambleInfo; + } + + ///////////////////////////////////////////////////////////////////////////////// + // + // EPG + // + ///////////////////////////////////////////////////////////////////////////////// + + /*! + * @brief Check whether this add-on provides EPG information. + * @return True if supported, false otherwise. + */ + bool SupportsEPG() const { return m_addonCapabilities && m_addonCapabilities->bSupportsEPG; } + + /*! + * @brief Check whether this add-on supports asynchronous transfer of epg events. + * @return True if supported, false otherwise. + */ + bool SupportsAsyncEPGTransfer() const + { + return m_addonCapabilities && m_addonCapabilities->bSupportsAsyncEPGTransfer; + } + + ///////////////////////////////////////////////////////////////////////////////// + // + // Timers + // + ///////////////////////////////////////////////////////////////////////////////// + + /*! + * @brief Check whether this add-on supports the creation and editing of timers. + * @return True if supported, false otherwise. + */ + bool SupportsTimers() const + { + return m_addonCapabilities && m_addonCapabilities->bSupportsTimers; + } + + ///////////////////////////////////////////////////////////////////////////////// + // + // Recordings + // + ///////////////////////////////////////////////////////////////////////////////// + + /*! + * @brief Check whether this add-on supports recordings. + * @return True if supported, false otherwise. + */ + bool SupportsRecordings() const + { + return m_addonCapabilities && m_addonCapabilities->bSupportsRecordings; + } + + /*! + * @brief Check whether this add-on supports undelete of deleted recordings. + * @return True if supported, false otherwise. + */ + bool SupportsRecordingsUndelete() const + { + return m_addonCapabilities && m_addonCapabilities->bSupportsRecordings && + m_addonCapabilities->bSupportsRecordingsUndelete; + } + + /*! + * @brief Check whether this add-on supports play count for recordings. + * @return True if supported, false otherwise. + */ + bool SupportsRecordingsPlayCount() const + { + return m_addonCapabilities && m_addonCapabilities->bSupportsRecordings && + m_addonCapabilities->bSupportsRecordingPlayCount; + } + + /*! + * @brief Check whether this add-on supports store/retrieve of last played position for recordings.. + * @return True if supported, false otherwise. + */ + bool SupportsRecordingsLastPlayedPosition() const + { + return m_addonCapabilities && m_addonCapabilities->bSupportsRecordings && + m_addonCapabilities->bSupportsLastPlayedPosition; + } + + /*! + * @brief Check whether this add-on supports retrieving an edit decision list for recordings. + * @return True if supported, false otherwise. + */ + bool SupportsRecordingsEdl() const + { + return m_addonCapabilities && m_addonCapabilities->bSupportsRecordings && + m_addonCapabilities->bSupportsRecordingEdl; + } + + /*! + * @brief Check whether this add-on supports retrieving an edit decision list for epg tags. + * @return True if supported, false otherwise. + */ + bool SupportsEpgTagEdl() const + { + return m_addonCapabilities && m_addonCapabilities->bSupportsEPG && + m_addonCapabilities->bSupportsEPGEdl; + } + + /*! + * @brief Check whether this add-on supports renaming recordings.. + * @return True if supported, false otherwise. + */ + bool SupportsRecordingsRename() const + { + return m_addonCapabilities && m_addonCapabilities->bSupportsRecordings && + m_addonCapabilities->bSupportsRecordingsRename; + } + + /*! + * @brief Check whether this add-on supports changing lifetime of recording. + * @return True if supported, false otherwise. + */ + bool SupportsRecordingsLifetimeChange() const + { + return m_addonCapabilities && m_addonCapabilities->bSupportsRecordings && + m_addonCapabilities->bSupportsRecordingsLifetimeChange; + } + + /*! + * @brief Obtain a list with all possible values for recordings lifetime. + * @param list out, the list with the values or an empty list, if lifetime is not supported. + */ + void GetRecordingsLifetimeValues(std::vector<std::pair<std::string, int>>& list) const; + + /*! + * @brief Check whether this add-on supports retrieving the size recordings.. + * @return True if supported, false otherwise. + */ + bool SupportsRecordingsSize() const + { + return m_addonCapabilities && m_addonCapabilities->bSupportsRecordings && + m_addonCapabilities->bSupportsRecordingSize; + } + + /*! + * @brief Check whether this add-on supports deleting recordings. + * @return True if supported, false otherwise. + */ + bool SupportsRecordingsDelete() const + { + return m_addonCapabilities && m_addonCapabilities->bSupportsRecordings && + m_addonCapabilities->bSupportsRecordingsDelete; + } + + ///////////////////////////////////////////////////////////////////////////////// + // + // Streams + // + ///////////////////////////////////////////////////////////////////////////////// + + /*! + * @brief Check whether this add-on provides an input stream. false if Kodi handles the stream. + * @return True if supported, false otherwise. + */ + bool HandlesInputStream() const + { + return m_addonCapabilities && m_addonCapabilities->bHandlesInputStream; + } + + /*! + * @brief Check whether this add-on demultiplexes packets. + * @return True if supported, false otherwise. + */ + bool HandlesDemuxing() const + { + return m_addonCapabilities && m_addonCapabilities->bHandlesDemuxing; + } + +private: + void InitRecordingsLifetimeValues(); + + std::unique_ptr<PVR_ADDON_CAPABILITIES> m_addonCapabilities; + std::vector<std::pair<std::string, int>> m_recordingsLifetimeValues; +}; + +} // namespace PVR diff --git a/xbmc/pvr/addons/PVRClientMenuHooks.cpp b/xbmc/pvr/addons/PVRClientMenuHooks.cpp new file mode 100644 index 0000000..12f150a --- /dev/null +++ b/xbmc/pvr/addons/PVRClientMenuHooks.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2012-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 "PVRClientMenuHooks.h" + +#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_menu_hook.h" +#include "guilib/LocalizeStrings.h" +#include "pvr/PVRContextMenus.h" +#include "utils/log.h" + +namespace PVR +{ + +CPVRClientMenuHook::CPVRClientMenuHook(const std::string& addonId, const PVR_MENUHOOK& hook) +: m_addonId(addonId), + m_hook(new PVR_MENUHOOK(hook)) +{ + if (hook.category != PVR_MENUHOOK_UNKNOWN && + hook.category != PVR_MENUHOOK_ALL && + hook.category != PVR_MENUHOOK_CHANNEL && + hook.category != PVR_MENUHOOK_TIMER && + hook.category != PVR_MENUHOOK_EPG && + hook.category != PVR_MENUHOOK_RECORDING && + hook.category != PVR_MENUHOOK_DELETED_RECORDING && + hook.category != PVR_MENUHOOK_SETTING) + CLog::LogF(LOGERROR, "Unknown PVR_MENUHOOK_CAT value: {}", hook.category); +} + +bool CPVRClientMenuHook::operator ==(const CPVRClientMenuHook& right) const +{ + if (this == &right) + return true; + + return m_addonId == right.m_addonId && + m_hook->iHookId == right.m_hook->iHookId && + m_hook->iLocalizedStringId == right.m_hook->iLocalizedStringId && + m_hook->category == right.m_hook->category; +} + +bool CPVRClientMenuHook::IsAllHook() const +{ + return m_hook->category == PVR_MENUHOOK_ALL; +} + +bool CPVRClientMenuHook::IsChannelHook() const +{ + return m_hook->category == PVR_MENUHOOK_CHANNEL; +} + +bool CPVRClientMenuHook::IsTimerHook() const +{ + return m_hook->category == PVR_MENUHOOK_TIMER; +} + +bool CPVRClientMenuHook::IsEpgHook() const +{ + return m_hook->category == PVR_MENUHOOK_EPG; +} + +bool CPVRClientMenuHook::IsRecordingHook() const +{ + return m_hook->category == PVR_MENUHOOK_RECORDING; +} + +bool CPVRClientMenuHook::IsDeletedRecordingHook() const +{ + return m_hook->category == PVR_MENUHOOK_DELETED_RECORDING; +} + +bool CPVRClientMenuHook::IsSettingsHook() const +{ + return m_hook->category == PVR_MENUHOOK_SETTING; +} + +std::string CPVRClientMenuHook::GetAddonId() const +{ + return m_addonId; +} + +unsigned int CPVRClientMenuHook::GetId() const +{ + return m_hook->iHookId; +} + +unsigned int CPVRClientMenuHook::GetLabelId() const +{ + return m_hook->iLocalizedStringId; +} + +std::string CPVRClientMenuHook::GetLabel() const +{ + return g_localizeStrings.GetAddonString(m_addonId, m_hook->iLocalizedStringId); +} + +void CPVRClientMenuHooks::AddHook(const PVR_MENUHOOK& addonHook) +{ + if (!m_hooks) + m_hooks.reset(new std::vector<CPVRClientMenuHook>()); + + const CPVRClientMenuHook hook(m_addonId, addonHook); + m_hooks->emplace_back(hook); + CPVRContextMenuManager::GetInstance().AddMenuHook(hook); +} + +void CPVRClientMenuHooks::Clear() +{ + if (!m_hooks) + return; + + for (const auto& hook : *m_hooks) + CPVRContextMenuManager::GetInstance().RemoveMenuHook(hook); + + m_hooks.reset(); +} + +std::vector<CPVRClientMenuHook> CPVRClientMenuHooks::GetHooks( + const std::function<bool(const CPVRClientMenuHook& hook)>& function) const +{ + std::vector<CPVRClientMenuHook> hooks; + + if (!m_hooks) + return hooks; + + for (const CPVRClientMenuHook& hook : *m_hooks) + { + if (function(hook) || hook.IsAllHook()) + hooks.emplace_back(hook); + } + return hooks; +} + +std::vector<CPVRClientMenuHook> CPVRClientMenuHooks::GetChannelHooks() const +{ + return GetHooks([](const CPVRClientMenuHook& hook) + { + return hook.IsChannelHook(); + }); +} + +std::vector<CPVRClientMenuHook> CPVRClientMenuHooks::GetTimerHooks() const +{ + return GetHooks([](const CPVRClientMenuHook& hook) + { + return hook.IsTimerHook(); + }); +} + +std::vector<CPVRClientMenuHook> CPVRClientMenuHooks::GetEpgHooks() const +{ + return GetHooks([](const CPVRClientMenuHook& hook) + { + return hook.IsEpgHook(); + }); +} + +std::vector<CPVRClientMenuHook> CPVRClientMenuHooks::GetRecordingHooks() const +{ + return GetHooks([](const CPVRClientMenuHook& hook) + { + return hook.IsRecordingHook(); + }); +} + +std::vector<CPVRClientMenuHook> CPVRClientMenuHooks::GetDeletedRecordingHooks() const +{ + return GetHooks([](const CPVRClientMenuHook& hook) + { + return hook.IsDeletedRecordingHook(); + }); +} + +std::vector<CPVRClientMenuHook> CPVRClientMenuHooks::GetSettingsHooks() const +{ + return GetHooks([](const CPVRClientMenuHook& hook) + { + return hook.IsSettingsHook(); + }); +} + +} // namespace PVR diff --git a/xbmc/pvr/addons/PVRClientMenuHooks.h b/xbmc/pvr/addons/PVRClientMenuHooks.h new file mode 100644 index 0000000..69d7f79 --- /dev/null +++ b/xbmc/pvr/addons/PVRClientMenuHooks.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2012-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. + */ + +#pragma once + +#include <functional> +#include <memory> +#include <string> +#include <vector> + +struct PVR_MENUHOOK; + +namespace PVR +{ + class CPVRClientMenuHook + { + public: + CPVRClientMenuHook() = delete; + virtual ~CPVRClientMenuHook() = default; + + CPVRClientMenuHook(const std::string& addonId, const PVR_MENUHOOK& hook); + + bool operator ==(const CPVRClientMenuHook& right) const; + + bool IsAllHook() const; + bool IsChannelHook() const; + bool IsTimerHook() const; + bool IsEpgHook() const; + bool IsRecordingHook() const; + bool IsDeletedRecordingHook() const; + bool IsSettingsHook() const; + + std::string GetAddonId() const; + unsigned int GetId() const; + unsigned int GetLabelId() const; + std::string GetLabel() const; + + private: + std::string m_addonId; + std::shared_ptr<PVR_MENUHOOK> m_hook; + }; + + class CPVRClientMenuHooks + { + public: + CPVRClientMenuHooks() = default; + virtual ~CPVRClientMenuHooks() = default; + + explicit CPVRClientMenuHooks(const std::string& addonId) : m_addonId(addonId) {} + + void AddHook(const PVR_MENUHOOK& addonHook); + void Clear(); + + std::vector<CPVRClientMenuHook> GetChannelHooks() const; + std::vector<CPVRClientMenuHook> GetTimerHooks() const; + std::vector<CPVRClientMenuHook> GetEpgHooks() const; + std::vector<CPVRClientMenuHook> GetRecordingHooks() const; + std::vector<CPVRClientMenuHook> GetDeletedRecordingHooks() const; + std::vector<CPVRClientMenuHook> GetSettingsHooks() const; + + private: + std::vector<CPVRClientMenuHook> GetHooks( + const std::function<bool(const CPVRClientMenuHook& hook)>& function) const; + + std::string m_addonId; + std::unique_ptr<std::vector<CPVRClientMenuHook>> m_hooks; + }; +} diff --git a/xbmc/pvr/addons/PVRClientUID.cpp b/xbmc/pvr/addons/PVRClientUID.cpp new file mode 100644 index 0000000..8788385 --- /dev/null +++ b/xbmc/pvr/addons/PVRClientUID.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2012-2022 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 "PVRClientUID.h" + +#include <functional> + +using namespace PVR; + +int CPVRClientUID::GetUID() const +{ + if (!m_uidCreated) + { + std::hash<std::string> hasher; + + // Note: For database backwards compatibility reasons the hash of the first instance + // must be calculated just from the addonId, not from addonId and instanceId. + m_uid = static_cast<int>(hasher( + (m_instanceID > ADDON::ADDON_FIRST_INSTANCE_ID ? std::to_string(m_instanceID) + "@" : "") + + m_addonID)); + if (m_uid < 0) + m_uid = -m_uid; + + m_uidCreated = true; + } + + return m_uid; +} diff --git a/xbmc/pvr/addons/PVRClientUID.h b/xbmc/pvr/addons/PVRClientUID.h new file mode 100644 index 0000000..5b5d1c0 --- /dev/null +++ b/xbmc/pvr/addons/PVRClientUID.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2012-2022 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. + */ + +#pragma once + +#include "addons/IAddon.h" + +#include <string> + +namespace PVR +{ +class CPVRClientUID final +{ +public: + CPVRClientUID(const std::string& addonID, ADDON::AddonInstanceId instanceID) + : m_addonID(addonID), m_instanceID(instanceID) + { + } + + virtual ~CPVRClientUID() = default; + + /*! + * @brief Return the numeric UID. + * @return The numeric UID. + */ + int GetUID() const; + +private: + CPVRClientUID() = delete; + + std::string m_addonID; + ADDON::AddonInstanceId m_instanceID{ADDON::ADDON_SINGLETON_INSTANCE_ID}; + + mutable bool m_uidCreated{false}; + mutable int m_uid{0}; +}; +} // namespace PVR diff --git a/xbmc/pvr/addons/PVRClients.cpp b/xbmc/pvr/addons/PVRClients.cpp new file mode 100644 index 0000000..8cda035 --- /dev/null +++ b/xbmc/pvr/addons/PVRClients.cpp @@ -0,0 +1,977 @@ +/* + * Copyright (C) 2012-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 "PVRClients.h" + +#include "ServiceBroker.h" +#include "addons/AddonEvents.h" +#include "addons/AddonManager.h" +#include "addons/addoninfo/AddonInfo.h" +#include "addons/addoninfo/AddonType.h" +#include "guilib/LocalizeStrings.h" +#include "messaging/ApplicationMessenger.h" +#include "pvr/PVREventLogJob.h" +#include "pvr/PVRManager.h" +#include "pvr/PVRPlaybackState.h" +#include "pvr/addons/PVRClient.h" +#include "pvr/addons/PVRClientUID.h" +#include "pvr/channels/PVRChannelGroupInternal.h" +#include "pvr/guilib/PVRGUIProgressHandler.h" +#include "utils/JobManager.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <algorithm> +#include <functional> +#include <memory> +#include <mutex> +#include <string> +#include <utility> +#include <vector> + +using namespace ADDON; +using namespace PVR; + +CPVRClients::CPVRClients() +{ + CServiceBroker::GetAddonMgr().RegisterAddonMgrCallback(AddonType::PVRDLL, this); + CServiceBroker::GetAddonMgr().Events().Subscribe(this, &CPVRClients::OnAddonEvent); +} + +CPVRClients::~CPVRClients() +{ + CServiceBroker::GetAddonMgr().Events().Unsubscribe(this); + CServiceBroker::GetAddonMgr().UnregisterAddonMgrCallback(AddonType::PVRDLL); + + for (const auto& client : m_clientMap) + { + client.second->Destroy(); + } +} + +void CPVRClients::Start() +{ + UpdateClients(); +} + +void CPVRClients::Stop() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& client : m_clientMap) + { + client.second->Stop(); + } +} + +void CPVRClients::Continue() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& client : m_clientMap) + { + client.second->Continue(); + } +} + +void CPVRClients::UpdateClients( + const std::string& changedAddonId /* = "" */, + ADDON::AddonInstanceId changedInstanceId /* = ADDON::ADDON_SINGLETON_INSTANCE_ID */) +{ + std::vector<std::pair<AddonInfoPtr, bool>> addonsWithStatus; + if (!GetAddonsWithStatus(changedAddonId, addonsWithStatus)) + return; + + std::vector<std::shared_ptr<CPVRClient>> clientsToCreate; // client + std::vector<std::pair<int, std::string>> clientsToReCreate; // client id, addon name + std::vector<int> clientsToDestroy; // client id + + { + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& addonWithStatus : addonsWithStatus) + { + const AddonInfoPtr addon = addonWithStatus.first; + const std::vector<std::pair<ADDON::AddonInstanceId, bool>> instanceIdsWithStatus = + GetInstanceIdsWithStatus(addon, addonWithStatus.second); + + for (const auto& instanceIdWithStatus : instanceIdsWithStatus) + { + const ADDON::AddonInstanceId instanceId = instanceIdWithStatus.first; + bool instanceEnabled = instanceIdWithStatus.second; + const CPVRClientUID clientUID(addon->ID(), instanceId); + const int clientId = clientUID.GetUID(); + + if (instanceEnabled && (!IsKnownClient(clientId) || !IsCreatedClient(clientId))) + { + std::shared_ptr<CPVRClient> client; + const bool isKnownClient = IsKnownClient(clientId); + if (isKnownClient) + { + client = GetClient(clientId); + } + else + { + client = std::make_shared<CPVRClient>(addon, instanceId, clientId); + if (!client) + { + CLog::LogF(LOGERROR, "Severe error, incorrect add-on type"); + continue; + } + } + + // determine actual enabled state of instance + if (instanceId != ADDON_SINGLETON_INSTANCE_ID) + instanceEnabled = client->IsEnabled(); + + if (instanceEnabled) + clientsToCreate.emplace_back(client); + else if (isKnownClient) + clientsToDestroy.emplace_back(clientId); + } + else if (IsCreatedClient(clientId)) + { + // determine actual enabled state of instance + if (instanceEnabled && instanceId != ADDON_SINGLETON_INSTANCE_ID) + { + const std::shared_ptr<CPVRClient> client = GetClient(clientId); + instanceEnabled = client ? client->IsEnabled() : false; + } + + if (instanceEnabled) + clientsToReCreate.emplace_back(clientId, addon->Name()); + else + clientsToDestroy.emplace_back(clientId); + } + } + } + } + + if (!clientsToCreate.empty() || !clientsToReCreate.empty() || !clientsToDestroy.empty()) + { + CServiceBroker::GetPVRManager().Stop(); + + auto progressHandler = std::make_unique<CPVRGUIProgressHandler>( + g_localizeStrings.Get(19239)); // Creating PVR clients + + unsigned int i = 0; + for (const auto& client : clientsToCreate) + { + progressHandler->UpdateProgress(client->Name(), i++, + clientsToCreate.size() + clientsToReCreate.size()); + + const ADDON_STATUS status = client->Create(); + + if (status != ADDON_STATUS_OK) + { + CLog::LogF(LOGERROR, "Failed to create add-on {}, status = {}", client->ID(), status); + if (status == ADDON_STATUS_PERMANENT_FAILURE) + { + CServiceBroker::GetAddonMgr().DisableAddon(client->ID(), + AddonDisabledReason::PERMANENT_FAILURE); + CServiceBroker::GetJobManager()->AddJob( + new CPVREventLogJob(true, EventLevel::Error, client->Name(), + g_localizeStrings.Get(24070), client->Icon()), + nullptr); + } + } + } + + for (const auto& clientInfo : clientsToReCreate) + { + progressHandler->UpdateProgress(clientInfo.second, i++, + clientsToCreate.size() + clientsToReCreate.size()); + + // stop and recreate client + StopClient(clientInfo.first, true /* restart */); + } + + progressHandler.reset(); + + for (const auto& client : clientsToDestroy) + { + // destroy client + StopClient(client, false /* no restart */); + } + + if (!clientsToCreate.empty()) + { + // update created clients map + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& client : clientsToCreate) + { + if (m_clientMap.find(client->GetID()) == m_clientMap.end()) + { + m_clientMap.insert({client->GetID(), client}); + } + } + } + + CServiceBroker::GetPVRManager().Start(); + } +} + +bool CPVRClients::RequestRestart(const std::string& addonId, + ADDON::AddonInstanceId instanceId, + bool bDataChanged) +{ + CServiceBroker::GetJobManager()->Submit([this, addonId, instanceId] { + UpdateClients(addonId, instanceId); + return true; + }); + return true; +} + +bool CPVRClients::StopClient(int clientId, bool restart) +{ + // stop playback if needed + if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlaying()) + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_MEDIA_STOP); + + std::unique_lock<CCriticalSection> lock(m_critSection); + + const std::shared_ptr<CPVRClient> client = GetClient(clientId); + if (client) + { + if (restart) + { + client->ReCreate(); + } + else + { + const auto it = m_clientMap.find(clientId); + if (it != m_clientMap.end()) + m_clientMap.erase(it); + + client->Destroy(); + } + return true; + } + + return false; +} + +void CPVRClients::OnAddonEvent(const AddonEvent& event) +{ + if (typeid(event) == typeid(AddonEvents::Enabled) || // also called on install, + typeid(event) == typeid(AddonEvents::Disabled) || // not called on uninstall + typeid(event) == typeid(AddonEvents::UnInstalled) || + typeid(event) == typeid(AddonEvents::ReInstalled) || + typeid(event) == typeid(AddonEvents::InstanceAdded) || + typeid(event) == typeid(AddonEvents::InstanceRemoved)) + { + // update addons + const std::string addonId = event.addonId; + const ADDON::AddonInstanceId instanceId = event.instanceId; + if (CServiceBroker::GetAddonMgr().HasType(addonId, AddonType::PVRDLL)) + { + CServiceBroker::GetJobManager()->Submit([this, addonId, instanceId] { + UpdateClients(addonId, instanceId); + return true; + }); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// client access +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +std::shared_ptr<CPVRClient> CPVRClients::GetClient(int clientId) const +{ + if (clientId <= PVR_INVALID_CLIENT_ID) + return {}; + + std::unique_lock<CCriticalSection> lock(m_critSection); + const auto it = m_clientMap.find(clientId); + if (it != m_clientMap.end()) + return it->second; + + return {}; +} + +int CPVRClients::CreatedClientAmount() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return std::count_if(m_clientMap.cbegin(), m_clientMap.cend(), + [](const auto& client) { return client.second->ReadyToUse(); }); +} + +bool CPVRClients::HasCreatedClients() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return std::any_of(m_clientMap.cbegin(), m_clientMap.cend(), + [](const auto& client) { return client.second->ReadyToUse(); }); +} + +bool CPVRClients::IsKnownClient(int clientId) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + // valid client IDs start at 1 + const auto it = m_clientMap.find(clientId); + return (it != m_clientMap.end() && (*it).second->GetID() > 0); +} + +bool CPVRClients::IsCreatedClient(int iClientId) const +{ + return GetCreatedClient(iClientId) != nullptr; +} + +std::shared_ptr<CPVRClient> CPVRClients::GetCreatedClient(int clientId) const +{ + std::shared_ptr<CPVRClient> client = GetClient(clientId); + if (client && client->ReadyToUse()) + return client; + + return {}; +} + +CPVRClientMap CPVRClients::GetCreatedClients() const +{ + CPVRClientMap clients; + + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& client : m_clientMap) + { + if (client.second->ReadyToUse()) + { + clients.insert(std::make_pair(client.second->GetID(), client.second)); + } + } + + return clients; +} + +std::vector<CVariant> CPVRClients::GetClientProviderInfos() const +{ + std::vector<AddonInfoPtr> addonInfos; + // Get enabled and disabled PVR client addon infos + CServiceBroker::GetAddonMgr().GetAddonInfos(addonInfos, false, AddonType::PVRDLL); + + std::unique_lock<CCriticalSection> lock(m_critSection); + + std::vector<CVariant> clientProviderInfos; + for (const auto& addonInfo : addonInfos) + { + std::vector<ADDON::AddonInstanceId> instanceIds = addonInfo->GetKnownInstanceIds(); + for (const auto& instanceId : instanceIds) + { + CVariant clientProviderInfo(CVariant::VariantTypeObject); + clientProviderInfo["clientid"] = CPVRClientUID(addonInfo->ID(), instanceId).GetUID(); + clientProviderInfo["addonid"] = addonInfo->ID(); + clientProviderInfo["instanceid"] = instanceId; + clientProviderInfo["enabled"] = + !CServiceBroker::GetAddonMgr().IsAddonDisabled(addonInfo->ID()); + clientProviderInfo["name"] = addonInfo->Name(); + clientProviderInfo["icon"] = addonInfo->Icon(); + auto& artMap = addonInfo->Art(); + auto thumbEntry = artMap.find("thumb"); + if (thumbEntry != artMap.end()) + clientProviderInfo["thumb"] = thumbEntry->second; + + clientProviderInfos.emplace_back(clientProviderInfo); + } + } + + return clientProviderInfos; +} + +int CPVRClients::GetFirstCreatedClientID() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + const auto it = std::find_if(m_clientMap.cbegin(), m_clientMap.cend(), + [](const auto& client) { return client.second->ReadyToUse(); }); + return it != m_clientMap.cend() ? (*it).second->GetID() : -1; +} + +PVR_ERROR CPVRClients::GetCallableClients(CPVRClientMap& clientsReady, + std::vector<int>& clientsNotReady) const +{ + clientsNotReady.clear(); + + std::vector<AddonInfoPtr> addons; + CServiceBroker::GetAddonMgr().GetAddonInfos(addons, true, AddonType::PVRDLL); + + for (const auto& addon : addons) + { + std::vector<ADDON::AddonInstanceId> instanceIds = addon->GetKnownInstanceIds(); + for (const auto& instanceId : instanceIds) + { + const int clientId = CPVRClientUID(addon->ID(), instanceId).GetUID(); + const std::shared_ptr<CPVRClient> client = GetClient(clientId); + + if (client && client->ReadyToUse() && !client->IgnoreClient()) + { + clientsReady.insert(std::make_pair(clientId, client)); + } + else + { + clientsNotReady.emplace_back(clientId); + } + } + } + + return clientsNotReady.empty() ? PVR_ERROR_NO_ERROR : PVR_ERROR_SERVER_ERROR; +} + +int CPVRClients::EnabledClientAmount() const +{ + CPVRClientMap clientMap; + { + std::unique_lock<CCriticalSection> lock(m_critSection); + clientMap = m_clientMap; + } + + ADDON::CAddonMgr& addonMgr = CServiceBroker::GetAddonMgr(); + return std::count_if(clientMap.cbegin(), clientMap.cend(), [&addonMgr](const auto& client) { + return !addonMgr.IsAddonDisabled(client.second->ID()); + }); +} + +bool CPVRClients::IsEnabledClient(int clientId) const +{ + const std::shared_ptr<CPVRClient> client = GetClient(clientId); + return client && !CServiceBroker::GetAddonMgr().IsAddonDisabled(client->ID()); +} + +std::vector<CVariant> CPVRClients::GetEnabledClientInfos() const +{ + std::vector<CVariant> clientInfos; + + CPVRClientMap clientMap; + { + std::unique_lock<CCriticalSection> lock(m_critSection); + clientMap = m_clientMap; + } + + for (const auto& client : clientMap) + { + const auto& addonInfo = + CServiceBroker::GetAddonMgr().GetAddonInfo(client.second->ID(), AddonType::PVRDLL); + + if (addonInfo) + { + // This will be the same variant structure used in the json api + CVariant clientInfo(CVariant::VariantTypeObject); + clientInfo["clientid"] = client.first; + clientInfo["addonid"] = client.second->ID(); + clientInfo["instanceid"] = client.second->InstanceId(); + clientInfo["label"] = addonInfo->Name(); // Note that this is called label instead of name + + const auto& capabilities = client.second->GetClientCapabilities(); + clientInfo["supportstv"] = capabilities.SupportsTV(); + clientInfo["supportsradio"] = capabilities.SupportsRadio(); + clientInfo["supportsepg"] = capabilities.SupportsEPG(); + clientInfo["supportsrecordings"] = capabilities.SupportsRecordings(); + clientInfo["supportstimers"] = capabilities.SupportsTimers(); + clientInfo["supportschannelgroups"] = capabilities.SupportsChannelGroups(); + clientInfo["supportschannelscan"] = capabilities.SupportsChannelScan(); + clientInfo["supportchannelproviders"] = capabilities.SupportsProviders(); + + clientInfos.push_back(clientInfo); + } + } + + return clientInfos; +} + +bool CPVRClients::HasIgnoredClients() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return std::any_of(m_clientMap.cbegin(), m_clientMap.cend(), + [](const auto& client) { return client.second->IgnoreClient(); }); +} + +std::vector<ADDON::AddonInstanceId> CPVRClients::GetKnownInstanceIds( + const std::string& addonID) const +{ + std::vector<ADDON::AddonInstanceId> instanceIds; + + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& entry : m_clientMap) + { + if (entry.second->ID() == addonID) + instanceIds.emplace_back(entry.second->InstanceId()); + } + + return instanceIds; +} + +bool CPVRClients::GetAddonsWithStatus( + const std::string& changedAddonId, + std::vector<std::pair<AddonInfoPtr, bool>>& addonsWithStatus) const +{ + std::vector<AddonInfoPtr> addons; + CServiceBroker::GetAddonMgr().GetAddonInfos(addons, false, AddonType::PVRDLL); + + if (addons.empty()) + return false; + + bool foundChangedAddon = changedAddonId.empty(); + for (const auto& addon : addons) + { + bool enabled = !CServiceBroker::GetAddonMgr().IsAddonDisabled(addon->ID()); + addonsWithStatus.emplace_back(std::make_pair(addon, enabled)); + + if (!foundChangedAddon && addon->ID() == changedAddonId) + foundChangedAddon = true; + } + + return foundChangedAddon; +} + +std::vector<std::pair<ADDON::AddonInstanceId, bool>> CPVRClients::GetInstanceIdsWithStatus( + const AddonInfoPtr& addon, bool addonIsEnabled) const +{ + std::vector<std::pair<ADDON::AddonInstanceId, bool>> instanceIdsWithStatus; + + std::vector<ADDON::AddonInstanceId> instanceIds = addon->GetKnownInstanceIds(); + std::transform(instanceIds.cbegin(), instanceIds.cend(), + std::back_inserter(instanceIdsWithStatus), [addonIsEnabled](const auto& id) { + return std::pair<ADDON::AddonInstanceId, bool>(id, addonIsEnabled); + }); + + // find removed instances + const std::vector<ADDON::AddonInstanceId> knownInstanceIds = GetKnownInstanceIds(addon->ID()); + for (const auto& knownInstanceId : knownInstanceIds) + { + if (std::find(instanceIds.begin(), instanceIds.end(), knownInstanceId) == instanceIds.end()) + { + // instance was removed + instanceIdsWithStatus.emplace_back( + std::pair<ADDON::AddonInstanceId, bool>(knownInstanceId, false)); + } + } + + return instanceIdsWithStatus; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// client API calls +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +std::vector<SBackend> CPVRClients::GetBackendProperties() const +{ + std::vector<SBackend> backendProperties; + + ForCreatedClients(__FUNCTION__, [&backendProperties](const std::shared_ptr<CPVRClient>& client) { + SBackend properties; + + if (client->GetDriveSpace(properties.diskTotal, properties.diskUsed) == PVR_ERROR_NO_ERROR) + { + properties.diskTotal *= 1024; + properties.diskUsed *= 1024; + } + + int iAmount = 0; + if (client->GetProvidersAmount(iAmount) == PVR_ERROR_NO_ERROR) + properties.numProviders = iAmount; + if (client->GetChannelGroupsAmount(iAmount) == PVR_ERROR_NO_ERROR) + properties.numChannelGroups = iAmount; + if (client->GetChannelsAmount(iAmount) == PVR_ERROR_NO_ERROR) + properties.numChannels = iAmount; + if (client->GetTimersAmount(iAmount) == PVR_ERROR_NO_ERROR) + properties.numTimers = iAmount; + if (client->GetRecordingsAmount(false, iAmount) == PVR_ERROR_NO_ERROR) + properties.numRecordings = iAmount; + if (client->GetRecordingsAmount(true, iAmount) == PVR_ERROR_NO_ERROR) + properties.numDeletedRecordings = iAmount; + properties.name = client->GetBackendName(); + properties.version = client->GetBackendVersion(); + properties.host = client->GetConnectionString(); + + backendProperties.emplace_back(properties); + return PVR_ERROR_NO_ERROR; + }); + + return backendProperties; +} + +bool CPVRClients::GetTimers(const std::vector<std::shared_ptr<CPVRClient>>& clients, + CPVRTimersContainer* timers, + std::vector<int>& failedClients) +{ + return ForClients(__FUNCTION__, clients, + [timers](const std::shared_ptr<CPVRClient>& client) { + return client->GetTimers(timers); + }, + failedClients) == PVR_ERROR_NO_ERROR; +} + +PVR_ERROR CPVRClients::GetTimerTypes(std::vector<std::shared_ptr<CPVRTimerType>>& results) const +{ + return ForCreatedClients(__FUNCTION__, [&results](const std::shared_ptr<CPVRClient>& client) { + std::vector<std::shared_ptr<CPVRTimerType>> types; + PVR_ERROR ret = client->GetTimerTypes(types); + if (ret == PVR_ERROR_NO_ERROR) + results.insert(results.end(), types.begin(), types.end()); + return ret; + }); +} + +PVR_ERROR CPVRClients::GetRecordings(const std::vector<std::shared_ptr<CPVRClient>>& clients, + CPVRRecordings* recordings, + bool deleted, + std::vector<int>& failedClients) +{ + return ForClients(__FUNCTION__, clients, + [recordings, deleted](const std::shared_ptr<CPVRClient>& client) { + return client->GetRecordings(recordings, deleted); + }, + failedClients); +} + +PVR_ERROR CPVRClients::DeleteAllRecordingsFromTrash() +{ + return ForCreatedClients(__FUNCTION__, [](const std::shared_ptr<CPVRClient>& client) { + return client->DeleteAllRecordingsFromTrash(); + }); +} + +PVR_ERROR CPVRClients::SetEPGMaxPastDays(int iPastDays) +{ + return ForCreatedClients(__FUNCTION__, [iPastDays](const std::shared_ptr<CPVRClient>& client) { + return client->SetEPGMaxPastDays(iPastDays); + }); +} + +PVR_ERROR CPVRClients::SetEPGMaxFutureDays(int iFutureDays) +{ + return ForCreatedClients(__FUNCTION__, [iFutureDays](const std::shared_ptr<CPVRClient>& client) { + return client->SetEPGMaxFutureDays(iFutureDays); + }); +} + +PVR_ERROR CPVRClients::GetChannels(const std::vector<std::shared_ptr<CPVRClient>>& clients, + bool bRadio, + std::vector<std::shared_ptr<CPVRChannel>>& channels, + std::vector<int>& failedClients) +{ + return ForClients(__FUNCTION__, clients, + [bRadio, &channels](const std::shared_ptr<CPVRClient>& client) { + return client->GetChannels(bRadio, channels); + }, + failedClients); +} + +PVR_ERROR CPVRClients::GetProviders(const std::vector<std::shared_ptr<CPVRClient>>& clients, + CPVRProvidersContainer* providers, + std::vector<int>& failedClients) +{ + return ForClients(__FUNCTION__, clients, + [providers](const std::shared_ptr<CPVRClient>& client) { + return client->GetProviders(*providers); + }, + failedClients); +} + +PVR_ERROR CPVRClients::GetChannelGroups(const std::vector<std::shared_ptr<CPVRClient>>& clients, + CPVRChannelGroups* groups, + std::vector<int>& failedClients) +{ + return ForClients(__FUNCTION__, clients, + [groups](const std::shared_ptr<CPVRClient>& client) { + return client->GetChannelGroups(groups); + }, + failedClients); +} + +PVR_ERROR CPVRClients::GetChannelGroupMembers( + const std::vector<std::shared_ptr<CPVRClient>>& clients, + CPVRChannelGroup* group, + std::vector<std::shared_ptr<CPVRChannelGroupMember>>& groupMembers, + std::vector<int>& failedClients) +{ + return ForClients(__FUNCTION__, clients, + [group, &groupMembers](const std::shared_ptr<CPVRClient>& client) { + return client->GetChannelGroupMembers(group, groupMembers); + }, + failedClients); +} + +std::vector<std::shared_ptr<CPVRClient>> CPVRClients::GetClientsSupportingChannelScan() const +{ + std::vector<std::shared_ptr<CPVRClient>> possibleScanClients; + + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& entry : m_clientMap) + { + const auto& client = entry.second; + if (client->ReadyToUse() && !client->IgnoreClient() && + client->GetClientCapabilities().SupportsChannelScan()) + possibleScanClients.emplace_back(client); + } + + return possibleScanClients; +} + +std::vector<std::shared_ptr<CPVRClient>> CPVRClients::GetClientsSupportingChannelSettings(bool bRadio) const +{ + std::vector<std::shared_ptr<CPVRClient>> possibleSettingsClients; + + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& entry : m_clientMap) + { + const auto& client = entry.second; + if (client->ReadyToUse() && !client->IgnoreClient()) + { + const CPVRClientCapabilities& caps = client->GetClientCapabilities(); + if (caps.SupportsChannelSettings() && + ((bRadio && caps.SupportsRadio()) || (!bRadio && caps.SupportsTV()))) + possibleSettingsClients.emplace_back(client); + } + } + + return possibleSettingsClients; +} + +bool CPVRClients::AnyClientSupportingRecordingsSize() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return std::any_of(m_clientMap.cbegin(), m_clientMap.cend(), [](const auto& entry) { + const auto& client = entry.second; + return client->ReadyToUse() && !client->IgnoreClient() && + client->GetClientCapabilities().SupportsRecordingsSize(); + }); +} + +bool CPVRClients::AnyClientSupportingEPG() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return std::any_of(m_clientMap.cbegin(), m_clientMap.cend(), [](const auto& entry) { + const auto& client = entry.second; + return client->ReadyToUse() && !client->IgnoreClient() && + client->GetClientCapabilities().SupportsEPG(); + }); +} + +bool CPVRClients::AnyClientSupportingRecordings() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return std::any_of(m_clientMap.cbegin(), m_clientMap.cend(), [](const auto& entry) { + const auto& client = entry.second; + return client->ReadyToUse() && !client->IgnoreClient() && + client->GetClientCapabilities().SupportsRecordings(); + }); +} + +bool CPVRClients::AnyClientSupportingRecordingsDelete() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return std::any_of(m_clientMap.cbegin(), m_clientMap.cend(), [](const auto& entry) { + const auto& client = entry.second; + return client->ReadyToUse() && !client->IgnoreClient() && + client->GetClientCapabilities().SupportsRecordingsDelete(); + }); +} + +void CPVRClients::OnSystemSleep() +{ + ForCreatedClients(__FUNCTION__, [](const std::shared_ptr<CPVRClient>& client) { + client->OnSystemSleep(); + return PVR_ERROR_NO_ERROR; + }); +} + +void CPVRClients::OnSystemWake() +{ + ForCreatedClients(__FUNCTION__, [](const std::shared_ptr<CPVRClient>& client) { + client->OnSystemWake(); + return PVR_ERROR_NO_ERROR; + }); +} + +void CPVRClients::OnPowerSavingActivated() +{ + ForCreatedClients(__FUNCTION__, [](const std::shared_ptr<CPVRClient>& client) { + client->OnPowerSavingActivated(); + return PVR_ERROR_NO_ERROR; + }); +} + +void CPVRClients::OnPowerSavingDeactivated() +{ + ForCreatedClients(__FUNCTION__, [](const std::shared_ptr<CPVRClient>& client) { + client->OnPowerSavingDeactivated(); + return PVR_ERROR_NO_ERROR; + }); +} + +void CPVRClients::ConnectionStateChange(CPVRClient* client, + const std::string& strConnectionString, + PVR_CONNECTION_STATE newState, + const std::string& strMessage) +{ + if (!client) + return; + + int iMsg = -1; + EventLevel eLevel = EventLevel::Error; + bool bNotify = true; + + switch (newState) + { + case PVR_CONNECTION_STATE_SERVER_UNREACHABLE: + iMsg = 35505; // Server is unreachable + if (client->GetPreviousConnectionState() == PVR_CONNECTION_STATE_UNKNOWN || + client->GetPreviousConnectionState() == PVR_CONNECTION_STATE_CONNECTING) + { + // Make our users happy. There were so many complaints about this notification because their TV backend + // was not up quick enough after Kodi start. So, ignore the very first 'server not reachable' notification. + bNotify = false; + } + break; + case PVR_CONNECTION_STATE_SERVER_MISMATCH: + iMsg = 35506; // Server does not respond properly + break; + case PVR_CONNECTION_STATE_VERSION_MISMATCH: + iMsg = 35507; // Server version is not compatible + break; + case PVR_CONNECTION_STATE_ACCESS_DENIED: + iMsg = 35508; // Access denied + break; + case PVR_CONNECTION_STATE_CONNECTED: + eLevel = EventLevel::Basic; + iMsg = 36034; // Connection established + if (client->GetPreviousConnectionState() == PVR_CONNECTION_STATE_UNKNOWN || + client->GetPreviousConnectionState() == PVR_CONNECTION_STATE_CONNECTING) + bNotify = false; + break; + case PVR_CONNECTION_STATE_DISCONNECTED: + iMsg = 36030; // Connection lost + break; + case PVR_CONNECTION_STATE_CONNECTING: + eLevel = EventLevel::Information; + iMsg = 35509; // Connecting + bNotify = false; + break; + default: + CLog::LogF(LOGERROR, "Unknown connection state"); + return; + } + + // Use addon-supplied message, if present + std::string strMsg; + if (!strMessage.empty()) + strMsg = strMessage; + else + strMsg = g_localizeStrings.Get(iMsg); + + if (!strConnectionString.empty()) + strMsg = StringUtils::Format("{} ({})", strMsg, strConnectionString); + + // Notify user. + CServiceBroker::GetJobManager()->AddJob( + new CPVREventLogJob(bNotify, eLevel, client->GetFriendlyName(), strMsg, client->Icon()), + nullptr); +} + +namespace +{ + +void LogClientWarning(const char* strFunctionName, const std::shared_ptr<CPVRClient>& client) +{ + if (client->IgnoreClient()) + CLog::Log(LOGWARNING, "{}: Not calling add-on '{}'. Add-on not (yet) connected.", + strFunctionName, client->ID()); + else if (!client->ReadyToUse()) + CLog::Log(LOGWARNING, "{}: Not calling add-on '{}'. Add-on not ready to use.", strFunctionName, + client->ID()); + else + CLog::Log(LOGERROR, "{}: Not calling add-on '{}' for unexpected reason.", strFunctionName, + client->ID()); +} + +} // unnamed namespace + +PVR_ERROR CPVRClients::ForCreatedClients(const char* strFunctionName, + const PVRClientFunction& function) const +{ + std::vector<int> failedClients; + return ForCreatedClients(strFunctionName, function, failedClients); +} + +PVR_ERROR CPVRClients::ForCreatedClients(const char* strFunctionName, + const PVRClientFunction& function, + std::vector<int>& failedClients) const +{ + PVR_ERROR lastError = PVR_ERROR_NO_ERROR; + + CPVRClientMap clients; + GetCallableClients(clients, failedClients); + + if (!failedClients.empty()) + { + std::shared_ptr<CPVRClient> client; + for (int id : failedClients) + { + client = GetClient(id); + if (client) + LogClientWarning(strFunctionName, client); + } + } + + for (const auto& clientEntry : clients) + { + PVR_ERROR currentError = function(clientEntry.second); + + if (currentError != PVR_ERROR_NO_ERROR && currentError != PVR_ERROR_NOT_IMPLEMENTED) + { + lastError = currentError; + failedClients.emplace_back(clientEntry.first); + } + } + return lastError; +} + +PVR_ERROR CPVRClients::ForClients(const char* strFunctionName, + const std::vector<std::shared_ptr<CPVRClient>>& clients, + const PVRClientFunction& function, + std::vector<int>& failedClients) const +{ + if (clients.empty()) + return ForCreatedClients(strFunctionName, function, failedClients); + + PVR_ERROR lastError = PVR_ERROR_NO_ERROR; + + failedClients.clear(); + + { + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& entry : m_clientMap) + { + if (entry.second->ReadyToUse() && !entry.second->IgnoreClient() && + std::any_of(clients.cbegin(), clients.cend(), + [&entry](const auto& client) { return client->GetID() == entry.first; })) + { + // Allow ready to use clients that shall be called + continue; + } + + failedClients.emplace_back(entry.first); + } + } + + for (const auto& client : clients) + { + if (std::none_of(failedClients.cbegin(), failedClients.cend(), + [&client](int failedClientId) { return failedClientId == client->GetID(); })) + { + PVR_ERROR currentError = function(client); + + if (currentError != PVR_ERROR_NO_ERROR && currentError != PVR_ERROR_NOT_IMPLEMENTED) + { + lastError = currentError; + failedClients.emplace_back(client->GetID()); + } + } + else + { + LogClientWarning(strFunctionName, client); + } + } + return lastError; +} diff --git a/xbmc/pvr/addons/PVRClients.h b/xbmc/pvr/addons/PVRClients.h new file mode 100644 index 0000000..32248fa --- /dev/null +++ b/xbmc/pvr/addons/PVRClients.h @@ -0,0 +1,485 @@ +/* + * Copyright (C) 2012-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. + */ + +#pragma once + +#include "addons/IAddonManagerCallback.h" +#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h" +#include "threads/CriticalSection.h" + +#include <functional> +#include <map> +#include <memory> +#include <string> +#include <vector> + +class CVariant; + +namespace ADDON +{ + struct AddonEvent; + class CAddonInfo; +} + +namespace PVR +{ +class CPVRChannel; +class CPVRChannelGroupInternal; +class CPVRChannelGroup; +class CPVRChannelGroupMember; +class CPVRChannelGroups; +class CPVRProvidersContainer; +class CPVRClient; +class CPVREpg; +class CPVRRecordings; +class CPVRTimerType; +class CPVRTimersContainer; + +typedef std::map<int, std::shared_ptr<CPVRClient>> CPVRClientMap; + +/** + * Holds generic data about a backend (number of channels etc.) + */ +struct SBackend +{ + std::string name; + std::string version; + std::string host; + int numTimers = 0; + int numRecordings = 0; + int numDeletedRecordings = 0; + int numProviders = 0; + int numChannelGroups = 0; + int numChannels = 0; + uint64_t diskUsed = 0; + uint64_t diskTotal = 0; +}; + + class CPVRClients : public ADDON::IAddonMgrCallback + { + public: + CPVRClients(); + ~CPVRClients() override; + + /*! + * @brief Start all clients. + */ + void Start(); + + /*! + * @brief Stop all clients. + */ + void Stop(); + + /*! + * @brief Continue all clients. + */ + void Continue(); + + /*! + * @brief Update all clients, sync with Addon Manager state (start, restart, shutdown clients). + * @param changedAddonId The id of the changed addon, empty string denotes 'any addon'. + * @param changedInstanceId The Identifier of the changed add-on instance + */ + void UpdateClients( + const std::string& changedAddonId = "", + ADDON::AddonInstanceId changedInstanceId = ADDON::ADDON_SINGLETON_INSTANCE_ID); + + /*! + * @brief Restart a single client add-on. + * @param addonId The add-on to restart. + * @param instanceId Instance identifier to use + * @param bDataChanged True if the client's data changed, false otherwise (unused). + * @return True if the client was found and restarted, false otherwise. + */ + bool RequestRestart(const std::string& addonId, + ADDON::AddonInstanceId instanceId, + bool bDataChanged) override; + + /*! + * @brief Stop a client. + * @param clientId The id of the client to stop. + * @param restart If true, restart the client. + * @return True if the client was found, false otherwise. + */ + bool StopClient(int clientId, bool restart); + + /*! + * @brief Handle addon events (enable, disable, ...). + * @param event The addon event. + */ + void OnAddonEvent(const ADDON::AddonEvent& event); + + /*! + * @brief Get the number of created clients. + * @return The amount of created clients. + */ + int CreatedClientAmount() const; + + /*! + * @brief Check whether there are any created clients. + * @return True if at least one client is created. + */ + bool HasCreatedClients() const; + + /*! + * @brief Check whether a given client ID points to a created client. + * @param iClientId The client ID. + * @return True if the the client ID represents a created client, false otherwise. + */ + bool IsCreatedClient(int iClientId) const; + + /*! + * @brief Get the the client for the given client id, if it is created. + * @param clientId The ID of the client to get. + * @return The client if found, nullptr otherwise. + */ + std::shared_ptr<CPVRClient> GetCreatedClient(int clientId) const; + + /*! + * @brief Get all created clients. + * @return All created clients. + */ + CPVRClientMap GetCreatedClients() const; + + /*! + * @brief Get the ID of the first created client. + * @return the ID or -1 if no clients are created; + */ + int GetFirstCreatedClientID(); + + /*! + * @brief Check whether there are any created, but not (yet) connected clients. + * @return True if at least one client is ignored. + */ + bool HasIgnoredClients() const; + + /*! + * @brief Get the number of enabled clients. + * @return The amount of enabled clients. + */ + int EnabledClientAmount() const; + + /*! + * @brief Check whether a given client ID points to an enabled client. + * @param clientId The client ID. + * @return True if the the client ID represents an enabled client, false otherwise. + */ + bool IsEnabledClient(int clientId) const; + + /*! + * @brief Get a list of the enabled client infos. + * @return A list of enabled client infos. + */ + std::vector<CVariant> GetEnabledClientInfos() const; + + /*! + * @brief Get info required for providers. Include both enabled and disabled PVR add-ons + * @return A list containing the information required to create client providers. + */ + std::vector<CVariant> GetClientProviderInfos() const; + + //@} + + /*! @name general methods */ + //@{ + + /*! + * @brief Returns properties about all created clients + * @return the properties + */ + std::vector<SBackend> GetBackendProperties() const; + + //@} + + /*! @name Timer methods */ + //@{ + + /*! + * @brief Get all timers from the given clients + * @param clients The clients to fetch data from. Leave empty to fetch data from all created clients. + * @param timers Store the timers in this container. + * @param failedClients in case of errors will contain the ids of the clients for which the timers could not be obtained. + * @return true on success for all clients, false in case of error for at least one client. + */ + bool GetTimers(const std::vector<std::shared_ptr<CPVRClient>>& clients, + CPVRTimersContainer* timers, + std::vector<int>& failedClients); + + /*! + * @brief Get all supported timer types. + * @param results The container to store the result in. + * @return PVR_ERROR_NO_ERROR if the operation succeeded, the respective PVR_ERROR value otherwise. + */ + PVR_ERROR GetTimerTypes(std::vector<std::shared_ptr<CPVRTimerType>>& results) const; + + //@} + + /*! @name Recording methods */ + //@{ + + /*! + * @brief Get all recordings from the given clients + * @param clients The clients to fetch data from. Leave empty to fetch data from all created clients. + * @param recordings Store the recordings in this container. + * @param deleted If true, return deleted recordings, return not deleted recordings otherwise. + * @param failedClients in case of errors will contain the ids of the clients for which the recordings could not be obtained. + * @return PVR_ERROR_NO_ERROR if the operation succeeded, the respective PVR_ERROR value otherwise. + */ + PVR_ERROR GetRecordings(const std::vector<std::shared_ptr<CPVRClient>>& clients, + CPVRRecordings* recordings, + bool deleted, + std::vector<int>& failedClients); + + /*! + * @brief Delete all "soft" deleted recordings permanently on the backend. + * @return PVR_ERROR_NO_ERROR if the operation succeeded, the respective PVR_ERROR value otherwise. + */ + PVR_ERROR DeleteAllRecordingsFromTrash(); + + //@} + + /*! @name EPG methods */ + //@{ + + /*! + * @brief Tell all clients the past time frame to use when notifying epg events back to Kodi. + * + * The clients might push epg events asynchronously to Kodi using the callback function + * EpgEventStateChange. To be able to only push events that are actually of interest for Kodi, + * clients need to know about the future epg time frame Kodi uses. + * + * @param[in] iPastDays number of days before "now". + * @ref EPG_TIMEFRAME_UNLIMITED means that Kodi is interested in all + * epg events, regardless of event times. + * @return @ref PVR_ERROR_NO_ERROR if the operation succeeded, the respective @ref PVR_ERROR + * value otherwise. + */ + PVR_ERROR SetEPGMaxPastDays(int iPastDays); + + /*! + * @brief Tell all clients the future time frame to use when notifying epg events back to Kodi. + * + * The clients might push epg events asynchronously to Kodi using the callback function + * EpgEventStateChange. To be able to only push events that are actually of interest for Kodi, + * clients need to know about the future epg time frame Kodi uses. + * + * @param[in] iFutureDays number of days from "now". + * @ref EPG_TIMEFRAME_UNLIMITED means that Kodi is interested in all + * epg events, regardless of event times. + * @return @ref PVR_ERROR_NO_ERROR if the operation succeeded, the respective @ref PVR_ERROR + * value otherwise. + */ + PVR_ERROR SetEPGMaxFutureDays(int iFutureDays); + + //@} + + /*! @name Channel methods */ + //@{ + + /*! + * @brief Get all channels from the given clients. + * @param clients The clients to fetch data from. Leave empty to fetch data from all created clients. + * @param bRadio Whether to fetch radio or TV channels. + * @param channels The container to store the channels. + * @param failedClients in case of errors will contain the ids of the clients for which the channels could not be obtained. + * @return PVR_ERROR_NO_ERROR if the channels were fetched successfully, last error otherwise. + */ + PVR_ERROR GetChannels(const std::vector<std::shared_ptr<CPVRClient>>& clients, + bool bRadio, + std::vector<std::shared_ptr<CPVRChannel>>& channels, + std::vector<int>& failedClients); + + /*! + * @brief Get all providers from backends. + * @param clients The clients to fetch data from. Leave empty to fetch data from all created clients. + * @param group The container to store the providers in. + * @param failedClients in case of errors will contain the ids of the clients for which the providers could not be obtained. + * @return PVR_ERROR_NO_ERROR if the providers were fetched successfully, last error otherwise. + */ + PVR_ERROR GetProviders(const std::vector<std::shared_ptr<CPVRClient>>& clients, + CPVRProvidersContainer* providers, + std::vector<int>& failedClients); + + /*! + * @brief Get all channel groups from the given clients. + * @param clients The clients to fetch data from. Leave empty to fetch data from all created clients. + * @param groups Store the channel groups in this container. + * @param failedClients in case of errors will contain the ids of the clients for which the channel groups could not be obtained. + * @return PVR_ERROR_NO_ERROR if the channel groups were fetched successfully, last error otherwise. + */ + PVR_ERROR GetChannelGroups(const std::vector<std::shared_ptr<CPVRClient>>& clients, + CPVRChannelGroups* groups, + std::vector<int>& failedClients); + + /*! + * @brief Get all group members of a channel group from the given clients. + * @param clients The clients to fetch data from. Leave empty to fetch data from all created clients. + * @param group The group to get the member for. + * @param groupMembers The container for the group members. + * @param failedClients in case of errors will contain the ids of the clients for which the channel group members could not be obtained. + * @return PVR_ERROR_NO_ERROR if the channel group members were fetched successfully, last error otherwise. + */ + PVR_ERROR GetChannelGroupMembers( + const std::vector<std::shared_ptr<CPVRClient>>& clients, + CPVRChannelGroup* group, + std::vector<std::shared_ptr<CPVRChannelGroupMember>>& groupMembers, + std::vector<int>& failedClients); + + /*! + * @brief Get a list of clients providing a channel scan dialog. + * @return All clients supporting channel scan. + */ + std::vector<std::shared_ptr<CPVRClient>> GetClientsSupportingChannelScan() const; + + /*! + * @brief Get a list of clients providing a channel settings dialog. + * @return All clients supporting channel settings. + */ + std::vector<std::shared_ptr<CPVRClient>> GetClientsSupportingChannelSettings(bool bRadio) const; + + /*! + * @brief Get whether or not any client supports recording size. + * @return True if any client supports recording size. + */ + bool AnyClientSupportingRecordingsSize() const; + + /*! + * @brief Get whether or not any client supports EPG. + * @return True if any client supports EPG. + */ + bool AnyClientSupportingEPG() const; + + /*! + * @brief Get whether or not any client supports recordings. + * @return True if any client supports recordings. + */ + bool AnyClientSupportingRecordings() const; + //@} + + /*! + * @brief Get whether or not any client supports recordings delete. + * @return True if any client supports recordings delete. + */ + bool AnyClientSupportingRecordingsDelete() const; + //@} + + /*! @name Power management methods */ + //@{ + + /*! + * @brief Propagate "system sleep" event to clients + */ + void OnSystemSleep(); + + /*! + * @brief Propagate "system wakeup" event to clients + */ + void OnSystemWake(); + + /*! + * @brief Propagate "power saving activated" event to clients + */ + void OnPowerSavingActivated(); + + /*! + * @brief Propagate "power saving deactivated" event to clients + */ + void OnPowerSavingDeactivated(); + + //@} + + /*! + * @brief Notify a change of an addon connection state. + * @param client The changed client. + * @param strConnectionString A human-readable string providing additional information. + * @param newState The new connection state. + * @param strMessage A human readable string replacing default state message. + */ + void ConnectionStateChange(CPVRClient* client, + const std::string& strConnectionString, + PVR_CONNECTION_STATE newState, + const std::string& strMessage); + + private: + /*! + * @brief Get the known instance ids for a given addon id. + * @param addonID The addon id. + * @return The list of known instance ids. + */ + std::vector<ADDON::AddonInstanceId> GetKnownInstanceIds(const std::string& addonID) const; + + bool GetAddonsWithStatus( + const std::string& changedAddonId, + std::vector<std::pair<std::shared_ptr<ADDON::CAddonInfo>, bool>>& addonsWithStatus) const; + + std::vector<std::pair<ADDON::AddonInstanceId, bool>> GetInstanceIdsWithStatus( + const std::shared_ptr<ADDON::CAddonInfo>& addon, bool addonIsEnabled) const; + + /*! + * @brief Get the client instance for a given client id. + * @param clientId The id of the client to get. + * @return The client if found, nullptr otherwise. + */ + std::shared_ptr<CPVRClient> GetClient(int clientId) const; + + /*! + * @brief Check whether a client is known. + * @param iClientId The id of the client to check. + * @return True if this client is known, false otherwise. + */ + bool IsKnownClient(int iClientId) const; + + /*! + * @brief Get all created clients and clients not (yet) ready to use. + * @param clientsReady Store the created clients in this map. + * @param clientsNotReady Store the the ids of the not (yet) ready clients in this list. + * @return PVR_ERROR_NO_ERROR in case all clients are ready, PVR_ERROR_SERVER_ERROR otherwise. + */ + PVR_ERROR GetCallableClients(CPVRClientMap& clientsReady, + std::vector<int>& clientsNotReady) const; + + typedef std::function<PVR_ERROR(const std::shared_ptr<CPVRClient>&)> PVRClientFunction; + + /*! + * @brief Wraps calls to the given clients in order to do common pre and post function invocation actions. + * @param strFunctionName The function name, for logging purposes. + * @param clients The clients to wrap. + * @param function The function to wrap. It has to have return type PVR_ERROR and must take a const reference to a std::shared_ptr<CPVRClient> as parameter. + * @param failedClients Contains a list of the ids of clients for that the call failed, if any. + * @return PVR_ERROR_NO_ERROR on success, any other PVR_ERROR_* value otherwise. + */ + PVR_ERROR ForClients(const char* strFunctionName, + const std::vector<std::shared_ptr<CPVRClient>>& clients, + const PVRClientFunction& function, + std::vector<int>& failedClients) const; + + /*! + * @brief Wraps calls to all created clients in order to do common pre and post function invocation actions. + * @param strFunctionName The function name, for logging purposes. + * @param function The function to wrap. It has to have return type PVR_ERROR and must take a const reference to a std::shared_ptr<CPVRClient> as parameter. + * @return PVR_ERROR_NO_ERROR on success, any other PVR_ERROR_* value otherwise. + */ + PVR_ERROR ForCreatedClients(const char* strFunctionName, + const PVRClientFunction& function) const; + + /*! + * @brief Wraps calls to all created clients in order to do common pre and post function invocation actions. + * @param strFunctionName The function name, for logging purposes. + * @param function The function to wrap. It has to have return type PVR_ERROR and must take a const reference to a std::shared_ptr<CPVRClient> as parameter. + * @param failedClients Contains a list of the ids of clients for that the call failed, if any. + * @return PVR_ERROR_NO_ERROR on success, any other PVR_ERROR_* value otherwise. + */ + PVR_ERROR ForCreatedClients(const char* strFunctionName, + const PVRClientFunction& function, + std::vector<int>& failedClients) const; + + mutable CCriticalSection m_critSection; + CPVRClientMap m_clientMap; + }; +} |