MythTV master
recordingrule.cpp
Go to the documentation of this file.
1
2#include "recordingrule.h"
3
4#include <utility>
5
6#include <QTimeZone>
7
8// libmythbase
11#include "libmythbase/mythdb.h"
13
14// libmythtv
15#include "scheduledrecording.h" // For RescheduleMatch()
16#include "playgroup.h" // For GetInitialName()
17#include "recordingprofile.h" // For constants
18
19static inline QString null_to_empty(const QString &str)
20{
21 return str.isEmpty() ? "" : str;
22}
23
24// If the GetNumSetting() calls here are ever removed, update schema
25// upgrade 1302 in dbcheck.cpp to manually apply them to the Default
26// template. Failing to do so will cause users upgrading from older
27// versions to lose those settings.
28
30 : m_findtime(QTime::fromString("00:00:00", Qt::ISODate)),
31 m_findid(QDate(1970, 1, 1).daysTo(MythDate::current().toLocalTime().date())
32 + 719528)
33{
34 QDateTime dt = MythDate::current();
35 m_enddate = m_startdate = dt.date();
36 m_endtime = m_starttime = dt.time();
37
39}
40
45{
46 std::shared_ptr<MythSortHelper>sh = getMythSortHelper();
47
48 if (m_sortTitle.isEmpty() and not m_title.isEmpty())
49 m_sortTitle = sh->doTitle(m_title);
50 if (m_sortSubtitle.isEmpty() and not m_subtitle.isEmpty())
51 m_sortSubtitle = sh->doTitle(m_subtitle);
52}
53
57bool RecordingRule::Load(bool asTemplate)
58{
59 if (m_recordID <= 0)
60 return false;
61
63 query.prepare("SELECT type, search, " // 00-01
64 "recpriority, prefinput, startoffset, endoffset, dupmethod, dupin, " // 02-07
65 "inactive, profile, recgroup, storagegroup, playgroup, autoexpire, " // 08-13
66 "maxepisodes, maxnewest, autocommflag, autotranscode, transcoder, " // 14-18
67 "autouserjob1, autouserjob2, autouserjob3, autouserjob4, " // 19-22
68 "autometadata, parentid, title, subtitle, description, season, episode, " // 23-29
69 "category, starttime, startdate, endtime, enddate, seriesid, programid, " // 30-36
70 "inetref, chanid, station, findday, findtime, findid, " // 37-42
71 "next_record, last_record, last_delete, avg_delay, filter, recgroupid, " // 43-48
72 "autoextend " // 49
73 "FROM record WHERE recordid = :RECORDID ;");
74
75 query.bindValue(":RECORDID", m_recordID);
76
77 if (!query.exec())
78 {
79 MythDB::DBError("SELECT record", query);
80 return false;
81 }
82
83 if (!query.next())
84 return false;
85
86 // Schedule
87 if (!asTemplate)
88 {
89 m_type = static_cast<RecordingType>(query.value(0).toInt());
90 m_searchType = static_cast<RecSearchType>(query.value(1).toInt());
91 }
92 m_recPriority = query.value(2).toInt();
93 m_prefInput = query.value(3).toInt();
94 m_startOffset = query.value(4).toInt();
95 m_endOffset = query.value(5).toInt();
97 (query.value(6).toInt());
98 m_dupIn = static_cast<RecordingDupInType>(query.value(7).toInt());
99 m_filter = query.value(47).toUInt();
100 m_isInactive = query.value(8).toBool();
101 m_autoExtend = static_cast<AutoExtendType>(query.value(49).toUInt());
102
103 // Storage
104 m_recProfile = query.value(9).toString();
105 m_recGroupID = query.value(48).toUInt();
106 m_storageGroup = query.value(11).toString();
107 m_playGroup = query.value(12).toString();
108 m_autoExpire = query.value(13).toBool();
109 m_maxEpisodes = query.value(14).toInt();
110 m_maxNewest = query.value(15).toBool();
111
112 // Post Process
113 m_autoCommFlag = query.value(16).toBool();
114 m_autoTranscode = query.value(17).toBool();
115 m_transcoder = query.value(18).toInt();
116 m_autoUserJob1 = query.value(19).toBool();
117 m_autoUserJob2 = query.value(20).toBool();
118 m_autoUserJob3 = query.value(21).toBool();
119 m_autoUserJob4 = query.value(22).toBool();
120 m_autoMetadataLookup = query.value(23).toBool();
121
122 if (!asTemplate)
123 {
124 // Original rule id for override rule
125 m_parentRecID = query.value(24).toInt();
126
127 // Recording metadata
128 m_title = query.value(25).toString();
129 m_subtitle = query.value(26).toString();
130 m_description = query.value(27).toString();
131 m_season = query.value(28).toUInt();
132 m_episode = query.value(29).toUInt();
133 m_category = query.value(30).toString();
134 m_starttime = query.value(31).toTime();
135 m_startdate = query.value(32).toDate();
136 m_endtime = query.value(33).toTime();
137 m_enddate = query.value(34).toDate();
138 m_seriesid = query.value(35).toString();
139 m_programid = query.value(36).toString();
140 m_inetref = query.value(37).toString();
141
142 // Associated data for rule types
143 m_channelid = query.value(38).toInt();
144 m_station = query.value(39).toString();
145 m_findday = query.value(40).toInt();
146 m_findtime = query.value(41).toTime();
147 m_findid = query.value(42).toInt();
148
149 // Statistic fields - Used to generate statistics about particular rules
150 // and influence watch list weighting
151 m_nextRecording = MythDate::as_utc(query.value(43).toDateTime());
152 m_lastRecorded = MythDate::as_utc(query.value(44).toDateTime());
153 m_lastDeleted = MythDate::as_utc(query.value(45).toDateTime());
154 m_averageDelay = query.value(46).toInt();
155 }
156
159 m_template = (asTemplate || m_isTemplate) ?
160 query.value(30).toString() : "";
161
162 if (!asTemplate)
163 m_loaded = true;
164
166 return true;
167}
168
170{
171 if (!proginfo)
172 return false;
173
174 m_progInfo = proginfo;
175
176 m_recordID = proginfo->GetRecordingRuleID();
177 if (m_recordID)
178 {
179 if (!Load())
180 return false;
181 }
182 else
183 {
184 LoadTemplate(proginfo->GetTitle(), proginfo->GetCategory(),
185 proginfo->GetCategoryTypeString());
186 }
187
188 if (m_type != kTemplateRecord &&
190 {
192 if (!proginfo->GetRecordingRuleID())
194 }
195
197 m_loaded = true;
198 return true;
199}
200
217bool RecordingRule::LoadBySearch(RecSearchType lsearch, const QString& textname,
218 const QString& forwhat, QString joininfo,
219 ProgramInfo *pginfo)
220{
222
223 int rid = 0;
224 query.prepare("SELECT recordid FROM record WHERE "
225 "search = :SEARCH AND description LIKE :FORWHAT");
226 query.bindValue(":SEARCH", lsearch);
227 query.bindValue(":FORWHAT", forwhat);
228
229 if (query.exec())
230 {
231 if (query.next())
232 rid = query.value(0).toInt();
233 // else rid is zero, which is valid, we're looking at a new rule
234 }
235 else
236 {
237 MythDB::DBError("loadBySearch", query);
238 return false;
239 }
240
241 if (rid)
242 {
243 m_recordID = rid;
244 if (!Load())
245 return false;
246 }
247 else
248 {
249 LoadTemplate("Default");
250
251 QString searchType;
252 m_searchType = lsearch;
253 searchType = SearchTypeToString(m_searchType);
254
255 QString ltitle = QString("%1 (%2)").arg(textname, searchType);
256 m_title = ltitle;
257 m_sortTitle = nullptr;
258 m_subtitle = m_sortSubtitle = std::move(joininfo);
259 m_description = forwhat;
260
261 if (pginfo)
262 {
263 m_findday =
264 (pginfo->GetScheduledStartTime().toLocalTime().date()
265 .dayOfWeek() + 1) % 7;
266 m_findtime = pginfo->GetScheduledStartTime().toLocalTime().time();
267 m_findid = QDate(1970, 1, 1).daysTo(
268 pginfo->GetScheduledStartTime().toLocalTime().date()) + 719528;
269 }
270 }
271
273 m_loaded = true;
274 return true;
275}
276
277bool RecordingRule::LoadTemplate(const QString& title,
278 const QString& category,
279 const QString& categoryType)
280{
281 QString lcategory = category.isEmpty() ? "Default" : category;
282 QString lcategoryType = categoryType.isEmpty() ? "Default" : categoryType;
283
285 query.prepare("SELECT recordid, category, "
286 " (category = :TITLE1) AS titlematch, "
287 " (category = :CAT1) AS catmatch, "
288 " (category = :CATTYPE1) AS typematch "
289 "FROM record "
290 "WHERE type = :TEMPLATE AND "
291 " (category IN (:TITLE2, :CAT2, :CATTYPE2) "
292 " OR category = 'Default') "
293 "ORDER BY titlematch DESC, catmatch DESC, typematch DESC"
294 );
295 query.bindValue(":TEMPLATE", kTemplateRecord);
296 query.bindValue(":TITLE1", title);
297 query.bindValue(":TITLE2", title);
298 query.bindValue(":CAT1", lcategory);
299 query.bindValue(":CAT2", lcategory);
300 query.bindValue(":CATTYPE1", lcategoryType);
301 query.bindValue(":CATTYPE2", lcategoryType);
302
303 if (!query.exec())
304 {
305 MythDB::DBError("LoadByTemplate", query);
306 return false;
307 }
308
309 if (!query.next())
310 return false;
311
312 int savedRecordID = m_recordID;
313 m_recordID = query.value(0).toInt();
314 bool result = Load(true);
315 m_recordID = savedRecordID;
316
317 return result;
318}
319
320bool RecordingRule::MakeTemplate(QString category)
321{
322 if (m_recordID > 0)
323 return false;
324
325 if (category.compare(tr("Default"), Qt::CaseInsensitive) == 0)
326 {
327 category = "Default";
328 m_title = tr("Default (Template)");
330 }
331 else
332 {
333 //: %1 is the category
334 m_title = tr("%1 (Template)").arg(category);
336 }
337
338 LoadTemplate(category);
339 m_recordID = 0;
341 m_category = category;
342 m_loaded = true;
343 m_isTemplate = true;
344
345 return true;
346}
347
348bool RecordingRule::ModifyPowerSearchByID(int rid, const QString& textname,
349 QString forwhat, QString joininfo)
350{
351 if (rid <= 0)
352 return false;
353
354 m_recordID = rid;
355 if (!Load() || m_searchType != kPowerSearch)
356 return false;
357
358 QString ltitle = QString("%1 (%2)").arg(textname, tr("Power Search"));
359 m_title = ltitle;
360 m_sortTitle = nullptr;
361 m_subtitle = m_sortSubtitle = std::move(joininfo);
362 m_description = std::move(forwhat);
363
365 m_loaded = true;
366 return true;
367}
368
370{
371 if (m_recordID <= 0)
372 return false;
373
375 return false;
376
377 m_isOverride = true;
379 m_recordID = 0;
381 m_isInactive = false;
382
385
387
388 return true;
389}
390
391bool RecordingRule::Save(bool sendSig)
392{
393 QString sql = "SET type = :TYPE, search = :SEARCHTYPE, "
394 "recpriority = :RECPRIORITY, prefinput = :INPUT, "
395 "startoffset = :STARTOFFSET, endoffset = :ENDOFFSET, "
396 "dupmethod = :DUPMETHOD, dupin = :DUPIN, "
397 "filter = :FILTER, autoextend = :AUTOEXTEND, "
398 "inactive = :INACTIVE, profile = :RECPROFILE, "
399 "recgroup = :RECGROUP, "
400 "recgroupid = :RECGROUPID, "
401 "storagegroup = :STORAGEGROUP, "
402 "playgroup = :PLAYGROUP, autoexpire = :AUTOEXPIRE, "
403 "maxepisodes = :MAXEPISODES, maxnewest = :MAXNEWEST, "
404 "autocommflag = :AUTOCOMMFLAG, "
405 "autotranscode = :AUTOTRANSCODE, "
406 "transcoder = :TRANSCODER, autouserjob1 = :AUTOUSERJOB1, "
407 "autouserjob2 = :AUTOUSERJOB2, "
408 "autouserjob3 = :AUTOUSERJOB3, "
409 "autouserjob4 = :AUTOUSERJOB4, "
410 "autometadata = :AUTOMETADATA, "
411 "parentid = :PARENTID, title = :TITLE, "
412 "subtitle = :SUBTITLE, season = :SEASON, episode = :EPISODE, "
413 "description = :DESCRIPTION, category = :CATEGORY, "
414 "starttime = :STARTTIME, startdate = :STARTDATE, "
415 "endtime = :ENDTIME, enddate = :ENDDATE, seriesid = :SERIESID, "
416 "programid = :PROGRAMID, inetref = :INETREF, chanid = :CHANID, "
417 "station = :STATION, findday = :FINDDAY, "
418 "findtime = :FINDTIME, findid = :FINDID, "
419 "next_record = :NEXTREC, last_record = :LASTREC, "
420 "last_delete = :LASTDELETE, avg_delay = :AVGDELAY ";
421
422 QString sqlquery;
423 if (m_recordID > 0 || (m_recordTable != "record" && m_tempID > 0))
424 {
425 sqlquery = QString("UPDATE %1 %2 WHERE recordid = :RECORDID;")
426 .arg(m_recordTable, sql);
427 }
428 else
429 {
430 sqlquery = QString("INSERT INTO %1 %2;").arg(m_recordTable, sql);
431 }
432
434 query.prepare(sqlquery);
435 query.bindValue(":TYPE", m_type);
436 query.bindValue(":SEARCHTYPE", m_searchType);
437 query.bindValue(":RECPRIORITY", m_recPriority);
438 query.bindValue(":INPUT", m_prefInput);
439 query.bindValue(":STARTOFFSET", m_startOffset);
440 query.bindValue(":ENDOFFSET", m_endOffset);
441 query.bindValue(":DUPMETHOD", m_dupMethod);
442 query.bindValue(":DUPIN", m_dupIn);
443 query.bindValue(":FILTER", m_filter);
444 query.bindValue(":AUTOEXTEND", static_cast<int>(m_autoExtend));
445 query.bindValue(":INACTIVE", m_isInactive);
446 query.bindValue(":RECPROFILE", null_to_empty(m_recProfile));
447 // Temporary, remove once transition to recgroupid is complete
449 query.bindValue(":RECGROUPID", m_recGroupID);
450 query.bindValue(":STORAGEGROUP", null_to_empty(m_storageGroup));
451 query.bindValue(":PLAYGROUP", null_to_empty(m_playGroup));
452 query.bindValue(":AUTOEXPIRE", m_autoExpire);
453 query.bindValue(":MAXEPISODES", m_maxEpisodes);
454 query.bindValue(":MAXNEWEST", m_maxNewest);
455 query.bindValue(":AUTOCOMMFLAG", m_autoCommFlag);
456 query.bindValue(":AUTOTRANSCODE", m_autoTranscode);
457 query.bindValue(":TRANSCODER", m_transcoder);
458 query.bindValue(":AUTOUSERJOB1", m_autoUserJob1);
459 query.bindValue(":AUTOUSERJOB2", m_autoUserJob2);
460 query.bindValue(":AUTOUSERJOB3", m_autoUserJob3);
461 query.bindValue(":AUTOUSERJOB4", m_autoUserJob4);
462 query.bindValue(":AUTOMETADATA", m_autoMetadataLookup);
463 query.bindValue(":PARENTID", m_parentRecID);
464 query.bindValue(":TITLE", m_title);
465 query.bindValue(":SUBTITLE", null_to_empty(m_subtitle));
466 query.bindValue(":DESCRIPTION", null_to_empty(m_description));
467 query.bindValue(":SEASON", m_season);
468 query.bindValue(":EPISODE", m_episode);
469 query.bindValue(":CATEGORY", null_to_empty(m_category));
470 query.bindValue(":STARTTIME", m_starttime);
471 query.bindValue(":STARTDATE", m_startdate);
472 query.bindValue(":ENDTIME", m_endtime);
473 query.bindValue(":ENDDATE", m_enddate);
474 query.bindValue(":SERIESID", null_to_empty(m_seriesid));
475 query.bindValue(":PROGRAMID", null_to_empty(m_programid));
476 query.bindValue(":INETREF", null_to_empty(m_inetref));
477 query.bindValue(":CHANID", m_channelid);
478 query.bindValue(":STATION", null_to_empty(m_station));
479 query.bindValue(":FINDDAY", m_findday);
480 query.bindValue(":FINDTIME", m_findtime);
481 query.bindValue(":FINDID", m_findid);
482 query.bindValue(":NEXTREC", m_nextRecording);
483 query.bindValue(":LASTREC", m_lastRecorded);
484 query.bindValue(":LASTDELETE", m_lastDeleted);
485 query.bindValue(":AVGDELAY", m_averageDelay);
486
487 if (m_recordTable != "record" && m_tempID > 0)
488 query.bindValue(":RECORDID", m_tempID);
489 else if (m_recordID > 0)
490 query.bindValue(":RECORDID", m_recordID);
491
492 if (!query.exec())
493 {
494 MythDB::DBError("UPDATE/INSERT record", query);
495 return false;
496 }
497
498 if (m_recordTable != "record" && m_tempID <= 0)
499 m_tempID = query.lastInsertId().toInt();
500 else if (m_recordID <= 0)
501 m_recordID = query.lastInsertId().toInt();
502
503 if (sendSig)
505 QString("SaveRule %1").arg(m_title));
506
507 return true;
508}
509
510bool RecordingRule::Delete(bool sendSig)
511{
512 if (m_recordID < 0)
513 return false;
514
516 query.prepare("DELETE FROM record WHERE recordid = :RECORDID");
517 query.bindValue(":RECORDID", m_recordID);
518 if (!query.exec())
519 {
520 MythDB::DBError("ScheduledRecording::remove -- record", query);
521 return false;
522 }
523
524 query.prepare("DELETE FROM oldfind WHERE recordid = :RECORDID");
525 query.bindValue(":RECORDID", m_recordID);
526 if (!query.exec())
527 {
528 MythDB::DBError("ScheduledRecording::remove -- oldfind", query);
529 }
530
532 {
533 query.prepare("DELETE FROM program WHERE manualid = :RECORDID");
534 query.bindValue(":RECORDID", m_recordID);
535 if (!query.exec())
536 {
537 MythDB::DBError("ScheduledRecording::remove -- oldfind", query);
538 }
539 }
540
541 if (sendSig)
543 QString("DeleteRule %1").arg(m_title));
544
545 // Set m_recordID to zero, the rule is no longer in the database so it's
546 // not valid. Should you want, this allows a rule to be removed from the
547 // database and then re-inserted with Save()
548 m_recordID = 0;
549
550 return true;
551}
552
553void RecordingRule::ToMap(InfoMap &infoMap, uint date_format) const
554{
555 if (m_title == "Default (Template)")
556 {
557 infoMap["title"] = tr("Default (Template)");
558 infoMap["sorttitle"] = tr("Default (Template)");
559 }
560 else
561 {
562 infoMap["title"] = m_title;
563 infoMap["sorttitle"] = m_sortTitle;
564 }
565 infoMap["subtitle"] = m_subtitle;
566 infoMap["sortsubtitle"] = m_sortSubtitle;
567 infoMap["description"] = m_description;
568 infoMap["season"] = QString::number(m_season);
569 infoMap["episode"] = QString::number(m_episode);
570
571 if (m_category == "Default")
572 infoMap["category"] = tr("Default", "category");
573 else
574 infoMap["category"] = m_category;
575 infoMap["callsign"] = m_station;
576
577#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
578 QDateTime starttm(m_startdate, m_starttime, Qt::UTC);
579#else
580 QDateTime starttm(m_startdate, m_starttime, QTimeZone(QTimeZone::UTC));
581#endif
582 infoMap["starttime"] = MythDate::toString(starttm, date_format | MythDate::kTime);
583 infoMap["startdate"] = MythDate::toString(
584 starttm, date_format | MythDate::kDateFull | MythDate::kSimplify);
585
586#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
587 QDateTime endtm(m_enddate, m_endtime, Qt::UTC);
588#else
589 QDateTime endtm(m_enddate, m_endtime, QTimeZone(QTimeZone::UTC));
590#endif
591 infoMap["endtime"] = MythDate::toString(endtm, date_format | MythDate::kTime);
592 infoMap["enddate"] = MythDate::toString(
593 endtm, date_format | MythDate::kDateFull | MythDate::kSimplify);
594
595 infoMap["inetref"] = m_inetref;
596 infoMap["chanid"] = QString::number(m_channelid);
597 infoMap["channel"] = m_station;
598
599#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
600 QDateTime startts(m_startdate, m_starttime, Qt::UTC);
601 QDateTime endts(m_enddate, m_endtime, Qt::UTC);
602#else
603 static const QTimeZone utc(QTimeZone::UTC);
604 QDateTime startts(m_startdate, m_starttime, utc);
605 QDateTime endts(m_enddate, m_endtime, utc);
606#endif
607
608 int seconds = startts.secsTo(endts);
609 int minutes = seconds / 60;
610 int hours = minutes / 60;
611 minutes = minutes % 60;
612
613 infoMap["lenmins"] = QCoreApplication::translate("(Common)", "%n minute(s)",
614 "", minutes);
615
616 QString minstring = QCoreApplication::translate("(Common)", "%n minute(s)",
617 "", minutes);
618
619 QString hourstring = QCoreApplication::translate("(Common)", "%n hour(s)",
620 "", hours);
621
622 if (hours > 0)
623 {
624 //: Time duration, %1 is replaced by the hours, %2 by the minutes
625 infoMap["lentime"] = QCoreApplication::translate("(Common)", "%1 %2",
626 "Hours and minutes").arg(hourstring, minstring);
627 }
628 else
629 {
630 infoMap["lentime"] = minstring;
631 }
632
633
634 infoMap["timedate"] = MythDate::toString(
635 startts, date_format | MythDate::kDateTimeFull | MythDate::kSimplify) + " - " +
636 MythDate::toString(endts, date_format | MythDate::kTime);
637
638 infoMap["shorttimedate"] =
640 startts, date_format | MythDate::kDateTimeShort | MythDate::kSimplify) + " - " +
641 MythDate::toString(endts, date_format | MythDate::kTime);
642
644 {
645#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
646 QDateTime ldt =
647 QDateTime(MythDate::current().toLocalTime().date(), m_findtime,
648 Qt::LocalTime);
649#else
650 QDateTime ldt =
651 QDateTime(MythDate::current().toLocalTime().date(), m_findtime, utc);
652#endif
653 QString findfrom = MythDate::toString(ldt, date_format | MythDate::kTime);
654 if (m_type == kWeeklyRecord)
655 {
656 int daynum = ((m_findday + 5) % 7) + 1;
657 findfrom = QString("%1, %2")
658 .arg(gCoreContext->GetQLocale().dayName(daynum, QLocale::ShortFormat),
659 findfrom);
660 }
661 infoMap["subtitle"] = tr("(%1 or later) %3",
662 "e.g. (Sunday or later) program "
663 "subtitle").arg(findfrom, m_subtitle);
664 }
665
666 infoMap["searchtype"] = SearchTypeToString(m_searchType);
667 if (m_searchType != kNoSearch)
668 infoMap["searchforwhat"] = m_description;
669
670 using namespace MythDate;
671 if (m_nextRecording.isValid())
672 {
673 infoMap["nextrecording"] = MythDate::toString(
674 m_nextRecording, date_format | kDateFull | kAddYear);
675 }
676 if (m_lastRecorded.isValid())
677 {
678 infoMap["lastrecorded"] = MythDate::toString(
679 m_lastRecorded, date_format | kDateFull | kAddYear);
680 }
681 if (m_lastDeleted.isValid())
682 {
683 infoMap["lastdeleted"] = MythDate::toString(
684 m_lastDeleted, date_format | kDateFull | kAddYear);
685 }
686
687 infoMap["ruletype"] = toString(m_type);
688 infoMap["rectype"] = toString(m_type);
689 infoMap["autoextend"] = toString(m_autoExtend);
690
691 if (m_template == "Default")
692 infoMap["template"] = tr("Default", "Default template");
693 else
694 infoMap["template"] = m_template;
695}
696
697void RecordingRule::UseTempTable(bool usetemp, const QString& table)
698{
700
701 if (usetemp)
702 {
703 m_recordTable = table;
704
705 query.prepare("SELECT GET_LOCK(:LOCK, 2);");
706 query.bindValue(":LOCK", "DiffSchedule");
707 if (!query.exec())
708 {
709 MythDB::DBError("Obtaining lock in testRecording", query);
710 return;
711 }
712
713 query.prepare(QString("DROP TABLE IF EXISTS %1;").arg(table));
714 if (!query.exec())
715 {
716 MythDB::DBError("Deleting old table in testRecording", query);
717 return;
718 }
719
720 query.prepare(QString("CREATE TABLE %1 SELECT * FROM record;")
721 .arg(table));
722 if (!query.exec())
723 {
724 MythDB::DBError("Create new temp table", query);
725 return;
726 }
727
728 query.prepare(QString("ALTER TABLE %1 MODIFY recordid int(10) "
729 "UNSIGNED NOT NULL AUTO_INCREMENT primary key;")
730 .arg(table));
731 if (!query.exec())
732 {
733 MythDB::DBError("Modify recordid column to include "
734 "auto-increment", query);
735 return;
736 }
737
738 if (m_recordID > 0)
740
741 Save(false);
742 }
743 else
744 {
745 query.prepare("SELECT RELEASE_LOCK(:LOCK);");
746 query.bindValue(":LOCK", "DiffSchedule");
747 if (!query.exec())
748 {
749 MythDB::DBError("Free lock", query);
750 return;
751 }
752 m_recordTable = "record";
753 m_tempID = 0;
754 }
755}
756
758{
759 if (!m_progInfo)
760 return;
761
775 if (m_recordID <= 0)
776 {
777 m_findday =
778 (m_progInfo->GetScheduledStartTime().toLocalTime().date()
779 .dayOfWeek() + 1) % 7;
780 m_findtime = m_progInfo->GetScheduledStartTime().toLocalTime().time();
781 m_findid = QDate(1970, 1, 1).daysTo(
782 m_progInfo->GetScheduledStartTime().toLocalTime().date()) + 719528;
783 }
784 else
785 {
786 if (m_findid > 0)
788 else
789 {
790 QDate epoch(1970, 1, 1);
791 m_findid = epoch.daysTo(
792 m_progInfo->GetScheduledStartTime().toLocalTime().date())
793 + 719528;
794 }
795 }
797
798 if (m_inetref.isEmpty())
799 {
803 }
805}
806
808{
810 query.prepare("SELECT SUM(1 << filterid) FROM recordfilter "
811 "WHERE filterid >= 0 AND filterid < :NUMFILTERS AND "
812 " TRIM(clause) <> '' AND newruledefault <> 0");
813 query.bindValue(":NUMFILTERS", RecordingRule::kNumFilters);
814 if (!query.exec())
815 {
816 MythDB::DBError("GetDefaultFilter", query);
817 return 0;
818 }
819
820 if (!query.next())
821 return 0;
822
823 return query.value(0).toUInt();
824}
825
827{
828 QString searchTypeString;
829
830 switch (searchType)
831 {
832 case kNoSearch:
833 searchTypeString = ""; // Allow themers to decide what to display
834 break;
835 case kPowerSearch:
836 searchTypeString = tr("Power Search");
837 break;
838 case kTitleSearch:
839 searchTypeString = tr("Title Search");
840 break;
841 case kKeywordSearch:
842 searchTypeString = tr("Keyword Search");
843 break;
844 case kPeopleSearch:
845 searchTypeString = tr("People Search");
846 break;
847 default:
848 searchTypeString = tr("Unknown Search");
849 break;
850 }
851
852 return searchTypeString;
853}
854
856{
857 QStringList result;
858
860 query.prepare("SELECT category "
861 "FROM record "
862 "WHERE type = :TEMPLATE "
863 "ORDER BY category = 'Default' DESC, category"
864 );
865 query.bindValue(":TEMPLATE", kTemplateRecord);
866
867 if (!query.exec())
868 {
869 MythDB::DBError("LoadByTemplate", query);
870 return result;
871 }
872
873 while (query.next())
874 result << query.value(0).toString();
875
876 return result;
877}
878
879bool RecordingRule::IsValid(QString &msg) const
880{
881 bool isOverride = false;
882 switch (m_type)
883 {
884 case kSingleRecord:
885 case kDailyRecord:
886 case kAllRecord:
887 case kWeeklyRecord:
888 case kOneRecord:
889 case kTemplateRecord:
890 break;
891 case kOverrideRecord:
892 case kDontRecord:
893 isOverride = true;
894 break;
895 case kNotRecording:
896 default:
897 msg = QString("Invalid recording type.");
898 return false;
899 }
900
901 bool isNormal = false;
902 bool isSearch = false;
903 bool isManual = false;
904 switch (m_searchType)
905 {
906 case kNoSearch:
907 isNormal = true;
908 break;
909 case kPowerSearch:
910 case kTitleSearch:
911 case kKeywordSearch:
912 case kPeopleSearch:
913 isSearch = true;
914 break;
915 case kManualSearch:
916 isManual = true;
917 break;
918 default:
919 msg = QString("Invalid search type.");
920 return false;
921 }
922
923 if (!isOverride &&
924 ((isNormal && (m_type == kDailyRecord || m_type == kWeeklyRecord)) ||
925 (isSearch && (m_type != kDailyRecord && m_type != kWeeklyRecord &&
926 m_type != kOneRecord && m_type != kAllRecord)) ||
927 (isManual && (m_type != kDailyRecord && m_type != kWeeklyRecord &&
929 {
930 msg = QString("Invalid recording type/search type.");
931 return false;
932 }
933
934 if ((m_parentRecID && !isOverride) ||
935 (!m_parentRecID && isOverride))
936 {
937 msg = QString("Invalid parentID/override.");
938 return false;
939 }
940
941 // Inactive overrides cause errors so are disallowed.
942 if (isOverride && m_isInactive)
943 {
944 msg = QString("Invalid Inactive Override.");
945 return false;
946 }
947
948 if (m_title.isEmpty())
949 {
950 msg = QString("Invalid title.");
951 return false;
952 }
953
955 {
956 if (m_description.contains(';') || m_subtitle.contains(';'))
957 {
958 msg = QString("Invalid SQL, contains semicolon");
959 return false;
960 }
962 query.prepare(QString("SELECT NULL FROM (program, channel, oldrecorded AS oldrecstatus) "
963 "%1 WHERE %2 LIMIT 5")
965 if (!query.exec())
966 {
967 msg = QString("Invalid SQL Where clause." + query.lastError().databaseText());
968 return false;
969 }
970 }
971 else if (isSearch)
972 {
973 if (m_description.isEmpty())
974 {
975 msg = QString("Invalid search value.");
976 return false;
977 }
978 }
979
980 if (m_type != kTemplateRecord && !isSearch)
981 {
982 if (!m_startdate.isValid() || !m_starttime.isValid() ||
983 !m_enddate.isValid() || !m_endtime.isValid())
984 {
985 msg = QString("Invalid start/end date/time.");
986 return false;
987 }
988#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
989 qint64 secsto = QDateTime(m_startdate, m_starttime, Qt::UTC)
990 .secsTo(QDateTime(m_enddate, m_endtime, Qt::UTC));
991#else
992 static const QTimeZone utc(QTimeZone::UTC);
993 qint64 secsto = QDateTime(m_startdate, m_starttime, utc)
994 .secsTo(QDateTime(m_enddate, m_endtime, utc));
995#endif
996 if (secsto <= 0 || secsto > (24 * 3600))
997 {
998 msg = QString("Invalid duration.");
999 return false;
1000 }
1001
1002 if (!m_channelid || m_station.isEmpty())
1003 {
1004 msg = QString("Invalid channel.");
1005 return false;
1006 }
1007 }
1008
1009 if (m_type != kTemplateRecord
1010 && (m_findday < 0 || m_findday > 6 || !m_findtime.isValid()) )
1011 {
1012 msg = QString("Invalid find values.");
1013 return false;
1014 }
1015
1016 if (m_recPriority < -99 || m_recPriority > 99)
1017 {
1018 msg = QString("Invalid priority.");
1019 return false;
1020 }
1021
1022 if (m_startOffset < -480 || m_startOffset > 480 ||
1023 m_endOffset < -480 || m_endOffset > 480)
1024 {
1025 msg = QString("Invalid start/end offset.");
1026 return false;
1027 }
1028
1029 if (m_prefInput < 0)
1030 {
1031 msg = QString("Invalid preferred input.");
1032 return false;
1033 }
1034
1035 unsigned valid_mask = (1 << kNumFilters) - 1;
1036 if ((m_filter & valid_mask) != m_filter)
1037 {
1038 msg = QString("Invalid filter value.");
1039 return false;
1040 }
1041
1042 if (m_recProfile.isEmpty() || (m_recGroupID == 0) ||
1043 m_storageGroup.isEmpty() || m_playGroup.isEmpty())
1044 {
1045 msg = QString("Invalid group value.");
1046 return false;
1047 }
1048
1049 if (m_maxEpisodes < 0)
1050 {
1051 msg = QString("Invalid max episodes value.");
1052 return false;
1053 }
1054
1055 if (m_dupIn & ~(kDupsInAll | kDupsNewEpi))
1056 {
1057 msg = QString("Invalid duplicate scope.");
1058 return false;
1059 }
1060
1063 {
1064 msg = QString("Invalid duplicate method.");
1065 return false;
1066 }
1067
1068 if (m_transcoder < 0)
1069 {
1070 msg = QString("Invalid transcoder value.");
1071 return false;
1072 }
1073
1075 {
1076 msg = QString("Invalid auto extend value.");
1077 return false;
1078 }
1079
1080 return true;
1081}
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
QSqlError lastError(void) const
Definition: mythdbcon.h:213
QVariant value(int i) const
Definition: mythdbcon.h:204
static MSqlQueryInfo SchedCon()
Returns dedicated connection. (Required for using temporary SQL tables.)
Definition: mythdbcon.cpp:580
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
QLocale GetQLocale(void)
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:226
static QString GetInitialName(const ProgramInfo *pi)
Definition: playgroup.cpp:253
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
uint GetRecordingRuleID(void) const
Definition: programinfo.h:453
QString GetSeriesID(void) const
Definition: programinfo.h:439
QString GetCategoryTypeString(void) const
Returns catType as a string.
uint GetEpisode(void) const
Definition: programinfo.h:368
QString GetProgramID(void) const
Definition: programinfo.h:440
QDateTime GetScheduledEndTime(void) const
The scheduled end time of the program.
Definition: programinfo.h:398
QString GetInetRef(void) const
Definition: programinfo.h:441
QString GetSortSubtitle(void) const
Definition: programinfo.h:365
QString GetDescription(void) const
Definition: programinfo.h:366
QString GetTitle(void) const
Definition: programinfo.h:362
QDateTime GetScheduledStartTime(void) const
The scheduled start time of program.
Definition: programinfo.h:391
uint GetFindID(void) const
Definition: programinfo.h:472
QString GetSortTitle(void) const
Definition: programinfo.h:363
QString GetSubtitle(void) const
Definition: programinfo.h:364
QString GetCategory(void) const
Definition: programinfo.h:370
uint GetSeason(void) const
Definition: programinfo.h:367
QString GetChannelSchedulingID(void) const
This is the unique programming identifier of a channel.
Definition: programinfo.h:384
static QString GetRecgroupString(uint recGroupID)
Temporary helper during transition from string to ID.
RecordingType m_type
QString m_sortSubtitle
Definition: recordingrule.h:81
bool MakeTemplate(QString category)
QString m_station
Definition: recordingrule.h:99
void UseTempTable(bool usetemp, const QString &table="record_tmp")
QString m_programid
Definition: recordingrule.h:86
QString m_category
Definition: recordingrule.h:83
int m_findday
Day of the week for once per week etc.
QString m_inetref
Definition: recordingrule.h:88
bool LoadTemplate(const QString &title, const QString &category="Default", const QString &categoryType="Default")
RecSearchType m_searchType
bool ModifyPowerSearchByID(int rid, const QString &textname, QString forwhat, QString joininfo="")
bool LoadByProgram(const ProgramInfo *proginfo)
unsigned m_filter
bool IsValid(QString &msg) const
static unsigned GetDefaultFilter(void)
QTime m_findtime
Time for timeslot rules.
QString m_description
Definition: recordingrule.h:82
QString m_storageGroup
QDateTime m_nextRecording
static QString SearchTypeToString(RecSearchType searchType)
bool LoadBySearch(RecSearchType lsearch, const QString &textname, const QString &forwhat, QString joininfo="", ProgramInfo *pginfo=nullptr)
Load a recording rule based on search parameters.
int m_recordID
Unique Recording Rule ID.
Definition: recordingrule.h:71
RecordingDupInType m_dupIn
QString m_title
Definition: recordingrule.h:78
int m_channelid
callsign?
void ToMap(InfoMap &infoMap, uint date_format=0) const
QString m_recProfile
void ensureSortFields(void)
Ensure that the sortTitle and sortSubtitle fields are set.
bool m_isInactive
Recording rule is enabled?
Definition: recordingrule.h:75
bool MakeOverride(void)
static QStringList GetTemplateNames(void)
QString m_playGroup
bool Save(bool sendSig=true)
QString m_recordTable
QString m_subtitle
Definition: recordingrule.h:80
RecordingDupMethodType m_dupMethod
QDateTime m_lastDeleted
const ProgramInfo * m_progInfo
bool Load(bool asTemplate=false)
Load a single rule from the recorded table.
void AssignProgramInfo()
bool Delete(bool sendSig=true)
QString m_template
QString m_seriesid
Definition: recordingrule.h:85
QDateTime m_lastRecorded
bool m_autoMetadataLookup
static const int kNumFilters
Definition: recordingrule.h:34
AutoExtendType m_autoExtend
QString m_sortTitle
Definition: recordingrule.h:79
static void RescheduleMatch(uint recordid, uint sourceid, uint mplexid, const QDateTime &maxstarttime, const QString &why)
unsigned int uint
Definition: freesurround.h:24
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
std::shared_ptr< MythSortHelper > getMythSortHelper(void)
Get a pointer to the MythSortHelper singleton.
QHash< QString, QString > InfoMap
Definition: mythtypes.h:15
QDateTime as_utc(const QDateTime &old_dt)
Returns copy of QDateTime with TimeSpec set to UTC.
Definition: mythdate.cpp:28
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:93
@ kDateTimeFull
Default local time.
Definition: mythdate.h:23
@ kSimplify
Do Today/Yesterday/Tomorrow transform.
Definition: mythdate.h:26
@ kDateFull
Default local time.
Definition: mythdate.h:19
@ kDateTimeShort
Default local time.
Definition: mythdate.h:24
@ ISODate
Default UTC.
Definition: mythdate.h:17
@ kTime
Default local time.
Definition: mythdate.h:22
@ kAddYear
Add year to string if not included.
Definition: mythdate.h:25
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:39
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
static QString null_to_empty(const QString &str)
RecSearchType
@ kTitleSearch
@ kPowerSearch
@ kKeywordSearch
@ kManualSearch
@ kNoSearch
@ kPeopleSearch
AutoExtendType
RecordingDupInType
@ kDupsInAll
@ kDupsNewEpi
RecordingType
@ kOneRecord
@ kWeeklyRecord
@ kNotRecording
@ kAllRecord
@ kOverrideRecord
@ kSingleRecord
@ kDailyRecord
@ kTemplateRecord
@ kDontRecord
RecordingDupMethodType
@ kDupCheckSub
@ kDupCheckSubThenDesc
@ kDupCheckDesc
@ kDupCheckNone