[cstdstring] demise Format, replacing with StringUtils::Format
[vuplus_xbmc] / xbmc / pvr / recordings / PVRRecordings.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 "FileItem.h"
22 #include "dialogs/GUIDialogOK.h"
23 #include "guilib/GUIWindowManager.h"
24 #include "guilib/LocalizeStrings.h"
25 #include "Util.h"
26 #include "URL.h"
27 #include "utils/log.h"
28 #include "threads/SingleLock.h"
29 #include "video/VideoDatabase.h"
30
31 #include "utils/URIUtils.h"
32 #include "utils/StringUtils.h"
33
34 #include "pvr/PVRManager.h"
35 #include "pvr/addons/PVRClients.h"
36 #include "PVRRecordings.h"
37 #include "utils/StringUtils.h"
38
39 using namespace PVR;
40
41 CPVRRecordings::CPVRRecordings(void) :
42     m_bIsUpdating(false)
43 {
44
45 }
46
47 void CPVRRecordings::UpdateFromClients(void)
48 {
49   CSingleLock lock(m_critSection);
50   Clear();
51   g_PVRClients->GetRecordings(this);
52 }
53
54 CStdString CPVRRecordings::TrimSlashes(const CStdString &strOrig) const
55 {
56   CStdString strReturn(strOrig);
57   while (strReturn.Left(1) == "/")
58     strReturn.erase(0, 1);
59
60   URIUtils::RemoveSlashAtEnd(strReturn);
61
62   return strReturn;
63 }
64
65 const CStdString CPVRRecordings::GetDirectoryFromPath(const CStdString &strPath, const CStdString &strBase) const
66 {
67   CStdString strReturn;
68   CStdString strUsePath = TrimSlashes(strPath);
69   CStdString strUseBase = TrimSlashes(strBase);
70
71   /* strip the base or return an empty value if it doesn't fit or match */
72   if (!strUseBase.IsEmpty())
73   {
74     /* adding "/" to make sure that base matches the complete folder name and not only parts of it */
75     if (strUsePath.GetLength() <= strUseBase.GetLength() || strUsePath.Left(strUseBase.GetLength() + 1) != strUseBase + "/")
76       return strReturn;
77     strUsePath.erase(0, strUseBase.GetLength());
78   }
79
80   /* check for more occurences */
81   int iDelimiter = strUsePath.Find('/');
82   if (iDelimiter > 0)
83     strReturn = strUsePath.Left(iDelimiter);
84   else
85     strReturn = strUsePath;
86
87   return TrimSlashes(strReturn);
88 }
89
90 bool CPVRRecordings::IsDirectoryMember(const CStdString &strDirectory, const CStdString &strEntryDirectory, bool bDirectMember /* = true */) const
91 {
92   CStdString strUseDirectory = TrimSlashes(strDirectory);
93   CStdString strUseEntryDirectory = TrimSlashes(strEntryDirectory);
94
95   return strUseEntryDirectory.Left(strUseDirectory.GetLength()).Equals(strUseDirectory) &&
96       (!bDirectMember || strUseEntryDirectory.Equals(strUseDirectory));
97 }
98
99 void CPVRRecordings::GetContents(const CStdString &strDirectory, CFileItemList *results)
100 {
101   for (unsigned int iRecordingPtr = 0; iRecordingPtr < m_recordings.size(); iRecordingPtr++)
102   {
103     CPVRRecording *current = m_recordings.at(iRecordingPtr);
104     bool directMember = !HasAllRecordingsPathExtension(strDirectory);
105     if (!IsDirectoryMember(RemoveAllRecordingsPathExtension(strDirectory), current->m_strDirectory, directMember))
106       continue;
107
108     current->UpdateMetadata();
109     CFileItemPtr pFileItem(new CFileItem(*current));
110     pFileItem->SetLabel2(current->RecordingTimeAsLocalTime().GetAsLocalizedDateTime(true, false));
111     pFileItem->m_dateTime = current->RecordingTimeAsLocalTime();
112     pFileItem->SetPath(current->m_strFileNameAndPath);
113
114     if (!current->m_strIconPath.IsEmpty())
115       pFileItem->SetIconImage(current->m_strIconPath);
116
117     if (!current->m_strThumbnailPath.IsEmpty())
118       pFileItem->SetArt("thumb", current->m_strThumbnailPath);
119
120     if (!current->m_strFanartPath.IsEmpty())
121       pFileItem->SetArt("fanart", current->m_strFanartPath);
122
123     pFileItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, pFileItem->GetPVRRecordingInfoTag()->m_playCount > 0);
124
125     results->Add(pFileItem);
126   }
127 }
128
129 void CPVRRecordings::GetSubDirectories(const CStdString &strBase, CFileItemList *results, bool bAutoSkip /* = true */)
130 {
131   CStdString strUseBase = TrimSlashes(strBase);
132
133   std::set<CStdString> unwatchedFolders;
134
135   for (unsigned int iRecordingPtr = 0; iRecordingPtr < m_recordings.size(); iRecordingPtr++)
136   {
137     CPVRRecording *current = m_recordings.at(iRecordingPtr);
138     const CStdString strCurrent = GetDirectoryFromPath(current->m_strDirectory, strUseBase);
139     if (strCurrent.IsEmpty())
140       continue;
141
142     CStdString strFilePath;
143     if(strUseBase.empty())
144       strFilePath = StringUtils::Format("pvr://recordings/%s/", strCurrent.c_str());
145     else
146       strFilePath = StringUtils::Format("pvr://recordings/%s/%s/", strUseBase.c_str(), strCurrent.c_str());
147
148     if (!results->Contains(strFilePath))
149     {
150       current->UpdateMetadata();
151       CFileItemPtr pFileItem;
152       pFileItem.reset(new CFileItem(strCurrent, true));
153       pFileItem->SetPath(strFilePath);
154       pFileItem->SetLabel(strCurrent);
155       pFileItem->SetLabelPreformated(true);
156       pFileItem->m_dateTime = current->RecordingTimeAsLocalTime();
157
158       // Initialize folder overlay from play count (either directly from client or from video database)
159       if (current->m_playCount > 0)
160         pFileItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_WATCHED, false);
161       else
162         unwatchedFolders.insert(strFilePath);
163
164       results->Add(pFileItem);
165     }
166     else
167     {
168       CFileItemPtr pFileItem;
169       pFileItem=results->Get(strFilePath);
170       if (pFileItem->m_dateTime<current->RecordingTimeAsLocalTime())
171         pFileItem->m_dateTime  = current->RecordingTimeAsLocalTime();
172
173       // Unset folder overlay if recording is unwatched
174       if (unwatchedFolders.find(strFilePath) == unwatchedFolders.end())
175       {
176         if (current->m_playCount == 0)
177         {
178           pFileItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, false);
179           unwatchedFolders.insert(strFilePath);
180         }
181       }
182     }
183   }
184
185   int subDirectories = results->Size();
186   CFileItemList files;
187   GetContents(strBase, &files);
188
189   if (bAutoSkip && results->Size() == 1 && files.Size() == 0)
190   {
191     CStdString strGetPath = StringUtils::Format("%s/%s/", strUseBase.c_str(), results->Get(0)->GetLabel().c_str());
192
193     results->Clear();
194
195     CLog::Log(LOGDEBUG, "CPVRRecordings - %s - '%s' only has 1 subdirectory, selecting that directory ('%s')",
196         __FUNCTION__, strUseBase.c_str(), strGetPath.c_str());
197     GetSubDirectories(strGetPath, results, true);
198     return;
199   }
200
201   results->Append(files);
202
203   // Add 'All Recordings' item (if we have at least one subdirectory in the list)
204   if (subDirectories > 0)
205   {
206     CStdString strLabel(g_localizeStrings.Get(19270)); // "* All recordings"
207     CFileItemPtr pItem(new CFileItem(strLabel));
208     CStdString strAllPath;
209     if(strUseBase.empty())
210       strAllPath = "pvr://recordings";
211     else
212       strAllPath = StringUtils::Format("pvr://recordings/%s", strUseBase.c_str());
213     pItem->SetPath(AddAllRecordingsPathExtension(strAllPath));
214     pItem->SetSpecialSort(SortSpecialOnTop);
215     pItem->SetLabelPreformated(true);
216     pItem->m_bIsFolder = true;
217     pItem->m_bIsShareOrDrive = false;
218     for(int i=0; i<results->Size(); ++i)
219     {
220       if(pItem->m_dateTime < results->Get(i)->m_dateTime)
221         pItem->m_dateTime = results->Get(i)->m_dateTime;
222     }
223     results->AddFront(pItem, 0);
224   }
225 }
226
227 bool CPVRRecordings::HasAllRecordingsPathExtension(const CStdString &strDirectory)
228 {
229   CStdString strUseDir = TrimSlashes(strDirectory);
230   CStdString strAllRecordingsPathExtension(PVR_ALL_RECORDINGS_PATH_EXTENSION);
231
232   if (strUseDir.GetLength() < strAllRecordingsPathExtension.GetLength())
233     return false;
234
235   if (strUseDir.GetLength() == strAllRecordingsPathExtension.GetLength())
236     return strUseDir.Equals(strAllRecordingsPathExtension);
237
238   return strUseDir.Right(strAllRecordingsPathExtension.GetLength() + 1).Equals("/" + strAllRecordingsPathExtension);
239 }
240
241 CStdString CPVRRecordings::AddAllRecordingsPathExtension(const CStdString &strDirectory)
242 {
243   if (HasAllRecordingsPathExtension(strDirectory))
244     return strDirectory;
245
246   CStdString strResult = strDirectory;
247   if (!StringUtils::EndsWith(strDirectory, "/"))
248     strResult = strResult + "/";
249
250   return strResult + PVR_ALL_RECORDINGS_PATH_EXTENSION + "/";
251 }
252
253 CStdString CPVRRecordings::RemoveAllRecordingsPathExtension(const CStdString &strDirectory)
254 {
255   if (!HasAllRecordingsPathExtension(strDirectory))
256     return strDirectory;
257
258   return strDirectory.Left(strDirectory.GetLength() - strlen(PVR_ALL_RECORDINGS_PATH_EXTENSION) - 1);
259 }
260
261 int CPVRRecordings::Load(void)
262 {
263   Update();
264
265   return m_recordings.size();
266 }
267
268 void CPVRRecordings::Unload()
269 {
270   Clear();
271 }
272
273 void CPVRRecordings::Update(void)
274 {
275   CSingleLock lock(m_critSection);
276   if (m_bIsUpdating)
277     return;
278   m_bIsUpdating = true;
279   lock.Leave();
280
281   CLog::Log(LOGDEBUG, "CPVRRecordings - %s - updating recordings", __FUNCTION__);
282   UpdateFromClients();
283
284   lock.Enter();
285   m_bIsUpdating = false;
286   SetChanged();
287   lock.Leave();
288
289   NotifyObservers(ObservableMessageRecordings);
290 }
291
292 int CPVRRecordings::GetNumRecordings()
293 {
294   CSingleLock lock(m_critSection);
295   return m_recordings.size();
296 }
297
298 int CPVRRecordings::GetRecordings(CFileItemList* results)
299 {
300   CSingleLock lock(m_critSection);
301
302   for (unsigned int iRecordingPtr = 0; iRecordingPtr < m_recordings.size(); iRecordingPtr++)
303   {
304     CFileItemPtr pFileItem(new CFileItem(*m_recordings.at(iRecordingPtr)));
305     results->Add(pFileItem);
306   }
307
308   return m_recordings.size();
309 }
310
311 bool CPVRRecordings::DeleteRecording(const CFileItem &item)
312 {
313   if (!item.IsPVRRecording())
314   {
315     CLog::Log(LOGERROR, "CPVRRecordings - %s - cannot delete file: no valid recording tag", __FUNCTION__);
316     return false;
317   }
318
319   CPVRRecording *tag = (CPVRRecording *)item.GetPVRRecordingInfoTag();
320   return tag->Delete();
321 }
322
323 bool CPVRRecordings::RenameRecording(CFileItem &item, CStdString &strNewName)
324 {
325   bool bReturn = false;
326
327   if (!item.IsPVRRecording())
328   {
329     CLog::Log(LOGERROR, "CPVRRecordings - %s - cannot rename file: no valid recording tag", __FUNCTION__);
330     return bReturn;
331   }
332
333   CPVRRecording* tag = item.GetPVRRecordingInfoTag();
334   return tag->Rename(strNewName);
335 }
336
337 bool CPVRRecordings::SetRecordingsPlayCount(const CFileItemPtr &item, int count)
338 {
339   bool bResult = false;
340
341   CVideoDatabase database;
342   if (database.Open())
343   {
344     bResult = true;
345
346     CLog::Log(LOGDEBUG, "CPVRRecordings - %s - item path %s", __FUNCTION__, item->GetPath().c_str());
347     CFileItemList items;
348     if (item->m_bIsFolder)
349     {
350       CStdString strPath = item->GetPath();
351       CDirectory::GetDirectory(strPath, items);
352     }
353     else
354       items.Add(item);
355
356     CLog::Log(LOGDEBUG, "CPVRRecordings - %s - will set watched for %d items", __FUNCTION__, items.Size());
357     for (int i=0;i<items.Size();++i)
358     {
359       CLog::Log(LOGDEBUG, "CPVRRecordings - %s - setting watched for item %d", __FUNCTION__, i);
360
361       CFileItemPtr pItem=items[i];
362       if (pItem->m_bIsFolder)
363       {
364         CLog::Log(LOGDEBUG, "CPVRRecordings - %s - path %s is a folder, will call recursively", __FUNCTION__, pItem->GetPath().c_str());
365         if (pItem->GetLabel() != "..")
366         {
367           SetRecordingsPlayCount(pItem, count);
368         }
369         continue;
370       }
371
372       pItem->GetPVRRecordingInfoTag()->SetPlayCount(count);
373
374       // Clear resume bookmark
375       if (count > 0)
376       {
377         database.ClearBookMarksOfFile(pItem->GetPath(), CBookmark::RESUME);
378         pItem->GetPVRRecordingInfoTag()->SetLastPlayedPosition(0);
379       }
380
381       database.SetPlayCount(*pItem, count);
382     }
383
384     database.Close();
385   }
386
387   return bResult;
388 }
389
390 bool CPVRRecordings::GetDirectory(const CStdString& strPath, CFileItemList &items)
391 {
392   bool bSuccess(false);
393
394   {
395     CSingleLock lock(m_critSection);
396
397     CURL url(strPath);
398     CStdString strFileName = url.GetFileName();
399     URIUtils::RemoveSlashAtEnd(strFileName);
400
401     if (strFileName.Left(10) == "recordings")
402     {
403       strFileName.erase(0, 10);
404       GetSubDirectories(strFileName, &items, true);
405       bSuccess = true;
406     }
407   }
408
409   return bSuccess;
410 }
411
412 void CPVRRecordings::SetPlayCount(const CFileItem &item, int iPlayCount)
413 {
414   if (!item.HasPVRRecordingInfoTag())
415     return;
416
417   const CPVRRecording *recording = item.GetPVRRecordingInfoTag();
418   CSingleLock lock(m_critSection);
419   for (unsigned int iRecordingPtr = 0; iRecordingPtr < m_recordings.size(); iRecordingPtr++)
420   {
421     CPVRRecording *current = m_recordings.at(iRecordingPtr);
422     if (current->m_iClientId == recording->m_iClientId && current->m_strRecordingId.Equals(recording->m_strRecordingId))
423     {
424       current->SetPlayCount(iPlayCount);
425       break;
426     }
427   }
428 }
429
430 void CPVRRecordings::GetAll(CFileItemList &items)
431 {
432   CSingleLock lock(m_critSection);
433   for (unsigned int iRecordingPtr = 0; iRecordingPtr < m_recordings.size(); iRecordingPtr++)
434   {
435     CPVRRecording *current = m_recordings.at(iRecordingPtr);
436     current->UpdateMetadata();
437
438     CFileItemPtr pFileItem(new CFileItem(*current));
439     pFileItem->SetLabel2(current->RecordingTimeAsLocalTime().GetAsLocalizedDateTime(true, false));
440     pFileItem->m_dateTime = current->RecordingTimeAsLocalTime();
441     pFileItem->SetPath(current->m_strFileNameAndPath);
442     pFileItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, pFileItem->GetPVRRecordingInfoTag()->m_playCount > 0);
443
444     items.Add(pFileItem);
445   }
446 }
447
448 CFileItemPtr CPVRRecordings::GetByPath(const CStdString &path)
449 {
450   CURL url(path);
451   CStdString fileName = url.GetFileName();
452   URIUtils::RemoveSlashAtEnd(fileName);
453
454   CSingleLock lock(m_critSection);
455
456   if (fileName.Left(11) == "recordings/")
457   {
458     for (unsigned int iRecordingPtr = 0; iRecordingPtr < m_recordings.size(); iRecordingPtr++)
459     {
460       if(path.Equals(m_recordings.at(iRecordingPtr)->m_strFileNameAndPath))
461       {
462         CFileItemPtr fileItem(new CFileItem(*m_recordings.at(iRecordingPtr)));
463         return fileItem;
464       }
465     }
466   }
467
468   CFileItemPtr fileItem(new CFileItem);
469   return fileItem;
470 }
471
472 void CPVRRecordings::Clear()
473 {
474   CSingleLock lock(m_critSection);
475
476   for (unsigned int iRecordingPtr = 0; iRecordingPtr < m_recordings.size(); iRecordingPtr++)
477     delete m_recordings.at(iRecordingPtr);
478   m_recordings.erase(m_recordings.begin(), m_recordings.end());
479 }
480
481 void CPVRRecordings::UpdateEntry(const CPVRRecording &tag)
482 {
483   bool bFound = false;
484   CSingleLock lock(m_critSection);
485
486   for (unsigned int iRecordingPtr = 0; iRecordingPtr < m_recordings.size(); iRecordingPtr++)
487   {
488     CPVRRecording *currentTag = m_recordings.at(iRecordingPtr);
489     if (currentTag->m_iClientId == tag.m_iClientId &&
490         currentTag->m_strRecordingId.Equals(tag.m_strRecordingId))
491     {
492       currentTag->Update(tag);
493       bFound = true;
494       break;
495     }
496   }
497
498   if (!bFound)
499   {
500     CPVRRecording *newTag = new CPVRRecording();
501     newTag->Update(tag);
502     m_recordings.push_back(newTag);
503   }
504 }