diff options
Diffstat (limited to 'xbmc/pvr/timers')
-rw-r--r-- | xbmc/pvr/timers/CMakeLists.txt | 13 | ||||
-rw-r--r-- | xbmc/pvr/timers/PVRTimerInfoTag.cpp | 1389 | ||||
-rw-r--r-- | xbmc/pvr/timers/PVRTimerInfoTag.h | 644 | ||||
-rw-r--r-- | xbmc/pvr/timers/PVRTimerRuleMatcher.cpp | 193 | ||||
-rw-r--r-- | xbmc/pvr/timers/PVRTimerRuleMatcher.h | 47 | ||||
-rw-r--r-- | xbmc/pvr/timers/PVRTimerType.cpp | 418 | ||||
-rw-r--r-- | xbmc/pvr/timers/PVRTimerType.h | 411 | ||||
-rw-r--r-- | xbmc/pvr/timers/PVRTimers.cpp | 1338 | ||||
-rw-r--r-- | xbmc/pvr/timers/PVRTimers.h | 331 | ||||
-rw-r--r-- | xbmc/pvr/timers/PVRTimersPath.cpp | 82 | ||||
-rw-r--r-- | xbmc/pvr/timers/PVRTimersPath.h | 50 |
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 |