summaryrefslogtreecommitdiffstats
path: root/xbmc/pvr/channels/PVRChannelGroups.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/pvr/channels/PVRChannelGroups.cpp')
-rw-r--r--xbmc/pvr/channels/PVRChannelGroups.cpp621
1 files changed, 621 insertions, 0 deletions
diff --git a/xbmc/pvr/channels/PVRChannelGroups.cpp b/xbmc/pvr/channels/PVRChannelGroups.cpp
new file mode 100644
index 0000000..77cc2b5
--- /dev/null
+++ b/xbmc/pvr/channels/PVRChannelGroups.cpp
@@ -0,0 +1,621 @@
+/*
+ * 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 "PVRChannelGroups.h"
+
+#include "ServiceBroker.h"
+#include "pvr/PVRCachedImages.h"
+#include "pvr/PVRDatabase.h"
+#include "pvr/PVRManager.h"
+#include "pvr/addons/PVRClient.h"
+#include "pvr/addons/PVRClientUID.h"
+#include "pvr/addons/PVRClients.h"
+#include "pvr/channels/PVRChannel.h"
+#include "pvr/channels/PVRChannelGroupInternal.h"
+#include "pvr/channels/PVRChannelGroupMember.h"
+#include "pvr/channels/PVRChannelsPath.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <chrono>
+#include <iterator>
+#include <memory>
+#include <mutex>
+#include <numeric>
+#include <string>
+#include <vector>
+
+using namespace PVR;
+
+CPVRChannelGroups::CPVRChannelGroups(bool bRadio) :
+ m_bRadio(bRadio)
+{
+}
+
+CPVRChannelGroups::~CPVRChannelGroups()
+{
+ Unload();
+}
+
+void CPVRChannelGroups::Unload()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (const auto& group : m_groups)
+ group->Unload();
+
+ m_groups.clear();
+ m_failedClientsForChannelGroups.clear();
+}
+
+bool CPVRChannelGroups::Update(const std::shared_ptr<CPVRChannelGroup>& group,
+ bool bUpdateFromClient /* = false */)
+{
+ if (group->GroupName().empty() && group->GroupID() <= 0)
+ return true;
+
+ std::shared_ptr<CPVRChannelGroup> updateGroup;
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ // There can be only one internal group! Make sure we never push a new one!
+ if (group->IsInternalGroup())
+ updateGroup = GetGroupAll();
+
+ // try to find the group by id
+ if (!updateGroup && group->GroupID() > 0)
+ updateGroup = GetById(group->GroupID());
+
+ // try to find the group by name if we didn't find it yet
+ if (!updateGroup)
+ updateGroup = GetByName(group->GroupName());
+
+ if (updateGroup)
+ {
+ updateGroup->SetPath(group->GetPath());
+ updateGroup->SetGroupID(group->GroupID());
+ updateGroup->SetGroupType(group->GroupType());
+ updateGroup->SetPosition(group->GetPosition());
+
+ // don't override properties we only store locally in our PVR database
+ if (!bUpdateFromClient)
+ {
+ updateGroup->SetLastWatched(group->LastWatched());
+ updateGroup->SetHidden(group->IsHidden());
+ updateGroup->SetLastOpened(group->LastOpened());
+ }
+ }
+ else
+ {
+ updateGroup = group;
+ m_groups.emplace_back(updateGroup);
+ }
+ }
+
+ // sort groups
+ SortGroups();
+
+ // persist changes
+ if (bUpdateFromClient)
+ return updateGroup->Persist();
+
+ return true;
+}
+
+void CPVRChannelGroups::SortGroups()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ // check if one of the group holds a valid sort position
+ const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(),
+ [](const auto& group) { return (group->GetPosition() > 0); });
+
+ // sort by position if we found a valid sort position
+ if (it != m_groups.cend())
+ {
+ std::sort(m_groups.begin(), m_groups.end(), [](const auto& group1, const auto& group2) {
+ return group1->GetPosition() < group2->GetPosition();
+ });
+ }
+}
+
+std::shared_ptr<CPVRChannelGroupMember> CPVRChannelGroups::GetChannelGroupMemberByPath(
+ const CPVRChannelsPath& path) const
+{
+ if (path.IsChannel())
+ {
+ const std::shared_ptr<CPVRChannelGroup> group = GetByName(path.GetGroupName());
+ if (group)
+ return group->GetByUniqueID(
+ {CPVRClientUID(path.GetAddonID(), path.GetInstanceID()).GetUID(), path.GetChannelUID()});
+ }
+
+ return {};
+}
+
+std::shared_ptr<CPVRChannelGroup> CPVRChannelGroups::GetById(int iGroupId) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), [iGroupId](const auto& group) {
+ return group->GroupID() == iGroupId;
+ });
+ return (it != m_groups.cend()) ? (*it) : std::shared_ptr<CPVRChannelGroup>();
+}
+
+std::vector<std::shared_ptr<CPVRChannelGroup>> CPVRChannelGroups::GetGroupsByChannel(const std::shared_ptr<CPVRChannel>& channel, bool bExcludeHidden /* = false */) const
+{
+ std::vector<std::shared_ptr<CPVRChannelGroup>> groups;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ std::copy_if(m_groups.cbegin(), m_groups.cend(), std::back_inserter(groups),
+ [bExcludeHidden, &channel](const auto& group) {
+ return (!bExcludeHidden || !group->IsHidden()) && group->IsGroupMember(channel);
+ });
+ return groups;
+}
+
+std::shared_ptr<CPVRChannelGroup> CPVRChannelGroups::GetGroupByPath(const std::string& strInPath) const
+{
+ const CPVRChannelsPath path(strInPath);
+ if (path.IsChannelGroup())
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(),
+ [&path](const auto& group) { return group->GetPath() == path; });
+ if (it != m_groups.cend())
+ return (*it);
+ }
+ return {};
+}
+
+std::shared_ptr<CPVRChannelGroup> CPVRChannelGroups::GetByName(const std::string& strName) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), [&strName](const auto& group) {
+ return group->GroupName() == strName;
+ });
+ return (it != m_groups.cend()) ? (*it) : std::shared_ptr<CPVRChannelGroup>();
+}
+
+bool CPVRChannelGroups::HasValidDataForClients(
+ const std::vector<std::shared_ptr<CPVRClient>>& clients) const
+{
+ return m_failedClientsForChannelGroups.empty() ||
+ std::none_of(clients.cbegin(), clients.cend(),
+ [this](const std::shared_ptr<CPVRClient>& client) {
+ return std::find(m_failedClientsForChannelGroups.cbegin(),
+ m_failedClientsForChannelGroups.cend(),
+ client->GetID()) != m_failedClientsForChannelGroups.cend();
+ });
+}
+
+bool CPVRChannelGroups::UpdateFromClients(const std::vector<std::shared_ptr<CPVRClient>>& clients,
+ bool bChannelsOnly /* = false */)
+{
+ bool bSyncWithBackends = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_PVRMANAGER_SYNCCHANNELGROUPS);
+ bool bUpdateAllGroups = !bChannelsOnly && bSyncWithBackends;
+ bool bReturn = true;
+
+ // sync groups
+ const int iSize = m_groups.size();
+ if (bUpdateAllGroups)
+ {
+ // get channel groups from the clients
+ CServiceBroker::GetPVRManager().Clients()->GetChannelGroups(clients, this,
+ m_failedClientsForChannelGroups);
+ CLog::LogFC(LOGDEBUG, LOGPVR, "{} new user defined {} channel groups fetched from clients",
+ (m_groups.size() - iSize), m_bRadio ? "radio" : "TV");
+ }
+ else if (!bSyncWithBackends)
+ {
+ CLog::LogFC(LOGDEBUG, LOGPVR, "'sync channelgroups' is disabled; skipping groups from clients");
+ }
+
+ // sync channels in groups
+ std::vector<std::shared_ptr<CPVRChannelGroup>> groups;
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ groups = m_groups;
+ }
+
+ std::vector<std::shared_ptr<CPVRChannelGroup>> emptyGroups;
+
+ for (const auto& group : groups)
+ {
+ if (bUpdateAllGroups || group->IsInternalGroup())
+ {
+ const int iMemberCount = group->Size();
+ if (!group->UpdateFromClients(clients))
+ {
+ CLog::LogFC(LOGERROR, LOGPVR, "Failed to update channel group '{}'", group->GroupName());
+ bReturn = false;
+ }
+
+ const int iChangedMembersCount = static_cast<int>(group->Size()) - iMemberCount;
+ if (iChangedMembersCount > 0)
+ {
+ CLog::LogFC(LOGDEBUG, LOGPVR, "{} channel group members added to group '{}'",
+ iChangedMembersCount, group->GroupName());
+ }
+ else if (iChangedMembersCount < 0)
+ {
+ CLog::LogFC(LOGDEBUG, LOGPVR, "{} channel group members removed from group '{}'",
+ -iChangedMembersCount, group->GroupName());
+ }
+ else
+ {
+ // could still be changed if same amount of members was removed as was added, but too
+ // complicated to calculate just for debug logging
+ }
+ }
+
+ // remove empty groups if sync with backend is enabled and we have valid data from all clients
+ if (bSyncWithBackends && group->Size() == 0 && !group->IsInternalGroup() &&
+ HasValidDataForClients(clients) && group->HasValidDataForClients(clients))
+ {
+ emptyGroups.emplace_back(group);
+ }
+
+ if (bReturn &&
+ group->IsInternalGroup() &&
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bPVRChannelIconsAutoScan)
+ {
+ CServiceBroker::GetPVRManager().TriggerSearchMissingChannelIcons(group);
+ }
+ }
+
+ for (const auto& group : emptyGroups)
+ {
+ CLog::LogFC(LOGDEBUG, LOGPVR, "Deleting empty channel group '{}'", group->GroupName());
+ DeleteGroup(group);
+ }
+
+ if (bChannelsOnly)
+ {
+ // changes in the all channels group may require resorting/renumbering of other groups.
+ // if we updated all groups this already has been done while updating the single groups.
+ UpdateChannelNumbersFromAllChannelsGroup();
+ }
+
+ CServiceBroker::GetPVRManager().PublishEvent(PVREvent::ChannelGroupsInvalidated);
+
+ // persist changes
+ return PersistAll() && bReturn;
+}
+
+bool CPVRChannelGroups::UpdateChannelNumbersFromAllChannelsGroup()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return std::accumulate(
+ m_groups.cbegin(), m_groups.cend(), false, [](bool changed, const auto& group) {
+ return group->UpdateChannelNumbersFromAllChannelsGroup() ? true : changed;
+ });
+}
+
+std::shared_ptr<CPVRChannelGroup> CPVRChannelGroups::CreateChannelGroup(
+ int iType, const CPVRChannelsPath& path)
+{
+ if (iType == PVR_GROUP_TYPE_INTERNAL)
+ return std::make_shared<CPVRChannelGroupInternal>(path);
+ else
+ return std::make_shared<CPVRChannelGroup>(path, GetGroupAll());
+}
+
+bool CPVRChannelGroups::LoadFromDatabase(const std::vector<std::shared_ptr<CPVRClient>>& clients)
+{
+ const std::shared_ptr<CPVRDatabase> database(CServiceBroker::GetPVRManager().GetTVDatabase());
+ if (!database)
+ return false;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ // Ensure we have an internal group. It is important that the internal group is created before
+ // loading contents from database and that it gets inserted in front of m_groups. Look at
+ // GetGroupAll() implementation to see why.
+ if (m_groups.empty())
+ {
+ const auto internalGroup = std::make_shared<CPVRChannelGroupInternal>(m_bRadio);
+ m_groups.emplace_back(internalGroup);
+ }
+
+ CLog::LogFC(LOGDEBUG, LOGPVR, "Loading all {} channel groups and members",
+ m_bRadio ? "radio" : "TV");
+
+ // load all channels from the database
+ std::map<std::pair<int, int>, std::shared_ptr<CPVRChannel>> channels;
+ database->Get(m_bRadio, clients, channels);
+ CLog::LogFC(LOGDEBUG, LOGPVR, "Fetched {} {} channels from the database", channels.size(),
+ m_bRadio ? "radio" : "TV");
+
+ // load all groups from the database
+ const int iLoaded = database->Get(*this);
+ CLog::LogFC(LOGDEBUG, LOGPVR, "Fetched {} {} groups from the database", iLoaded,
+ m_bRadio ? "radio" : "TV");
+
+ // load all group members from the database
+ for (const auto& group : m_groups)
+ {
+ if (!group->LoadFromDatabase(channels, clients))
+ {
+ CLog::LogFC(LOGERROR, LOGPVR,
+ "Failed to load members of {} channel group '{}' from the database",
+ m_bRadio ? "radio" : "TV", group->GroupName());
+ }
+ }
+
+ // Hide empty groups
+ for (auto it = m_groups.begin(); it != m_groups.end();)
+ {
+ if ((*it)->Size() == 0 && !(*it)->IsInternalGroup())
+ it = m_groups.erase(it);
+ else
+ ++it;
+ }
+
+ return true;
+}
+
+bool CPVRChannelGroups::PersistAll()
+{
+ CLog::LogFC(LOGDEBUG, LOGPVR, "Persisting all channel group changes");
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return std::accumulate(
+ m_groups.cbegin(), m_groups.cend(), true,
+ [](bool success, const auto& group) { return !group->Persist() ? false : success; });
+}
+
+std::shared_ptr<CPVRChannelGroup> CPVRChannelGroups::GetGroupAll() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (!m_groups.empty())
+ return m_groups.front();
+
+ return std::shared_ptr<CPVRChannelGroup>();
+}
+
+std::shared_ptr<CPVRChannelGroup> CPVRChannelGroups::GetLastGroup() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (!m_groups.empty())
+ return m_groups.back();
+
+ return std::shared_ptr<CPVRChannelGroup>();
+}
+
+GroupMemberPair CPVRChannelGroups::GetLastAndPreviousToLastPlayedChannelGroupMember() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (m_groups.empty())
+ return {};
+
+ auto groups = m_groups;
+ lock.unlock();
+
+ std::sort(groups.begin(), groups.end(),
+ [](const auto& a, const auto& b) { return a->LastWatched() > b->LastWatched(); });
+
+ // Last is always 'first' of last played group.
+ const GroupMemberPair members = groups[0]->GetLastAndPreviousToLastPlayedChannelGroupMember();
+ std::shared_ptr<CPVRChannelGroupMember> last = members.first;
+
+ // Previous to last is either 'second' of first group or 'first' of second group.
+ std::shared_ptr<CPVRChannelGroupMember> previousToLast = members.second;
+ if (groups.size() > 1 && groups[0]->LastWatched() && groups[1]->LastWatched() && members.second &&
+ members.second->Channel()->LastWatched())
+ {
+ if (groups[1]->LastWatched() >= members.second->Channel()->LastWatched())
+ {
+ const GroupMemberPair membersPreviousToLastPlayedGroup =
+ groups[1]->GetLastAndPreviousToLastPlayedChannelGroupMember();
+ previousToLast = membersPreviousToLastPlayedGroup.first;
+ }
+ }
+
+ return {last, previousToLast};
+}
+
+std::shared_ptr<CPVRChannelGroup> CPVRChannelGroups::GetLastOpenedGroup() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return std::accumulate(
+ m_groups.cbegin(), m_groups.cend(), std::shared_ptr<CPVRChannelGroup>{},
+ [](const std::shared_ptr<CPVRChannelGroup>& last,
+ const std::shared_ptr<CPVRChannelGroup>& group)
+ {
+ return group->LastOpened() > 0 && (!last || group->LastOpened() > last->LastOpened())
+ ? group
+ : last;
+ });
+}
+
+std::vector<std::shared_ptr<CPVRChannelGroup>> CPVRChannelGroups::GetMembers(bool bExcludeHidden /* = false */) const
+{
+ std::vector<std::shared_ptr<CPVRChannelGroup>> groups;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ std::copy_if(
+ m_groups.cbegin(), m_groups.cend(), std::back_inserter(groups),
+ [bExcludeHidden](const auto& group) { return (!bExcludeHidden || !group->IsHidden()); });
+ return groups;
+}
+
+std::shared_ptr<CPVRChannelGroup> CPVRChannelGroups::GetPreviousGroup(const CPVRChannelGroup& group) const
+{
+ {
+ bool bReturnNext = false;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (std::vector<std::shared_ptr<CPVRChannelGroup>>::const_reverse_iterator it = m_groups.rbegin(); it != m_groups.rend(); ++it)
+ {
+ // return this entry
+ if (bReturnNext && !(*it)->IsHidden())
+ return *it;
+
+ // return the next entry
+ if ((*it)->GroupID() == group.GroupID())
+ bReturnNext = true;
+ }
+
+ // no match return last visible group
+ for (std::vector<std::shared_ptr<CPVRChannelGroup>>::const_reverse_iterator it = m_groups.rbegin(); it != m_groups.rend(); ++it)
+ {
+ if (!(*it)->IsHidden())
+ return *it;
+ }
+ }
+
+ // no match
+ return GetLastGroup();
+}
+
+std::shared_ptr<CPVRChannelGroup> CPVRChannelGroups::GetNextGroup(const CPVRChannelGroup& group) const
+{
+ {
+ bool bReturnNext = false;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (std::vector<std::shared_ptr<CPVRChannelGroup>>::const_iterator it = m_groups.begin(); it != m_groups.end(); ++it)
+ {
+ // return this entry
+ if (bReturnNext && !(*it)->IsHidden())
+ return *it;
+
+ // return the next entry
+ if ((*it)->GroupID() == group.GroupID())
+ bReturnNext = true;
+ }
+
+ // no match return first visible group
+ for (std::vector<std::shared_ptr<CPVRChannelGroup>>::const_iterator it = m_groups.begin(); it != m_groups.end(); ++it)
+ {
+ if (!(*it)->IsHidden())
+ return *it;
+ }
+ }
+
+ // no match
+ return GetFirstGroup();
+}
+
+bool CPVRChannelGroups::AddGroup(const std::string& strName)
+{
+ bool bPersist(false);
+ std::shared_ptr<CPVRChannelGroup> group;
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ // check if there's no group with the same name yet
+ group = GetByName(strName);
+ if (!group)
+ {
+ // create a new group
+ group.reset(new CPVRChannelGroup(CPVRChannelsPath(m_bRadio, strName), GetGroupAll()));
+
+ m_groups.push_back(group);
+ bPersist = true;
+
+ CServiceBroker::GetPVRManager().PublishEvent(PVREvent::ChannelGroupsInvalidated);
+ }
+ }
+
+ // persist in the db if a new group was added
+ return bPersist ? group->Persist() : true;
+}
+
+bool CPVRChannelGroups::DeleteGroup(const std::shared_ptr<CPVRChannelGroup>& group)
+{
+ // don't delete internal groups
+ if (group->IsInternalGroup())
+ {
+ CLog::LogF(LOGERROR, "Internal channel group cannot be deleted");
+ return false;
+ }
+
+ bool bFound(false);
+
+ // delete the group in this container
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (auto it = m_groups.begin(); it != m_groups.end(); ++it)
+ {
+ if (*it == group || (group->GroupID() > 0 && (*it)->GroupID() == group->GroupID()))
+ {
+ m_groups.erase(it);
+ bFound = true;
+ break;
+ }
+ }
+ }
+
+ if (bFound && group->GroupID() > 0)
+ {
+ // delete the group from the database
+ group->Delete();
+ CServiceBroker::GetPVRManager().PublishEvent(PVREvent::ChannelGroupsInvalidated);
+ }
+ return bFound;
+}
+
+bool CPVRChannelGroups::HideGroup(const std::shared_ptr<CPVRChannelGroup>& group, bool bHide)
+{
+ bool bReturn = false;
+
+ if (group)
+ {
+ if (group->SetHidden(bHide))
+ {
+ // state changed
+ CServiceBroker::GetPVRManager().PublishEvent(PVREvent::ChannelGroupsInvalidated);
+ }
+ bReturn = true;
+ }
+ return bReturn;
+}
+
+bool CPVRChannelGroups::CreateChannelEpgs()
+{
+ bool bReturn(false);
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (std::vector<std::shared_ptr<CPVRChannelGroup>>::iterator it = m_groups.begin(); it != m_groups.end(); ++it)
+ {
+ /* Only create EPGs for the internal groups */
+ if ((*it)->IsInternalGroup())
+ bReturn = (*it)->CreateChannelEpgs();
+ }
+ return bReturn;
+}
+
+int CPVRChannelGroups::CleanupCachedImages()
+{
+ int iCleanedImages = 0;
+
+ // cleanup channels
+ iCleanedImages += GetGroupAll()->CleanupCachedImages();
+
+ // cleanup groups
+ std::vector<std::string> urlsToCheck;
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ std::transform(m_groups.cbegin(), m_groups.cend(), std::back_inserter(urlsToCheck),
+ [](const auto& group) { return group->GetPath(); });
+ }
+
+ // kodi-generated thumbnail (see CPVRThumbLoader)
+ const std::string path = StringUtils::Format("pvr://channels/{}/", IsRadio() ? "radio" : "tv");
+ iCleanedImages += CPVRCachedImages::Cleanup({{"pvr", path}}, urlsToCheck, true);
+
+ return iCleanedImages;
+}