summaryrefslogtreecommitdiffstats
path: root/xbmc/pvr/timers
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/pvr/timers')
-rw-r--r--xbmc/pvr/timers/CMakeLists.txt13
-rw-r--r--xbmc/pvr/timers/PVRTimerInfoTag.cpp1389
-rw-r--r--xbmc/pvr/timers/PVRTimerInfoTag.h644
-rw-r--r--xbmc/pvr/timers/PVRTimerRuleMatcher.cpp193
-rw-r--r--xbmc/pvr/timers/PVRTimerRuleMatcher.h47
-rw-r--r--xbmc/pvr/timers/PVRTimerType.cpp418
-rw-r--r--xbmc/pvr/timers/PVRTimerType.h411
-rw-r--r--xbmc/pvr/timers/PVRTimers.cpp1338
-rw-r--r--xbmc/pvr/timers/PVRTimers.h331
-rw-r--r--xbmc/pvr/timers/PVRTimersPath.cpp82
-rw-r--r--xbmc/pvr/timers/PVRTimersPath.h50
11 files changed, 4916 insertions, 0 deletions
diff --git a/xbmc/pvr/timers/CMakeLists.txt b/xbmc/pvr/timers/CMakeLists.txt
new file mode 100644
index 0000000..e1031b4
--- /dev/null
+++ b/xbmc/pvr/timers/CMakeLists.txt
@@ -0,0 +1,13 @@
+set(SOURCES PVRTimerInfoTag.cpp
+ PVRTimerRuleMatcher.cpp
+ PVRTimers.cpp
+ PVRTimersPath.cpp
+ PVRTimerType.cpp)
+
+set(HEADERS PVRTimerInfoTag.h
+ PVRTimerRuleMatcher.h
+ PVRTimers.h
+ PVRTimersPath.h
+ PVRTimerType.h)
+
+core_add_library(pvr_timers)
diff --git a/xbmc/pvr/timers/PVRTimerInfoTag.cpp b/xbmc/pvr/timers/PVRTimerInfoTag.cpp
new file mode 100644
index 0000000..24df4cc
--- /dev/null
+++ b/xbmc/pvr/timers/PVRTimerInfoTag.cpp
@@ -0,0 +1,1389 @@
+/*
+ * 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 "PVRTimerInfoTag.h"
+
+#include "ServiceBroker.h"
+#include "guilib/LocalizeStrings.h"
+#include "pvr/PVRDatabase.h"
+#include "pvr/PVRManager.h"
+#include "pvr/addons/PVRClient.h"
+#include "pvr/addons/PVRClients.h"
+#include "pvr/channels/PVRChannel.h"
+#include "pvr/channels/PVRChannelGroups.h"
+#include "pvr/channels/PVRChannelGroupsContainer.h"
+#include "pvr/epg/Epg.h"
+#include "pvr/epg/EpgInfoTag.h"
+#include "pvr/timers/PVRTimersPath.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+
+#include <ctime>
+#include <memory>
+#include <mutex>
+#include <stdexcept>
+#include <string>
+
+using namespace PVR;
+
+CPVRTimerInfoTag::CPVRTimerInfoTag(bool bRadio /* = false */)
+ : m_strTitle(g_localizeStrings.Get(19056)), // New Timer
+ m_iClientId(CServiceBroker::GetPVRManager().Clients()->GetFirstCreatedClientID()),
+ m_iClientIndex(PVR_TIMER_NO_CLIENT_INDEX),
+ m_iParentClientIndex(PVR_TIMER_NO_PARENT),
+ m_iClientChannelUid(PVR_CHANNEL_INVALID_UID),
+ m_iPriority(DEFAULT_RECORDING_PRIORITY),
+ m_iLifetime(DEFAULT_RECORDING_LIFETIME),
+ m_iPreventDupEpisodes(DEFAULT_RECORDING_DUPLICATEHANDLING),
+ m_bIsRadio(bRadio),
+ m_iMarginStart(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ CSettings::SETTING_PVRRECORD_MARGINSTART)),
+ m_iMarginEnd(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ CSettings::SETTING_PVRRECORD_MARGINEND)),
+ m_iEpgUid(EPG_TAG_INVALID_UID),
+ m_StartTime(CDateTime::GetUTCDateTime()),
+ m_StopTime(m_StartTime + CDateTimeSpan(0, 2, 0, 0)),
+ m_FirstDay(m_StartTime)
+{
+ static const uint64_t iMustHaveAttr = PVR_TIMER_TYPE_IS_MANUAL;
+ static const uint64_t iMustNotHaveAttr =
+ PVR_TIMER_TYPE_IS_REPEATING | PVR_TIMER_TYPE_FORBIDS_NEW_INSTANCES;
+
+ std::shared_ptr<CPVRTimerType> type;
+
+ const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId);
+ if (client && client->GetClientCapabilities().SupportsTimers())
+ {
+ // default to first available type for given client
+ type = CPVRTimerType::GetFirstAvailableType(client);
+ }
+
+ // fallback to manual one-shot reminder, which is always available
+ if (!type)
+ type = CPVRTimerType::CreateFromAttributes(iMustHaveAttr | PVR_TIMER_TYPE_IS_REMINDER,
+ iMustNotHaveAttr, m_iClientId);
+
+ if (type)
+ SetTimerType(type);
+ else
+ {
+ CLog::LogF(LOGERROR, "Unable to obtain timer type!");
+ throw std::logic_error("CPVRTimerInfoTag::CPVRTimerInfoTag - Unable to obtain timer type!");
+ }
+
+ m_iWeekdays = m_timerType->IsTimerRule() ? PVR_WEEKDAY_ALLDAYS : PVR_WEEKDAY_NONE;
+
+ UpdateSummary();
+}
+
+CPVRTimerInfoTag::CPVRTimerInfoTag(const PVR_TIMER& timer,
+ const std::shared_ptr<CPVRChannel>& channel,
+ unsigned int iClientId)
+ : m_strTitle(timer.strTitle),
+ m_strEpgSearchString(timer.strEpgSearchString),
+ m_bFullTextEpgSearch(timer.bFullTextEpgSearch),
+ m_strDirectory(timer.strDirectory),
+ m_state(timer.state),
+ m_iClientId(iClientId),
+ m_iClientIndex(timer.iClientIndex),
+ m_iParentClientIndex(timer.iParentClientIndex),
+ m_iClientChannelUid(channel ? channel->UniqueID()
+ : (timer.iClientChannelUid > 0) ? timer.iClientChannelUid
+ : PVR_CHANNEL_INVALID_UID),
+ m_bStartAnyTime(timer.bStartAnyTime),
+ m_bEndAnyTime(timer.bEndAnyTime),
+ m_iPriority(timer.iPriority),
+ m_iLifetime(timer.iLifetime),
+ m_iMaxRecordings(timer.iMaxRecordings),
+ m_iWeekdays(timer.iWeekdays),
+ m_iPreventDupEpisodes(timer.iPreventDuplicateEpisodes),
+ m_iRecordingGroup(timer.iRecordingGroup),
+ m_strFileNameAndPath(
+ StringUtils::Format("pvr://client{}/timers/{}", m_iClientId, m_iClientIndex)),
+ m_bIsRadio(channel && channel->IsRadio()),
+ m_iMarginStart(timer.iMarginStart),
+ m_iMarginEnd(timer.iMarginEnd),
+ m_iEpgUid(timer.iEpgUid),
+ m_strSeriesLink(timer.strSeriesLink),
+ m_StartTime(
+ timer.startTime +
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iPVRTimeCorrection),
+ m_StopTime(timer.endTime +
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iPVRTimeCorrection),
+ m_channel(channel)
+{
+ if (timer.firstDay)
+ m_FirstDay = CDateTime(
+ timer.firstDay +
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iPVRTimeCorrection);
+ else
+ m_FirstDay = CDateTime::GetUTCDateTime();
+
+ if (m_iClientIndex == PVR_TIMER_NO_CLIENT_INDEX)
+ CLog::LogF(LOGERROR, "Invalid client index supplied by client {} (must be > 0)!", m_iClientId);
+
+ const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId);
+ if (client && client->GetClientCapabilities().SupportsTimers())
+ {
+ // begin compat section
+
+ // Create timer type according to certain timer values already available in Isengard.
+ // This is for migration only and does not make changes to the addons obsolete. Addons should
+ // work and benefit from some UI changes (e.g. some of the timer settings dialog enhancements),
+ // but all old problems/bugs due to static attributes and values will remain the same as in
+ // Isengard. Also, new features (like epg search) are not available to addons automatically.
+ // This code can be removed once all addons actually support the respective PVR Addon API version.
+ if (timer.iTimerType == PVR_TIMER_TYPE_NONE)
+ {
+ // Create type according to certain timer values.
+ uint64_t iMustHave = PVR_TIMER_TYPE_ATTRIBUTE_NONE;
+ uint64_t iMustNotHave = PVR_TIMER_TYPE_FORBIDS_NEW_INSTANCES;
+
+ if (timer.iEpgUid == PVR_TIMER_NO_EPG_UID && timer.iWeekdays != PVR_WEEKDAY_NONE)
+ iMustHave |= PVR_TIMER_TYPE_IS_REPEATING;
+ else
+ iMustNotHave |= PVR_TIMER_TYPE_IS_REPEATING;
+
+ if (timer.iEpgUid == PVR_TIMER_NO_EPG_UID)
+ iMustHave |= PVR_TIMER_TYPE_IS_MANUAL;
+ else
+ iMustNotHave |= PVR_TIMER_TYPE_IS_MANUAL;
+
+ const std::shared_ptr<CPVRTimerType> type =
+ CPVRTimerType::CreateFromAttributes(iMustHave, iMustNotHave, m_iClientId);
+
+ if (type)
+ SetTimerType(type);
+ }
+ // end compat section
+ else
+ {
+ SetTimerType(CPVRTimerType::CreateFromIds(timer.iTimerType, m_iClientId));
+ }
+
+ if (!m_timerType)
+ {
+ CLog::LogF(LOGERROR, "No timer type, although timers are supported by client {}!",
+ m_iClientId);
+ throw std::logic_error("CPVRTimerInfoTag::CPVRTimerInfoTag - Unable to obtain timer type!");
+ }
+ else if (m_iEpgUid == EPG_TAG_INVALID_UID && m_timerType->IsEpgBasedOnetime())
+ {
+ CLog::LogF(LOGERROR, "No epg tag given for epg based timer type ({})!",
+ m_timerType->GetTypeId());
+ }
+ }
+
+ UpdateSummary();
+ UpdateEpgInfoTag();
+}
+
+bool CPVRTimerInfoTag::operator==(const CPVRTimerInfoTag& right) const
+{
+ bool bChannelsMatch = true;
+ if (m_channel && right.m_channel)
+ bChannelsMatch = *m_channel == *right.m_channel;
+ else if (m_channel != right.m_channel)
+ bChannelsMatch = false;
+
+ return (bChannelsMatch && m_iClientIndex == right.m_iClientIndex &&
+ m_iParentClientIndex == right.m_iParentClientIndex &&
+ m_strSummary == right.m_strSummary && m_iClientChannelUid == right.m_iClientChannelUid &&
+ m_bIsRadio == right.m_bIsRadio && m_iPreventDupEpisodes == right.m_iPreventDupEpisodes &&
+ m_iRecordingGroup == right.m_iRecordingGroup && m_StartTime == right.m_StartTime &&
+ m_StopTime == right.m_StopTime && m_bStartAnyTime == right.m_bStartAnyTime &&
+ m_bEndAnyTime == right.m_bEndAnyTime && m_FirstDay == right.m_FirstDay &&
+ m_iWeekdays == right.m_iWeekdays && m_iPriority == right.m_iPriority &&
+ m_iLifetime == right.m_iLifetime && m_iMaxRecordings == right.m_iMaxRecordings &&
+ m_strFileNameAndPath == right.m_strFileNameAndPath && m_strTitle == right.m_strTitle &&
+ m_strEpgSearchString == right.m_strEpgSearchString &&
+ m_bFullTextEpgSearch == right.m_bFullTextEpgSearch &&
+ m_strDirectory == right.m_strDirectory && m_iClientId == right.m_iClientId &&
+ m_iMarginStart == right.m_iMarginStart && m_iMarginEnd == right.m_iMarginEnd &&
+ m_state == right.m_state && m_timerType == right.m_timerType &&
+ m_iTimerId == right.m_iTimerId && m_strSeriesLink == right.m_strSeriesLink &&
+ m_iEpgUid == right.m_iEpgUid && m_iTVChildTimersActive == right.m_iTVChildTimersActive &&
+ m_iTVChildTimersConflictNOK == right.m_iTVChildTimersConflictNOK &&
+ m_iTVChildTimersRecording == right.m_iTVChildTimersRecording &&
+ m_iTVChildTimersErrors == right.m_iTVChildTimersErrors &&
+ m_iRadioChildTimersActive == right.m_iRadioChildTimersActive &&
+ m_iRadioChildTimersConflictNOK == right.m_iRadioChildTimersConflictNOK &&
+ m_iRadioChildTimersRecording == right.m_iRadioChildTimersRecording &&
+ m_iRadioChildTimersErrors == right.m_iRadioChildTimersErrors);
+}
+
+bool CPVRTimerInfoTag::operator!=(const CPVRTimerInfoTag& right) const
+{
+ return !(*this == right);
+}
+
+void CPVRTimerInfoTag::FillAddonData(PVR_TIMER& timer) const
+{
+ time_t start, end, firstDay;
+ StartAsUTC().GetAsTime(start);
+ EndAsUTC().GetAsTime(end);
+ FirstDayAsUTC().GetAsTime(firstDay);
+ const std::shared_ptr<CPVREpgInfoTag> epgTag = GetEpgInfoTag();
+ const int iPVRTimeCorrection =
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iPVRTimeCorrection;
+
+ timer = {};
+ timer.iClientIndex = m_iClientIndex;
+ timer.iParentClientIndex = m_iParentClientIndex;
+ timer.state = m_state;
+ timer.iTimerType = GetTimerType()->GetTypeId();
+ timer.iClientChannelUid = m_iClientChannelUid;
+ strncpy(timer.strTitle, m_strTitle.c_str(), sizeof(timer.strTitle) - 1);
+ strncpy(timer.strEpgSearchString, m_strEpgSearchString.c_str(),
+ sizeof(timer.strEpgSearchString) - 1);
+ timer.bFullTextEpgSearch = m_bFullTextEpgSearch;
+ strncpy(timer.strDirectory, m_strDirectory.c_str(), sizeof(timer.strDirectory) - 1);
+ timer.iPriority = m_iPriority;
+ timer.iLifetime = m_iLifetime;
+ timer.iMaxRecordings = m_iMaxRecordings;
+ timer.iPreventDuplicateEpisodes = m_iPreventDupEpisodes;
+ timer.iRecordingGroup = m_iRecordingGroup;
+ timer.iWeekdays = m_iWeekdays;
+ timer.startTime = start - iPVRTimeCorrection;
+ timer.endTime = end - iPVRTimeCorrection;
+ timer.bStartAnyTime = m_bStartAnyTime;
+ timer.bEndAnyTime = m_bEndAnyTime;
+ timer.firstDay = firstDay - iPVRTimeCorrection;
+ timer.iEpgUid = epgTag ? epgTag->UniqueBroadcastID() : PVR_TIMER_NO_EPG_UID;
+ strncpy(timer.strSummary, m_strSummary.c_str(), sizeof(timer.strSummary) - 1);
+ timer.iMarginStart = m_iMarginStart;
+ timer.iMarginEnd = m_iMarginEnd;
+ timer.iGenreType = epgTag ? epgTag->GenreType() : 0;
+ timer.iGenreSubType = epgTag ? epgTag->GenreSubType() : 0;
+ strncpy(timer.strSeriesLink, SeriesLink().c_str(), sizeof(timer.strSeriesLink) - 1);
+}
+
+void CPVRTimerInfoTag::Serialize(CVariant& value) const
+{
+ value["channelid"] = m_channel != NULL ? m_channel->ChannelID() : -1;
+ value["summary"] = m_strSummary;
+ value["isradio"] = m_bIsRadio;
+ value["preventduplicateepisodes"] = m_iPreventDupEpisodes;
+ value["starttime"] = m_StartTime.IsValid() ? m_StartTime.GetAsDBDateTime() : "";
+ value["endtime"] = m_StopTime.IsValid() ? m_StopTime.GetAsDBDateTime() : "";
+ value["startanytime"] = m_bStartAnyTime;
+ value["endanytime"] = m_bEndAnyTime;
+ value["runtime"] = m_StartTime.IsValid() && m_StopTime.IsValid()
+ ? (m_StopTime - m_StartTime).GetSecondsTotal()
+ : 0;
+ value["firstday"] = m_FirstDay.IsValid() ? m_FirstDay.GetAsDBDate() : "";
+
+ CVariant weekdays(CVariant::VariantTypeArray);
+ if (m_iWeekdays & PVR_WEEKDAY_MONDAY)
+ weekdays.push_back("monday");
+ if (m_iWeekdays & PVR_WEEKDAY_TUESDAY)
+ weekdays.push_back("tuesday");
+ if (m_iWeekdays & PVR_WEEKDAY_WEDNESDAY)
+ weekdays.push_back("wednesday");
+ if (m_iWeekdays & PVR_WEEKDAY_THURSDAY)
+ weekdays.push_back("thursday");
+ if (m_iWeekdays & PVR_WEEKDAY_FRIDAY)
+ weekdays.push_back("friday");
+ if (m_iWeekdays & PVR_WEEKDAY_SATURDAY)
+ weekdays.push_back("saturday");
+ if (m_iWeekdays & PVR_WEEKDAY_SUNDAY)
+ weekdays.push_back("sunday");
+ value["weekdays"] = weekdays;
+
+ value["priority"] = m_iPriority;
+ value["lifetime"] = m_iLifetime;
+ value["title"] = m_strTitle;
+ value["directory"] = m_strDirectory;
+ value["startmargin"] = m_iMarginStart;
+ value["endmargin"] = m_iMarginEnd;
+
+ value["timerid"] = m_iTimerId;
+
+ switch (m_state)
+ {
+ case PVR_TIMER_STATE_NEW:
+ value["state"] = "new";
+ break;
+ case PVR_TIMER_STATE_SCHEDULED:
+ value["state"] = "scheduled";
+ break;
+ case PVR_TIMER_STATE_RECORDING:
+ value["state"] = "recording";
+ break;
+ case PVR_TIMER_STATE_COMPLETED:
+ value["state"] = "completed";
+ break;
+ case PVR_TIMER_STATE_ABORTED:
+ value["state"] = "aborted";
+ break;
+ case PVR_TIMER_STATE_CANCELLED:
+ value["state"] = "cancelled";
+ break;
+ case PVR_TIMER_STATE_CONFLICT_OK:
+ value["state"] = "conflict_ok";
+ break;
+ case PVR_TIMER_STATE_CONFLICT_NOK:
+ value["state"] = "conflict_notok";
+ break;
+ case PVR_TIMER_STATE_ERROR:
+ value["state"] = "error";
+ break;
+ case PVR_TIMER_STATE_DISABLED:
+ value["state"] = "disabled";
+ break;
+ default:
+ value["state"] = "unknown";
+ break;
+ }
+
+ value["istimerrule"] = m_timerType->IsTimerRule();
+ value["ismanual"] = m_timerType->IsManual();
+ value["isreadonly"] = m_timerType->IsReadOnly();
+ value["isreminder"] = m_timerType->IsReminder();
+
+ value["epgsearchstring"] = m_strEpgSearchString;
+ value["fulltextepgsearch"] = m_bFullTextEpgSearch;
+ value["recordinggroup"] = m_iRecordingGroup;
+ value["maxrecordings"] = m_iMaxRecordings;
+ value["epguid"] = m_iEpgUid;
+ value["broadcastid"] = m_epgTag ? m_epgTag->DatabaseID() : -1;
+ value["serieslink"] = m_strSeriesLink;
+
+ value["clientid"] = m_iClientId;
+}
+
+void CPVRTimerInfoTag::UpdateSummary()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_strSummary.clear();
+
+ const std::string startDate(StartAsLocalTime().GetAsLocalizedDate());
+ const std::string endDate(EndAsLocalTime().GetAsLocalizedDate());
+
+ if (m_bEndAnyTime)
+ {
+ m_strSummary = StringUtils::Format(
+ "{} {} {}",
+ m_iWeekdays != PVR_WEEKDAY_NONE ? GetWeekdaysString()
+ : startDate, //for "Any day" set PVR_WEEKDAY_ALLDAYS
+ g_localizeStrings.Get(19107), // "at"
+ m_bStartAnyTime ? g_localizeStrings.Get(19161) /* "any time" */
+ : StartAsLocalTime().GetAsLocalizedTime("", false));
+ }
+ else if ((m_iWeekdays != PVR_WEEKDAY_NONE) || (startDate == endDate))
+ {
+ m_strSummary = StringUtils::Format(
+ "{} {} {} {} {}",
+ m_iWeekdays != PVR_WEEKDAY_NONE ? GetWeekdaysString()
+ : startDate, //for "Any day" set PVR_WEEKDAY_ALLDAYS
+ g_localizeStrings.Get(19159), // "from"
+ m_bStartAnyTime ? g_localizeStrings.Get(19161) /* "any time" */
+ : StartAsLocalTime().GetAsLocalizedTime("", false),
+ g_localizeStrings.Get(19160), // "to"
+ m_bEndAnyTime ? g_localizeStrings.Get(19161) /* "any time" */
+ : EndAsLocalTime().GetAsLocalizedTime("", false));
+ }
+ else
+ {
+ m_strSummary =
+ StringUtils::Format("{} {} {} {} {} {}", startDate,
+ g_localizeStrings.Get(19159), // "from"
+ m_bStartAnyTime ? g_localizeStrings.Get(19161) /* "any time" */
+ : StartAsLocalTime().GetAsLocalizedTime("", false),
+ g_localizeStrings.Get(19160), // "to"
+ endDate,
+ m_bEndAnyTime ? g_localizeStrings.Get(19161) /* "any time" */
+ : EndAsLocalTime().GetAsLocalizedTime("", false));
+ }
+}
+
+void CPVRTimerInfoTag::SetTimerType(const std::shared_ptr<CPVRTimerType>& type)
+{
+ if (!type)
+ throw std::logic_error("CPVRTimerInfoTag::SetTimerType - Attempt to set 'null' timer type!");
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_timerType = type;
+
+ if (m_iClientIndex == PVR_TIMER_NO_CLIENT_INDEX)
+ {
+ m_iPriority = m_timerType->GetPriorityDefault();
+ m_iLifetime = m_timerType->GetLifetimeDefault();
+ m_iMaxRecordings = m_timerType->GetMaxRecordingsDefault();
+ m_iPreventDupEpisodes = m_timerType->GetPreventDuplicateEpisodesDefault();
+ m_iRecordingGroup = m_timerType->GetRecordingGroupDefault();
+ }
+
+ if (!m_timerType->IsTimerRule())
+ m_iWeekdays = PVR_WEEKDAY_NONE;
+}
+
+std::string CPVRTimerInfoTag::GetStatus(bool bRadio) const
+{
+ std::string strReturn = g_localizeStrings.Get(305);
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (URIUtils::PathEquals(m_strFileNameAndPath, CPVRTimersPath::PATH_ADDTIMER))
+ strReturn = g_localizeStrings.Get(19026);
+ else if (m_state == PVR_TIMER_STATE_CANCELLED || m_state == PVR_TIMER_STATE_ABORTED)
+ strReturn = g_localizeStrings.Get(13106);
+ else if (m_state == PVR_TIMER_STATE_RECORDING)
+ strReturn = g_localizeStrings.Get(19162);
+ else if (m_state == PVR_TIMER_STATE_CONFLICT_OK)
+ strReturn = g_localizeStrings.Get(19275);
+ else if (m_state == PVR_TIMER_STATE_CONFLICT_NOK)
+ strReturn = g_localizeStrings.Get(19276);
+ else if (m_state == PVR_TIMER_STATE_ERROR)
+ strReturn = g_localizeStrings.Get(257);
+ else if (m_state == PVR_TIMER_STATE_DISABLED)
+ strReturn = g_localizeStrings.Get(13106);
+ else if (m_state == PVR_TIMER_STATE_COMPLETED)
+ {
+ if ((m_iTVChildTimersRecording > 0 && !bRadio) || (m_iRadioChildTimersRecording > 0 && bRadio))
+ strReturn = g_localizeStrings.Get(19162); // "Recording active"
+ else
+ strReturn = g_localizeStrings.Get(19256); // "Completed"
+ }
+ else if (m_state == PVR_TIMER_STATE_SCHEDULED || m_state == PVR_TIMER_STATE_NEW)
+ {
+ if ((m_iTVChildTimersRecording > 0 && !bRadio) || (m_iRadioChildTimersRecording > 0 && bRadio))
+ strReturn = g_localizeStrings.Get(19162); // "Recording active"
+ else if ((m_iTVChildTimersErrors > 0 && !bRadio) || (m_iRadioChildTimersErrors > 0 && bRadio))
+ strReturn = g_localizeStrings.Get(257); // "Error"
+ else if ((m_iTVChildTimersConflictNOK > 0 && !bRadio) ||
+ (m_iRadioChildTimersConflictNOK > 0 && bRadio))
+ strReturn = g_localizeStrings.Get(19276); // "Conflict error"
+ else if ((m_iTVChildTimersActive > 0 && !bRadio) || (m_iRadioChildTimersActive > 0 && bRadio))
+ strReturn = StringUtils::Format(g_localizeStrings.Get(19255),
+ bRadio ? m_iRadioChildTimersActive
+ : m_iTVChildTimersActive); // "{} scheduled"
+ }
+
+ return strReturn;
+}
+
+/**
+ * Get the type string of this timer
+ */
+std::string CPVRTimerInfoTag::GetTypeAsString() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_timerType->GetDescription();
+}
+
+namespace
+{
+void AppendDay(std::string& strReturn, unsigned int iId)
+{
+ if (!strReturn.empty())
+ strReturn += "-";
+
+ if (iId > 0)
+ strReturn += g_localizeStrings.Get(iId).c_str();
+ else
+ strReturn += "__";
+}
+} // unnamed namespace
+
+std::string CPVRTimerInfoTag::GetWeekdaysString(unsigned int iWeekdays,
+ bool bEpgBased,
+ bool bLongMultiDaysFormat)
+{
+ std::string strReturn;
+
+ if (iWeekdays == PVR_WEEKDAY_NONE)
+ return strReturn;
+ else if (iWeekdays == PVR_WEEKDAY_ALLDAYS)
+ strReturn = bEpgBased ? g_localizeStrings.Get(807) // "Any day"
+ : g_localizeStrings.Get(808); // "Every day"
+ else if (iWeekdays == PVR_WEEKDAY_MONDAY)
+ strReturn = g_localizeStrings.Get(831); // "Mondays"
+ else if (iWeekdays == PVR_WEEKDAY_TUESDAY)
+ strReturn = g_localizeStrings.Get(832); // "Tuesdays"
+ else if (iWeekdays == PVR_WEEKDAY_WEDNESDAY)
+ strReturn = g_localizeStrings.Get(833); // "Wednesdays"
+ else if (iWeekdays == PVR_WEEKDAY_THURSDAY)
+ strReturn = g_localizeStrings.Get(834); // "Thursdays"
+ else if (iWeekdays == PVR_WEEKDAY_FRIDAY)
+ strReturn = g_localizeStrings.Get(835); // "Fridays"
+ else if (iWeekdays == PVR_WEEKDAY_SATURDAY)
+ strReturn = g_localizeStrings.Get(836); // "Saturdays"
+ else if (iWeekdays == PVR_WEEKDAY_SUNDAY)
+ strReturn = g_localizeStrings.Get(837); // "Sundays"
+ else
+ {
+ // Any other combination. Assemble custom string.
+ if (iWeekdays & PVR_WEEKDAY_MONDAY)
+ AppendDay(strReturn, 19149); // Mo
+ else if (bLongMultiDaysFormat)
+ AppendDay(strReturn, 0);
+
+ if (iWeekdays & PVR_WEEKDAY_TUESDAY)
+ AppendDay(strReturn, 19150); // Tu
+ else if (bLongMultiDaysFormat)
+ AppendDay(strReturn, 0);
+
+ if (iWeekdays & PVR_WEEKDAY_WEDNESDAY)
+ AppendDay(strReturn, 19151); // We
+ else if (bLongMultiDaysFormat)
+ AppendDay(strReturn, 0);
+
+ if (iWeekdays & PVR_WEEKDAY_THURSDAY)
+ AppendDay(strReturn, 19152); // Th
+ else if (bLongMultiDaysFormat)
+ AppendDay(strReturn, 0);
+
+ if (iWeekdays & PVR_WEEKDAY_FRIDAY)
+ AppendDay(strReturn, 19153); // Fr
+ else if (bLongMultiDaysFormat)
+ AppendDay(strReturn, 0);
+
+ if (iWeekdays & PVR_WEEKDAY_SATURDAY)
+ AppendDay(strReturn, 19154); // Sa
+ else if (bLongMultiDaysFormat)
+ AppendDay(strReturn, 0);
+
+ if (iWeekdays & PVR_WEEKDAY_SUNDAY)
+ AppendDay(strReturn, 19155); // So
+ else if (bLongMultiDaysFormat)
+ AppendDay(strReturn, 0);
+ }
+ return strReturn;
+}
+
+std::string CPVRTimerInfoTag::GetWeekdaysString() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return GetWeekdaysString(m_iWeekdays, m_timerType->IsEpgBased(), false);
+}
+
+bool CPVRTimerInfoTag::IsOwnedByClient() const
+{
+ return m_timerType->GetClientId() > -1;
+}
+
+bool CPVRTimerInfoTag::AddToClient() const
+{
+ const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId);
+ if (client)
+ return client->AddTimer(*this) == PVR_ERROR_NO_ERROR;
+ return false;
+}
+
+TimerOperationResult CPVRTimerInfoTag::DeleteFromClient(bool bForce /* = false */) const
+{
+ const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId);
+ PVR_ERROR error = PVR_ERROR_UNKNOWN;
+
+ if (client)
+ error = client->DeleteTimer(*this, bForce);
+
+ if (error == PVR_ERROR_RECORDING_RUNNING)
+ return TimerOperationResult::RECORDING;
+
+ return (error == PVR_ERROR_NO_ERROR) ? TimerOperationResult::OK : TimerOperationResult::FAILED;
+}
+
+bool CPVRTimerInfoTag::Persist()
+{
+ const std::shared_ptr<CPVRDatabase> database = CServiceBroker::GetPVRManager().GetTVDatabase();
+ if (database)
+ return database->Persist(*this);
+
+ return false;
+}
+
+bool CPVRTimerInfoTag::DeleteFromDatabase()
+{
+ const std::shared_ptr<CPVRDatabase> database = CServiceBroker::GetPVRManager().GetTVDatabase();
+ if (database)
+ return database->Delete(*this);
+
+ return false;
+}
+
+bool CPVRTimerInfoTag::UpdateEntry(const std::shared_ptr<CPVRTimerInfoTag>& tag)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ m_iClientId = tag->m_iClientId;
+ m_iClientIndex = tag->m_iClientIndex;
+ m_iParentClientIndex = tag->m_iParentClientIndex;
+ m_strTitle = tag->m_strTitle;
+ m_strEpgSearchString = tag->m_strEpgSearchString;
+ m_bFullTextEpgSearch = tag->m_bFullTextEpgSearch;
+ m_strDirectory = tag->m_strDirectory;
+ m_iClientChannelUid = tag->m_iClientChannelUid;
+ m_StartTime = tag->m_StartTime;
+ m_StopTime = tag->m_StopTime;
+ m_bStartAnyTime = tag->m_bStartAnyTime;
+ m_bEndAnyTime = tag->m_bEndAnyTime;
+ m_FirstDay = tag->m_FirstDay;
+ m_iPriority = tag->m_iPriority;
+ m_iLifetime = tag->m_iLifetime;
+ m_iMaxRecordings = tag->m_iMaxRecordings;
+ m_state = tag->m_state;
+ m_iPreventDupEpisodes = tag->m_iPreventDupEpisodes;
+ m_iRecordingGroup = tag->m_iRecordingGroup;
+ m_iWeekdays = tag->m_iWeekdays;
+ m_bIsRadio = tag->m_bIsRadio;
+ m_iMarginStart = tag->m_iMarginStart;
+ m_iMarginEnd = tag->m_iMarginEnd;
+ m_strSeriesLink = tag->m_strSeriesLink;
+ m_iEpgUid = tag->m_iEpgUid;
+ m_epgTag = tag->m_epgTag;
+ m_strSummary = tag->m_strSummary;
+ m_channel = tag->m_channel;
+ m_bProbedEpgTag = tag->m_bProbedEpgTag;
+
+ m_iTVChildTimersActive = tag->m_iTVChildTimersActive;
+ m_iTVChildTimersConflictNOK = tag->m_iTVChildTimersConflictNOK;
+ m_iTVChildTimersRecording = tag->m_iTVChildTimersRecording;
+ m_iTVChildTimersErrors = tag->m_iTVChildTimersErrors;
+ m_iRadioChildTimersActive = tag->m_iRadioChildTimersActive;
+ m_iRadioChildTimersConflictNOK = tag->m_iRadioChildTimersConflictNOK;
+ m_iRadioChildTimersRecording = tag->m_iRadioChildTimersRecording;
+ m_iRadioChildTimersErrors = tag->m_iRadioChildTimersErrors;
+
+ SetTimerType(tag->m_timerType);
+
+ if (m_strSummary.empty())
+ UpdateSummary();
+
+ UpdateEpgInfoTag();
+
+ return true;
+}
+
+bool CPVRTimerInfoTag::UpdateChildState(const std::shared_ptr<CPVRTimerInfoTag>& childTimer,
+ bool bAdd)
+{
+ if (!childTimer || childTimer->m_iParentClientIndex != m_iClientIndex)
+ return false;
+
+ int iDelta = bAdd ? +1 : -1;
+ switch (childTimer->m_state)
+ {
+ case PVR_TIMER_STATE_NEW:
+ case PVR_TIMER_STATE_SCHEDULED:
+ case PVR_TIMER_STATE_CONFLICT_OK:
+ if (childTimer->m_bIsRadio)
+ m_iRadioChildTimersActive += iDelta;
+ else
+ m_iTVChildTimersActive += iDelta;
+ break;
+ case PVR_TIMER_STATE_RECORDING:
+ if (childTimer->m_bIsRadio)
+ {
+ m_iRadioChildTimersActive += iDelta;
+ m_iRadioChildTimersRecording += iDelta;
+ }
+ else
+ {
+ m_iTVChildTimersActive += iDelta;
+ m_iTVChildTimersRecording += iDelta;
+ }
+ break;
+ case PVR_TIMER_STATE_CONFLICT_NOK:
+ if (childTimer->m_bIsRadio)
+ m_iRadioChildTimersConflictNOK += iDelta;
+ else
+ m_iTVChildTimersConflictNOK += iDelta;
+ break;
+ case PVR_TIMER_STATE_ERROR:
+ if (childTimer->m_bIsRadio)
+ m_iRadioChildTimersErrors += iDelta;
+ else
+ m_iTVChildTimersErrors += iDelta;
+ break;
+ case PVR_TIMER_STATE_COMPLETED:
+ case PVR_TIMER_STATE_ABORTED:
+ case PVR_TIMER_STATE_CANCELLED:
+ case PVR_TIMER_STATE_DISABLED:
+ //these are not the child timers we are looking for
+ break;
+ }
+ return true;
+}
+
+void CPVRTimerInfoTag::ResetChildState()
+{
+ m_iTVChildTimersActive = 0;
+ m_iTVChildTimersRecording = 0;
+ m_iTVChildTimersConflictNOK = 0;
+ m_iTVChildTimersErrors = 0;
+ m_iRadioChildTimersActive = 0;
+ m_iRadioChildTimersRecording = 0;
+ m_iRadioChildTimersConflictNOK = 0;
+ m_iRadioChildTimersErrors = 0;
+}
+
+bool CPVRTimerInfoTag::UpdateOnClient()
+{
+ const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId);
+ return client && (client->UpdateTimer(*this) == PVR_ERROR_NO_ERROR);
+}
+
+std::string CPVRTimerInfoTag::ChannelName() const
+{
+ std::string strReturn;
+ std::shared_ptr<CPVRChannel> channeltag = Channel();
+ if (channeltag)
+ strReturn = channeltag->ChannelName();
+ else if (m_timerType->IsEpgBasedTimerRule())
+ strReturn = StringUtils::Format("({})", g_localizeStrings.Get(809)); // "Any channel"
+
+ return strReturn;
+}
+
+std::string CPVRTimerInfoTag::ChannelIcon() const
+{
+ std::string strReturn;
+ std::shared_ptr<CPVRChannel> channeltag = Channel();
+ if (channeltag)
+ strReturn = channeltag->IconPath();
+ return strReturn;
+}
+
+std::shared_ptr<CPVRTimerInfoTag> CPVRTimerInfoTag::CreateReminderFromDate(
+ const CDateTime& start,
+ int iDuration,
+ const std::shared_ptr<CPVRTimerInfoTag>& parent /* = std::shared_ptr<CPVRTimerInfoTag>() */)
+{
+ bool bReadOnly = !!parent; // children of reminder rules are always read-only
+ std::shared_ptr<CPVRTimerInfoTag> newTimer =
+ CreateFromDate(parent->Channel(), start, iDuration, true, bReadOnly);
+ if (newTimer && parent)
+ {
+ // set parent
+ newTimer->m_iParentClientIndex = parent->m_iClientIndex;
+
+ // set relevant props for the new one-time reminder timer
+ newTimer->m_strTitle = parent->Title();
+ }
+ return newTimer;
+}
+
+static const time_t INSTANT_TIMER_START =
+ 0; // PVR addon API: special start time value to denote an instant timer
+
+std::shared_ptr<CPVRTimerInfoTag> CPVRTimerInfoTag::CreateInstantTimerTag(
+ const std::shared_ptr<CPVRChannel>& channel,
+ int iDuration /* = DEFAULT_PVRRECORD_INSTANTRECORDTIME */)
+{
+ return CreateFromDate(channel, CDateTime(INSTANT_TIMER_START), iDuration, false, false);
+}
+
+std::shared_ptr<CPVRTimerInfoTag> CPVRTimerInfoTag::CreateTimerTag(
+ const std::shared_ptr<CPVRChannel>& channel, const CDateTime& start, int iDuration)
+{
+ return CreateFromDate(channel, start, iDuration, false, false);
+}
+
+std::shared_ptr<CPVRTimerInfoTag> CPVRTimerInfoTag::CreateFromDate(
+ const std::shared_ptr<CPVRChannel>& channel,
+ const CDateTime& start,
+ int iDuration,
+ bool bCreateReminder,
+ bool bReadOnly)
+{
+ if (!channel)
+ {
+ CLog::LogF(LOGERROR, "No channel");
+ return std::shared_ptr<CPVRTimerInfoTag>();
+ }
+
+ bool bInstantStart = (start == CDateTime(INSTANT_TIMER_START));
+
+ std::shared_ptr<CPVREpgInfoTag> epgTag;
+ if (!bCreateReminder) // time-based reminders never have epg tags
+ {
+ if (bInstantStart)
+ epgTag = channel->GetEPGNow();
+ else if (channel->GetEPG())
+ epgTag = channel->GetEPG()->GetTagBetween(start, start + CDateTimeSpan(0, 0, iDuration, 0));
+ }
+
+ std::shared_ptr<CPVRTimerInfoTag> newTimer;
+ if (epgTag)
+ {
+ if (epgTag->IsRecordable())
+ {
+ newTimer = CreateFromEpg(epgTag, false, bCreateReminder, bReadOnly);
+ }
+ else
+ {
+ CLog::LogF(LOGERROR, "EPG tag is not recordable");
+ return std::shared_ptr<CPVRTimerInfoTag>();
+ }
+ }
+
+ if (!newTimer)
+ {
+ newTimer.reset(new CPVRTimerInfoTag);
+
+ newTimer->m_iClientIndex = PVR_TIMER_NO_CLIENT_INDEX;
+ newTimer->m_iParentClientIndex = PVR_TIMER_NO_PARENT;
+ newTimer->m_channel = channel;
+ newTimer->m_strTitle = channel->ChannelName();
+ newTimer->m_iClientChannelUid = channel->UniqueID();
+ newTimer->m_iClientId = channel->ClientID();
+ newTimer->m_bIsRadio = channel->IsRadio();
+
+ uint64_t iMustHaveAttribs = PVR_TIMER_TYPE_IS_MANUAL;
+ if (bCreateReminder)
+ iMustHaveAttribs |= PVR_TIMER_TYPE_IS_REMINDER;
+ if (bReadOnly)
+ iMustHaveAttribs |= PVR_TIMER_TYPE_IS_READONLY;
+
+ // timertype: manual one-shot timer for given client
+ const std::shared_ptr<CPVRTimerType> timerType = CPVRTimerType::CreateFromAttributes(
+ iMustHaveAttribs, PVR_TIMER_TYPE_IS_REPEATING | PVR_TIMER_TYPE_FORBIDS_NEW_INSTANCES,
+ channel->ClientID());
+ if (!timerType)
+ {
+ CLog::LogF(LOGERROR, "Unable to create one shot manual timer type");
+ return std::shared_ptr<CPVRTimerInfoTag>();
+ }
+
+ newTimer->SetTimerType(timerType);
+
+ if (epgTag)
+ newTimer->SetEpgInfoTag(epgTag);
+ else
+ newTimer->UpdateEpgInfoTag();
+ }
+
+ /* no matter the timer was created from an epg tag, overwrite timer start and end times. */
+ CDateTime now(CDateTime::GetUTCDateTime());
+ if (bInstantStart)
+ newTimer->SetStartFromUTC(now);
+ else
+ newTimer->SetStartFromUTC(start);
+
+ if (iDuration == DEFAULT_PVRRECORD_INSTANTRECORDTIME)
+ iDuration = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ CSettings::SETTING_PVRRECORD_INSTANTRECORDTIME);
+
+ if (bInstantStart)
+ {
+ CDateTime endTime = now + CDateTimeSpan(0, 0, iDuration ? iDuration : 120, 0);
+ newTimer->SetEndFromUTC(endTime);
+ }
+ else
+ {
+ CDateTime endTime = start + CDateTimeSpan(0, 0, iDuration ? iDuration : 120, 0);
+ newTimer->SetEndFromUTC(endTime);
+ }
+
+ /* update summary string according to instant recording start/end time */
+ newTimer->UpdateSummary();
+
+ if (bInstantStart)
+ {
+ // "Instant recording: <summary>
+ newTimer->m_strSummary = StringUtils::Format(g_localizeStrings.Get(19093), newTimer->Summary());
+
+ // now that we have a nice summary, we can set the "special" start time value that indicates an instant recording
+ newTimer->SetStartFromUTC(start);
+ }
+
+ newTimer->m_iMarginStart = 0;
+ newTimer->m_iMarginEnd = 0;
+
+ /* unused only for reference */
+ newTimer->m_strFileNameAndPath = CPVRTimersPath::PATH_NEW;
+
+ return newTimer;
+}
+
+std::shared_ptr<CPVRTimerInfoTag> CPVRTimerInfoTag::CreateReminderFromEpg(
+ const std::shared_ptr<CPVREpgInfoTag>& tag,
+ const std::shared_ptr<CPVRTimerInfoTag>& parent /* = std::shared_ptr<CPVRTimerInfoTag>() */)
+{
+ bool bReadOnly = !!parent; // children of reminder rules are always read-only
+ std::shared_ptr<CPVRTimerInfoTag> newTimer = CreateFromEpg(tag, false, true, bReadOnly);
+ if (newTimer && parent)
+ {
+ // set parent
+ newTimer->m_iParentClientIndex = parent->m_iClientIndex;
+
+ // set relevant props for the new one-time reminder timer (everything not epg-related)
+ newTimer->m_iMarginStart = parent->m_iMarginStart;
+ newTimer->m_iMarginEnd = parent->m_iMarginEnd;
+ }
+ return newTimer;
+}
+
+std::shared_ptr<CPVRTimerInfoTag> CPVRTimerInfoTag::CreateFromEpg(
+ const std::shared_ptr<CPVREpgInfoTag>& tag, bool bCreateRule /* = false */)
+{
+ return CreateFromEpg(tag, bCreateRule, false, false);
+}
+
+std::shared_ptr<CPVRTimerInfoTag> CPVRTimerInfoTag::CreateFromEpg(
+ const std::shared_ptr<CPVREpgInfoTag>& tag,
+ bool bCreateRule,
+ bool bCreateReminder,
+ bool bReadOnly /* = false */)
+{
+ std::shared_ptr<CPVRTimerInfoTag> newTag(new CPVRTimerInfoTag());
+
+ /* check if a valid channel is set */
+ const std::shared_ptr<CPVRChannel> channel =
+ CServiceBroker::GetPVRManager().ChannelGroups()->GetChannelForEpgTag(tag);
+ if (!channel)
+ {
+ CLog::LogF(LOGERROR, "EPG tag has no channel");
+ return std::shared_ptr<CPVRTimerInfoTag>();
+ }
+
+ newTag->m_iClientIndex = PVR_TIMER_NO_CLIENT_INDEX;
+ newTag->m_iParentClientIndex = PVR_TIMER_NO_PARENT;
+ if (!CServiceBroker::GetPVRManager().IsParentalLocked(tag))
+ newTag->m_strTitle = tag->Title();
+ if (newTag->m_strTitle.empty())
+ newTag->m_strTitle = channel->ChannelName();
+ newTag->m_iClientChannelUid = channel->UniqueID();
+ newTag->m_iClientId = channel->ClientID();
+ newTag->m_bIsRadio = channel->IsRadio();
+ newTag->m_channel = channel;
+ newTag->m_strSeriesLink = tag->SeriesLink();
+ newTag->m_iEpgUid = tag->UniqueBroadcastID();
+ newTag->SetStartFromUTC(tag->StartAsUTC());
+ newTag->SetEndFromUTC(tag->EndAsUTC());
+
+ const uint64_t iMustNotHaveAttribs = PVR_TIMER_TYPE_IS_MANUAL |
+ PVR_TIMER_TYPE_FORBIDS_NEW_INSTANCES |
+ PVR_TIMER_TYPE_FORBIDS_EPG_TAG_ON_CREATE;
+ std::shared_ptr<CPVRTimerType> timerType;
+ if (bCreateRule)
+ {
+ // create epg-based timer rule, prefer rule using series link if available.
+
+ uint64_t iMustHaveAttribs = PVR_TIMER_TYPE_IS_REPEATING;
+ if (bCreateReminder)
+ iMustHaveAttribs |= PVR_TIMER_TYPE_IS_REMINDER;
+ if (bReadOnly)
+ iMustHaveAttribs |= PVR_TIMER_TYPE_IS_READONLY;
+
+ if (!tag->SeriesLink().empty())
+ timerType = CPVRTimerType::CreateFromAttributes(
+ iMustHaveAttribs | PVR_TIMER_TYPE_REQUIRES_EPG_SERIESLINK_ON_CREATE, iMustNotHaveAttribs,
+ channel->ClientID());
+ if (!timerType)
+ timerType = CPVRTimerType::CreateFromAttributes(
+ iMustHaveAttribs, iMustNotHaveAttribs | PVR_TIMER_TYPE_REQUIRES_EPG_SERIESLINK_ON_CREATE,
+ channel->ClientID());
+ if (timerType)
+ {
+ if (timerType->SupportsEpgTitleMatch())
+ newTag->m_strEpgSearchString = newTag->m_strTitle;
+
+ if (timerType->SupportsWeekdays())
+ newTag->m_iWeekdays = PVR_WEEKDAY_ALLDAYS;
+
+ if (timerType->SupportsStartAnyTime())
+ newTag->m_bStartAnyTime = true;
+
+ if (timerType->SupportsEndAnyTime())
+ newTag->m_bEndAnyTime = true;
+ }
+ }
+ else
+ {
+ // create one-shot epg-based timer
+
+ uint64_t iMustHaveAttribs = PVR_TIMER_TYPE_ATTRIBUTE_NONE;
+ if (bCreateReminder)
+ iMustHaveAttribs |= PVR_TIMER_TYPE_IS_REMINDER;
+ if (bReadOnly)
+ iMustHaveAttribs |= PVR_TIMER_TYPE_IS_READONLY;
+
+ timerType = CPVRTimerType::CreateFromAttributes(
+ iMustHaveAttribs, PVR_TIMER_TYPE_IS_REPEATING | iMustNotHaveAttribs, channel->ClientID());
+ }
+
+ if (!timerType)
+ {
+ CLog::LogF(LOGERROR, "Unable to create any epg-based timer type");
+ return std::shared_ptr<CPVRTimerInfoTag>();
+ }
+
+ newTag->SetTimerType(timerType);
+ newTag->UpdateSummary();
+ newTag->SetEpgInfoTag(tag);
+
+ /* unused only for reference */
+ newTag->m_strFileNameAndPath = CPVRTimersPath::PATH_NEW;
+
+ return newTag;
+}
+
+//! @todo CDateTime class does not handle daylight saving timezone bias correctly (and cannot easily
+// be changed to do so due to performance and platform specific issues). In most cases this only
+// causes GUI presentation glitches, but reminder timer rules rely on correct local time values.
+
+namespace
+{
+#define IsLeapYear(y) ((y % 4 == 0) && (y % 100 != 0 || y % 400 == 0))
+
+int days_from_0(int year)
+{
+ year--;
+ return 365 * year + (year / 400) - (year / 100) + (year / 4);
+}
+
+int days_from_1970(int32_t year)
+{
+ static const int days_from_0_to_1970 = days_from_0(1970);
+ return days_from_0(year) - days_from_0_to_1970;
+}
+
+int days_from_1jan(int year, int month, int day)
+{
+ static const int days[2][12] = {{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
+ {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}};
+ return days[IsLeapYear(year)][month - 1] + day - 1;
+}
+
+time_t mytimegm(struct tm* time)
+{
+ int year = time->tm_year + 1900;
+ int month = time->tm_mon;
+
+ if (month > 11)
+ {
+ year += month / 12;
+ month %= 12;
+ }
+ else if (month < 0)
+ {
+ int years_diff = (-month + 11) / 12;
+ year -= years_diff;
+ month += 12 * years_diff;
+ }
+
+ month++;
+
+ int day_of_year = days_from_1jan(year, month, time->tm_mday);
+ int days_since_epoch = days_from_1970(year) + day_of_year;
+
+ return 3600 * 24 * days_since_epoch + 3600 * time->tm_hour + 60 * time->tm_min + time->tm_sec;
+}
+} // namespace
+
+CDateTime CPVRTimerInfoTag::ConvertUTCToLocalTime(const CDateTime& utc)
+{
+ time_t time = 0;
+ utc.GetAsTime(time);
+
+ struct tm* tms;
+#ifdef HAVE_LOCALTIME_R
+ struct tm gbuf;
+ tms = localtime_r(&time, &gbuf);
+#else
+ tms = localtime(&time);
+#endif
+
+ if (!tms)
+ {
+ CLog::LogF(LOGWARNING, "localtime() returned NULL!");
+ return {};
+ }
+
+ return CDateTime(mytimegm(tms));
+}
+
+CDateTime CPVRTimerInfoTag::ConvertLocalTimeToUTC(const CDateTime& local)
+{
+ time_t time = 0;
+ local.GetAsTime(time);
+
+ struct tm* tms;
+
+ // obtain dst flag for given datetime
+#ifdef HAVE_LOCALTIME_R
+ struct tm loc_buf;
+ tms = localtime_r(&time, &loc_buf);
+#else
+ tms = localtime(&time);
+#endif
+
+ if (!tms)
+ {
+ CLog::LogF(LOGWARNING, "localtime() returned NULL!");
+ return {};
+ }
+
+ int isdst = tms->tm_isdst;
+
+#ifdef HAVE_GMTIME_R
+ struct tm gm_buf;
+ tms = gmtime_r(&time, &gm_buf);
+#else
+ tms = gmtime(&time);
+#endif
+
+ if (!tms)
+ {
+ CLog::LogF(LOGWARNING, "gmtime() returned NULL!");
+ return {};
+ }
+
+ tms->tm_isdst = isdst;
+ return CDateTime(mktime(tms));
+}
+
+CDateTime CPVRTimerInfoTag::StartAsUTC() const
+{
+ return m_StartTime;
+}
+
+CDateTime CPVRTimerInfoTag::StartAsLocalTime() const
+{
+ return ConvertUTCToLocalTime(m_StartTime);
+}
+
+void CPVRTimerInfoTag::SetStartFromUTC(const CDateTime& start)
+{
+ m_StartTime = start;
+}
+
+void CPVRTimerInfoTag::SetStartFromLocalTime(const CDateTime& start)
+{
+ m_StartTime = ConvertLocalTimeToUTC(start);
+}
+
+CDateTime CPVRTimerInfoTag::EndAsUTC() const
+{
+ return m_StopTime;
+}
+
+CDateTime CPVRTimerInfoTag::EndAsLocalTime() const
+{
+ return ConvertUTCToLocalTime(m_StopTime);
+}
+
+void CPVRTimerInfoTag::SetEndFromUTC(const CDateTime& end)
+{
+ m_StopTime = end;
+}
+
+void CPVRTimerInfoTag::SetEndFromLocalTime(const CDateTime& end)
+{
+ m_StopTime = ConvertLocalTimeToUTC(end);
+}
+
+int CPVRTimerInfoTag::GetDuration() const
+{
+ time_t start, end;
+ m_StartTime.GetAsTime(start);
+ m_StopTime.GetAsTime(end);
+ return end - start > 0 ? end - start : 3600;
+}
+
+CDateTime CPVRTimerInfoTag::FirstDayAsUTC() const
+{
+ return m_FirstDay;
+}
+
+CDateTime CPVRTimerInfoTag::FirstDayAsLocalTime() const
+{
+ return ConvertUTCToLocalTime(m_FirstDay);
+}
+
+void CPVRTimerInfoTag::SetFirstDayFromUTC(const CDateTime& firstDay)
+{
+ m_FirstDay = firstDay;
+}
+
+void CPVRTimerInfoTag::SetFirstDayFromLocalTime(const CDateTime& firstDay)
+{
+ m_FirstDay = ConvertLocalTimeToUTC(firstDay);
+}
+
+std::string CPVRTimerInfoTag::GetNotificationText() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ int stringID = 0;
+
+ switch (m_state)
+ {
+ case PVR_TIMER_STATE_ABORTED:
+ case PVR_TIMER_STATE_CANCELLED:
+ stringID = 19224; // Recording aborted
+ break;
+ case PVR_TIMER_STATE_SCHEDULED:
+ if (IsTimerRule())
+ stringID = 19058; // Timer enabled
+ else
+ stringID = 19225; // Recording scheduled
+ break;
+ case PVR_TIMER_STATE_RECORDING:
+ stringID = 19226; // Recording started
+ break;
+ case PVR_TIMER_STATE_COMPLETED:
+ stringID = 19227; // Recording completed
+ break;
+ case PVR_TIMER_STATE_CONFLICT_OK:
+ case PVR_TIMER_STATE_CONFLICT_NOK:
+ stringID = 19277; // Recording conflict
+ break;
+ case PVR_TIMER_STATE_ERROR:
+ stringID = 19278; // Recording error
+ break;
+ case PVR_TIMER_STATE_DISABLED:
+ stringID = 19057; // Timer disabled
+ break;
+ default:
+ break;
+ }
+
+ if (stringID != 0)
+ return StringUtils::Format("{}: '{}'", g_localizeStrings.Get(stringID), m_strTitle);
+
+ return {};
+}
+
+std::string CPVRTimerInfoTag::GetDeletedNotificationText() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ int stringID = 0;
+ // The state in this case is the state the timer had when it was last seen
+ switch (m_state)
+ {
+ case PVR_TIMER_STATE_RECORDING:
+ stringID = 19227; // Recording completed
+ break;
+ case PVR_TIMER_STATE_SCHEDULED:
+ default:
+ if (IsTimerRule())
+ stringID = 828; // Timer rule deleted
+ else
+ stringID = 19228; // Timer deleted
+ }
+
+ return StringUtils::Format("{}: '{}'", g_localizeStrings.Get(stringID), m_strTitle);
+}
+
+void CPVRTimerInfoTag::SetEpgInfoTag(const std::shared_ptr<CPVREpgInfoTag>& tag)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_epgTag = tag;
+ m_bProbedEpgTag = true;
+}
+
+void CPVRTimerInfoTag::UpdateEpgInfoTag()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_epgTag.reset();
+ m_bProbedEpgTag = false;
+ GetEpgInfoTag();
+}
+
+std::shared_ptr<CPVREpgInfoTag> CPVRTimerInfoTag::GetEpgInfoTag(bool bCreate /* = true */) const
+{
+ if (!m_epgTag && !m_bProbedEpgTag && bCreate && CServiceBroker::GetPVRManager().EpgsCreated())
+ {
+ std::shared_ptr<CPVRChannel> channel(m_channel);
+ if (!channel)
+ {
+ channel = CServiceBroker::GetPVRManager()
+ .ChannelGroups()
+ ->Get(m_bIsRadio)
+ ->GetGroupAll()
+ ->GetByUniqueID(m_iClientChannelUid, m_iClientId);
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_channel = channel;
+ }
+
+ if (channel)
+ {
+ const std::shared_ptr<CPVREpg> epg(channel->GetEPG());
+ if (epg)
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (!m_epgTag && m_iEpgUid != EPG_TAG_INVALID_UID)
+ {
+ m_epgTag = epg->GetTagByBroadcastId(m_iEpgUid);
+ }
+
+ if (!m_epgTag && !IsTimerRule() && IsOwnedByClient())
+ {
+ time_t startTime = 0;
+ time_t endTime = 0;
+
+ StartAsUTC().GetAsTime(startTime);
+ if (startTime > 0)
+ EndAsUTC().GetAsTime(endTime);
+
+ if (startTime > 0 && endTime > 0)
+ {
+ // try to fetch missing epg tag from backend
+ m_epgTag = epg->GetTagBetween(StartAsUTC() - CDateTimeSpan(0, 0, 2, 0),
+ EndAsUTC() + CDateTimeSpan(0, 0, 2, 0), true);
+ if (m_epgTag)
+ m_iEpgUid = m_epgTag->UniqueBroadcastID();
+ }
+ }
+ }
+ }
+ m_bProbedEpgTag = true;
+ }
+ return m_epgTag;
+}
+
+bool CPVRTimerInfoTag::HasChannel() const
+{
+ return m_channel.get() != nullptr;
+}
+
+std::shared_ptr<CPVRChannel> CPVRTimerInfoTag::Channel() const
+{
+ return m_channel;
+}
+
+void CPVRTimerInfoTag::UpdateChannel()
+{
+ const std::shared_ptr<CPVRChannel> channel(CServiceBroker::GetPVRManager()
+ .ChannelGroups()
+ ->Get(m_bIsRadio)
+ ->GetGroupAll()
+ ->GetByUniqueID(m_iClientChannelUid, m_iClientId));
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_channel = channel;
+}
+
+const std::string& CPVRTimerInfoTag::Title() const
+{
+ return m_strTitle;
+}
+
+const std::string& CPVRTimerInfoTag::Summary() const
+{
+ return m_strSummary;
+}
+
+const std::string& CPVRTimerInfoTag::Path() const
+{
+ return m_strFileNameAndPath;
+}
+
+const std::string& CPVRTimerInfoTag::SeriesLink() const
+{
+ return m_strSeriesLink;
+}
diff --git a/xbmc/pvr/timers/PVRTimerInfoTag.h b/xbmc/pvr/timers/PVRTimerInfoTag.h
new file mode 100644
index 0000000..ad2d3d8
--- /dev/null
+++ b/xbmc/pvr/timers/PVRTimerInfoTag.h
@@ -0,0 +1,644 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "XBDateTime.h"
+#include "pvr/timers/PVRTimerType.h"
+#include "threads/CriticalSection.h"
+#include "utils/ISerializable.h"
+
+#include <memory>
+#include <string>
+
+struct PVR_TIMER;
+
+namespace PVR
+{
+class CPVRChannel;
+class CPVREpgInfoTag;
+
+enum class TimerOperationResult
+{
+ OK = 0,
+ FAILED,
+ RECORDING // The timer was not deleted because it is currently recording (see DeleteTimer).
+};
+
+class CPVRTimerInfoTag final : public ISerializable
+{
+ // allow these classes direct access to members as they act as timer tag instance factories.
+ friend class CGUIDialogPVRTimerSettings;
+ friend class CPVRDatabase;
+
+public:
+ explicit CPVRTimerInfoTag(bool bRadio = false);
+ CPVRTimerInfoTag(const PVR_TIMER& timer,
+ const std::shared_ptr<CPVRChannel>& channel,
+ unsigned int iClientId);
+
+ bool operator==(const CPVRTimerInfoTag& right) const;
+ bool operator!=(const CPVRTimerInfoTag& right) const;
+
+ /*!
+ * @brief Copy over data to the given PVR_TIMER instance.
+ * @param timer The timer instance to fill.
+ */
+ void FillAddonData(PVR_TIMER& timer) const;
+
+ // ISerializable implementation
+ void Serialize(CVariant& value) const override;
+
+ static constexpr int DEFAULT_PVRRECORD_INSTANTRECORDTIME = -1;
+
+ /*!
+ * @brief create a tag for an instant timer for a given channel
+ * @param channel is the channel the instant timer is to be created for
+ * @param iDuration is the duration for the instant timer, DEFAULT_PVRRECORD_INSTANTRECORDTIME
+ * denotes system default (setting value)
+ * @return the timer or null if timer could not be created
+ */
+ static std::shared_ptr<CPVRTimerInfoTag> CreateInstantTimerTag(
+ const std::shared_ptr<CPVRChannel>& channel,
+ int iDuration = DEFAULT_PVRRECORD_INSTANTRECORDTIME);
+
+ /*!
+ * @brief create a tag for a timer for a given channel at given time for given duration
+ * @param channel is the channel the timer is to be created for
+ * @param start is the start time for the recording
+ * @param iDuration is the duration of the recording
+ * @return the timer or null if timer could not be created
+ */
+ static std::shared_ptr<CPVRTimerInfoTag> CreateTimerTag(
+ const std::shared_ptr<CPVRChannel>& channel, const CDateTime& start, int iDuration);
+
+ /*!
+ * @brief create a recording or reminder timer or timer rule for the given epg info tag.
+ * @param tag the epg info tag
+ * @param bCreateRule if true, create a timer rule, create a one shot timer otherwise
+ * @param bCreateReminder if true, create a reminder timer or rule, create a recording timer
+ * or rule otherwise
+ * @param bReadOnly whether the timer/rule is read only
+ * @return the timer or null if timer could not be created
+ */
+ static std::shared_ptr<CPVRTimerInfoTag> CreateFromEpg(const std::shared_ptr<CPVREpgInfoTag>& tag,
+ bool bCreateRule,
+ bool bCreateReminder,
+ bool bReadOnly = false);
+
+ /*!
+ * @brief create a timer or timer rule for the given epg info tag.
+ * @param tag the epg info tag
+ * @param bCreateRule if true, create a timer rule, create a one shot timer otherwise
+ * @return the timer or null if timer could not be created
+ */
+ static std::shared_ptr<CPVRTimerInfoTag> CreateFromEpg(const std::shared_ptr<CPVREpgInfoTag>& tag,
+ bool bCreateRule = false);
+
+ /*!
+ * @brief create a reminder timer for the given start date.
+ * @param start the start date
+ * @param iDuration the duration the reminder is valid
+ * @param parent If non-zero, the new timer will be made a child of the given timer rule
+ * @return the timer or null if timer could not be created
+ */
+ static std::shared_ptr<CPVRTimerInfoTag> CreateReminderFromDate(
+ const CDateTime& start,
+ int iDuration,
+ const std::shared_ptr<CPVRTimerInfoTag>& parent = std::shared_ptr<CPVRTimerInfoTag>());
+
+ /*!
+ * @brief create a reminder timer for the given epg info tag.
+ * @param tag the epg info tag
+ * @param parent If non-zero, the new timer will be made a child of the given timer rule
+ * @return the timer or null if timer could not be created
+ */
+ static std::shared_ptr<CPVRTimerInfoTag> CreateReminderFromEpg(
+ const std::shared_ptr<CPVREpgInfoTag>& tag,
+ const std::shared_ptr<CPVRTimerInfoTag>& parent = std::shared_ptr<CPVRTimerInfoTag>());
+
+ /*!
+ * @brief Associate the given epg tag with this timer.
+ * @param tag The epg tag to assign.
+ */
+ void SetEpgInfoTag(const std::shared_ptr<CPVREpgInfoTag>& tag);
+
+ /*!
+ * @brief get the epg info tag associated with this timer, if any
+ * @param bCreate if true, try to find the epg tag if not yet set (lazy evaluation)
+ * @return the epg info tag associated with this timer or null if there is no tag
+ */
+ std::shared_ptr<CPVREpgInfoTag> GetEpgInfoTag(bool bCreate = true) const;
+
+ /*!
+ * @brief updates this timer excluding the state of any children.
+ * @param tag A timer containing the data that shall be merged into this timer's data.
+ * @return true if the timer was updated successfully
+ */
+ bool UpdateEntry(const std::shared_ptr<CPVRTimerInfoTag>& tag);
+
+ /*!
+ * @brief merge in the state of this child timer.
+ * @param childTimer The child timer
+ * @param bAdd If true, add child's data to parent's state, otherwise subtract.
+ * @return true if the child timer's state was merged successfully
+ */
+ bool UpdateChildState(const std::shared_ptr<CPVRTimerInfoTag>& childTimer, bool bAdd);
+
+ /*!
+ * @brief reset the state of children related to this timer.
+ */
+ void ResetChildState();
+
+ /*!
+ * @brief Whether this timer is active.
+ * @return True if this timer is active, false otherwise.
+ */
+ bool IsActive() const
+ {
+ return m_state == PVR_TIMER_STATE_SCHEDULED || m_state == PVR_TIMER_STATE_RECORDING ||
+ m_state == PVR_TIMER_STATE_CONFLICT_OK || m_state == PVR_TIMER_STATE_CONFLICT_NOK ||
+ m_state == PVR_TIMER_STATE_ERROR;
+ }
+
+ /*!
+ * @brief Whether this timer is broken.
+ * @return True if this timer won't result in a recording because it is broken for some reason,
+ * false otherwise
+ */
+ bool IsBroken() const
+ {
+ return m_state == PVR_TIMER_STATE_CONFLICT_NOK || m_state == PVR_TIMER_STATE_ERROR;
+ }
+
+ /*!
+ * @brief Whether this timer has a conflict.
+ * @return True if this timer won't result in a recording because it is in conflict with another
+ * timer or live stream, false otherwise.
+ */
+ bool HasConflict() const { return m_state == PVR_TIMER_STATE_CONFLICT_NOK; }
+
+ /*!
+ * @brief Whether this timer is currently recording.
+ * @return True if recording, false otherwise.
+ */
+ bool IsRecording() const { return m_state == PVR_TIMER_STATE_RECORDING; }
+
+ /*!
+ * @brief Whether this timer is disabled, for example by the user.
+ * @return True if disabled, false otherwise.
+ */
+ bool IsDisabled() const { return m_state == PVR_TIMER_STATE_DISABLED; }
+
+ /*!
+ * @brief Gets the type of this timer.
+ * @return the timer type or NULL if this tag has no timer type.
+ */
+ const std::shared_ptr<CPVRTimerType> GetTimerType() const { return m_timerType; }
+
+ /*!
+ * @brief Sets the type of this timer.
+ * @param the new timer type.
+ */
+ void SetTimerType(const std::shared_ptr<CPVRTimerType>& type);
+
+ /*!
+ * @brief Checks whether this is a timer rule (vs. one time timer).
+ * @return True if this is a timer rule, false otherwise.
+ */
+ bool IsTimerRule() const { return m_timerType && m_timerType->IsTimerRule(); }
+
+ /*!
+ * @brief Checks whether this is a reminder timer (vs. recording timer).
+ * @return True if this is a reminder timer, false otherwise.
+ */
+ bool IsReminder() const { return m_timerType && m_timerType->IsReminder(); }
+
+ /*!
+ * @brief Checks whether this is a manual (vs. epg-based) timer.
+ * @return True if this is a manual timer, false otherwise.
+ */
+ bool IsManual() const { return m_timerType && m_timerType->IsManual(); }
+
+ /*!
+ * @brief Checks whether this is an epg-based (vs. manual) timer.
+ * @return True if this is an epg-Based timer, false otherwise.
+ */
+ bool IsEpgBased() const { return !IsManual(); }
+
+ /*!
+ * @brief The ID of the client for this timer.
+ * @return The client ID or -1 if this is a local timer.
+ */
+ int ClientID() const { return m_iClientId; }
+
+ /*!
+ * @brief Check, whether this timer is owned by a pvr client or by Kodi.
+ * @return True, if owned by a pvr client, false otherwise.
+ */
+ bool IsOwnedByClient() const;
+
+ /*!
+ * @brief Whether this timer is for Radio or TV.
+ * @return True if Radio, false otherwise.
+ */
+ bool IsRadio() const { return m_bIsRadio; }
+
+ /*!
+ * @brief The path that identifies this timer.
+ * @return The path.
+ */
+ const std::string& Path() const;
+
+ /*!
+ * @brief The index for this timer, as given by the client.
+ * @return The client index or PVR_TIMER_NO_CLIENT_INDEX if the timer was just created locally
+ * by Kodi and was not yet added by the client.
+ */
+ int ClientIndex() const { return m_iClientIndex; }
+
+ /*!
+ * @brief The index for the parent of this timer, as given by the client. Timers scheduled by a
+ * timer rule will have a parant index != PVR_TIMER_NO_PARENT.
+ * @return The client index or PVR_TIMER_NO_PARENT if the timer has no parent.
+ */
+ int ParentClientIndex() const { return m_iParentClientIndex; }
+
+ /*!
+ * @brief Whether this timer has a parent.
+ * @return True if timer has a parent, false otherwise.
+ */
+ bool HasParent() const { return m_iParentClientIndex != PVR_TIMER_NO_PARENT; }
+
+ /*!
+ * @brief The local ID for this timer, as given by Kodi.
+ * @return The ID or 0 if not yet set.
+ */
+ unsigned int TimerID() const { return m_iTimerId; }
+
+ /*!
+ * @brief Set the local ID for this timer.
+ * @param id The ID to set.
+ */
+ void SetTimerID(unsigned int id) { m_iTimerId = id; }
+
+ /*!
+ * @brief The UID of the channel for this timer.
+ * @return The channel UID or PVR_CHANNEL_INVALID_UID if not available.
+ */
+ int ClientChannelUID() const { return m_iClientChannelUid; }
+
+ /*!
+ * @brief The state for this timer.
+ * @return The state.
+ */
+ PVR_TIMER_STATE State() const { return m_state; }
+
+ /*!
+ * @brief Set the state for this timer.
+ * @param state The state to set.
+ */
+ void SetState(PVR_TIMER_STATE state) { m_state = state; }
+
+ /*!
+ * @brief The title for this timer.
+ * @return The title.
+ */
+ const std::string& Title() const;
+
+ /*!
+ * @brief Check whether this timer has an associated channel.
+ * @return True if this timer has a channel set, false otherwise.
+ */
+ bool HasChannel() const;
+
+ /*!
+ * @brief Get the channel associated with this timer, if any.
+ * @return the channel or null if non is associated with this timer.
+ */
+ std::shared_ptr<CPVRChannel> Channel() const;
+
+ /*!
+ * @brief Update the channel associated with this timer, based on current client ID and
+ * channel UID.
+ */
+ void UpdateChannel();
+
+ /*!
+ * @brief The name of the channel associated with this timer, if any.
+ * @return The channel name.
+ */
+ std::string ChannelName() const;
+
+ /*!
+ * @brief The path for the channel icon associated with this timer, if any.
+ * @return The channel icon path.
+ */
+ std::string ChannelIcon() const;
+
+ /*!
+ * @brief The start date and time for this timer, as UTC.
+ * @return The start date and time.
+ */
+ CDateTime StartAsUTC() const;
+
+ /*!
+ * @brief The start date and time for this timer, as local time.
+ * @return The start date and time.
+ */
+ CDateTime StartAsLocalTime() const;
+
+ /*!
+ * @brief Set the start date and time from a CDateTime instance carrying the data as UTC.
+ * @param start The start date and time as UTC.
+ */
+ void SetStartFromUTC(const CDateTime& start);
+
+ /*!
+ * @brief Set the start date and time from a CDateTime instance carrying the data as local time.
+ * @param start The start date and time as local time.
+ */
+ void SetStartFromLocalTime(const CDateTime& start);
+
+ /*!
+ * @brief The end date and time for this timer, as UTC.
+ * @return The start date and time.
+ */
+ CDateTime EndAsUTC() const;
+
+ /*!
+ * @brief The end date and time for this timer, as local time.
+ * @return The start date and time.
+ */
+ CDateTime EndAsLocalTime() const;
+
+ /*!
+ * @brief Set the end date and time from a CDateTime instance carrying the data as UTC.
+ * @param start The end date and time as UTC.
+ */
+ void SetEndFromUTC(const CDateTime& end);
+
+ /*!
+ * @brief Set the end date and time from a CDateTime instance carrying the data as local time.
+ * @param start The end date and time as local time.
+ */
+ void SetEndFromLocalTime(const CDateTime& end);
+
+ /*!
+ * @brief The first day for this timer, as UTC.
+ * @return The first day.
+ */
+ CDateTime FirstDayAsUTC() const;
+
+ /*!
+ * @brief The first day for this timer, as local time.
+ * @return The first day.
+ */
+ CDateTime FirstDayAsLocalTime() const;
+
+ /*!
+ * @brief Set the first dday from a CDateTime instance carrying the data as UTC.
+ * @param start The first day as UTC.
+ */
+ void SetFirstDayFromUTC(const CDateTime& firstDay);
+
+ /*!
+ * @brief Set the first dday from a CDateTime instance carrying the data as local time.
+ * @param start The first day as local time.
+ */
+ void SetFirstDayFromLocalTime(const CDateTime& firstDay);
+
+ /*!
+ * @brief Helper function to convert a given CDateTime containing data as UTC to local time.
+ * @param utc A CDateTime instance carrying data as UTC.
+ * @return A CDateTime instance carrying data as local time.
+ */
+ static CDateTime ConvertUTCToLocalTime(const CDateTime& utc);
+
+ /*!
+ * @brief Helper function to convert a given CDateTime containing data as local time to UTC.
+ * @param local A CDateTime instance carrying data as local time.
+ * @return A CDateTime instance carrying data as UTC.
+ */
+ static CDateTime ConvertLocalTimeToUTC(const CDateTime& local);
+
+ /*!
+ * @brief Get the duration of this timer in seconds, excluding padding times.
+ * @return The duration.
+ */
+ int GetDuration() const;
+
+ /*!
+ * @brief Get time in minutes to start the recording before the start time of the programme.
+ * @return The start padding time.
+ */
+ unsigned int MarginStart() const { return m_iMarginStart; }
+
+ /*!
+ * @brief Get time in minutes to end the recording after the end time of the programme.
+ * @return The end padding time.
+ */
+ unsigned int MarginEnd() const { return m_iMarginEnd; }
+
+ /*!
+ * @brief For timer rules, the days of week this timer rule is scheduled for.
+ * @return The days of week.
+ */
+ unsigned int WeekDays() const { return m_iWeekdays; }
+
+ /*!
+ * @brief For timer rules, whether start time is "any time", not a particular time.
+ * @return True, if timer start is "any time", false otherwise.
+ */
+ bool IsStartAnyTime() const { return m_bStartAnyTime; }
+
+ /*!
+ * @brief For timer rules, whether end time is "any time", not a particular time.
+ * @return True, if timer end is "any time", false otherwise.
+ */
+ bool IsEndAnyTime() const { return m_bEndAnyTime; }
+
+ /*!
+ * @brief For timer rules, whether only the EPG programme title shall be searched or also other
+ * data like the programme's plot, if available.
+ * @return True, if not only the programme's title shall be included in EPG search,
+ * false otherwise.
+ */
+ bool IsFullTextEpgSearch() const { return m_bFullTextEpgSearch; }
+
+ /*!
+ * @brief For timer rules, the epg data match string for searches. Format is backend-dependent,
+ * for example regexp.
+ * @return The search string
+ */
+ const std::string& EpgSearchString() const { return m_strEpgSearchString; }
+
+ /*!
+ * @brief The series link for this timer.
+ * @return The series link or empty string, if not available.
+ */
+ const std::string& SeriesLink() const;
+
+ /*!
+ * @brief Get the UID of the epg event associated with this timer tag, if any.
+ * @return The UID or EPG_TAG_INVALID_UID.
+ */
+ unsigned int UniqueBroadcastID() const { return m_iEpgUid; }
+
+ /*!
+ * @brief Add this timer to the backend, transferring all local data of this timer to the backend.
+ * @return True on success, false otherwise.
+ */
+ bool AddToClient() const;
+
+ /*!
+ * @brief Delete this timer on the backend.
+ * @param bForce Control what to do in case the timer is currently recording.
+ * True to force to delete the timer, false to return TimerDeleteResult::RECORDING.
+ * @return The result.
+ */
+ TimerOperationResult DeleteFromClient(bool bForce = false) const;
+
+ /*!
+ * @brief Update this timer on the backend, transferring all local data of this timer to
+ * the backend.
+ * @return True on success, false otherwise.
+ */
+ bool UpdateOnClient();
+
+ /*!
+ * @brief Persist this timer in the local database.
+ * @return True on success, false otherwise.
+ */
+ bool Persist();
+
+ /*!
+ * @brief Delete this timer from the local database.
+ * @return True on success, false otherwise.
+ */
+ bool DeleteFromDatabase();
+
+ /*!
+ * @brief GUI support: Get the text for the timer GUI notification.
+ * @return The notification text.
+ */
+ std::string GetNotificationText() const;
+
+ /*!
+ * @brief GUI support: Get the text for the timer GUI notification when a timer has been deleted.
+ * @return The notification text.
+ */
+ std::string GetDeletedNotificationText() const;
+
+ /*!
+ * @brief GUI support: Get the summary text for this timer, reflecting the timer schedule in a
+ * human readable form.
+ * @return The summary string.
+ */
+ const std::string& Summary() const;
+
+ /*!
+ * @brief GUI support: Update the summary text for this timer.
+ */
+ void UpdateSummary();
+
+ /*!
+ * @brief GUI support: Get the status text for this timer, reflecting its current state in a
+ * human readable form.
+ * @return The status string.
+ */
+ std::string GetStatus(bool bRadio) const;
+
+ /*!
+ * @brief GUI support: Get the timer string in a human readable form.
+ * @return The type string.
+ */
+ std::string GetTypeAsString() const;
+
+ /*!
+ * @brief GUI support: Return string representation for any possible combination of weekdays.
+ * @param iWeekdays weekdays as bit mask (0x01 = Mo, 0x02 = Tu, ...)
+ * @param bEpgBased context is an epg-based timer
+ * @param bLongMultiDaysFormat use long format. ("Mo-__-We-__-Fr-Sa-__" vs. "Mo-We-Fr-Sa")
+ * @return The weekdays string representation.
+ */
+ static std::string GetWeekdaysString(unsigned int iWeekdays,
+ bool bEpgBased,
+ bool bLongMultiDaysFormat);
+
+private:
+ CPVRTimerInfoTag(const CPVRTimerInfoTag& tag) = delete;
+ CPVRTimerInfoTag& operator=(const CPVRTimerInfoTag& orig) = delete;
+
+ std::string GetWeekdaysString() const;
+ void UpdateEpgInfoTag();
+
+ static std::shared_ptr<CPVRTimerInfoTag> CreateFromDate(
+ const std::shared_ptr<CPVRChannel>& channel,
+ const CDateTime& start,
+ int iDuration,
+ bool bCreateReminder,
+ bool bReadOnly);
+
+ mutable CCriticalSection m_critSection;
+
+ std::string m_strTitle; /*!< @brief name of this timer */
+ std::string
+ m_strEpgSearchString; /*!< @brief a epg data match string for epg-based timer rules. Format is backend-dependent, for example regexp */
+ bool m_bFullTextEpgSearch =
+ false; /*!< @brief indicates whether only epg episode title can be matched by the pvr backend or "more" (backend-dependent") data. */
+ std::string m_strDirectory; /*!< @brief directory where the recording must be stored */
+ std::string m_strSummary; /*!< @brief summary string with the time to show inside a GUI list */
+ PVR_TIMER_STATE m_state = PVR_TIMER_STATE_SCHEDULED; /*!< @brief the state of this timer */
+ int m_iClientId; /*!< @brief ID of the backend */
+ int m_iClientIndex; /*!< @brief index number of the tag, given by the backend, PVR_TIMER_NO_CLIENT_INDEX for new */
+ int m_iParentClientIndex; /*!< @brief for timers scheduled by a timer rule, the index number of the parent, given by the backend, PVR_TIMER_NO_PARENT for no parent */
+ int m_iClientChannelUid; /*!< @brief channel uid */
+ bool m_bStartAnyTime =
+ false; /*!< @brief Ignore start date and time clock. Record at 'Any Time' */
+ bool m_bEndAnyTime = false; /*!< @brief Ignore end date and time clock. Record at 'Any Time' */
+ int m_iPriority; /*!< @brief priority of the timer */
+ int m_iLifetime; /*!< @brief lifetime of the timer in days */
+ int m_iMaxRecordings =
+ 0; /*!< @brief (optional) backend setting for maximum number of recordings to keep*/
+ unsigned int m_iWeekdays; /*!< @brief bit based store of weekdays for timer rules */
+ unsigned int
+ m_iPreventDupEpisodes; /*!< @brief only record new episodes for epg-based timer rules */
+ unsigned int m_iRecordingGroup =
+ 0; /*!< @brief (optional) if set, the addon/backend stores the recording to a group (sub-folder) */
+ std::string m_strFileNameAndPath; /*!< @brief file name is only for reference */
+ bool m_bIsRadio; /*!< @brief is radio channel if set */
+ unsigned int m_iTimerId = 0; /*!< @brief id that won't change as long as Kodi is running */
+ unsigned int
+ m_iMarginStart; /*!< @brief (optional) if set, the backend starts the recording iMarginStart minutes before startTime. */
+ unsigned int
+ m_iMarginEnd; /*!< @brief (optional) if set, the backend ends the recording iMarginEnd minutes after endTime. */
+ mutable unsigned int
+ m_iEpgUid; /*!< id of epg event associated with this timer, EPG_TAG_INVALID_UID if none. */
+ std::string m_strSeriesLink; /*!< series link */
+
+ CDateTime m_StartTime; /*!< start time */
+ CDateTime m_StopTime; /*!< stop time */
+ CDateTime m_FirstDay; /*!< if it is a manual timer rule the first date it starts */
+ std::shared_ptr<CPVRTimerType> m_timerType; /*!< the type of this timer */
+
+ unsigned int m_iTVChildTimersActive = 0;
+ unsigned int m_iTVChildTimersConflictNOK = 0;
+ unsigned int m_iTVChildTimersRecording = 0;
+ unsigned int m_iTVChildTimersErrors = 0;
+ unsigned int m_iRadioChildTimersActive = 0;
+ unsigned int m_iRadioChildTimersConflictNOK = 0;
+ unsigned int m_iRadioChildTimersRecording = 0;
+ unsigned int m_iRadioChildTimersErrors = 0;
+
+ mutable std::shared_ptr<CPVREpgInfoTag> m_epgTag; /*!< epg info tag matching m_iEpgUid. */
+ mutable std::shared_ptr<CPVRChannel> m_channel;
+
+ mutable bool m_bProbedEpgTag = false;
+};
+} // namespace PVR
diff --git a/xbmc/pvr/timers/PVRTimerRuleMatcher.cpp b/xbmc/pvr/timers/PVRTimerRuleMatcher.cpp
new file mode 100644
index 0000000..9178e85
--- /dev/null
+++ b/xbmc/pvr/timers/PVRTimerRuleMatcher.cpp
@@ -0,0 +1,193 @@
+/*
+ * 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 "PVRTimerRuleMatcher.h"
+
+#include "XBDateTime.h"
+#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h" // PVR_CHANNEL_INVALID_UID
+#include "pvr/epg/EpgInfoTag.h"
+#include "pvr/timers/PVRTimerInfoTag.h"
+#include "utils/RegExp.h"
+
+using namespace PVR;
+
+CPVRTimerRuleMatcher::CPVRTimerRuleMatcher(const std::shared_ptr<CPVRTimerInfoTag>& timerRule,
+ const CDateTime& start)
+ : m_timerRule(timerRule), m_start(CPVRTimerInfoTag::ConvertUTCToLocalTime(start))
+{
+}
+
+CPVRTimerRuleMatcher::~CPVRTimerRuleMatcher() = default;
+
+std::shared_ptr<CPVRChannel> CPVRTimerRuleMatcher::GetChannel() const
+{
+ if (m_timerRule->GetTimerType()->SupportsChannels())
+ return m_timerRule->Channel();
+
+ return {};
+}
+
+CDateTime CPVRTimerRuleMatcher::GetNextTimerStart() const
+{
+ if (!m_timerRule->GetTimerType()->SupportsStartTime())
+ return CDateTime(); // invalid datetime
+
+ const CDateTime startDateLocal = m_timerRule->GetTimerType()->SupportsFirstDay()
+ ? m_timerRule->FirstDayAsLocalTime()
+ : m_start;
+ const CDateTime startTimeLocal = m_timerRule->StartAsLocalTime();
+ CDateTime nextStart(startDateLocal.GetYear(), startDateLocal.GetMonth(), startDateLocal.GetDay(),
+ startTimeLocal.GetHour(), startTimeLocal.GetMinute(), 0);
+
+ const CDateTimeSpan oneDay(1, 0, 0, 0);
+ while (nextStart < m_start)
+ {
+ nextStart += oneDay;
+ }
+
+ if (m_timerRule->GetTimerType()->SupportsWeekdays() &&
+ m_timerRule->WeekDays() != PVR_WEEKDAY_ALLDAYS)
+ {
+ bool bMatch = false;
+ while (!bMatch)
+ {
+ int startWeekday = nextStart.GetDayOfWeek();
+ if (startWeekday == 0)
+ startWeekday = 7;
+
+ bMatch = ((1 << (startWeekday - 1)) & m_timerRule->WeekDays());
+ if (!bMatch)
+ nextStart += oneDay;
+ }
+ }
+
+ return nextStart.GetAsUTCDateTime();
+}
+
+bool CPVRTimerRuleMatcher::Matches(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const
+{
+ return epgTag && CPVRTimerInfoTag::ConvertUTCToLocalTime(epgTag->EndAsUTC()) > m_start &&
+ MatchSeriesLink(epgTag) && MatchChannel(epgTag) && MatchStart(epgTag) &&
+ MatchEnd(epgTag) && MatchDayOfWeek(epgTag) && MatchSearchText(epgTag);
+}
+
+bool CPVRTimerRuleMatcher::MatchSeriesLink(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const
+{
+ if (m_timerRule->GetTimerType()->RequiresEpgSeriesLinkOnCreate())
+ return epgTag->SeriesLink() == m_timerRule->SeriesLink();
+ else
+ return true;
+}
+
+bool CPVRTimerRuleMatcher::MatchChannel(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const
+{
+ if (m_timerRule->GetTimerType()->SupportsAnyChannel() &&
+ m_timerRule->ClientChannelUID() == PVR_CHANNEL_INVALID_UID)
+ return true; // matches any channel
+
+ if (m_timerRule->GetTimerType()->SupportsChannels())
+ return m_timerRule->ClientChannelUID() != PVR_CHANNEL_INVALID_UID &&
+ epgTag->ClientID() == m_timerRule->ClientID() &&
+ epgTag->UniqueChannelID() == m_timerRule->ClientChannelUID();
+ else
+ return true;
+}
+
+bool CPVRTimerRuleMatcher::MatchStart(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const
+{
+ if (m_timerRule->GetTimerType()->SupportsFirstDay())
+ {
+ // only year, month and day do matter here...
+ const CDateTime startEpgLocal = CPVRTimerInfoTag::ConvertUTCToLocalTime(epgTag->StartAsUTC());
+ const CDateTime startEpg(startEpgLocal.GetYear(), startEpgLocal.GetMonth(),
+ startEpgLocal.GetDay(), 0, 0, 0);
+ const CDateTime firstDayLocal = m_timerRule->FirstDayAsLocalTime();
+ const CDateTime startTimer(firstDayLocal.GetYear(), firstDayLocal.GetMonth(),
+ firstDayLocal.GetDay(), 0, 0, 0);
+ if (startEpg < startTimer)
+ return false;
+ }
+
+ if (m_timerRule->GetTimerType()->SupportsStartAnyTime() && m_timerRule->IsStartAnyTime())
+ return true; // matches any start time
+
+ if (m_timerRule->GetTimerType()->SupportsStartTime())
+ {
+ // only hours and minutes do matter here...
+ const CDateTime startEpgLocal = CPVRTimerInfoTag::ConvertUTCToLocalTime(epgTag->StartAsUTC());
+ const CDateTime startEpg(2000, 1, 1, startEpgLocal.GetHour(), startEpgLocal.GetMinute(), 0);
+ const CDateTime startTimerLocal = m_timerRule->StartAsLocalTime();
+ const CDateTime startTimer(2000, 1, 1, startTimerLocal.GetHour(), startTimerLocal.GetMinute(),
+ 0);
+ return startEpg >= startTimer;
+ }
+ else
+ return true;
+}
+
+bool CPVRTimerRuleMatcher::MatchEnd(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const
+{
+ if (m_timerRule->GetTimerType()->SupportsEndAnyTime() && m_timerRule->IsEndAnyTime())
+ return true; // matches any end time
+
+ if (m_timerRule->GetTimerType()->SupportsEndTime())
+ {
+ // only hours and minutes do matter here...
+ const CDateTime endEpgLocal = CPVRTimerInfoTag::ConvertUTCToLocalTime(epgTag->EndAsUTC());
+ const CDateTime endEpg(2000, 1, 1, endEpgLocal.GetHour(), endEpgLocal.GetMinute(), 0);
+ const CDateTime endTimerLocal = m_timerRule->EndAsLocalTime();
+ const CDateTime endTimer(2000, 1, 1, endTimerLocal.GetHour(), endTimerLocal.GetMinute(), 0);
+ return endEpg <= endTimer;
+ }
+ else
+ return true;
+}
+
+bool CPVRTimerRuleMatcher::MatchDayOfWeek(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const
+{
+ if (m_timerRule->GetTimerType()->SupportsWeekdays())
+ {
+ if (m_timerRule->WeekDays() != PVR_WEEKDAY_ALLDAYS)
+ {
+ const CDateTime startEpgLocal = CPVRTimerInfoTag::ConvertUTCToLocalTime(epgTag->StartAsUTC());
+ int startWeekday = startEpgLocal.GetDayOfWeek();
+ if (startWeekday == 0)
+ startWeekday = 7;
+
+ return ((1 << (startWeekday - 1)) & m_timerRule->WeekDays());
+ }
+ }
+ return true;
+}
+
+bool CPVRTimerRuleMatcher::MatchSearchText(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const
+{
+ if (m_timerRule->GetTimerType()->SupportsEpgFulltextMatch() && m_timerRule->IsFullTextEpgSearch())
+ {
+ if (!m_textSearch)
+ {
+ m_textSearch.reset(new CRegExp(true /* case insensitive */));
+ m_textSearch->RegComp(m_timerRule->EpgSearchString());
+ }
+ return m_textSearch->RegFind(epgTag->Title()) >= 0 ||
+ m_textSearch->RegFind(epgTag->EpisodeName()) >= 0 ||
+ m_textSearch->RegFind(epgTag->PlotOutline()) >= 0 ||
+ m_textSearch->RegFind(epgTag->Plot()) >= 0;
+ }
+ else if (m_timerRule->GetTimerType()->SupportsEpgTitleMatch())
+ {
+ if (!m_textSearch)
+ {
+ m_textSearch.reset(new CRegExp(true /* case insensitive */));
+ m_textSearch->RegComp(m_timerRule->EpgSearchString());
+ }
+ return m_textSearch->RegFind(epgTag->Title()) >= 0;
+ }
+ else
+ return true;
+}
diff --git a/xbmc/pvr/timers/PVRTimerRuleMatcher.h b/xbmc/pvr/timers/PVRTimerRuleMatcher.h
new file mode 100644
index 0000000..9d502af
--- /dev/null
+++ b/xbmc/pvr/timers/PVRTimerRuleMatcher.h
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "XBDateTime.h"
+
+#include <memory>
+
+class CRegExp;
+
+namespace PVR
+{
+class CPVRChannel;
+class CPVRTimerInfoTag;
+class CPVREpgInfoTag;
+
+class CPVRTimerRuleMatcher
+{
+public:
+ CPVRTimerRuleMatcher(const std::shared_ptr<CPVRTimerInfoTag>& timerRule, const CDateTime& start);
+ virtual ~CPVRTimerRuleMatcher();
+
+ std::shared_ptr<CPVRTimerInfoTag> GetTimerRule() const { return m_timerRule; }
+
+ std::shared_ptr<CPVRChannel> GetChannel() const;
+ CDateTime GetNextTimerStart() const;
+ bool Matches(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const;
+
+private:
+ bool MatchSeriesLink(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const;
+ bool MatchChannel(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const;
+ bool MatchStart(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const;
+ bool MatchEnd(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const;
+ bool MatchDayOfWeek(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const;
+ bool MatchSearchText(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const;
+
+ const std::shared_ptr<CPVRTimerInfoTag> m_timerRule;
+ CDateTime m_start;
+ mutable std::unique_ptr<CRegExp> m_textSearch;
+};
+} // namespace PVR
diff --git a/xbmc/pvr/timers/PVRTimerType.cpp b/xbmc/pvr/timers/PVRTimerType.cpp
new file mode 100644
index 0000000..db20fa1
--- /dev/null
+++ b/xbmc/pvr/timers/PVRTimerType.cpp
@@ -0,0 +1,418 @@
+/*
+ * 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 "PVRTimerType.h"
+
+#include "ServiceBroker.h"
+#include "guilib/LocalizeStrings.h"
+#include "pvr/PVRManager.h"
+#include "pvr/addons/PVRClient.h"
+#include "pvr/addons/PVRClients.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <iterator>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+using namespace PVR;
+
+const std::vector<std::shared_ptr<CPVRTimerType>> CPVRTimerType::GetAllTypes()
+{
+ std::vector<std::shared_ptr<CPVRTimerType>> allTypes;
+ CServiceBroker::GetPVRManager().Clients()->GetTimerTypes(allTypes);
+
+ // Add local reminder timer types. Local reminders are always available.
+ int iTypeId = PVR_TIMER_TYPE_NONE;
+
+ // one time time-based reminder
+ allTypes.emplace_back(std::make_shared<CPVRTimerType>(++iTypeId,
+ PVR_TIMER_TYPE_IS_MANUAL |
+ PVR_TIMER_TYPE_IS_REMINDER |
+ PVR_TIMER_TYPE_SUPPORTS_CHANNELS |
+ PVR_TIMER_TYPE_SUPPORTS_START_TIME |
+ PVR_TIMER_TYPE_SUPPORTS_END_TIME));
+
+ // one time epg-based reminder
+ allTypes.emplace_back(std::make_shared<CPVRTimerType>(++iTypeId,
+ PVR_TIMER_TYPE_IS_REMINDER |
+ PVR_TIMER_TYPE_REQUIRES_EPG_TAG_ON_CREATE |
+ PVR_TIMER_TYPE_SUPPORTS_CHANNELS |
+ PVR_TIMER_TYPE_SUPPORTS_START_TIME |
+ PVR_TIMER_TYPE_SUPPORTS_START_MARGIN));
+
+ // time-based reminder rule
+ allTypes.emplace_back(std::make_shared<CPVRTimerType>(++iTypeId,
+ PVR_TIMER_TYPE_IS_REPEATING |
+ PVR_TIMER_TYPE_IS_MANUAL |
+ PVR_TIMER_TYPE_IS_REMINDER |
+ PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE |
+ PVR_TIMER_TYPE_SUPPORTS_CHANNELS |
+ PVR_TIMER_TYPE_SUPPORTS_START_TIME |
+ PVR_TIMER_TYPE_SUPPORTS_END_TIME |
+ PVR_TIMER_TYPE_SUPPORTS_FIRST_DAY |
+ PVR_TIMER_TYPE_SUPPORTS_WEEKDAYS));
+
+ // one time read-only time-based reminder (created by timer rule)
+ allTypes.emplace_back(std::make_shared<CPVRTimerType>(++iTypeId,
+ PVR_TIMER_TYPE_IS_MANUAL |
+ PVR_TIMER_TYPE_IS_REMINDER |
+ PVR_TIMER_TYPE_IS_READONLY |
+ PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE |
+ PVR_TIMER_TYPE_SUPPORTS_CHANNELS |
+ PVR_TIMER_TYPE_SUPPORTS_START_TIME |
+ PVR_TIMER_TYPE_SUPPORTS_END_TIME,
+ g_localizeStrings.Get(819))); // One time (Scheduled by timer rule)
+
+ // epg-based reminder rule
+ allTypes.emplace_back(std::make_shared<CPVRTimerType>(++iTypeId,
+ PVR_TIMER_TYPE_IS_REPEATING |
+ PVR_TIMER_TYPE_IS_REMINDER |
+ PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE |
+ PVR_TIMER_TYPE_SUPPORTS_TITLE_EPG_MATCH |
+ PVR_TIMER_TYPE_SUPPORTS_FULLTEXT_EPG_MATCH |
+ PVR_TIMER_TYPE_SUPPORTS_CHANNELS |
+ PVR_TIMER_TYPE_SUPPORTS_ANY_CHANNEL |
+ PVR_TIMER_TYPE_SUPPORTS_START_TIME |
+ PVR_TIMER_TYPE_SUPPORTS_START_ANYTIME |
+ PVR_TIMER_TYPE_SUPPORTS_END_TIME |
+ PVR_TIMER_TYPE_SUPPORTS_END_ANYTIME |
+ PVR_TIMER_TYPE_SUPPORTS_FIRST_DAY |
+ PVR_TIMER_TYPE_SUPPORTS_WEEKDAYS |
+ PVR_TIMER_TYPE_SUPPORTS_START_END_MARGIN));
+
+ // one time read-only epg-based reminder (created by timer rule)
+ allTypes.emplace_back(std::make_shared<CPVRTimerType>(++iTypeId,
+ PVR_TIMER_TYPE_IS_REMINDER |
+ PVR_TIMER_TYPE_IS_READONLY |
+ PVR_TIMER_TYPE_REQUIRES_EPG_TAG_ON_CREATE |
+ PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE |
+ PVR_TIMER_TYPE_SUPPORTS_CHANNELS |
+ PVR_TIMER_TYPE_SUPPORTS_START_TIME |
+ PVR_TIMER_TYPE_SUPPORTS_START_MARGIN,
+ g_localizeStrings.Get(819))); // One time (Scheduled by timer rule)
+
+ return allTypes;
+}
+
+const std::shared_ptr<CPVRTimerType> CPVRTimerType::GetFirstAvailableType(const std::shared_ptr<CPVRClient>& client)
+{
+ if (client)
+ {
+ std::vector<std::shared_ptr<CPVRTimerType>> types;
+ if (client->GetTimerTypes(types) == PVR_ERROR_NO_ERROR && !types.empty())
+ {
+ return *(types.begin());
+ }
+ }
+ return {};
+}
+
+std::shared_ptr<CPVRTimerType> CPVRTimerType::CreateFromIds(unsigned int iTypeId, int iClientId)
+{
+ const std::vector<std::shared_ptr<CPVRTimerType>> types = GetAllTypes();
+ const auto it =
+ std::find_if(types.cbegin(), types.cend(), [iClientId, iTypeId](const auto& type) {
+ return type->GetClientId() == iClientId && type->GetTypeId() == iTypeId;
+ });
+ if (it != types.cend())
+ return (*it);
+
+ if (iClientId != -1)
+ {
+ // fallback. try to obtain local timer type.
+ std::shared_ptr<CPVRTimerType> type = CreateFromIds(iTypeId, -1);
+ if (type)
+ return type;
+ }
+
+ CLog::LogF(LOGERROR, "Unable to resolve numeric timer type ({}, {})", iTypeId, iClientId);
+ return {};
+}
+
+std::shared_ptr<CPVRTimerType> CPVRTimerType::CreateFromAttributes(uint64_t iMustHaveAttr,
+ uint64_t iMustNotHaveAttr,
+ int iClientId)
+{
+ const std::vector<std::shared_ptr<CPVRTimerType>> types = GetAllTypes();
+ const auto it = std::find_if(types.cbegin(), types.cend(),
+ [iClientId, iMustHaveAttr, iMustNotHaveAttr](const auto& type) {
+ return type->GetClientId() == iClientId &&
+ (type->GetAttributes() & iMustHaveAttr) == iMustHaveAttr &&
+ (type->GetAttributes() & iMustNotHaveAttr) == 0;
+ });
+ if (it != types.cend())
+ return (*it);
+
+ if (iClientId != -1)
+ {
+ // fallback. try to obtain local timer type.
+ std::shared_ptr<CPVRTimerType> type = CreateFromAttributes(iMustHaveAttr, iMustNotHaveAttr, -1);
+ if (type)
+ return type;
+ }
+
+ CLog::LogF(LOGERROR, "Unable to resolve timer type (0x{:x}, 0x{:x}, {})", iMustHaveAttr,
+ iMustNotHaveAttr, iClientId);
+ return {};
+}
+
+CPVRTimerType::CPVRTimerType() :
+ m_iTypeId(PVR_TIMER_TYPE_NONE),
+ m_iAttributes(PVR_TIMER_TYPE_ATTRIBUTE_NONE)
+{
+}
+
+CPVRTimerType::CPVRTimerType(const PVR_TIMER_TYPE& type, int iClientId) :
+ m_iClientId(iClientId),
+ m_iTypeId(type.iId),
+ m_iAttributes(type.iAttributes),
+ m_strDescription(type.strDescription)
+{
+ InitDescription();
+ InitAttributeValues(type);
+}
+
+CPVRTimerType::CPVRTimerType(unsigned int iTypeId,
+ uint64_t iAttributes,
+ const std::string& strDescription)
+ : m_iTypeId(iTypeId), m_iAttributes(iAttributes), m_strDescription(strDescription)
+{
+ InitDescription();
+}
+
+CPVRTimerType::~CPVRTimerType() = default;
+
+bool CPVRTimerType::operator ==(const CPVRTimerType& right) const
+{
+ return (m_iClientId == right.m_iClientId &&
+ m_iTypeId == right.m_iTypeId &&
+ m_iAttributes == right.m_iAttributes &&
+ m_strDescription == right.m_strDescription &&
+ m_priorityValues == right.m_priorityValues &&
+ m_iPriorityDefault == right.m_iPriorityDefault &&
+ m_lifetimeValues == right.m_lifetimeValues &&
+ m_iLifetimeDefault == right.m_iLifetimeDefault &&
+ m_maxRecordingsValues == right.m_maxRecordingsValues &&
+ m_iMaxRecordingsDefault == right.m_iMaxRecordingsDefault &&
+ m_preventDupEpisodesValues == right.m_preventDupEpisodesValues &&
+ m_iPreventDupEpisodesDefault == right.m_iPreventDupEpisodesDefault &&
+ m_recordingGroupValues == right.m_recordingGroupValues &&
+ m_iRecordingGroupDefault == right.m_iRecordingGroupDefault);
+}
+
+bool CPVRTimerType::operator !=(const CPVRTimerType& right) const
+{
+ return !(*this == right);
+}
+
+void CPVRTimerType::InitDescription()
+{
+ // if no description was given, compile it
+ if (m_strDescription.empty())
+ {
+ int id;
+ if (m_iAttributes & PVR_TIMER_TYPE_IS_REPEATING)
+ {
+ id = (m_iAttributes & PVR_TIMER_TYPE_IS_MANUAL)
+ ? 822 // "Timer rule"
+ : 823; // "Timer rule (guide-based)"
+ }
+ else
+ {
+ id = (m_iAttributes & PVR_TIMER_TYPE_IS_MANUAL)
+ ? 820 // "One time"
+ : 821; // "One time (guide-based)
+ }
+ m_strDescription = g_localizeStrings.Get(id);
+ }
+
+ // add reminder/recording prefix
+ int prefixId = (m_iAttributes & PVR_TIMER_TYPE_IS_REMINDER)
+ ? 824 // Reminder: ...
+ : 825; // Recording: ...
+
+ m_strDescription = StringUtils::Format(g_localizeStrings.Get(prefixId), m_strDescription);
+}
+
+void CPVRTimerType::InitAttributeValues(const PVR_TIMER_TYPE& type)
+{
+ InitPriorityValues(type);
+ InitLifetimeValues(type);
+ InitMaxRecordingsValues(type);
+ InitPreventDuplicateEpisodesValues(type);
+ InitRecordingGroupValues(type);
+}
+
+void CPVRTimerType::InitPriorityValues(const PVR_TIMER_TYPE& type)
+{
+ if (type.iPrioritiesSize > 0)
+ {
+ for (unsigned int i = 0; i < type.iPrioritiesSize; ++i)
+ {
+ std::string strDescr(type.priorities[i].strDescription);
+ if (strDescr.empty())
+ {
+ // No description given by addon. Create one from value.
+ strDescr = std::to_string(type.priorities[i].iValue);
+ }
+ m_priorityValues.emplace_back(strDescr, type.priorities[i].iValue);
+ }
+
+ m_iPriorityDefault = type.iPrioritiesDefault;
+ }
+ else if (SupportsPriority())
+ {
+ // No values given by addon, but priority supported. Use default values 1..100
+ for (int i = 1; i < 101; ++i)
+ m_priorityValues.emplace_back(std::to_string(i), i);
+
+ m_iPriorityDefault = DEFAULT_RECORDING_PRIORITY;
+ }
+ else
+ {
+ // No priority supported.
+ m_iPriorityDefault = DEFAULT_RECORDING_PRIORITY;
+ }
+}
+
+void CPVRTimerType::GetPriorityValues(std::vector<std::pair<std::string, int>>& list) const
+{
+ std::copy(m_priorityValues.cbegin(), m_priorityValues.cend(), std::back_inserter(list));
+}
+
+void CPVRTimerType::InitLifetimeValues(const PVR_TIMER_TYPE& type)
+{
+ if (type.iLifetimesSize > 0)
+ {
+ for (unsigned int i = 0; i < type.iLifetimesSize; ++i)
+ {
+ int iValue = type.lifetimes[i].iValue;
+ std::string strDescr(type.lifetimes[i].strDescription);
+ if (strDescr.empty())
+ {
+ // No description given by addon. Create one from value.
+ strDescr = std::to_string(iValue);
+ }
+ m_lifetimeValues.emplace_back(strDescr, iValue);
+ }
+
+ m_iLifetimeDefault = type.iLifetimesDefault;
+ }
+ else if (SupportsLifetime())
+ {
+ // No values given by addon, but lifetime supported. Use default values 1..365
+ for (int i = 1; i < 366; ++i)
+ {
+ m_lifetimeValues.emplace_back(StringUtils::Format(g_localizeStrings.Get(17999), i),
+ i); // "{} days"
+ }
+ m_iLifetimeDefault = DEFAULT_RECORDING_LIFETIME;
+ }
+ else
+ {
+ // No lifetime supported.
+ m_iLifetimeDefault = DEFAULT_RECORDING_LIFETIME;
+ }
+}
+
+void CPVRTimerType::GetLifetimeValues(std::vector<std::pair<std::string, int>>& list) const
+{
+ std::copy(m_lifetimeValues.cbegin(), m_lifetimeValues.cend(), std::back_inserter(list));
+}
+
+void CPVRTimerType::InitMaxRecordingsValues(const PVR_TIMER_TYPE& type)
+{
+ if (type.iMaxRecordingsSize > 0)
+ {
+ for (unsigned int i = 0; i < type.iMaxRecordingsSize; ++i)
+ {
+ std::string strDescr(type.maxRecordings[i].strDescription);
+ if (strDescr.empty())
+ {
+ // No description given by addon. Create one from value.
+ strDescr = std::to_string(type.maxRecordings[i].iValue);
+ }
+ m_maxRecordingsValues.emplace_back(strDescr, type.maxRecordings[i].iValue);
+ }
+
+ m_iMaxRecordingsDefault = type.iMaxRecordingsDefault;
+ }
+}
+
+void CPVRTimerType::GetMaxRecordingsValues(std::vector<std::pair<std::string, int>>& list) const
+{
+ std::copy(m_maxRecordingsValues.cbegin(), m_maxRecordingsValues.cend(), std::back_inserter(list));
+}
+
+void CPVRTimerType::InitPreventDuplicateEpisodesValues(const PVR_TIMER_TYPE& type)
+{
+ if (type.iPreventDuplicateEpisodesSize > 0)
+ {
+ for (unsigned int i = 0; i < type.iPreventDuplicateEpisodesSize; ++i)
+ {
+ std::string strDescr(type.preventDuplicateEpisodes[i].strDescription);
+ if (strDescr.empty())
+ {
+ // No description given by addon. Create one from value.
+ strDescr = std::to_string(type.preventDuplicateEpisodes[i].iValue);
+ }
+ m_preventDupEpisodesValues.emplace_back(strDescr, type.preventDuplicateEpisodes[i].iValue);
+ }
+
+ m_iPreventDupEpisodesDefault = type.iPreventDuplicateEpisodesDefault;
+ }
+ else if (SupportsRecordOnlyNewEpisodes())
+ {
+ // No values given by addon, but prevent duplicate episodes supported. Use default values 0..1
+ m_preventDupEpisodesValues.emplace_back(g_localizeStrings.Get(815), 0); // "Record all episodes"
+ m_preventDupEpisodesValues.emplace_back(g_localizeStrings.Get(816), 1); // "Record only new episodes"
+ m_iPreventDupEpisodesDefault = DEFAULT_RECORDING_DUPLICATEHANDLING;
+ }
+ else
+ {
+ // No prevent duplicate episodes supported.
+ m_iPreventDupEpisodesDefault = DEFAULT_RECORDING_DUPLICATEHANDLING;
+ }
+}
+
+void CPVRTimerType::GetPreventDuplicateEpisodesValues(std::vector<std::pair<std::string, int>>& list) const
+{
+ std::copy(m_preventDupEpisodesValues.cbegin(), m_preventDupEpisodesValues.cend(),
+ std::back_inserter(list));
+}
+
+void CPVRTimerType::InitRecordingGroupValues(const PVR_TIMER_TYPE& type)
+{
+ if (type.iRecordingGroupSize > 0)
+ {
+ for (unsigned int i = 0; i < type.iRecordingGroupSize; ++i)
+ {
+ std::string strDescr(type.recordingGroup[i].strDescription);
+ if (strDescr.empty())
+ {
+ // No description given by addon. Create one from value.
+ strDescr = StringUtils::Format("{} {}",
+ g_localizeStrings.Get(811), // Recording group
+ type.recordingGroup[i].iValue);
+ }
+ m_recordingGroupValues.emplace_back(strDescr, type.recordingGroup[i].iValue);
+ }
+
+ m_iRecordingGroupDefault = type.iRecordingGroupDefault;
+ }
+}
+
+void CPVRTimerType::GetRecordingGroupValues(std::vector< std::pair<std::string, int>>& list) const
+{
+ std::copy(m_recordingGroupValues.cbegin(), m_recordingGroupValues.cend(),
+ std::back_inserter(list));
+}
diff --git a/xbmc/pvr/timers/PVRTimerType.h b/xbmc/pvr/timers/PVRTimerType.h
new file mode 100644
index 0000000..8ee9e22
--- /dev/null
+++ b/xbmc/pvr/timers/PVRTimerType.h
@@ -0,0 +1,411 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+struct PVR_TIMER_TYPE;
+
+namespace PVR
+{
+ class CPVRClient;
+
+ static const int DEFAULT_RECORDING_PRIORITY = 50;
+ static const int DEFAULT_RECORDING_LIFETIME = 99; // days
+ static const unsigned int DEFAULT_RECORDING_DUPLICATEHANDLING = 0;
+
+ class CPVRTimerType
+ {
+ public:
+ /*!
+ * @brief Return a list with all known timer types.
+ * @return A list of timer types or an empty list if no types available.
+ */
+ static const std::vector<std::shared_ptr<CPVRTimerType>> GetAllTypes();
+
+ /*!
+ * @brief Return the first available timer type from given client.
+ * @param client the PVR client.
+ * @return A timer type or NULL if none available.
+ */
+ static const std::shared_ptr<CPVRTimerType> GetFirstAvailableType(const std::shared_ptr<CPVRClient>& client);
+
+ /*!
+ * @brief Create a timer type from given timer type id and client id.
+ * @param iTimerType the timer type id.
+ * @param iClientId the PVR client id.
+ * @return A timer type instance.
+ */
+ static std::shared_ptr<CPVRTimerType> CreateFromIds(unsigned int iTypeId, int iClientId);
+
+ /*!
+ * @brief Create a timer type from given timer type attributes and client id.
+ * @param iMustHaveAttr a combination of PVR_TIMER_TYPE_* attributes the type to create must have.
+ * @param iMustNotHaveAttr a combination of PVR_TIMER_TYPE_* attributes the type to create must not have.
+ * @param iClientId the PVR client id.
+ * @return A timer type instance.
+ */
+ static std::shared_ptr<CPVRTimerType> CreateFromAttributes(uint64_t iMustHaveAttr,
+ uint64_t iMustNotHaveAttr,
+ int iClientId);
+
+ CPVRTimerType();
+ CPVRTimerType(const PVR_TIMER_TYPE& type, int iClientId);
+ CPVRTimerType(unsigned int iTypeId,
+ uint64_t iAttributes,
+ const std::string& strDescription = "");
+
+ virtual ~CPVRTimerType();
+
+ CPVRTimerType(const CPVRTimerType& type) = delete;
+ CPVRTimerType& operator=(const CPVRTimerType& orig) = delete;
+
+ bool operator ==(const CPVRTimerType& right) const;
+ bool operator !=(const CPVRTimerType& right) const;
+
+ /*!
+ * @brief Get the PVR client id for this type.
+ * @return The PVR client id.
+ */
+ int GetClientId() const { return m_iClientId; }
+
+ /*!
+ * @brief Get the numeric type id of this type.
+ * @return The type id.
+ */
+ unsigned int GetTypeId() const { return m_iTypeId; }
+
+ /*!
+ * @brief Get the plain text (UI) description of this type.
+ * @return The description.
+ */
+ const std::string& GetDescription() const { return m_strDescription; }
+
+ /*!
+ * @brief Get the attributes of this type.
+ * @return The attributes.
+ */
+ uint64_t GetAttributes() const { return m_iAttributes; }
+
+ /*!
+ * @brief Check whether this type is for timer rules or one time timers.
+ * @return True if type represents a timer rule, false otherwise.
+ */
+ bool IsTimerRule() const { return (m_iAttributes & PVR_TIMER_TYPE_IS_REPEATING) > 0; }
+
+ /*!
+ * @brief Check whether this type is for reminder timers or recording timers.
+ * @return True if type represents a reminder timer, false otherwise.
+ */
+ bool IsReminder() const { return (m_iAttributes & PVR_TIMER_TYPE_IS_REMINDER) > 0; }
+
+ /*!
+ * @brief Check whether this type is for timer rules or one time timers.
+ * @return True if type represents a one time timer, false otherwise.
+ */
+ bool IsOnetime() const { return !IsTimerRule(); }
+
+ /*!
+ * @brief Check whether this type is for epg-based or manual timers.
+ * @return True if manual, false otherwise.
+ */
+ bool IsManual() const { return (m_iAttributes & PVR_TIMER_TYPE_IS_MANUAL) > 0; }
+
+ /*!
+ * @brief Check whether this type is for epg-based or manual timers.
+ * @return True if epg-based, false otherwise.
+ */
+ bool IsEpgBased() const { return !IsManual(); }
+
+ /*!
+ * @brief Check whether this type is for epg-based timer rules.
+ * @return True if epg-based timer rule, false otherwise.
+ */
+ bool IsEpgBasedTimerRule() const { return IsEpgBased() && IsTimerRule(); }
+
+ /*!
+ * @brief Check whether this type is for one time epg-based timers.
+ * @return True if one time epg-based, false otherwise.
+ */
+ bool IsEpgBasedOnetime() const { return IsEpgBased() && IsOnetime(); }
+
+ /*!
+ * @brief Check whether this type is for manual timer rules.
+ * @return True if manual timer rule, false otherwise.
+ */
+ bool IsManualTimerRule() const { return IsManual() && IsTimerRule(); }
+
+ /*!
+ * @brief Check whether this type is for one time manual timers.
+ * @return True if one time manual, false otherwise.
+ */
+ bool IsManualOnetime() const { return IsManual() && IsOnetime(); }
+
+ /*!
+ * @brief Check whether this type is readonly (must not be modified after initial creation).
+ * @return True if readonly, false otherwise.
+ */
+ bool IsReadOnly() const { return (m_iAttributes & PVR_TIMER_TYPE_IS_READONLY) > 0; }
+
+ /*!
+ * @brief Check whether this type allows deletion.
+ * @return True if type allows deletion, false otherwise.
+ */
+ bool AllowsDelete() const { return !IsReadOnly() || SupportsReadOnlyDelete(); }
+
+ /*!
+ * @brief Check whether this type forbids creation of new timers of this type.
+ * @return True if new instances are forbidden, false otherwise.
+ */
+ bool ForbidsNewInstances() const { return (m_iAttributes & PVR_TIMER_TYPE_FORBIDS_NEW_INSTANCES) > 0; }
+
+ /*!
+ * @brief Check whether this timer type is forbidden when epg tag info is present.
+ * @return True if new instances are forbidden when epg info is present, false otherwise.
+ */
+ bool ForbidsEpgTagOnCreate() const { return (m_iAttributes & PVR_TIMER_TYPE_FORBIDS_EPG_TAG_ON_CREATE) > 0; }
+
+ /*!
+ * @brief Check whether this timer type requires epg tag info to be present.
+ * @return True if new instances require EPG info, false otherwise.
+ */
+ bool RequiresEpgTagOnCreate() const { return (m_iAttributes & (PVR_TIMER_TYPE_REQUIRES_EPG_TAG_ON_CREATE |
+ PVR_TIMER_TYPE_REQUIRES_EPG_SERIES_ON_CREATE |
+ PVR_TIMER_TYPE_REQUIRES_EPG_SERIESLINK_ON_CREATE)) > 0; }
+
+ /*!
+ * @brief Check whether this timer type requires epg tag info including series attributes to be present.
+ * @return True if new instances require an EPG tag with series attributes, false otherwise.
+ */
+ bool RequiresEpgSeriesOnCreate() const { return (m_iAttributes & PVR_TIMER_TYPE_REQUIRES_EPG_SERIES_ON_CREATE) > 0; }
+
+ /*!
+ * @brief Check whether this timer type requires epg tag info including a series link to be present.
+ * @return True if new instances require an EPG tag with a series link, false otherwise.
+ */
+ bool RequiresEpgSeriesLinkOnCreate() const { return (m_iAttributes & PVR_TIMER_TYPE_REQUIRES_EPG_SERIESLINK_ON_CREATE) > 0; }
+
+ /*!
+ * @brief Check whether this type supports the "enabling/disabling" of timers of its type.
+ * @return True if "enabling/disabling" feature is supported, false otherwise.
+ */
+ bool SupportsEnableDisable() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE) > 0; }
+
+ /*!
+ * @brief Check whether this type supports channels.
+ * @return True if channels are supported, false otherwise.
+ */
+ bool SupportsChannels() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_CHANNELS) > 0; }
+
+ /*!
+ * @brief Check whether this type supports start time.
+ * @return True if start time values are supported, false otherwise.
+ */
+ bool SupportsStartTime() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_TIME) > 0; }
+
+ /*!
+ * @brief Check whether this type supports end time.
+ * @return True if end time values are supported, false otherwise.
+ */
+ bool SupportsEndTime() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_END_TIME) > 0; }
+ /*!
+ * @brief Check whether this type supports start any time.
+ * @return True if start any time is supported, false otherwise.
+ */
+ bool SupportsStartAnyTime() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_ANYTIME) > 0; }
+
+ /*!
+ * @brief Check whether this type supports end any time.
+ * @return True if end any time is supported, false otherwise.
+ */
+ bool SupportsEndAnyTime() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_END_ANYTIME) > 0; }
+
+ /*!
+ * @brief Check whether this type supports matching a search string against epg episode title.
+ * @return True if title matching is supported, false otherwise.
+ */
+ bool SupportsEpgTitleMatch() const { return (m_iAttributes & (PVR_TIMER_TYPE_SUPPORTS_TITLE_EPG_MATCH | PVR_TIMER_TYPE_SUPPORTS_FULLTEXT_EPG_MATCH)) > 0; }
+
+ /*!
+ * @brief Check whether this type supports matching a search string against extended (fulltext) epg data. This
+ includes title matching.
+ * @return True if fulltext matching is supported, false otherwise.
+ */
+ bool SupportsEpgFulltextMatch() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_FULLTEXT_EPG_MATCH) > 0; }
+
+ /*!
+ * @brief Check whether this type supports a first day the timer is active.
+ * @return True if first day is supported, false otherwise.
+ */
+ bool SupportsFirstDay() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_FIRST_DAY) > 0; }
+
+ /*!
+ * @brief Check whether this type supports weekdays for timer schedules.
+ * @return True if weekdays are supported, false otherwise.
+ */
+ bool SupportsWeekdays() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_WEEKDAYS) > 0; }
+
+ /*!
+ * @brief Check whether this type supports the "record only new episodes" feature.
+ * @return True if the "record only new episodes" feature is supported, false otherwise.
+ */
+ bool SupportsRecordOnlyNewEpisodes() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_RECORD_ONLY_NEW_EPISODES) > 0; }
+
+ /*!
+ * @brief Check whether this type supports pre record time.
+ * @return True if pre record time is supported, false otherwise.
+ */
+ bool SupportsStartMargin() const
+ {
+ return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_MARGIN) > 0 ||
+ (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_END_MARGIN) > 0;
+ }
+
+ /*!
+ * @brief Check whether this type supports post record time.
+ * @return True if post record time is supported, false otherwise.
+ */
+ bool SupportsEndMargin() const
+ {
+ return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_END_MARGIN) > 0 ||
+ (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_END_MARGIN) > 0;
+ }
+
+ /*!
+ * @brief Check whether this type supports recording priorities.
+ * @return True if recording priority is supported, false otherwise.
+ */
+ bool SupportsPriority() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_PRIORITY) > 0; }
+
+ /*!
+ * @brief Check whether this type supports lifetime for recordings.
+ * @return True if recording lifetime is supported, false otherwise.
+ */
+ bool SupportsLifetime() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_LIFETIME) > 0; }
+
+ /*!
+ * @brief Check whether this type supports MaxRecordings for recordings.
+ * @return True if MaxRecordings is supported, false otherwise.
+ */
+ bool SupportsMaxRecordings() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_MAX_RECORDINGS) > 0; }
+
+ /*!
+ * @brief Check whether this type supports user specified recording folders.
+ * @return True if recording folders are supported, false otherwise.
+ */
+ bool SupportsRecordingFolders() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_RECORDING_FOLDERS) > 0; }
+
+ /*!
+ * @brief Check whether this type supports recording groups.
+ * @return True if recording groups are supported, false otherwise.
+ */
+ bool SupportsRecordingGroup() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_RECORDING_GROUP) > 0; }
+
+ /*!
+ * @brief Check whether this type supports 'any channel', for example for defining a timer rule that should match any channel instead of a particular channel.
+ * @return True if any channel is supported, false otherwise.
+ */
+ bool SupportsAnyChannel() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_ANY_CHANNEL) > 0; }
+
+ /*!
+ * @brief Check whether this type supports deletion of an otherwise read-only timer.
+ * @return True if read-only deletion is supported, false otherwise.
+ */
+ bool SupportsReadOnlyDelete() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_READONLY_DELETE) > 0; }
+
+ /*!
+ * @brief Obtain a list with all possible values for the priority attribute.
+ * @param list out, the list with the values or an empty list, if priority is not supported by this type.
+ */
+ void GetPriorityValues(std::vector<std::pair<std::string, int>>& list) const;
+
+ /*!
+ * @brief Obtain the default value for the priority attribute.
+ * @return the default value.
+ */
+ int GetPriorityDefault() const { return m_iPriorityDefault; }
+
+ /*!
+ * @brief Obtain a list with all possible values for the lifetime attribute.
+ * @param list out, the list with the values or an empty list, if lifetime is not supported by this type.
+ */
+ void GetLifetimeValues(std::vector<std::pair<std::string, int>>& list) const;
+
+ /*!
+ * @brief Obtain the default value for the lifetime attribute.
+ * @return the default value.
+ */
+ int GetLifetimeDefault() const { return m_iLifetimeDefault; }
+
+ /*!
+ * @brief Obtain a list with all possible values for the MaxRecordings attribute.
+ * @param list out, the list with the values or an empty list, if MaxRecordings is not supported by this type.
+ */
+ void GetMaxRecordingsValues(std::vector<std::pair<std::string, int>>& list) const;
+
+ /*!
+ * @brief Obtain the default value for the MaxRecordings attribute.
+ * @return the default value.
+ */
+ int GetMaxRecordingsDefault() const { return m_iMaxRecordingsDefault; }
+
+ /*!
+ * @brief Obtain a list with all possible values for the duplicate episode prevention attribute.
+ * @param list out, the list with the values or an empty list, if duplicate episode prevention is not supported by this type.
+ */
+ void GetPreventDuplicateEpisodesValues(std::vector<std::pair<std::string, int>>& list) const;
+
+ /*!
+ * @brief Obtain the default value for the duplicate episode prevention attribute.
+ * @return the default value.
+ */
+ int GetPreventDuplicateEpisodesDefault() const { return m_iPreventDupEpisodesDefault; }
+
+ /*!
+ * @brief Obtain a list with all possible values for the recording group attribute.
+ * @param list out, the list with the values or an empty list, if recording group is not supported by this type.
+ */
+ void GetRecordingGroupValues(std::vector<std::pair<std::string, int>>& list) const;
+
+ /*!
+ * @brief Obtain the default value for the Recording Group attribute.
+ * @return the default value.
+ */
+ int GetRecordingGroupDefault() const { return m_iRecordingGroupDefault; }
+
+ private:
+ void InitDescription();
+ void InitAttributeValues(const PVR_TIMER_TYPE& type);
+ void InitPriorityValues(const PVR_TIMER_TYPE& type);
+ void InitLifetimeValues(const PVR_TIMER_TYPE& type);
+ void InitMaxRecordingsValues(const PVR_TIMER_TYPE& type);
+ void InitPreventDuplicateEpisodesValues(const PVR_TIMER_TYPE& type);
+ void InitRecordingGroupValues(const PVR_TIMER_TYPE& type);
+
+ int m_iClientId = -1;
+ unsigned int m_iTypeId;
+ uint64_t m_iAttributes;
+ std::string m_strDescription;
+ std::vector< std::pair<std::string, int> > m_priorityValues;
+ int m_iPriorityDefault = DEFAULT_RECORDING_PRIORITY;
+ std::vector< std::pair<std::string, int> > m_lifetimeValues;
+ int m_iLifetimeDefault = DEFAULT_RECORDING_LIFETIME;
+ std::vector< std::pair<std::string, int> > m_maxRecordingsValues;
+ int m_iMaxRecordingsDefault = 0;
+ std::vector< std::pair<std::string, int> > m_preventDupEpisodesValues;
+ unsigned int m_iPreventDupEpisodesDefault = DEFAULT_RECORDING_DUPLICATEHANDLING;
+ std::vector< std::pair<std::string, int> > m_recordingGroupValues;
+ unsigned int m_iRecordingGroupDefault = 0;
+ };
+}
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);
+}
diff --git a/xbmc/pvr/timers/PVRTimers.h b/xbmc/pvr/timers/PVRTimers.h
new file mode 100644
index 0000000..6b0e4e7
--- /dev/null
+++ b/xbmc/pvr/timers/PVRTimers.h
@@ -0,0 +1,331 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "pvr/settings/PVRSettings.h"
+#include "threads/Thread.h"
+
+#include <map>
+#include <memory>
+#include <queue>
+#include <vector>
+
+class CDateTime;
+
+namespace PVR
+{
+enum class TimerOperationResult;
+enum class PVREvent;
+
+class CPVRChannel;
+class CPVRClient;
+class CPVREpgInfoTag;
+class CPVRTimerInfoTag;
+class CPVRTimersPath;
+
+class CPVRTimersContainer
+{
+public:
+ /*!
+ * @brief Add a timer tag to this container or update the tag if already present in this
+ * container.
+ * @param The timer tag
+ * @return True, if the update was successful. False, otherwise.
+ */
+ bool UpdateFromClient(const std::shared_ptr<CPVRTimerInfoTag>& timer);
+
+ /*!
+ * @brief Get the timer tag denoted by given client id and timer id.
+ * @param iClientId The client id.
+ * @param iClientIndex The timer id.
+ * @return the timer tag if found, null otherwise.
+ */
+ std::shared_ptr<CPVRTimerInfoTag> GetByClient(int iClientId, int iClientIndex) const;
+
+ typedef std::vector<std::shared_ptr<CPVRTimerInfoTag>> VecTimerInfoTag;
+ typedef std::map<CDateTime, VecTimerInfoTag> MapTags;
+
+ /*!
+ * @brief Get the timertags map.
+ * @return The map.
+ */
+ const MapTags& GetTags() const { return m_tags; }
+
+protected:
+ void InsertEntry(const std::shared_ptr<CPVRTimerInfoTag>& newTimer);
+
+ mutable CCriticalSection m_critSection;
+ unsigned int m_iLastId = 0;
+ MapTags m_tags;
+};
+
+class CPVRTimers : public CPVRTimersContainer, private CThread
+{
+public:
+ CPVRTimers();
+ ~CPVRTimers() override = default;
+
+ /*!
+ * @brief start the timer update thread.
+ */
+ void Start();
+
+ /*!
+ * @brief stop the timer update thread.
+ */
+ void Stop();
+
+ /*!
+ * @brief Update all timers from PVR database and from given clients.
+ * @param clients The PVR clients data should be loaded for. Leave empty for all clients.
+ * @return True on success, false otherwise.
+ */
+ bool Update(const std::vector<std::shared_ptr<CPVRClient>>& clients);
+
+ /*!
+ * @brief unload all timers.
+ */
+ void Unload();
+
+ /*!
+ * @brief Update data with recordings from the given clients, sync with local data.
+ * @param clients The clients to fetch data from. Leave empty to fetch data from all created
+ * clients.
+ * @return True on success, false otherwise.
+ */
+ bool UpdateFromClients(const std::vector<std::shared_ptr<CPVRClient>>& clients);
+
+ /*!
+ * @param bIgnoreReminders include or ignore reminders
+ * @return The tv or radio timer that will be active next (state scheduled), or nullptr if none.
+ */
+ std::shared_ptr<CPVRTimerInfoTag> GetNextActiveTimer(bool bIgnoreReminders = true) const;
+
+ /*!
+ * @return The tv timer that will be active next (state scheduled), or nullptr if none.
+ */
+ std::shared_ptr<CPVRTimerInfoTag> GetNextActiveTVTimer() const;
+
+ /*!
+ * @return The radio timer that will be active next (state scheduled), or nullptr if none.
+ */
+ std::shared_ptr<CPVRTimerInfoTag> GetNextActiveRadioTimer() const;
+
+ /*!
+ * @return All timers that are active (states scheduled or recording)
+ */
+ std::vector<std::shared_ptr<CPVRTimerInfoTag>> GetActiveTimers() const;
+
+ /*!
+ * @return Next due reminder, if any. Removes it from the queue of due reminders.
+ */
+ std::shared_ptr<CPVRTimerInfoTag> GetNextReminderToAnnnounce();
+
+ /*!
+ * Get all timers
+ * @return The list of all timers
+ */
+ std::vector<std::shared_ptr<CPVRTimerInfoTag>> GetAll() const;
+
+ /*!
+ * @return The amount of tv and radio timers that are active (states scheduled or recording)
+ */
+ int AmountActiveTimers() const;
+
+ /*!
+ * @return The amount of tv timers that are active (states scheduled or recording)
+ */
+ int AmountActiveTVTimers() const;
+
+ /*!
+ * @return The amount of radio timers that are active (states scheduled or recording)
+ */
+ int AmountActiveRadioTimers() const;
+
+ /*!
+ * @return All tv and radio timers that are recording
+ */
+ std::vector<std::shared_ptr<CPVRTimerInfoTag>> GetActiveRecordings() const;
+
+ /*!
+ * @return All tv timers that are recording
+ */
+ std::vector<std::shared_ptr<CPVRTimerInfoTag>> GetActiveTVRecordings() const;
+
+ /*!
+ * @return All radio timers that are recording
+ */
+ std::vector<std::shared_ptr<CPVRTimerInfoTag>> GetActiveRadioRecordings() const;
+
+ /*!
+ * @return True when recording, false otherwise.
+ */
+ bool IsRecording() const;
+
+ /*!
+ * @brief Check if a recording is running on the given channel.
+ * @param channel The channel to check.
+ * @return True when recording, false otherwise.
+ */
+ bool IsRecordingOnChannel(const CPVRChannel& channel) const;
+
+ /*!
+ * @brief Obtain the active timer for a given channel.
+ * @param channel The channel to check.
+ * @return the timer, null otherwise.
+ */
+ std::shared_ptr<CPVRTimerInfoTag> GetActiveTimerForChannel(
+ const std::shared_ptr<CPVRChannel>& channel) const;
+
+ /*!
+ * @return The amount of tv and radio timers that are currently recording
+ */
+ int AmountActiveRecordings() const;
+
+ /*!
+ * @return The amount of tv timers that are currently recording
+ */
+ int AmountActiveTVRecordings() const;
+
+ /*!
+ * @return The amount of radio timers that are currently recording
+ */
+ int AmountActiveRadioRecordings() const;
+
+ /*!
+ * @brief Delete all timers on a channel.
+ * @param channel The channel to delete the timers for.
+ * @param bDeleteTimerRules True to delete timer rules too, false otherwise.
+ * @param bCurrentlyActiveOnly True to delete timers that are currently running only.
+ * @return True if timers any were deleted, false otherwise.
+ */
+ bool DeleteTimersOnChannel(const std::shared_ptr<CPVRChannel>& channel,
+ bool bDeleteTimerRules = true,
+ bool bCurrentlyActiveOnly = false);
+
+ /*!
+ * @return Next event time (timer or daily wake up)
+ */
+ CDateTime GetNextEventTime() const;
+
+ /*!
+ * @brief Add a timer to the client. Doesn't add the timer to the container. The backend will do
+ * this.
+ * @param tag The timer to add.
+ * @return True if timer add request was sent correctly, false if not.
+ */
+ bool AddTimer(const std::shared_ptr<CPVRTimerInfoTag>& tag);
+
+ /*!
+ * @brief Delete a timer on the client. Doesn't delete the timer from the container. The backend
+ * will do this.
+ * @param tag The timer to delete.
+ * @param bForce Control what to do in case the timer is currently recording.
+ * True to force to delete the timer, false to return TimerDeleteResult::RECORDING.
+ * @param bDeleteRule Also delete the timer rule that scheduled the timer instead of single timer
+ * only.
+ * @return The result.
+ */
+ TimerOperationResult DeleteTimer(const std::shared_ptr<CPVRTimerInfoTag>& tag,
+ bool bForce = false,
+ bool bDeleteRule = false);
+
+ /*!
+ * @brief Update the timer on the client. Doesn't update the timer in the container. The backend
+ * will do this.
+ * @param tag The timer to update.
+ * @return True if timer update request was sent correctly, false if not.
+ */
+ bool UpdateTimer(const std::shared_ptr<CPVRTimerInfoTag>& tag);
+
+ /*!
+ * @brief Get the timer tag that matches the given epg tag.
+ * @param epgTag The epg tag.
+ * @return The requested timer tag, or nullptr if none was found.
+ */
+ std::shared_ptr<CPVRTimerInfoTag> GetTimerForEpgTag(
+ const std::shared_ptr<CPVREpgInfoTag>& epgTag) const;
+
+ /*!
+ * @brief Get the timer rule for a given timer tag
+ * @param timer The timer to query the timer rule for
+ * @return The timer rule, or nullptr if none was found.
+ */
+ std::shared_ptr<CPVRTimerInfoTag> GetTimerRule(
+ const std::shared_ptr<CPVRTimerInfoTag>& timer) const;
+
+ /*!
+ * @brief Update the channel pointers.
+ */
+ void UpdateChannels();
+
+ /*!
+ * @brief CEventStream callback for PVR events.
+ * @param event The event.
+ */
+ void Notify(const PVREvent& event);
+
+ /*!
+ * @brief Get a timer tag given it's unique ID
+ * @param iTimerId The ID to find
+ * @return The tag, or an empty one when not found
+ */
+ std::shared_ptr<CPVRTimerInfoTag> GetById(unsigned int iTimerId) const;
+
+private:
+ void Process() override;
+
+ /*!
+ * @brief Load all timers from PVR database.
+ * @param clients The PVR clients data should be loaded for. Leave empty for all clients.
+ * @return True on success, false otherwise.
+ */
+ bool LoadFromDatabase(const std::vector<std::shared_ptr<CPVRClient>>& clients);
+
+ void RemoveEntry(const std::shared_ptr<CPVRTimerInfoTag>& tag);
+ bool UpdateEntries(const CPVRTimersContainer& timers, const std::vector<int>& failedClients);
+ bool UpdateEntries(int iMaxNotificationDelay);
+ std::shared_ptr<CPVRTimerInfoTag> UpdateEntry(const std::shared_ptr<CPVRTimerInfoTag>& timer);
+
+ bool AddLocalTimer(const std::shared_ptr<CPVRTimerInfoTag>& tag, bool bNotify);
+ bool DeleteLocalTimer(const std::shared_ptr<CPVRTimerInfoTag>& tag, bool bNotify);
+ bool UpdateLocalTimer(const std::shared_ptr<CPVRTimerInfoTag>& tag);
+ std::shared_ptr<CPVRTimerInfoTag> PersistAndUpdateLocalTimer(
+ const std::shared_ptr<CPVRTimerInfoTag>& timer,
+ const std::shared_ptr<CPVRTimerInfoTag>& parentTimer);
+ void NotifyTimersEvent(bool bAddedOrDeleted = true);
+
+ enum TimerKind
+ {
+ TimerKindAny = 0,
+ TimerKindTV,
+ TimerKindRadio
+ };
+
+ bool KindMatchesTag(const TimerKind& eKind, const std::shared_ptr<CPVRTimerInfoTag>& tag) const;
+
+ std::shared_ptr<CPVRTimerInfoTag> GetNextActiveTimer(const TimerKind& eKind,
+ bool bIgnoreReminders) const;
+ int AmountActiveTimers(const TimerKind& eKind) const;
+ std::vector<std::shared_ptr<CPVRTimerInfoTag>> GetActiveRecordings(const TimerKind& eKind) const;
+ int AmountActiveRecordings(const TimerKind& eKind) const;
+
+ bool CheckAndAppendTimerNotification(std::vector<std::pair<int, std::string>>& timerNotifications,
+ const std::shared_ptr<CPVRTimerInfoTag>& tag,
+ bool bDeleted) const;
+
+ bool m_bIsUpdating = false;
+ CPVRSettings m_settings;
+ std::queue<std::shared_ptr<CPVRTimerInfoTag>> m_remindersToAnnounce;
+ bool m_bReminderRulesUpdatePending = false;
+
+ bool m_bFirstUpdate = true;
+ std::vector<int> m_failedClients;
+};
+} // namespace PVR
diff --git a/xbmc/pvr/timers/PVRTimersPath.cpp b/xbmc/pvr/timers/PVRTimersPath.cpp
new file mode 100644
index 0000000..ea2265c
--- /dev/null
+++ b/xbmc/pvr/timers/PVRTimersPath.cpp
@@ -0,0 +1,82 @@
+/*
+ * 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 "PVRTimersPath.h"
+
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+#include <cstdlib>
+#include <string>
+#include <vector>
+
+using namespace PVR;
+
+const std::string CPVRTimersPath::PATH_ADDTIMER = "pvr://timers/addtimer/";
+const std::string CPVRTimersPath::PATH_NEW = "pvr://timers/new/";
+const std::string CPVRTimersPath::PATH_TV_TIMERS = "pvr://timers/tv/timers/";
+const std::string CPVRTimersPath::PATH_RADIO_TIMERS = "pvr://timers/radio/timers/";
+const std::string CPVRTimersPath::PATH_TV_TIMER_RULES = "pvr://timers/tv/rules/";
+const std::string CPVRTimersPath::PATH_RADIO_TIMER_RULES = "pvr://timers/radio/rules/";
+
+CPVRTimersPath::CPVRTimersPath(const std::string& strPath)
+{
+ Init(strPath);
+}
+
+CPVRTimersPath::CPVRTimersPath(const std::string& strPath, int iClientId, int iParentId)
+{
+ if (Init(strPath))
+ {
+ // set/replace client and parent id.
+ m_path = StringUtils::Format("pvr://timers/{}/{}/{}/{}", m_bRadio ? "radio" : "tv",
+ m_bTimerRules ? "rules" : "timers", iClientId, iParentId);
+ m_iClientId = iClientId;
+ m_iParentId = iParentId;
+ m_bRoot = false;
+ }
+}
+
+CPVRTimersPath::CPVRTimersPath(bool bRadio, bool bTimerRules)
+ : m_path(StringUtils::Format(
+ "pvr://timers/{}/{}", bRadio ? "radio" : "tv", bTimerRules ? "rules" : "timers")),
+ m_bValid(true),
+ m_bRoot(true),
+ m_bRadio(bRadio),
+ m_bTimerRules(bTimerRules)
+{
+}
+
+bool CPVRTimersPath::Init(const std::string& strPath)
+{
+ std::string strVarPath(strPath);
+ URIUtils::RemoveSlashAtEnd(strVarPath);
+
+ m_path = strVarPath;
+ const std::vector<std::string> segments = URIUtils::SplitPath(m_path);
+
+ m_bValid = (((segments.size() == 4) || (segments.size() == 6)) && (segments.at(1) == "timers") &&
+ ((segments.at(2) == "radio") || (segments.at(2) == "tv")) &&
+ ((segments.at(3) == "rules") || (segments.at(3) == "timers")));
+ m_bRoot = (m_bValid && (segments.size() == 4));
+ m_bRadio = (m_bValid && (segments.at(2) == "radio"));
+ m_bTimerRules = (m_bValid && (segments.at(3) == "rules"));
+
+ if (!m_bValid || m_bRoot)
+ {
+ m_iClientId = -1;
+ m_iParentId = 0;
+ }
+ else
+ {
+ m_iClientId = std::stoi(segments.at(4));
+ m_iParentId = std::stoi(segments.at(5));
+ }
+
+ return m_bValid;
+}
diff --git a/xbmc/pvr/timers/PVRTimersPath.h b/xbmc/pvr/timers/PVRTimersPath.h
new file mode 100644
index 0000000..01fd5f0
--- /dev/null
+++ b/xbmc/pvr/timers/PVRTimersPath.h
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <string>
+
+namespace PVR
+{
+class CPVRTimersPath
+{
+public:
+ static const std::string PATH_ADDTIMER;
+ static const std::string PATH_NEW;
+ static const std::string PATH_TV_TIMERS;
+ static const std::string PATH_TV_TIMER_RULES;
+ static const std::string PATH_RADIO_TIMERS;
+ static const std::string PATH_RADIO_TIMER_RULES;
+
+ explicit CPVRTimersPath(const std::string& strPath);
+ CPVRTimersPath(const std::string& strPath, int iClientId, int iParentId);
+ CPVRTimersPath(bool bRadio, bool bTimerRules);
+
+ bool IsValid() const { return m_bValid; }
+
+ const std::string& GetPath() const { return m_path; }
+ bool IsTimersRoot() const { return m_bRoot; }
+ bool IsTimerRule() const { return !IsTimersRoot(); }
+ bool IsRadio() const { return m_bRadio; }
+ bool IsRules() const { return m_bTimerRules; }
+ int GetClientId() const { return m_iClientId; }
+ int GetParentId() const { return m_iParentId; }
+
+private:
+ bool Init(const std::string& strPath);
+
+ std::string m_path;
+ bool m_bValid = false;
+ bool m_bRoot = false;
+ bool m_bRadio = false;
+ bool m_bTimerRules = false;
+ int m_iClientId = -1;
+ int m_iParentId = 0;
+};
+} // namespace PVR