summaryrefslogtreecommitdiffstats
path: root/xbmc/pvr/addons
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
commitc04dcc2e7d834218ef2d4194331e383402495ae1 (patch)
tree7333e38d10d75386e60f336b80c2443c1166031d /xbmc/pvr/addons
parentInitial commit. (diff)
downloadkodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz
kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/pvr/addons')
-rw-r--r--xbmc/pvr/addons/CMakeLists.txt13
-rw-r--r--xbmc/pvr/addons/PVRClient.cpp2023
-rw-r--r--xbmc/pvr/addons/PVRClient.h1062
-rw-r--r--xbmc/pvr/addons/PVRClientCapabilities.cpp85
-rw-r--r--xbmc/pvr/addons/PVRClientCapabilities.h277
-rw-r--r--xbmc/pvr/addons/PVRClientMenuHooks.cpp185
-rw-r--r--xbmc/pvr/addons/PVRClientMenuHooks.h73
-rw-r--r--xbmc/pvr/addons/PVRClientUID.cpp33
-rw-r--r--xbmc/pvr/addons/PVRClientUID.h42
-rw-r--r--xbmc/pvr/addons/PVRClients.cpp977
-rw-r--r--xbmc/pvr/addons/PVRClients.h485
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__, [&times](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;
+ };
+}