summaryrefslogtreecommitdiffstats
path: root/xbmc/pvr/timers/PVRTimers.cpp
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/timers/PVRTimers.cpp
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/timers/PVRTimers.cpp')
-rw-r--r--xbmc/pvr/timers/PVRTimers.cpp1338
1 files changed, 1338 insertions, 0 deletions
diff --git a/xbmc/pvr/timers/PVRTimers.cpp b/xbmc/pvr/timers/PVRTimers.cpp
new file mode 100644
index 0000000..760bb19
--- /dev/null
+++ b/xbmc/pvr/timers/PVRTimers.cpp
@@ -0,0 +1,1338 @@
+/*
+ * 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 "PVRTimers.h"
+
+#include "ServiceBroker.h"
+#include "pvr/PVRDatabase.h"
+#include "pvr/PVREventLogJob.h"
+#include "pvr/PVRManager.h"
+#include "pvr/addons/PVRClient.h"
+#include "pvr/addons/PVRClients.h"
+#include "pvr/channels/PVRChannel.h"
+#include "pvr/epg/Epg.h"
+#include "pvr/epg/EpgContainer.h"
+#include "pvr/epg/EpgInfoTag.h"
+#include "pvr/timers/PVRTimerInfoTag.h"
+#include "pvr/timers/PVRTimerRuleMatcher.h"
+#include "settings/Settings.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <iterator>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <numeric>
+#include <string>
+#include <utility>
+#include <vector>
+
+using namespace PVR;
+using namespace std::chrono_literals;
+
+namespace
+{
+constexpr auto MAX_NOTIFICATION_DELAY = 10s;
+}
+
+bool CPVRTimersContainer::UpdateFromClient(const std::shared_ptr<CPVRTimerInfoTag>& timer)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ std::shared_ptr<CPVRTimerInfoTag> tag = GetByClient(timer->ClientID(), timer->ClientIndex());
+ if (tag)
+ {
+ return tag->UpdateEntry(timer);
+ }
+ else
+ {
+ timer->SetTimerID(++m_iLastId);
+ InsertEntry(timer);
+ }
+
+ return true;
+}
+
+std::shared_ptr<CPVRTimerInfoTag> CPVRTimersContainer::GetByClient(int iClientId,
+ int iClientIndex) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (const auto& startDates : m_tags)
+ {
+ const auto it = std::find_if(startDates.second.cbegin(), startDates.second.cend(),
+ [iClientId, iClientIndex](const auto& timer) {
+ return timer->ClientID() == iClientId &&
+ timer->ClientIndex() == iClientIndex;
+ });
+ if (it != startDates.second.cend())
+ return (*it);
+ }
+
+ return {};
+}
+
+void CPVRTimersContainer::InsertEntry(const std::shared_ptr<CPVRTimerInfoTag>& newTimer)
+{
+ auto it = m_tags.find(newTimer->IsStartAnyTime() ? CDateTime() : newTimer->StartAsUTC());
+ if (it == m_tags.end())
+ {
+ VecTimerInfoTag addEntry({newTimer});
+ m_tags.insert(std::make_pair(newTimer->IsStartAnyTime() ? CDateTime() : newTimer->StartAsUTC(),
+ addEntry));
+ }
+ else
+ {
+ it->second.emplace_back(newTimer);
+ }
+}
+
+CPVRTimers::CPVRTimers()
+ : CThread("PVRTimers"),
+ m_settings({CSettings::SETTING_PVRPOWERMANAGEMENT_DAILYWAKEUP,
+ CSettings::SETTING_PVRPOWERMANAGEMENT_PREWAKEUP,
+ CSettings::SETTING_PVRPOWERMANAGEMENT_BACKENDIDLETIME,
+ CSettings::SETTING_PVRPOWERMANAGEMENT_DAILYWAKEUPTIME,
+ CSettings::SETTING_PVRRECORD_TIMERNOTIFICATIONS})
+{
+}
+
+bool CPVRTimers::Update(const std::vector<std::shared_ptr<CPVRClient>>& clients)
+{
+ return LoadFromDatabase(clients) && UpdateFromClients(clients);
+}
+
+bool CPVRTimers::LoadFromDatabase(const std::vector<std::shared_ptr<CPVRClient>>& clients)
+{
+ // load local timers from database
+ const std::shared_ptr<CPVRDatabase> database = CServiceBroker::GetPVRManager().GetTVDatabase();
+ if (database)
+ {
+ const std::vector<std::shared_ptr<CPVRTimerInfoTag>> timers =
+ database->GetTimers(*this, clients);
+
+ if (std::accumulate(timers.cbegin(), timers.cend(), false,
+ [this](bool changed, const auto& timer) {
+ return (UpdateEntry(timer) != nullptr) ? true : changed;
+ }))
+ NotifyTimersEvent();
+ }
+
+ // ensure that every timer has its channel set
+ UpdateChannels();
+ return true;
+}
+
+void CPVRTimers::Unload()
+{
+ // remove all tags
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_tags.clear();
+}
+
+void CPVRTimers::Start()
+{
+ Stop();
+
+ CServiceBroker::GetPVRManager().Events().Subscribe(this, &CPVRTimers::Notify);
+ Create();
+}
+
+void CPVRTimers::Stop()
+{
+ StopThread();
+ CServiceBroker::GetPVRManager().Events().Unsubscribe(this);
+}
+
+bool CPVRTimers::UpdateFromClients(const std::vector<std::shared_ptr<CPVRClient>>& clients)
+{
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (m_bIsUpdating)
+ return false;
+ m_bIsUpdating = true;
+ }
+
+ CLog::LogFC(LOGDEBUG, LOGPVR, "Updating timers");
+ CPVRTimersContainer newTimerList;
+ std::vector<int> failedClients;
+ CServiceBroker::GetPVRManager().Clients()->GetTimers(clients, &newTimerList, failedClients);
+ return UpdateEntries(newTimerList, failedClients);
+}
+
+void CPVRTimers::Process()
+{
+ while (!m_bStop)
+ {
+ // update all timers not owned by a client (e.g. reminders)
+ UpdateEntries(MAX_NOTIFICATION_DELAY.count());
+
+ CThread::Sleep(MAX_NOTIFICATION_DELAY);
+ }
+}
+
+bool CPVRTimers::IsRecording() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ for (const auto& tagsEntry : m_tags)
+ {
+ if (std::any_of(tagsEntry.second.cbegin(), tagsEntry.second.cend(),
+ [](const auto& timersEntry) { return timersEntry->IsRecording(); }))
+ return true;
+ }
+
+ return false;
+}
+
+void CPVRTimers::RemoveEntry(const std::shared_ptr<CPVRTimerInfoTag>& tag)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ auto it = m_tags.find(tag->IsStartAnyTime() ? CDateTime() : tag->StartAsUTC());
+ if (it != m_tags.end())
+ {
+ it->second.erase(std::remove_if(it->second.begin(), it->second.end(),
+ [&tag](const std::shared_ptr<CPVRTimerInfoTag>& timer) {
+ return tag->ClientID() == timer->ClientID() &&
+ tag->ClientIndex() == timer->ClientIndex();
+ }),
+ it->second.end());
+
+ if (it->second.empty())
+ m_tags.erase(it);
+ }
+}
+
+bool CPVRTimers::CheckAndAppendTimerNotification(
+ std::vector<std::pair<int, std::string>>& timerNotifications,
+ const std::shared_ptr<CPVRTimerInfoTag>& tag,
+ bool bDeleted) const
+{
+ // no notification on first update or if previous update failed for tag's client.
+ if (!m_bFirstUpdate && std::find(m_failedClients.cbegin(), m_failedClients.cend(),
+ tag->ClientID()) == m_failedClients.cend())
+ {
+ const std::string strMessage =
+ bDeleted ? tag->GetDeletedNotificationText() : tag->GetNotificationText();
+ timerNotifications.emplace_back(std::make_pair(tag->ClientID(), strMessage));
+ return true;
+ }
+ return false;
+}
+
+bool CPVRTimers::UpdateEntries(const CPVRTimersContainer& timers,
+ const std::vector<int>& failedClients)
+{
+ bool bChanged(false);
+ bool bAddedOrDeleted(false);
+ std::vector<std::pair<int, std::string>> timerNotifications;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ /* go through the timer list and check for updated or new timers */
+ for (const auto& tagsEntry : timers.GetTags())
+ {
+ for (const auto& timersEntry : tagsEntry.second)
+ {
+ /* check if this timer is present in this container */
+ const std::shared_ptr<CPVRTimerInfoTag> existingTimer =
+ GetByClient(timersEntry->ClientID(), timersEntry->ClientIndex());
+ if (existingTimer)
+ {
+ /* if it's present, update the current tag */
+ bool bStateChanged(existingTimer->State() != timersEntry->State());
+ if (existingTimer->UpdateEntry(timersEntry))
+ {
+ bChanged = true;
+ existingTimer->ResetChildState();
+
+ if (bStateChanged)
+ CheckAndAppendTimerNotification(timerNotifications, existingTimer, false);
+
+ CLog::LogFC(LOGDEBUG, LOGPVR, "Updated timer {} on client {}", timersEntry->ClientIndex(),
+ timersEntry->ClientID());
+ }
+ }
+ else
+ {
+ /* new timer */
+ std::shared_ptr<CPVRTimerInfoTag> newTimer =
+ std::shared_ptr<CPVRTimerInfoTag>(new CPVRTimerInfoTag);
+ newTimer->UpdateEntry(timersEntry);
+ newTimer->SetTimerID(++m_iLastId);
+ InsertEntry(newTimer);
+
+ bChanged = true;
+ bAddedOrDeleted = true;
+
+ CheckAndAppendTimerNotification(timerNotifications, newTimer, false);
+
+ CLog::LogFC(LOGDEBUG, LOGPVR, "Added timer {} on client {}", timersEntry->ClientIndex(),
+ timersEntry->ClientID());
+ }
+ }
+ }
+
+ /* to collect timer with changed starting time */
+ VecTimerInfoTag timersToMove;
+
+ /* check for deleted timers */
+ for (auto it = m_tags.begin(); it != m_tags.end();)
+ {
+ for (auto it2 = it->second.begin(); it2 != it->second.end();)
+ {
+ const std::shared_ptr<CPVRTimerInfoTag> timer = *it2;
+ if (!timers.GetByClient(timer->ClientID(), timer->ClientIndex()))
+ {
+ /* timer was not found */
+ bool bIgnoreTimer = !timer->IsOwnedByClient();
+ if (!bIgnoreTimer)
+ {
+ bIgnoreTimer = std::any_of(
+ failedClients.cbegin(), failedClients.cend(),
+ [&timer](const auto& failedClient) { return failedClient == timer->ClientID(); });
+ }
+
+ if (bIgnoreTimer)
+ {
+ ++it2;
+ continue;
+ }
+
+ CLog::LogFC(LOGDEBUG, LOGPVR, "Deleted timer {} on client {}", timer->ClientIndex(),
+ timer->ClientID());
+
+ CheckAndAppendTimerNotification(timerNotifications, timer, true);
+
+ it2 = it->second.erase(it2);
+
+ bChanged = true;
+ bAddedOrDeleted = true;
+ }
+ else if ((timer->IsStartAnyTime() && it->first != CDateTime()) ||
+ (!timer->IsStartAnyTime() && timer->StartAsUTC() != it->first))
+ {
+ /* timer start has changed */
+ CLog::LogFC(LOGDEBUG, LOGPVR, "Changed start time timer {} on client {}",
+ timer->ClientIndex(), timer->ClientID());
+
+ /* remember timer */
+ timersToMove.push_back(timer);
+
+ /* remove timer for now, reinsert later */
+ it2 = it->second.erase(it2);
+
+ bChanged = true;
+ bAddedOrDeleted = true;
+ }
+ else
+ {
+ ++it2;
+ }
+ }
+ if (it->second.empty())
+ it = m_tags.erase(it);
+ else
+ ++it;
+ }
+
+ /* reinsert timers with changed timer start */
+ for (const auto& timer : timersToMove)
+ {
+ InsertEntry(timer);
+ }
+
+ /* update child information for all parent timers */
+ for (const auto& tagsEntry : m_tags)
+ {
+ for (const auto& timersEntry : tagsEntry.second)
+ {
+ if (timersEntry->IsTimerRule())
+ timersEntry->ResetChildState();
+ }
+
+ for (const auto& timersEntry : tagsEntry.second)
+ {
+ const std::shared_ptr<CPVRTimerInfoTag> parentTimer = GetTimerRule(timersEntry);
+ if (parentTimer)
+ parentTimer->UpdateChildState(timersEntry, true);
+ }
+ }
+
+ m_failedClients = failedClients;
+ m_bFirstUpdate = false;
+ m_bIsUpdating = false;
+
+ if (bChanged)
+ {
+ UpdateChannels();
+ lock.unlock();
+
+ NotifyTimersEvent(bAddedOrDeleted);
+
+ if (!timerNotifications.empty())
+ {
+ CPVREventLogJob* job = new CPVREventLogJob;
+
+ /* queue notifications / fill eventlog */
+ for (const auto& entry : timerNotifications)
+ {
+ const std::shared_ptr<CPVRClient> client =
+ CServiceBroker::GetPVRManager().GetClient(entry.first);
+ if (client)
+ {
+ job->AddEvent(m_settings.GetBoolValue(CSettings::SETTING_PVRRECORD_TIMERNOTIFICATIONS),
+ EventLevel::Information, // info, no error
+ client->GetFriendlyName(), entry.second, client->Icon());
+ }
+ }
+
+ CServiceBroker::GetJobManager()->AddJob(job, nullptr);
+ }
+ }
+
+ return true;
+}
+
+namespace
+{
+std::vector<std::shared_ptr<CPVREpgInfoTag>> GetEpgTagsForTimerRule(
+ const CPVRTimerRuleMatcher& matcher)
+{
+ std::vector<std::shared_ptr<CPVREpgInfoTag>> matches;
+
+ const std::shared_ptr<CPVRChannel> channel = matcher.GetChannel();
+ if (channel)
+ {
+ // match single channel
+ const std::shared_ptr<CPVREpg> epg = channel->GetEPG();
+ if (epg)
+ {
+ const std::vector<std::shared_ptr<CPVREpgInfoTag>> tags = epg->GetTags();
+ std::copy_if(tags.cbegin(), tags.cend(), std::back_inserter(matches),
+ [&matcher](const auto& tag) { return matcher.Matches(tag); });
+ }
+ }
+ else
+ {
+ // match any channel
+ const std::vector<std::shared_ptr<CPVREpg>> epgs =
+ CServiceBroker::GetPVRManager().EpgContainer().GetAllEpgs();
+
+ for (const auto& epg : epgs)
+ {
+ const std::vector<std::shared_ptr<CPVREpgInfoTag>> tags = epg->GetTags();
+ std::copy_if(tags.cbegin(), tags.cend(), std::back_inserter(matches),
+ [&matcher](const auto& tag) { return matcher.Matches(tag); });
+ }
+ }
+
+ return matches;
+}
+
+void AddTimerRuleToEpgMap(
+ const std::shared_ptr<CPVRTimerInfoTag>& timer,
+ const CDateTime& now,
+ std::map<std::shared_ptr<CPVREpg>, std::vector<std::shared_ptr<CPVRTimerRuleMatcher>>>& epgMap,
+ bool& bFetchedAllEpgs)
+{
+ const std::shared_ptr<CPVRChannel> channel = timer->Channel();
+ if (channel)
+ {
+ const std::shared_ptr<CPVREpg> epg = channel->GetEPG();
+ if (epg)
+ {
+ const std::shared_ptr<CPVRTimerRuleMatcher> matcher =
+ std::make_shared<CPVRTimerRuleMatcher>(timer, now);
+ auto it = epgMap.find(epg);
+ if (it == epgMap.end())
+ epgMap.insert({epg, {matcher}});
+ else
+ it->second.emplace_back(matcher);
+ }
+ }
+ else
+ {
+ // rule matches "any channel" => we need to check all channels
+ if (!bFetchedAllEpgs)
+ {
+ const std::vector<std::shared_ptr<CPVREpg>> epgs =
+ CServiceBroker::GetPVRManager().EpgContainer().GetAllEpgs();
+ for (const auto& epg : epgs)
+ {
+ const std::shared_ptr<CPVRTimerRuleMatcher> matcher =
+ std::make_shared<CPVRTimerRuleMatcher>(timer, now);
+ auto it = epgMap.find(epg);
+ if (it == epgMap.end())
+ epgMap.insert({epg, {matcher}});
+ else
+ it->second.emplace_back(matcher);
+ }
+ bFetchedAllEpgs = true;
+ }
+ else
+ {
+ for (auto& epgMapEntry : epgMap)
+ {
+ const std::shared_ptr<CPVRTimerRuleMatcher> matcher =
+ std::make_shared<CPVRTimerRuleMatcher>(timer, now);
+ epgMapEntry.second.emplace_back(matcher);
+ }
+ }
+ }
+}
+} // unnamed namespace
+
+bool CPVRTimers::UpdateEntries(int iMaxNotificationDelay)
+{
+ std::vector<std::shared_ptr<CPVRTimerInfoTag>> timersToReinsert;
+ std::vector<std::pair<std::shared_ptr<CPVRTimerInfoTag>, std::shared_ptr<CPVRTimerInfoTag>>>
+ childTimersToInsert;
+ bool bChanged = false;
+ const CDateTime now = CDateTime::GetUTCDateTime();
+ bool bFetchedAllEpgs = false;
+ std::map<std::shared_ptr<CPVREpg>, std::vector<std::shared_ptr<CPVRTimerRuleMatcher>>> epgMap;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ for (auto it = m_tags.begin(); it != m_tags.end();)
+ {
+ for (auto it2 = it->second.begin(); it2 != it->second.end();)
+ {
+ std::shared_ptr<CPVRTimerInfoTag> timer = *it2;
+ bool bDeleteTimer = false;
+ if (!timer->IsOwnedByClient())
+ {
+ if (timer->IsEpgBased())
+ {
+ // update epg tag
+ const std::shared_ptr<CPVREpg> epg =
+ CServiceBroker::GetPVRManager().EpgContainer().GetByChannelUid(
+ timer->Channel()->ClientID(), timer->Channel()->UniqueID());
+ if (epg)
+ {
+ const std::shared_ptr<CPVREpgInfoTag> epgTag =
+ epg->GetTagByBroadcastId(timer->UniqueBroadcastID());
+ if (epgTag)
+ {
+ timer->SetEpgInfoTag(epgTag);
+
+ bool bStartChanged =
+ !timer->IsStartAnyTime() && epgTag->StartAsUTC() != timer->StartAsUTC();
+ bool bEndChanged = !timer->IsEndAnyTime() && epgTag->EndAsUTC() != timer->EndAsUTC();
+ if (bStartChanged || bEndChanged)
+ {
+ if (bStartChanged)
+ timer->SetStartFromUTC(epgTag->StartAsUTC());
+ if (bEndChanged)
+ timer->SetEndFromUTC(epgTag->EndAsUTC());
+
+ timer->UpdateSummary();
+ bChanged = true;
+
+ if (bStartChanged)
+ {
+ // start time changed. timer must be reinserted in timer map
+ bDeleteTimer = true;
+ timersToReinsert.emplace_back(timer); // remember and reinsert/save later
+ }
+ else
+ {
+ // save changes to database
+ timer->Persist();
+ }
+ }
+ }
+ }
+ }
+
+ // check for due timers and announce/delete them
+ int iMarginStart = timer->GetTimerType()->SupportsStartMargin() ? timer->MarginStart() : 0;
+ if (!timer->IsTimerRule() &&
+ (timer->StartAsUTC() - CDateTimeSpan(0, 0, iMarginStart, iMaxNotificationDelay)) < now)
+ {
+ if (timer->IsReminder() && !timer->IsDisabled())
+ {
+ // reminder is due / over due. announce it.
+ m_remindersToAnnounce.push(timer);
+ }
+
+ if (timer->EndAsUTC() >= now)
+ {
+ // disable timer until timer's end time is due
+ if (!timer->IsDisabled())
+ {
+ timer->SetState(PVR_TIMER_STATE_DISABLED);
+ bChanged = true;
+ }
+ }
+ else
+ {
+ // end time due. delete completed timer
+ bChanged = true;
+ bDeleteTimer = true;
+ timer->DeleteFromDatabase();
+ }
+ }
+
+ if (timer->IsTimerRule() && timer->IsReminder() && timer->IsActive())
+ {
+ if (timer->IsEpgBased())
+ {
+ if (m_bReminderRulesUpdatePending)
+ AddTimerRuleToEpgMap(timer, now, epgMap, bFetchedAllEpgs);
+ }
+ else
+ {
+ // create new children of time-based reminder timer rules
+ const CPVRTimerRuleMatcher matcher(timer, now);
+ const CDateTime nextStart = matcher.GetNextTimerStart();
+ if (nextStart.IsValid())
+ {
+ bool bCreate = false;
+ const auto it1 = m_tags.find(nextStart);
+ if (it1 == m_tags.end())
+ bCreate = true;
+ else
+ bCreate = std::none_of(it1->second.cbegin(), it1->second.cend(),
+ [&timer](const std::shared_ptr<CPVRTimerInfoTag>& tmr) {
+ return tmr->ParentClientIndex() == timer->ClientIndex();
+ });
+ if (bCreate)
+ {
+ const CDateTimeSpan duration = timer->EndAsUTC() - timer->StartAsUTC();
+ const std::shared_ptr<CPVRTimerInfoTag> childTimer =
+ CPVRTimerInfoTag::CreateReminderFromDate(
+ nextStart, duration.GetSecondsTotal() / 60, timer);
+ if (childTimer)
+ {
+ bChanged = true;
+ childTimersToInsert.emplace_back(
+ std::make_pair(timer, childTimer)); // remember and insert/save later
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (bDeleteTimer)
+ {
+ const std::shared_ptr<CPVRTimerInfoTag> parent = GetTimerRule(timer);
+ if (parent)
+ parent->UpdateChildState(timer, false);
+
+ it2 = it->second.erase(it2);
+ }
+ else
+ {
+ ++it2;
+ }
+ }
+
+ if (it->second.empty())
+ it = m_tags.erase(it);
+ else
+ ++it;
+ }
+
+ // create new children of local epg-based reminder timer rules
+ for (const auto& epgMapEntry : epgMap)
+ {
+ const auto epgTags = epgMapEntry.first->GetTags();
+ for (const auto& epgTag : epgTags)
+ {
+ if (GetTimerForEpgTag(epgTag))
+ continue;
+
+ for (const auto& matcher : epgMapEntry.second)
+ {
+ if (!matcher->Matches(epgTag))
+ continue;
+
+ const std::shared_ptr<CPVRTimerInfoTag> childTimer =
+ CPVRTimerInfoTag::CreateReminderFromEpg(epgTag, matcher->GetTimerRule());
+ if (childTimer)
+ {
+ bChanged = true;
+ childTimersToInsert.emplace_back(std::make_pair(
+ matcher->GetTimerRule(), childTimer)); // remember and insert/save later
+ }
+ }
+ }
+ }
+
+ // reinsert timers with changed timer start
+ for (const auto& timer : timersToReinsert)
+ {
+ InsertEntry(timer);
+ timer->Persist();
+ }
+
+ // insert new children of time-based local timer rules
+ for (const auto& timerPair : childTimersToInsert)
+ {
+ PersistAndUpdateLocalTimer(timerPair.second, timerPair.first);
+ }
+
+ m_bReminderRulesUpdatePending = false;
+
+ // announce changes
+ if (bChanged)
+ NotifyTimersEvent();
+
+ if (!m_remindersToAnnounce.empty())
+ CServiceBroker::GetPVRManager().PublishEvent(PVREvent::AnnounceReminder);
+
+ return bChanged;
+}
+
+std::shared_ptr<CPVRTimerInfoTag> CPVRTimers::GetNextReminderToAnnnounce()
+{
+ std::shared_ptr<CPVRTimerInfoTag> ret;
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (!m_remindersToAnnounce.empty())
+ {
+ ret = m_remindersToAnnounce.front();
+ m_remindersToAnnounce.pop();
+ }
+ return ret;
+}
+
+bool CPVRTimers::KindMatchesTag(const TimerKind& eKind,
+ const std::shared_ptr<CPVRTimerInfoTag>& tag) const
+{
+ return (eKind == TimerKindAny) || (eKind == TimerKindTV && !tag->IsRadio()) ||
+ (eKind == TimerKindRadio && tag->IsRadio());
+}
+
+std::shared_ptr<CPVRTimerInfoTag> CPVRTimers::GetNextActiveTimer(const TimerKind& eKind,
+ bool bIgnoreReminders) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ for (const auto& tagsEntry : m_tags)
+ {
+ for (const auto& timersEntry : tagsEntry.second)
+ {
+ if (bIgnoreReminders && timersEntry->IsReminder())
+ continue;
+
+ if (KindMatchesTag(eKind, timersEntry) && timersEntry->IsActive() &&
+ !timersEntry->IsRecording() && !timersEntry->IsTimerRule() && !timersEntry->IsBroken())
+ return timersEntry;
+ }
+ }
+
+ return {};
+}
+
+std::shared_ptr<CPVRTimerInfoTag> CPVRTimers::GetNextActiveTimer(
+ bool bIgnoreReminders /* = true */) const
+{
+ return GetNextActiveTimer(TimerKindAny, bIgnoreReminders);
+}
+
+std::shared_ptr<CPVRTimerInfoTag> CPVRTimers::GetNextActiveTVTimer() const
+{
+ return GetNextActiveTimer(TimerKindTV, true);
+}
+
+std::shared_ptr<CPVRTimerInfoTag> CPVRTimers::GetNextActiveRadioTimer() const
+{
+ return GetNextActiveTimer(TimerKindRadio, true);
+}
+
+std::vector<std::shared_ptr<CPVRTimerInfoTag>> CPVRTimers::GetActiveTimers() const
+{
+ std::vector<std::shared_ptr<CPVRTimerInfoTag>> tags;
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ for (const auto& tagsEntry : m_tags)
+ {
+ std::copy_if(tagsEntry.second.cbegin(), tagsEntry.second.cend(), std::back_inserter(tags),
+ [](const auto& timersEntry) {
+ return timersEntry->IsActive() && !timersEntry->IsBroken() &&
+ !timersEntry->IsReminder() && !timersEntry->IsTimerRule();
+ });
+ }
+
+ return tags;
+}
+
+int CPVRTimers::AmountActiveTimers(const TimerKind& eKind) const
+{
+ int iReturn = 0;
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ for (const auto& tagsEntry : m_tags)
+ {
+ iReturn += std::count_if(tagsEntry.second.cbegin(), tagsEntry.second.cend(),
+ [this, &eKind](const auto& timersEntry) {
+ return KindMatchesTag(eKind, timersEntry) &&
+ timersEntry->IsActive() && !timersEntry->IsBroken() &&
+ !timersEntry->IsReminder() && !timersEntry->IsTimerRule();
+ });
+ }
+
+ return iReturn;
+}
+
+int CPVRTimers::AmountActiveTimers() const
+{
+ return AmountActiveTimers(TimerKindAny);
+}
+
+int CPVRTimers::AmountActiveTVTimers() const
+{
+ return AmountActiveTimers(TimerKindTV);
+}
+
+int CPVRTimers::AmountActiveRadioTimers() const
+{
+ return AmountActiveTimers(TimerKindRadio);
+}
+
+std::vector<std::shared_ptr<CPVRTimerInfoTag>> CPVRTimers::GetActiveRecordings(
+ const TimerKind& eKind) const
+{
+ std::vector<std::shared_ptr<CPVRTimerInfoTag>> tags;
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ for (const auto& tagsEntry : m_tags)
+ {
+ std::copy_if(tagsEntry.second.cbegin(), tagsEntry.second.cend(), std::back_inserter(tags),
+ [this, &eKind](const auto& timersEntry) {
+ return KindMatchesTag(eKind, timersEntry) && timersEntry->IsRecording() &&
+ !timersEntry->IsTimerRule() && !timersEntry->IsBroken() &&
+ !timersEntry->IsReminder();
+ });
+ }
+
+ return tags;
+}
+
+std::vector<std::shared_ptr<CPVRTimerInfoTag>> CPVRTimers::GetActiveRecordings() const
+{
+ return GetActiveRecordings(TimerKindAny);
+}
+
+std::vector<std::shared_ptr<CPVRTimerInfoTag>> CPVRTimers::GetActiveTVRecordings() const
+{
+ return GetActiveRecordings(TimerKindTV);
+}
+
+std::vector<std::shared_ptr<CPVRTimerInfoTag>> CPVRTimers::GetActiveRadioRecordings() const
+{
+ return GetActiveRecordings(TimerKindRadio);
+}
+
+int CPVRTimers::AmountActiveRecordings(const TimerKind& eKind) const
+{
+ int iReturn = 0;
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ for (const auto& tagsEntry : m_tags)
+ {
+ iReturn += std::count_if(tagsEntry.second.cbegin(), tagsEntry.second.cend(),
+ [this, &eKind](const auto& timersEntry) {
+ return KindMatchesTag(eKind, timersEntry) &&
+ timersEntry->IsRecording() && !timersEntry->IsTimerRule() &&
+ !timersEntry->IsBroken() && !timersEntry->IsReminder();
+ });
+ }
+
+ return iReturn;
+}
+
+int CPVRTimers::AmountActiveRecordings() const
+{
+ return AmountActiveRecordings(TimerKindAny);
+}
+
+int CPVRTimers::AmountActiveTVRecordings() const
+{
+ return AmountActiveRecordings(TimerKindTV);
+}
+
+int CPVRTimers::AmountActiveRadioRecordings() const
+{
+ return AmountActiveRecordings(TimerKindRadio);
+}
+
+/********** channel methods **********/
+
+bool CPVRTimers::DeleteTimersOnChannel(const std::shared_ptr<CPVRChannel>& channel,
+ bool bDeleteTimerRules /* = true */,
+ bool bCurrentlyActiveOnly /* = false */)
+{
+ bool bReturn = false;
+ bool bChanged = false;
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ for (MapTags::reverse_iterator it = m_tags.rbegin(); it != m_tags.rend(); ++it)
+ {
+ for (const auto& timersEntry : (*it).second)
+ {
+ bool bDeleteActiveItem = !bCurrentlyActiveOnly || timersEntry->IsRecording();
+ bool bDeleteTimerRuleItem = bDeleteTimerRules || !timersEntry->IsTimerRule();
+ bool bChannelsMatch = timersEntry->HasChannel() && timersEntry->Channel() == channel;
+
+ if (bDeleteActiveItem && bDeleteTimerRuleItem && bChannelsMatch)
+ {
+ CLog::LogFC(LOGDEBUG, LOGPVR, "Deleted timer {} on client {}", timersEntry->ClientIndex(),
+ timersEntry->ClientID());
+ bReturn = (timersEntry->DeleteFromClient(true) == TimerOperationResult::OK) || bReturn;
+ bChanged = true;
+ }
+ }
+ }
+ }
+
+ if (bChanged)
+ NotifyTimersEvent();
+
+ return bReturn;
+}
+
+std::shared_ptr<CPVRTimerInfoTag> CPVRTimers::UpdateEntry(
+ const std::shared_ptr<CPVRTimerInfoTag>& timer)
+{
+ bool bChanged = false;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ std::shared_ptr<CPVRTimerInfoTag> tag = GetByClient(timer->ClientID(), timer->ClientIndex());
+ if (tag)
+ {
+ bool bReinsert = tag->StartAsUTC() != timer->StartAsUTC();
+ if (bReinsert)
+ {
+ RemoveEntry(tag);
+ }
+
+ bChanged = tag->UpdateEntry(timer);
+
+ if (bReinsert)
+ {
+ InsertEntry(tag);
+ }
+ }
+ else
+ {
+ tag.reset(new CPVRTimerInfoTag());
+ if (tag->UpdateEntry(timer))
+ {
+ tag->SetTimerID(++m_iLastId);
+ InsertEntry(tag);
+ bChanged = true;
+ }
+ }
+
+ return bChanged ? tag : std::shared_ptr<CPVRTimerInfoTag>();
+}
+
+bool CPVRTimers::AddTimer(const std::shared_ptr<CPVRTimerInfoTag>& tag)
+{
+ bool bReturn = false;
+ if (tag->IsOwnedByClient())
+ {
+ bReturn = tag->AddToClient();
+ }
+ else
+ {
+ bReturn = AddLocalTimer(tag, true);
+ }
+ return bReturn;
+}
+
+TimerOperationResult CPVRTimers::DeleteTimer(const std::shared_ptr<CPVRTimerInfoTag>& tag,
+ bool bForce /* = false */,
+ bool bDeleteRule /* = false */)
+{
+ TimerOperationResult ret = TimerOperationResult::FAILED;
+ if (!tag)
+ return ret;
+
+ std::shared_ptr<CPVRTimerInfoTag> tagToDelete = tag;
+
+ if (bDeleteRule)
+ {
+ /* delete the timer rule that scheduled this timer. */
+ const std::shared_ptr<CPVRTimerInfoTag> ruleTag = GetTimerRule(tagToDelete);
+ if (!ruleTag)
+ {
+ CLog::LogF(LOGERROR, "Unable to obtain timer rule for given timer");
+ return ret;
+ }
+
+ tagToDelete = ruleTag;
+ }
+
+ if (tagToDelete->IsOwnedByClient())
+ {
+ ret = tagToDelete->DeleteFromClient(bForce);
+ }
+ else
+ {
+ if (DeleteLocalTimer(tagToDelete, true))
+ ret = TimerOperationResult::OK;
+ }
+
+ return ret;
+}
+
+bool CPVRTimers::UpdateTimer(const std::shared_ptr<CPVRTimerInfoTag>& tag)
+{
+ bool bReturn = false;
+ if (tag->IsOwnedByClient())
+ {
+ bReturn = tag->UpdateOnClient();
+ }
+ else
+ {
+ bReturn = UpdateLocalTimer(tag);
+ }
+ return bReturn;
+}
+
+bool CPVRTimers::AddLocalTimer(const std::shared_ptr<CPVRTimerInfoTag>& tag, bool bNotify)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ const std::shared_ptr<CPVRTimerInfoTag> persistedTimer = PersistAndUpdateLocalTimer(tag, nullptr);
+ bool bReturn = !!persistedTimer;
+
+ if (bReturn && persistedTimer->IsTimerRule() && persistedTimer->IsActive())
+ {
+ if (persistedTimer->IsEpgBased())
+ {
+ // create and persist children of local epg-based timer rule
+ const std::vector<std::shared_ptr<CPVREpgInfoTag>> epgTags =
+ GetEpgTagsForTimerRule(CPVRTimerRuleMatcher(persistedTimer, CDateTime::GetUTCDateTime()));
+ for (const auto& epgTag : epgTags)
+ {
+ const std::shared_ptr<CPVRTimerInfoTag> childTimer =
+ CPVRTimerInfoTag::CreateReminderFromEpg(epgTag, persistedTimer);
+ if (childTimer)
+ {
+ PersistAndUpdateLocalTimer(childTimer, persistedTimer);
+ }
+ }
+ }
+ else
+ {
+ // create and persist children of local time-based timer rule
+ const CDateTime nextStart =
+ CPVRTimerRuleMatcher(persistedTimer, CDateTime::GetUTCDateTime()).GetNextTimerStart();
+ if (nextStart.IsValid())
+ {
+ const CDateTimeSpan duration = persistedTimer->EndAsUTC() - persistedTimer->StartAsUTC();
+ const std::shared_ptr<CPVRTimerInfoTag> childTimer =
+ CPVRTimerInfoTag::CreateReminderFromDate(nextStart, duration.GetSecondsTotal() / 60,
+ persistedTimer);
+ if (childTimer)
+ {
+ PersistAndUpdateLocalTimer(childTimer, persistedTimer);
+ }
+ }
+ }
+ }
+
+ if (bNotify && bReturn)
+ {
+ lock.unlock();
+ NotifyTimersEvent();
+ }
+
+ return bReturn;
+}
+
+bool CPVRTimers::DeleteLocalTimer(const std::shared_ptr<CPVRTimerInfoTag>& tag, bool bNotify)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ RemoveEntry(tag);
+
+ bool bReturn = tag->DeleteFromDatabase();
+
+ if (bReturn && tag->IsTimerRule())
+ {
+ // delete children of local timer rule
+ for (auto it = m_tags.begin(); it != m_tags.end();)
+ {
+ for (auto it2 = it->second.begin(); it2 != it->second.end();)
+ {
+ std::shared_ptr<CPVRTimerInfoTag> timer = *it2;
+ if (timer->ParentClientIndex() == tag->ClientIndex())
+ {
+ tag->UpdateChildState(timer, false);
+ it2 = it->second.erase(it2);
+ timer->DeleteFromDatabase();
+ }
+ else
+ {
+ ++it2;
+ }
+ }
+
+ if (it->second.empty())
+ it = m_tags.erase(it);
+ else
+ ++it;
+ }
+ }
+
+ if (bNotify && bReturn)
+ {
+ lock.unlock();
+ NotifyTimersEvent();
+ }
+
+ return bReturn;
+}
+
+bool CPVRTimers::UpdateLocalTimer(const std::shared_ptr<CPVRTimerInfoTag>& tag)
+{
+ // delete and re-create timer and children, if any.
+ bool bReturn = DeleteLocalTimer(tag, false);
+
+ if (bReturn)
+ bReturn = AddLocalTimer(tag, false);
+
+ if (bReturn)
+ NotifyTimersEvent();
+
+ return bReturn;
+}
+
+std::shared_ptr<CPVRTimerInfoTag> CPVRTimers::PersistAndUpdateLocalTimer(
+ const std::shared_ptr<CPVRTimerInfoTag>& timer,
+ const std::shared_ptr<CPVRTimerInfoTag>& parentTimer)
+{
+ std::shared_ptr<CPVRTimerInfoTag> tag;
+ bool bReturn = timer->Persist();
+ if (bReturn)
+ {
+ tag = UpdateEntry(timer);
+ if (tag && parentTimer)
+ parentTimer->UpdateChildState(timer, true);
+ }
+ return bReturn ? tag : std::shared_ptr<CPVRTimerInfoTag>();
+}
+
+bool CPVRTimers::IsRecordingOnChannel(const CPVRChannel& channel) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ for (const auto& tagsEntry : m_tags)
+ {
+ if (std::any_of(tagsEntry.second.cbegin(), tagsEntry.second.cend(),
+ [&channel](const auto& timersEntry) {
+ return timersEntry->IsRecording() &&
+ timersEntry->ClientChannelUID() == channel.UniqueID() &&
+ timersEntry->ClientID() == channel.ClientID();
+ }))
+ return true;
+ }
+
+ return false;
+}
+
+std::shared_ptr<CPVRTimerInfoTag> CPVRTimers::GetActiveTimerForChannel(
+ const std::shared_ptr<CPVRChannel>& channel) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (const auto& tagsEntry : m_tags)
+ {
+ const auto it = std::find_if(tagsEntry.second.cbegin(), tagsEntry.second.cend(),
+ [&channel](const auto& timersEntry) {
+ return timersEntry->IsRecording() &&
+ timersEntry->ClientChannelUID() == channel->UniqueID() &&
+ timersEntry->ClientID() == channel->ClientID();
+ });
+ if (it != tagsEntry.second.cend())
+ return (*it);
+ }
+
+ return {};
+}
+
+std::shared_ptr<CPVRTimerInfoTag> CPVRTimers::GetTimerForEpgTag(
+ const std::shared_ptr<CPVREpgInfoTag>& epgTag) const
+{
+ if (epgTag)
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ for (const auto& tagsEntry : m_tags)
+ {
+ for (const auto& timersEntry : tagsEntry.second)
+ {
+ if (timersEntry->IsTimerRule())
+ continue;
+
+ if (timersEntry->GetEpgInfoTag(false) == epgTag)
+ return timersEntry;
+
+ if (timersEntry->ClientChannelUID() != PVR_CHANNEL_INVALID_UID &&
+ timersEntry->ClientChannelUID() == epgTag->UniqueChannelID() &&
+ timersEntry->ClientID() == epgTag->ClientID())
+ {
+ if (timersEntry->UniqueBroadcastID() != EPG_TAG_INVALID_UID &&
+ timersEntry->UniqueBroadcastID() == epgTag->UniqueBroadcastID())
+ return timersEntry;
+
+ if (timersEntry->IsRadio() == epgTag->IsRadio() &&
+ timersEntry->StartAsUTC() <= epgTag->StartAsUTC() &&
+ timersEntry->EndAsUTC() >= epgTag->EndAsUTC())
+ return timersEntry;
+ }
+ }
+ }
+ }
+
+ return {};
+}
+
+std::shared_ptr<CPVRTimerInfoTag> CPVRTimers::GetTimerRule(
+ const std::shared_ptr<CPVRTimerInfoTag>& timer) const
+{
+ if (timer)
+ {
+ const int iParentClientIndex = timer->ParentClientIndex();
+ if (iParentClientIndex != PVR_TIMER_NO_PARENT)
+ {
+ int iClientId = timer->ClientID();
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (const auto& tagsEntry : m_tags)
+ {
+ const auto it = std::find_if(tagsEntry.second.cbegin(), tagsEntry.second.cend(),
+ [iClientId, iParentClientIndex](const auto& timersEntry) {
+ return timersEntry->ClientID() == iClientId &&
+ timersEntry->ClientIndex() == iParentClientIndex;
+ });
+ if (it != tagsEntry.second.cend())
+ return (*it);
+ }
+ }
+ }
+
+ return {};
+}
+
+void CPVRTimers::Notify(const PVREvent& event)
+{
+ switch (static_cast<PVREvent>(event))
+ {
+ case PVREvent::EpgContainer:
+ CServiceBroker::GetPVRManager().TriggerTimersUpdate();
+ break;
+ case PVREvent::Epg:
+ case PVREvent::EpgItemUpdate:
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bReminderRulesUpdatePending = true;
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+CDateTime CPVRTimers::GetNextEventTime() const
+{
+ const bool dailywakup =
+ m_settings.GetBoolValue(CSettings::SETTING_PVRPOWERMANAGEMENT_DAILYWAKEUP);
+ const CDateTime now = CDateTime::GetUTCDateTime();
+ const CDateTimeSpan prewakeup(
+ 0, 0, m_settings.GetIntValue(CSettings::SETTING_PVRPOWERMANAGEMENT_PREWAKEUP), 0);
+ const CDateTimeSpan idle(
+ 0, 0, m_settings.GetIntValue(CSettings::SETTING_PVRPOWERMANAGEMENT_BACKENDIDLETIME), 0);
+
+ CDateTime wakeuptime;
+
+ /* Check next active time */
+ const std::shared_ptr<CPVRTimerInfoTag> timer = GetNextActiveTimer(false);
+ if (timer)
+ {
+ const CDateTimeSpan prestart(0, 0, timer->MarginStart(), 0);
+ const CDateTime start = timer->StartAsUTC();
+ wakeuptime =
+ ((start - prestart - prewakeup - idle) > now) ? start - prestart - prewakeup : now + idle;
+ }
+
+ /* check daily wake up */
+ if (dailywakup)
+ {
+ CDateTime dailywakeuptime;
+ dailywakeuptime.SetFromDBTime(
+ m_settings.GetStringValue(CSettings::SETTING_PVRPOWERMANAGEMENT_DAILYWAKEUPTIME));
+ dailywakeuptime = dailywakeuptime.GetAsUTCDateTime();
+
+ dailywakeuptime.SetDateTime(now.GetYear(), now.GetMonth(), now.GetDay(),
+ dailywakeuptime.GetHour(), dailywakeuptime.GetMinute(),
+ dailywakeuptime.GetSecond());
+
+ if ((dailywakeuptime - idle) < now)
+ {
+ const CDateTimeSpan oneDay(1, 0, 0, 0);
+ dailywakeuptime += oneDay;
+ }
+ if (!wakeuptime.IsValid() || dailywakeuptime < wakeuptime)
+ wakeuptime = dailywakeuptime;
+ }
+
+ const CDateTime retVal(wakeuptime);
+ return retVal;
+}
+
+void CPVRTimers::UpdateChannels()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (const auto& tagsEntry : m_tags)
+ {
+ for (const auto& timersEntry : tagsEntry.second)
+ timersEntry->UpdateChannel();
+ }
+}
+
+std::vector<std::shared_ptr<CPVRTimerInfoTag>> CPVRTimers::GetAll() const
+{
+ std::vector<std::shared_ptr<CPVRTimerInfoTag>> timers;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (const auto& tagsEntry : m_tags)
+ {
+ std::copy(tagsEntry.second.cbegin(), tagsEntry.second.cend(), std::back_inserter(timers));
+ }
+
+ return timers;
+}
+
+std::shared_ptr<CPVRTimerInfoTag> CPVRTimers::GetById(unsigned int iTimerId) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (const auto& tagsEntry : m_tags)
+ {
+ const auto it = std::find_if(
+ tagsEntry.second.cbegin(), tagsEntry.second.cend(),
+ [iTimerId](const auto& timersEntry) { return timersEntry->TimerID() == iTimerId; });
+ if (it != tagsEntry.second.cend())
+ return (*it);
+ }
+
+ return {};
+}
+
+void CPVRTimers::NotifyTimersEvent(bool bAddedOrDeleted /* = true */)
+{
+ CServiceBroker::GetPVRManager().PublishEvent(bAddedOrDeleted ? PVREvent::TimersInvalidated
+ : PVREvent::Timers);
+}