1 /*
2  *  Copyright (C) 2016-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "PVRGUIActions.h"
10 
11 #include "Application.h"
12 #include "FileItem.h"
13 #include "ServiceBroker.h"
14 #include "Util.h"
15 #include "cores/DataCacheCore.h"
16 #include "dialogs/GUIDialogBusy.h"
17 #include "dialogs/GUIDialogKaiToast.h"
18 #include "dialogs/GUIDialogNumeric.h"
19 #include "dialogs/GUIDialogProgress.h"
20 #include "dialogs/GUIDialogSelect.h"
21 #include "dialogs/GUIDialogYesNo.h"
22 #include "filesystem/IDirectory.h"
23 #include "guilib/GUIComponent.h"
24 #include "guilib/GUIKeyboardFactory.h"
25 #include "guilib/GUIWindowManager.h"
26 #include "guilib/LocalizeStrings.h"
27 #include "guilib/WindowIDs.h"
28 #include "messaging/ApplicationMessenger.h"
29 #include "messaging/helpers/DialogHelper.h"
30 #include "messaging/helpers/DialogOKHelper.h"
31 #include "network/Network.h"
32 #include "pvr/PVRDatabase.h"
33 #include "pvr/PVREventLogJob.h"
34 #include "pvr/PVRItem.h"
35 #include "pvr/PVRManager.h"
36 #include "pvr/PVRPlaybackState.h"
37 #include "pvr/PVRStreamProperties.h"
38 #include "pvr/addons/PVRClient.h"
39 #include "pvr/addons/PVRClientMenuHooks.h"
40 #include "pvr/addons/PVRClients.h"
41 #include "pvr/channels/PVRChannel.h"
42 #include "pvr/channels/PVRChannelGroup.h"
43 #include "pvr/channels/PVRChannelGroups.h"
44 #include "pvr/channels/PVRChannelGroupsContainer.h"
45 #include "pvr/dialogs/GUIDialogPVRChannelGuide.h"
46 #include "pvr/dialogs/GUIDialogPVRGuideInfo.h"
47 #include "pvr/dialogs/GUIDialogPVRRecordingInfo.h"
48 #include "pvr/dialogs/GUIDialogPVRRecordingSettings.h"
49 #include "pvr/dialogs/GUIDialogPVRTimerSettings.h"
50 #include "pvr/epg/EpgContainer.h"
51 #include "pvr/epg/EpgDatabase.h"
52 #include "pvr/epg/EpgInfoTag.h"
53 #include "pvr/recordings/PVRRecording.h"
54 #include "pvr/recordings/PVRRecordings.h"
55 #include "pvr/recordings/PVRRecordingsPath.h"
56 #include "pvr/timers/PVRTimerInfoTag.h"
57 #include "pvr/timers/PVRTimers.h"
58 #include "pvr/windows/GUIWindowPVRSearch.h"
59 #include "settings/MediaSettings.h"
60 #include "settings/Settings.h"
61 #include "threads/IRunnable.h"
62 #include "threads/SingleLock.h"
63 #include "utils/StringUtils.h"
64 #include "utils/SystemInfo.h"
65 #include "utils/URIUtils.h"
66 #include "utils/Variant.h"
67 #include "utils/log.h"
68 #include "video/VideoDatabase.h"
69 
70 #include <chrono>
71 #include <iterator>
72 #include <map>
73 #include <memory>
74 #include <string>
75 #include <thread>
76 #include <utility>
77 #include <vector>
78 
79 using namespace KODI::MESSAGING;
80 
81 namespace PVR
82 {
83   class AsyncRecordingAction : private IRunnable
84   {
85   public:
86     bool Execute(const CFileItemPtr& item);
87 
88   protected:
89     AsyncRecordingAction() = default;
90 
91   private:
92     // IRunnable implementation
93     void Run() override;
94 
95     // the worker function
96     virtual bool DoRun(const CFileItemPtr& item) = 0;
97 
98     CFileItemPtr m_item;
99     bool m_bSuccess = false;
100   };
101 
Execute(const CFileItemPtr & item)102   bool AsyncRecordingAction::Execute(const CFileItemPtr& item)
103   {
104     m_item = item;
105     CGUIDialogBusy::Wait(this, 100, false);
106     return m_bSuccess;
107   }
108 
Run()109   void AsyncRecordingAction::Run()
110   {
111     m_bSuccess = DoRun(m_item);
112 
113     if (m_bSuccess)
114       CServiceBroker::GetPVRManager().TriggerRecordingsUpdate();
115   }
116 
117   class AsyncRenameRecording : public AsyncRecordingAction
118   {
119   public:
AsyncRenameRecording(const std::string & strNewName)120     explicit AsyncRenameRecording(const std::string& strNewName) : m_strNewName(strNewName) {}
121 
122   private:
DoRun(const std::shared_ptr<CFileItem> & item)123     bool DoRun(const std::shared_ptr<CFileItem>& item) override
124     {
125       if (item->IsUsablePVRRecording())
126       {
127         return item->GetPVRRecordingInfoTag()->Rename(m_strNewName);
128       }
129       else
130       {
131         CLog::LogF(LOGERROR, "Cannot rename item '{}': no valid recording tag", item->GetPath());
132         return false;
133       }
134     }
135     std::string m_strNewName;
136   };
137 
138   class AsyncDeleteRecording : public AsyncRecordingAction
139   {
140   public:
AsyncDeleteRecording(bool bWatchedOnly=false)141     explicit AsyncDeleteRecording(bool bWatchedOnly = false) : m_bWatchedOnly(bWatchedOnly) {}
142 
143   private:
DoRun(const std::shared_ptr<CFileItem> & item)144     bool DoRun(const std::shared_ptr<CFileItem>& item) override
145     {
146       CFileItemList items;
147       if (item->m_bIsFolder)
148       {
149         CUtil::GetRecursiveListing(item->GetPath(), items, "", XFILE::DIR_FLAG_NO_FILE_INFO);
150       }
151       else
152       {
153         items.Add(item);
154       }
155 
156       bool bReturn = true;
157       for (const auto& itemToDelete : items)
158       {
159         if (itemToDelete->IsPVRRecording() &&
160             (!m_bWatchedOnly || itemToDelete->GetPVRRecordingInfoTag()->GetPlayCount() > 0))
161           bReturn &= itemToDelete->GetPVRRecordingInfoTag()->Delete();
162       }
163       return bReturn;
164     }
165     bool m_bWatchedOnly = false;
166   };
167 
168   class AsyncEmptyRecordingsTrash : public AsyncRecordingAction
169   {
170   private:
DoRun(const std::shared_ptr<CFileItem> & item)171     bool DoRun(const std::shared_ptr<CFileItem>& item) override
172     {
173       return CServiceBroker::GetPVRManager().Clients()->DeleteAllRecordingsFromTrash() == PVR_ERROR_NO_ERROR;
174     }
175   };
176 
177   class AsyncUndeleteRecording : public AsyncRecordingAction
178   {
179   private:
DoRun(const std::shared_ptr<CFileItem> & item)180     bool DoRun(const std::shared_ptr<CFileItem>& item) override
181     {
182       if (item->IsDeletedPVRRecording())
183       {
184         return item->GetPVRRecordingInfoTag()->Undelete();
185       }
186       else
187       {
188         CLog::LogF(LOGERROR, "Cannot undelete item '{}': no valid recording tag", item->GetPath());
189         return false;
190       }
191     }
192   };
193 
194   class AsyncSetRecordingPlayCount : public AsyncRecordingAction
195   {
196   private:
DoRun(const CFileItemPtr & item)197     bool DoRun(const CFileItemPtr& item) override
198     {
199       const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(*item);
200       if (client)
201       {
202         const std::shared_ptr<CPVRRecording> recording = item->GetPVRRecordingInfoTag();
203         return client->SetRecordingPlayCount(*recording, recording->GetLocalPlayCount()) == PVR_ERROR_NO_ERROR;
204       }
205       return false;
206     }
207   };
208 
209   class AsyncSetRecordingLifetime : public AsyncRecordingAction
210   {
211   private:
DoRun(const CFileItemPtr & item)212     bool DoRun(const CFileItemPtr& item) override
213     {
214       const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(*item);
215       if (client)
216         return client->SetRecordingLifetime(*item->GetPVRRecordingInfoTag()) == PVR_ERROR_NO_ERROR;
217       return false;
218     }
219   };
220 
CPVRGUIActions()221   CPVRGUIActions::CPVRGUIActions()
222     : m_settings({CSettings::SETTING_LOOKANDFEEL_STARTUPACTION,
223                   CSettings::SETTING_PVRMANAGER_PRESELECTPLAYINGCHANNEL,
224                   CSettings::SETTING_PVRRECORD_INSTANTRECORDTIME,
225                   CSettings::SETTING_PVRRECORD_INSTANTRECORDACTION,
226                   CSettings::SETTING_PVRPLAYBACK_CONFIRMCHANNELSWITCH,
227                   CSettings::SETTING_PVRPLAYBACK_SWITCHTOFULLSCREENCHANNELTYPES,
228                   CSettings::SETTING_PVRPARENTAL_PIN, CSettings::SETTING_PVRPARENTAL_ENABLED,
229                   CSettings::SETTING_PVRPOWERMANAGEMENT_DAILYWAKEUPTIME,
230                   CSettings::SETTING_PVRPOWERMANAGEMENT_BACKENDIDLETIME,
231                   CSettings::SETTING_PVRREMINDERS_AUTOCLOSEDELAY,
232                   CSettings::SETTING_PVRREMINDERS_AUTORECORD,
233                   CSettings::SETTING_PVRREMINDERS_AUTOSWITCH})
234   {
235   }
236 
ShowEPGInfo(const CFileItemPtr & item) const237   bool CPVRGUIActions::ShowEPGInfo(const CFileItemPtr& item) const
238   {
239     const std::shared_ptr<CPVRChannel> channel(CPVRItem(item).GetChannel());
240     if (channel && CheckParentalLock(channel) != ParentalCheckResult::SUCCESS)
241       return false;
242 
243     const std::shared_ptr<CPVREpgInfoTag> epgTag(CPVRItem(item).GetEpgInfoTag());
244     if (!epgTag)
245     {
246       CLog::LogF(LOGERROR, "No epg tag!");
247       return false;
248     }
249 
250     CGUIDialogPVRGuideInfo* pDlgInfo = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogPVRGuideInfo>(WINDOW_DIALOG_PVR_GUIDE_INFO);
251     if (!pDlgInfo)
252     {
253       CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_PVR_GUIDE_INFO!");
254       return false;
255     }
256 
257     pDlgInfo->SetProgInfo(epgTag);
258     pDlgInfo->Open();
259     return true;
260   }
261 
262 
ShowChannelEPG(const CFileItemPtr & item) const263   bool CPVRGUIActions::ShowChannelEPG(const CFileItemPtr& item) const
264   {
265     const std::shared_ptr<CPVRChannel> channel(CPVRItem(item).GetChannel());
266     if (channel && CheckParentalLock(channel) != ParentalCheckResult::SUCCESS)
267       return false;
268 
269     CGUIDialogPVRChannelGuide* pDlgInfo = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogPVRChannelGuide>(WINDOW_DIALOG_PVR_CHANNEL_GUIDE);
270     if (!pDlgInfo)
271     {
272       CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_PVR_CHANNEL_GUIDE!");
273       return false;
274     }
275 
276     pDlgInfo->Open(channel);
277     return true;
278   }
279 
280 
ShowRecordingInfo(const CFileItemPtr & item) const281   bool CPVRGUIActions::ShowRecordingInfo(const CFileItemPtr& item) const
282   {
283     if (!item->IsPVRRecording())
284     {
285       CLog::LogF(LOGERROR, "No recording!");
286       return false;
287     }
288 
289     CGUIDialogPVRRecordingInfo* pDlgInfo = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogPVRRecordingInfo>(WINDOW_DIALOG_PVR_RECORDING_INFO);
290     if (!pDlgInfo)
291     {
292       CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_PVR_RECORDING_INFO!");
293       return false;
294     }
295 
296     pDlgInfo->SetRecording(item.get());
297     pDlgInfo->Open();
298     return true;
299   }
300 
FindSimilar(const std::shared_ptr<CFileItem> & item) const301   bool CPVRGUIActions::FindSimilar(const std::shared_ptr<CFileItem>& item) const
302   {
303     const bool bRadio(CPVRItem(item).IsRadio());
304 
305     int windowSearchId = bRadio ? WINDOW_RADIO_SEARCH : WINDOW_TV_SEARCH;
306     CGUIWindowPVRSearchBase* windowSearch;
307     if (bRadio)
308       windowSearch = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowPVRRadioSearch>(windowSearchId);
309     else
310       windowSearch = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowPVRTVSearch>(windowSearchId);
311 
312     if (!windowSearch)
313     {
314       CLog::LogF(LOGERROR, "Unable to get {}!",
315                  bRadio ? "WINDOW_RADIO_SEARCH" : "WINDOW_TV_SEARCH");
316       return false;
317     }
318 
319     //! @todo If we want dialogs to spawn program search in a clean way - without having to force-close any
320     //        other dialogs - we must introduce a search dialog with functionality similar to the search window.
321 
322     for (int iId = CServiceBroker::GetGUI()->GetWindowManager().GetTopmostModalDialog(true /* ignoreClosing */);
323          iId != WINDOW_INVALID;
324          iId = CServiceBroker::GetGUI()->GetWindowManager().GetTopmostModalDialog(true /* ignoreClosing */))
325     {
326       CLog::LogF(LOGWARNING,
327                  "Have to close modal dialog with id {} before search window can be opened.", iId);
328 
329       CGUIWindow* window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(iId);
330       if (window)
331       {
332         window->Close();
333       }
334       else
335       {
336         CLog::LogF(LOGERROR, "Unable to get window instance {}! Cannot open search window.", iId);
337         return false; // return, otherwise we run into an endless loop
338       }
339     }
340 
341     windowSearch->SetItemToSearch(item);
342     CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(windowSearchId);
343     return true;
344   };
345 
ShowTimerSettings(const std::shared_ptr<CPVRTimerInfoTag> & timer) const346   bool CPVRGUIActions::ShowTimerSettings(const std::shared_ptr<CPVRTimerInfoTag>& timer) const
347   {
348     CGUIDialogPVRTimerSettings* pDlgInfo = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogPVRTimerSettings>(WINDOW_DIALOG_PVR_TIMER_SETTING);
349     if (!pDlgInfo)
350     {
351       CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_PVR_TIMER_SETTING!");
352       return false;
353     }
354 
355     pDlgInfo->SetTimer(timer);
356     pDlgInfo->Open();
357 
358     return pDlgInfo->IsConfirmed();
359   }
360 
AddReminder(const std::shared_ptr<CFileItem> & item) const361   bool CPVRGUIActions::AddReminder(const std::shared_ptr<CFileItem>& item) const
362   {
363     const std::shared_ptr<CPVREpgInfoTag> epgTag = CPVRItem(item).GetEpgInfoTag();
364     if (!epgTag)
365     {
366       CLog::LogF(LOGERROR, "No epg tag!");
367       return false;
368     }
369 
370     if (CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(epgTag))
371     {
372       HELPERS::ShowOKDialogText(CVariant{19033}, // "Information"
373                                 CVariant{19034}); // "There is already a timer set for this event"
374       return false;
375     }
376 
377     const std::shared_ptr<CPVRTimerInfoTag> newTimer = CPVRTimerInfoTag::CreateReminderFromEpg(epgTag);
378     if (!newTimer)
379     {
380       HELPERS::ShowOKDialogText(CVariant{19033}, // "Information"
381                                 CVariant{19094}); // Timer creation failed. Unsupported timer type.
382       return false;
383     }
384 
385     return AddTimer(newTimer);
386  }
387 
AddTimer(bool bRadio) const388   bool CPVRGUIActions::AddTimer(bool bRadio) const
389   {
390     const std::shared_ptr<CPVRTimerInfoTag> newTimer(new CPVRTimerInfoTag(bRadio));
391     if (ShowTimerSettings(newTimer))
392     {
393       return AddTimer(newTimer);
394     }
395     return false;
396   }
397 
AddTimer(const CFileItemPtr & item,bool bShowTimerSettings) const398   bool CPVRGUIActions::AddTimer(const CFileItemPtr& item, bool bShowTimerSettings) const
399   {
400     return AddTimer(item, false, bShowTimerSettings, false);
401   }
402 
AddTimerRule(const std::shared_ptr<CFileItem> & item,bool bShowTimerSettings,bool bFallbackToOneShotTimer) const403   bool CPVRGUIActions::AddTimerRule(const std::shared_ptr<CFileItem>& item, bool bShowTimerSettings, bool bFallbackToOneShotTimer) const
404   {
405     return AddTimer(item, true, bShowTimerSettings, bFallbackToOneShotTimer);
406   }
407 
AddTimer(const std::shared_ptr<CFileItem> & item,bool bCreateRule,bool bShowTimerSettings,bool bFallbackToOneShotTimer) const408   bool CPVRGUIActions::AddTimer(const std::shared_ptr<CFileItem>& item, bool bCreateRule, bool bShowTimerSettings, bool bFallbackToOneShotTimer) const
409   {
410     const std::shared_ptr<CPVRChannel> channel(CPVRItem(item).GetChannel());
411     if (!channel)
412     {
413       CLog::LogF(LOGERROR, "No channel!");
414       return false;
415     }
416 
417     if (CheckParentalLock(channel) != ParentalCheckResult::SUCCESS)
418       return false;
419 
420     std::shared_ptr<CPVREpgInfoTag> epgTag = CPVRItem(item).GetEpgInfoTag();
421     if (epgTag)
422     {
423       if (epgTag->IsGapTag())
424         epgTag.reset(); // for gap tags, we can only create instant timers
425     }
426     else if (bCreateRule)
427     {
428       CLog::LogF(LOGERROR, "No epg tag!");
429       return false;
430     }
431 
432     std::shared_ptr<CPVRTimerInfoTag> timer(bCreateRule || !epgTag ? nullptr : CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(epgTag));
433     std::shared_ptr<CPVRTimerInfoTag> rule (bCreateRule ? CServiceBroker::GetPVRManager().Timers()->GetTimerRule(timer) : nullptr);
434     if (timer || rule)
435     {
436       HELPERS::ShowOKDialogText(CVariant{19033}, CVariant{19034}); // "Information", "There is already a timer set for this event"
437       return false;
438     }
439 
440     std::shared_ptr<CPVRTimerInfoTag> newTimer(epgTag ? CPVRTimerInfoTag::CreateFromEpg(epgTag, bCreateRule) : CPVRTimerInfoTag::CreateInstantTimerTag(channel));
441     if (!newTimer)
442     {
443       if (bCreateRule && bFallbackToOneShotTimer)
444         newTimer = CPVRTimerInfoTag::CreateFromEpg(epgTag, false);
445 
446       if (!newTimer)
447       {
448         HELPERS::ShowOKDialogText(CVariant{19033}, // "Information"
449                                   bCreateRule
450                                     ? CVariant{19095} // Timer rule creation failed. Unsupported timer type.
451                                     : CVariant{19094}); // Timer creation failed. Unsupported timer type.
452         return false;
453       }
454     }
455 
456     if (bShowTimerSettings)
457     {
458       if (!ShowTimerSettings(newTimer))
459         return false;
460     }
461 
462     return AddTimer(newTimer);
463   }
464 
AddTimer(const std::shared_ptr<CPVRTimerInfoTag> & item) const465   bool CPVRGUIActions::AddTimer(const std::shared_ptr<CPVRTimerInfoTag>& item) const
466   {
467     if (!item->Channel() && item->GetTimerType() && !item->GetTimerType()->IsEpgBasedTimerRule())
468     {
469       CLog::LogF(LOGERROR, "No channel given");
470       HELPERS::ShowOKDialogText(CVariant{257}, CVariant{19109}); // "Error", "Could not save the timer. Check the log for more information about this message."
471       return false;
472     }
473 
474     if (!item->IsTimerRule() && item->GetEpgInfoTag() && !item->GetEpgInfoTag()->IsRecordable())
475     {
476       HELPERS::ShowOKDialogText(CVariant{19033}, CVariant{19189}); // "Information", "The PVR backend does not allow to record this event."
477       return false;
478     }
479 
480     if (CheckParentalLock(item->Channel()) != ParentalCheckResult::SUCCESS)
481       return false;
482 
483     if (!CServiceBroker::GetPVRManager().Timers()->AddTimer(item))
484     {
485       HELPERS::ShowOKDialogText(CVariant{257}, CVariant{19109}); // "Error", "Could not save the timer. Check the log for more information about this message."
486       return false;
487     }
488 
489     return true;
490   }
491 
492   namespace
493   {
494     enum PVRRECORD_INSTANTRECORDACTION
495     {
496       NONE = -1,
497       RECORD_CURRENT_SHOW = 0,
498       RECORD_INSTANTRECORDTIME = 1,
499       ASK = 2,
500       RECORD_30_MINUTES = 3,
501       RECORD_60_MINUTES = 4,
502       RECORD_120_MINUTES = 5,
503       RECORD_NEXT_SHOW = 6
504     };
505 
506     class InstantRecordingActionSelector
507     {
508     public:
509       explicit InstantRecordingActionSelector(int iInstantRecordTime);
510       virtual ~InstantRecordingActionSelector() = default;
511 
512       void AddAction(PVRRECORD_INSTANTRECORDACTION eAction, const std::string& title);
513       void PreSelectAction(PVRRECORD_INSTANTRECORDACTION eAction);
514       PVRRECORD_INSTANTRECORDACTION Select();
515 
516     private:
517       int m_iInstantRecordTime;
518       CGUIDialogSelect* m_pDlgSelect; // not owner!
519       std::map<PVRRECORD_INSTANTRECORDACTION, int> m_actions;
520     };
521 
InstantRecordingActionSelector(int iInstantRecordTime)522     InstantRecordingActionSelector::InstantRecordingActionSelector(int iInstantRecordTime)
523     : m_iInstantRecordTime(iInstantRecordTime),
524       m_pDlgSelect(CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT))
525     {
526       if (m_pDlgSelect)
527       {
528         m_pDlgSelect->SetMultiSelection(false);
529         m_pDlgSelect->SetHeading(CVariant{19086}); // Instant recording action
530       }
531       else
532       {
533         CLog::LogF(LOGERROR, "Unable to obtain WINDOW_DIALOG_SELECT instance");
534       }
535     }
536 
AddAction(PVRRECORD_INSTANTRECORDACTION eAction,const std::string & title)537     void InstantRecordingActionSelector::AddAction(PVRRECORD_INSTANTRECORDACTION eAction, const std::string& title)
538     {
539       if (m_actions.find(eAction) == m_actions.end())
540       {
541         switch (eAction)
542         {
543           case RECORD_INSTANTRECORDTIME:
544             m_pDlgSelect->Add(StringUtils::Format(g_localizeStrings.Get(19090).c_str(), m_iInstantRecordTime)); // Record next <default duration> minutes
545             break;
546           case RECORD_30_MINUTES:
547             m_pDlgSelect->Add(StringUtils::Format(g_localizeStrings.Get(19090).c_str(), 30)); // Record next 30 minutes
548             break;
549           case RECORD_60_MINUTES:
550             m_pDlgSelect->Add(StringUtils::Format(g_localizeStrings.Get(19090).c_str(), 60)); // Record next 60 minutes
551             break;
552           case RECORD_120_MINUTES:
553             m_pDlgSelect->Add(StringUtils::Format(g_localizeStrings.Get(19090).c_str(), 120)); // Record next 120 minutes
554             break;
555           case RECORD_CURRENT_SHOW:
556             m_pDlgSelect->Add(StringUtils::Format(g_localizeStrings.Get(19091).c_str(), title.c_str())); // Record current show (<title>)
557             break;
558           case RECORD_NEXT_SHOW:
559             m_pDlgSelect->Add(StringUtils::Format(g_localizeStrings.Get(19092).c_str(), title.c_str())); // Record next show (<title>)
560             break;
561           case NONE:
562           case ASK:
563           default:
564             return;
565         }
566 
567         m_actions.insert(std::make_pair(eAction, m_actions.size()));
568       }
569     }
570 
PreSelectAction(PVRRECORD_INSTANTRECORDACTION eAction)571     void InstantRecordingActionSelector::PreSelectAction(PVRRECORD_INSTANTRECORDACTION eAction)
572     {
573       const auto& it = m_actions.find(eAction);
574       if (it != m_actions.end())
575         m_pDlgSelect->SetSelected(it->second);
576     }
577 
Select()578     PVRRECORD_INSTANTRECORDACTION InstantRecordingActionSelector::Select()
579     {
580       PVRRECORD_INSTANTRECORDACTION eAction = NONE;
581 
582       m_pDlgSelect->Open();
583 
584       if (m_pDlgSelect->IsConfirmed())
585       {
586         int iSelection = m_pDlgSelect->GetSelectedItem();
587         for (const auto& action : m_actions)
588         {
589           if (action.second == iSelection)
590           {
591             eAction = action.first;
592             break;
593           }
594         }
595       }
596 
597       return eAction;
598     }
599 
600   } // unnamed namespace
601 
ToggleRecordingOnPlayingChannel()602   bool CPVRGUIActions::ToggleRecordingOnPlayingChannel()
603   {
604     const std::shared_ptr<CPVRChannel> channel = CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel();
605     if (channel && channel->CanRecord())
606       return SetRecordingOnChannel(channel, !CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*channel));
607 
608     return false;
609   }
610 
SetRecordingOnChannel(const std::shared_ptr<CPVRChannel> & channel,bool bOnOff)611   bool CPVRGUIActions::SetRecordingOnChannel(const std::shared_ptr<CPVRChannel>& channel, bool bOnOff)
612   {
613     bool bReturn = false;
614 
615     if (!channel)
616       return bReturn;
617 
618     if (CheckParentalLock(channel) != ParentalCheckResult::SUCCESS)
619       return bReturn;
620 
621     const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(channel->ClientID());
622     if (client && client->GetClientCapabilities().SupportsTimers())
623     {
624       /* timers are supported on this channel */
625       if (bOnOff && !CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*channel))
626       {
627         std::shared_ptr<CPVREpgInfoTag> epgTag;
628         int iDuration = m_settings.GetIntValue(CSettings::SETTING_PVRRECORD_INSTANTRECORDTIME);
629 
630         int iAction = m_settings.GetIntValue(CSettings::SETTING_PVRRECORD_INSTANTRECORDACTION);
631         switch (iAction)
632         {
633           case RECORD_CURRENT_SHOW:
634             epgTag = channel->GetEPGNow();
635             break;
636 
637           case RECORD_INSTANTRECORDTIME:
638             epgTag.reset();
639             break;
640 
641           case ASK:
642           {
643             PVRRECORD_INSTANTRECORDACTION ePreselect = RECORD_INSTANTRECORDTIME;
644             const int iDurationDefault = m_settings.GetIntValue(CSettings::SETTING_PVRRECORD_INSTANTRECORDTIME);
645             InstantRecordingActionSelector selector(iDurationDefault);
646             std::shared_ptr<CPVREpgInfoTag> epgTagNext;
647 
648             // fixed length recordings
649             selector.AddAction(RECORD_30_MINUTES, "");
650             selector.AddAction(RECORD_60_MINUTES, "");
651             selector.AddAction(RECORD_120_MINUTES, "");
652 
653             if (iDurationDefault != 30 && iDurationDefault != 60 && iDurationDefault != 120)
654               selector.AddAction(RECORD_INSTANTRECORDTIME, "");
655 
656             // epg-based recordings
657             epgTag = channel->GetEPGNow();
658             if (epgTag)
659             {
660               bool bLocked = CServiceBroker::GetPVRManager().IsParentalLocked(epgTag);
661 
662               // "now"
663               const std::string currentTitle = bLocked ? g_localizeStrings.Get(19266) /* Parental locked */ : epgTag->Title();
664               selector.AddAction(RECORD_CURRENT_SHOW, currentTitle);
665               ePreselect = RECORD_CURRENT_SHOW;
666 
667               // "next"
668               epgTagNext = channel->GetEPGNext();
669               if (epgTagNext)
670               {
671                 const std::string nextTitle = bLocked ? g_localizeStrings.Get(19266) /* Parental locked */ : epgTagNext->Title();
672                 selector.AddAction(RECORD_NEXT_SHOW, nextTitle);
673 
674                 // be smart. if current show is almost over, preselect next show.
675                 if (epgTag->ProgressPercentage() > 90.0f)
676                   ePreselect = RECORD_NEXT_SHOW;
677               }
678             }
679 
680             if (ePreselect == RECORD_INSTANTRECORDTIME)
681             {
682               if (iDurationDefault == 30)
683                 ePreselect = RECORD_30_MINUTES;
684               else if (iDurationDefault == 60)
685                 ePreselect = RECORD_60_MINUTES;
686               else if (iDurationDefault == 120)
687                 ePreselect = RECORD_120_MINUTES;
688             }
689 
690             selector.PreSelectAction(ePreselect);
691 
692             PVRRECORD_INSTANTRECORDACTION eSelected = selector.Select();
693             switch (eSelected)
694             {
695               case NONE:
696                 return false; // dialog canceled
697 
698               case RECORD_30_MINUTES:
699                 iDuration = 30;
700                 epgTag.reset();
701                 break;
702 
703               case RECORD_60_MINUTES:
704                 iDuration = 60;
705                 epgTag.reset();
706                 break;
707 
708               case RECORD_120_MINUTES:
709                 iDuration = 120;
710                 epgTag.reset();
711                 break;
712 
713               case RECORD_INSTANTRECORDTIME:
714                 iDuration = iDurationDefault;
715                 epgTag.reset();
716                 break;
717 
718               case RECORD_CURRENT_SHOW:
719                 break;
720 
721               case RECORD_NEXT_SHOW:
722                 epgTag = epgTagNext;
723                 break;
724 
725               default:
726                 CLog::LogF(LOGERROR,
727                            "Unknown instant record action selection ({}), defaulting to fixed "
728                            "length recording.",
729                            static_cast<int>(eSelected));
730                 epgTag.reset();
731                 break;
732             }
733             break;
734           }
735 
736           default:
737             CLog::LogF(LOGERROR,
738                        "Unknown instant record action setting value ({}), defaulting to fixed "
739                        "length recording.",
740                        iAction);
741             break;
742         }
743 
744         const std::shared_ptr<CPVRTimerInfoTag> newTimer(epgTag ? CPVRTimerInfoTag::CreateFromEpg(epgTag, false) : CPVRTimerInfoTag::CreateInstantTimerTag(channel, iDuration));
745 
746         if (newTimer)
747           bReturn = CServiceBroker::GetPVRManager().Timers()->AddTimer(newTimer);
748 
749         if (!bReturn)
750           HELPERS::ShowOKDialogText(CVariant{257}, CVariant{19164}); // "Error", "Could not start recording. Check the log for more information about this message."
751       }
752       else if (!bOnOff && CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*channel))
753       {
754         /* delete active timers */
755         bReturn = CServiceBroker::GetPVRManager().Timers()->DeleteTimersOnChannel(channel, true, true);
756 
757         if (!bReturn)
758           HELPERS::ShowOKDialogText(CVariant{257}, CVariant{19170}); // "Error", "Could not stop recording. Check the log for more information about this message."
759       }
760     }
761 
762     return bReturn;
763   }
764 
ToggleTimer(const CFileItemPtr & item) const765   bool CPVRGUIActions::ToggleTimer(const CFileItemPtr& item) const
766   {
767     if (!item->HasEPGInfoTag())
768       return false;
769 
770     const std::shared_ptr<CPVRTimerInfoTag> timer(CPVRItem(item).GetTimerInfoTag());
771     if (timer)
772     {
773       if (timer->IsRecording())
774         return StopRecording(item);
775       else
776         return DeleteTimer(item);
777     }
778     else
779       return AddTimer(item, false);
780   }
781 
ToggleTimerState(const CFileItemPtr & item) const782   bool CPVRGUIActions::ToggleTimerState(const CFileItemPtr& item) const
783   {
784     if (!item->HasPVRTimerInfoTag())
785       return false;
786 
787     const std::shared_ptr<CPVRTimerInfoTag> timer(item->GetPVRTimerInfoTag());
788     if (timer->m_state == PVR_TIMER_STATE_DISABLED)
789       timer->m_state = PVR_TIMER_STATE_SCHEDULED;
790     else
791       timer->m_state = PVR_TIMER_STATE_DISABLED;
792 
793     if (CServiceBroker::GetPVRManager().Timers()->UpdateTimer(timer))
794       return true;
795 
796     HELPERS::ShowOKDialogText(CVariant{257}, CVariant{19263}); // "Error", "Could not update the timer. Check the log for more information about this message."
797     return false;
798   }
799 
EditTimer(const CFileItemPtr & item) const800   bool CPVRGUIActions::EditTimer(const CFileItemPtr& item) const
801   {
802     const std::shared_ptr<CPVRTimerInfoTag> timer(CPVRItem(item).GetTimerInfoTag());
803     if (!timer)
804     {
805       CLog::LogF(LOGERROR, "No timer!");
806       return false;
807     }
808 
809     // clone the timer.
810     const std::shared_ptr<CPVRTimerInfoTag> newTimer(new CPVRTimerInfoTag);
811     newTimer->UpdateEntry(timer);
812 
813     if (ShowTimerSettings(newTimer) && (!timer->GetTimerType()->IsReadOnly() || timer->GetTimerType()->SupportsEnableDisable()))
814     {
815       if (newTimer->GetTimerType() == timer->GetTimerType())
816       {
817         if (CServiceBroker::GetPVRManager().Timers()->UpdateTimer(newTimer))
818           return true;
819 
820         HELPERS::ShowOKDialogText(CVariant{257}, CVariant{19263}); // "Error", "Could not update the timer. Check the log for more information about this message."
821         return false;
822       }
823       else
824       {
825         // timer type changed. delete the original timer, then create the new timer. this order is
826         // important. for instance, the new timer might be a rule which schedules the original timer.
827         // deleting the original timer after creating the rule would do literally this and we would
828         // end up with one timer missing wrt to the rule defined by the new timer.
829         if (DeleteTimer(timer, timer->IsRecording(), false))
830         {
831           if (AddTimer(newTimer))
832             return true;
833 
834           // rollback.
835           return AddTimer(timer);
836         }
837       }
838     }
839     return false;
840   }
841 
EditTimerRule(const CFileItemPtr & item) const842   bool CPVRGUIActions::EditTimerRule(const CFileItemPtr& item) const
843   {
844     const std::shared_ptr<CFileItem> parentTimer = GetTimerRule(item);
845     if (parentTimer)
846       return EditTimer(parentTimer);
847 
848     return false;
849   }
850 
GetTimerRule(const std::shared_ptr<CFileItem> & item) const851   std::shared_ptr<CFileItem> CPVRGUIActions::GetTimerRule(const std::shared_ptr<CFileItem>& item) const
852   {
853     std::shared_ptr<CPVRTimerInfoTag> timer;
854     if (item && item->HasEPGInfoTag())
855       timer = CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(item->GetEPGInfoTag());
856     else if (item && item->HasPVRTimerInfoTag())
857       timer = item->GetPVRTimerInfoTag();
858 
859     if (timer)
860     {
861       timer = CServiceBroker::GetPVRManager().Timers()->GetTimerRule(timer);
862       if (timer)
863         return std::make_shared<CFileItem>(timer);
864     }
865     return {};
866   }
867 
DeleteTimer(const CFileItemPtr & item) const868   bool CPVRGUIActions::DeleteTimer(const CFileItemPtr& item) const
869   {
870     return DeleteTimer(item, false, false);
871   }
872 
DeleteTimerRule(const CFileItemPtr & item) const873   bool CPVRGUIActions::DeleteTimerRule(const CFileItemPtr& item) const
874   {
875     return DeleteTimer(item, false, true);
876   }
877 
DeleteTimer(const CFileItemPtr & item,bool bIsRecording,bool bDeleteRule) const878   bool CPVRGUIActions::DeleteTimer(const CFileItemPtr& item, bool bIsRecording, bool bDeleteRule) const
879   {
880     std::shared_ptr<CPVRTimerInfoTag> timer;
881     const std::shared_ptr<CPVRRecording> recording(CPVRItem(item).GetRecording());
882     if (recording)
883       timer = recording->GetRecordingTimer();
884 
885     if (!timer)
886       timer = CPVRItem(item).GetTimerInfoTag();
887 
888     if (!timer)
889     {
890       CLog::LogF(LOGERROR, "No timer!");
891       return false;
892     }
893 
894     if (bDeleteRule && !timer->IsTimerRule())
895       timer = CServiceBroker::GetPVRManager().Timers()->GetTimerRule(timer);
896 
897     if (!timer)
898     {
899       CLog::LogF(LOGERROR, "No timer rule!");
900       return false;
901     }
902 
903     if (bIsRecording)
904     {
905       if (ConfirmStopRecording(timer))
906       {
907         if (CServiceBroker::GetPVRManager().Timers()->DeleteTimer(timer, true, false) == TimerOperationResult::OK)
908           return true;
909 
910         HELPERS::ShowOKDialogText(CVariant{257}, CVariant{19170}); // "Error", "Could not stop recording. Check the log for more information about this message."
911         return false;
912       }
913     }
914     else if (timer->HasTimerType() && !timer->GetTimerType()->AllowsDelete())
915     {
916       return false;
917     }
918     else
919     {
920       bool bAlsoDeleteRule(false);
921       if (ConfirmDeleteTimer(timer, bAlsoDeleteRule))
922         return DeleteTimer(timer, false, bAlsoDeleteRule);
923     }
924     return false;
925   }
926 
DeleteTimer(const std::shared_ptr<CPVRTimerInfoTag> & timer,bool bIsRecording,bool bDeleteRule) const927   bool CPVRGUIActions::DeleteTimer(const std::shared_ptr<CPVRTimerInfoTag>& timer, bool bIsRecording, bool bDeleteRule) const
928   {
929     TimerOperationResult result = CServiceBroker::GetPVRManager().Timers()->DeleteTimer(timer, bIsRecording, bDeleteRule);
930     switch (result)
931     {
932       case TimerOperationResult::RECORDING:
933       {
934         // recording running. ask the user if it should be deleted anyway
935         if (HELPERS::ShowYesNoDialogText(CVariant{122},   // "Confirm delete"
936                                          CVariant{19122}) // "This timer is still recording. Are you sure you want to delete this timer?"
937             != HELPERS::DialogResponse::YES)
938           return false;
939 
940         return DeleteTimer(timer, true, bDeleteRule);
941       }
942       case TimerOperationResult::OK:
943       {
944         return true;
945       }
946       case TimerOperationResult::FAILED:
947       {
948         HELPERS::ShowOKDialogText(CVariant{257}, CVariant{19110}); // "Error", "Could not delete the timer. Check the log for more information about this message."
949         return false;
950       }
951       default:
952       {
953         CLog::LogF(LOGERROR, "Unhandled TimerOperationResult ({})!", static_cast<int>(result));
954         break;
955       }
956     }
957     return false;
958   }
959 
ConfirmDeleteTimer(const std::shared_ptr<CPVRTimerInfoTag> & timer,bool & bDeleteRule) const960   bool CPVRGUIActions::ConfirmDeleteTimer(const std::shared_ptr<CPVRTimerInfoTag>& timer, bool& bDeleteRule) const
961   {
962     bool bConfirmed(false);
963     const std::shared_ptr<CPVRTimerInfoTag> parentTimer(CServiceBroker::GetPVRManager().Timers()->GetTimerRule(timer));
964 
965     if (parentTimer && parentTimer->HasTimerType() && parentTimer->GetTimerType()->AllowsDelete())
966     {
967       // timer was scheduled by a deletable timer rule. prompt user for confirmation for deleting the timer rule, including scheduled timers.
968       bool bCancel(false);
969       bDeleteRule = CGUIDialogYesNo::ShowAndGetInput(CVariant{122}, // "Confirm delete"
970                                                      CVariant{840}, // "Do you want to delete only this timer or also the timer rule that has scheduled it?"
971                                                      CVariant{""},
972                                                      CVariant{timer->Title()},
973                                                      bCancel,
974                                                      CVariant{841}, // "Only this"
975                                                      CVariant{593}, // "All"
976                                                      0); // no autoclose
977       bConfirmed = !bCancel;
978     }
979     else
980     {
981       bDeleteRule = false;
982 
983       // prompt user for confirmation for deleting the timer
984       bConfirmed = CGUIDialogYesNo::ShowAndGetInput(CVariant{122}, // "Confirm delete"
985                                                     timer->IsTimerRule()
986                                                       ? CVariant{845}  // "Are you sure you want to delete this timer rule and all timers it has scheduled?"
987                                                       : CVariant{846}, // "Are you sure you want to delete this timer?"
988                                                     CVariant{""},
989                                                     CVariant{timer->Title()});
990     }
991 
992     return bConfirmed;
993   }
994 
StopRecording(const CFileItemPtr & item) const995   bool CPVRGUIActions::StopRecording(const CFileItemPtr& item) const
996   {
997     if (!DeleteTimer(item, true, false))
998       return false;
999 
1000     CServiceBroker::GetPVRManager().TriggerRecordingsUpdate();
1001     return true;
1002   }
1003 
ConfirmStopRecording(const std::shared_ptr<CPVRTimerInfoTag> & timer) const1004   bool CPVRGUIActions::ConfirmStopRecording(const std::shared_ptr<CPVRTimerInfoTag>& timer) const
1005   {
1006     return CGUIDialogYesNo::ShowAndGetInput(CVariant{847}, // "Confirm stop recording"
1007                                             CVariant{848}, // "Are you sure you want to stop this recording?"
1008                                             CVariant{""},
1009                                             CVariant{timer->Title()});
1010   }
1011 
EditRecording(const CFileItemPtr & item) const1012   bool CPVRGUIActions::EditRecording(const CFileItemPtr& item) const
1013   {
1014     const std::shared_ptr<CPVRRecording> recording = CPVRItem(item).GetRecording();
1015     if (!recording)
1016     {
1017       CLog::LogF(LOGERROR, "No recording!");
1018       return false;
1019     }
1020 
1021     std::shared_ptr<CPVRRecording> origRecording(new CPVRRecording);
1022     origRecording->Update(*recording,
1023                           *CServiceBroker::GetPVRManager().GetClient(recording->m_iClientId));
1024 
1025     if (!ShowRecordingSettings(recording))
1026       return false;
1027 
1028     if (origRecording->m_strTitle != recording->m_strTitle)
1029     {
1030       if (!AsyncRenameRecording(recording->m_strTitle).Execute(item))
1031         CLog::LogF(LOGERROR, "Renaming recording failed!");
1032     }
1033 
1034     if (origRecording->GetLocalPlayCount() != recording->GetLocalPlayCount())
1035     {
1036       if (!AsyncSetRecordingPlayCount().Execute(item))
1037         CLog::LogF(LOGERROR, "Setting recording playcount failed!");
1038     }
1039 
1040     if (origRecording->m_iLifetime != recording->m_iLifetime)
1041     {
1042       if (!AsyncSetRecordingLifetime().Execute(item))
1043         CLog::LogF(LOGERROR, "Setting recording lifetime failed!");
1044     }
1045 
1046     return true;
1047   }
1048 
CanEditRecording(const CFileItem & item) const1049   bool CPVRGUIActions::CanEditRecording(const CFileItem& item) const
1050   {
1051     return CGUIDialogPVRRecordingSettings::CanEditRecording(item);
1052   }
1053 
DeleteRecording(const CFileItemPtr & item) const1054   bool CPVRGUIActions::DeleteRecording(const CFileItemPtr& item) const
1055   {
1056     if ((!item->IsPVRRecording() && !item->m_bIsFolder) || item->IsParentFolder())
1057       return false;
1058 
1059     if (!ConfirmDeleteRecording(item))
1060       return false;
1061 
1062     if (!AsyncDeleteRecording().Execute(item))
1063     {
1064       HELPERS::ShowOKDialogText(CVariant{257}, CVariant{19111}); // "Error", "PVR backend error. Check the log for more information about this message."
1065       return false;
1066     }
1067 
1068     return true;
1069   }
1070 
ConfirmDeleteRecording(const CFileItemPtr & item) const1071   bool CPVRGUIActions::ConfirmDeleteRecording(const CFileItemPtr& item) const
1072   {
1073     return CGUIDialogYesNo::ShowAndGetInput(CVariant{122}, // "Confirm delete"
1074                                             item->m_bIsFolder
1075                                               ? CVariant{19113} // "Delete all recordings in this folder?"
1076                                               : item->GetPVRRecordingInfoTag()->IsDeleted()
1077                                                 ? CVariant{19294}  // "Remove this deleted recording from trash? This operation cannot be reverted."
1078                                                 : CVariant{19112}, // "Delete this recording?"
1079                                             CVariant{""},
1080                                             CVariant{item->GetLabel()});
1081   }
1082 
DeleteWatchedRecordings(const std::shared_ptr<CFileItem> & item) const1083   bool CPVRGUIActions::DeleteWatchedRecordings(const std::shared_ptr<CFileItem>& item) const
1084   {
1085     if (!item->m_bIsFolder || item->IsParentFolder())
1086       return false;
1087 
1088     if (!ConfirmDeleteWatchedRecordings(item))
1089       return false;
1090 
1091     if (!AsyncDeleteRecording(true).Execute(item))
1092     {
1093       HELPERS::ShowOKDialogText(
1094           CVariant{257},
1095           CVariant{
1096               19111}); // "Error", "PVR backend error. Check the log for more information about this message."
1097       return false;
1098     }
1099 
1100     return true;
1101   }
1102 
ConfirmDeleteWatchedRecordings(const std::shared_ptr<CFileItem> & item) const1103   bool CPVRGUIActions::ConfirmDeleteWatchedRecordings(const std::shared_ptr<CFileItem>& item) const
1104   {
1105     return CGUIDialogYesNo::ShowAndGetInput(
1106         CVariant{122}, // "Confirm delete"
1107         CVariant{19328}, // "Delete all watched recordings in this folder?"
1108         CVariant{""}, CVariant{item->GetLabel()});
1109   }
1110 
DeleteAllRecordingsFromTrash() const1111   bool CPVRGUIActions::DeleteAllRecordingsFromTrash() const
1112   {
1113     if (!ConfirmDeleteAllRecordingsFromTrash())
1114       return false;
1115 
1116     if (!AsyncEmptyRecordingsTrash().Execute(CFileItemPtr()))
1117       return false;
1118 
1119     return true;
1120   }
1121 
ConfirmDeleteAllRecordingsFromTrash() const1122   bool CPVRGUIActions::ConfirmDeleteAllRecordingsFromTrash() const
1123   {
1124     return CGUIDialogYesNo::ShowAndGetInput(CVariant{19292},  // "Delete all permanently"
1125                                             CVariant{19293}); // "Remove all deleted recordings from trash? This operation cannot be reverted."
1126   }
1127 
UndeleteRecording(const CFileItemPtr & item) const1128   bool CPVRGUIActions::UndeleteRecording(const CFileItemPtr& item) const
1129   {
1130     if (!item->IsDeletedPVRRecording())
1131       return false;
1132 
1133     if (!AsyncUndeleteRecording().Execute(item))
1134     {
1135       HELPERS::ShowOKDialogText(CVariant{257}, CVariant{19111}); // "Error", "PVR backend error. Check the log for more information about this message."
1136       return false;
1137     }
1138 
1139     return true;
1140   }
1141 
ShowRecordingSettings(const std::shared_ptr<CPVRRecording> & recording) const1142   bool CPVRGUIActions::ShowRecordingSettings(const std::shared_ptr<CPVRRecording>& recording) const
1143   {
1144     CGUIDialogPVRRecordingSettings* pDlgInfo = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogPVRRecordingSettings>(WINDOW_DIALOG_PVR_RECORDING_SETTING);
1145     if (!pDlgInfo)
1146     {
1147       CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_PVR_RECORDING_SETTING!");
1148       return false;
1149     }
1150 
1151     pDlgInfo->SetRecording(recording);
1152     pDlgInfo->Open();
1153 
1154     return pDlgInfo->IsConfirmed();
1155   }
1156 
GetResumeLabel(const CFileItem & item) const1157   std::string CPVRGUIActions::GetResumeLabel(const CFileItem& item) const
1158   {
1159     std::string resumeString;
1160 
1161     const std::shared_ptr<CPVRRecording> recording(CPVRItem(CFileItemPtr(new CFileItem(item))).GetRecording());
1162     if (recording && !recording->IsDeleted())
1163     {
1164       int positionInSeconds = lrint(recording->GetResumePoint().timeInSeconds);
1165       if (positionInSeconds > 0)
1166         resumeString = StringUtils::Format(g_localizeStrings.Get(12022).c_str(),
1167                                            StringUtils::SecondsToTimeString(positionInSeconds, TIME_FORMAT_HH_MM_SS).c_str());
1168     }
1169     return resumeString;
1170   }
1171 
CheckResumeRecording(const CFileItemPtr & item) const1172   bool CPVRGUIActions::CheckResumeRecording(const CFileItemPtr& item) const
1173   {
1174     bool bPlayIt(true);
1175     std::string resumeString(GetResumeLabel(*item));
1176     if (!resumeString.empty())
1177     {
1178       CContextButtons choices;
1179       choices.Add(CONTEXT_BUTTON_RESUME_ITEM, resumeString);
1180       choices.Add(CONTEXT_BUTTON_PLAY_ITEM, 12021); // Play from beginning
1181       int choice = CGUIDialogContextMenu::ShowAndGetChoice(choices);
1182       if (choice > 0)
1183         item->m_lStartOffset = choice == CONTEXT_BUTTON_RESUME_ITEM ? STARTOFFSET_RESUME : 0;
1184       else
1185         bPlayIt = false; // context menu cancelled
1186     }
1187     return bPlayIt;
1188   }
1189 
ResumePlayRecording(const CFileItemPtr & item,bool bFallbackToPlay) const1190   bool CPVRGUIActions::ResumePlayRecording(const CFileItemPtr& item, bool bFallbackToPlay) const
1191   {
1192     bool bCanResume = !GetResumeLabel(*item).empty();
1193     if (bCanResume)
1194     {
1195       item->m_lStartOffset = STARTOFFSET_RESUME;
1196     }
1197     else
1198     {
1199       if (bFallbackToPlay)
1200         item->m_lStartOffset = 0;
1201       else
1202         return false;
1203     }
1204 
1205     return PlayRecording(item, false);
1206   }
1207 
CheckAndSwitchToFullscreen(bool bFullscreen) const1208   void CPVRGUIActions::CheckAndSwitchToFullscreen(bool bFullscreen) const
1209   {
1210     CMediaSettings::GetInstance().SetMediaStartWindowed(!bFullscreen);
1211 
1212     if (bFullscreen)
1213     {
1214       CGUIMessage msg(GUI_MSG_FULLSCREEN, 0, CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow());
1215       CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
1216     }
1217   }
1218 
StartPlayback(CFileItem * item,bool bFullscreen,CPVRStreamProperties * epgProps) const1219   void CPVRGUIActions::StartPlayback(CFileItem* item,
1220                                      bool bFullscreen,
1221                                      CPVRStreamProperties* epgProps) const
1222   {
1223     // Obtain dynamic playback url and properties from the respective pvr client
1224     const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(*item);
1225     if (client)
1226     {
1227       CPVRStreamProperties props;
1228 
1229       if (item->IsPVRChannel())
1230       {
1231         // If this was an EPG Tag to be played as live then PlayEpgTag() will create a channel
1232         // fileitem instead and pass the epg tags props so we use those and skip the client call
1233         if (epgProps)
1234           props = *epgProps;
1235         else
1236           client->GetChannelStreamProperties(item->GetPVRChannelInfoTag(), props);
1237       }
1238       else if (item->IsPVRRecording())
1239       {
1240         client->GetRecordingStreamProperties(item->GetPVRRecordingInfoTag(), props);
1241       }
1242       else if (item->IsEPG())
1243       {
1244         if (epgProps) // we already have props from PlayEpgTag()
1245           props = *epgProps;
1246         else
1247           client->GetEpgTagStreamProperties(item->GetEPGInfoTag(), props);
1248       }
1249 
1250       if (props.size())
1251       {
1252         const std::string url = props.GetStreamURL();
1253         if (!url.empty())
1254           item->SetDynPath(url);
1255 
1256         const std::string mime = props.GetStreamMimeType();
1257         if (!mime.empty())
1258         {
1259           item->SetMimeType(mime);
1260           item->SetContentLookup(false);
1261         }
1262 
1263         for (const auto& prop : props)
1264           item->SetProperty(prop.first, prop.second);
1265       }
1266     }
1267 
1268     CApplicationMessenger::GetInstance().PostMsg(TMSG_MEDIA_PLAY, 0, 0, static_cast<void*>(item));
1269     CheckAndSwitchToFullscreen(bFullscreen);
1270   }
1271 
PlayRecording(const CFileItemPtr & item,bool bCheckResume) const1272   bool CPVRGUIActions::PlayRecording(const CFileItemPtr& item, bool bCheckResume) const
1273   {
1274     const std::shared_ptr<CPVRRecording> recording(CPVRItem(item).GetRecording());
1275     if (!recording)
1276       return false;
1277 
1278     if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingRecording(recording))
1279     {
1280       CGUIMessage msg(GUI_MSG_FULLSCREEN, 0, CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow());
1281       CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
1282       return true;
1283     }
1284 
1285     if (!bCheckResume || CheckResumeRecording(item))
1286     {
1287       CFileItem* itemToPlay = new CFileItem(recording);
1288       itemToPlay->m_lStartOffset = item->m_lStartOffset;
1289       StartPlayback(itemToPlay, true);
1290     }
1291     return true;
1292   }
1293 
PlayEpgTag(const CFileItemPtr & item) const1294   bool CPVRGUIActions::PlayEpgTag(const CFileItemPtr& item) const
1295   {
1296     const std::shared_ptr<CPVREpgInfoTag> epgTag(CPVRItem(item).GetEpgInfoTag());
1297     if (!epgTag)
1298       return false;
1299 
1300     const std::shared_ptr<CPVRChannel> channelTag(CPVRItem(item).GetChannel());
1301     if (!channelTag)
1302       return false;
1303 
1304     // Obtain dynamic playback url and properties from the respective pvr client
1305     const std::shared_ptr<CPVRClient> client =
1306         CServiceBroker::GetPVRManager().GetClient(epgTag->ClientID());
1307     if (!client)
1308       return false;
1309 
1310     CPVRStreamProperties props;
1311     client->GetEpgTagStreamProperties(epgTag, props);
1312 
1313     if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingEpgTag(epgTag))
1314     {
1315       CGUIMessage msg(GUI_MSG_FULLSCREEN, 0, CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow());
1316       CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
1317       return true;
1318     }
1319 
1320     StartPlayback(props.EPGPlaybackAsLive() ? new CFileItem(channelTag) : new CFileItem(epgTag),
1321                   true, &props);
1322     return true;
1323   }
1324 
SwitchToChannel(const CFileItemPtr & item,bool bCheckResume) const1325   bool CPVRGUIActions::SwitchToChannel(const CFileItemPtr& item, bool bCheckResume) const
1326   {
1327     if (item->m_bIsFolder)
1328       return false;
1329 
1330     std::shared_ptr<CPVRRecording> recording;
1331     const std::shared_ptr<CPVRChannel> channel(CPVRItem(item).GetChannel());
1332     if (channel)
1333     {
1334       bool bSwitchToFullscreen = CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingChannel(channel);
1335 
1336       if (!bSwitchToFullscreen)
1337       {
1338         recording = CServiceBroker::GetPVRManager().Recordings()->GetRecordingForEpgTag(channel->GetEPGNow());
1339         bSwitchToFullscreen = recording && CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingRecording(recording);
1340       }
1341 
1342       if (bSwitchToFullscreen)
1343       {
1344         CGUIMessage msg(GUI_MSG_FULLSCREEN, 0, CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow());
1345         CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
1346         return true;
1347       }
1348     }
1349 
1350     ParentalCheckResult result = channel ? CheckParentalLock(channel) : ParentalCheckResult::FAILED;
1351     if (result == ParentalCheckResult::SUCCESS)
1352     {
1353       // switch to channel or if recording present, ask whether to switch or play recording...
1354       if (!recording)
1355         recording = CServiceBroker::GetPVRManager().Recordings()->GetRecordingForEpgTag(channel->GetEPGNow());
1356 
1357       if (recording)
1358       {
1359         bool bCancel(false);
1360         bool bPlayRecording = CGUIDialogYesNo::ShowAndGetInput(CVariant{19687}, // "Play recording"
1361                                                                CVariant{""},
1362                                                                CVariant{12021}, // "Play from beginning"
1363                                                                CVariant{recording->m_strTitle},
1364                                                                bCancel,
1365                                                                CVariant{19000}, // "Switch to channel"
1366                                                                CVariant{19687}, // "Play recording"
1367                                                                0); // no autoclose
1368         if (bCancel)
1369           return false;
1370 
1371         if (bPlayRecording)
1372         {
1373           const CFileItemPtr recordingItem(new CFileItem(recording));
1374           return PlayRecording(recordingItem, bCheckResume);
1375         }
1376       }
1377 
1378       bool bFullscreen;
1379       switch (m_settings.GetIntValue(CSettings::SETTING_PVRPLAYBACK_SWITCHTOFULLSCREENCHANNELTYPES))
1380       {
1381         case 0: // never
1382           bFullscreen = false;
1383           break;
1384         case 1: // TV channels
1385           bFullscreen = !channel->IsRadio();
1386           break;
1387         case 2: // Radio channels
1388           bFullscreen = channel->IsRadio();
1389           break;
1390         case 3: // TV and radio channels
1391         default:
1392           bFullscreen = true;
1393           break;
1394       }
1395       StartPlayback(new CFileItem(channel), bFullscreen);
1396       return true;
1397     }
1398     else if (result == ParentalCheckResult::FAILED)
1399     {
1400       const std::string channelName = channel ? channel->ChannelName() : g_localizeStrings.Get(19029); // Channel
1401       const std::string msg = StringUtils::Format(g_localizeStrings.Get(19035).c_str(), channelName.c_str()); // CHANNELNAME could not be played. Check the log for details.
1402 
1403       CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(19166), msg); // PVR information
1404     }
1405 
1406     return false;
1407   }
1408 
SwitchToChannel(PlaybackType type) const1409   bool CPVRGUIActions::SwitchToChannel(PlaybackType type) const
1410   {
1411     std::shared_ptr<CPVRChannel> channel;
1412     bool bIsRadio(false);
1413 
1414     // check if the desired PlaybackType is already playing,
1415     // and if not, try to grab the last played channel of this type
1416     switch (type)
1417     {
1418       case PlaybackTypeRadio:
1419       {
1420         if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingRadio())
1421           return true;
1422 
1423         const std::shared_ptr<CPVRChannelGroup> allGroup = CServiceBroker::GetPVRManager().ChannelGroups()->GetGroupAllRadio();
1424         if (allGroup)
1425           channel = allGroup->GetLastPlayedChannel();
1426 
1427         bIsRadio = true;
1428         break;
1429       }
1430       case PlaybackTypeTV:
1431       {
1432         if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingTV())
1433           return true;
1434 
1435         const std::shared_ptr<CPVRChannelGroup> allGroup = CServiceBroker::GetPVRManager().ChannelGroups()->GetGroupAllTV();
1436         if (allGroup)
1437           channel = allGroup->GetLastPlayedChannel();
1438 
1439         break;
1440       }
1441       default:
1442         if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlaying())
1443           return true;
1444 
1445         channel = CServiceBroker::GetPVRManager().ChannelGroups()->GetLastPlayedChannel();
1446         break;
1447     }
1448 
1449     // if we have a last played channel, start playback
1450     if (channel)
1451     {
1452       return SwitchToChannel(std::make_shared<CFileItem>(channel), true);
1453     }
1454     else
1455     {
1456       // if we don't, find the active channel group of the demanded type and play it's first channel
1457       const std::shared_ptr<CPVRChannelGroup> channelGroup = CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingGroup(bIsRadio);
1458       if (channelGroup)
1459       {
1460         // try to start playback of first channel in this group
1461         const std::vector<std::shared_ptr<PVRChannelGroupMember>> groupMembers = channelGroup->GetMembers();
1462         if (!groupMembers.empty())
1463         {
1464           return SwitchToChannel(std::make_shared<CFileItem>((*groupMembers.begin())->channel), true);
1465         }
1466       }
1467     }
1468 
1469     CLog::LogF(LOGERROR,
1470                "Could not determine {} channel to playback. No last played channel found, and "
1471                "first channel of active group could also not be determined.",
1472                bIsRadio ? "Radio" : "TV");
1473 
1474     CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error,
1475                                           g_localizeStrings.Get(19166), // PVR information
1476                                           StringUtils::Format(g_localizeStrings.Get(19035).c_str(),
1477                                                               g_localizeStrings.Get(bIsRadio ? 19021 : 19020).c_str())); // Radio/TV could not be played. Check the log for details.
1478     return false;
1479   }
1480 
PlayChannelOnStartup() const1481   bool CPVRGUIActions::PlayChannelOnStartup() const
1482   {
1483     int iAction = m_settings.GetIntValue(CSettings::SETTING_LOOKANDFEEL_STARTUPACTION);
1484     if (iAction != STARTUP_ACTION_PLAY_TV &&
1485         iAction != STARTUP_ACTION_PLAY_RADIO)
1486       return false;
1487 
1488     bool playTV = iAction == STARTUP_ACTION_PLAY_TV;
1489     const std::shared_ptr<CPVRChannelGroupsContainer> groups(CServiceBroker::GetPVRManager().ChannelGroups());
1490     std::shared_ptr<CPVRChannelGroup> group = playTV ? groups->GetGroupAllTV() : groups->GetGroupAllRadio();
1491 
1492     // get the last played channel or fallback to first channel
1493     std::shared_ptr<CPVRChannel> channel = group->GetLastPlayedChannel();
1494     if (channel)
1495     {
1496       group = groups->GetLastPlayedGroup(channel->ChannelID());
1497     }
1498     else
1499     {
1500       // fallback to first channel
1501       auto channels(group->GetMembers());
1502       if (channels.empty())
1503         return false;
1504 
1505       channel = channels.front()->channel;
1506     }
1507 
1508     CLog::Log(LOGINFO, "PVR is starting playback of channel '{}'", channel->ChannelName());
1509     CServiceBroker::GetPVRManager().PlaybackState()->SetPlayingGroup(group);
1510     return SwitchToChannel(std::make_shared<CFileItem>(channel), true);
1511   }
1512 
PlayMedia(const CFileItemPtr & item) const1513   bool CPVRGUIActions::PlayMedia(const CFileItemPtr& item) const
1514   {
1515     CFileItemPtr pvrItem(item);
1516     if (URIUtils::IsPVRChannel(item->GetPath()) && !item->HasPVRChannelInfoTag())
1517     {
1518       const std::shared_ptr<CPVRChannel> pvrChannel =
1519           CServiceBroker::GetPVRManager().ChannelGroups()->GetByPath(item->GetPath());
1520       if (pvrChannel)
1521         pvrItem = std::make_shared<CFileItem>(pvrChannel);
1522     }
1523     else if (URIUtils::IsPVRRecording(item->GetPath()) && !item->HasPVRRecordingInfoTag())
1524       pvrItem = std::make_shared<CFileItem>(CServiceBroker::GetPVRManager().Recordings()->GetByPath(item->GetPath()));
1525 
1526     bool bCheckResume = true;
1527     if (item->HasProperty("check_resume"))
1528       bCheckResume = item->GetProperty("check_resume").asBoolean();
1529 
1530     if (pvrItem && pvrItem->HasPVRChannelInfoTag())
1531     {
1532       return SwitchToChannel(pvrItem, bCheckResume);
1533     }
1534     else if (pvrItem && pvrItem->HasPVRRecordingInfoTag())
1535     {
1536       return PlayRecording(pvrItem, bCheckResume);
1537     }
1538 
1539     return false;
1540   }
1541 
HideChannel(const CFileItemPtr & item) const1542   bool CPVRGUIActions::HideChannel(const CFileItemPtr& item) const
1543   {
1544     const std::shared_ptr<CPVRChannel> channel(item->GetPVRChannelInfoTag());
1545 
1546     /* check if the channel tag is valid */
1547     if (!channel || !channel->ChannelNumber().IsValid())
1548       return false;
1549 
1550     if (!CGUIDialogYesNo::ShowAndGetInput(CVariant{19054}, // "Hide channel"
1551                                           CVariant{19039}, // "Are you sure you want to hide this channel?"
1552                                           CVariant{""},
1553                                           CVariant{channel->ChannelName()}))
1554       return false;
1555 
1556     if (!CServiceBroker::GetPVRManager().ChannelGroups()->GetGroupAll(channel->IsRadio())->RemoveFromGroup(channel))
1557       return false;
1558 
1559     CGUIWindowPVRBase* pvrWindow = dynamic_cast<CGUIWindowPVRBase*>(CServiceBroker::GetGUI()->GetWindowManager().GetWindow(CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow()));
1560     if (pvrWindow)
1561       pvrWindow->DoRefresh();
1562     else
1563       CLog::LogF(LOGERROR, "Called on non-pvr window. No refresh possible.");
1564 
1565     return true;
1566   }
1567 
StartChannelScan()1568   bool CPVRGUIActions::StartChannelScan()
1569   {
1570     return StartChannelScan(PVR_INVALID_CLIENT_ID);
1571   }
1572 
StartChannelScan(int clientId)1573   bool CPVRGUIActions::StartChannelScan(int clientId)
1574   {
1575     if (!CServiceBroker::GetPVRManager().IsStarted() || IsRunningChannelScan())
1576       return false;
1577 
1578     std::shared_ptr<CPVRClient> scanClient;
1579     std::vector<std::shared_ptr<CPVRClient>> possibleScanClients = CServiceBroker::GetPVRManager().Clients()->GetClientsSupportingChannelScan();
1580     m_bChannelScanRunning = true;
1581 
1582     if (clientId != PVR_INVALID_CLIENT_ID)
1583     {
1584       for (const auto& client : possibleScanClients)
1585       {
1586         if (client->GetID() == clientId)
1587         {
1588           scanClient = client;
1589           break;
1590         }
1591       }
1592 
1593       if (!scanClient)
1594       {
1595         CLog::LogF(LOGERROR,
1596                    "Provided client id '%d' could not be found in list of possible scan clients!",
1597                    clientId);
1598         m_bChannelScanRunning = false;
1599         return false;
1600       }
1601     }
1602     /* multiple clients found */
1603     else if (possibleScanClients.size() > 1)
1604     {
1605       CGUIDialogSelect* pDialog= CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
1606       if (!pDialog)
1607       {
1608         CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_SELECT!");
1609         m_bChannelScanRunning = false;
1610         return false;
1611       }
1612 
1613       pDialog->Reset();
1614       pDialog->SetHeading(CVariant{19119}); // "On which backend do you want to search?"
1615 
1616       for (const auto& client : possibleScanClients)
1617         pDialog->Add(client->GetFriendlyName());
1618 
1619       pDialog->Open();
1620 
1621       int selection = pDialog->GetSelectedItem();
1622       if (selection >= 0)
1623         scanClient = possibleScanClients[selection];
1624     }
1625     /* one client found */
1626     else if (possibleScanClients.size() == 1)
1627     {
1628       scanClient = possibleScanClients[0];
1629     }
1630     /* no clients found */
1631     else if (!scanClient)
1632     {
1633       HELPERS::ShowOKDialogText(CVariant{19033},  // "Information"
1634                                     CVariant{19192}); // "None of the connected PVR backends supports scanning for channels."
1635       m_bChannelScanRunning = false;
1636       return false;
1637     }
1638 
1639     /* start the channel scan */
1640     CLog::LogFC(LOGDEBUG, LOGPVR, "Starting to scan for channels on client {}",
1641                 scanClient->GetFriendlyName());
1642     long perfCnt = XbmcThreads::SystemClockMillis();
1643 
1644     /* do the scan */
1645     if (scanClient->StartChannelScan() != PVR_ERROR_NO_ERROR)
1646       HELPERS::ShowOKDialogText(CVariant{257},    // "Error"
1647                                     CVariant{19193}); // "The channel scan can't be started. Check the log for more information about this message."
1648 
1649     CLog::LogFC(LOGDEBUG, LOGPVR, "Channel scan finished after {}.{} seconds",
1650                 (XbmcThreads::SystemClockMillis() - perfCnt) / 1000,
1651                 (XbmcThreads::SystemClockMillis() - perfCnt) % 1000);
1652     m_bChannelScanRunning = false;
1653     return true;
1654   }
1655 
ProcessSettingsMenuHooks()1656   bool CPVRGUIActions::ProcessSettingsMenuHooks()
1657   {
1658     CPVRClientMap clients;
1659     CServiceBroker::GetPVRManager().Clients()->GetCreatedClients(clients);
1660 
1661     std::vector<std::pair<std::shared_ptr<CPVRClient>, CPVRClientMenuHook>> settingsHooks;
1662     for (const auto& client : clients)
1663     {
1664       for (const auto& hook : client.second->GetMenuHooks()->GetSettingsHooks())
1665       {
1666         settingsHooks.emplace_back(std::make_pair(client.second, hook));
1667       }
1668     }
1669 
1670     if (settingsHooks.empty())
1671       return true; // no settings hooks, no error
1672 
1673     auto selectedHook = settingsHooks.begin();
1674 
1675     // if there is only one settings hook, execute it directly, otherwise let the user select
1676     if (settingsHooks.size() > 1)
1677     {
1678       CGUIDialogSelect* pDialog= CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
1679       if (!pDialog)
1680       {
1681         CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_SELECT!");
1682         return false;
1683       }
1684 
1685       pDialog->Reset();
1686       pDialog->SetHeading(CVariant{19196}); // "PVR client specific actions"
1687 
1688       for (const auto& hook : settingsHooks)
1689       {
1690         if (clients.size() == 1)
1691           pDialog->Add(hook.second.GetLabel());
1692         else
1693           pDialog->Add(hook.first->GetBackendName() + ": " + hook.second.GetLabel());
1694       }
1695 
1696       pDialog->Open();
1697 
1698       int selection = pDialog->GetSelectedItem();
1699       if (selection < 0)
1700         return true; // cancelled
1701 
1702       std::advance(selectedHook, selection);
1703     }
1704     return selectedHook->first->CallSettingsMenuHook(selectedHook->second) == PVR_ERROR_NO_ERROR;
1705   }
1706 
ResetPVRDatabase(bool bResetEPGOnly)1707   bool CPVRGUIActions::ResetPVRDatabase(bool bResetEPGOnly)
1708   {
1709     CGUIDialogProgress* pDlgProgress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
1710     if (!pDlgProgress)
1711     {
1712       CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_PROGRESS!");
1713       return false;
1714     }
1715 
1716     if (bResetEPGOnly)
1717     {
1718       if (!CGUIDialogYesNo::ShowAndGetInput(CVariant{19098},  // "Warning!"
1719                                             CVariant{19188})) // "All your guide data will be cleared. Are you sure?"
1720         return false;
1721     }
1722     else
1723     {
1724       if (CheckParentalPIN() != ParentalCheckResult::SUCCESS ||
1725           !CGUIDialogYesNo::ShowAndGetInput(CVariant{19098},  // "Warning!"
1726                                             CVariant{19186})) // "All your TV related data (channels, groups, guide) will be cleared. Are you sure?"
1727         return false;
1728     }
1729 
1730     CDateTime::ResetTimezoneBias();
1731 
1732     CLog::LogFC(LOGDEBUG, LOGPVR, "PVR clearing {} database",
1733                 bResetEPGOnly ? "EPG" : "PVR and EPG");
1734 
1735     pDlgProgress->SetHeading(CVariant{313}); // "Cleaning database"
1736     pDlgProgress->SetLine(0, CVariant{g_localizeStrings.Get(19187)}); // "Clearing all related data."
1737     pDlgProgress->SetLine(1, CVariant{""});
1738     pDlgProgress->SetLine(2, CVariant{""});
1739 
1740     pDlgProgress->Open();
1741     pDlgProgress->Progress();
1742 
1743     if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlaying())
1744     {
1745       CLog::Log(LOGINFO, "PVR is stopping playback for {} database reset",
1746                 bResetEPGOnly ? "EPG" : "PVR and EPG");
1747       CApplicationMessenger::GetInstance().SendMsg(TMSG_MEDIA_STOP);
1748     }
1749 
1750     pDlgProgress->SetPercentage(10);
1751     pDlgProgress->Progress();
1752 
1753     const std::shared_ptr<CPVRDatabase> pvrDatabase(CServiceBroker::GetPVRManager().GetTVDatabase());
1754     const std::shared_ptr<CPVREpgDatabase> epgDatabase(CServiceBroker::GetPVRManager().EpgContainer().GetEpgDatabase());
1755 
1756     // increase db open refcounts, so they don't get closed during following pvr manager shutdown
1757     pvrDatabase->Open();
1758     epgDatabase->Open();
1759 
1760     // stop pvr manager; close both pvr and epg databases
1761     CServiceBroker::GetPVRManager().Stop();
1762 
1763     /* reset the EPG pointers */
1764     pvrDatabase->ResetEPG();
1765     pDlgProgress->SetPercentage(bResetEPGOnly ? 40 : 20);
1766     pDlgProgress->Progress();
1767 
1768     /* clean the EPG database */
1769     epgDatabase->DeleteEpg();
1770     pDlgProgress->SetPercentage(bResetEPGOnly ? 70 : 40);
1771     pDlgProgress->Progress();
1772 
1773     if (!bResetEPGOnly)
1774     {
1775       pvrDatabase->DeleteChannelGroups();
1776       pDlgProgress->SetPercentage(60);
1777       pDlgProgress->Progress();
1778 
1779       /* delete all channels */
1780       pvrDatabase->DeleteChannels();
1781       pDlgProgress->SetPercentage(70);
1782       pDlgProgress->Progress();
1783 
1784       /* delete all timers */
1785       pvrDatabase->DeleteTimers();
1786       pDlgProgress->SetPercentage(80);
1787       pDlgProgress->Progress();
1788 
1789       /* delete all clients */
1790       pvrDatabase->DeleteClients();
1791       pDlgProgress->SetPercentage(90);
1792       pDlgProgress->Progress();
1793 
1794       /* delete all channel and recording settings */
1795       CVideoDatabase videoDatabase;
1796 
1797       if (videoDatabase.Open())
1798       {
1799         videoDatabase.EraseAllVideoSettings("pvr://channels/");
1800         videoDatabase.EraseAllVideoSettings(CPVRRecordingsPath::PATH_RECORDINGS);
1801         videoDatabase.Close();
1802       }
1803     }
1804 
1805     // decrease db open refcounts; this actually closes dbs because refcounts drops to zero
1806     pvrDatabase->Close();
1807     epgDatabase->Close();
1808 
1809     CLog::LogFC(LOGDEBUG, LOGPVR, "{} database cleared", bResetEPGOnly ? "EPG" : "PVR and EPG");
1810 
1811     CLog::Log(LOGINFO, "Restarting the PVR Manager after {} database reset",
1812               bResetEPGOnly ? "EPG" : "PVR and EPG");
1813     CServiceBroker::GetPVRManager().Start();
1814 
1815     pDlgProgress->SetPercentage(100);
1816     pDlgProgress->Close();
1817     return true;
1818   }
1819 
CheckParentalLock(const std::shared_ptr<CPVRChannel> & channel) const1820   ParentalCheckResult CPVRGUIActions::CheckParentalLock(const std::shared_ptr<CPVRChannel>& channel) const
1821   {
1822     if (!CServiceBroker::GetPVRManager().IsParentalLocked(channel))
1823       return ParentalCheckResult::SUCCESS;
1824 
1825     ParentalCheckResult ret = CheckParentalPIN();
1826 
1827     if (ret == ParentalCheckResult::FAILED)
1828       CLog::LogF(LOGERROR, "Parental lock verification failed for channel '{}': wrong PIN entered.",
1829                  channel->ChannelName());
1830 
1831     return ret;
1832   }
1833 
CheckParentalPIN() const1834   ParentalCheckResult CPVRGUIActions::CheckParentalPIN() const
1835   {
1836     if (!m_settings.GetBoolValue(CSettings::SETTING_PVRPARENTAL_ENABLED))
1837       return ParentalCheckResult::SUCCESS;
1838 
1839     std::string pinCode = m_settings.GetStringValue(CSettings::SETTING_PVRPARENTAL_PIN);
1840     if (pinCode.empty())
1841       return ParentalCheckResult::SUCCESS;
1842 
1843     InputVerificationResult ret = CGUIDialogNumeric::ShowAndVerifyInput(pinCode, g_localizeStrings.Get(19262), true); // "Parental control. Enter PIN:"
1844 
1845     if (ret == InputVerificationResult::SUCCESS)
1846     {
1847       CServiceBroker::GetPVRManager().RestartParentalTimer();
1848       return ParentalCheckResult::SUCCESS;
1849     }
1850     else if (ret == InputVerificationResult::FAILED)
1851     {
1852       HELPERS::ShowOKDialogText(CVariant{19264}, CVariant{19265}); // "Incorrect PIN", "The entered PIN was incorrect."
1853       return ParentalCheckResult::FAILED;
1854     }
1855     else
1856     {
1857       return ParentalCheckResult::CANCELED;
1858     }
1859   }
1860 
CanSystemPowerdown(bool bAskUser) const1861   bool CPVRGUIActions::CanSystemPowerdown(bool bAskUser /*= true*/) const
1862   {
1863     bool bReturn(true);
1864     if (CServiceBroker::GetPVRManager().IsStarted())
1865     {
1866       std::shared_ptr<CPVRTimerInfoTag> cause;
1867       if (!AllLocalBackendsIdle(cause))
1868       {
1869         if (bAskUser)
1870         {
1871           std::string text;
1872 
1873           if (cause)
1874           {
1875             if (cause->IsRecording())
1876             {
1877               text = StringUtils::Format(g_localizeStrings.Get(19691).c_str(), // "PVR is currently recording...."
1878                                          cause->Title().c_str(),
1879                                          cause->ChannelName().c_str());
1880             }
1881             else
1882             {
1883               // Next event is due to a local recording or reminder.
1884               const CDateTime now(CDateTime::GetUTCDateTime());
1885               const CDateTime start(cause->StartAsUTC());
1886               const CDateTimeSpan prestart(0, 0, cause->MarginStart(), 0);
1887 
1888               CDateTimeSpan diff(start - now);
1889               diff -= prestart;
1890               int mins = diff.GetSecondsTotal() / 60;
1891 
1892               std::string dueStr;
1893               if (mins > 1)
1894               {
1895                 // "%d minutes"
1896                 dueStr = StringUtils::Format(g_localizeStrings.Get(19694).c_str(), mins);
1897               }
1898               else
1899               {
1900                 // "about a minute"
1901                 dueStr = g_localizeStrings.Get(19695);
1902               }
1903 
1904               text = StringUtils::Format(cause->IsReminder()
1905                                            ? g_localizeStrings.Get(19690).c_str() // "PVR has scheduled a reminder...."
1906                                            : g_localizeStrings.Get(19692).c_str(), // "PVR will start recording...."
1907                                          cause->Title().c_str(),
1908                                          cause->ChannelName().c_str(),
1909                                          dueStr.c_str());
1910             }
1911           }
1912           else
1913           {
1914             // Next event is due to automatic daily wakeup of PVR.
1915             const CDateTime now(CDateTime::GetUTCDateTime());
1916 
1917             CDateTime dailywakeuptime;
1918             dailywakeuptime.SetFromDBTime(m_settings.GetStringValue(CSettings::SETTING_PVRPOWERMANAGEMENT_DAILYWAKEUPTIME));
1919             dailywakeuptime = dailywakeuptime.GetAsUTCDateTime();
1920 
1921             const CDateTimeSpan diff(dailywakeuptime - now);
1922             int mins = diff.GetSecondsTotal() / 60;
1923 
1924             std::string dueStr;
1925             if (mins > 1)
1926             {
1927               // "%d minutes"
1928               dueStr = StringUtils::Format(g_localizeStrings.Get(19694).c_str(), mins);
1929             }
1930             else
1931             {
1932               // "about a minute"
1933               dueStr = g_localizeStrings.Get(19695);
1934             }
1935 
1936             text = StringUtils::Format(g_localizeStrings.Get(19693).c_str(), // "Daily wakeup is due in...."
1937                                        dueStr.c_str());
1938           }
1939 
1940           // Inform user about PVR being busy. Ask if user wants to powerdown anyway.
1941           bReturn = HELPERS::ShowYesNoDialogText(CVariant{19685}, // "Confirm shutdown"
1942                                                  CVariant{text},
1943                                                  CVariant{222}, // "Shutdown anyway",
1944                                                  CVariant{19696}, // "Cancel"
1945                                                  10000) // timeout value before closing
1946                     == HELPERS::DialogResponse::YES;
1947         }
1948         else
1949           bReturn = false; // do not powerdown (busy, but no user interaction requested).
1950       }
1951     }
1952     return bReturn;
1953   }
1954 
AllLocalBackendsIdle(std::shared_ptr<CPVRTimerInfoTag> & causingEvent) const1955   bool CPVRGUIActions::AllLocalBackendsIdle(std::shared_ptr<CPVRTimerInfoTag>& causingEvent) const
1956   {
1957     // active recording on local backend?
1958     const std::vector<std::shared_ptr<CPVRTimerInfoTag>> activeRecordings = CServiceBroker::GetPVRManager().Timers()->GetActiveRecordings();
1959     for (const auto& timer : activeRecordings)
1960     {
1961       if (EventOccursOnLocalBackend(std::make_shared<CFileItem>(timer)))
1962       {
1963         causingEvent = timer;
1964         return false;
1965       }
1966     }
1967 
1968     // soon recording on local backend?
1969     if (IsNextEventWithinBackendIdleTime())
1970     {
1971       const std::shared_ptr<CPVRTimerInfoTag> timer = CServiceBroker::GetPVRManager().Timers()->GetNextActiveTimer(false);
1972       if (!timer)
1973       {
1974         // Next event is due to automatic daily wakeup of PVR!
1975         causingEvent.reset();
1976         return false;
1977       }
1978 
1979       if (EventOccursOnLocalBackend(std::make_shared<CFileItem>(timer)))
1980       {
1981         causingEvent = timer;
1982         return false;
1983       }
1984     }
1985     return true;
1986   }
1987 
EventOccursOnLocalBackend(const CFileItemPtr & item) const1988   bool CPVRGUIActions::EventOccursOnLocalBackend(const CFileItemPtr& item) const
1989   {
1990     if (item && item->HasPVRTimerInfoTag())
1991     {
1992       const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(*item);
1993       if (client)
1994       {
1995         const std::string hostname = client->GetBackendHostname();
1996         if (!hostname.empty() && CServiceBroker::GetNetwork().IsLocalHost(hostname))
1997           return true;
1998       }
1999     }
2000     return false;
2001   }
2002 
2003   namespace
2004   {
GetAnnouncerText(const std::shared_ptr<CPVRTimerInfoTag> & timer,int idEpg,int idNoEpg)2005     std::string GetAnnouncerText(const std::shared_ptr<CPVRTimerInfoTag>& timer, int idEpg, int idNoEpg)
2006     {
2007       std::string text;
2008       if (timer->IsEpgBased())
2009       {
2010         text = StringUtils::Format(g_localizeStrings.Get(idEpg),
2011                                    timer->Title(), // tv show title
2012                                    timer->ChannelName(),
2013                                    timer->StartAsLocalTime().GetAsLocalizedDateTime(false, false));
2014       }
2015       else
2016       {
2017         text = StringUtils::Format(g_localizeStrings.Get(idNoEpg),
2018                                    timer->ChannelName(),
2019                                    timer->StartAsLocalTime().GetAsLocalizedDateTime(false, false));
2020       }
2021       return text;
2022     }
2023 
AddEventLogEntry(const std::shared_ptr<CPVRTimerInfoTag> & timer,int idEpg,int idNoEpg)2024     void AddEventLogEntry(const std::shared_ptr<CPVRTimerInfoTag>& timer, int idEpg, int idNoEpg)
2025     {
2026       std::string name;
2027       std::string icon;
2028 
2029       const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(timer->GetTimerType()->GetClientId());
2030       if (client)
2031       {
2032         name = client->Name();
2033         icon = client->Icon();
2034       }
2035       else
2036       {
2037         name = g_sysinfo.GetAppName();
2038         icon = "special://xbmc/media/icon256x256.png";
2039       }
2040 
2041       CPVREventLogJob* job = new CPVREventLogJob;
2042       job->AddEvent(false, // do not display a toast, only log event
2043                     false, // info, no error
2044                     name,
2045                     GetAnnouncerText(timer, idEpg, idNoEpg),
2046                     icon);
2047       CJobManager::GetInstance().AddJob(job, nullptr);
2048     }
2049   } // unnamed namespace
2050 
IsNextEventWithinBackendIdleTime() const2051   bool CPVRGUIActions::IsNextEventWithinBackendIdleTime() const
2052   {
2053     // timers going off soon?
2054     const CDateTime now(CDateTime::GetUTCDateTime());
2055     const CDateTimeSpan idle(0, 0, m_settings.GetIntValue(CSettings::SETTING_PVRPOWERMANAGEMENT_BACKENDIDLETIME), 0);
2056     const CDateTime next(CServiceBroker::GetPVRManager().Timers()->GetNextEventTime());
2057     const CDateTimeSpan delta(next - now);
2058 
2059     return (delta <= idle);
2060   }
2061 
AnnounceReminder(const std::shared_ptr<CPVRTimerInfoTag> & timer) const2062   void CPVRGUIActions::AnnounceReminder(const std::shared_ptr<CPVRTimerInfoTag>& timer) const
2063   {
2064     if (!timer->IsReminder())
2065     {
2066       CLog::LogF(LOGERROR, "No reminder timer!");
2067       return;
2068     }
2069 
2070     if (timer->EndAsUTC() < CDateTime::GetUTCDateTime())
2071     {
2072       // expired. timer end is in the past. write event log entry.
2073       AddEventLogEntry(timer, 19305, 19306); // Deleted missed PVR reminder ...
2074       return;
2075     }
2076 
2077     if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingChannel(timer->Channel()))
2078     {
2079       // no need for an announcement. channel in question is already playing.
2080       return;
2081     }
2082 
2083     // show the reminder dialog
2084     CGUIDialogProgress* dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
2085     if (!dialog)
2086       return;
2087 
2088     dialog->Reset();
2089 
2090     dialog->SetHeading(CVariant{19312}); // "PVR reminder"
2091     dialog->ShowChoice(0, CVariant{19165}); // "Switch"
2092 
2093     std::string text = GetAnnouncerText(timer, 19307, 19308); // Reminder for ...
2094 
2095     bool bCanRecord = false;
2096     const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(timer->m_iClientId);
2097     if (client && client->GetClientCapabilities().SupportsTimers())
2098     {
2099       bCanRecord = true;
2100       dialog->ShowChoice(1, CVariant{264}); // "Record"
2101       dialog->ShowChoice(2, CVariant{222}); // "Cancel"
2102 
2103       if (m_settings.GetBoolValue(CSettings::SETTING_PVRREMINDERS_AUTORECORD))
2104         text += "\n\n" + g_localizeStrings.Get(
2105                              19309); // (Auto-close of this reminder will schedule a recording...)
2106       else if (m_settings.GetBoolValue(CSettings::SETTING_PVRREMINDERS_AUTOSWITCH))
2107         text += "\n\n" + g_localizeStrings.Get(
2108                              19331); // (Auto-close of this reminder will swicth to channel...)
2109     }
2110     else
2111     {
2112       dialog->ShowChoice(1, CVariant{222}); // "Cancel"
2113     }
2114 
2115     dialog->SetText(text);
2116     dialog->SetPercentage(100);
2117 
2118     dialog->Open();
2119 
2120     int result = CGUIDialogProgress::CHOICE_NONE;
2121 
2122     static constexpr int PROGRESS_TIMESLICE_MILLISECS = 50;
2123 
2124     const int iWait = m_settings.GetIntValue(CSettings::SETTING_PVRREMINDERS_AUTOCLOSEDELAY) * 1000;
2125     int iRemaining = iWait;
2126     while (iRemaining > 0)
2127     {
2128       result = dialog->GetChoice();
2129       if (result != CGUIDialogProgress::CHOICE_NONE)
2130         break;
2131 
2132       std::this_thread::sleep_for(std::chrono::milliseconds(PROGRESS_TIMESLICE_MILLISECS));
2133 
2134       iRemaining -= PROGRESS_TIMESLICE_MILLISECS;
2135       dialog->SetPercentage(iRemaining * 100 / iWait);
2136       dialog->Progress();
2137     }
2138 
2139     dialog->Close();
2140 
2141     bool bAutoClosed = (iRemaining <= 0);
2142     bool bSwitch = (result == 0);
2143     bool bRecord = (result == 1);
2144 
2145     if (bAutoClosed)
2146     {
2147       bRecord = (bCanRecord && m_settings.GetBoolValue(CSettings::SETTING_PVRREMINDERS_AUTORECORD));
2148       bSwitch = m_settings.GetBoolValue(CSettings::SETTING_PVRREMINDERS_AUTOSWITCH);
2149     }
2150 
2151     if (bRecord)
2152     {
2153       std::shared_ptr<CPVRTimerInfoTag> newTimer;
2154 
2155       std::shared_ptr<CPVREpgInfoTag> epgTag = timer->GetEpgInfoTag();
2156       if (epgTag)
2157       {
2158         newTimer = CPVRTimerInfoTag::CreateFromEpg(epgTag, false);
2159         if (newTimer)
2160         {
2161           // an epgtag can only have max one timer - we need to clear the reminder to be able to
2162           // attach the recording timer
2163           DeleteTimer(timer, false, false);
2164         }
2165       }
2166       else
2167       {
2168         int iDuration = (timer->EndAsUTC() - timer->StartAsUTC()).GetSecondsTotal() / 60;
2169         newTimer =
2170             CPVRTimerInfoTag::CreateTimerTag(timer->Channel(), timer->StartAsUTC(), iDuration);
2171       }
2172 
2173       if (newTimer)
2174       {
2175         // schedule recording
2176         AddTimer(std::make_shared<CFileItem>(newTimer), false);
2177       }
2178 
2179       if (bAutoClosed)
2180       {
2181         AddEventLogEntry(timer, 19310,
2182                          19311); // Scheduled recording for auto-closed PVR reminder ...
2183       }
2184     }
2185 
2186     if (bSwitch)
2187     {
2188       SwitchToChannel(std::make_shared<CFileItem>(timer->Channel()), false);
2189 
2190       if (bAutoClosed)
2191       {
2192         AddEventLogEntry(timer, 19332, 19333); // Switched channel for auto-closed PVR reminder ...
2193       }
2194     }
2195   }
2196 
AnnounceReminders() const2197   void CPVRGUIActions::AnnounceReminders() const
2198   {
2199     // Prevent multiple yesno dialogs, all on same call stack, due to gui message processing while dialog is open.
2200     if (m_bReminderAnnouncementRunning)
2201       return;
2202 
2203     m_bReminderAnnouncementRunning = true;
2204     std::shared_ptr<CPVRTimerInfoTag> timer = CServiceBroker::GetPVRManager().Timers()->GetNextReminderToAnnnounce();
2205     while (timer)
2206     {
2207       AnnounceReminder(timer);
2208       timer = CServiceBroker::GetPVRManager().Timers()->GetNextReminderToAnnnounce();
2209     }
2210     m_bReminderAnnouncementRunning = false;
2211   }
2212 
SetSelectedItemPath(bool bRadio,const std::string & path)2213   void CPVRGUIActions::SetSelectedItemPath(bool bRadio, const std::string& path)
2214   {
2215     CSingleLock lock(m_critSection);
2216     if (bRadio)
2217       m_selectedItemPathRadio = path;
2218     else
2219       m_selectedItemPathTV = path;
2220   }
2221 
GetSelectedItemPath(bool bRadio) const2222   std::string CPVRGUIActions::GetSelectedItemPath(bool bRadio) const
2223   {
2224     if (m_settings.GetBoolValue(CSettings::SETTING_PVRMANAGER_PRESELECTPLAYINGCHANNEL))
2225     {
2226       CPVRManager& mgr = CServiceBroker::GetPVRManager();
2227 
2228       // if preselect playing channel is activated, return the path of the playing channel, if any.
2229       const std::shared_ptr<CPVRChannel> playingChannel = mgr.PlaybackState()->GetPlayingChannel();
2230       if (playingChannel && playingChannel->IsRadio() == bRadio)
2231         return playingChannel->Path();
2232 
2233       const std::shared_ptr<CPVREpgInfoTag> playingTag = mgr.PlaybackState()->GetPlayingEpgTag();
2234       if (playingTag && playingTag->IsRadio() == bRadio)
2235       {
2236         const std::shared_ptr<CPVRChannel> channel =
2237             mgr.ChannelGroups()->GetChannelForEpgTag(playingTag);
2238         if (channel)
2239           return channel->Path();
2240       }
2241     }
2242 
2243     CSingleLock lock(m_critSection);
2244     return bRadio ? m_selectedItemPathRadio : m_selectedItemPathTV;
2245   }
2246 
SeekForward()2247   void CPVRGUIActions::SeekForward()
2248   {
2249     time_t playbackStartTime = CServiceBroker::GetDataCacheCore().GetStartTime();
2250     if (playbackStartTime > 0)
2251     {
2252       const std::shared_ptr<CPVRChannel> playingChannel = CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel();
2253       if (playingChannel)
2254       {
2255         time_t nextTime = 0;
2256         std::shared_ptr<CPVREpgInfoTag> next = playingChannel->GetEPGNext();
2257         if (next)
2258         {
2259           next->StartAsUTC().GetAsTime(nextTime);
2260         }
2261         else
2262         {
2263           // if there is no next event, jump to end of currently playing event
2264           next = playingChannel->GetEPGNow();
2265           if (next)
2266             next->EndAsUTC().GetAsTime(nextTime);
2267         }
2268 
2269         int64_t seekTime = 0;
2270         if (nextTime != 0)
2271         {
2272           seekTime = (nextTime - playbackStartTime) * 1000;
2273         }
2274         else
2275         {
2276           // no epg; jump to end of buffer
2277           seekTime = CServiceBroker::GetDataCacheCore().GetMaxTime();
2278         }
2279         CApplicationMessenger::GetInstance().PostMsg(TMSG_MEDIA_SEEK_TIME, seekTime);
2280       }
2281     }
2282   }
2283 
SeekBackward(unsigned int iThreshold)2284   void CPVRGUIActions::SeekBackward(unsigned int iThreshold)
2285   {
2286     time_t playbackStartTime = CServiceBroker::GetDataCacheCore().GetStartTime();
2287     if (playbackStartTime > 0)
2288     {
2289       const std::shared_ptr<CPVRChannel> playingChannel = CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel();
2290       if (playingChannel)
2291       {
2292         time_t prevTime = 0;
2293         std::shared_ptr<CPVREpgInfoTag> prev = playingChannel->GetEPGNow();
2294         if (prev)
2295         {
2296           prev->StartAsUTC().GetAsTime(prevTime);
2297 
2298           // if playback time of current event is above threshold jump to start of current event
2299           int64_t playTime = CServiceBroker::GetDataCacheCore().GetPlayTime() / 1000;
2300           if ((playbackStartTime + playTime - prevTime) <= iThreshold)
2301           {
2302             // jump to start of previous event
2303             prevTime = 0;
2304             prev = playingChannel->GetEPGPrevious();
2305             if (prev)
2306               prev->StartAsUTC().GetAsTime(prevTime);
2307           }
2308         }
2309 
2310         int64_t seekTime = 0;
2311         if (prevTime != 0)
2312         {
2313           seekTime = (prevTime - playbackStartTime) * 1000;
2314         }
2315         else
2316         {
2317           // no epg; jump to begin of buffer
2318           seekTime = CServiceBroker::GetDataCacheCore().GetMinTime();
2319         }
2320         CApplicationMessenger::GetInstance().PostMsg(TMSG_MEDIA_SEEK_TIME, seekTime);
2321       }
2322     }
2323   }
2324 
GetChannelNumberInputHandler()2325   CPVRChannelNumberInputHandler& CPVRGUIActions::GetChannelNumberInputHandler()
2326   {
2327     // window/dialog specific input handler
2328     CPVRChannelNumberInputHandler* windowInputHandler = dynamic_cast<CPVRChannelNumberInputHandler*>(CServiceBroker::GetGUI()->GetWindowManager().GetWindow(CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog()));
2329     if (windowInputHandler)
2330       return *windowInputHandler;
2331 
2332     // default
2333     return m_channelNumberInputHandler;
2334   }
2335 
GetChannelNavigator()2336   CPVRGUIChannelNavigator& CPVRGUIActions::GetChannelNavigator()
2337   {
2338     return m_channelNavigator;
2339   }
2340 
OnPlaybackStarted(const CFileItemPtr & item)2341   void CPVRGUIActions::OnPlaybackStarted(const CFileItemPtr& item)
2342   {
2343     std::shared_ptr<CPVRChannel> channel = item->GetPVRChannelInfoTag();
2344     if (!channel && item->HasEPGInfoTag())
2345     {
2346       channel = CServiceBroker::GetPVRManager().ChannelGroups()->GetChannelForEpgTag(
2347           item->GetEPGInfoTag());
2348     }
2349 
2350     if (channel)
2351     {
2352       m_channelNavigator.SetPlayingChannel(channel);
2353       SetSelectedItemPath(channel->IsRadio(), channel->Path());
2354     }
2355   }
2356 
OnPlaybackStopped(const CFileItemPtr & item)2357   void CPVRGUIActions::OnPlaybackStopped(const CFileItemPtr& item)
2358   {
2359     if (item->HasPVRChannelInfoTag() || item->HasEPGInfoTag())
2360     {
2361       m_channelNavigator.ClearPlayingChannel();
2362     }
2363   }
2364 
AppendChannelNumberCharacter(char cCharacter)2365   void CPVRChannelSwitchingInputHandler::AppendChannelNumberCharacter(char cCharacter)
2366   {
2367     // special case. if only a single zero was typed in, switch to previously played channel.
2368     if (GetCurrentDigitCount() == 0 && cCharacter == '0')
2369     {
2370       SwitchToPreviousChannel();
2371       return;
2372     }
2373 
2374     CPVRChannelNumberInputHandler::AppendChannelNumberCharacter(cCharacter);
2375   }
2376 
GetChannelNumbers(std::vector<std::string> & channelNumbers)2377   void CPVRChannelSwitchingInputHandler::GetChannelNumbers(std::vector<std::string>& channelNumbers)
2378   {
2379     CPVRManager& pvrMgr = CServiceBroker::GetPVRManager();
2380     const std::shared_ptr<CPVRChannel> playingChannel = pvrMgr.PlaybackState()->GetPlayingChannel();
2381     if (playingChannel)
2382     {
2383       const std::shared_ptr<CPVRChannelGroup> group = pvrMgr.ChannelGroups()->GetGroupAll(playingChannel->IsRadio());
2384       if (group)
2385         group->GetChannelNumbers(channelNumbers);
2386     }
2387   }
2388 
OnInputDone()2389   void CPVRChannelSwitchingInputHandler::OnInputDone()
2390   {
2391     CPVRChannelNumber channelNumber = GetChannelNumber();
2392     if (channelNumber.GetChannelNumber())
2393       SwitchToChannel(channelNumber);
2394   }
2395 
SwitchToChannel(const CPVRChannelNumber & channelNumber)2396   void CPVRChannelSwitchingInputHandler::SwitchToChannel(const CPVRChannelNumber& channelNumber)
2397   {
2398     if (channelNumber.IsValid() && CServiceBroker::GetPVRManager().PlaybackState()->IsPlaying())
2399     {
2400       const std::shared_ptr<CPVRChannel> playingChannel = CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel();
2401       if (playingChannel)
2402       {
2403         if (channelNumber != playingChannel->ChannelNumber())
2404         {
2405           // channel number present in playing group?
2406           bool bRadio = playingChannel->IsRadio();
2407           const std::shared_ptr<CPVRChannelGroup> group = CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingGroup(bRadio);
2408           std::shared_ptr<CPVRChannel> channel = group->GetByChannelNumber(channelNumber);
2409 
2410           if (!channel)
2411           {
2412             // channel number present in any group?
2413             const CPVRChannelGroups* groupAccess = CServiceBroker::GetPVRManager().ChannelGroups()->Get(bRadio);
2414             const std::vector<std::shared_ptr<CPVRChannelGroup>> groups = groupAccess->GetMembers(true);
2415             for (const auto& currentGroup : groups)
2416             {
2417               channel = currentGroup->GetByChannelNumber(channelNumber);
2418               if (channel)
2419               {
2420                 // switch channel group
2421                 CServiceBroker::GetPVRManager().PlaybackState()->SetPlayingGroup(currentGroup);
2422                 break;
2423               }
2424             }
2425           }
2426 
2427           if (channel)
2428           {
2429             CApplicationMessenger::GetInstance().PostMsg(
2430               TMSG_GUI_ACTION, WINDOW_INVALID, -1,
2431               static_cast<void*>(new CAction(ACTION_CHANNEL_SWITCH,
2432                                              static_cast<float>(channelNumber.GetChannelNumber()),
2433                                              static_cast<float>(channelNumber.GetSubChannelNumber()))));
2434           }
2435         }
2436       }
2437     }
2438   }
2439 
SwitchToPreviousChannel()2440   void CPVRChannelSwitchingInputHandler::SwitchToPreviousChannel()
2441   {
2442     if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlaying())
2443     {
2444       const std::shared_ptr<CPVRChannel> playingChannel = CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel();
2445       if (playingChannel)
2446       {
2447         const std::shared_ptr<CPVRChannelGroup> group = CServiceBroker::GetPVRManager().ChannelGroups()->GetPreviousPlayedGroup();
2448         if (group)
2449         {
2450           CServiceBroker::GetPVRManager().PlaybackState()->SetPlayingGroup(group);
2451           const std::shared_ptr<CPVRChannel> channel = group->GetLastPlayedChannel(playingChannel->ChannelID());
2452           if (channel)
2453           {
2454             const CPVRChannelNumber channelNumber = channel->ChannelNumber();
2455             CApplicationMessenger::GetInstance().SendMsg(
2456               TMSG_GUI_ACTION, WINDOW_INVALID, -1,
2457               static_cast<void*>(new CAction(ACTION_CHANNEL_SWITCH,
2458                                              static_cast<float>(channelNumber.GetChannelNumber()),
2459                                              static_cast<float>(channelNumber.GetSubChannelNumber()))));
2460           }
2461         }
2462       }
2463     }
2464   }
2465 
2466 } // namespace PVR
2467