/* * 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 #include #include #include #include #include #include #include #include using namespace PVR; using namespace std::chrono_literals; namespace { constexpr auto MAX_NOTIFICATION_DELAY = 10s; } bool CPVRTimersContainer::UpdateFromClient(const std::shared_ptr& timer) { std::unique_lock lock(m_critSection); std::shared_ptr tag = GetByClient(timer->ClientID(), timer->ClientIndex()); if (tag) { return tag->UpdateEntry(timer); } else { timer->SetTimerID(++m_iLastId); InsertEntry(timer); } return true; } std::shared_ptr CPVRTimersContainer::GetByClient(int iClientId, int iClientIndex) const { std::unique_lock 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& 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>& clients) { return LoadFromDatabase(clients) && UpdateFromClients(clients); } bool CPVRTimers::LoadFromDatabase(const std::vector>& clients) { // load local timers from database const std::shared_ptr database = CServiceBroker::GetPVRManager().GetTVDatabase(); if (database) { const std::vector> 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 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>& clients) { { std::unique_lock lock(m_critSection); if (m_bIsUpdating) return false; m_bIsUpdating = true; } CLog::LogFC(LOGDEBUG, LOGPVR, "Updating timers"); CPVRTimersContainer newTimerList; std::vector 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 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& tag) { std::unique_lock 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& 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>& timerNotifications, const std::shared_ptr& 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& failedClients) { bool bChanged(false); bool bAddedOrDeleted(false); std::vector> timerNotifications; std::unique_lock 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 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 newTimer = std::shared_ptr(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 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 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 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> GetEpgTagsForTimerRule( const CPVRTimerRuleMatcher& matcher) { std::vector> matches; const std::shared_ptr channel = matcher.GetChannel(); if (channel) { // match single channel const std::shared_ptr epg = channel->GetEPG(); if (epg) { const std::vector> 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> epgs = CServiceBroker::GetPVRManager().EpgContainer().GetAllEpgs(); for (const auto& epg : epgs) { const std::vector> 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& timer, const CDateTime& now, std::map, std::vector>>& epgMap, bool& bFetchedAllEpgs) { const std::shared_ptr channel = timer->Channel(); if (channel) { const std::shared_ptr epg = channel->GetEPG(); if (epg) { const std::shared_ptr matcher = std::make_shared(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> epgs = CServiceBroker::GetPVRManager().EpgContainer().GetAllEpgs(); for (const auto& epg : epgs) { const std::shared_ptr matcher = std::make_shared(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 matcher = std::make_shared(timer, now); epgMapEntry.second.emplace_back(matcher); } } } } } // unnamed namespace bool CPVRTimers::UpdateEntries(int iMaxNotificationDelay) { std::vector> timersToReinsert; std::vector, std::shared_ptr>> childTimersToInsert; bool bChanged = false; const CDateTime now = CDateTime::GetUTCDateTime(); bool bFetchedAllEpgs = false; std::map, std::vector>> epgMap; std::unique_lock 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 timer = *it2; bool bDeleteTimer = false; if (!timer->IsOwnedByClient()) { if (timer->IsEpgBased()) { // update epg tag const std::shared_ptr epg = CServiceBroker::GetPVRManager().EpgContainer().GetByChannelUid( timer->Channel()->ClientID(), timer->Channel()->UniqueID()); if (epg) { const std::shared_ptr 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& tmr) { return tmr->ParentClientIndex() == timer->ClientIndex(); }); if (bCreate) { const CDateTimeSpan duration = timer->EndAsUTC() - timer->StartAsUTC(); const std::shared_ptr 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 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 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 CPVRTimers::GetNextReminderToAnnnounce() { std::shared_ptr ret; std::unique_lock 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& tag) const { return (eKind == TimerKindAny) || (eKind == TimerKindTV && !tag->IsRadio()) || (eKind == TimerKindRadio && tag->IsRadio()); } std::shared_ptr CPVRTimers::GetNextActiveTimer(const TimerKind& eKind, bool bIgnoreReminders) const { std::unique_lock 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 CPVRTimers::GetNextActiveTimer( bool bIgnoreReminders /* = true */) const { return GetNextActiveTimer(TimerKindAny, bIgnoreReminders); } std::shared_ptr CPVRTimers::GetNextActiveTVTimer() const { return GetNextActiveTimer(TimerKindTV, true); } std::shared_ptr CPVRTimers::GetNextActiveRadioTimer() const { return GetNextActiveTimer(TimerKindRadio, true); } std::vector> CPVRTimers::GetActiveTimers() const { std::vector> tags; std::unique_lock 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 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> CPVRTimers::GetActiveRecordings( const TimerKind& eKind) const { std::vector> tags; std::unique_lock 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> CPVRTimers::GetActiveRecordings() const { return GetActiveRecordings(TimerKindAny); } std::vector> CPVRTimers::GetActiveTVRecordings() const { return GetActiveRecordings(TimerKindTV); } std::vector> CPVRTimers::GetActiveRadioRecordings() const { return GetActiveRecordings(TimerKindRadio); } int CPVRTimers::AmountActiveRecordings(const TimerKind& eKind) const { int iReturn = 0; std::unique_lock 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& channel, bool bDeleteTimerRules /* = true */, bool bCurrentlyActiveOnly /* = false */) { bool bReturn = false; bool bChanged = false; { std::unique_lock 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 CPVRTimers::UpdateEntry( const std::shared_ptr& timer) { bool bChanged = false; std::unique_lock lock(m_critSection); std::shared_ptr 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(); } bool CPVRTimers::AddTimer(const std::shared_ptr& tag) { bool bReturn = false; if (tag->IsOwnedByClient()) { bReturn = tag->AddToClient(); } else { bReturn = AddLocalTimer(tag, true); } return bReturn; } TimerOperationResult CPVRTimers::DeleteTimer(const std::shared_ptr& tag, bool bForce /* = false */, bool bDeleteRule /* = false */) { TimerOperationResult ret = TimerOperationResult::FAILED; if (!tag) return ret; std::shared_ptr tagToDelete = tag; if (bDeleteRule) { /* delete the timer rule that scheduled this timer. */ const std::shared_ptr 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& tag) { bool bReturn = false; if (tag->IsOwnedByClient()) { bReturn = tag->UpdateOnClient(); } else { bReturn = UpdateLocalTimer(tag); } return bReturn; } bool CPVRTimers::AddLocalTimer(const std::shared_ptr& tag, bool bNotify) { std::unique_lock lock(m_critSection); const std::shared_ptr 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> epgTags = GetEpgTagsForTimerRule(CPVRTimerRuleMatcher(persistedTimer, CDateTime::GetUTCDateTime())); for (const auto& epgTag : epgTags) { const std::shared_ptr 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 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& tag, bool bNotify) { std::unique_lock 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 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& 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 CPVRTimers::PersistAndUpdateLocalTimer( const std::shared_ptr& timer, const std::shared_ptr& parentTimer) { std::shared_ptr tag; bool bReturn = timer->Persist(); if (bReturn) { tag = UpdateEntry(timer); if (tag && parentTimer) parentTimer->UpdateChildState(timer, true); } return bReturn ? tag : std::shared_ptr(); } bool CPVRTimers::IsRecordingOnChannel(const CPVRChannel& channel) const { std::unique_lock 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 CPVRTimers::GetActiveTimerForChannel( const std::shared_ptr& channel) const { std::unique_lock 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 CPVRTimers::GetTimerForEpgTag( const std::shared_ptr& epgTag) const { if (epgTag) { std::unique_lock 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 CPVRTimers::GetTimerRule( const std::shared_ptr& timer) const { if (timer) { const int iParentClientIndex = timer->ParentClientIndex(); if (iParentClientIndex != PVR_TIMER_NO_PARENT) { int iClientId = timer->ClientID(); std::unique_lock 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(event)) { case PVREvent::EpgContainer: CServiceBroker::GetPVRManager().TriggerTimersUpdate(); break; case PVREvent::Epg: case PVREvent::EpgItemUpdate: { std::unique_lock 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 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 lock(m_critSection); for (const auto& tagsEntry : m_tags) { for (const auto& timersEntry : tagsEntry.second) timersEntry->UpdateChannel(); } } std::vector> CPVRTimers::GetAll() const { std::vector> timers; std::unique_lock 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 CPVRTimers::GetById(unsigned int iTimerId) const { std::unique_lock 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); }