summaryrefslogtreecommitdiffstats
path: root/xbmc/pvr/guilib/PVRGUIActionsTimers.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/pvr/guilib/PVRGUIActionsTimers.cpp')
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsTimers.cpp1009
1 files changed, 1009 insertions, 0 deletions
diff --git a/xbmc/pvr/guilib/PVRGUIActionsTimers.cpp b/xbmc/pvr/guilib/PVRGUIActionsTimers.cpp
new file mode 100644
index 0000000..be8e313
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIActionsTimers.cpp
@@ -0,0 +1,1009 @@
+/*
+ * Copyright (C) 2016-2022 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 "PVRGUIActionsTimers.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "dialogs/GUIDialogProgress.h"
+#include "dialogs/GUIDialogSelect.h"
+#include "dialogs/GUIDialogYesNo.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/WindowIDs.h"
+#include "messaging/helpers/DialogHelper.h"
+#include "messaging/helpers/DialogOKHelper.h"
+#include "pvr/PVREventLogJob.h"
+#include "pvr/PVRItem.h"
+#include "pvr/PVRManager.h"
+#include "pvr/PVRPlaybackState.h"
+#include "pvr/addons/PVRClient.h"
+#include "pvr/channels/PVRChannel.h"
+#include "pvr/channels/PVRChannelGroupMember.h"
+#include "pvr/dialogs/GUIDialogPVRTimerSettings.h"
+#include "pvr/epg/EpgInfoTag.h"
+#include "pvr/guilib/PVRGUIActionsChannels.h"
+#include "pvr/guilib/PVRGUIActionsParentalControl.h"
+#include "pvr/guilib/PVRGUIActionsPlayback.h"
+#include "pvr/recordings/PVRRecording.h"
+#include "pvr/timers/PVRTimerInfoTag.h"
+#include "pvr/timers/PVRTimers.h"
+#include "settings/Settings.h"
+#include "utils/StringUtils.h"
+#include "utils/SystemInfo.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <string>
+#include <thread>
+#include <utility>
+
+using namespace PVR;
+using namespace KODI::MESSAGING;
+
+CPVRGUIActionsTimers::CPVRGUIActionsTimers()
+ : m_settings({CSettings::SETTING_PVRRECORD_INSTANTRECORDTIME,
+ CSettings::SETTING_PVRRECORD_INSTANTRECORDACTION,
+ CSettings::SETTING_PVRREMINDERS_AUTOCLOSEDELAY,
+ CSettings::SETTING_PVRREMINDERS_AUTORECORD,
+ CSettings::SETTING_PVRREMINDERS_AUTOSWITCH})
+{
+}
+
+bool CPVRGUIActionsTimers::ShowTimerSettings(const std::shared_ptr<CPVRTimerInfoTag>& timer) const
+{
+ CGUIDialogPVRTimerSettings* pDlgInfo =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogPVRTimerSettings>(
+ WINDOW_DIALOG_PVR_TIMER_SETTING);
+ if (!pDlgInfo)
+ {
+ CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_PVR_TIMER_SETTING!");
+ return false;
+ }
+
+ pDlgInfo->SetTimer(timer);
+ pDlgInfo->Open();
+
+ return pDlgInfo->IsConfirmed();
+}
+
+bool CPVRGUIActionsTimers::AddReminder(const CFileItem& item) const
+{
+ const std::shared_ptr<CPVREpgInfoTag> epgTag = CPVRItem(item).GetEpgInfoTag();
+ if (!epgTag)
+ {
+ CLog::LogF(LOGERROR, "No epg tag!");
+ return false;
+ }
+
+ if (CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(epgTag))
+ {
+ HELPERS::ShowOKDialogText(CVariant{19033}, // "Information"
+ CVariant{19034}); // "There is already a timer set for this event"
+ return false;
+ }
+
+ const std::shared_ptr<CPVRTimerInfoTag> newTimer =
+ CPVRTimerInfoTag::CreateReminderFromEpg(epgTag);
+ if (!newTimer)
+ {
+ HELPERS::ShowOKDialogText(CVariant{19033}, // "Information"
+ CVariant{19094}); // Timer creation failed. Unsupported timer type.
+ return false;
+ }
+
+ return AddTimer(newTimer);
+}
+
+bool CPVRGUIActionsTimers::AddTimer(bool bRadio) const
+{
+ const std::shared_ptr<CPVRTimerInfoTag> newTimer(new CPVRTimerInfoTag(bRadio));
+ if (ShowTimerSettings(newTimer))
+ {
+ return AddTimer(newTimer);
+ }
+ return false;
+}
+
+bool CPVRGUIActionsTimers::AddTimer(const CFileItem& item, bool bShowTimerSettings) const
+{
+ return AddTimer(item, false, bShowTimerSettings, false);
+}
+
+bool CPVRGUIActionsTimers::AddTimerRule(const CFileItem& item,
+ bool bShowTimerSettings,
+ bool bFallbackToOneShotTimer) const
+{
+ return AddTimer(item, true, bShowTimerSettings, bFallbackToOneShotTimer);
+}
+
+bool CPVRGUIActionsTimers::AddTimer(const CFileItem& item,
+ bool bCreateRule,
+ bool bShowTimerSettings,
+ bool bFallbackToOneShotTimer) const
+{
+ const std::shared_ptr<CPVRChannel> channel(CPVRItem(item).GetChannel());
+ if (!channel)
+ {
+ CLog::LogF(LOGERROR, "No channel!");
+ return false;
+ }
+
+ if (CServiceBroker::GetPVRManager().Get<PVR::GUI::Parental>().CheckParentalLock(channel) !=
+ ParentalCheckResult::SUCCESS)
+ return false;
+
+ std::shared_ptr<CPVREpgInfoTag> epgTag = CPVRItem(item).GetEpgInfoTag();
+ if (epgTag)
+ {
+ if (epgTag->IsGapTag())
+ epgTag.reset(); // for gap tags, we can only create instant timers
+ }
+ else if (bCreateRule)
+ {
+ CLog::LogF(LOGERROR, "No epg tag!");
+ return false;
+ }
+
+ std::shared_ptr<CPVRTimerInfoTag> timer(
+ bCreateRule || !epgTag ? nullptr
+ : CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(epgTag));
+ std::shared_ptr<CPVRTimerInfoTag> rule(
+ bCreateRule ? CServiceBroker::GetPVRManager().Timers()->GetTimerRule(timer) : nullptr);
+ if (timer || rule)
+ {
+ HELPERS::ShowOKDialogText(
+ CVariant{19033},
+ CVariant{19034}); // "Information", "There is already a timer set for this event"
+ return false;
+ }
+
+ std::shared_ptr<CPVRTimerInfoTag> newTimer(
+ epgTag ? CPVRTimerInfoTag::CreateFromEpg(epgTag, bCreateRule)
+ : CPVRTimerInfoTag::CreateInstantTimerTag(channel));
+ if (!newTimer)
+ {
+ if (bCreateRule && bFallbackToOneShotTimer)
+ newTimer = CPVRTimerInfoTag::CreateFromEpg(epgTag, false);
+
+ if (!newTimer)
+ {
+ HELPERS::ShowOKDialogText(
+ CVariant{19033}, // "Information"
+ bCreateRule ? CVariant{19095} // Timer rule creation failed. Unsupported timer type.
+ : CVariant{19094}); // Timer creation failed. Unsupported timer type.
+ return false;
+ }
+ }
+
+ if (bShowTimerSettings)
+ {
+ if (!ShowTimerSettings(newTimer))
+ return false;
+ }
+
+ return AddTimer(newTimer);
+}
+
+bool CPVRGUIActionsTimers::AddTimer(const std::shared_ptr<CPVRTimerInfoTag>& item) const
+{
+ if (!item->Channel() && !item->GetTimerType()->IsEpgBasedTimerRule())
+ {
+ CLog::LogF(LOGERROR, "No channel given");
+ HELPERS::ShowOKDialogText(
+ CVariant{257},
+ CVariant{
+ 19109}); // "Error", "Could not save the timer. Check the log for more information about this message."
+ return false;
+ }
+
+ if (!item->IsTimerRule() && item->GetEpgInfoTag() && !item->GetEpgInfoTag()->IsRecordable())
+ {
+ HELPERS::ShowOKDialogText(
+ CVariant{19033},
+ CVariant{19189}); // "Information", "The PVR backend does not allow to record this event."
+ return false;
+ }
+
+ if (CServiceBroker::GetPVRManager().Get<PVR::GUI::Parental>().CheckParentalLock(
+ item->Channel()) != ParentalCheckResult::SUCCESS)
+ return false;
+
+ if (!CServiceBroker::GetPVRManager().Timers()->AddTimer(item))
+ {
+ HELPERS::ShowOKDialogText(
+ CVariant{257},
+ CVariant{
+ 19109}); // "Error", "Could not save the timer. Check the log for more information about this message."
+ return false;
+ }
+
+ return true;
+}
+
+namespace
+{
+enum PVRRECORD_INSTANTRECORDACTION
+{
+ NONE = -1,
+ RECORD_CURRENT_SHOW = 0,
+ RECORD_INSTANTRECORDTIME = 1,
+ ASK = 2,
+ RECORD_30_MINUTES = 3,
+ RECORD_60_MINUTES = 4,
+ RECORD_120_MINUTES = 5,
+ RECORD_NEXT_SHOW = 6
+};
+
+class InstantRecordingActionSelector
+{
+public:
+ explicit InstantRecordingActionSelector(int iInstantRecordTime);
+ virtual ~InstantRecordingActionSelector() = default;
+
+ void AddAction(PVRRECORD_INSTANTRECORDACTION eAction, const std::string& title);
+ void PreSelectAction(PVRRECORD_INSTANTRECORDACTION eAction);
+ PVRRECORD_INSTANTRECORDACTION Select();
+
+private:
+ int m_iInstantRecordTime;
+ CGUIDialogSelect* m_pDlgSelect; // not owner!
+ std::map<PVRRECORD_INSTANTRECORDACTION, int> m_actions;
+};
+
+InstantRecordingActionSelector::InstantRecordingActionSelector(int iInstantRecordTime)
+ : m_iInstantRecordTime(iInstantRecordTime),
+ m_pDlgSelect(CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(
+ WINDOW_DIALOG_SELECT))
+{
+ if (m_pDlgSelect)
+ {
+ m_pDlgSelect->Reset();
+ m_pDlgSelect->SetMultiSelection(false);
+ m_pDlgSelect->SetHeading(CVariant{19086}); // Instant recording action
+ }
+ else
+ {
+ CLog::LogF(LOGERROR, "Unable to obtain WINDOW_DIALOG_SELECT instance");
+ }
+}
+
+void InstantRecordingActionSelector::AddAction(PVRRECORD_INSTANTRECORDACTION eAction,
+ const std::string& title)
+{
+ if (m_actions.find(eAction) == m_actions.end())
+ {
+ switch (eAction)
+ {
+ case RECORD_INSTANTRECORDTIME:
+ m_pDlgSelect->Add(
+ StringUtils::Format(g_localizeStrings.Get(19090),
+ m_iInstantRecordTime)); // Record next <default duration> minutes
+ break;
+ case RECORD_30_MINUTES:
+ m_pDlgSelect->Add(
+ StringUtils::Format(g_localizeStrings.Get(19090), 30)); // Record next 30 minutes
+ break;
+ case RECORD_60_MINUTES:
+ m_pDlgSelect->Add(
+ StringUtils::Format(g_localizeStrings.Get(19090), 60)); // Record next 60 minutes
+ break;
+ case RECORD_120_MINUTES:
+ m_pDlgSelect->Add(
+ StringUtils::Format(g_localizeStrings.Get(19090), 120)); // Record next 120 minutes
+ break;
+ case RECORD_CURRENT_SHOW:
+ m_pDlgSelect->Add(StringUtils::Format(g_localizeStrings.Get(19091),
+ title)); // Record current show (<title>)
+ break;
+ case RECORD_NEXT_SHOW:
+ m_pDlgSelect->Add(StringUtils::Format(g_localizeStrings.Get(19092),
+ title)); // Record next show (<title>)
+ break;
+ case NONE:
+ case ASK:
+ default:
+ return;
+ }
+
+ m_actions.insert(std::make_pair(eAction, static_cast<int>(m_actions.size())));
+ }
+}
+
+void InstantRecordingActionSelector::PreSelectAction(PVRRECORD_INSTANTRECORDACTION eAction)
+{
+ const auto& it = m_actions.find(eAction);
+ if (it != m_actions.end())
+ m_pDlgSelect->SetSelected(it->second);
+}
+
+PVRRECORD_INSTANTRECORDACTION InstantRecordingActionSelector::Select()
+{
+ PVRRECORD_INSTANTRECORDACTION eAction = NONE;
+
+ m_pDlgSelect->Open();
+
+ if (m_pDlgSelect->IsConfirmed())
+ {
+ int iSelection = m_pDlgSelect->GetSelectedItem();
+ const auto it =
+ std::find_if(m_actions.cbegin(), m_actions.cend(),
+ [iSelection](const auto& action) { return action.second == iSelection; });
+
+ if (it != m_actions.cend())
+ eAction = (*it).first;
+ }
+
+ return eAction;
+}
+
+} // unnamed namespace
+
+bool CPVRGUIActionsTimers::ToggleRecordingOnPlayingChannel()
+{
+ const std::shared_ptr<CPVRChannel> channel =
+ CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel();
+ if (channel && channel->CanRecord())
+ return SetRecordingOnChannel(
+ channel, !CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*channel));
+
+ return false;
+}
+
+bool CPVRGUIActionsTimers::SetRecordingOnChannel(const std::shared_ptr<CPVRChannel>& channel,
+ bool bOnOff)
+{
+ bool bReturn = false;
+
+ if (!channel)
+ return bReturn;
+
+ if (CServiceBroker::GetPVRManager().Get<PVR::GUI::Parental>().CheckParentalLock(channel) !=
+ ParentalCheckResult::SUCCESS)
+ return bReturn;
+
+ const std::shared_ptr<CPVRClient> client =
+ CServiceBroker::GetPVRManager().GetClient(channel->ClientID());
+ if (client && client->GetClientCapabilities().SupportsTimers())
+ {
+ /* timers are supported on this channel */
+ if (bOnOff && !CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*channel))
+ {
+ std::shared_ptr<CPVREpgInfoTag> epgTag;
+ int iDuration = m_settings.GetIntValue(CSettings::SETTING_PVRRECORD_INSTANTRECORDTIME);
+
+ int iAction = m_settings.GetIntValue(CSettings::SETTING_PVRRECORD_INSTANTRECORDACTION);
+ switch (iAction)
+ {
+ case RECORD_CURRENT_SHOW:
+ epgTag = channel->GetEPGNow();
+ break;
+
+ case RECORD_INSTANTRECORDTIME:
+ epgTag.reset();
+ break;
+
+ case ASK:
+ {
+ PVRRECORD_INSTANTRECORDACTION ePreselect = RECORD_INSTANTRECORDTIME;
+ const int iDurationDefault =
+ m_settings.GetIntValue(CSettings::SETTING_PVRRECORD_INSTANTRECORDTIME);
+ InstantRecordingActionSelector selector(iDurationDefault);
+ std::shared_ptr<CPVREpgInfoTag> epgTagNext;
+
+ // fixed length recordings
+ selector.AddAction(RECORD_30_MINUTES, "");
+ selector.AddAction(RECORD_60_MINUTES, "");
+ selector.AddAction(RECORD_120_MINUTES, "");
+
+ if (iDurationDefault != 30 && iDurationDefault != 60 && iDurationDefault != 120)
+ selector.AddAction(RECORD_INSTANTRECORDTIME, "");
+
+ // epg-based recordings
+ epgTag = channel->GetEPGNow();
+ if (epgTag)
+ {
+ bool bLocked = CServiceBroker::GetPVRManager().IsParentalLocked(epgTag);
+
+ // "now"
+ const std::string currentTitle =
+ bLocked ? g_localizeStrings.Get(19266) /* Parental locked */ : epgTag->Title();
+ selector.AddAction(RECORD_CURRENT_SHOW, currentTitle);
+ ePreselect = RECORD_CURRENT_SHOW;
+
+ // "next"
+ epgTagNext = channel->GetEPGNext();
+ if (epgTagNext)
+ {
+ const std::string nextTitle = bLocked
+ ? g_localizeStrings.Get(19266) /* Parental locked */
+ : epgTagNext->Title();
+ selector.AddAction(RECORD_NEXT_SHOW, nextTitle);
+
+ // be smart. if current show is almost over, preselect next show.
+ if (epgTag->ProgressPercentage() > 90.0f)
+ ePreselect = RECORD_NEXT_SHOW;
+ }
+ }
+
+ if (ePreselect == RECORD_INSTANTRECORDTIME)
+ {
+ if (iDurationDefault == 30)
+ ePreselect = RECORD_30_MINUTES;
+ else if (iDurationDefault == 60)
+ ePreselect = RECORD_60_MINUTES;
+ else if (iDurationDefault == 120)
+ ePreselect = RECORD_120_MINUTES;
+ }
+
+ selector.PreSelectAction(ePreselect);
+
+ PVRRECORD_INSTANTRECORDACTION eSelected = selector.Select();
+ switch (eSelected)
+ {
+ case NONE:
+ return false; // dialog canceled
+
+ case RECORD_30_MINUTES:
+ iDuration = 30;
+ epgTag.reset();
+ break;
+
+ case RECORD_60_MINUTES:
+ iDuration = 60;
+ epgTag.reset();
+ break;
+
+ case RECORD_120_MINUTES:
+ iDuration = 120;
+ epgTag.reset();
+ break;
+
+ case RECORD_INSTANTRECORDTIME:
+ iDuration = iDurationDefault;
+ epgTag.reset();
+ break;
+
+ case RECORD_CURRENT_SHOW:
+ break;
+
+ case RECORD_NEXT_SHOW:
+ epgTag = epgTagNext;
+ break;
+
+ default:
+ CLog::LogF(LOGERROR,
+ "Unknown instant record action selection ({}), defaulting to fixed "
+ "length recording.",
+ static_cast<int>(eSelected));
+ epgTag.reset();
+ break;
+ }
+ break;
+ }
+
+ default:
+ CLog::LogF(LOGERROR,
+ "Unknown instant record action setting value ({}), defaulting to fixed "
+ "length recording.",
+ iAction);
+ break;
+ }
+
+ const std::shared_ptr<CPVRTimerInfoTag> newTimer(
+ epgTag ? CPVRTimerInfoTag::CreateFromEpg(epgTag, false)
+ : CPVRTimerInfoTag::CreateInstantTimerTag(channel, iDuration));
+
+ if (newTimer)
+ bReturn = CServiceBroker::GetPVRManager().Timers()->AddTimer(newTimer);
+
+ if (!bReturn)
+ HELPERS::ShowOKDialogText(
+ CVariant{257},
+ CVariant{
+ 19164}); // "Error", "Could not start recording. Check the log for more information about this message."
+ }
+ else if (!bOnOff && CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*channel))
+ {
+ /* delete active timers */
+ bReturn =
+ CServiceBroker::GetPVRManager().Timers()->DeleteTimersOnChannel(channel, true, true);
+
+ if (!bReturn)
+ HELPERS::ShowOKDialogText(
+ CVariant{257},
+ CVariant{
+ 19170}); // "Error", "Could not stop recording. Check the log for more information about this message."
+ }
+ }
+
+ return bReturn;
+}
+
+bool CPVRGUIActionsTimers::ToggleTimer(const CFileItem& item) const
+{
+ if (!item.HasEPGInfoTag())
+ return false;
+
+ const std::shared_ptr<CPVRTimerInfoTag> timer(CPVRItem(item).GetTimerInfoTag());
+ if (timer)
+ {
+ if (timer->IsRecording())
+ return StopRecording(item);
+ else
+ return DeleteTimer(item);
+ }
+ else
+ return AddTimer(item, false);
+}
+
+bool CPVRGUIActionsTimers::ToggleTimerState(const CFileItem& item) const
+{
+ if (!item.HasPVRTimerInfoTag())
+ return false;
+
+ const std::shared_ptr<CPVRTimerInfoTag> timer = item.GetPVRTimerInfoTag();
+ if (timer->IsDisabled())
+ timer->SetState(PVR_TIMER_STATE_SCHEDULED);
+ else
+ timer->SetState(PVR_TIMER_STATE_DISABLED);
+
+ if (CServiceBroker::GetPVRManager().Timers()->UpdateTimer(timer))
+ return true;
+
+ HELPERS::ShowOKDialogText(
+ CVariant{257},
+ CVariant{
+ 19263}); // "Error", "Could not update the timer. Check the log for more information about this message."
+ return false;
+}
+
+bool CPVRGUIActionsTimers::EditTimer(const CFileItem& item) const
+{
+ const std::shared_ptr<CPVRTimerInfoTag> timer(CPVRItem(item).GetTimerInfoTag());
+ if (!timer)
+ {
+ CLog::LogF(LOGERROR, "No timer!");
+ return false;
+ }
+
+ // clone the timer.
+ const std::shared_ptr<CPVRTimerInfoTag> newTimer(new CPVRTimerInfoTag);
+ newTimer->UpdateEntry(timer);
+
+ if (ShowTimerSettings(newTimer) &&
+ (!timer->GetTimerType()->IsReadOnly() || timer->GetTimerType()->SupportsEnableDisable()))
+ {
+ if (newTimer->GetTimerType() == timer->GetTimerType())
+ {
+ if (CServiceBroker::GetPVRManager().Timers()->UpdateTimer(newTimer))
+ return true;
+
+ HELPERS::ShowOKDialogText(
+ CVariant{257},
+ CVariant{
+ 19263}); // "Error", "Could not update the timer. Check the log for more information about this message."
+ return false;
+ }
+ else
+ {
+ // timer type changed. delete the original timer, then create the new timer. this order is
+ // important. for instance, the new timer might be a rule which schedules the original timer.
+ // deleting the original timer after creating the rule would do literally this and we would
+ // end up with one timer missing wrt to the rule defined by the new timer.
+ if (DeleteTimer(timer, timer->IsRecording(), false))
+ {
+ if (AddTimer(newTimer))
+ return true;
+
+ // rollback.
+ return AddTimer(timer);
+ }
+ }
+ }
+ return false;
+}
+
+bool CPVRGUIActionsTimers::EditTimerRule(const CFileItem& item) const
+{
+ const std::shared_ptr<CFileItem> parentTimer = GetTimerRule(item);
+ if (parentTimer)
+ return EditTimer(*parentTimer);
+
+ return false;
+}
+
+std::shared_ptr<CFileItem> CPVRGUIActionsTimers::GetTimerRule(const CFileItem& item) const
+{
+ std::shared_ptr<CPVRTimerInfoTag> timer;
+ if (item.HasEPGInfoTag())
+ timer = CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(item.GetEPGInfoTag());
+ else if (item.HasPVRTimerInfoTag())
+ timer = item.GetPVRTimerInfoTag();
+
+ if (timer)
+ {
+ timer = CServiceBroker::GetPVRManager().Timers()->GetTimerRule(timer);
+ if (timer)
+ return std::make_shared<CFileItem>(timer);
+ }
+ return {};
+}
+
+bool CPVRGUIActionsTimers::DeleteTimer(const CFileItem& item) const
+{
+ return DeleteTimer(item, false, false);
+}
+
+bool CPVRGUIActionsTimers::DeleteTimerRule(const CFileItem& item) const
+{
+ return DeleteTimer(item, false, true);
+}
+
+bool CPVRGUIActionsTimers::DeleteTimer(const CFileItem& item,
+ bool bIsRecording,
+ bool bDeleteRule) const
+{
+ std::shared_ptr<CPVRTimerInfoTag> timer;
+ const std::shared_ptr<CPVRRecording> recording(CPVRItem(item).GetRecording());
+ if (recording)
+ timer = recording->GetRecordingTimer();
+
+ if (!timer)
+ timer = CPVRItem(item).GetTimerInfoTag();
+
+ if (!timer)
+ {
+ CLog::LogF(LOGERROR, "No timer!");
+ return false;
+ }
+
+ if (bDeleteRule && !timer->IsTimerRule())
+ timer = CServiceBroker::GetPVRManager().Timers()->GetTimerRule(timer);
+
+ if (!timer)
+ {
+ CLog::LogF(LOGERROR, "No timer rule!");
+ return false;
+ }
+
+ if (bIsRecording)
+ {
+ if (ConfirmStopRecording(timer))
+ {
+ if (CServiceBroker::GetPVRManager().Timers()->DeleteTimer(timer, true, false) ==
+ TimerOperationResult::OK)
+ return true;
+
+ HELPERS::ShowOKDialogText(
+ CVariant{257},
+ CVariant{
+ 19170}); // "Error", "Could not stop recording. Check the log for more information about this message."
+ return false;
+ }
+ }
+ else if (!timer->GetTimerType()->AllowsDelete())
+ {
+ return false;
+ }
+ else
+ {
+ bool bAlsoDeleteRule(false);
+ if (ConfirmDeleteTimer(timer, bAlsoDeleteRule))
+ return DeleteTimer(timer, false, bAlsoDeleteRule);
+ }
+ return false;
+}
+
+bool CPVRGUIActionsTimers::DeleteTimer(const std::shared_ptr<CPVRTimerInfoTag>& timer,
+ bool bIsRecording,
+ bool bDeleteRule) const
+{
+ TimerOperationResult result =
+ CServiceBroker::GetPVRManager().Timers()->DeleteTimer(timer, bIsRecording, bDeleteRule);
+ switch (result)
+ {
+ case TimerOperationResult::RECORDING:
+ {
+ // recording running. ask the user if it should be deleted anyway
+ if (HELPERS::ShowYesNoDialogText(
+ CVariant{122}, // "Confirm delete"
+ CVariant{
+ 19122}) // "This timer is still recording. Are you sure you want to delete this timer?"
+ != HELPERS::DialogResponse::CHOICE_YES)
+ return false;
+
+ return DeleteTimer(timer, true, bDeleteRule);
+ }
+ case TimerOperationResult::OK:
+ {
+ return true;
+ }
+ case TimerOperationResult::FAILED:
+ {
+ HELPERS::ShowOKDialogText(
+ CVariant{257},
+ CVariant{
+ 19110}); // "Error", "Could not delete the timer. Check the log for more information about this message."
+ return false;
+ }
+ default:
+ {
+ CLog::LogF(LOGERROR, "Unhandled TimerOperationResult ({})!", static_cast<int>(result));
+ break;
+ }
+ }
+ return false;
+}
+
+bool CPVRGUIActionsTimers::ConfirmDeleteTimer(const std::shared_ptr<CPVRTimerInfoTag>& timer,
+ bool& bDeleteRule) const
+{
+ bool bConfirmed(false);
+ const std::shared_ptr<CPVRTimerInfoTag> parentTimer(
+ CServiceBroker::GetPVRManager().Timers()->GetTimerRule(timer));
+
+ if (parentTimer && parentTimer->GetTimerType()->AllowsDelete())
+ {
+ // timer was scheduled by a deletable timer rule. prompt user for confirmation for deleting the timer rule, including scheduled timers.
+ bool bCancel(false);
+ bDeleteRule = CGUIDialogYesNo::ShowAndGetInput(
+ CVariant{122}, // "Confirm delete"
+ CVariant{
+ 840}, // "Do you want to delete only this timer or also the timer rule that has scheduled it?"
+ CVariant{""}, CVariant{timer->Title()}, bCancel, CVariant{841}, // "Only this"
+ CVariant{593}, // "All"
+ 0); // no autoclose
+ bConfirmed = !bCancel;
+ }
+ else
+ {
+ bDeleteRule = false;
+
+ // prompt user for confirmation for deleting the timer
+ bConfirmed = CGUIDialogYesNo::ShowAndGetInput(
+ CVariant{122}, // "Confirm delete"
+ timer->IsTimerRule()
+ ? CVariant{845}
+ // "Are you sure you want to delete this timer rule and all timers it has scheduled?"
+ : CVariant{846}, // "Are you sure you want to delete this timer?"
+ CVariant{""}, CVariant{timer->Title()});
+ }
+
+ return bConfirmed;
+}
+
+bool CPVRGUIActionsTimers::StopRecording(const CFileItem& item) const
+{
+ if (!DeleteTimer(item, true, false))
+ return false;
+
+ CServiceBroker::GetPVRManager().TriggerRecordingsUpdate();
+ return true;
+}
+
+bool CPVRGUIActionsTimers::ConfirmStopRecording(
+ const std::shared_ptr<CPVRTimerInfoTag>& timer) const
+{
+ return CGUIDialogYesNo::ShowAndGetInput(
+ CVariant{847}, // "Confirm stop recording"
+ CVariant{848}, // "Are you sure you want to stop this recording?"
+ CVariant{""}, CVariant{timer->Title()});
+}
+
+namespace
+{
+std::string GetAnnouncerText(const std::shared_ptr<CPVRTimerInfoTag>& timer, int idEpg, int idNoEpg)
+{
+ std::string text;
+ if (timer->IsEpgBased())
+ {
+ text = StringUtils::Format(g_localizeStrings.Get(idEpg),
+ timer->Title(), // tv show title
+ timer->ChannelName(),
+ timer->StartAsLocalTime().GetAsLocalizedDateTime(false, false));
+ }
+ else
+ {
+ text = StringUtils::Format(g_localizeStrings.Get(idNoEpg), timer->ChannelName(),
+ timer->StartAsLocalTime().GetAsLocalizedDateTime(false, false));
+ }
+ return text;
+}
+
+void AddEventLogEntry(const std::shared_ptr<CPVRTimerInfoTag>& timer, int idEpg, int idNoEpg)
+{
+ std::string name;
+ std::string icon;
+
+ const std::shared_ptr<CPVRClient> client =
+ CServiceBroker::GetPVRManager().GetClient(timer->GetTimerType()->GetClientId());
+ if (client)
+ {
+ name = client->GetFriendlyName();
+ icon = client->Icon();
+ }
+ else
+ {
+ name = g_sysinfo.GetAppName();
+ icon = "special://xbmc/media/icon256x256.png";
+ }
+
+ CPVREventLogJob* job = new CPVREventLogJob;
+ job->AddEvent(false, // do not display a toast, only log event
+ EventLevel::Information, // info, no error
+ name, GetAnnouncerText(timer, idEpg, idNoEpg), icon);
+ CServiceBroker::GetJobManager()->AddJob(job, nullptr);
+}
+} // unnamed namespace
+
+void CPVRGUIActionsTimers::AnnounceReminder(const std::shared_ptr<CPVRTimerInfoTag>& timer) const
+{
+ if (!timer->IsReminder())
+ {
+ CLog::LogF(LOGERROR, "No reminder timer!");
+ return;
+ }
+
+ if (timer->EndAsUTC() < CDateTime::GetUTCDateTime())
+ {
+ // expired. timer end is in the past. write event log entry.
+ AddEventLogEntry(timer, 19305, 19306); // Deleted missed PVR reminder ...
+ return;
+ }
+
+ if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingChannel(timer->Channel()))
+ {
+ // no need for an announcement. channel in question is already playing.
+ return;
+ }
+
+ // show the reminder dialog
+ CGUIDialogProgress* dialog =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(
+ WINDOW_DIALOG_PROGRESS);
+ if (!dialog)
+ return;
+
+ dialog->Reset();
+
+ dialog->SetHeading(CVariant{19312}); // "PVR reminder"
+ dialog->ShowChoice(0, CVariant{19165}); // "Switch"
+
+ std::string text = GetAnnouncerText(timer, 19307, 19308); // Reminder for ...
+
+ bool bCanRecord = false;
+ const std::shared_ptr<CPVRClient> client =
+ CServiceBroker::GetPVRManager().GetClient(timer->ClientID());
+ if (client && client->GetClientCapabilities().SupportsTimers())
+ {
+ bCanRecord = true;
+ dialog->ShowChoice(1, CVariant{264}); // "Record"
+ dialog->ShowChoice(2, CVariant{222}); // "Cancel"
+
+ if (m_settings.GetBoolValue(CSettings::SETTING_PVRREMINDERS_AUTORECORD))
+ text += "\n\n" + g_localizeStrings.Get(
+ 19309); // (Auto-close of this reminder will schedule a recording...)
+ else if (m_settings.GetBoolValue(CSettings::SETTING_PVRREMINDERS_AUTOSWITCH))
+ text += "\n\n" + g_localizeStrings.Get(
+ 19331); // (Auto-close of this reminder will switch to channel...)
+ }
+ else
+ {
+ dialog->ShowChoice(1, CVariant{222}); // "Cancel"
+ }
+
+ dialog->SetText(text);
+ dialog->SetPercentage(100);
+
+ dialog->Open();
+
+ int result = CGUIDialogProgress::CHOICE_NONE;
+
+ static constexpr int PROGRESS_TIMESLICE_MILLISECS = 50;
+
+ const int iWait = m_settings.GetIntValue(CSettings::SETTING_PVRREMINDERS_AUTOCLOSEDELAY) * 1000;
+ int iRemaining = iWait;
+ while (iRemaining > 0)
+ {
+ result = dialog->GetChoice();
+ if (result != CGUIDialogProgress::CHOICE_NONE)
+ break;
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(PROGRESS_TIMESLICE_MILLISECS));
+
+ iRemaining -= PROGRESS_TIMESLICE_MILLISECS;
+ dialog->SetPercentage(iRemaining * 100 / iWait);
+ dialog->Progress();
+ }
+
+ dialog->Close();
+
+ bool bAutoClosed = (iRemaining <= 0);
+ bool bSwitch = (result == 0);
+ bool bRecord = (result == 1);
+
+ if (bAutoClosed)
+ {
+ bRecord = (bCanRecord && m_settings.GetBoolValue(CSettings::SETTING_PVRREMINDERS_AUTORECORD));
+ bSwitch = m_settings.GetBoolValue(CSettings::SETTING_PVRREMINDERS_AUTOSWITCH);
+ }
+
+ if (bRecord)
+ {
+ std::shared_ptr<CPVRTimerInfoTag> newTimer;
+
+ std::shared_ptr<CPVREpgInfoTag> epgTag = timer->GetEpgInfoTag();
+ if (epgTag)
+ {
+ newTimer = CPVRTimerInfoTag::CreateFromEpg(epgTag, false);
+ if (newTimer)
+ {
+ // an epgtag can only have max one timer - we need to clear the reminder to be able to
+ // attach the recording timer
+ DeleteTimer(timer, false, false);
+ }
+ }
+ else
+ {
+ int iDuration = (timer->EndAsUTC() - timer->StartAsUTC()).GetSecondsTotal() / 60;
+ newTimer = CPVRTimerInfoTag::CreateTimerTag(timer->Channel(), timer->StartAsUTC(), iDuration);
+ }
+
+ if (newTimer)
+ {
+ // schedule recording
+ AddTimer(CFileItem(newTimer), false);
+ }
+
+ if (bAutoClosed)
+ {
+ AddEventLogEntry(timer, 19310,
+ 19311); // Scheduled recording for auto-closed PVR reminder ...
+ }
+ }
+
+ if (bSwitch)
+ {
+ const std::shared_ptr<CPVRChannelGroupMember> groupMember =
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().GetChannelGroupMember(
+ timer->Channel());
+ if (groupMember)
+ {
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().SwitchToChannel(
+ CFileItem(groupMember), false);
+
+ if (bAutoClosed)
+ {
+ AddEventLogEntry(timer, 19332,
+ 19333); // Switched channel for auto-closed PVR reminder ...
+ }
+ }
+ }
+}
+
+void CPVRGUIActionsTimers::AnnounceReminders() const
+{
+ // Prevent multiple yesno dialogs, all on same call stack, due to gui message processing while dialog is open.
+ if (m_bReminderAnnouncementRunning)
+ return;
+
+ m_bReminderAnnouncementRunning = true;
+ std::shared_ptr<CPVRTimerInfoTag> timer =
+ CServiceBroker::GetPVRManager().Timers()->GetNextReminderToAnnnounce();
+ while (timer)
+ {
+ AnnounceReminder(timer);
+ timer = CServiceBroker::GetPVRManager().Timers()->GetNextReminderToAnnnounce();
+ }
+ m_bReminderAnnouncementRunning = false;
+}