1 /*
2  *  Copyright (C) 2012-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "PVRRecordings.h"
10 
11 #include "ServiceBroker.h"
12 #include "pvr/PVRManager.h"
13 #include "pvr/addons/PVRClients.h"
14 #include "pvr/epg/EpgInfoTag.h"
15 #include "pvr/recordings/PVRRecording.h"
16 #include "pvr/recordings/PVRRecordingsPath.h"
17 #include "threads/SingleLock.h"
18 #include "utils/URIUtils.h"
19 #include "utils/log.h"
20 #include "video/VideoDatabase.h"
21 
22 #include <memory>
23 #include <utility>
24 #include <vector>
25 
26 using namespace PVR;
27 
28 CPVRRecordings::CPVRRecordings() = default;
29 
~CPVRRecordings()30 CPVRRecordings::~CPVRRecordings()
31 {
32   if (m_database && m_database->IsOpen())
33     m_database->Close();
34 }
35 
UpdateFromClients()36 void CPVRRecordings::UpdateFromClients()
37 {
38   CSingleLock lock(m_critSection);
39 
40   for (const auto& recording : m_recordings)
41     recording.second->SetDirty(true);
42 
43   std::vector<int> failedClients;
44   CServiceBroker::GetPVRManager().Clients()->GetRecordings(this, false, failedClients);
45   CServiceBroker::GetPVRManager().Clients()->GetRecordings(this, true, failedClients);
46 
47   // remove recordings that were deleted at the backend
48   for (auto it = m_recordings.begin(); it != m_recordings.end();)
49   {
50     if ((*it).second->IsDirty() && std::find(failedClients.begin(), failedClients.end(),
51                                              (*it).second->ClientID()) == failedClients.end())
52       it = m_recordings.erase(it);
53     else
54       ++it;
55   }
56 }
57 
Load()58 int CPVRRecordings::Load()
59 {
60   Unload();
61   Update();
62   return m_recordings.size();
63 }
64 
Unload()65 void CPVRRecordings::Unload()
66 {
67   CSingleLock lock(m_critSection);
68   m_bDeletedTVRecordings = false;
69   m_bDeletedRadioRecordings = false;
70   m_iTVRecordings = 0;
71   m_iRadioRecordings = 0;
72   m_recordings.clear();
73 }
74 
Update()75 void CPVRRecordings::Update()
76 {
77   CSingleLock lock(m_critSection);
78   if (m_bIsUpdating)
79     return;
80   m_bIsUpdating = true;
81   lock.Leave();
82 
83   CLog::LogFC(LOGDEBUG, LOGPVR, "Updating recordings");
84   UpdateFromClients();
85 
86   lock.Enter();
87   m_bIsUpdating = false;
88   lock.Leave();
89 
90   CServiceBroker::GetPVRManager().PublishEvent(PVREvent::RecordingsInvalidated);
91 }
92 
UpdateInProgressSize()93 void CPVRRecordings::UpdateInProgressSize()
94 {
95   CSingleLock lock(m_critSection);
96   if (m_bIsUpdating)
97     return;
98   m_bIsUpdating = true;
99 
100   CLog::LogFC(LOGDEBUG, LOGPVR, "Updating recordings size");
101   bool bHaveUpdatedInProgessRecording = false;
102   for (auto& recording : m_recordings)
103   {
104     if (recording.second->IsInProgress())
105     {
106       if (recording.second->UpdateRecordingSize())
107         bHaveUpdatedInProgessRecording = true;
108     }
109   }
110 
111   m_bIsUpdating = false;
112 
113   if (bHaveUpdatedInProgessRecording)
114     CServiceBroker::GetPVRManager().PublishEvent(PVREvent::RecordingsInvalidated);
115 }
116 
GetNumTVRecordings() const117 int CPVRRecordings::GetNumTVRecordings() const
118 {
119   CSingleLock lock(m_critSection);
120   return m_iTVRecordings;
121 }
122 
HasDeletedTVRecordings() const123 bool CPVRRecordings::HasDeletedTVRecordings() const
124 {
125   CSingleLock lock(m_critSection);
126   return m_bDeletedTVRecordings;
127 }
128 
GetNumRadioRecordings() const129 int CPVRRecordings::GetNumRadioRecordings() const
130 {
131   CSingleLock lock(m_critSection);
132   return m_iRadioRecordings;
133 }
134 
HasDeletedRadioRecordings() const135 bool CPVRRecordings::HasDeletedRadioRecordings() const
136 {
137   CSingleLock lock(m_critSection);
138   return m_bDeletedRadioRecordings;
139 }
140 
GetAll() const141 std::vector<std::shared_ptr<CPVRRecording>> CPVRRecordings::GetAll() const
142 {
143   std::vector<std::shared_ptr<CPVRRecording>> recordings;
144 
145   CSingleLock lock(m_critSection);
146   for (const auto& recordingEntry : m_recordings)
147   {
148     recordings.emplace_back(recordingEntry.second);
149   }
150 
151   return recordings;
152 }
153 
GetById(unsigned int iId) const154 std::shared_ptr<CPVRRecording> CPVRRecordings::GetById(unsigned int iId) const
155 {
156   CSingleLock lock(m_critSection);
157   for (const auto& recording : m_recordings)
158   {
159     if (iId == recording.second->m_iRecordingId)
160       return recording.second;
161   }
162 
163   return {};
164 }
165 
GetByPath(const std::string & path) const166 std::shared_ptr<CPVRRecording> CPVRRecordings::GetByPath(const std::string& path) const
167 {
168   CSingleLock lock(m_critSection);
169 
170   CPVRRecordingsPath recPath(path);
171   if (recPath.IsValid())
172   {
173     bool bDeleted = recPath.IsDeleted();
174     bool bRadio = recPath.IsRadio();
175 
176     for (const auto& recording : m_recordings)
177     {
178       std::shared_ptr<CPVRRecording> current = recording.second;
179       // Omit recordings not matching criteria
180       if (!URIUtils::PathEquals(path, current->m_strFileNameAndPath) ||
181           bDeleted != current->IsDeleted() || bRadio != current->IsRadio())
182         continue;
183 
184       return current;
185     }
186   }
187 
188   return {};
189 }
190 
GetById(int iClientId,const std::string & strRecordingId) const191 std::shared_ptr<CPVRRecording> CPVRRecordings::GetById(int iClientId, const std::string& strRecordingId) const
192 {
193   std::shared_ptr<CPVRRecording> retVal;
194   CSingleLock lock(m_critSection);
195   const auto it = m_recordings.find(CPVRRecordingUid(iClientId, strRecordingId));
196   if (it != m_recordings.end())
197     retVal = it->second;
198 
199   return retVal;
200 }
201 
UpdateFromClient(const std::shared_ptr<CPVRRecording> & tag,const CPVRClient & client)202 void CPVRRecordings::UpdateFromClient(const std::shared_ptr<CPVRRecording>& tag,
203                                       const CPVRClient& client)
204 {
205   CSingleLock lock(m_critSection);
206 
207   if (tag->IsDeleted())
208   {
209     if (tag->IsRadio())
210       m_bDeletedRadioRecordings = true;
211     else
212       m_bDeletedTVRecordings = true;
213   }
214 
215   std::shared_ptr<CPVRRecording> existingTag = GetById(tag->m_iClientId, tag->m_strRecordingId);
216   if (existingTag)
217   {
218     existingTag->Update(*tag, client);
219     existingTag->SetDirty(false);
220   }
221   else
222   {
223     tag->UpdateMetadata(GetVideoDatabase(), client);
224     tag->m_iRecordingId = ++m_iLastId;
225     m_recordings.insert({CPVRRecordingUid(tag->m_iClientId, tag->m_strRecordingId), tag});
226     if (tag->IsRadio())
227       ++m_iRadioRecordings;
228     else
229       ++m_iTVRecordings;
230   }
231 }
232 
GetRecordingForEpgTag(const std::shared_ptr<CPVREpgInfoTag> & epgTag) const233 std::shared_ptr<CPVRRecording> CPVRRecordings::GetRecordingForEpgTag(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const
234 {
235   if (!epgTag)
236     return {};
237 
238   CSingleLock lock(m_critSection);
239 
240   for (const auto& recording : m_recordings)
241   {
242     if (recording.second->IsDeleted())
243       continue;
244 
245     if (recording.second->ClientID() != epgTag->ClientID())
246       continue;
247 
248     if (recording.second->ChannelUid() != epgTag->UniqueChannelID())
249       continue;
250 
251     unsigned int iEpgEvent = recording.second->BroadcastUid();
252     if (iEpgEvent != EPG_TAG_INVALID_UID)
253     {
254       if (iEpgEvent == epgTag->UniqueBroadcastID())
255         return recording.second;
256     }
257     else
258     {
259       if (recording.second->RecordingTimeAsUTC() <= epgTag->StartAsUTC() &&
260           recording.second->EndTimeAsUTC() >= epgTag->EndAsUTC())
261         return recording.second;
262     }
263   }
264 
265   return std::shared_ptr<CPVRRecording>();
266 }
267 
SetRecordingsPlayCount(const std::shared_ptr<CPVRRecording> & recording,int count)268 bool CPVRRecordings::SetRecordingsPlayCount(const std::shared_ptr<CPVRRecording>& recording, int count)
269 {
270   return ChangeRecordingsPlayCount(recording, count);
271 }
272 
IncrementRecordingsPlayCount(const std::shared_ptr<CPVRRecording> & recording)273 bool CPVRRecordings::IncrementRecordingsPlayCount(const std::shared_ptr<CPVRRecording>& recording)
274 {
275   return ChangeRecordingsPlayCount(recording, INCREMENT_PLAY_COUNT);
276 }
277 
ChangeRecordingsPlayCount(const std::shared_ptr<CPVRRecording> & recording,int count)278 bool CPVRRecordings::ChangeRecordingsPlayCount(const std::shared_ptr<CPVRRecording>& recording, int count)
279 {
280   if (recording)
281   {
282     CSingleLock lock(m_critSection);
283 
284     CVideoDatabase& db = GetVideoDatabase();
285     if (db.IsOpen())
286     {
287       if (count == INCREMENT_PLAY_COUNT)
288         recording->IncrementPlayCount();
289       else
290         recording->SetPlayCount(count);
291 
292       // Clear resume bookmark
293       if (recording->GetPlayCount() > 0)
294       {
295         db.ClearBookMarksOfFile(recording->m_strFileNameAndPath, CBookmark::RESUME);
296         recording->SetResumePoint(CBookmark());
297       }
298 
299       CServiceBroker::GetPVRManager().PublishEvent(PVREvent::RecordingsInvalidated);
300       return true;
301     }
302   }
303 
304   return false;
305 }
306 
MarkWatched(const std::shared_ptr<CPVRRecording> & recording,bool bWatched)307 bool CPVRRecordings::MarkWatched(const std::shared_ptr<CPVRRecording>& recording, bool bWatched)
308 {
309   if (bWatched)
310     return IncrementRecordingsPlayCount(recording);
311   else
312     return SetRecordingsPlayCount(recording, 0);
313 }
314 
ResetResumePoint(const std::shared_ptr<CPVRRecording> & recording)315 bool CPVRRecordings::ResetResumePoint(const std::shared_ptr<CPVRRecording>& recording)
316 {
317   bool bResult = false;
318 
319   if (recording)
320   {
321     CSingleLock lock(m_critSection);
322 
323     CVideoDatabase& db = GetVideoDatabase();
324     if (db.IsOpen())
325     {
326       bResult = true;
327 
328       db.ClearBookMarksOfFile(recording->m_strFileNameAndPath, CBookmark::RESUME);
329       recording->SetResumePoint(CBookmark());
330 
331       CServiceBroker::GetPVRManager().PublishEvent(PVREvent::RecordingsInvalidated);
332     }
333   }
334   return bResult;
335 }
336 
GetVideoDatabase()337 CVideoDatabase& CPVRRecordings::GetVideoDatabase()
338 {
339   if (!m_database)
340   {
341     m_database.reset(new CVideoDatabase());
342     m_database->Open();
343 
344     if (!m_database->IsOpen())
345       CLog::LogF(LOGERROR, "Failed to open the video database");
346   }
347 
348   return *m_database;
349 }
350