From abae60b0c3f593d34af4905e3b66ed450a772a58 Mon Sep 17 00:00:00 2001
From: ksooo <3226626+ksooo@users.noreply.github.com>
Date: Mon, 12 Jun 2023 17:00:26 +0200
Subject: [PVR] Dynamic timer types: Update timer types from client whenever a
 timers update for the client is requested.

---
 xbmc/pvr/addons/PVRClient.cpp    | 208 ++++++++++++++++++++++-----------------
 xbmc/pvr/addons/PVRClient.h      |  11 ++-
 xbmc/pvr/addons/PVRClients.cpp   |  34 +++++--
 xbmc/pvr/addons/PVRClients.h     |  14 ++-
 xbmc/pvr/timers/PVRTimerType.cpp |  26 ++++-
 xbmc/pvr/timers/PVRTimerType.h   |   6 ++
 xbmc/pvr/timers/PVRTimers.cpp    |   5 +-
 7 files changed, 192 insertions(+), 112 deletions(-)

diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp
index 642fdbbcd4..9c553d7aa8 100644
--- a/xbmc/pvr/addons/PVRClient.cpp
+++ b/xbmc/pvr/addons/PVRClient.cpp
@@ -305,7 +305,6 @@ bool CPVRClient::GetAddonProperties()
     return false;
 
   PVR_ADDON_CAPABILITIES addonCapabilities = {};
-  std::vector<std::shared_ptr<CPVRTimerType>> timerTypes;
 
   /* get the capabilities */
   PVR_ERROR retVal = DoAddonCall(
@@ -318,96 +317,8 @@ bool CPVRClient::GetAddonProperties()
   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 {}.", GetID());
-              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;
 }
@@ -1078,11 +989,124 @@ PVR_ERROR CPVRClient::UpdateTimer(const CPVRTimerInfoTag& timer)
       m_clientCapabilities.SupportsTimers());
 }
 
-PVR_ERROR CPVRClient::GetTimerTypes(std::vector<std::shared_ptr<CPVRTimerType>>& results) const
+const std::vector<std::shared_ptr<CPVRTimerType>>& CPVRClient::GetTimerTypes() const
 {
   std::unique_lock<CCriticalSection> lock(m_critSection);
-  results = m_timertypes;
-  return PVR_ERROR_NO_ERROR;
+  return m_timertypes;
+}
+
+PVR_ERROR CPVRClient::UpdateTimerTypes()
+{
+  std::vector<std::shared_ptr<CPVRTimerType>> timerTypes;
+
+  PVR_ERROR retVal = DoAddonCall(
+      __func__,
+      [this, &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 (m_clientCapabilities.SupportsEPG())
+          {
+            // 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 {}.", GetID());
+              continue;
+            }
+            timerTypes.emplace_back(std::make_shared<CPVRTimerType>(types_array[i], m_iClientId));
+          }
+        }
+        return retval;
+      },
+      m_clientCapabilities.SupportsTimers(), false);
+
+  if (retVal == PVR_ERROR_NO_ERROR)
+  {
+    std::vector<std::shared_ptr<CPVRTimerType>> newTimerTypes;
+    newTimerTypes.reserve(timerTypes.size());
+
+    std::unique_lock<CCriticalSection> lock(m_critSection);
+
+    for (const auto& type : timerTypes)
+    {
+      const auto it = std::find_if(m_timertypes.cbegin(), m_timertypes.cend(),
+                                   [&type](const std::shared_ptr<CPVRTimerType>& entry) {
+                                     return entry->GetTypeId() == type->GetTypeId();
+                                   });
+      if (it == m_timertypes.cend())
+      {
+        newTimerTypes.emplace_back(type);
+      }
+      else
+      {
+        (*it)->Update(*type);
+        newTimerTypes.emplace_back(*it);
+      }
+    }
+
+    m_timertypes = newTimerTypes;
+  }
+  return retVal;
 }
 
 PVR_ERROR CPVRClient::GetStreamReadChunkSize(int& iChunkSize)
diff --git a/xbmc/pvr/addons/PVRClient.h b/xbmc/pvr/addons/PVRClient.h
index b088a579ec..c045431d25 100644
--- a/xbmc/pvr/addons/PVRClient.h
+++ b/xbmc/pvr/addons/PVRClient.h
@@ -489,11 +489,16 @@ public:
   PVR_ERROR UpdateTimer(const CPVRTimerInfoTag& timer);
 
   /*!
-   * @brief Get all timer types supported by the backend.
-   * @param results The container to store the result in.
+   * @brief Update all timer types supported by the backend.
    * @return PVR_ERROR_NO_ERROR if the list has been fetched successfully.
    */
-  PVR_ERROR GetTimerTypes(std::vector<std::shared_ptr<CPVRTimerType>>& results) const;
+  PVR_ERROR UpdateTimerTypes();
+
+  /*!
+   * @brief Get the timer types supported by the backend, without updating them from the backend.
+   * @return the types.
+   */
+  const std::vector<std::shared_ptr<CPVRTimerType>>& GetTimerTypes() const;
 
   //@}
   /** @name PVR live stream methods */
diff --git a/xbmc/pvr/addons/PVRClients.cpp b/xbmc/pvr/addons/PVRClients.cpp
index bceda373a5..db4aee11d5 100644
--- a/xbmc/pvr/addons/PVRClients.cpp
+++ b/xbmc/pvr/addons/PVRClients.cpp
@@ -600,15 +600,31 @@ bool CPVRClients::GetTimers(const std::vector<std::shared_ptr<CPVRClient>>& clie
                     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::UpdateTimerTypes(const std::vector<std::shared_ptr<CPVRClient>>& clients,
+                                        std::vector<int>& failedClients)
+{
+  return ForClients(
+      __FUNCTION__, clients,
+      [](const std::shared_ptr<CPVRClient>& client) { return client->UpdateTimerTypes(); },
+      failedClients);
+}
+
+const std::vector<std::shared_ptr<CPVRTimerType>> CPVRClients::GetTimerTypes() const
+{
+  std::vector<std::shared_ptr<CPVRTimerType>> types;
+
+  std::unique_lock<CCriticalSection> lock(m_critSection);
+  for (const auto& entry : m_clientMap)
+  {
+    const auto& client = entry.second;
+    if (client->ReadyToUse() && !client->IgnoreClient())
+    {
+      const auto& clientTypes = client->GetTimerTypes();
+      types.insert(types.end(), clientTypes.begin(), clientTypes.end());
+    }
+  }
+
+  return types;
 }
 
 PVR_ERROR CPVRClients::GetRecordings(const std::vector<std::shared_ptr<CPVRClient>>& clients,
diff --git a/xbmc/pvr/addons/PVRClients.h b/xbmc/pvr/addons/PVRClients.h
index 32248fa322..23ee19eade 100644
--- a/xbmc/pvr/addons/PVRClients.h
+++ b/xbmc/pvr/addons/PVRClients.h
@@ -212,11 +212,19 @@ struct SBackend
                    std::vector<int>& failedClients);
 
     /*!
-     * @brief Get all supported timer types.
-     * @param results The container to store the result in.
+     * @brief Update all timer types from the given clients
+     * @param clients The clients to fetch data from. Leave empty to fetch data from all created clients.
+     * @param failedClients in case of errors will contain the ids of the clients for which the timer types could not be obtained.
      * @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;
+    PVR_ERROR UpdateTimerTypes(const std::vector<std::shared_ptr<CPVRClient>>& clients,
+                               std::vector<int>& failedClients);
+
+    /*!
+     * @brief Get all timer types supported by the backends, without updating them from the backends.
+     * @return the types.
+     */
+    const std::vector<std::shared_ptr<CPVRTimerType>> GetTimerTypes() const;
 
     //@}
 
diff --git a/xbmc/pvr/timers/PVRTimerType.cpp b/xbmc/pvr/timers/PVRTimerType.cpp
index db20fa10af..fd4cc0eba6 100644
--- a/xbmc/pvr/timers/PVRTimerType.cpp
+++ b/xbmc/pvr/timers/PVRTimerType.cpp
@@ -27,8 +27,8 @@ using namespace PVR;
 
 const std::vector<std::shared_ptr<CPVRTimerType>> CPVRTimerType::GetAllTypes()
 {
-  std::vector<std::shared_ptr<CPVRTimerType>> allTypes;
-  CServiceBroker::GetPVRManager().Clients()->GetTimerTypes(allTypes);
+  std::vector<std::shared_ptr<CPVRTimerType>> allTypes =
+      CServiceBroker::GetPVRManager().Clients()->GetTimerTypes();
 
   // Add local reminder timer types. Local reminders are always available.
   int iTypeId = PVR_TIMER_TYPE_NONE;
@@ -107,8 +107,8 @@ const std::shared_ptr<CPVRTimerType> CPVRTimerType::GetFirstAvailableType(const
 {
   if (client)
   {
-    std::vector<std::shared_ptr<CPVRTimerType>> types;
-    if (client->GetTimerTypes(types) == PVR_ERROR_NO_ERROR && !types.empty())
+    const std::vector<std::shared_ptr<CPVRTimerType>>& types = client->GetTimerTypes();
+    if (!types.empty())
     {
       return *(types.begin());
     }
@@ -214,6 +214,24 @@ bool CPVRTimerType::operator !=(const CPVRTimerType& right) const
   return !(*this == right);
 }
 
+void CPVRTimerType::Update(const CPVRTimerType& type)
+{
+  m_iClientId = type.m_iClientId;
+  m_iTypeId = type.m_iTypeId;
+  m_iAttributes = type.m_iAttributes;
+  m_strDescription = type.m_strDescription;
+  m_priorityValues = type.m_priorityValues;
+  m_iPriorityDefault = type.m_iPriorityDefault;
+  m_lifetimeValues = type.m_lifetimeValues;
+  m_iLifetimeDefault = type.m_iLifetimeDefault;
+  m_maxRecordingsValues = type.m_maxRecordingsValues;
+  m_iMaxRecordingsDefault = type.m_iMaxRecordingsDefault;
+  m_preventDupEpisodesValues = type.m_preventDupEpisodesValues;
+  m_iPreventDupEpisodesDefault = type.m_iPreventDupEpisodesDefault;
+  m_recordingGroupValues = type.m_recordingGroupValues;
+  m_iRecordingGroupDefault = type.m_iRecordingGroupDefault;
+}
+
 void CPVRTimerType::InitDescription()
 {
   // if no description was given, compile it
diff --git a/xbmc/pvr/timers/PVRTimerType.h b/xbmc/pvr/timers/PVRTimerType.h
index 8ee9e225d6..34405a7e1b 100644
--- a/xbmc/pvr/timers/PVRTimerType.h
+++ b/xbmc/pvr/timers/PVRTimerType.h
@@ -74,6 +74,12 @@ namespace PVR
     bool operator ==(const CPVRTimerType& right) const;
     bool operator !=(const CPVRTimerType& right) const;
 
+    /*!
+     * @brief Update the data of this instance with the data given by another type instance.
+     * @param type The instance containing the updated data.
+     */
+    void Update(const CPVRTimerType& type);
+
     /*!
      * @brief Get the PVR client id for this type.
      * @return The PVR client id.
diff --git a/xbmc/pvr/timers/PVRTimers.cpp b/xbmc/pvr/timers/PVRTimers.cpp
index 760bb192f7..9537a52016 100644
--- a/xbmc/pvr/timers/PVRTimers.cpp
+++ b/xbmc/pvr/timers/PVRTimers.cpp
@@ -157,9 +157,12 @@ bool CPVRTimers::UpdateFromClients(const std::vector<std::shared_ptr<CPVRClient>
     m_bIsUpdating = true;
   }
 
+  CLog::LogFC(LOGDEBUG, LOGPVR, "Updating timer types");
+  std::vector<int> failedClients;
+  CServiceBroker::GetPVRManager().Clients()->UpdateTimerTypes(clients, failedClients);
+
   CLog::LogFC(LOGDEBUG, LOGPVR, "Updating timers");
   CPVRTimersContainer newTimerList;
-  std::vector<int> failedClients;
   CServiceBroker::GetPVRManager().Clients()->GetTimers(clients, &newTimerList, failedClients);
   return UpdateEntries(newTimerList, failedClients);
 }
-- 
cgit v1.2.3