From f4aca04da6ebd004e885bc5084a75ea9f5f52972 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 8 Jun 2024 23:56:58 +0200 Subject: [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. --- .../resource.language.en_gb/resources/strings.po | 1 + xbmc/pvr/channels/CMakeLists.txt | 2 + xbmc/pvr/channels/PVRChannelGroup.h | 1 + xbmc/pvr/channels/PVRChannelGroupFactory.cpp | 17 ++- xbmc/pvr/channels/PVRChannelGroupMergedByName.cpp | 164 +++++++++++++++++++++ xbmc/pvr/channels/PVRChannelGroupMergedByName.h | 103 +++++++++++++ 6 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 xbmc/pvr/channels/PVRChannelGroupMergedByName.cpp create mode 100644 xbmc/pvr/channels/PVRChannelGroupMergedByName.h 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 CPVRChannelGroupFactory::CreateGroup( return std::make_shared(groupPath, allChannels); case PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS_SINGLE_CLIENT: return std::make_shared(groupPath, allChannels); + case PVR_GROUP_TYPE_SYSTEM_MERGED_BY_NAME: + return std::make_shared(groupPath, allChannels); case PVR_GROUP_TYPE_CLIENT: return std::make_shared(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> CPVRChannelGroupFactory::CreateMi const std::shared_ptr& allChannelsGroup, const std::vector>& allChannelGroups) { - return CPVRChannelGroupAllChannelsSingleClient::CreateMissingGroups(allChannelsGroup, - allChannelGroups); + std::vector> newGroups{ + CPVRChannelGroupAllChannelsSingleClient::CreateMissingGroups(allChannelsGroup, + allChannelGroups)}; + + std::vector> 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 +#include + +using namespace PVR; + +bool CPVRChannelGroupMergedByName::ShouldBeIgnored( + const std::vector>& allChannelGroups) const +{ + std::unique_lock 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 GetGroupNames(const std::vector>& groups) +{ + std::unordered_map 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 names; + for (const auto& name : knownNamesCountMap) + { + if (name.second > 1) + names.emplace_back(name.first); + } + return names; +} +} // namespace + +std::vector> CPVRChannelGroupMergedByName::CreateMissingGroups( + const std::shared_ptr& allChannelsGroup, + const std::vector>& allChannelGroups) +{ + std::vector> addedGroups; + + const std::vector 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 mergedByNameGroup{ + std::make_shared(path, allChannelsGroup)}; + mergedByNameGroup->SetClientGroupName(name); + addedGroups.emplace_back(mergedByNameGroup); + } + + return addedGroups; +} + +bool CPVRChannelGroupMergedByName::UpdateGroupMembers( + const std::shared_ptr& allChannelsGroup, + const std::vector>& allChannelGroups) +{ + std::vector> 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( + 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& 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>& 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>& 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> CreateMissingGroups( + const std::shared_ptr& allChannelsGroup, + const std::vector>& 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& allChannelsGroup, + const std::vector>& allChannelGroups) override; +}; +} // namespace PVR -- cgit v1.2.3