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 "PVRRecording.h"
10 
11 #include "ServiceBroker.h"
12 #include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h"
13 #include "guilib/LocalizeStrings.h"
14 #include "pvr/PVRManager.h"
15 #include "pvr/addons/PVRClient.h"
16 #include "pvr/channels/PVRChannel.h"
17 #include "pvr/channels/PVRChannelGroupsContainer.h"
18 #include "pvr/epg/Epg.h"
19 #include "pvr/recordings/PVRRecordingsPath.h"
20 #include "pvr/timers/PVRTimerInfoTag.h"
21 #include "pvr/timers/PVRTimers.h"
22 #include "settings/AdvancedSettings.h"
23 #include "settings/SettingsComponent.h"
24 #include "utils/StringUtils.h"
25 #include "utils/Variant.h"
26 #include "utils/log.h"
27 #include "video/VideoDatabase.h"
28 
29 #include <memory>
30 #include <string>
31 #include <vector>
32 
33 using namespace PVR;
34 
CPVRRecordingUid(int iClientId,const std::string & strRecordingId)35 CPVRRecordingUid::CPVRRecordingUid(int iClientId, const std::string& strRecordingId) :
36   m_iClientId(iClientId),
37   m_strRecordingId(strRecordingId)
38 {
39 }
40 
operator >(const CPVRRecordingUid & right) const41 bool CPVRRecordingUid::operator >(const CPVRRecordingUid& right) const
42 {
43   return (m_iClientId == right.m_iClientId) ?
44             m_strRecordingId > right.m_strRecordingId :
45             m_iClientId > right.m_iClientId;
46 }
47 
operator <(const CPVRRecordingUid & right) const48 bool CPVRRecordingUid::operator <(const CPVRRecordingUid& right) const
49 {
50   return (m_iClientId == right.m_iClientId) ?
51             m_strRecordingId < right.m_strRecordingId :
52             m_iClientId < right.m_iClientId;
53 }
54 
operator ==(const CPVRRecordingUid & right) const55 bool CPVRRecordingUid::operator ==(const CPVRRecordingUid& right) const
56 {
57   return m_iClientId == right.m_iClientId && m_strRecordingId == right.m_strRecordingId;
58 }
59 
operator !=(const CPVRRecordingUid & right) const60 bool CPVRRecordingUid::operator !=(const CPVRRecordingUid& right) const
61 {
62   return m_iClientId != right.m_iClientId || m_strRecordingId != right.m_strRecordingId;
63 }
64 
65 
CPVRRecording()66 CPVRRecording::CPVRRecording()
67 {
68   Reset();
69 }
70 
CPVRRecording(const PVR_RECORDING & recording,unsigned int iClientId)71 CPVRRecording::CPVRRecording(const PVR_RECORDING& recording, unsigned int iClientId)
72 {
73   Reset();
74 
75   m_strRecordingId = recording.strRecordingId;
76   m_strTitle = recording.strTitle;
77   m_strShowTitle = recording.strEpisodeName;
78   m_iSeason = recording.iSeriesNumber;
79   m_iEpisode = recording.iEpisodeNumber;
80   if (recording.iYear > 0)
81     SetYear(recording.iYear);
82   m_iClientId = iClientId;
83   m_recordingTime = recording.recordingTime + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iPVRTimeCorrection;
84   m_iPriority = recording.iPriority;
85   m_iLifetime = recording.iLifetime;
86   // Deleted recording is placed at the root of the deleted view
87   m_strDirectory = recording.bIsDeleted ? "" : recording.strDirectory;
88   m_strPlot = recording.strPlot;
89   m_strPlotOutline = recording.strPlotOutline;
90   m_strChannelName = recording.strChannelName;
91   m_strIconPath = recording.strIconPath;
92   m_strThumbnailPath = recording.strThumbnailPath;
93   m_strFanartPath = recording.strFanartPath;
94   m_bIsDeleted = recording.bIsDeleted;
95   m_iEpgEventId = recording.iEpgEventId;
96   m_iChannelUid = recording.iChannelUid;
97   if (strlen(recording.strFirstAired) > 0)
98     m_firstAired.SetFromW3CDateTime(recording.strFirstAired);
99   m_iFlags = recording.iFlags;
100   if (recording.sizeInBytes >= 0)
101     m_sizeInBytes = recording.sizeInBytes;
102 
103   SetGenre(recording.iGenreType, recording.iGenreSubType, recording.strGenreDescription);
104   CVideoInfoTag::SetPlayCount(recording.iPlayCount);
105   CVideoInfoTag::SetResumePoint(recording.iLastPlayedPosition, recording.iDuration, "");
106   SetDuration(recording.iDuration);
107 
108   //  As the channel a recording was done on (probably long time ago) might no longer be
109   //  available today prefer addon-supplied channel type (tv/radio) over channel attribute.
110   if (recording.channelType != PVR_RECORDING_CHANNEL_TYPE_UNKNOWN)
111   {
112     m_bRadio = recording.channelType == PVR_RECORDING_CHANNEL_TYPE_RADIO;
113   }
114   else
115   {
116     const std::shared_ptr<CPVRChannel> channel(Channel());
117     if (channel)
118     {
119       m_bRadio = channel->IsRadio();
120     }
121     else
122     {
123       const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId);
124       bool bSupportsRadio = client && client->GetClientCapabilities().SupportsRadio();
125       if (bSupportsRadio && client && client->GetClientCapabilities().SupportsTV())
126       {
127         CLog::Log(LOGWARNING, "Unable to determine channel type. Defaulting to TV.");
128         m_bRadio = false; // Assume TV.
129       }
130       else
131       {
132         m_bRadio = bSupportsRadio;
133       }
134     }
135   }
136 
137   UpdatePath();
138 }
139 
operator ==(const CPVRRecording & right) const140 bool CPVRRecording::operator ==(const CPVRRecording& right) const
141 {
142   CSingleLock lock(m_critSection);
143   return (this == &right) ||
144       (m_strRecordingId == right.m_strRecordingId &&
145        m_iClientId == right.m_iClientId &&
146        m_strChannelName == right.m_strChannelName &&
147        m_recordingTime == right.m_recordingTime &&
148        GetDuration() == right.GetDuration() &&
149        m_strPlotOutline == right.m_strPlotOutline &&
150        m_strPlot == right.m_strPlot &&
151        m_iPriority == right.m_iPriority &&
152        m_iLifetime == right.m_iLifetime &&
153        m_strDirectory == right.m_strDirectory &&
154        m_strFileNameAndPath == right.m_strFileNameAndPath &&
155        m_strTitle == right.m_strTitle &&
156        m_strShowTitle == right.m_strShowTitle &&
157        m_iSeason == right.m_iSeason &&
158        m_iEpisode == right.m_iEpisode &&
159        GetPremiered() == right.GetPremiered() &&
160        m_strIconPath == right.m_strIconPath &&
161        m_strThumbnailPath == right.m_strThumbnailPath &&
162        m_strFanartPath == right.m_strFanartPath &&
163        m_iRecordingId == right.m_iRecordingId &&
164        m_bIsDeleted == right.m_bIsDeleted &&
165        m_iEpgEventId == right.m_iEpgEventId &&
166        m_iChannelUid == right.m_iChannelUid &&
167        m_bRadio == right.m_bRadio &&
168        m_genre == right.m_genre &&
169        m_iGenreType == right.m_iGenreType &&
170        m_iGenreSubType == right.m_iGenreSubType &&
171        m_firstAired == right.m_firstAired &&
172        m_iFlags == right.m_iFlags &&
173        m_sizeInBytes == right.m_sizeInBytes);
174 }
175 
operator !=(const CPVRRecording & right) const176 bool CPVRRecording::operator !=(const CPVRRecording& right) const
177 {
178   return !(*this == right);
179 }
180 
Serialize(CVariant & value) const181 void CPVRRecording::Serialize(CVariant& value) const
182 {
183   CVideoInfoTag::Serialize(value);
184 
185   value["channel"] = m_strChannelName;
186   value["lifetime"] = m_iLifetime;
187   value["directory"] = m_strDirectory;
188   value["icon"] = m_strIconPath;
189   value["starttime"] = m_recordingTime.IsValid() ? m_recordingTime.GetAsDBDateTime() : "";
190   value["endtime"] = m_recordingTime.IsValid() ? EndTimeAsUTC().GetAsDBDateTime() : "";
191   value["recordingid"] = m_iRecordingId;
192   value["isdeleted"] = m_bIsDeleted;
193   value["epgeventid"] = m_iEpgEventId;
194   value["channeluid"] = m_iChannelUid;
195   value["radio"] = m_bRadio;
196   value["genre"] = m_genre;
197 
198   if (!value.isMember("art"))
199     value["art"] = CVariant(CVariant::VariantTypeObject);
200   if (!m_strThumbnailPath.empty())
201     value["art"]["thumb"] = m_strThumbnailPath;
202   if (!m_strFanartPath.empty())
203     value["art"]["fanart"] = m_strFanartPath;
204 
205   value["clientid"] = m_iClientId;
206 }
207 
ToSortable(SortItem & sortable,Field field) const208 void CPVRRecording::ToSortable(SortItem& sortable, Field field) const
209 {
210   CSingleLock lock(m_critSection);
211   if (field == FieldSize)
212     sortable[FieldSize] = m_sizeInBytes;
213   else
214     CVideoInfoTag::ToSortable(sortable, field);
215 }
216 
Reset()217 void CPVRRecording::Reset()
218 {
219   m_strRecordingId     .clear();
220   m_iClientId = -1;
221   m_strChannelName     .clear();
222   m_strDirectory       .clear();
223   m_iPriority = -1;
224   m_iLifetime = -1;
225   m_strFileNameAndPath .clear();
226   m_strIconPath        .clear();
227   m_strThumbnailPath   .clear();
228   m_strFanartPath      .clear();
229   m_bGotMetaData = false;
230   m_iRecordingId = 0;
231   m_bIsDeleted = false;
232   m_iEpgEventId = EPG_TAG_INVALID_UID;
233   m_iSeason = -1;
234   m_iEpisode = -1;
235   m_iChannelUid = PVR_CHANNEL_INVALID_UID;
236   m_bRadio = false;
237   m_iFlags = PVR_RECORDING_FLAG_UNDEFINED;
238   {
239     CSingleLock lock(m_critSection);
240     m_sizeInBytes = 0;
241   }
242 
243   m_recordingTime.Reset();
244   CVideoInfoTag::Reset();
245 }
246 
Delete()247 bool CPVRRecording::Delete()
248 {
249   std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId);
250   return client && (client->DeleteRecording(*this) == PVR_ERROR_NO_ERROR);
251 }
252 
Undelete()253 bool CPVRRecording::Undelete()
254 {
255   const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId);
256   return client && (client->UndeleteRecording(*this) == PVR_ERROR_NO_ERROR);
257 }
258 
Rename(const std::string & strNewName)259 bool CPVRRecording::Rename(const std::string& strNewName)
260 {
261   m_strTitle = strNewName;
262   const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId);
263   return client && (client->RenameRecording(*this) == PVR_ERROR_NO_ERROR);
264 }
265 
SetPlayCount(int count)266 bool CPVRRecording::SetPlayCount(int count)
267 {
268   const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId);
269   if (client && client->GetClientCapabilities().SupportsRecordingsPlayCount())
270   {
271     if (client->SetRecordingPlayCount(*this, count) != PVR_ERROR_NO_ERROR)
272       return false;
273   }
274 
275   return CVideoInfoTag::SetPlayCount(count);
276 }
277 
IncrementPlayCount()278 bool CPVRRecording::IncrementPlayCount()
279 {
280   const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId);
281   if (client && client->GetClientCapabilities().SupportsRecordingsPlayCount())
282   {
283     if (client->SetRecordingPlayCount(*this, CVideoInfoTag::GetPlayCount() + 1) != PVR_ERROR_NO_ERROR)
284       return false;
285   }
286 
287   return CVideoInfoTag::IncrementPlayCount();
288 }
289 
SetResumePoint(const CBookmark & resumePoint)290 bool CPVRRecording::SetResumePoint(const CBookmark& resumePoint)
291 {
292   const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId);
293   if (client && client->GetClientCapabilities().SupportsRecordingsLastPlayedPosition())
294   {
295     if (client->SetRecordingLastPlayedPosition(*this, lrint(resumePoint.timeInSeconds)) != PVR_ERROR_NO_ERROR)
296       return false;
297   }
298 
299   return CVideoInfoTag::SetResumePoint(resumePoint);
300 }
301 
SetResumePoint(double timeInSeconds,double totalTimeInSeconds,const std::string & playerState)302 bool CPVRRecording::SetResumePoint(double timeInSeconds, double totalTimeInSeconds, const std::string& playerState /* = "" */)
303 {
304   const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId);
305   if (client && client->GetClientCapabilities().SupportsRecordingsLastPlayedPosition())
306   {
307     if (client->SetRecordingLastPlayedPosition(*this, lrint(timeInSeconds)) != PVR_ERROR_NO_ERROR)
308       return false;
309   }
310 
311   return CVideoInfoTag::SetResumePoint(timeInSeconds, totalTimeInSeconds, playerState);
312 }
313 
GetResumePoint() const314 CBookmark CPVRRecording::GetResumePoint() const
315 {
316   const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId);
317   if (client && client->GetClientCapabilities().SupportsRecordingsLastPlayedPosition() &&
318       m_resumePointRefetchTimeout.IsTimePast())
319   {
320     // @todo: root cause should be fixed. details: https://github.com/xbmc/xbmc/pull/14961
321     m_resumePointRefetchTimeout.Set(10000); // update resume point from backend at most every 10 secs
322 
323     int pos = -1;
324     client->GetRecordingLastPlayedPosition(*this, pos);
325 
326     if (pos >= 0)
327     {
328       CBookmark resumePoint(CVideoInfoTag::GetResumePoint());
329       resumePoint.timeInSeconds = pos;
330       CPVRRecording* pThis = const_cast<CPVRRecording*>(this);
331       pThis->CVideoInfoTag::SetResumePoint(resumePoint);
332     }
333   }
334   return CVideoInfoTag::GetResumePoint();
335 }
336 
UpdateRecordingSize()337 bool CPVRRecording::UpdateRecordingSize()
338 {
339   const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId);
340   if (client && client->GetClientCapabilities().SupportsRecordingsSize() &&
341       m_recordingSizeRefetchTimeout.IsTimePast())
342   {
343     // @todo: root cause should be fixed. details: https://github.com/xbmc/xbmc/pull/14961
344     m_recordingSizeRefetchTimeout.Set(10000); // update size from backend at most every 10 secs
345 
346     int64_t sizeInBytes = -1;
347     client->GetRecordingSize(*this, sizeInBytes);
348 
349     CSingleLock lock(m_critSection);
350     if (sizeInBytes >= 0 && sizeInBytes != m_sizeInBytes)
351     {
352       CSingleLock lock(m_critSection);
353       m_sizeInBytes = sizeInBytes;
354       return true;
355     }
356   }
357 
358   return false;
359 }
360 
UpdateMetadata(CVideoDatabase & db,const CPVRClient & client)361 void CPVRRecording::UpdateMetadata(CVideoDatabase& db, const CPVRClient& client)
362 {
363   if (m_bGotMetaData || !db.IsOpen())
364     return;
365 
366   if (!client.GetClientCapabilities().SupportsRecordingsPlayCount())
367     CVideoInfoTag::SetPlayCount(db.GetPlayCount(m_strFileNameAndPath));
368 
369   if (!client.GetClientCapabilities().SupportsRecordingsLastPlayedPosition())
370   {
371     CBookmark resumePoint;
372     if (db.GetResumeBookMark(m_strFileNameAndPath, resumePoint))
373       CVideoInfoTag::SetResumePoint(resumePoint);
374   }
375 
376   m_bGotMetaData = true;
377 }
378 
GetEdl() const379 std::vector<PVR_EDL_ENTRY> CPVRRecording::GetEdl() const
380 {
381   std::vector<PVR_EDL_ENTRY> edls;
382 
383   const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId);
384   if (client && client->GetClientCapabilities().SupportsRecordingsEdl())
385     client->GetRecordingEdl(*this, edls);
386 
387   return edls;
388 }
389 
Update(const CPVRRecording & tag,const CPVRClient & client)390 void CPVRRecording::Update(const CPVRRecording& tag, const CPVRClient& client)
391 {
392   m_strRecordingId = tag.m_strRecordingId;
393   m_iClientId = tag.m_iClientId;
394   m_strTitle = tag.m_strTitle;
395   m_strShowTitle = tag.m_strShowTitle;
396   m_iSeason = tag.m_iSeason;
397   m_iEpisode = tag.m_iEpisode;
398   SetPremiered(tag.GetPremiered());
399   m_recordingTime = tag.m_recordingTime;
400   m_iPriority = tag.m_iPriority;
401   m_iLifetime = tag.m_iLifetime;
402   m_strDirectory = tag.m_strDirectory;
403   m_strPlot = tag.m_strPlot;
404   m_strPlotOutline = tag.m_strPlotOutline;
405   m_strChannelName = tag.m_strChannelName;
406   m_genre = tag.m_genre;
407   m_strIconPath = tag.m_strIconPath;
408   m_strThumbnailPath = tag.m_strThumbnailPath;
409   m_strFanartPath = tag.m_strFanartPath;
410   m_bIsDeleted = tag.m_bIsDeleted;
411   m_iEpgEventId = tag.m_iEpgEventId;
412   m_iChannelUid = tag.m_iChannelUid;
413   m_bRadio = tag.m_bRadio;
414   m_firstAired = tag.m_firstAired;
415   m_iFlags = tag.m_iFlags;
416   {
417     CSingleLock lock(m_critSection);
418     m_sizeInBytes = tag.m_sizeInBytes;
419   }
420 
421   if (client.GetClientCapabilities().SupportsRecordingsPlayCount())
422     CVideoInfoTag::SetPlayCount(tag.GetLocalPlayCount());
423 
424   if (client.GetClientCapabilities().SupportsRecordingsLastPlayedPosition())
425     CVideoInfoTag::SetResumePoint(tag.GetLocalResumePoint());
426 
427   SetDuration(tag.GetDuration());
428 
429   if (m_iGenreType == EPG_GENRE_USE_STRING || m_iGenreSubType == EPG_GENRE_USE_STRING)
430   {
431     /* No type/subtype. Use the provided description */
432     m_genre = tag.m_genre;
433   }
434   else
435   {
436     /* Determine genre description by type/subtype */
437     m_genre = StringUtils::Split(CPVREpg::ConvertGenreIdToString(tag.m_iGenreType, tag.m_iGenreSubType), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
438   }
439 
440   //Old Method of identifying TV show title and subtitle using m_strDirectory and strPlotOutline (deprecated)
441   std::string strShow = StringUtils::Format("%s - ", g_localizeStrings.Get(20364).c_str());
442   if (StringUtils::StartsWithNoCase(m_strPlotOutline, strShow))
443   {
444     CLog::Log(LOGWARNING, "PVR addon provides episode name in strPlotOutline which is deprecated");
445     std::string strEpisode = m_strPlotOutline;
446     std::string strTitle = m_strDirectory;
447 
448     size_t pos = strTitle.rfind('/');
449     strTitle.erase(0, pos + 1);
450     strEpisode.erase(0, strShow.size());
451     m_strTitle = strTitle;
452     pos = strEpisode.find('-');
453     strEpisode.erase(0, pos + 2);
454     m_strShowTitle = strEpisode;
455   }
456 
457   UpdatePath();
458 }
459 
UpdatePath()460 void CPVRRecording::UpdatePath()
461 {
462   m_strFileNameAndPath = CPVRRecordingsPath(
463     m_bIsDeleted, m_bRadio, m_strDirectory, m_strTitle, m_iSeason, m_iEpisode, GetYear(), m_strShowTitle, m_strChannelName, m_recordingTime, m_strRecordingId);
464 }
465 
RecordingTimeAsLocalTime() const466 const CDateTime& CPVRRecording::RecordingTimeAsLocalTime() const
467 {
468   static CDateTime tmp;
469   tmp.SetFromUTCDateTime(m_recordingTime);
470 
471   return tmp;
472 }
473 
EndTimeAsUTC() const474 CDateTime CPVRRecording::EndTimeAsUTC() const
475 {
476   unsigned int duration = GetDuration();
477   return m_recordingTime + CDateTimeSpan(0, 0, duration / 60, duration % 60);
478 }
479 
EndTimeAsLocalTime() const480 CDateTime CPVRRecording::EndTimeAsLocalTime() const
481 {
482   CDateTime ret;
483   ret.SetFromUTCDateTime(EndTimeAsUTC());
484   return ret;
485 }
486 
WillBeExpiredWithNewLifetime(int iLifetime) const487 bool CPVRRecording::WillBeExpiredWithNewLifetime(int iLifetime) const
488 {
489   if (iLifetime > 0)
490     return (EndTimeAsUTC() + CDateTimeSpan(iLifetime, 0, 0, 0)) <= CDateTime::GetUTCDateTime();
491 
492   return false;
493 }
494 
ExpirationTimeAsLocalTime() const495 CDateTime CPVRRecording::ExpirationTimeAsLocalTime() const
496 {
497   CDateTime ret;
498   if (m_iLifetime > 0)
499     ret = EndTimeAsLocalTime() + CDateTimeSpan(m_iLifetime, 0, 0, 0);
500 
501   return ret;
502 }
503 
GetTitleFromURL(const std::string & url)504 std::string CPVRRecording::GetTitleFromURL(const std::string& url)
505 {
506   return CPVRRecordingsPath(url).GetTitle();
507 }
508 
Channel() const509 std::shared_ptr<CPVRChannel> CPVRRecording::Channel() const
510 {
511   if (m_iChannelUid != PVR_CHANNEL_INVALID_UID)
512     return CServiceBroker::GetPVRManager().ChannelGroups()->GetByUniqueID(m_iChannelUid, m_iClientId);
513 
514   return std::shared_ptr<CPVRChannel>();
515 }
516 
ChannelUid() const517 int CPVRRecording::ChannelUid() const
518 {
519   return m_iChannelUid;
520 }
521 
ClientID() const522 int CPVRRecording::ClientID() const
523 {
524   return m_iClientId;
525 }
526 
GetRecordingTimer() const527 std::shared_ptr<CPVRTimerInfoTag> CPVRRecording::GetRecordingTimer() const
528 {
529   const std::vector<std::shared_ptr<CPVRTimerInfoTag>> recordingTimers = CServiceBroker::GetPVRManager().Timers()->GetActiveRecordings();
530 
531   for (const auto& timer : recordingTimers)
532   {
533     if (timer->m_iClientId == ClientID() &&
534         timer->m_iClientChannelUid == ChannelUid())
535     {
536       // first, match epg event uids, if available
537       if (timer->UniqueBroadcastID() == BroadcastUid() &&
538           timer->UniqueBroadcastID() != EPG_TAG_INVALID_UID)
539         return timer;
540 
541       // alternatively, match start and end times
542       const CDateTime timerStart = timer->StartAsUTC() - CDateTimeSpan(0, 0, timer->m_iMarginStart, 0);
543       const CDateTime timerEnd = timer->EndAsUTC() + CDateTimeSpan(0, 0, timer->m_iMarginEnd, 0);
544       if (timerStart <= RecordingTimeAsUTC() &&
545           timerEnd >= EndTimeAsUTC())
546         return timer;
547     }
548   }
549   return {};
550 }
551 
IsInProgress() const552 bool CPVRRecording::IsInProgress() const
553 {
554   // Note: It is not enough to only check recording time and duration against 'now'.
555   //       Only the state of the related timer is a safe indicator that the backend
556   //       actually is recording this.
557 
558   return GetRecordingTimer() != nullptr;
559 }
560 
SetGenre(int iGenreType,int iGenreSubType,const std::string & strGenre)561 void CPVRRecording::SetGenre(int iGenreType, int iGenreSubType, const std::string& strGenre)
562 {
563   m_iGenreType = iGenreType;
564   m_iGenreSubType = iGenreSubType;
565 
566   if ((iGenreType == EPG_GENRE_USE_STRING || iGenreSubType == EPG_GENRE_USE_STRING) && !strGenre.empty())
567   {
568     /* Type and sub type are not given. Use the provided genre description if available. */
569     m_genre = StringUtils::Split(strGenre, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
570   }
571   else
572   {
573     /* Determine the genre description from the type and subtype IDs */
574     m_genre = StringUtils::Split(CPVREpg::ConvertGenreIdToString(iGenreType, iGenreSubType), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
575   }
576 }
577 
GetGenresLabel() const578 const std::string CPVRRecording::GetGenresLabel() const
579 {
580   return StringUtils::Join(m_genre, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
581 }
582 
FirstAired() const583 CDateTime CPVRRecording::FirstAired() const
584 {
585   return m_firstAired;
586 }
587 
SetYear(int year)588 void CPVRRecording::SetYear(int year)
589 {
590   if (year > 0)
591     m_premiered = CDateTime(year, 1, 1, 0, 0, 0);
592 }
593 
GetYear() const594 int CPVRRecording::GetYear() const
595 {
596   return m_premiered.GetYear();
597 }
598 
HasYear() const599 bool CPVRRecording::HasYear() const
600 {
601   return m_premiered.IsValid();
602 }
603 
IsNew() const604 bool CPVRRecording::IsNew() const
605 {
606   return (m_iFlags & PVR_RECORDING_FLAG_IS_NEW) > 0;
607 }
608 
IsPremiere() const609 bool CPVRRecording::IsPremiere() const
610 {
611   return (m_iFlags & PVR_RECORDING_FLAG_IS_PREMIERE) > 0;
612 }
613 
IsLive() const614 bool CPVRRecording::IsLive() const
615 {
616   return (m_iFlags & PVR_RECORDING_FLAG_IS_LIVE) > 0;
617 }
618 
IsFinale() const619 bool CPVRRecording::IsFinale() const
620 {
621   return (m_iFlags & PVR_RECORDING_FLAG_IS_FINALE) > 0;
622 }
623 
GetSizeInBytes() const624 int64_t CPVRRecording::GetSizeInBytes() const
625 {
626   CSingleLock lock(m_critSection);
627   return m_sizeInBytes;
628 }
629