aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorksooo <3226626+ksooo@users.noreply.github.com>2024-06-08 23:56:58 +0200
committerksooo <3226626+ksooo@users.noreply.github.com>2024-07-13 12:05:57 +0200
commitf4aca04da6ebd004e885bc5084a75ea9f5f52972 (patch)
tree21234e4bcd3c5994d1b5b842aeafcb9caacd1c08
parent07f51558d8a03d997d1f6df46938c97094f1393f (diff)
[PVR] Groups: Automatically create a channel group containing all channels from groups with equal group name.
Example: A group with name 'A' is provided by two different client addons, create a group 'A [All channels]' containing all channels from both 'A' groups.
-rw-r--r--addons/resource.language.en_gb/resources/strings.po1
-rw-r--r--xbmc/pvr/channels/CMakeLists.txt2
-rw-r--r--xbmc/pvr/channels/PVRChannelGroup.h1
-rw-r--r--xbmc/pvr/channels/PVRChannelGroupFactory.cpp17
-rw-r--r--xbmc/pvr/channels/PVRChannelGroupMergedByName.cpp164
-rw-r--r--xbmc/pvr/channels/PVRChannelGroupMergedByName.h103
6 files changed, 286 insertions, 2 deletions
diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po
index ab0a84adac..bc5d2f735b 100644
--- a/addons/resource.language.en_gb/resources/strings.po
+++ b/addons/resource.language.en_gb/resources/strings.po
@@ -3998,6 +3998,7 @@ msgstr ""
#. Label postfix for channel groups containing content from all channels (e.g. "PVR Client 1 [All channels]")
#: xbmc/pvr/channels/PVRChannelGroupAllChannelsSingleClient.cpp
+#: xbmc/pvr/channels/PVRChannelGroupMergedByName.cpp
msgctxt "#859"
msgid "{0:s} [All channels]"
msgstr ""
diff --git a/xbmc/pvr/channels/CMakeLists.txt b/xbmc/pvr/channels/CMakeLists.txt
index 7094e029e6..54206d27a0 100644
--- a/xbmc/pvr/channels/CMakeLists.txt
+++ b/xbmc/pvr/channels/CMakeLists.txt
@@ -5,6 +5,7 @@ set(SOURCES PVRChannel.cpp
PVRChannelGroupAllChannelsSingleClient.cpp
PVRChannelGroupFromClient.cpp
PVRChannelGroupMember.cpp
+ PVRChannelGroupMergedByName.cpp
PVRChannelGroupSettings.cpp
PVRChannelGroups.cpp
PVRChannelGroupsContainer.cpp
@@ -20,6 +21,7 @@ set(HEADERS PVRChannel.h
PVRChannelGroupFromClient.h
PVRChannelGroupFromUser.h
PVRChannelGroupMember.h
+ PVRChannelGroupMergedByName.h
PVRChannelGroupSettings.h
PVRChannelGroups.h
PVRChannelGroupsContainer.h
diff --git a/xbmc/pvr/channels/PVRChannelGroup.h b/xbmc/pvr/channels/PVRChannelGroup.h
index af521abf98..b4cc6c7a3a 100644
--- a/xbmc/pvr/channels/PVRChannelGroup.h
+++ b/xbmc/pvr/channels/PVRChannelGroup.h
@@ -28,6 +28,7 @@ static constexpr int PVR_GROUP_TYPE_CLIENT = 0;
static constexpr int PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS_ALL_CLIENTS = 1;
static constexpr int PVR_GROUP_TYPE_USER = 2;
static constexpr int PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS_SINGLE_CLIENT = 3;
+static constexpr int PVR_GROUP_TYPE_SYSTEM_MERGED_BY_NAME = 4;
static constexpr int PVR_GROUP_CLIENT_ID_UNKNOWN = -2;
static constexpr int PVR_GROUP_CLIENT_ID_LOCAL = -1;
diff --git a/xbmc/pvr/channels/PVRChannelGroupFactory.cpp b/xbmc/pvr/channels/PVRChannelGroupFactory.cpp
index 0eec8b5e08..86f4f00a70 100644
--- a/xbmc/pvr/channels/PVRChannelGroupFactory.cpp
+++ b/xbmc/pvr/channels/PVRChannelGroupFactory.cpp
@@ -12,6 +12,7 @@
#include "pvr/channels/PVRChannelGroupAllChannelsSingleClient.h"
#include "pvr/channels/PVRChannelGroupFromClient.h"
#include "pvr/channels/PVRChannelGroupFromUser.h"
+#include "pvr/channels/PVRChannelGroupMergedByName.h"
#include "pvr/channels/PVRChannelsPath.h"
#include "utils/log.h"
@@ -52,6 +53,8 @@ std::shared_ptr<CPVRChannelGroup> CPVRChannelGroupFactory::CreateGroup(
return std::make_shared<CPVRChannelGroupFromUser>(groupPath, allChannels);
case PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS_SINGLE_CLIENT:
return std::make_shared<CPVRChannelGroupAllChannelsSingleClient>(groupPath, allChannels);
+ case PVR_GROUP_TYPE_SYSTEM_MERGED_BY_NAME:
+ return std::make_shared<CPVRChannelGroupMergedByName>(groupPath, allChannels);
case PVR_GROUP_TYPE_CLIENT:
return std::make_shared<CPVRChannelGroupFromClient>(groupPath, allChannels);
default:
@@ -71,6 +74,8 @@ int CPVRChannelGroupFactory::GetGroupTypePriority(
return 0; // highest
case PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS_SINGLE_CLIENT:
return 1;
+ case PVR_GROUP_TYPE_SYSTEM_MERGED_BY_NAME:
+ return 2;
// User groups, created and managed by the user
case PVR_GROUP_TYPE_USER:
@@ -91,6 +96,14 @@ std::vector<std::shared_ptr<CPVRChannelGroup>> CPVRChannelGroupFactory::CreateMi
const std::shared_ptr<CPVRChannelGroup>& allChannelsGroup,
const std::vector<std::shared_ptr<CPVRChannelGroup>>& allChannelGroups)
{
- return CPVRChannelGroupAllChannelsSingleClient::CreateMissingGroups(allChannelsGroup,
- allChannelGroups);
+ std::vector<std::shared_ptr<CPVRChannelGroup>> newGroups{
+ CPVRChannelGroupAllChannelsSingleClient::CreateMissingGroups(allChannelsGroup,
+ allChannelGroups)};
+
+ std::vector<std::shared_ptr<CPVRChannelGroup>> newGroupsTmp{
+ CPVRChannelGroupMergedByName::CreateMissingGroups(allChannelsGroup, allChannelGroups)};
+ if (!newGroupsTmp.empty())
+ newGroups.insert(newGroups.end(), newGroupsTmp.cbegin(), newGroupsTmp.cend());
+
+ return newGroups;
}
diff --git a/xbmc/pvr/channels/PVRChannelGroupMergedByName.cpp b/xbmc/pvr/channels/PVRChannelGroupMergedByName.cpp
new file mode 100644
index 0000000000..067cd36b72
--- /dev/null
+++ b/xbmc/pvr/channels/PVRChannelGroupMergedByName.cpp
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2024 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 "PVRChannelGroupMergedByName.h"
+
+#include "guilib/LocalizeStrings.h"
+#include "pvr/channels/PVRChannelGroupMember.h"
+#include "utils/StringUtils.h"
+
+#include <unordered_map>
+#include <utility>
+
+using namespace PVR;
+
+bool CPVRChannelGroupMergedByName::ShouldBeIgnored(
+ const std::vector<std::shared_ptr<CPVRChannelGroup>>& allChannelGroups) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ // Ignore the group if it is empty or only members from one group are present.
+
+ if (m_members.empty())
+ return true;
+
+ const std::string mergedGroupName{!ClientGroupName().empty() ? ClientGroupName() : GroupName()};
+ static constexpr int NO_GROUP_FOUND{-1};
+ int matchingGroup{NO_GROUP_FOUND};
+
+ for (const auto& member : m_members)
+ {
+ for (const auto& group : allChannelGroups)
+ {
+ if (group->GetOrigin() != Origin::USER && group->GetOrigin() != Origin::CLIENT)
+ continue;
+
+ if (group->GroupName() != mergedGroupName && group->ClientGroupName() != mergedGroupName)
+ continue;
+
+ if (group->IsGroupMember(member.second))
+ {
+ if (matchingGroup != NO_GROUP_FOUND && matchingGroup != group->GroupID())
+ {
+ // Found a second group containing a member of this group. This group must not be ignored.
+ return false;
+ }
+ // First match or no new group. Continue with next group.
+ matchingGroup = group->GroupID();
+ }
+ }
+ }
+ return true;
+}
+
+namespace
+{
+std::vector<std::string> GetGroupNames(const std::vector<std::shared_ptr<CPVRChannelGroup>>& groups)
+{
+ std::unordered_map<std::string, unsigned int> knownNamesCountMap;
+
+ // Structure group data for easy further processing.
+ for (const auto& group : groups)
+ {
+ const std::string groupName{!group->ClientGroupName().empty() ? group->ClientGroupName()
+ : group->GroupName()};
+ switch (group->GetOrigin())
+ {
+ case CPVRChannelGroup::Origin::SYSTEM:
+ {
+ // Ignore system-created groups, except merged by name groups
+ if (group->GroupType() == PVR_GROUP_TYPE_SYSTEM_MERGED_BY_NAME)
+ {
+ const auto it = knownNamesCountMap.find(groupName);
+ if (it == knownNamesCountMap.end())
+ knownNamesCountMap.insert({groupName, 0}); // remember we found a merged group
+ else
+ (*it).second = 0; // reset groups counter. we do not need a new merged group
+ }
+ break;
+ }
+
+ case CPVRChannelGroup::Origin::USER:
+ case CPVRChannelGroup::Origin::CLIENT:
+ {
+ const auto it = knownNamesCountMap.find(groupName);
+ if (it == knownNamesCountMap.end())
+ knownNamesCountMap.insert({groupName, 1}); // first occurance
+ else if ((*it).second > 0)
+ (*it).second++; // second+ occurance
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ std::vector<std::string> names;
+ for (const auto& name : knownNamesCountMap)
+ {
+ if (name.second > 1)
+ names.emplace_back(name.first);
+ }
+ return names;
+}
+} // namespace
+
+std::vector<std::shared_ptr<CPVRChannelGroup>> CPVRChannelGroupMergedByName::CreateMissingGroups(
+ const std::shared_ptr<CPVRChannelGroup>& allChannelsGroup,
+ const std::vector<std::shared_ptr<CPVRChannelGroup>>& allChannelGroups)
+{
+ std::vector<std::shared_ptr<CPVRChannelGroup>> addedGroups;
+
+ const std::vector<std::string> names{GetGroupNames(allChannelGroups)};
+ for (const auto& name : names)
+ {
+ const std::string groupName{StringUtils::Format(g_localizeStrings.Get(859), name)};
+ const CPVRChannelsPath path{allChannelsGroup->IsRadio(), groupName, PVR_GROUP_CLIENT_ID_LOCAL};
+ const std::shared_ptr<CPVRChannelGroup> mergedByNameGroup{
+ std::make_shared<CPVRChannelGroupMergedByName>(path, allChannelsGroup)};
+ mergedByNameGroup->SetClientGroupName(name);
+ addedGroups.emplace_back(mergedByNameGroup);
+ }
+
+ return addedGroups;
+}
+
+bool CPVRChannelGroupMergedByName::UpdateGroupMembers(
+ const std::shared_ptr<CPVRChannelGroup>& allChannelsGroup,
+ const std::vector<std::shared_ptr<CPVRChannelGroup>>& allChannelGroups)
+{
+ std::vector<std::shared_ptr<CPVRChannelGroupMember>> groupMembers;
+
+ // Collect and populate matching members.
+ for (const auto& group : allChannelGroups)
+ {
+ const std::string groupName{!group->ClientGroupName().empty() ? group->ClientGroupName()
+ : group->GroupName()};
+ if (groupName != ClientGroupName())
+ continue;
+
+ switch (group->GetOrigin())
+ {
+ case CPVRChannelGroup::Origin::USER:
+ case CPVRChannelGroup::Origin::CLIENT:
+ {
+ const auto members{group->GetMembers()};
+ for (const auto& member : members)
+ {
+ groupMembers.emplace_back(std::make_shared<CPVRChannelGroupMember>(
+ GroupID(), GroupName(), GetClientID(), member->Channel()));
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ return UpdateGroupEntries(groupMembers);
+}
diff --git a/xbmc/pvr/channels/PVRChannelGroupMergedByName.h b/xbmc/pvr/channels/PVRChannelGroupMergedByName.h
new file mode 100644
index 0000000000..b7f7fbdc58
--- /dev/null
+++ b/xbmc/pvr/channels/PVRChannelGroupMergedByName.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 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 "pvr/channels/PVRChannelGroup.h"
+
+namespace PVR
+{
+
+class CPVRChannelGroupMergedByName : public CPVRChannelGroup
+{
+public:
+ /*!
+ * @brief Create a new channel group instance.
+ * @param path The channel group path.
+ * @param allChannelsGroup The channel group containing all TV or radio channels.
+ */
+ CPVRChannelGroupMergedByName(const CPVRChannelsPath& path,
+ const std::shared_ptr<const CPVRChannelGroup>& allChannelsGroup)
+ : CPVRChannelGroup(path, allChannelsGroup)
+ {
+ }
+
+ /*!
+ * @brief Get the group's origin.
+ * @return The origin.
+ */
+ Origin GetOrigin() const override { return Origin::SYSTEM; }
+
+ /*!
+ * @brief Return the type of this group.
+ */
+ int GroupType() const override { return PVR_GROUP_TYPE_SYSTEM_MERGED_BY_NAME; }
+
+ /*!
+ * @brief Check whether this group could be deleted by the user.
+ * @return True if the group could be deleted, false otherwise.
+ */
+ bool SupportsDelete() const override { return false; }
+
+ /*!
+ * @brief Check whether members could be added to this group by the user.
+ * @return True if members could be added, false otherwise.
+ */
+ bool SupportsMemberAdd() const override { return false; }
+
+ /*!
+ * @brief Check whether members could be removed from this group by the user.
+ * @return True if members could be removed, false otherwise.
+ */
+ bool SupportsMemberRemove() const override { return false; }
+
+ /*!
+ * @brief Check whether this group is owner of the channel instances it contains.
+ * @return True if owner, false otherwise.
+ */
+ bool IsChannelsOwner() const override { return false; }
+
+ /*!
+ * @brief Update data with channel group members from the given clients, sync with local data.
+ * @param clients The clients to fetch data from. Leave empty to fetch data from all created clients.
+ * @return True on success, false otherwise.
+ */
+ bool UpdateFromClients(const std::vector<std::shared_ptr<CPVRClient>>& clients) override
+ {
+ return true; // Nothing to update from any client for locally managed groups.
+ }
+
+ /*!
+ * @brief Check whether this group should be ignored, e.g. not presented to the user and API.
+ * @param allChannelGroups All available channel groups.
+ * @return True if to be ignored, false otherwise.
+ */
+ bool ShouldBeIgnored(
+ const std::vector<std::shared_ptr<CPVRChannelGroup>>& allChannelGroups) const override;
+
+ /*!
+ * @brief Create any missing channel groups (e.g. after an update of groups/members/clients).
+ * @param allChannelsGroup The all channels group.
+ * @param allChannelGroups All channel groups.
+ * @return The newly created groups, if any.
+ */
+ static std::vector<std::shared_ptr<CPVRChannelGroup>> CreateMissingGroups(
+ const std::shared_ptr<CPVRChannelGroup>& allChannelsGroup,
+ const std::vector<std::shared_ptr<CPVRChannelGroup>>& allChannelGroups);
+
+ /*!
+ * @brief Update all group members.
+ * @param allChannelsGroup The all channels group.
+ * @param allChannelGroups All available channel groups.
+ * @return True on success, false otherwise.
+ */
+ bool UpdateGroupMembers(
+ const std::shared_ptr<CPVRChannelGroup>& allChannelsGroup,
+ const std::vector<std::shared_ptr<CPVRChannelGroup>>& allChannelGroups) override;
+};
+} // namespace PVR