Merge pull request #4700 from MartijnKaijser/13.1b2
[vuplus_xbmc] / xbmc / pvr / windows / GUIWindowPVRRecordings.cpp
1 /*
2  *      Copyright (C) 2012-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "GUIWindowPVRRecordings.h"
22
23 #include "guilib/GUIKeyboardFactory.h"
24 #include "dialogs/GUIDialogYesNo.h"
25 #include "guilib/GUIWindowManager.h"
26 #include "guilib/Key.h"
27 #include "guilib/LocalizeStrings.h"
28 #include "GUIInfoManager.h"
29 #include "pvr/PVRManager.h"
30 #include "pvr/recordings/PVRRecordings.h"
31 #include "pvr/timers/PVRTimers.h"
32 #include "pvr/windows/GUIWindowPVR.h"
33 #include "utils/log.h"
34 #include "utils/StringUtils.h"
35 #include "threads/SingleLock.h"
36 #include "pvr/addons/PVRClients.h"
37 #include "video/windows/GUIWindowVideoNav.h"
38
39 using namespace PVR;
40
41 CGUIWindowPVRRecordings::CGUIWindowPVRRecordings(CGUIWindowPVR *parent) :
42   CGUIWindowPVRCommon(parent, PVR_WINDOW_RECORDINGS, CONTROL_BTNRECORDINGS, CONTROL_LIST_RECORDINGS)
43 {
44   m_strSelectedPath = "pvr://recordings/";
45 }
46
47 void CGUIWindowPVRRecordings::UnregisterObservers(void)
48 {
49   CSingleLock lock(m_critSection);
50   if(g_PVRRecordings)
51     g_PVRRecordings->UnregisterObserver(this);
52   if(g_PVRTimers)
53     g_PVRTimers->UnregisterObserver(this);
54   g_infoManager.UnregisterObserver(this);
55 }
56
57 void CGUIWindowPVRRecordings::ResetObservers(void)
58 {
59   CSingleLock lock(m_critSection);
60   g_PVRRecordings->RegisterObserver(this);
61   g_PVRTimers->RegisterObserver(this);
62   g_infoManager.RegisterObserver(this);
63 }
64
65 CStdString CGUIWindowPVRRecordings::GetResumeString(const CFileItem& item)
66 {
67   CStdString resumeString;
68   if (item.IsPVRRecording())
69   {
70
71     // First try to find the resume position on the back-end, if that fails use video database
72     int positionInSeconds = item.GetPVRRecordingInfoTag()->GetLastPlayedPosition();
73     // If the back-end does report a saved position it will be picked up by FileItem
74     if (positionInSeconds < 0)
75     {
76       CVideoDatabase db;
77       if (db.Open())
78       {
79         CBookmark bookmark;
80         CStdString itemPath(item.GetPVRRecordingInfoTag()->m_strFileNameAndPath);
81         if (db.GetResumeBookMark(itemPath, bookmark) )
82           positionInSeconds = lrint(bookmark.timeInSeconds);
83         db.Close();
84       }
85     }
86
87     // Suppress resume from 0
88     if (positionInSeconds > 0)
89       resumeString = StringUtils::Format(g_localizeStrings.Get(12022).c_str(), StringUtils::SecondsToTimeString(positionInSeconds).c_str());
90   }
91   return resumeString;
92 }
93
94 void CGUIWindowPVRRecordings::GetContextButtons(int itemNumber, CContextButtons &buttons) const
95 {
96   if (itemNumber < 0 || itemNumber >= m_parent->m_vecItems->Size())
97     return;
98   CFileItemPtr pItem = m_parent->m_vecItems->Get(itemNumber);
99
100   if (pItem->HasPVRRecordingInfoTag())
101   {
102     buttons.Add(CONTEXT_BUTTON_INFO, 19053);      /* Get Information of this recording */
103     buttons.Add(CONTEXT_BUTTON_FIND, 19003);      /* Find similar program */
104     buttons.Add(CONTEXT_BUTTON_PLAY_ITEM, 12021); /* Play this recording */
105     CStdString resumeString = GetResumeString(*pItem);
106     if (!resumeString.empty())
107     {
108       buttons.Add(CONTEXT_BUTTON_RESUME_ITEM, resumeString);
109     }
110   }
111   if (pItem->m_bIsFolder)
112   {
113     // Have both options for folders since we don't know whether all childs are watched/unwatched
114     buttons.Add(CONTEXT_BUTTON_MARK_UNWATCHED, 16104); /* Mark as UnWatched */
115     buttons.Add(CONTEXT_BUTTON_MARK_WATCHED, 16103);   /* Mark as Watched */
116   }
117   if (pItem->HasPVRRecordingInfoTag())
118   {
119     if (pItem->GetPVRRecordingInfoTag()->m_playCount > 0)
120       buttons.Add(CONTEXT_BUTTON_MARK_UNWATCHED, 16104); /* Mark as UnWatched */
121     else
122       buttons.Add(CONTEXT_BUTTON_MARK_WATCHED, 16103);   /* Mark as Watched */
123
124     buttons.Add(CONTEXT_BUTTON_RENAME, 118);      /* Rename this recording */
125     buttons.Add(CONTEXT_BUTTON_DELETE, 117);      /* Delete this recording */
126   }
127   buttons.Add(CONTEXT_BUTTON_SORTBY_NAME, 103);       /* sort by name */
128   buttons.Add(CONTEXT_BUTTON_SORTBY_DATE, 104);       /* sort by date */
129
130   if (pItem->HasPVRRecordingInfoTag() &&
131       g_PVRClients->HasMenuHooks(pItem->GetPVRRecordingInfoTag()->m_iClientId, PVR_MENUHOOK_RECORDING))
132     buttons.Add(CONTEXT_BUTTON_MENU_HOOKS, 19195);      /* PVR client specific action */
133
134   // Update sort by button
135 //if (m_guiState->GetSortMethod()!=SortByNone)
136 //{
137 //  CStdString sortLabel;
138 //  sortLabel.Format(g_localizeStrings.Get(550).c_str(), g_localizeStrings.Get(m_guiState->GetSortMethodLabel()).c_str());
139 //  buttons.Add(CONTEXT_BUTTON_SORTBY, sortLabel);   /* Sort method */
140 //
141 //  if (m_guiState->GetDisplaySortOrder()==SortOrderAscending)
142 //    buttons.Add(CONTEXT_BUTTON_SORTASC, 584);        /* Sort up or down */
143 //  else
144 //    buttons.Add(CONTEXT_BUTTON_SORTASC, 585);        /* Sort up or down */
145 //}
146 }
147
148 bool CGUIWindowPVRRecordings::OnAction(const CAction &action)
149 {
150   if (action.GetID() == ACTION_PARENT_DIR ||
151       action.GetID() == ACTION_NAV_BACK)
152   {
153     if (m_parent->m_vecItems->GetPath() != "pvr://recordings/")
154     {
155       m_parent->GoParentFolder();
156       return true;
157     }
158   }
159   return CGUIWindowPVRCommon::OnAction(action);
160 }
161
162 bool CGUIWindowPVRRecordings::OnContextButton(int itemNumber, CONTEXT_BUTTON button)
163 {
164   if (itemNumber < 0 || itemNumber >= m_parent->m_vecItems->Size())
165     return false;
166   CFileItemPtr pItem = m_parent->m_vecItems->Get(itemNumber);
167
168   return OnContextButtonPlay(pItem.get(), button) ||
169       OnContextButtonRename(pItem.get(), button) ||
170       OnContextButtonDelete(pItem.get(), button) ||
171       OnContextButtonInfo(pItem.get(), button) ||
172       OnContextButtonMarkWatched(pItem, button) ||
173       CGUIWindowPVRCommon::OnContextButton(itemNumber, button);
174 }
175
176 void CGUIWindowPVRRecordings::OnWindowUnload(void)
177 {
178   m_strSelectedPath = m_parent->m_vecItems->GetPath();
179   CGUIWindowPVRCommon::OnWindowUnload();
180 }
181
182 void CGUIWindowPVRRecordings::UpdateData(bool bUpdateSelectedFile /* = true */)
183 {
184   CSingleLock lock(m_critSection);
185   CLog::Log(LOGDEBUG, "CGUIWindowPVRRecordings - %s - update window '%s'. set view to %d", __FUNCTION__, GetName(), m_iControlList);
186   m_bUpdateRequired = false;
187
188   /* lock the graphics context while updating */
189   CSingleLock graphicsLock(g_graphicsContext);
190
191   m_iSelected = m_parent->m_viewControl.GetSelectedItem();
192   if (!StringUtils::StartsWith(m_parent->m_vecItems->GetPath(), "pvr://recordings/"))
193     m_strSelectedPath = "pvr://recordings/";
194   else
195     m_strSelectedPath = m_parent->m_vecItems->GetPath();
196
197   m_parent->m_viewControl.SetCurrentView(m_iControlList);
198   ShowBusyItem();
199   m_parent->m_vecItems->Clear();
200   m_parent->m_vecItems->SetPath(m_strSelectedPath);
201   m_parent->Update(m_strSelectedPath);
202   m_parent->m_viewControl.SetItems(*m_parent->m_vecItems);
203
204   if (bUpdateSelectedFile)
205   {
206     if (!SelectPlayingFile())
207       m_parent->m_viewControl.SetSelectedItem(m_iSelected);
208   }
209
210   m_parent->SetLabel(CONTROL_LABELHEADER, g_localizeStrings.Get(19017));
211   m_parent->SetLabel(CONTROL_LABELGROUP, "");
212 }
213
214 void CGUIWindowPVRRecordings::Notify(const Observable &obs, const ObservableMessage msg)
215 {
216   if (msg == ObservableMessageTimers || msg == ObservableMessageCurrentItem)
217   {
218     if (IsVisible())
219       SetInvalid();
220     else
221       m_bUpdateRequired = true;
222   }
223   else if (msg == ObservableMessageRecordings || msg == ObservableMessageTimersReset)
224   {
225     if (IsVisible())
226       UpdateData();
227     else
228       m_bUpdateRequired = true;
229   }
230 }
231
232 bool CGUIWindowPVRRecordings::OnClickButton(CGUIMessage &message)
233 {
234   bool bReturn = false;
235
236   if (IsSelectedButton(message))
237   {
238     bReturn = true;
239     g_PVRManager.TriggerRecordingsUpdate();
240   }
241
242   return bReturn;
243 }
244
245 bool CGUIWindowPVRRecordings::OnClickList(CGUIMessage &message)
246 {
247   bool bReturn = false;
248
249   if (IsSelectedList(message))
250   {
251     bReturn = true;
252     int iAction = message.GetParam1();
253     int iItem = m_parent->m_viewControl.GetSelectedItem();
254
255     /* get the fileitem pointer */
256     if (iItem < 0 || iItem >= (int) m_parent->m_vecItems->Size())
257       return bReturn;
258     CFileItemPtr pItem = m_parent->m_vecItems->Get(iItem);
259
260     /* process actions */
261     if (iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK || iAction == ACTION_PLAY)
262     {
263       int choice = CONTEXT_BUTTON_PLAY_ITEM;
264       CStdString resumeString = GetResumeString(*pItem);
265       if (!resumeString.empty())
266       {
267         CContextButtons choices;
268         choices.Add(CONTEXT_BUTTON_RESUME_ITEM, resumeString);
269         choices.Add(CONTEXT_BUTTON_PLAY_ITEM, 12021);
270         choice = CGUIDialogContextMenu::ShowAndGetChoice(choices);
271       }
272       if (choice < 0)
273         bReturn = true;
274       else
275         bReturn = OnContextButtonPlay(pItem.get(), (CONTEXT_BUTTON)choice);
276     }
277     else if (iAction == ACTION_CONTEXT_MENU || iAction == ACTION_MOUSE_RIGHT_CLICK)
278       m_parent->OnPopupMenu(iItem);
279     else if (iAction == ACTION_SHOW_INFO)
280       ShowRecordingInfo(pItem.get());
281     else if (iAction == ACTION_DELETE_ITEM)
282       bReturn = ActionDeleteRecording(pItem.get());
283     else
284       bReturn = false;
285   }
286
287   return bReturn;
288 }
289
290 bool CGUIWindowPVRRecordings::OnContextButtonDelete(CFileItem *item, CONTEXT_BUTTON button)
291 {
292   bool bReturn = false;
293
294   if (button == CONTEXT_BUTTON_DELETE)
295   {
296     bReturn = false;
297
298     CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO);
299     if (!pDialog)
300       return bReturn;
301     pDialog->SetHeading(122);
302     pDialog->SetLine(0, 19043);
303     pDialog->SetLine(1, "");
304     pDialog->SetLine(2, item->GetPVRRecordingInfoTag()->m_strTitle);
305     pDialog->DoModal();
306
307     if (!pDialog->IsConfirmed())
308       return bReturn;
309
310     bReturn = g_PVRRecordings->DeleteRecording(*item);
311   }
312
313   return bReturn;
314 }
315
316 bool CGUIWindowPVRRecordings::OnContextButtonInfo(CFileItem *item, CONTEXT_BUTTON button)
317 {
318   bool bReturn = false;
319
320   if (button == CONTEXT_BUTTON_INFO)
321   {
322     bReturn = true;
323     ShowRecordingInfo(item);
324   }
325
326   return bReturn;
327 }
328
329 bool CGUIWindowPVRRecordings::OnContextButtonPlay(CFileItem *item, CONTEXT_BUTTON button)
330 {
331   bool bReturn = false;
332
333   if ((button == CONTEXT_BUTTON_PLAY_ITEM) ||
334       (button == CONTEXT_BUTTON_RESUME_ITEM))
335   {
336     item->m_lStartOffset = button == CONTEXT_BUTTON_RESUME_ITEM ? STARTOFFSET_RESUME : 0;
337     bReturn = PlayFile(item, false); /* play recording */
338   }
339
340   return bReturn;
341 }
342
343 bool CGUIWindowPVRRecordings::OnContextButtonRename(CFileItem *item, CONTEXT_BUTTON button)
344 {
345   bool bReturn = false;
346
347   if (button == CONTEXT_BUTTON_RENAME)
348   {
349     bReturn = true;
350
351     CPVRRecording *recording = item->GetPVRRecordingInfoTag();
352     CStdString strNewName = recording->m_strTitle;
353     if (CGUIKeyboardFactory::ShowAndGetInput(strNewName, g_localizeStrings.Get(19041), false))
354     {
355       if (g_PVRRecordings->RenameRecording(*item, strNewName))
356         UpdateData();
357     }
358   }
359
360   return bReturn;
361 }
362
363 bool CGUIWindowPVRRecordings::OnContextButtonMarkWatched(const CFileItemPtr &item, CONTEXT_BUTTON button)
364 {
365   bool bReturn = false;
366
367   if (button == CONTEXT_BUTTON_MARK_WATCHED)
368   {
369     bReturn = true;
370
371     int newSelection = m_parent->m_viewControl.GetSelectedItem();
372     g_PVRRecordings->SetRecordingsPlayCount(item, 1);
373     m_parent->m_viewControl.SetSelectedItem(newSelection);
374
375     UpdateData();
376   }
377
378   if (button == CONTEXT_BUTTON_MARK_UNWATCHED)
379   {
380     bReturn = true;
381
382     g_PVRRecordings->SetRecordingsPlayCount(item, 0);
383
384     UpdateData();
385   }
386
387   return bReturn;
388 }
389
390 void CGUIWindowPVRRecordings::BeforeUpdate(const CStdString &strDirectory)
391 {
392   // set items path to current directory
393   m_parent->m_vecItems->SetPath(strDirectory);
394
395   if (m_thumbLoader.IsLoading())
396     m_thumbLoader.StopThread();
397 }
398
399 void CGUIWindowPVRRecordings::AfterUpdate(CFileItemList& items)
400 {
401   if (!items.IsEmpty())
402   {
403     CFileItemList files;
404     for (int i = 0; i < items.Size(); i++)
405     {
406       CFileItemPtr pItem = items[i];
407       if (!pItem->m_bIsFolder)
408         files.Add(pItem);
409     }
410
411     if (!files.IsEmpty())
412     {
413       files.SetPath(items.GetPath());
414       if(m_database.Open())
415       {
416         if (g_PVRRecordings->HasAllRecordingsPathExtension(files.GetPath()))
417         {
418           // Build a map of all files belonging to common subdirectories and call
419           // LoadVideoInfo for each item list
420           typedef boost::shared_ptr<CFileItemList> CFileItemListPtr;
421           typedef std::map<CStdString, CFileItemListPtr> DirectoryMap;
422
423           DirectoryMap directory_map;
424           for (int i = 0; i < files.Size(); i++)
425           {
426             CStdString strDirectory = URIUtils::GetDirectory(files[i]->GetPath());
427             DirectoryMap::iterator it = directory_map.find(strDirectory);
428             if (it == directory_map.end())
429               it = directory_map.insert(std::make_pair(
430                   strDirectory, CFileItemListPtr(new CFileItemList(strDirectory)))).first;
431             it->second->Add(files[i]);
432           }
433
434           for (DirectoryMap::iterator it = directory_map.begin(); it != directory_map.end(); it++)
435             CGUIWindowVideoNav::LoadVideoInfo(*it->second, m_database, false);
436         }
437         else
438           CGUIWindowVideoNav::LoadVideoInfo(files, m_database, false);
439         m_database.Close();
440       }
441       m_thumbLoader.Load(files);
442     }
443   }
444 }