MythTV master
recordinginfo.cpp
Go to the documentation of this file.
1// POSIX headers
2#include <sys/types.h>
3
4// C headers
5#include <cstdlib>
6
7// Qt headers
8#include <QMap>
9#include <QTimeZone>
10
11// MythTV headers
14#include "libmythbase/mythdb.h"
17
18#include "jobqueue.h"
19#include "recordinginfo.h"
20#include "recordingrule.h"
21#include "scheduledrecording.h"
22
23#define LOC QString("RecordingInfo(%1): ").arg(GetBasename())
24
25const QRegularExpression RecordingInfo::kReSearchTypeName { R"(\s*\‍(.*\)$)" };
26const QRegularExpression RecordingInfo::kReLeadingAnd
27 { R"(^\s*AND\s*)", QRegularExpression::CaseInsensitiveOption };
28
30// works only for integer divisors of 60
31static const uint kUnknownProgramLength = 30;
32
34 const QString &_title,
35 const QString &_sortTitle,
36 const QString &_subtitle,
37 const QString &_sortSubtitle,
38 const QString &_description,
39 uint _season,
40 uint _episode,
41 uint _totalepisodes,
42 const QString &_syndicatedepisode,
43 const QString &_category,
44
45 uint _chanid,
46 const QString &_chanstr,
47 const QString &_chansign,
48 const QString &_channame,
49
50 const QString &_recgroup,
51 const QString &_playgroup,
52
53 const QString &_hostname,
54 const QString &_storagegroup,
55
56 uint _year,
57 uint _partnumber,
58 uint _parttotal,
59
60 const QString &_seriesid,
61 const QString &_programid,
62 const QString &_inetref,
63 const CategoryType _catType,
64
65 int _recpriority,
66
67 const QDateTime &_startts,
68 const QDateTime &_endts,
69 const QDateTime &_recstartts,
70 const QDateTime &_recendts,
71
72 float _stars,
73 QDate _originalAirDate,
74
75 bool _repeat,
76
77 RecStatus::Type _oldrecstatus,
78 bool _reactivate,
79
80 uint _recordid,
81 uint _parentid,
82 RecordingType _rectype,
83 RecordingDupInType _dupin,
84 RecordingDupMethodType _dupmethod,
85
86 uint _sourceid,
87 uint _inputid,
88
89 uint _findid,
90
91 bool _commfree,
92 uint _subtitleType,
93 uint _videoproperties,
94 uint _audioproperties,
95 bool _future,
96 int _schedorder,
97 uint _mplexid,
98 uint _sgroupid,
99 const QString &_inputname) :
101 _title, _sortTitle, _subtitle, _sortSubtitle,
102 _description, _season, _episode, _totalepisodes,
103 _category, _chanid, _chanstr, _chansign, _channame,
104 QString(), _recgroup, _playgroup,
105 _startts, _endts, _recstartts, _recendts,
106 _seriesid, _programid, _inetref, _inputname),
107 m_oldrecstatus(_oldrecstatus),
108 m_future(_future),
109 m_schedOrder(_schedorder),
110 m_mplexId(_mplexid),
111 m_sgroupId(_sgroupid),
112 m_desiredRecStartTs(_startts),
113 m_desiredRecEndTs(_endts)
114{
115 m_hostname = _hostname;
116 m_storageGroup = _storagegroup;
117
118 m_syndicatedEpisode = _syndicatedepisode;
119
120 m_year = _year;
121 m_partNumber = _partnumber;
122 m_partTotal = _parttotal;
123 m_catType = _catType;
124
125 m_recPriority = _recpriority;
126
127 m_stars = std::clamp(_stars, 0.0F, 1.0F);
128 m_originalAirDate = _originalAirDate;
129 if (m_originalAirDate.isValid() && m_originalAirDate < QDate(1895, 12, 28))
130 m_originalAirDate = QDate();
131
132 m_programFlags &= ~FL_REPEAT;
133 m_programFlags |= _repeat ? FL_REPEAT : FL_NONE;
134 m_programFlags &= ~FL_REACTIVATE;
135 m_programFlags |= _reactivate ? FL_REACTIVATE : FL_NONE;
136 m_programFlags &= ~FL_CHANCOMMFREE;
137 m_programFlags |= _commfree ? FL_CHANCOMMFREE : FL_NONE;
138
139 m_recordId = _recordid;
140 m_parentId = _parentid;
141 m_recType = _rectype;
142 m_dupIn = _dupin;
143 m_dupMethod = _dupmethod;
144
145 m_sourceId = _sourceid;
146 m_inputId = _inputid;
147 m_findId = _findid;
148
149 m_videoProperties = _videoproperties;
150 m_audioProperties = _audioproperties;
151 m_subtitleProperties = _subtitleType;
152
154 {
155 // start/end-offsets are invalid so ignore
158 }
159
161}
162
164 const QString &_title,
165 const QString &_sortTitle,
166 const QString &_subtitle,
167 const QString &_sortSubtitle,
168 const QString &_description,
169 uint _season,
170 uint _episode,
171 const QString &_category,
172
173 uint _chanid,
174 const QString &_chanstr,
175 const QString &_chansign,
176 const QString &_channame,
177
178 const QString &_recgroup,
179 const QString &_playgroup,
180
181 const QString &_seriesid,
182 const QString &_programid,
183 const QString &_inetref,
184
185 int _recpriority,
186
187 const QDateTime &_startts,
188 const QDateTime &_endts,
189 const QDateTime &_recstartts,
190 const QDateTime &_recendts,
191
192 RecStatus::Type _recstatus,
193
194 uint _recordid,
195 RecordingType _rectype,
196 RecordingDupInType _dupin,
197 RecordingDupMethodType _dupmethod,
198
199 uint _findid,
200
201 bool _commfree) :
203 _title, _sortTitle, _subtitle, _sortSubtitle,
204 _description, _season, _episode, 0,
205 _category, _chanid, _chanstr, _chansign, _channame,
206 QString(), _recgroup, _playgroup,
207 _startts, _endts, _recstartts, _recendts,
208 _seriesid, _programid, _inetref, ""),
209 m_desiredRecStartTs(_startts),
210 m_desiredRecEndTs(_endts)
211{
212 m_recPriority = _recpriority;
213
214 m_recStatus = _recstatus,
215
216 m_recordId = _recordid;
217 m_recType = _rectype;
218 m_dupIn = _dupin;
219 m_dupMethod = _dupmethod;
220
221 m_findId = _findid;
222
223 m_programFlags &= ~FL_CHANCOMMFREE;
224 m_programFlags |= _commfree ? FL_CHANCOMMFREE : FL_NONE;
225
227}
228
238 uint _chanid, const QDateTime &desiredts,
239 bool genUnknown, std::chrono::hours maxHours, LoadStatus *status)
240{
241 ProgramList schedList;
242 ProgramList progList;
243
244 MSqlBindings bindings;
245 QString querystr = "WHERE program.chanid = :CHANID AND "
246 " program.starttime < :STARTTS1 AND "
247 " program.endtime > :STARTTS2 ";
248 bindings[":CHANID"] = QString::number(_chanid);
249 QDateTime query_startts = desiredts.addSecs(50 - desiredts.time().second());
250 bindings[":STARTTS1"] = query_startts;
251 bindings[":STARTTS2"] = query_startts;
252
253 ::LoadFromScheduler(schedList);
254 LoadFromProgram(progList, querystr, bindings, schedList);
255
256 if (!progList.empty())
257 {
258 ProgramInfo *pginfo = progList[0];
259
260 if (maxHours > 0h)
261 {
262 auto maxSecs = duration_cast<std::chrono::seconds>(maxHours);
263 if (desiredts.secsTo(pginfo->GetScheduledEndTime()) > maxSecs.count())
264 {
265 pginfo->SetScheduledEndTime(desiredts.addSecs(maxSecs.count()));
266 pginfo->SetRecordingEndTime(pginfo->GetScheduledEndTime());
267 }
268 }
269
270 *this = *pginfo;
271 if (status)
272 *status = kFoundProgram;
273 return;
274 }
275
276 m_recStartTs = m_startTs = desiredts;
277 m_recEndTs = m_endTs = desiredts;
278 m_lastModified = desiredts;
279
281 query.prepare("SELECT chanid, channum, callsign, name, "
282 "commmethod, outputfilters "
283 "FROM channel "
284 "WHERE chanid = :CHANID");
285 query.bindValue(":CHANID", _chanid);
286
287 if (!query.exec())
288 {
289 MythDB::DBError("Loading Program overlapping a datetime", query);
290 if (status)
291 *status = kNoProgram;
292 return;
293 }
294
295 if (!query.next())
296 {
297 if (status)
298 *status = kNoProgram;
299 return;
300 }
301
302 m_chanId = query.value(0).toUInt();
303 m_chanStr = query.value(1).toString();
304 m_chanSign = query.value(2).toString();
305 m_chanName = query.value(3).toString();
306 m_programFlags &= ~FL_CHANCOMMFREE;
307 m_programFlags |= (query.value(4).toInt() == COMM_DETECT_COMMFREE) ?
308 FL_CHANCOMMFREE : FL_NONE;
309 m_chanPlaybackFilters = query.value(5).toString();
310
311 {
312 QMutexLocker locker(&s_staticDataLock);
313 if (s_unknownTitle.isEmpty())
314 s_unknownTitle = gCoreContext->GetSetting("UnknownTitle");
316 }
317
318 if (!genUnknown)
319 {
320 if (status)
321 *status = kFakedZeroMinProgram;
322 return;
323 }
324
325 // Round endtime up to the next half-hour.
326#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
327 m_endTs = QDateTime(
328 m_endTs.date(),
329 QTime(m_endTs.time().hour(),
330 m_endTs.time().minute() / kUnknownProgramLength
331 * kUnknownProgramLength), Qt::UTC);
332#else
333 m_endTs = QDateTime(m_endTs.date(),
334 QTime(m_endTs.time().hour(),
335 m_endTs.time().minute() / kUnknownProgramLength
337 QTimeZone(QTimeZone::UTC));
338#endif
339 m_endTs = m_endTs.addSecs(kUnknownProgramLength * 60LL);
340
341 // if under a minute, bump it up to the next half hour
342 if (m_startTs.secsTo(m_endTs) < 60)
343 m_endTs = m_endTs.addSecs(kUnknownProgramLength * 60LL);
344
346
347 // Find next program starttime
348 bindings.clear();
349 QDateTime nextstart = m_startTs;
350 querystr = "WHERE program.chanid = :CHANID AND "
351 " program.starttime > :STARTTS "
352 "GROUP BY program.starttime ORDER BY program.starttime ";
353 bindings[":CHANID"] = QString::number(_chanid);
354 bindings[":STARTTS"] = desiredts.addSecs(50 - desiredts.time().second());
355
356 const uint limit = 1;
357 uint count = 0;
358 LoadFromProgram(progList, querystr, bindings, schedList, 0, limit, count);
359
360 if (!progList.empty())
361 nextstart = (*progList.begin())->GetScheduledStartTime();
362
363 if (nextstart > m_startTs && nextstart < m_recEndTs)
364 m_recEndTs = m_endTs = nextstart;
365
366 if (status)
367 *status = kFakedLiveTVProgram;
368
371
373}
374
377 bool ignore_non_serialized_data)
378{
379 bool is_same =
380 ((m_chanId != 0U) && m_recStartTs.isValid() && m_startTs.isValid() &&
381 m_chanId == other.GetChanID() &&
384
385 ProgramInfo::clone(other, ignore_non_serialized_data);
386
387 if (!is_same)
388 {
389 delete m_record;
390 m_record = nullptr;
391 }
392
393 if (!ignore_non_serialized_data)
394 {
397 m_future = other.m_future;
399 m_mplexId = other.m_mplexId;
400 m_sgroupId = other.m_sgroupId;
403 }
404
405 delete m_recordingFile;
406 m_recordingFile = nullptr;
408}
409
412 bool ignore_non_serialized_data)
413{
414 bool is_same =
415 ((m_chanId != 0U) && m_recStartTs.isValid() && m_startTs.isValid() &&
416 m_chanId == other.GetChanID() &&
419
420 ProgramInfo::clone(other, ignore_non_serialized_data);
421
422 if (!is_same)
423 {
424 delete m_record;
425 m_record = nullptr;
426 }
427
430 m_future = false;
431 m_schedOrder = 0;
432 m_mplexId = 0;
433 m_sgroupId = 0;
434 m_desiredRecStartTs = QDateTime();
435 m_desiredRecEndTs = QDateTime();
436
437 delete m_recordingFile;
438 m_recordingFile = nullptr;
440}
441
443{
445
446 delete m_record;
447 m_record = nullptr;
448
451 m_future = false;
452 m_schedOrder = 0;
453 m_mplexId = 0;
454 m_sgroupId = 0;
455 m_desiredRecStartTs = QDateTime();
456 m_desiredRecEndTs = QDateTime();
457
458 delete m_recordingFile;
459 m_recordingFile = nullptr;
460}
461
462
467{
468 delete m_record;
469 m_record = nullptr;
470
471 delete m_recordingFile;
472 m_recordingFile = nullptr;
473}
474
481{
482 if (m_record == nullptr)
483 {
484 m_record = new RecordingRule();
485 m_record->LoadByProgram(this);
486 }
487
488 return m_record->m_type;
489}
490
497{
498 if (m_record == nullptr)
499 {
500 m_record = new RecordingRule();
501 m_record->LoadByProgram(this);
502 }
503
504 return m_record->m_recProfile;
505}
506
511{
512 if (m_record == nullptr)
513 {
514 m_record = new RecordingRule();
515 m_record->LoadByProgram(this);
516 }
517
518 int result = 0;
519
521 result |= JOB_TRANSCODE;
523 result |= JOB_COMMFLAG;
525 result |= JOB_METADATA;
527 result |= JOB_USERJOB1;
529 result |= JOB_USERJOB2;
531 result |= JOB_USERJOB3;
533 result |= JOB_USERJOB4;
534
535
536 return result;
537}
538
543{
545
546 if (getRecordID() < 0)
547 {
548 LOG(VB_GENERAL, LOG_ERR,
549 "ProgInfo Error: ApplyRecordRecID(void) needs recordid");
550 return;
551 }
552
553 query.prepare("UPDATE recorded "
554 "SET recordid = :RECID "
555 "WHERE chanid = :CHANID AND starttime = :START");
556
558 query.bindValue(":RECID", m_parentId);
559 else
560 query.bindValue(":RECID", getRecordID());
561 query.bindValue(":CHANID", m_chanId);
562 query.bindValue(":START", m_recStartTs);
563
564 if (!query.exec())
565 MythDB::DBError(LOC + "RecordID update", query);
566}
567
578{
580 if (newstate == kOverrideRecord || newstate == kDontRecord)
582 m_record->m_type = newstate;
583
584 if (save)
585 {
586 if (newstate == kNotRecording)
587 m_record->Delete();
588 else
589 m_record->Save();
590 }
591}
592
597void RecordingInfo::ApplyStarsChange(float newstarsvalue)
598{
600
601 query.prepare("UPDATE recorded"
602 " SET stars = :STARS"
603 " WHERE chanid = :CHANID"
604 " AND starttime = :START ;");
605 query.bindValue(":STARS", newstarsvalue);
606 query.bindValue(":START", m_recStartTs);
607 query.bindValue(":CHANID", m_chanId);
608
609 if (!query.exec())
610 MythDB::DBError("Stars update", query);
611
612 m_stars = newstarsvalue;
613
615}
616
623{
625
626 query.prepare("UPDATE recorded"
627 " SET originalairdate = :ORIGINALAIRDATE"
628 " WHERE chanid = :CHANID"
629 " AND starttime = :START ;");
630 query.bindValue(":ORIGINALAIRDATE", originalairdate);
631 query.bindValue(":CHANID", m_chanId);
632 query.bindValue(":START", m_recStartTs);
633
634 if (!query.exec())
635 MythDB::DBError("OriginalAirDate update", query);
636
637 m_originalAirDate = originalairdate;
638
640}
641
648{
650 m_record->m_recPriority = newrecpriority;
651 m_record->Save();
652}
653
659void RecordingInfo::ApplyRecordRecGroupChange(const QString &newrecgroup)
660{
662
663 int newrecgroupid = GetRecgroupID(newrecgroup);
664
665 // Catchall - in the event that the group doesn't exist, then to avoid
666 // breakage, we need to create it
667 if (newrecgroupid == 0)
668 {
669 query.prepare("INSERT INTO recgroups SET recgroup = :NAME, "
670 "displayname = :DISPLAYNAME");
671 query.bindValue(":NAME", newrecgroup);
672 query.bindValue(":DISPLAYNAME", newrecgroup);
673
674 if (query.exec())
675 newrecgroupid = query.lastInsertId().toInt();
676
677 if (newrecgroupid <= 0)
678 {
679 LOG(VB_GENERAL, LOG_ERR, QString("Could not create recording group (%1). "
680 "Does it already exist?").arg(newrecgroup));
681 return;
682 }
683 }
684
685 LOG(VB_GENERAL, LOG_NOTICE,
686 QString("ApplyRecordRecGroupChange: %1 to %2 (%3)")
687 .arg(m_recGroup, newrecgroup, QString::number(newrecgroupid)));
688
689 query.prepare("UPDATE recorded"
690 " SET recgroup = :RECGROUP, "
691 " recgroupid = :RECGROUPID "
692 " WHERE recordedid = :RECORDEDID");
693 query.bindValueNoNull(":RECGROUP", newrecgroup);
694 query.bindValue(":RECGROUPID", newrecgroupid);
695 query.bindValue(":RECORDEDID", m_recordedId);
696
697 if (!query.exec())
698 MythDB::DBError("RecGroup update", query);
699
700 m_recGroup = newrecgroup; // Deprecate in favour of recgroupid
701 //recgroupid = newrecgroupid;
702
704}
705
707{
709
710 QString newrecgroup;
711 if (newrecgroupid > 0)
712 {
713 newrecgroup = GetRecgroupString(newrecgroupid);
714
715 query.prepare("UPDATE recorded"
716 " SET recgroup = :RECGROUP, "
717 " recgroupid = :RECGROUPID "
718 " WHERE chanid = :CHANID"
719 " AND starttime = :START ;");
720 query.bindValueNoNull(":RECGROUP", newrecgroup);
721 query.bindValue(":RECGROUPID", newrecgroupid);
722 query.bindValue(":START", m_recStartTs);
723 query.bindValue(":CHANID", m_chanId);
724
725 if (!query.exec())
726 MythDB::DBError("RecGroup update", query);
727
728 m_recGroup = newrecgroup; // Deprecate in favour of recgroupid
729 //recgroupid = newrecgroupid;
730
732 }
733
734 LOG(VB_GENERAL, LOG_NOTICE,
735 QString("ApplyRecordRecGroupChange: %1 to %2 (%3)")
736 .arg(m_recGroup, newrecgroup).arg(newrecgroupid));
737}
738
744void RecordingInfo::ApplyRecordPlayGroupChange(const QString &newplaygroup)
745{
747
748 query.prepare("UPDATE recorded"
749 " SET playgroup = :PLAYGROUP"
750 " WHERE chanid = :CHANID"
751 " AND starttime = :START ;");
752 query.bindValueNoNull(":PLAYGROUP", newplaygroup);
753 query.bindValue(":START", m_recStartTs);
754 query.bindValue(":CHANID", m_chanId);
755
756 if (!query.exec())
757 MythDB::DBError("PlayGroup update", query);
758
759 m_playGroup = newplaygroup;
760
762}
763
769void RecordingInfo::ApplyStorageGroupChange(const QString &newstoragegroup)
770{
772
773 query.prepare("UPDATE recorded"
774 " SET storagegroup = :STORAGEGROUP"
775 " WHERE chanid = :CHANID"
776 " AND starttime = :START ;");
777 query.bindValueNoNull(":STORAGEGROUP", newstoragegroup);
778 query.bindValue(":START", m_recStartTs);
779 query.bindValue(":CHANID", m_chanId);
780
781 if (!query.exec())
782 MythDB::DBError("StorageGroup update", query);
783
784 m_storageGroup = newstoragegroup;
785
787}
788
796void RecordingInfo::ApplyRecordRecTitleChange(const QString &newTitle,
797 const QString &newSubtitle, const QString &newDescription)
798{
800 QString sql = "UPDATE recorded SET title = :TITLE, subtitle = :SUBTITLE ";
801 if (!newDescription.isNull())
802 sql += ", description = :DESCRIPTION ";
803 sql += " WHERE chanid = :CHANID AND starttime = :START ;";
804
805 query.prepare(sql);
806 query.bindValue(":TITLE", newTitle);
807 query.bindValueNoNull(":SUBTITLE", newSubtitle);
808 if (!newDescription.isNull())
809 query.bindValue(":DESCRIPTION", newDescription);
810 query.bindValue(":CHANID", m_chanId);
811 query.bindValue(":START", m_recStartTs);
812
813 if (!query.exec())
814 MythDB::DBError("RecTitle update", query);
815
816 m_title = newTitle;
817 m_subtitle = newSubtitle;
818 if (!newDescription.isNull())
819 m_description = newDescription;
820
822}
823
824/* \fn RecordingInfo::ApplyTranscoderProfileChangeById(int id)
825 * \brief Sets the transcoder profile for a recording
826 * \param profileid is the 'id' field from recordingprofiles table.
827 */
829{
831
832 query.prepare("UPDATE recorded "
833 "SET transcoder = :PROFILEID "
834 "WHERE chanid = :CHANID "
835 "AND starttime = :START");
836 query.bindValue(":PROFILEID", id);
837 query.bindValue(":CHANID", m_chanId);
838 query.bindValue(":START", m_recStartTs);
839
840 if (!query.exec())
841 MythDB::DBError(LOC + "unable to update transcoder "
842 "in recorded table", query);
843}
844
849{
850 if (profile == "Default") // use whatever is already in the transcoder
851 return;
852
854
855 if (profile == "Autodetect")
856 {
857 query.prepare("UPDATE recorded "
858 "SET transcoder = 0 "
859 "WHERE chanid = :CHANID "
860 "AND starttime = :START");
861 query.bindValue(":CHANID", m_chanId);
862 query.bindValue(":START", m_recStartTs);
863
864 if (!query.exec())
865 MythDB::DBError(LOC + "unable to update transcoder "
866 "in recorded table", query);
867 }
868 else
869 {
870 MSqlQuery pidquery(MSqlQuery::InitCon());
871 pidquery.prepare("SELECT r.id "
872 "FROM recordingprofiles r, profilegroups p "
873 "WHERE r.profilegroup = p.id "
874 "AND p.name = 'Transcoders' "
875 "AND r.name = :PROFILE ");
876 pidquery.bindValue(":PROFILE", profile);
877
878 if (!pidquery.exec())
879 {
880 MythDB::DBError("ProgramInfo: unable to query transcoder "
881 "profile ID", query);
882 }
883 else if (pidquery.next())
884 {
885 query.prepare("UPDATE recorded "
886 "SET transcoder = :TRANSCODER "
887 "WHERE chanid = :CHANID "
888 "AND starttime = :START");
889 query.bindValue(":TRANSCODER", pidquery.value(0).toInt());
890 query.bindValue(":CHANID", m_chanId);
891 query.bindValue(":START", m_recStartTs);
892
893 if (!query.exec())
894 MythDB::DBError(LOC + "unable to update transcoder "
895 "in recorded table", query);
896 }
897 else
898 {
899 LOG(VB_GENERAL, LOG_ERR,
900 "ProgramInfo: unable to query transcoder profile ID");
901 }
902 }
903}
904
910{
914 AddHistory(true, true);
915}
916
921{
923 if (curType == kNotRecording)
925}
926
931{
933 return m_record;
934}
935
940{
943 return m_recordId;
944}
945
947 uint chanid, const QDateTime& recstartts)
948{
949 if (chanid < 1)
950 {
951 LOG(VB_RECORD, LOG_WARNING,
952 QString("QueryRecordedIdFromKey: Invalid chanid %1").arg(chanid));
953 return false;
954 }
955 if (!recstartts.isValid())
956 {
957 LOG(VB_RECORD, LOG_WARNING,
958 QString("QueryRecordedIdFromKey: Invalid start ts %1")
959 .arg(recstartts.toString()));
960 return false;
961 }
962
964 query.prepare(
965 "SELECT recordedid FROM recorded "
966 "WHERE chanid = :CHANID AND starttime = :RECSTARTTS");
967 query.bindValue(":CHANID", chanid);
968 query.bindValue(":RECSTARTTS", recstartts);
969 if (query.exec() && query.next())
970 {
971 recordedid = query.value(0).toUInt();
972 return true;
973 }
974
975 return false;
976}
977
986void RecordingInfo::StartedRecording(const QString& ext)
987{
989
990 if (!InsertRecording(ext))
991 return;
992
993 LOG(VB_FILE, LOG_INFO, LOC + QString("StartedRecording: Recording to '%1'")
994 .arg(m_pathname));
995
996
998
999 query.prepare("DELETE FROM recordedseek WHERE chanid = :CHANID"
1000 " AND starttime = :START;");
1001 query.bindValue(":CHANID", m_chanId);
1002 query.bindValue(":START", m_recStartTs);
1003
1004 if (!query.exec() || !query.isActive())
1005 MythDB::DBError("Clear seek info on record", query);
1006
1007 query.prepare("DELETE FROM recordedmarkup WHERE chanid = :CHANID"
1008 " AND starttime = :START;");
1009 query.bindValue(":CHANID", m_chanId);
1010 query.bindValue(":START", m_recStartTs);
1011
1012 if (!query.exec() || !query.isActive())
1013 MythDB::DBError("Clear markup on record", query);
1014
1015 query.prepare("REPLACE INTO recordedcredits"
1016 " SELECT * FROM credits"
1017 " WHERE chanid = :CHANID AND starttime = :START;");
1018 query.bindValue(":CHANID", m_chanId);
1019 query.bindValue(":START", m_startTs);
1020 if (!query.exec() || !query.isActive())
1021 MythDB::DBError("Copy program credits on record", query);
1022
1023 query.prepare("REPLACE INTO recordedprogram"
1024 " SELECT * from program"
1025 " WHERE chanid = :CHANID AND starttime = :START"
1026 " AND title = :TITLE;");
1027 query.bindValue(":CHANID", m_chanId);
1028 query.bindValue(":START", m_startTs);
1029 query.bindValue(":TITLE", m_title);
1030 if (!query.exec() || !query.isActive())
1031 MythDB::DBError("Copy program data on record", query);
1032
1033 query.prepare("REPLACE INTO recordedrating"
1034 " SELECT * from programrating"
1035 " WHERE chanid = :CHANID AND starttime = :START;");
1036 query.bindValue(":CHANID", m_chanId);
1037 query.bindValue(":START", m_startTs);
1038 if (!query.exec() || !query.isActive())
1039 MythDB::DBError("Copy program ratings on record", query);
1040
1041 InsertFile();
1042}
1043
1044bool RecordingInfo::InsertRecording(const QString &ext, bool force_match)
1045{
1046 QString dirname = m_pathname;
1047
1048#if 1
1049 if (!dirname.isEmpty())
1050 {
1051 LOG(VB_GENERAL, LOG_DEBUG, LOC +
1052 QString("InsertRecording: m_pathname was '%1'. "
1053 "This is usually blank.").arg(dirname));
1054 }
1055#endif
1056
1058
1059 if (!m_record)
1060 {
1061 m_record = new RecordingRule();
1062 m_record->LoadByProgram(this);
1063 }
1064
1065 int count = 0;
1066 while (!InsertProgram(this, m_record) && count < 50)
1067 {
1068 if (force_match)
1069 {
1070 LOG(VB_GENERAL, LOG_ERR, "Failed to insert new recording.");
1071 return false;
1072 }
1073
1074 m_recStartTs = m_recStartTs.addSecs(1);
1076 ++count;
1077 }
1078
1079 if (count >= 50)
1080 {
1081 LOG(VB_GENERAL, LOG_ERR, "Could not insert program");
1082 return false;
1083 }
1084
1085 m_pathname = dirname + "/" + m_pathname;
1086
1087 return true;
1088}
1089
1091 const RecordingRule *rule)
1092{
1093 QString inputname = pg->GetInputName();
1094 int recgroupid = GetRecgroupID(pg->m_recGroup);
1095
1097
1098 if (!query.exec("LOCK TABLES recorded WRITE"))
1099 {
1100 MythDB::DBError("InsertProgram -- lock", query);
1101 return false;
1102 }
1103
1104 // Catchall - in the event that the group doesn't exist, then to avoid
1105 // breakage, we need to create it
1106 if (recgroupid == 0)
1107 {
1108 query.prepare("INSERT INTO recgroups SET recgroup = :NAME, "
1109 "displayname = :DISPLAYNAME");
1110 query.bindValue(":NAME", pg->m_recGroup);
1111 query.bindValue(":DISPLAYNAME", pg->m_recGroup);
1112
1113 if (query.exec())
1114 recgroupid = query.lastInsertId().toInt();
1115
1116 if (recgroupid <= 0)
1117 {
1118 LOG(VB_GENERAL, LOG_ERR, QString("Could not create recording group (%1). "
1119 "Does it already exist?")
1120 .arg(pg->m_recGroup));
1121 }
1122 }
1123
1124 query.prepare(
1125 "SELECT recordid "
1126 " FROM recorded "
1127 " WHERE chanid = :CHANID AND "
1128 " starttime = :STARTS");
1129 query.bindValue(":CHANID", pg->m_chanId);
1130 query.bindValue(":STARTS", pg->m_recStartTs);
1131
1132 bool err = true;
1133 if (!query.exec())
1134 {
1135 MythDB::DBError("InsertProgram -- select", query);
1136 }
1137 else if (query.next())
1138 {
1139 LOG(VB_GENERAL, LOG_ERR,
1140 QString("RecordingInfo::InsertProgram(%1): ")
1141 .arg(pg->toString()) + "recording already exists...");
1142 }
1143 else
1144 {
1145 err = false;
1146 }
1147
1148 if (err)
1149 {
1150 if (!query.exec("UNLOCK TABLES"))
1151 MythDB::DBError("InsertProgram -- unlock tables", query);
1152 return false;
1153 }
1154
1155 query.prepare(
1156 "INSERT INTO recorded "
1157 " (chanid, starttime, endtime, title, "
1158 " subtitle, description, season, episode, "
1159 " hostname, category, recgroup, autoexpire, "
1160 " recordid, seriesid, programid, inetref, "
1161 " stars, previouslyshown, originalairdate, "
1162 " findid, transcoder, playgroup, recpriority, "
1163 " basename, progstart, progend, profile, "
1164 " duplicate, storagegroup, inputname, recgroupid) "
1165 "VALUES"
1166 " (:CHANID, :STARTS, :ENDS, :TITLE, "
1167 " :SUBTITLE, :DESC, :SEASON, :EPISODE, "
1168 " :HOSTNAME, :CATEGORY, :RECGROUP, :AUTOEXP, "
1169 " :RECORDID, :SERIESID, :PROGRAMID, :INETREF, "
1170 " :STARS, :REPEAT, :ORIGAIRDATE, "
1171 " :FINDID, :TRANSCODER, :PLAYGROUP, :RECPRIORITY, "
1172 " :BASENAME, :PROGSTART, :PROGEND, :PROFILE, "
1173 " 0, :STORGROUP, :INPUTNAME, :RECGROUPID) "
1174 );
1175
1176 if (pg->m_recType == kOverrideRecord)
1177 query.bindValue(":RECORDID", pg->m_parentId);
1178 else
1179 query.bindValue(":RECORDID", pg->m_recordId);
1180
1181 if (pg->m_originalAirDate.isValid())
1182 {
1183 query.bindValue(":ORIGAIRDATE", pg->m_originalAirDate);
1184 // If there is no originalairdate use "year"
1185 }
1186 else if (pg->m_year == pg->m_recStartTs.date().year())
1187 {
1188 query.bindValue(":ORIGAIRDATE", pg->m_recStartTs.date());
1189 }
1190 else if (pg->m_year >= 1895)
1191 {
1192 query.bindValue(":ORIGAIRDATE", QDate(pg->m_year,1,1));
1193 }
1194 else
1195 {
1196 query.bindValue(":ORIGAIRDATE", "0000-00-00");
1197 }
1198
1199 query.bindValue(":CHANID", pg->m_chanId);
1200 query.bindValue(":STARTS", pg->m_recStartTs);
1201 query.bindValue(":ENDS", pg->m_recEndTs);
1202 query.bindValue(":TITLE", pg->m_title);
1203 query.bindValueNoNull(":SUBTITLE", pg->m_subtitle);
1204 query.bindValueNoNull(":DESC", pg->m_description);
1205 query.bindValue(":SEASON", pg->m_season);
1206 query.bindValue(":EPISODE", pg->m_episode);
1207 query.bindValue(":HOSTNAME", pg->m_hostname);
1208 query.bindValueNoNull(":CATEGORY", pg->m_category);
1209 query.bindValueNoNull(":RECGROUP", pg->m_recGroup);
1210 query.bindValue(":AUTOEXP", rule->m_autoExpire);
1211 query.bindValueNoNull(":SERIESID", pg->m_seriesId);
1212 query.bindValueNoNull(":PROGRAMID", pg->m_programId);
1213 query.bindValueNoNull(":INETREF", pg->m_inetRef);
1214 query.bindValue(":FINDID", pg->m_findId);
1215 query.bindValue(":STARS", pg->m_stars);
1216 query.bindValue(":REPEAT", pg->IsRepeat());
1217 query.bindValue(":TRANSCODER", rule->m_transcoder);
1218 query.bindValue(":PLAYGROUP", pg->m_playGroup);
1219 query.bindValue(":RECPRIORITY", rule->m_recPriority);
1220 query.bindValue(":BASENAME", pg->m_pathname);
1221 query.bindValueNoNull(":STORGROUP", pg->m_storageGroup);
1222 query.bindValue(":PROGSTART", pg->m_startTs);
1223 query.bindValue(":PROGEND", pg->m_endTs);
1224 query.bindValueNoNull(":PROFILE", rule->m_recProfile);
1225 query.bindValue(":INPUTNAME", inputname);
1226 query.bindValue(":RECGROUPID", recgroupid);
1227
1228 bool ok = query.exec() && (query.numRowsAffected() > 0);
1229 if (ok)
1230 {
1231 pg->SetRecordingID(query.lastInsertId().toUInt());
1232 }
1233 bool active = query.isActive();
1234
1235 if (!query.exec("UNLOCK TABLES"))
1236 MythDB::DBError("InsertProgram -- unlock tables", query);
1237
1238 if (!ok && !active)
1239 {
1240 MythDB::DBError("InsertProgram -- insert", query);
1241
1242 }
1243 else if (pg->m_recordId > 0)
1244 {
1245 query.prepare("UPDATE channel SET last_record = NOW() "
1246 "WHERE chanid = :CHANID");
1247 query.bindValue(":CHANID", pg->GetChanID());
1248 if (!query.exec())
1249 MythDB::DBError("InsertProgram -- channel last_record", query);
1250
1251 query.prepare("UPDATE record SET last_record = NOW() "
1252 "WHERE recordid = :RECORDID");
1253 query.bindValue(":RECORDID", pg->m_recordId);
1254 if (!query.exec())
1255 MythDB::DBError("InsertProgram -- record last_record", query);
1256
1257 if (pg->m_recType == kOverrideRecord && pg->m_parentId > 0)
1258 {
1259 query.prepare("UPDATE record SET last_record = NOW() "
1260 "WHERE recordid = :PARENTID");
1261 query.bindValue(":PARENTID", pg->m_parentId);
1262 if (!query.exec())
1263 MythDB::DBError("InsertProgram -- record last_record override",
1264 query);
1265 }
1266 }
1267
1268 return ok;
1269}
1270
1272{
1273 // File
1274 if (!GetRecordingFile())
1276 RecordingFile *recFile = GetRecordingFile();
1277 recFile->m_fileName = GetBasename();
1278 recFile->m_storageDeviceID = GetHostname();
1279 recFile->m_storageGroup = GetStorageGroup();
1280 recFile->Save();
1281
1283}
1284
1293void RecordingInfo::FinishedRecording(bool allowReRecord)
1294{
1296 query.prepare("UPDATE recorded SET endtime = :ENDTIME, "
1297 " duplicate = :DUPLICATE "
1298 "WHERE chanid = :CHANID AND "
1299 " starttime = :STARTTIME ");
1300 query.bindValue(":ENDTIME", m_recEndTs);
1301 query.bindValue(":CHANID", m_chanId);
1302 query.bindValue(":STARTTIME", m_recStartTs);
1303 query.bindValue(":DUPLICATE", !allowReRecord);
1304
1305 if (!query.exec())
1306 MythDB::DBError("FinishedRecording update", query);
1307
1309 if (!allowReRecord)
1310 {
1312
1313 qint64 starttime = m_recStartTs.toSecsSinceEpoch();
1314 qint64 endtime = m_recEndTs.toSecsSinceEpoch();
1315 SaveTotalDuration(std::chrono::seconds(endtime - starttime));
1316
1317 QString msg = "Finished recording";
1318 QString msg_subtitle = m_subtitle.isEmpty() ? "" :
1319 QString(" \"%1\"").arg(m_subtitle);
1320 QString details = QString("%1%2: channel %3")
1321 .arg(m_title, msg_subtitle, QString::number(m_chanId));
1322
1323 LOG(VB_GENERAL, LOG_INFO, QString("%1 %2").arg(msg, details));
1324 }
1325
1327}
1328
1334{
1336 query.prepare("UPDATE recorded SET endtime = :ENDTIME "
1337 "WHERE chanid = :CHANID AND "
1338 " starttime = :STARTTIME ");
1339 query.bindValue(":ENDTIME", m_recEndTs);
1340
1341 query.bindValue(":CHANID", m_chanId);
1342 query.bindValue(":STARTTIME", m_recStartTs);
1343
1344 if (!query.exec())
1345 MythDB::DBError("UpdateRecordingEnd update", query);
1346
1348}
1349
1354{
1355 MSqlQuery result(MSqlQuery::InitCon());
1356
1357 result.prepare("UPDATE oldrecorded SET reactivate = 1 "
1358 "WHERE station = :STATION AND "
1359 " starttime = :STARTTIME AND "
1360 " title = :TITLE;");
1361 result.bindValue(":STARTTIME", m_startTs);
1362 result.bindValue(":TITLE", m_title);
1363 result.bindValue(":STATION", m_chanSign);
1364
1365 if (!result.exec())
1366 MythDB::DBError("ReactivateRecording", result);
1367
1369}
1370
1374void RecordingInfo::AddHistory(bool resched, bool forcedup, bool future)
1375{
1376 bool dup = (GetRecordingStatus() == RecStatus::Recorded || forcedup);
1379 LOG(VB_SCHEDULE, LOG_INFO, QString("AddHistory: %1/%2, %3, %4, %5/%6")
1380 .arg(int(rs)).arg(int(m_oldrecstatus)).arg(future).arg(dup)
1382 if (!future)
1384 if (dup)
1385 SetReactivated(false);
1386 uint erecid = m_parentId ? m_parentId : m_recordId;
1387
1388 MSqlQuery result(MSqlQuery::InitCon());
1389
1390 result.prepare("REPLACE INTO oldrecorded (chanid,starttime,"
1391 "endtime,title,subtitle,description,season,episode,"
1392 "category,seriesid,programid,inetref,findid,recordid,"
1393 "station,rectype,recstatus,duplicate,reactivate,generic,"
1394 "future) "
1395 "VALUES(:CHANID,:START,:END,:TITLE,:SUBTITLE,:DESC,:SEASON,"
1396 ":EPISODE,:CATEGORY,:SERIESID,:PROGRAMID,:INETREF,"
1397 ":FINDID,:RECORDID,:STATION,:RECTYPE,:RECSTATUS,:DUPLICATE,"
1398 ":REACTIVATE,:GENERIC,:FUTURE);");
1399 result.bindValue(":CHANID", m_chanId);
1400 result.bindValue(":START", m_startTs);
1401 result.bindValue(":END", m_endTs);
1402 result.bindValue(":TITLE", m_title);
1403 result.bindValueNoNull(":SUBTITLE", m_subtitle);
1404 result.bindValueNoNull(":DESC", m_description);
1405 result.bindValue(":SEASON", m_season);
1406 result.bindValue(":EPISODE", m_episode);
1407 result.bindValueNoNull(":CATEGORY", m_category);
1408 result.bindValueNoNull(":SERIESID", m_seriesId);
1409 result.bindValueNoNull(":PROGRAMID", m_programId);
1410 result.bindValueNoNull(":INETREF", m_inetRef);
1411 result.bindValue(":FINDID", m_findId);
1412 result.bindValue(":RECORDID", erecid);
1413 result.bindValueNoNull(":STATION", m_chanSign);
1414 result.bindValue(":RECTYPE", m_recType);
1415 result.bindValue(":RECSTATUS", rs);
1416 result.bindValue(":DUPLICATE", dup);
1417 result.bindValue(":REACTIVATE", 0);
1418 result.bindValue(":GENERIC", IsGeneric());
1419 result.bindValue(":FUTURE", future);
1420
1421 if (!result.exec())
1422 MythDB::DBError("addHistory", result);
1423
1424 if (dup && m_findId)
1425 {
1426 result.prepare("REPLACE INTO oldfind (recordid, findid) "
1427 "VALUES(:RECORDID,:FINDID);");
1428 result.bindValue(":RECORDID", erecid);
1429 result.bindValue(":FINDID", m_findId);
1430
1431 if (!result.exec())
1432 MythDB::DBError("addFindHistory", result);
1433 }
1434
1435 // The adding of an entry to oldrecorded may affect near-future
1436 // scheduling decisions, so recalculate if told
1437 if (resched)
1438 ScheduledRecording::RescheduleCheck(*this, "AddHistory");
1439}
1440
1445{
1446 uint erecid = m_parentId ? m_parentId : m_recordId;
1447
1448 MSqlQuery result(MSqlQuery::InitCon());
1449
1450 result.prepare("DELETE FROM oldrecorded WHERE title = :TITLE AND "
1451 "starttime = :START AND station = :STATION");
1452 result.bindValue(":TITLE", m_title);
1453 result.bindValue(":START", m_recStartTs);
1454 result.bindValue(":STATION", m_chanSign);
1455
1456 if (!result.exec())
1457 MythDB::DBError("deleteHistory", result);
1458
1459 if (/*m_duplicate &&*/ m_findId)
1460 {
1461 result.prepare("DELETE FROM oldfind WHERE "
1462 "recordid = :RECORDID AND findid = :FINDID");
1463 result.bindValue(":RECORDID", erecid);
1464 result.bindValue(":FINDID", m_findId);
1465
1466 if (!result.exec())
1467 MythDB::DBError("deleteFindHistory", result);
1468 }
1469
1470 // The removal of an entry from oldrecorded may affect near-future
1471 // scheduling decisions, so recalculate
1472 ScheduledRecording::RescheduleCheck(*this, "DeleteHistory");
1473}
1474
1484{
1485 uint erecid = m_parentId ? m_parentId : m_recordId;
1486 uint din = m_dupIn;
1487 uint dmeth = m_dupMethod;
1488
1489 if (din == kDupsUnset)
1490 din = kDupsInAll;
1491 if (dmeth == kDupCheckUnset)
1492 dmeth = kDupCheckSubThenDesc;
1493
1494 MSqlQuery result(MSqlQuery::InitCon());
1495
1496 // Handle this specific entry in recorded.
1497 result.prepare("UPDATE recorded SET duplicate = 0 "
1498 "WHERE chanid = :CHANID "
1499 "AND starttime = :STARTTIME "
1500 "AND title = :TITLE;");
1501 result.bindValue(":STARTTIME", m_recStartTs);
1502 result.bindValue(":TITLE", m_title);
1503 result.bindValue(":CHANID", m_chanId);
1504
1505 if (!result.exec())
1506 MythDB::DBError("forgetRecorded1", result);
1507
1508 // Handle other matching entries in recorded.
1509 if (din & kDupsInRecorded)
1510 {
1511 result.prepare(
1512 "UPDATE recorded SET duplicate = 0 "
1513 "WHERE duplicate = 1 AND "
1514 " title = :TITLE AND "
1515 " ( "
1516 " (:PROGRAMID1 <> '' AND "
1517 " :PROGRAMID2 = recorded.programid) "
1518 " OR "
1519 " ( "
1520 " (:PROGRAMID3 = '' OR recorded.programid = '' OR "
1521 " LEFT(:PROGRAMID4, LOCATE('/', :PROGRAMID5)) <> "
1522 " LEFT(recorded.programid, "
1523 " LOCATE('/', recorded.programid))) "
1524 " AND "
1525 " (((:DUPMETHOD1 & 0x02) = 0) OR (:SUBTITLE1 <> '' "
1526 " AND :SUBTITLE2 = recorded.subtitle)) "
1527 " AND "
1528 " (((:DUPMETHOD2 & 0x04) = 0) OR (:DESCRIPTION1 <> '' "
1529 " AND :DESCRIPTION2 = recorded.description)) "
1530 " AND "
1531 " (((:DUPMETHOD3 & 0x08) = 0) OR "
1532 " (:SUBTITLE3 <> '' AND "
1533 " (:SUBTITLE4 = recorded.subtitle OR "
1534 " (recorded.subtitle = '' AND "
1535 " :SUBTITLE5 = recorded.description))) OR "
1536 " (:SUBTITLE6 = '' AND :DESCRIPTION3 <> '' AND "
1537 " (:DESCRIPTION4 = recorded.subtitle OR "
1538 " (recorded.subtitle = '' AND "
1539 " :DESCRIPTION5 = recorded.description)))) "
1540 " ) "
1541 " )" );
1542 result.bindValue(":TITLE", m_title);
1543 result.bindValueNoNull(":SUBTITLE1", m_subtitle);
1544 result.bindValueNoNull(":SUBTITLE2", m_subtitle);
1545 result.bindValueNoNull(":SUBTITLE3", m_subtitle);
1546 result.bindValueNoNull(":SUBTITLE4", m_subtitle);
1547 result.bindValueNoNull(":SUBTITLE5", m_subtitle);
1548 result.bindValueNoNull(":SUBTITLE6", m_subtitle);
1549 result.bindValueNoNull(":DESCRIPTION1", m_description);
1550 result.bindValueNoNull(":DESCRIPTION2", m_description);
1551 result.bindValueNoNull(":DESCRIPTION3", m_description);
1552 result.bindValueNoNull(":DESCRIPTION4", m_description);
1553 result.bindValueNoNull(":DESCRIPTION5", m_description);
1554 result.bindValueNoNull(":PROGRAMID1", m_programId);
1555 result.bindValueNoNull(":PROGRAMID2", m_programId);
1556 result.bindValueNoNull(":PROGRAMID3", m_programId);
1557 result.bindValueNoNull(":PROGRAMID4", m_programId);
1558 result.bindValueNoNull(":PROGRAMID5", m_programId);
1559 result.bindValue(":DUPMETHOD1", dmeth);
1560 result.bindValue(":DUPMETHOD2", dmeth);
1561 result.bindValue(":DUPMETHOD3", dmeth);
1562
1563 if (!result.exec())
1564 MythDB::DBError("forgetRecorded2", result);
1565 }
1566
1567 // Handle this specific entry in oldrecorded.
1568 result.prepare("UPDATE oldrecorded SET duplicate = 0 "
1569 "WHERE station = :STATION "
1570 "AND starttime = :STARTTIME "
1571 "AND title = :TITLE;");
1572 result.bindValue(":STARTTIME", m_startTs);
1573 result.bindValue(":TITLE", m_title);
1574 result.bindValue(":STATION", m_chanSign);
1575
1576 if (!result.exec())
1577 MythDB::DBError("forgetOldRecorded1", result);
1578
1579 // Handle other matching entries in oldrecorded.
1580 if (din & kDupsInOldRecorded)
1581 {
1582 result.prepare(
1583 "UPDATE oldrecorded SET duplicate = 0 "
1584 "WHERE duplicate = 1 AND "
1585 " title = :TITLE AND "
1586 " ( "
1587 " (:PROGRAMID1 <> '' AND "
1588 " :PROGRAMID2 = oldrecorded.programid) "
1589 " OR "
1590 " ( "
1591 " (:PROGRAMID3 = '' OR oldrecorded.programid = '' OR "
1592 " LEFT(:PROGRAMID4, LOCATE('/', :PROGRAMID5)) <> "
1593 " LEFT(oldrecorded.programid, "
1594 " LOCATE('/', oldrecorded.programid))) "
1595 " AND "
1596 " (((:DUPMETHOD1 & 0x02) = 0) OR (:SUBTITLE1 <> '' "
1597 " AND :SUBTITLE2 = oldrecorded.subtitle)) "
1598 " AND "
1599 " (((:DUPMETHOD2 & 0x04) = 0) OR (:DESCRIPTION1 <> '' "
1600 " AND :DESCRIPTION2 = oldrecorded.description)) "
1601 " AND "
1602 " (((:DUPMETHOD3 & 0x08) = 0) OR "
1603 " (:SUBTITLE3 <> '' AND "
1604 " (:SUBTITLE4 = oldrecorded.subtitle OR "
1605 " (oldrecorded.subtitle = '' AND "
1606 " :SUBTITLE5 = oldrecorded.description))) OR "
1607 " (:SUBTITLE6 = '' AND :DESCRIPTION3 <> '' AND "
1608 " (:DESCRIPTION4 = oldrecorded.subtitle OR "
1609 " (oldrecorded.subtitle = '' AND "
1610 " :DESCRIPTION5 = oldrecorded.description)))) "
1611 " ) "
1612 " )" );
1613 result.bindValue(":TITLE", m_title);
1614 result.bindValueNoNull(":SUBTITLE1", m_subtitle);
1615 result.bindValueNoNull(":SUBTITLE2", m_subtitle);
1616 result.bindValueNoNull(":SUBTITLE3", m_subtitle);
1617 result.bindValueNoNull(":SUBTITLE4", m_subtitle);
1618 result.bindValueNoNull(":SUBTITLE5", m_subtitle);
1619 result.bindValueNoNull(":SUBTITLE6", m_subtitle);
1620 result.bindValueNoNull(":DESCRIPTION1", m_description);
1621 result.bindValueNoNull(":DESCRIPTION2", m_description);
1622 result.bindValueNoNull(":DESCRIPTION3", m_description);
1623 result.bindValueNoNull(":DESCRIPTION4", m_description);
1624 result.bindValueNoNull(":DESCRIPTION5", m_description);
1625 result.bindValueNoNull(":PROGRAMID1", m_programId);
1626 result.bindValueNoNull(":PROGRAMID2", m_programId);
1627 result.bindValueNoNull(":PROGRAMID3", m_programId);
1628 result.bindValueNoNull(":PROGRAMID4", m_programId);
1629 result.bindValueNoNull(":PROGRAMID5", m_programId);
1630 result.bindValue(":DUPMETHOD1", dmeth);
1631 result.bindValue(":DUPMETHOD2", dmeth);
1632 result.bindValue(":DUPMETHOD3", dmeth);
1633
1634 if (!result.exec())
1635 MythDB::DBError("forgetOldRecorded2", result);
1636 }
1637
1638 // Remove any never records which aren't need anymore.
1639 result.prepare("DELETE FROM oldrecorded "
1640 "WHERE recstatus = :NEVER AND duplicate = 0");
1641 result.bindValue(":NEVER", RecStatus::NeverRecord);
1642
1643 if (!result.exec())
1644 MythDB::DBError("forgetNeverHistory", result);
1645
1646 // Handle matching entries in oldfind.
1647 if (m_findId)
1648 {
1649 result.prepare("DELETE FROM oldfind WHERE "
1650 "recordid = :RECORDID AND findid = :FINDID");
1651 result.bindValue(":RECORDID", erecid);
1652 result.bindValue(":FINDID", m_findId);
1653
1654 if (!result.exec())
1655 MythDB::DBError("forgetFindHistory", result);
1656 }
1657
1658 // The removal of an entry from oldrecorded may affect near-future
1659 // scheduling decisions, so recalculate
1660 ScheduledRecording::RescheduleCheck(*this, "ForgetHistory");
1661}
1662
1667{
1668 MSqlQuery result(MSqlQuery::InitCon());
1669
1670 result.prepare("UPDATE oldrecorded SET duplicate = 1 "
1671 "WHERE future = 0 AND duplicate = 0 "
1672 "AND title = :TITLE AND "
1673 "((programid = '' AND subtitle = :SUBTITLE"
1674 " AND description = :DESC) OR "
1675 " (programid <> '' AND programid = :PROGRAMID) OR "
1676 " (findid <> 0 AND findid = :FINDID))");
1677 result.bindValue(":TITLE", m_title);
1678 result.bindValueNoNull(":SUBTITLE", m_subtitle);
1679 result.bindValueNoNull(":DESC", m_description);
1680 result.bindValueNoNull(":PROGRAMID", m_programId);
1681 result.bindValue(":FINDID", m_findId);
1682
1683 if (!result.exec())
1684 MythDB::DBError("setDupHistory", result);
1685
1686 ScheduledRecording::RescheduleCheck(*this, "SetHistory");
1687}
1688
1694{
1695 str.replace("%RECID%", QString::number(getRecordID()));
1696 str.replace("%PARENTID%", QString::number(m_parentId));
1697 str.replace("%FINDID%", QString::number(m_findId));
1698 str.replace("%RECSTATUS%", QString::number(m_recStatus));
1699 str.replace("%RECTYPE%", QString::number(m_recType));
1700 str.replace("%REACTIVATE%", IsReactivated() ? "1" : "0");
1701 str.replace("%INPUTNAME%", GetInputName());
1702 str.replace("%CHANNUM%", GetChanNum());
1703
1705}
1706
1710uint RecordingInfo::GetRecgroupID(const QString& recGroup)
1711{
1713
1714 query.prepare("SELECT recgroupid FROM recgroups WHERE recgroup = :RECGROUP");
1715 query.bindValueNoNull(":RECGROUP", recGroup);
1716
1717 if (!query.exec())
1718 MythDB::DBError("RecGroup update", query);
1719
1720 if (!query.next())
1721 return 0;
1722
1723 return query.value(0).toUInt();
1724}
1725
1730{
1732
1733 query.prepare("SELECT recgroup FROM recgroups WHERE recgroupid = :RECGROUPID");
1734 query.bindValue(":RECGROUPID", recGroupID);
1735 if (!query.exec() || !query.next())
1736 {
1737 MythDB::DBError("GetRecgroupString()", query);
1738 return {};
1739 }
1740 return query.value(0).toString();
1741}
1742
1744{
1745 if (!m_recordingFile)
1746 {
1748 if (m_recordedId > 0)
1749 {
1752 }
1753 }
1754}
1755
1757{
1758 if (!GetRecordingFile())
1760 GetRecordingFile()->m_fileSize = fsize;
1761 GetRecordingFile()->Save(); // Ideally this would be called just the once when all metadata is gathered
1762
1764
1765 ProgramInfo::SaveFilesize(fsize); // Temporary
1766}
1767
1768void RecordingInfo::SetFilesize(uint64_t fsize)
1769{
1770 if (!GetRecordingFile())
1772 GetRecordingFile()->m_fileSize = fsize;
1774
1775 // Make sure the old storage location is updated for now
1777}
1778
1779uint64_t RecordingInfo::GetFilesize(void) const
1780{
1782 return GetRecordingFile()->m_fileSize;
1783
1784 // Temporary fallback to reading from old storage location
1785 return ProgramInfo::GetFilesize();
1786}
1787
1788
1789/* vim: set expandtab tabstop=4 shiftwidth=4: */
iterator begin(void)
bool empty(void) const
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:128
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:837
QVariant value(int i) const
Definition: mythdbcon.h:204
int numRowsAffected() const
Definition: mythdbcon.h:217
bool isActive(void) const
Definition: mythdbcon.h:215
void bindValueNoNull(const QString &placeholder, const QVariant &val)
Add a single binding, taking care not to set a NULL value.
Definition: mythdbcon.cpp:902
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:618
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:888
QVariant lastInsertId()
Return the id of the last inserted row.
Definition: mythdbcon.cpp:935
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:812
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:550
QString GetHostName(void)
QString GetSetting(const QString &key, const QString &defaultval="")
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:226
void insert(uint recordedid, PIAction action, uint64_t filesize=0ULL)
Holds information on recordings and videos.
Definition: programinfo.h:68
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:373
QString GetBasename(void) const
Definition: programinfo.h:345
uint32_t m_sourceId
Definition: programinfo.h:819
static ProgramInfoUpdater * s_updater
Definition: programinfo.h:859
virtual void clear(void)
VideoPropsType m_videoProperties
Definition: programinfo.h:824
QString m_hostname
Definition: programinfo.h:793
QString m_storageGroup
Definition: programinfo.h:794
QString toString(Verbosity v=kLongDescription, const QString &sep=":", const QString &grp="\"") const
QString m_pathname
Definition: programinfo.h:791
uint8_t m_dupIn
Definition: programinfo.h:833
uint32_t m_programFlags
ProgramFlag.
Definition: programinfo.h:823
virtual void SaveFilesize(uint64_t fsize)
Sets recording file size in database, and sets "filesize" field.
QString m_category
Definition: programinfo.h:777
QString m_chanPlaybackFilters
Definition: programinfo.h:786
uint32_t m_inputId
Definition: programinfo.h:820
uint m_recordedId
Definition: programinfo.h:836
QDateTime m_recEndTs
Definition: programinfo.h:806
uint16_t m_partTotal
Definition: programinfo.h:829
QDateTime GetScheduledEndTime(void) const
The scheduled end time of the program.
Definition: programinfo.h:398
uint32_t m_chanId
Definition: programinfo.h:782
void SetRecordingStatus(RecStatus::Type status)
Definition: programinfo.h:585
void SetScheduledStartTime(const QDateTime &dt)
Definition: programinfo.h:528
uint8_t m_dupMethod
Definition: programinfo.h:834
void SendAddedEvent(void) const
Sends event out that the ProgramInfo should be added to lists.
uint64_t m_fileSize
Definition: programinfo.h:801
bool IsRepeat(void) const
Definition: programinfo.h:492
void SaveTotalDuration(std::chrono::milliseconds duration)
Store the Total Duration at frame 0 in the recordedmarkup table.
QString CreateRecordBasename(const QString &ext) const
Returns a filename for a recording based on the recording channel and date.
QString GetHostname(void) const
Definition: programinfo.h:422
static QStringList LoadFromScheduler(const QString &tmptable, int recordid)
virtual void SetFilesize(uint64_t sz)
QString m_title
Definition: programinfo.h:768
bool IsReactivated(void) const
Definition: programinfo.h:494
void SetScheduledEndTime(const QDateTime &dt)
Definition: programinfo.h:529
QString GetStorageGroup(void) const
Definition: programinfo.h:423
QString GetTitle(void) const
Definition: programinfo.h:362
uint m_episode
Definition: programinfo.h:774
AudioPropsType m_audioProperties
Definition: programinfo.h:825
bool IsGeneric(void) const
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
Definition: programinfo.h:405
uint32_t m_findId
Definition: programinfo.h:821
QString m_description
Definition: programinfo.h:772
QDateTime GetScheduledStartTime(void) const
The scheduled start time of program.
Definition: programinfo.h:391
CategoryType m_catType
Definition: programinfo.h:799
QString m_seriesId
Definition: programinfo.h:796
QString m_syndicatedEpisode
Definition: programinfo.h:776
float m_stars
Rating, range [0..1].
Definition: programinfo.h:808
QString GetChanNum(void) const
This is the channel "number", in the form 1, 1_2, 1-2, 1#1, etc.
Definition: programinfo.h:377
int32_t m_recPriority
Definition: programinfo.h:780
QDateTime m_recStartTs
Definition: programinfo.h:805
QString GetInputName(void) const
Definition: programinfo.h:468
virtual void SubstituteMatches(QString &str)
Subsitute MATCH% type variable names in the given string.
QDateTime m_endTs
Definition: programinfo.h:804
SubtitlePropsType m_subtitleProperties
Definition: programinfo.h:826
QString m_programId
Definition: programinfo.h:797
QString m_chanStr
Definition: programinfo.h:783
uint16_t m_year
Definition: programinfo.h:827
QString m_chanSign
Definition: programinfo.h:784
QDate m_originalAirDate
Definition: programinfo.h:809
uint32_t m_recordId
Definition: programinfo.h:816
QString m_chanName
Definition: programinfo.h:785
uint32_t m_parentId
Definition: programinfo.h:817
QDateTime m_lastModified
Definition: programinfo.h:810
virtual uint64_t GetFilesize(void) const
void SetRecordingEndTime(const QDateTime &dt)
Definition: programinfo.h:531
virtual void clone(const ProgramInfo &other, bool ignore_non_serialized_data=false)
Copies important fields from other ProgramInfo.
QString m_subtitle
Definition: programinfo.h:770
RecStatus::Type GetRecordingStatus(void) const
Definition: programinfo.h:451
void SetReactivated(bool reactivate)
Definition: programinfo.h:546
int8_t m_recStatus
Definition: programinfo.h:831
QDateTime m_startTs
Definition: programinfo.h:803
void SendUpdateEvent(void) const
Sends event out that the ProgramInfo should be reloaded.
QString m_recGroup
Definition: programinfo.h:788
uint8_t m_recType
Definition: programinfo.h:832
QString m_inetRef
Definition: programinfo.h:798
uint16_t m_partNumber
Definition: programinfo.h:828
QString m_playGroup
Definition: programinfo.h:789
static QMutex s_staticDataLock
Definition: programinfo.h:858
Holds information on a recording file and it's video and audio streams.
Definition: recordingfile.h:30
QString m_storageDeviceID
Definition: recordingfile.h:40
QString m_storageGroup
Definition: recordingfile.h:41
QString m_fileName
Definition: recordingfile.h:44
uint64_t m_fileSize
Definition: recordingfile.h:45
Holds information on a TV Program one might wish to record.
Definition: recordinginfo.h:36
~RecordingInfo() override
Destructor deletes "record" if it exists.
QDateTime m_desiredRecStartTs
class RecordingRule * m_record
void SaveFilesize(uint64_t fsize) override
Sets recording file size in database, and sets "filesize" field.
void FinishedRecording(bool allowReRecord)
If not a premature stop, adds program to history of recorded programs.
void ApplyRecordPlayGroupChange(const QString &newplaygroup)
Sets the recording group, both in this RecordingInfo and in the database.
void SetDupHistory(void)
Set the duplicate flag in oldrecorded.
static QString GetRecgroupString(uint recGroupID)
Temporary helper during transition from string to ID.
void InsertFile(void)
void ApplyRecordStateChange(RecordingType newstate, bool save=true)
Sets RecordingType of "record", creating "record" if it does not exist.
void ApplyOriginalAirDateChange(QDate originalairdate)
RecordingInfo(void)
Definition: recordinginfo.h:38
void ApplyNeverRecord(void)
Set this program to never be recorded by inserting 'history' for it into the database with a status o...
void SubstituteMatches(QString &str) override
Replace MATCH% vars in the specified string.
void clear(void) override
RecordingRule * GetRecordingRule(void)
Returns the "record" field, creating it if necessary.
void ForgetHistory(void)
Forget the recording of a program so it will be recorded again.
bool InsertRecording(const QString &ext, bool force_match=false)
static bool QueryRecordedIdForKey(int &recordedid, uint chanid, const QDateTime &recstartts)
void ApplyRecordRecTitleChange(const QString &newTitle, const QString &newSubtitle, const QString &newDescription)
Sets the recording title, subtitle, and description both in this RecordingInfo and in the database.
static QString s_unknownTitle
RecStatus::Type m_oldrecstatus
void QuickRecord(void)
Create a kSingleRecord if not already scheduled.
void ApplyRecordRecGroupChange(const QString &newrecgroup)
Sets the recording group, both in this RecordingInfo and in the database.
void ReactivateRecording(void)
Asks the scheduler to restart this recording if possible.
static uint GetRecgroupID(const QString &recGroup)
Temporary helper during transition from string to ID.
static bool InsertProgram(RecordingInfo *pg, const RecordingRule *rule)
static const QRegularExpression kReLeadingAnd
int getRecordID(void)
Returns a record id, creating "record" it if necessary.
void ApplyStarsChange(float newstarsvalue)
Sets the stars value in the database.
QString GetProgramRecordingProfile(void) const
Returns recording profile name that will be, or was used, for this program, creating "record" field i...
int GetAutoRunJobs(void) const
Returns a bitmap of which jobs are attached to this RecordingInfo.
static const QRegularExpression kReSearchTypeName
void AddHistory(bool resched=true, bool forcedup=false, bool future=false)
Adds recording history, creating "record" it if necessary.
void ApplyRecordRecID(void)
Sets recordid to match RecordingRule recordid.
void SetFilesize(uint64_t fsize) override
void ApplyStorageGroupChange(const QString &newstoragegroup)
Sets the storage group, both in this RecordingInfo and in the database.
void UpdateRecordingEnd(void)
Update information in the recorded table when the end-time of a recording is changed.
void ApplyTranscoderProfileChangeById(int id)
uint64_t GetFilesize(void) const override
RecStatus::Type m_savedrecstatus
RecordingType GetProgramRecordingStatus(void)
Returns the recording type for this RecordingInfo, creating "record" field if necessary.
void DeleteHistory(void)
Deletes recording history, creating "record" it if necessary.
void ApplyRecordRecPriorityChange(int newrecpriority)
Sets recording priority of "record", creating "record" if it does not exist.
void LoadRecordingFile()
QDateTime m_desiredRecEndTs
RecordingFile * m_recordingFile
void StartedRecording(const QString &ext)
Inserts this RecordingInfo into the database as an existing recording.
void SetRecordingID(uint _recordedid) override
virtual void clone(const RecordingInfo &other, bool ignore_non_serialized_data=false)
Copies important fields from other RecordingInfo.
RecordingFile * GetRecordingFile() const
void ApplyTranscoderProfileChange(const QString &profile) const
Sets the transcoder profile for a recording.
Internal representation of a recording rule, mirrors the record table.
Definition: recordingrule.h:30
RecordingType m_type
bool LoadByProgram(const ProgramInfo *proginfo)
int m_recordID
Unique Recording Rule ID.
Definition: recordingrule.h:71
QString m_recProfile
bool MakeOverride(void)
bool Save(bool sendSig=true)
bool Delete(bool sendSig=true)
bool m_autoMetadataLookup
static void RescheduleCheck(const RecordingInfo &recinfo, const QString &why)
static void ReschedulePlace(const QString &why)
unsigned int uint
Definition: freesurround.h:24
@ JOB_USERJOB3
Definition: jobqueue.h:86
@ JOB_METADATA
Definition: jobqueue.h:80
@ JOB_USERJOB1
Definition: jobqueue.h:84
@ JOB_USERJOB2
Definition: jobqueue.h:85
@ JOB_COMMFLAG
Definition: jobqueue.h:79
@ JOB_USERJOB4
Definition: jobqueue.h:87
@ JOB_TRANSCODE
Definition: jobqueue.h:78
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QMap< QString, QVariant > MSqlBindings
typedef for a map of string -> string bindings for generic queries.
Definition: mythdbcon.h:100
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
@ ISODate
Default UTC.
Definition: mythdate.h:17
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
bool LoadFromProgram(ProgramList &destination, const QString &where, const QString &groupBy, const QString &orderBy, const MSqlBindings &bindings, const ProgramList &schedList)
@ kPIUpdateFileSize
@ COMM_DETECT_COMMFREE
Definition: programtypes.h:128
static eu8 clamp(eu8 value, eu8 low, eu8 high)
Definition: pxsup2dast.c:206
#define LOC
static const uint kUnknownProgramLength
RecordingDupInType
@ kDupsInAll
@ kDupsUnset
@ kDupsInRecorded
@ kDupsInOldRecorded
RecordingType
@ kNotRecording
@ kOverrideRecord
@ kSingleRecord
@ kDontRecord
RecordingDupMethodType
@ kDupCheckUnset
@ kDupCheckSubThenDesc