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"
30 #include "settings/Settings.h"
32 #include "utils/URIUtils.h"
33 #include "utils/StringUtils.h"
35 #include "pvr/PVRManager.h"
36 #include "pvr/addons/PVRClients.h"
37 #include "PVRRecordings.h"
41 CPVRRecordings::CPVRRecordings(void) :
48 void CPVRRecordings::UpdateFromClients(void)
50 CSingleLock lock(m_critSection);
52 g_PVRClients->GetRecordings(this);
55 CStdString CPVRRecordings::TrimSlashes(const CStdString &strOrig) const
57 CStdString strReturn(strOrig);
58 while (strReturn[0] == '/')
59 strReturn.erase(0, 1);
61 URIUtils::RemoveSlashAtEnd(strReturn);
66 const CStdString CPVRRecordings::GetDirectoryFromPath(const CStdString &strPath, const CStdString &strBase) const
69 CStdString strUsePath = TrimSlashes(strPath);
70 CStdString strUseBase = TrimSlashes(strBase);
72 /* strip the base or return an empty value if it doesn't fit or match */
73 if (!strUseBase.empty())
75 /* adding "/" to make sure that base matches the complete folder name and not only parts of it */
76 if (strUsePath.size() <= strUseBase.size() || !StringUtils::StartsWith(strUsePath, strUseBase + "/"))
78 strUsePath.erase(0, strUseBase.size());
81 /* check for more occurences */
82 size_t iDelimiter = strUsePath.find('/');
83 if (iDelimiter != std::string::npos && iDelimiter > 0)
84 strReturn = strUsePath.substr(0, iDelimiter);
86 strReturn = strUsePath;
88 return TrimSlashes(strReturn);
91 bool CPVRRecordings::IsDirectoryMember(const CStdString &strDirectory, const CStdString &strEntryDirectory, bool bDirectMember /* = true */) const
93 CStdString strUseDirectory = TrimSlashes(strDirectory);
94 CStdString strUseEntryDirectory = TrimSlashes(strEntryDirectory);
96 /* Case-insensitive comparison since sub folders are created with case-insensitive matching (GetSubDirectories) */
97 return StringUtils::StartsWithNoCase(strUseEntryDirectory, strUseDirectory) &&
98 (!bDirectMember || strUseEntryDirectory.Equals(strUseDirectory));
101 void CPVRRecordings::GetContents(const CStdString &strDirectory, CFileItemList *results)
103 for (unsigned int iRecordingPtr = 0; iRecordingPtr < m_recordings.size(); iRecordingPtr++)
105 CPVRRecording *current = m_recordings.at(iRecordingPtr);
106 bool directMember = !HasAllRecordingsPathExtension(strDirectory);
107 if (!IsDirectoryMember(RemoveAllRecordingsPathExtension(strDirectory), current->m_strDirectory, directMember))
110 current->UpdateMetadata();
111 CFileItemPtr pFileItem(new CFileItem(*current));
112 pFileItem->SetLabel2(current->RecordingTimeAsLocalTime().GetAsLocalizedDateTime(true, false));
113 pFileItem->m_dateTime = current->RecordingTimeAsLocalTime();
114 pFileItem->SetPath(current->m_strFileNameAndPath);
117 if (!current->m_strIconPath.empty())
119 pFileItem->SetIconImage(current->m_strIconPath);
120 pFileItem->SetArt("icon", current->m_strIconPath);
123 if (!current->m_strThumbnailPath.empty())
124 pFileItem->SetArt("thumb", current->m_strThumbnailPath);
126 if (!current->m_strFanartPath.empty())
127 pFileItem->SetArt("fanart", current->m_strFanartPath);
129 // Use the channel icon as a fallback when a thumbnail is not available
130 pFileItem->SetArtFallback("thumb", "icon");
132 pFileItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, pFileItem->GetPVRRecordingInfoTag()->m_playCount > 0);
134 results->Add(pFileItem);
138 void CPVRRecordings::GetSubDirectories(const CStdString &strBase, CFileItemList *results)
140 CStdString strUseBase = TrimSlashes(strBase);
142 std::set<CStdString> unwatchedFolders;
144 for (unsigned int iRecordingPtr = 0; iRecordingPtr < m_recordings.size(); iRecordingPtr++)
146 CPVRRecording *current = m_recordings.at(iRecordingPtr);
147 const CStdString strCurrent = GetDirectoryFromPath(current->m_strDirectory, strUseBase);
148 if (strCurrent.empty())
151 CStdString strFilePath;
152 if(strUseBase.empty())
153 strFilePath = StringUtils::Format("pvr://recordings/%s/", strCurrent.c_str());
155 strFilePath = StringUtils::Format("pvr://recordings/%s/%s/", strUseBase.c_str(), strCurrent.c_str());
157 if (!results->Contains(strFilePath))
159 current->UpdateMetadata();
160 CFileItemPtr pFileItem;
161 pFileItem.reset(new CFileItem(strCurrent, true));
162 pFileItem->SetPath(strFilePath);
163 pFileItem->SetLabel(strCurrent);
164 pFileItem->SetLabelPreformated(true);
165 pFileItem->m_dateTime = current->RecordingTimeAsLocalTime();
167 // Initialize folder overlay from play count (either directly from client or from video database)
168 if (current->m_playCount > 0)
169 pFileItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_WATCHED, false);
171 unwatchedFolders.insert(strFilePath);
173 results->Add(pFileItem);
177 CFileItemPtr pFileItem;
178 pFileItem=results->Get(strFilePath);
179 if (pFileItem->m_dateTime<current->RecordingTimeAsLocalTime())
180 pFileItem->m_dateTime = current->RecordingTimeAsLocalTime();
182 // Unset folder overlay if recording is unwatched
183 if (unwatchedFolders.find(strFilePath) == unwatchedFolders.end())
185 if (current->m_playCount == 0)
187 pFileItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, false);
188 unwatchedFolders.insert(strFilePath);
194 int subDirectories = results->Size();
196 GetContents(strBase, &files);
198 if (results->Size() == 1 && files.Size() == 0)
200 CStdString strGetPath = StringUtils::Format("%s/%s/", strUseBase.c_str(), results->Get(0)->GetLabel().c_str());
204 CLog::Log(LOGDEBUG, "CPVRRecordings - %s - '%s' only has 1 subdirectory, selecting that directory ('%s')",
205 __FUNCTION__, strUseBase.c_str(), strGetPath.c_str());
206 GetSubDirectories(strGetPath, results);
210 results->Append(files);
212 // Add 'All Recordings' item (if we have at least one subdirectory in the list)
213 if (subDirectories > 0)
215 CStdString strLabel(g_localizeStrings.Get(19270)); // "* All recordings"
216 CFileItemPtr pItem(new CFileItem(strLabel));
217 CStdString strAllPath;
218 if(strUseBase.empty())
219 strAllPath = "pvr://recordings";
221 strAllPath = StringUtils::Format("pvr://recordings/%s", strUseBase.c_str());
222 pItem->SetPath(AddAllRecordingsPathExtension(strAllPath));
223 pItem->SetSpecialSort(SortSpecialOnTop);
224 pItem->SetLabelPreformated(true);
225 pItem->m_bIsFolder = true;
226 pItem->m_bIsShareOrDrive = false;
227 for(int i=0; i<results->Size(); ++i)
229 if(pItem->m_dateTime < results->Get(i)->m_dateTime)
230 pItem->m_dateTime = results->Get(i)->m_dateTime;
232 results->AddFront(pItem, 0);
236 bool CPVRRecordings::HasAllRecordingsPathExtension(const CStdString &strDirectory)
238 CStdString strUseDir = TrimSlashes(strDirectory);
239 CStdString strAllRecordingsPathExtension(PVR_ALL_RECORDINGS_PATH_EXTENSION);
241 if (strUseDir.size() < strAllRecordingsPathExtension.size())
244 if (strUseDir.size() == strAllRecordingsPathExtension.size())
245 return strUseDir.Equals(strAllRecordingsPathExtension);
247 return StringUtils::EndsWith(strUseDir, "/" + strAllRecordingsPathExtension);
250 CStdString CPVRRecordings::AddAllRecordingsPathExtension(const CStdString &strDirectory)
252 if (HasAllRecordingsPathExtension(strDirectory))
255 CStdString strResult = strDirectory;
256 if (!StringUtils::EndsWith(strDirectory, "/"))
257 strResult = strResult + "/";
259 return strResult + PVR_ALL_RECORDINGS_PATH_EXTENSION + "/";
262 CStdString CPVRRecordings::RemoveAllRecordingsPathExtension(const CStdString &strDirectory)
264 if (!HasAllRecordingsPathExtension(strDirectory))
267 return strDirectory.substr(0, strDirectory.size() - strlen(PVR_ALL_RECORDINGS_PATH_EXTENSION) - 1);
270 int CPVRRecordings::Load(void)
274 return m_recordings.size();
277 void CPVRRecordings::Unload()
282 void CPVRRecordings::Update(void)
284 CSingleLock lock(m_critSection);
287 m_bIsUpdating = true;
290 CLog::Log(LOGDEBUG, "CPVRRecordings - %s - updating recordings", __FUNCTION__);
294 m_bIsUpdating = false;
298 NotifyObservers(ObservableMessageRecordings);
301 int CPVRRecordings::GetNumRecordings()
303 CSingleLock lock(m_critSection);
304 return m_recordings.size();
307 int CPVRRecordings::GetRecordings(CFileItemList* results)
309 CSingleLock lock(m_critSection);
311 for (unsigned int iRecordingPtr = 0; iRecordingPtr < m_recordings.size(); iRecordingPtr++)
313 CFileItemPtr pFileItem(new CFileItem(*m_recordings.at(iRecordingPtr)));
314 results->Add(pFileItem);
317 return m_recordings.size();
320 bool CPVRRecordings::DeleteRecording(const CFileItem &item)
322 if (!item.IsPVRRecording())
324 CLog::Log(LOGERROR, "CPVRRecordings - %s - cannot delete file: no valid recording tag", __FUNCTION__);
328 CPVRRecording *tag = (CPVRRecording *)item.GetPVRRecordingInfoTag();
329 return tag->Delete();
332 bool CPVRRecordings::RenameRecording(CFileItem &item, CStdString &strNewName)
334 bool bReturn = false;
336 if (!item.IsPVRRecording())
338 CLog::Log(LOGERROR, "CPVRRecordings - %s - cannot rename file: no valid recording tag", __FUNCTION__);
342 CPVRRecording* tag = item.GetPVRRecordingInfoTag();
343 return tag->Rename(strNewName);
346 bool CPVRRecordings::SetRecordingsPlayCount(const CFileItemPtr &item, int count)
348 bool bResult = false;
350 CVideoDatabase database;
355 CLog::Log(LOGDEBUG, "CPVRRecordings - %s - item path %s", __FUNCTION__, item->GetPath().c_str());
357 if (item->m_bIsFolder)
359 CStdString strPath = item->GetPath();
360 CDirectory::GetDirectory(strPath, items);
365 CLog::Log(LOGDEBUG, "CPVRRecordings - %s - will set watched for %d items", __FUNCTION__, items.Size());
366 for (int i=0;i<items.Size();++i)
368 CLog::Log(LOGDEBUG, "CPVRRecordings - %s - setting watched for item %d", __FUNCTION__, i);
370 CFileItemPtr pItem=items[i];
371 if (pItem->m_bIsFolder)
373 CLog::Log(LOGDEBUG, "CPVRRecordings - %s - path %s is a folder, will call recursively", __FUNCTION__, pItem->GetPath().c_str());
374 if (pItem->GetLabel() != "..")
376 SetRecordingsPlayCount(pItem, count);
381 pItem->GetPVRRecordingInfoTag()->SetPlayCount(count);
383 // Clear resume bookmark
386 database.ClearBookMarksOfFile(pItem->GetPath(), CBookmark::RESUME);
387 pItem->GetPVRRecordingInfoTag()->SetLastPlayedPosition(0);
390 database.SetPlayCount(*pItem, count);
399 bool CPVRRecordings::GetDirectory(const CStdString& strPath, CFileItemList &items)
401 bool bSuccess(false);
404 CSingleLock lock(m_critSection);
407 CStdString strFileName = url.GetFileName();
408 URIUtils::RemoveSlashAtEnd(strFileName);
410 if (StringUtils::StartsWith(strFileName, "recordings"))
412 strFileName.erase(0, 10);
413 GetSubDirectories(strFileName, &items);
421 void CPVRRecordings::SetPlayCount(const CFileItem &item, int iPlayCount)
423 if (!item.HasPVRRecordingInfoTag())
426 const CPVRRecording *recording = item.GetPVRRecordingInfoTag();
427 CSingleLock lock(m_critSection);
428 for (unsigned int iRecordingPtr = 0; iRecordingPtr < m_recordings.size(); iRecordingPtr++)
430 CPVRRecording *current = m_recordings.at(iRecordingPtr);
431 if (current->m_iClientId == recording->m_iClientId && current->m_strRecordingId.Equals(recording->m_strRecordingId))
433 current->SetPlayCount(iPlayCount);
439 void CPVRRecordings::GetAll(CFileItemList &items)
441 CSingleLock lock(m_critSection);
442 for (unsigned int iRecordingPtr = 0; iRecordingPtr < m_recordings.size(); iRecordingPtr++)
444 CPVRRecording *current = m_recordings.at(iRecordingPtr);
445 current->UpdateMetadata();
447 CFileItemPtr pFileItem(new CFileItem(*current));
448 pFileItem->SetLabel2(current->RecordingTimeAsLocalTime().GetAsLocalizedDateTime(true, false));
449 pFileItem->m_dateTime = current->RecordingTimeAsLocalTime();
450 pFileItem->SetPath(current->m_strFileNameAndPath);
451 pFileItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, pFileItem->GetPVRRecordingInfoTag()->m_playCount > 0);
453 items.Add(pFileItem);
457 CFileItemPtr CPVRRecordings::GetById(unsigned int iId) const
460 CSingleLock lock(m_critSection);
461 for (std::vector<CPVRRecording *>::const_iterator it = m_recordings.begin(); !item && it != m_recordings.end(); ++it)
463 if (iId == (*it)->m_iRecordingId)
464 item = CFileItemPtr(new CFileItem(**it));
470 CFileItemPtr CPVRRecordings::GetByPath(const CStdString &path)
473 CStdString fileName = url.GetFileName();
474 URIUtils::RemoveSlashAtEnd(fileName);
476 CSingleLock lock(m_critSection);
478 if (StringUtils::StartsWith(fileName, "recordings/"))
480 for (unsigned int iRecordingPtr = 0; iRecordingPtr < m_recordings.size(); iRecordingPtr++)
482 if(path.Equals(m_recordings.at(iRecordingPtr)->m_strFileNameAndPath))
484 CFileItemPtr fileItem(new CFileItem(*m_recordings.at(iRecordingPtr)));
490 CFileItemPtr fileItem(new CFileItem);
494 void CPVRRecordings::Clear()
496 CSingleLock lock(m_critSection);
498 for (unsigned int iRecordingPtr = 0; iRecordingPtr < m_recordings.size(); iRecordingPtr++)
499 delete m_recordings.at(iRecordingPtr);
500 m_recordings.erase(m_recordings.begin(), m_recordings.end());
503 void CPVRRecordings::UpdateEntry(const CPVRRecording &tag)
506 CSingleLock lock(m_critSection);
508 for (unsigned int iRecordingPtr = 0; iRecordingPtr < m_recordings.size(); iRecordingPtr++)
510 CPVRRecording *currentTag = m_recordings.at(iRecordingPtr);
511 if (currentTag->m_iClientId == tag.m_iClientId &&
512 currentTag->m_strRecordingId.Equals(tag.m_strRecordingId))
514 currentTag->Update(tag);
522 CPVRRecording *newTag = new CPVRRecording();
524 newTag->m_iRecordingId = ++m_iLastId;
525 m_recordings.push_back(newTag);