[release] version bump to 13.0 beta1
[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 then make sure there is a corresponding resume bookmark
74     if (positionInSeconds > 0)
75     {
76       CBookmark bookmark;
77       bookmark.timeInSeconds = positionInSeconds;
78       bookmark.totalTimeInSeconds = (double)item.GetPVRRecordingInfoTag()->GetDuration();
79       CVideoDatabase db;
80       if (db.Open())
81       {
82         CStdString itemPath(item.GetPVRRecordingInfoTag()->m_strFileNameAndPath);
83         db.AddBookMarkToFile(itemPath, bookmark, CBookmark::RESUME);
84         db.Close();
85       }
86     }
87     else if (positionInSeconds < 0)
88     {
89       CVideoDatabase db;
90       if (db.Open())
91       {
92         CBookmark bookmark;
93         CStdString itemPath(item.GetPVRRecordingInfoTag()->m_strFileNameAndPath);
94         if (db.GetResumeBookMark(itemPath, bookmark) )
95           positionInSeconds = lrint(bookmark.timeInSeconds);
96         db.Close();
97       }
98     }
99
100     // Suppress resume from 0
101     if (positionInSeconds > 0)
102       resumeString = StringUtils::Format(g_localizeStrings.Get(12022).c_str(), StringUtils::SecondsToTimeString(positionInSeconds).c_str());
103   }
104   return resumeString;
105 }
106
107 void CGUIWindowPVRRecordings::GetContextButtons(int itemNumber, CContextButtons &buttons) const
108 {
109   if (itemNumber < 0 || itemNumber >= m_parent->m_vecItems->Size())
110     return;
111   CFileItemPtr pItem = m_parent->m_vecItems->Get(itemNumber);
112
113   if (pItem->HasPVRRecordingInfoTag())
114   {
115     buttons.Add(CONTEXT_BUTTON_INFO, 19053);      /* Get Information of this recording */
116     buttons.Add(CONTEXT_BUTTON_FIND, 19003);      /* Find similar program */
117     buttons.Add(CONTEXT_BUTTON_PLAY_ITEM, 12021); /* Play this recording */
118     CStdString resumeString = GetResumeString(*pItem);
119     if (!resumeString.empty())
120     {
121       buttons.Add(CONTEXT_BUTTON_RESUME_ITEM, resumeString);
122     }
123   }
124   if (pItem->m_bIsFolder)
125   {
126     // Have both options for folders since we don't know whether all childs are watched/unwatched
127     buttons.Add(CONTEXT_BUTTON_MARK_UNWATCHED, 16104); /* Mark as UnWatched */
128     buttons.Add(CONTEXT_BUTTON_MARK_WATCHED, 16103);   /* Mark as Watched */
129   }
130   if (pItem->HasPVRRecordingInfoTag())
131   {
132     if (pItem->GetPVRRecordingInfoTag()->m_playCount > 0)
133       buttons.Add(CONTEXT_BUTTON_MARK_UNWATCHED, 16104); /* Mark as UnWatched */
134     else
135       buttons.Add(CONTEXT_BUTTON_MARK_WATCHED, 16103);   /* Mark as Watched */
136
137     buttons.Add(CONTEXT_BUTTON_RENAME, 118);      /* Rename this recording */
138     buttons.Add(CONTEXT_BUTTON_DELETE, 117);      /* Delete this recording */
139   }
140   buttons.Add(CONTEXT_BUTTON_SORTBY_NAME, 103);       /* sort by name */
141   buttons.Add(CONTEXT_BUTTON_SORTBY_DATE, 104);       /* sort by date */
142
143   if (pItem->HasPVRRecordingInfoTag() &&
144       g_PVRClients->HasMenuHooks(pItem->GetPVRRecordingInfoTag()->m_iClientId, PVR_MENUHOOK_RECORDING))
145     buttons.Add(CONTEXT_BUTTON_MENU_HOOKS, 19195);      /* PVR client specific action */
146
147   // Update sort by button
148 //if (m_guiState->GetSortMethod()!=SortByNone)
149 //{
150 //  CStdString sortLabel;
151 //  sortLabel.Format(g_localizeStrings.Get(550).c_str(), g_localizeStrings.Get(m_guiState->GetSortMethodLabel()).c_str());
152 //  buttons.Add(CONTEXT_BUTTON_SORTBY, sortLabel);   /* Sort method */
153 //
154 //  if (m_guiState->GetDisplaySortOrder()==SortOrderAscending)
155 //    buttons.Add(CONTEXT_BUTTON_SORTASC, 584);        /* Sort up or down */
156 //  else
157 //    buttons.Add(CONTEXT_BUTTON_SORTASC, 585);        /* Sort up or down */
158 //}
159 }
160
161 bool CGUIWindowPVRRecordings::OnAction(const CAction &action)
162 {
163   if (action.GetID() == ACTION_PARENT_DIR ||
164       action.GetID() == ACTION_NAV_BACK)
165   {
166     if (m_parent->m_vecItems->GetPath() != "pvr://recordings/")
167       m_parent->GoParentFolder();
168     else if (action.GetID() == ACTION_NAV_BACK)
169       g_windowManager.PreviousWindow();
170
171     return true;
172   }
173
174   return CGUIWindowPVRCommon::OnAction(action);
175 }
176
177 bool CGUIWindowPVRRecordings::OnContextButton(int itemNumber, CONTEXT_BUTTON button)
178 {
179   if (itemNumber < 0 || itemNumber >= m_parent->m_vecItems->Size())
180     return false;
181   CFileItemPtr pItem = m_parent->m_vecItems->Get(itemNumber);
182
183   return OnContextButtonPlay(pItem.get(), button) ||
184       OnContextButtonRename(pItem.get(), button) ||
185       OnContextButtonDelete(pItem.get(), button) ||
186       OnContextButtonInfo(pItem.get(), button) ||
187       OnContextButtonMarkWatched(pItem, button) ||
188       CGUIWindowPVRCommon::OnContextButton(itemNumber, button);
189 }
190
191 void CGUIWindowPVRRecordings::OnWindowUnload(void)
192 {
193   m_strSelectedPath = m_parent->m_vecItems->GetPath();
194   CGUIWindowPVRCommon::OnWindowUnload();
195 }
196
197 void CGUIWindowPVRRecordings::UpdateData(bool bUpdateSelectedFile /* = true */)
198 {
199   CSingleLock lock(m_critSection);
200   CLog::Log(LOGDEBUG, "CGUIWindowPVRRecordings - %s - update window '%s'. set view to %d", __FUNCTION__, GetName(), m_iControlList);
201   m_bUpdateRequired = false;
202
203   /* lock the graphics context while updating */
204   CSingleLock graphicsLock(g_graphicsContext);
205
206   m_iSelected = m_parent->m_viewControl.GetSelectedItem();
207   if (!StringUtils::StartsWith(m_parent->m_vecItems->GetPath(), "pvr://recordings/"))
208     m_strSelectedPath = "pvr://recordings/";
209   else
210     m_strSelectedPath = m_parent->m_vecItems->GetPath();
211
212   m_parent->m_viewControl.SetCurrentView(m_iControlList);
213   ShowBusyItem();
214   m_parent->m_vecItems->Clear();
215   m_parent->m_vecItems->SetPath(m_strSelectedPath);
216   m_parent->Update(m_strSelectedPath);
217   m_parent->m_viewControl.SetItems(*m_parent->m_vecItems);
218
219   if (bUpdateSelectedFile)
220   {
221     if (!SelectPlayingFile())
222       m_parent->m_viewControl.SetSelectedItem(m_iSelected);
223   }
224
225   m_parent->SetLabel(CONTROL_LABELHEADER, g_localizeStrings.Get(19017));
226   m_parent->SetLabel(CONTROL_LABELGROUP, "");
227 }
228
229 void CGUIWindowPVRRecordings::Notify(const Observable &obs, const ObservableMessage msg)
230 {
231   if (msg == ObservableMessageTimers || msg == ObservableMessageCurrentItem)
232   {
233     if (IsVisible())
234       SetInvalid();
235     else
236       m_bUpdateRequired = true;
237   }
238   else if (msg == ObservableMessageRecordings || msg == ObservableMessageTimersReset)
239   {
240     if (IsVisible())
241       UpdateData();
242     else
243       m_bUpdateRequired = true;
244   }
245 }
246
247 bool CGUIWindowPVRRecordings::OnClickButton(CGUIMessage &message)
248 {
249   bool bReturn = false;
250
251   if (IsSelectedButton(message))
252   {
253     bReturn = true;
254     g_PVRManager.TriggerRecordingsUpdate();
255   }
256
257   return bReturn;
258 }
259
260 bool CGUIWindowPVRRecordings::OnClickList(CGUIMessage &message)
261 {
262   bool bReturn = false;
263
264   if (IsSelectedList(message))
265   {
266     bReturn = true;
267     int iAction = message.GetParam1();
268     int iItem = m_parent->m_viewControl.GetSelectedItem();
269
270     /* get the fileitem pointer */
271     if (iItem < 0 || iItem >= (int) m_parent->m_vecItems->Size())
272       return bReturn;
273     CFileItemPtr pItem = m_parent->m_vecItems->Get(iItem);
274
275     /* process actions */
276     if (iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK || iAction == ACTION_PLAY)
277     {
278       int choice = CONTEXT_BUTTON_PLAY_ITEM;
279       CStdString resumeString = GetResumeString(*pItem);
280       if (!resumeString.empty())
281       {
282         CContextButtons choices;
283         choices.Add(CONTEXT_BUTTON_RESUME_ITEM, resumeString);
284         choices.Add(CONTEXT_BUTTON_PLAY_ITEM, 12021);
285         choice = CGUIDialogContextMenu::ShowAndGetChoice(choices);
286       }
287       if (choice < 0)
288         bReturn = true;
289       else
290         bReturn = OnContextButtonPlay(pItem.get(), (CONTEXT_BUTTON)choice);
291     }
292     else if (iAction == ACTION_CONTEXT_MENU || iAction == ACTION_MOUSE_RIGHT_CLICK)
293       m_parent->OnPopupMenu(iItem);
294     else if (iAction == ACTION_SHOW_INFO)
295       ShowRecordingInfo(pItem.get());
296     else if (iAction == ACTION_DELETE_ITEM)
297       bReturn = ActionDeleteRecording(pItem.get());
298     else
299       bReturn = false;
300   }
301
302   return bReturn;
303 }
304
305 bool CGUIWindowPVRRecordings::OnContextButtonDelete(CFileItem *item, CONTEXT_BUTTON button)
306 {
307   bool bReturn = false;
308
309   if (button == CONTEXT_BUTTON_DELETE)
310   {
311     bReturn = false;
312
313     CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO);
314     if (!pDialog)
315       return bReturn;
316     pDialog->SetHeading(122);
317     pDialog->SetLine(0, 19043);
318     pDialog->SetLine(1, "");
319     pDialog->SetLine(2, item->GetPVRRecordingInfoTag()->m_strTitle);
320     pDialog->DoModal();
321
322     if (!pDialog->IsConfirmed())
323       return bReturn;
324
325     bReturn = g_PVRRecordings->DeleteRecording(*item);
326   }
327
328   return bReturn;
329 }
330
331 bool CGUIWindowPVRRecordings::OnContextButtonInfo(CFileItem *item, CONTEXT_BUTTON button)
332 {
333   bool bReturn = false;
334
335   if (button == CONTEXT_BUTTON_INFO)
336   {
337     bReturn = true;
338     ShowRecordingInfo(item);
339   }
340
341   return bReturn;
342 }
343
344 bool CGUIWindowPVRRecordings::OnContextButtonPlay(CFileItem *item, CONTEXT_BUTTON button)
345 {
346   bool bReturn = false;
347
348   if ((button == CONTEXT_BUTTON_PLAY_ITEM) ||
349       (button == CONTEXT_BUTTON_RESUME_ITEM))
350   {
351     item->m_lStartOffset = button == CONTEXT_BUTTON_RESUME_ITEM ? STARTOFFSET_RESUME : 0;
352     bReturn = PlayFile(item, false); /* play recording */
353   }
354
355   return bReturn;
356 }
357
358 bool CGUIWindowPVRRecordings::OnContextButtonRename(CFileItem *item, CONTEXT_BUTTON button)
359 {
360   bool bReturn = false;
361
362   if (button == CONTEXT_BUTTON_RENAME)
363   {
364     bReturn = true;
365
366     CPVRRecording *recording = item->GetPVRRecordingInfoTag();
367     CStdString strNewName = recording->m_strTitle;
368     if (CGUIKeyboardFactory::ShowAndGetInput(strNewName, g_localizeStrings.Get(19041), false))
369     {
370       if (g_PVRRecordings->RenameRecording(*item, strNewName))
371         UpdateData();
372     }
373   }
374
375   return bReturn;
376 }
377
378 bool CGUIWindowPVRRecordings::OnContextButtonMarkWatched(const CFileItemPtr &item, CONTEXT_BUTTON button)
379 {
380   bool bReturn = false;
381
382   if (button == CONTEXT_BUTTON_MARK_WATCHED)
383   {
384     bReturn = true;
385
386     int newSelection = m_parent->m_viewControl.GetSelectedItem();
387     g_PVRRecordings->SetRecordingsPlayCount(item, 1);
388     m_parent->m_viewControl.SetSelectedItem(newSelection);
389
390     UpdateData();
391   }
392
393   if (button == CONTEXT_BUTTON_MARK_UNWATCHED)
394   {
395     bReturn = true;
396
397     g_PVRRecordings->SetRecordingsPlayCount(item, 0);
398
399     UpdateData();
400   }
401
402   return bReturn;
403 }
404
405 void CGUIWindowPVRRecordings::BeforeUpdate(const CStdString &strDirectory)
406 {
407   if (m_thumbLoader.IsLoading())
408     m_thumbLoader.StopThread();
409 }
410
411 void CGUIWindowPVRRecordings::AfterUpdate(CFileItemList& items)
412 {
413   if (!items.IsEmpty())
414   {
415     CFileItemList files;
416     for (int i = 0; i < items.Size(); i++)
417     {
418       CFileItemPtr pItem = items[i];
419       if (!pItem->m_bIsFolder)
420         files.Add(pItem);
421     }
422
423     if (!files.IsEmpty())
424     {
425       files.SetPath(items.GetPath());
426       if(m_database.Open())
427       {
428         if (g_PVRRecordings->HasAllRecordingsPathExtension(files.GetPath()))
429         {
430           // Build a map of all files belonging to common subdirectories and call
431           // LoadVideoInfo for each item list
432           typedef boost::shared_ptr<CFileItemList> CFileItemListPtr;
433           typedef std::map<CStdString, CFileItemListPtr> DirectoryMap;
434
435           DirectoryMap directory_map;
436           for (int i = 0; i < files.Size(); i++)
437           {
438             CStdString strDirectory = URIUtils::GetDirectory(files[i]->GetPath());
439             DirectoryMap::iterator it = directory_map.find(strDirectory);
440             if (it == directory_map.end())
441               it = directory_map.insert(std::make_pair(
442                   strDirectory, CFileItemListPtr(new CFileItemList(strDirectory)))).first;
443             it->second->Add(files[i]);
444           }
445
446           for (DirectoryMap::iterator it = directory_map.begin(); it != directory_map.end(); it++)
447             CGUIWindowVideoNav::LoadVideoInfo(*it->second, m_database, false);
448         }
449         else
450           CGUIWindowVideoNav::LoadVideoInfo(files, m_database, false);
451         m_database.Close();
452       }
453       m_thumbLoader.Load(files);
454     }
455   }
456 }