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