2 * Copyright (C) 2012-2013 Team XBMC
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)
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.
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/>.
22 #include "dialogs/GUIDialogOK.h"
23 #include "guilib/GUIWindowManager.h"
24 #include "guilib/LocalizeStrings.h"
27 #include "utils/log.h"
28 #include "threads/SingleLock.h"
29 #include "video/VideoDatabase.h"
31 #include "utils/URIUtils.h"
32 #include "utils/StringUtils.h"
34 #include "pvr/PVRManager.h"
35 #include "pvr/addons/PVRClients.h"
36 #include "PVRRecordings.h"
37 #include "utils/StringUtils.h"
41 CPVRRecordings::CPVRRecordings(void) :
47 void CPVRRecordings::UpdateFromClients(void)
49 CSingleLock lock(m_critSection);
51 g_PVRClients->GetRecordings(this);
54 CStdString CPVRRecordings::TrimSlashes(const CStdString &strOrig) const
56 CStdString strReturn(strOrig);
57 while (strReturn.Left(1) == "/")
58 strReturn.erase(0, 1);
60 URIUtils::RemoveSlashAtEnd(strReturn);
65 const CStdString CPVRRecordings::GetDirectoryFromPath(const CStdString &strPath, const CStdString &strBase) const
68 CStdString strUsePath = TrimSlashes(strPath);
69 CStdString strUseBase = TrimSlashes(strBase);
71 /* strip the base or return an empty value if it doesn't fit or match */
72 if (!strUseBase.IsEmpty())
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 + "/")
77 strUsePath.erase(0, strUseBase.GetLength());
80 /* check for more occurences */
81 int iDelimiter = strUsePath.Find('/');
83 strReturn = strUsePath.Left(iDelimiter);
85 strReturn = strUsePath;
87 return TrimSlashes(strReturn);
90 bool CPVRRecordings::IsDirectoryMember(const CStdString &strDirectory, const CStdString &strEntryDirectory, bool bDirectMember /* = true */) const
92 CStdString strUseDirectory = TrimSlashes(strDirectory);
93 CStdString strUseEntryDirectory = TrimSlashes(strEntryDirectory);
95 return strUseEntryDirectory.Left(strUseDirectory.GetLength()).Equals(strUseDirectory) &&
96 (!bDirectMember || strUseEntryDirectory.Equals(strUseDirectory));
99 void CPVRRecordings::GetContents(const CStdString &strDirectory, CFileItemList *results)
101 for (unsigned int iRecordingPtr = 0; iRecordingPtr < m_recordings.size(); iRecordingPtr++)
103 CPVRRecording *current = m_recordings.at(iRecordingPtr);
104 bool directMember = !HasAllRecordingsPathExtension(strDirectory);
105 if (!IsDirectoryMember(RemoveAllRecordingsPathExtension(strDirectory), current->m_strDirectory, directMember))
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);
114 if (!current->m_strIconPath.IsEmpty())
115 pFileItem->SetIconImage(current->m_strIconPath);
117 if (!current->m_strThumbnailPath.IsEmpty())
118 pFileItem->SetArt("thumb", current->m_strThumbnailPath);
120 if (!current->m_strFanartPath.IsEmpty())
121 pFileItem->SetArt("fanart", current->m_strFanartPath);
123 pFileItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, pFileItem->GetPVRRecordingInfoTag()->m_playCount > 0);
125 results->Add(pFileItem);
129 void CPVRRecordings::GetSubDirectories(const CStdString &strBase, CFileItemList *results, bool bAutoSkip /* = true */)
131 CStdString strUseBase = TrimSlashes(strBase);
133 std::set<CStdString> unwatchedFolders;
135 for (unsigned int iRecordingPtr = 0; iRecordingPtr < m_recordings.size(); iRecordingPtr++)
137 CPVRRecording *current = m_recordings.at(iRecordingPtr);
138 const CStdString strCurrent = GetDirectoryFromPath(current->m_strDirectory, strUseBase);
139 if (strCurrent.IsEmpty())
142 CStdString strFilePath;
143 if(strUseBase.empty())
144 strFilePath = StringUtils::Format("pvr://recordings/%s/", strCurrent.c_str());
146 strFilePath = StringUtils::Format("pvr://recordings/%s/%s/", strUseBase.c_str(), strCurrent.c_str());
148 if (!results->Contains(strFilePath))
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();
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);
162 unwatchedFolders.insert(strFilePath);
164 results->Add(pFileItem);
168 CFileItemPtr pFileItem;
169 pFileItem=results->Get(strFilePath);
170 if (pFileItem->m_dateTime<current->RecordingTimeAsLocalTime())
171 pFileItem->m_dateTime = current->RecordingTimeAsLocalTime();
173 // Unset folder overlay if recording is unwatched
174 if (unwatchedFolders.find(strFilePath) == unwatchedFolders.end())
176 if (current->m_playCount == 0)
178 pFileItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, false);
179 unwatchedFolders.insert(strFilePath);
185 int subDirectories = results->Size();
187 GetContents(strBase, &files);
189 if (bAutoSkip && results->Size() == 1 && files.Size() == 0)
191 CStdString strGetPath = StringUtils::Format("%s/%s/", strUseBase.c_str(), results->Get(0)->GetLabel().c_str());
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);
201 results->Append(files);
203 // Add 'All Recordings' item (if we have at least one subdirectory in the list)
204 if (subDirectories > 0)
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";
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)
220 if(pItem->m_dateTime < results->Get(i)->m_dateTime)
221 pItem->m_dateTime = results->Get(i)->m_dateTime;
223 results->AddFront(pItem, 0);
227 bool CPVRRecordings::HasAllRecordingsPathExtension(const CStdString &strDirectory)
229 CStdString strUseDir = TrimSlashes(strDirectory);
230 CStdString strAllRecordingsPathExtension(PVR_ALL_RECORDINGS_PATH_EXTENSION);
232 if (strUseDir.GetLength() < strAllRecordingsPathExtension.GetLength())
235 if (strUseDir.GetLength() == strAllRecordingsPathExtension.GetLength())
236 return strUseDir.Equals(strAllRecordingsPathExtension);
238 return strUseDir.Right(strAllRecordingsPathExtension.GetLength() + 1).Equals("/" + strAllRecordingsPathExtension);
241 CStdString CPVRRecordings::AddAllRecordingsPathExtension(const CStdString &strDirectory)
243 if (HasAllRecordingsPathExtension(strDirectory))
246 CStdString strResult = strDirectory;
247 if (!StringUtils::EndsWith(strDirectory, "/"))
248 strResult = strResult + "/";
250 return strResult + PVR_ALL_RECORDINGS_PATH_EXTENSION + "/";
253 CStdString CPVRRecordings::RemoveAllRecordingsPathExtension(const CStdString &strDirectory)
255 if (!HasAllRecordingsPathExtension(strDirectory))
258 return strDirectory.Left(strDirectory.GetLength() - strlen(PVR_ALL_RECORDINGS_PATH_EXTENSION) - 1);
261 int CPVRRecordings::Load(void)
265 return m_recordings.size();
268 void CPVRRecordings::Unload()
273 void CPVRRecordings::Update(void)
275 CSingleLock lock(m_critSection);
278 m_bIsUpdating = true;
281 CLog::Log(LOGDEBUG, "CPVRRecordings - %s - updating recordings", __FUNCTION__);
285 m_bIsUpdating = false;
289 NotifyObservers(ObservableMessageRecordings);
292 int CPVRRecordings::GetNumRecordings()
294 CSingleLock lock(m_critSection);
295 return m_recordings.size();
298 int CPVRRecordings::GetRecordings(CFileItemList* results)
300 CSingleLock lock(m_critSection);
302 for (unsigned int iRecordingPtr = 0; iRecordingPtr < m_recordings.size(); iRecordingPtr++)
304 CFileItemPtr pFileItem(new CFileItem(*m_recordings.at(iRecordingPtr)));
305 results->Add(pFileItem);
308 return m_recordings.size();
311 bool CPVRRecordings::DeleteRecording(const CFileItem &item)
313 if (!item.IsPVRRecording())
315 CLog::Log(LOGERROR, "CPVRRecordings - %s - cannot delete file: no valid recording tag", __FUNCTION__);
319 CPVRRecording *tag = (CPVRRecording *)item.GetPVRRecordingInfoTag();
320 return tag->Delete();
323 bool CPVRRecordings::RenameRecording(CFileItem &item, CStdString &strNewName)
325 bool bReturn = false;
327 if (!item.IsPVRRecording())
329 CLog::Log(LOGERROR, "CPVRRecordings - %s - cannot rename file: no valid recording tag", __FUNCTION__);
333 CPVRRecording* tag = item.GetPVRRecordingInfoTag();
334 return tag->Rename(strNewName);
337 bool CPVRRecordings::SetRecordingsPlayCount(const CFileItemPtr &item, int count)
339 bool bResult = false;
341 CVideoDatabase database;
346 CLog::Log(LOGDEBUG, "CPVRRecordings - %s - item path %s", __FUNCTION__, item->GetPath().c_str());
348 if (item->m_bIsFolder)
350 CStdString strPath = item->GetPath();
351 CDirectory::GetDirectory(strPath, items);
356 CLog::Log(LOGDEBUG, "CPVRRecordings - %s - will set watched for %d items", __FUNCTION__, items.Size());
357 for (int i=0;i<items.Size();++i)
359 CLog::Log(LOGDEBUG, "CPVRRecordings - %s - setting watched for item %d", __FUNCTION__, i);
361 CFileItemPtr pItem=items[i];
362 if (pItem->m_bIsFolder)
364 CLog::Log(LOGDEBUG, "CPVRRecordings - %s - path %s is a folder, will call recursively", __FUNCTION__, pItem->GetPath().c_str());
365 if (pItem->GetLabel() != "..")
367 SetRecordingsPlayCount(pItem, count);
372 pItem->GetPVRRecordingInfoTag()->SetPlayCount(count);
374 // Clear resume bookmark
377 database.ClearBookMarksOfFile(pItem->GetPath(), CBookmark::RESUME);
378 pItem->GetPVRRecordingInfoTag()->SetLastPlayedPosition(0);
381 database.SetPlayCount(*pItem, count);
390 bool CPVRRecordings::GetDirectory(const CStdString& strPath, CFileItemList &items)
392 bool bSuccess(false);
395 CSingleLock lock(m_critSection);
398 CStdString strFileName = url.GetFileName();
399 URIUtils::RemoveSlashAtEnd(strFileName);
401 if (strFileName.Left(10) == "recordings")
403 strFileName.erase(0, 10);
404 GetSubDirectories(strFileName, &items, true);
412 void CPVRRecordings::SetPlayCount(const CFileItem &item, int iPlayCount)
414 if (!item.HasPVRRecordingInfoTag())
417 const CPVRRecording *recording = item.GetPVRRecordingInfoTag();
418 CSingleLock lock(m_critSection);
419 for (unsigned int iRecordingPtr = 0; iRecordingPtr < m_recordings.size(); iRecordingPtr++)
421 CPVRRecording *current = m_recordings.at(iRecordingPtr);
422 if (current->m_iClientId == recording->m_iClientId && current->m_strRecordingId.Equals(recording->m_strRecordingId))
424 current->SetPlayCount(iPlayCount);
430 void CPVRRecordings::GetAll(CFileItemList &items)
432 CSingleLock lock(m_critSection);
433 for (unsigned int iRecordingPtr = 0; iRecordingPtr < m_recordings.size(); iRecordingPtr++)
435 CPVRRecording *current = m_recordings.at(iRecordingPtr);
436 current->UpdateMetadata();
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);
444 items.Add(pFileItem);
448 CFileItemPtr CPVRRecordings::GetByPath(const CStdString &path)
451 CStdString fileName = url.GetFileName();
452 URIUtils::RemoveSlashAtEnd(fileName);
454 CSingleLock lock(m_critSection);
456 if (fileName.Left(11) == "recordings/")
458 for (unsigned int iRecordingPtr = 0; iRecordingPtr < m_recordings.size(); iRecordingPtr++)
460 if(path.Equals(m_recordings.at(iRecordingPtr)->m_strFileNameAndPath))
462 CFileItemPtr fileItem(new CFileItem(*m_recordings.at(iRecordingPtr)));
468 CFileItemPtr fileItem(new CFileItem);
472 void CPVRRecordings::Clear()
474 CSingleLock lock(m_critSection);
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());
481 void CPVRRecordings::UpdateEntry(const CPVRRecording &tag)
484 CSingleLock lock(m_critSection);
486 for (unsigned int iRecordingPtr = 0; iRecordingPtr < m_recordings.size(); iRecordingPtr++)
488 CPVRRecording *currentTag = m_recordings.at(iRecordingPtr);
489 if (currentTag->m_iClientId == tag.m_iClientId &&
490 currentTag->m_strRecordingId.Equals(tag.m_strRecordingId))
492 currentTag->Update(tag);
500 CPVRRecording *newTag = new CPVRRecording();
502 m_recordings.push_back(newTag);