MythTV master
programdata.cpp
Go to the documentation of this file.
1// -*- Mode: c++ -*-
2
3// C++ includes
4#include <algorithm>
5#include <climits>
6#include <utility>
7
8// Qt includes
9#include <QtGlobal> // for qAbs
10
11// MythTV headers
12#include "libmythbase/mythdb.h"
14
15#include "channelutil.h"
16#include "mpeg/dvbdescriptors.h"
17#include "programdata.h"
18
19#define LOC QString("ProgramData: ")
20
21static const std::array<const std::string,DBPerson::kGuest+1> roles
22{
23 "",
24 "actor", "director", "producer", "executive_producer",
25 "writer", "guest_star", "host", "adapter",
26 "presenter", "commentator", "guest",
27};
28
29static QString denullify(const QString &str)
30{
31 return str.isNull() ? "" : str;
32}
33
34static QVariant denullify(const QDateTime &dt)
35{
36 return dt.isNull() ? QVariant("0000-00-00 00:00:00") : QVariant(dt);
37}
38
39static void add_genres(MSqlQuery &query, const QStringList &genres,
40 uint chanid, const QDateTime &starttime)
41{
42 QString relevance = QString("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
43 for (auto it = genres.constBegin(); (it != genres.constEnd()) &&
44 ((it - genres.constBegin()) < relevance.size()); ++it)
45 {
46 query.prepare(
47 "INSERT INTO programgenres "
48 " ( chanid, starttime, genre, relevance) "
49 "VALUES (:CHANID, :START, :genre, :relevance)");
50 query.bindValue(":CHANID", chanid);
51 query.bindValue(":START", starttime);
52 query.bindValue(":genre", *it);
53 query.bindValue(":relevance", relevance.at(it - genres.constBegin()));
54
55 if (!query.exec())
56 MythDB::DBError("programgenres insert", query);
57 }
58}
59
61 : m_role(other.m_role)
62 , m_name(other.m_name)
63 , m_priority(other.m_priority)
64 , m_character(other.m_character)
65{
66 m_name.squeeze();
67 m_character.squeeze();
68}
69
71{
72 if (this == &rhs)
73 return *this;
74 m_role = rhs.m_role;
75 m_name = rhs.m_name;
76 m_name.squeeze();
79 m_character.squeeze();
80 return *this;
81}
82
83DBPerson::DBPerson(Role role, QString name, int priority,
84 QString character)
85 : m_role(role)
86 , m_name(std::move(name))
87 , m_priority(priority)
88 , m_character(std::move(character))
89{
90 m_name.squeeze();
91 m_character.squeeze();
92}
93
94DBPerson::DBPerson(const QString &role, QString name,
95 int priority, QString character)
96 : m_role(kUnknown)
97 , m_name(std::move(name))
98 , m_priority(priority)
99 , m_character(std::move(character))
100{
101 if (!role.isEmpty())
102 {
103 std::string rolestr = role.toLower().toStdString();
104 for (size_t i = 0; i < roles.size(); ++i)
105 {
106 if (rolestr == roles[i])
107 m_role = (Role) i;
108 }
109 }
110 m_name.squeeze();
111 m_character.squeeze();
112}
113
114QString DBPerson::GetRole(void) const
115{
116 if ((m_role < kActor) || (m_role > kGuest))
117 return "guest";
118 return QString::fromStdString(roles[m_role]);
119}
120
121QString DBPerson::toString(void) const
122{
123 return QString("%1 %2 as %3").arg(m_role).arg(m_name, m_character);
124}
125
127 const QDateTime &starttime,
128 bool recording) const
129{
130 uint personid = GetPersonDB(query);
131 if (!personid && InsertPersonDB(query))
132 personid = GetPersonDB(query);
133
134 uint roleid = 0;
135 if (!m_character.isEmpty())
136 {
137 roleid = GetRoleDB(query);
138 if (!roleid && InsertRoleDB(query))
139 roleid = GetRoleDB(query);
140 }
141
142 return InsertCreditsDB(query, personid, roleid, chanid,
143 starttime, recording);
144}
145
147{
148 query.prepare(
149 "SELECT person "
150 "FROM people "
151 "WHERE name = :NAME");
152 query.bindValue(":NAME", m_name);
153
154 if (!query.exec())
155 MythDB::DBError("get_person", query);
156 else if (query.next())
157 return query.value(0).toUInt();
158
159 return 0;
160}
161
163{
164 query.prepare(
165 "INSERT IGNORE INTO people (name) "
166 "VALUES (:NAME);");
167 query.bindValue(":NAME", m_name);
168
169 if (query.exec())
170 return 1;
171
172 MythDB::DBError("insert_person", query);
173 return 0;
174}
175
177{
178 query.prepare(
179 "SELECT roleid "
180 "FROM roles "
181 "WHERE name = :NAME");
182 query.bindValue(":NAME", m_character);
183
184 if (query.exec() && query.next())
185 return query.value(0).toUInt();
186
187 return 0;
188}
189
191{
192 query.prepare(
193 "INSERT IGNORE INTO roles (name) "
194 "VALUES (:NAME);");
195 query.bindValue(":NAME", m_character);
196
197 if (query.exec())
198 return true;
199
200 MythDB::DBError("insert_role", query);
201 return false;
202}
203
205 uint chanid, const QDateTime &starttime,
206 bool recording) const
207{
208 if (!personid)
209 return 0;
210
211 QString table = recording ? "recordedcredits" : "credits";
212
213 query.prepare(QString("REPLACE INTO %1 "
214 " ( person, roleid, chanid, starttime, role, priority) "
215 "VALUES (:PERSON, :ROLEID, :CHANID, :STARTTIME, :ROLE, :PRIORITY);")
216 .arg(table));
217 query.bindValue(":PERSON", personid);
218 query.bindValue(":ROLEID", roleid);
219 query.bindValue(":CHANID", chanid);
220 query.bindValue(":STARTTIME", starttime);
221 query.bindValue(":ROLE", GetRole());
222 query.bindValue(":PRIORITY", m_priority);
223
224 if (query.exec())
225 return 1;
226
227 MythDB::DBError("insert_credits", query);
228 return 0;
229}
230
232{
233 if (this == &other)
234 return *this;
235
236 m_title = other.m_title;
237 m_subtitle = other.m_subtitle;
239 m_category = other.m_category;
240 m_starttime = other.m_starttime;
241 m_endtime = other.m_endtime;
242 m_airdate = other.m_airdate;
244
245 if (m_credits != other.m_credits)
246 {
247 if (m_credits)
248 {
249 delete m_credits;
250 m_credits = nullptr;
251 }
252
253 if (other.m_credits)
254 {
255 m_credits = new DBCredits;
256 m_credits->insert(m_credits->end(),
257 other.m_credits->begin(),
258 other.m_credits->end());
259 }
260 }
261
263 m_parttotal = other.m_parttotal;
268 m_stars = other.m_stars;
270 m_seriesId = other.m_seriesId;
271 m_programId = other.m_programId;
272 m_inetref = other.m_inetref;
274 m_ratings = other.m_ratings;
276 m_season = other.m_season;
277 m_episode = other.m_episode;
279 m_genres = other.m_genres;
280
281 Squeeze();
282
283 return *this;
284}
285
287{
288 m_title.squeeze();
289 m_subtitle.squeeze();
290 m_description.squeeze();
291 m_category.squeeze();
293 m_seriesId.squeeze();
294 m_programId.squeeze();
295 m_inetref.squeeze();
296}
297
298void DBEvent::AddPerson(DBPerson::Role role, const QString &name,
299 int priority, const QString &character)
300{
301 if (!m_credits)
302 m_credits = new DBCredits;
303
304 m_credits->emplace_back(role, name.simplified(),
305 priority, character.simplified());
306}
307
308void DBEvent::AddPerson(const QString &role, const QString &name,
309 int priority, const QString &character)
310{
311 if (!m_credits)
312 m_credits = new DBCredits;
313
314 m_credits->emplace_back(role, name.simplified(),
315 priority, character.simplified());
316}
317
319{
320 return ((m_starttime <= o.m_starttime && o.m_starttime < m_endtime) ||
322}
323
324// Processing new EIT entry starts here
326 MSqlQuery &query, uint chanid, int match_threshold) const
327{
328 // List the program that we are going to add
329 LOG(VB_EIT, LOG_DEBUG,
330 QString("EIT: new program: %1 %2 '%3' chanid %4")
331 .arg(m_starttime.toString(Qt::ISODate),
332 m_endtime.toString(Qt::ISODate),
333 m_title.left(35),
334 QString::number(chanid)));
335
336 // Do not insert or update when the program is in the past
337 QDateTime now = QDateTime::currentDateTimeUtc();
338 if (m_endtime < now)
339 {
340 LOG(VB_EIT, LOG_DEBUG,
341 QString("EIT: skip '%1' endtime is in the past")
342 .arg(m_title.left(35)));
343 return 0;
344 }
345
346 // Get all programs already in the database that overlap
347 // with our new program.
348 std::vector<DBEvent> programs;
349 uint count = GetOverlappingPrograms(query, chanid, programs);
350 int match = INT_MIN;
351 int i = -1;
352
353 // If there are no programs already in the database that overlap
354 // with our new program then we can simply insert it in the database.
355 if (!count)
356 return InsertDB(query, chanid);
357
358 // List all overlapping programs with start- and endtime.
359 for (uint j=0; j<count; ++j)
360 {
361 LOG(VB_EIT, LOG_DEBUG,
362 QString("EIT: overlap[%1] : %2 %3 '%4'")
363 .arg(QString::number(j),
364 programs[j].m_starttime.toString(Qt::ISODate),
365 programs[j].m_endtime.toString(Qt::ISODate),
366 programs[j].m_title.left(35)));
367 }
368
369 // Determine which of the overlapping programs is a match with
370 // our new program; if we have a match then our new program is considered
371 // to be an update of the matching program.
372 // The 2nd parameter "i" is the index of the best matching program.
373 match = GetMatch(programs, i);
374
375 // Update an existing program or insert a new program.
376 if (match >= match_threshold)
377 {
378 // We have a good match; update program[i] in the database
379 // with the new program data and move the overlapping programs
380 // out of the way.
381 LOG(VB_EIT, LOG_DEBUG,
382 QString("EIT: accept match[%1]: %2 '%3' vs. '%4'")
383 .arg(i).arg(match)
384 .arg(m_title.left(35),
385 programs[i].m_title.left(35)));
386 return UpdateDB(query, chanid, programs, i);
387 }
388
389 // If we are here then either we have a match but the match is
390 // not good enough (the "i >= 0" case) or we did not find
391 // a match at all.
392 if (i >= 0)
393 {
394 LOG(VB_EIT, LOG_DEBUG,
395 QString("EIT: reject match[%1]: %2 '%3' vs. '%4'")
396 .arg(i).arg(match)
397 .arg(m_title.left(35),
398 programs[i].m_title.left(35)));
399 }
400
401 // Move the overlapping programs out of the way and
402 // insert the new program.
403 return UpdateDB(query, chanid, programs, -1);
404}
405
406// Get all programs in the database that overlap with our new program.
407// We check for three ways in which we can have an overlap:
408// (1) Start of old program is inside our new program:
409// old program starts at or after our program AND
410// old program starts before end of our program;
411// e.g. new program s-------------e
412// old program s-------------e
413// or old program s-----e
414// This is the STIME1/ETIME1 comparison.
415// (2) End of old program is inside our new program:
416// old program ends after our program starts AND
417// old program ends before end of our program
418// e.g. new program s-------------e
419// old program s-------------e
420// or old program s-----e
421// This is the STIME2/ETIME2 comparison.
422// (3) We can have a new program is "inside" the old program:
423// old program starts before our program AND
424// old program ends after end of our program
425// e.g. new program s---------e
426// old program s-----------------e
427// This is the STIME3/ETIME3 comparison.
428//
430 MSqlQuery &query, uint chanid, std::vector<DBEvent> &programs) const
431{
432 uint count = 0;
433 query.prepare(
434 "SELECT title, subtitle, description, "
435 " category, category_type, "
436 " starttime, endtime, "
437 " subtitletypes+0,audioprop+0, videoprop+0, "
438 " seriesid, programid, "
439 " partnumber, parttotal, "
440 " syndicatedepisodenumber, "
441 " airdate, originalairdate, "
442 " previouslyshown,listingsource, "
443 " stars+0, "
444 " season, episode, totalepisodes, "
445 " inetref "
446 "FROM program "
447 "WHERE chanid = :CHANID AND "
448 " manualid = 0 AND "
449 " ( ( starttime >= :STIME1 AND starttime < :ETIME1 ) OR "
450 " ( endtime > :STIME2 AND endtime <= :ETIME2 ) OR "
451 " ( starttime < :STIME3 AND endtime > :ETIME3 ) )");
452 query.bindValue(":CHANID", chanid);
453 query.bindValue(":STIME1", m_starttime);
454 query.bindValue(":ETIME1", m_endtime);
455 query.bindValue(":STIME2", m_starttime);
456 query.bindValue(":ETIME2", m_endtime);
457 query.bindValue(":STIME3", m_starttime);
458 query.bindValue(":ETIME3", m_endtime);
459
460 if (!query.exec())
461 {
462 MythDB::DBError("GetOverlappingPrograms 1", query);
463 return 0;
464 }
465
466 while (query.next())
467 {
468 ProgramInfo::CategoryType category_type =
469 string_to_myth_category_type(query.value(4).toString());
470
471 DBEvent prog(
472 query.value(0).toString(),
473 query.value(1).toString(),
474 query.value(2).toString(),
475 query.value(3).toString(),
476 category_type,
477 MythDate::as_utc(query.value(5).toDateTime()),
478 MythDate::as_utc(query.value(6).toDateTime()),
479 query.value(7).toUInt(),
480 query.value(8).toUInt(),
481 query.value(9).toUInt(),
482 query.value(19).toFloat(),
483 query.value(10).toString(),
484 query.value(11).toString(),
485 query.value(18).toUInt(),
486 query.value(20).toUInt(), // Season
487 query.value(21).toUInt(), // Episode
488 query.value(22).toUInt()); // Total Episodes
489
490 prog.m_inetref = query.value(23).toString();
491 prog.m_partnumber = query.value(12).toUInt();
492 prog.m_parttotal = query.value(13).toUInt();
493 prog.m_syndicatedepisodenumber = query.value(14).toString();
494 prog.m_airdate = query.value(15).toUInt();
495 prog.m_originalairdate = query.value(16).toDate();
496 prog.m_previouslyshown = query.value(17).toBool();
497
498 programs.push_back(prog);
499 count++;
500 }
501
502 return count;
503}
504
505
506static int score_words(const QStringList &al, const QStringList &bl)
507{
508 QStringList::const_iterator ait = al.begin();
509 QStringList::const_iterator bit = bl.begin();
510 int score = 0;
511 for (; (ait != al.end()) && (bit != bl.end()); ++ait)
512 {
513 QStringList::const_iterator bit2 = bit;
514 int dist = 0;
515 int bscore = 0;
516 for (; bit2 != bl.end(); ++bit2)
517 {
518 if (*ait == *bit)
519 {
520 bscore = std::max(1000, 2000 - (dist * 500));
521 // lower score for short words
522 if (ait->length() < 5)
523 bscore /= 5 - ait->length();
524 break;
525 }
526 dist++;
527 }
528 if (bscore && dist < 3)
529 {
530 for (int i = 0; (i < dist) && bit != bl.end(); ++i)
531 ++bit;
532 }
533 score += bscore;
534 }
535
536 return score / al.size();
537}
538
539static int score_match(const QString &a, const QString &b)
540{
541 if (a.isEmpty() || b.isEmpty())
542 return 0;
543 if (a == b)
544 return 1000;
545
546 QString A = a.simplified().toUpper();
547 QString B = b.simplified().toUpper();
548 if (A == B)
549 return 1000;
550
551 QStringList al = A.split(" ", Qt::SkipEmptyParts);
552 if (al.isEmpty())
553 return 0;
554
555 QStringList bl = B.split(" ", Qt::SkipEmptyParts);
556 if (bl.isEmpty())
557 return 0;
558
559 // score words symmetrically
560 int score = (score_words(al, bl) + score_words(bl, al)) / 2;
561
562 return std::min(900, score);
563}
564
565int DBEvent::GetMatch(const std::vector<DBEvent> &programs, int &bestmatch) const
566{
567 bestmatch = -1;
568 int match_val = INT_MIN;
569 int duration = m_starttime.secsTo(m_endtime);
570
571 for (size_t i = 0; i < programs.size(); ++i)
572 {
573 int mv = 0;
574 int duration_loop = programs[i].m_starttime.secsTo(programs[i].m_endtime);
575
576 mv -= qAbs(m_starttime.secsTo(programs[i].m_starttime));
577 mv -= qAbs(m_endtime.secsTo(programs[i].m_endtime));
578 mv -= qAbs(duration - duration_loop);
579 mv += score_match(m_title, programs[i].m_title) * 10;
580 mv += score_match(m_subtitle, programs[i].m_subtitle);
581 mv += score_match(m_description, programs[i].m_description);
582
583 /* determine overlap of both programs
584 * we don't know which one starts first */
585 int overlap = 0;
586 if (m_starttime < programs[i].m_starttime)
587 {
588 overlap = programs[i].m_starttime.secsTo(m_endtime);
589 }
590 else if (m_starttime > programs[i].m_starttime)
591 {
592 overlap = m_starttime.secsTo(programs[i].m_endtime);
593 }
594 else
595 {
596 if (m_endtime <= programs[i].m_endtime)
597 overlap = m_starttime.secsTo(m_endtime);
598 else
599 overlap = m_starttime.secsTo(programs[i].m_endtime);
600 }
601
602 /* scale the score depending on the overlap length
603 * full score is preserved if the overlap is at least 1/2 of the length
604 * of the shorter program */
605 if (overlap > 0)
606 {
607 /* crappy providers apparently have events without duration
608 * ensure that the minimal duration is 2 second to avoid
609 * multiplying and more importantly dividing by zero */
610 int min_dur = std::max(2, std::min(duration, duration_loop));
611 overlap = std::min(overlap, min_dur/2);
612 mv *= overlap * 2;
613 mv /= min_dur;
614 }
615 else
616 {
617 LOG(VB_GENERAL, LOG_ERR,
618 QString("Unexpected result: shows don't "
619 "overlap\n\t%1: %2 - %3\n\t%4: %5 - %6")
620 .arg(m_title.left(35), 35)
621 .arg(m_starttime.toString(Qt::ISODate),
622 m_endtime.toString(Qt::ISODate))
623 .arg(programs[i].m_title.left(35), 35)
624 .arg(programs[i].m_starttime.toString(Qt::ISODate),
625 programs[i].m_endtime.toString(Qt::ISODate))
626 );
627 }
628
629 if (mv > match_val)
630 {
631 LOG(VB_EIT, LOG_DEBUG,
632 QString("GM : '%1' new best match '%2' with score %3")
633 .arg(m_title.left(35),
634 programs[i].m_title.left(35),
635 QString::number(mv)));
636 bestmatch = i;
637 match_val = mv;
638 }
639 }
640
641 return match_val;
642}
643
645 MSqlQuery &q, uint chanid, const std::vector<DBEvent> &p, int match) const
646{
647 // Adjust/delete overlaps;
648 bool ok = true;
649 for (size_t i = 0; i < p.size(); ++i)
650 {
651 if (i != (uint)match)
652 ok &= MoveOutOfTheWayDB(q, chanid, p[i]);
653 }
654
655 // If we failed to move programs out of the way, don't insert new ones..
656 if (!ok)
657 {
658 LOG(VB_EIT, LOG_DEBUG,
659 QString("EIT: cannot insert '%1' MoveOutOfTheWayDB failed")
660 .arg(m_title.left(35)));
661 return 0;
662 }
663
664 // No match, insert current item
665 if ((match < 0) || ((uint)match >= p.size()))
666 {
667 LOG(VB_EIT, LOG_DEBUG,
668 QString("EIT: insert '%1'")
669 .arg(m_title.left(35)));
670 return InsertDB(q, chanid);
671 }
672
673 // Changing a starttime of a program that is being recorded can
674 // start another recording of the same program.
675 // Therefore we skip updates that change a starttime in the past
676 // unless the endtime is later.
677 if (m_starttime != p[match].m_starttime)
678 {
679 QDateTime now = QDateTime::currentDateTimeUtc();
680 if (m_starttime < now && m_endtime <= p[match].m_endtime)
681 {
682 LOG(VB_EIT, LOG_DEBUG,
683 QString("EIT: skip '%1' starttime is in the past")
684 .arg(m_title.left(35)));
685 return 0;
686 }
687 }
688
689 // Update matched item with current data
690 LOG(VB_EIT, LOG_DEBUG,
691 QString("EIT: update '%1' with '%2'")
692 .arg(p[match].m_title.left(35),
693 m_title.left(35)));
694 return UpdateDB(q, chanid, p[match]);
695}
696
697// Update starttime in table record for single recordings
698// when the starttime of a program is changed.
699//
700// Return the number of rows affected:
701// 0 if program is not found in table record
702// 1 if program is found and updated
703//
704static int change_record(MSqlQuery &query, uint chanid,
705 const QDateTime &old_starttime,
706 const QDateTime &new_starttime)
707{
708 query.prepare("UPDATE record "
709 "SET starttime = :NEWSTARTTIME, "
710 " startdate = :NEWSTARTDATE "
711 "WHERE chanid = :CHANID "
712 "AND type = :TYPE "
713 "AND search = :SEARCH "
714 "AND starttime = :OLDSTARTTIME "
715 "AND startdate = :OLDSTARTDATE ");
716 query.bindValue(":CHANID", chanid);
717 query.bindValue(":TYPE", kSingleRecord);
718 query.bindValue(":SEARCH", kNoSearch);
719 query.bindValue(":OLDSTARTTIME", old_starttime.time());
720 query.bindValue(":OLDSTARTDATE", old_starttime.date());
721 query.bindValue(":NEWSTARTTIME", new_starttime.time());
722 query.bindValue(":NEWSTARTDATE", new_starttime.date());
723
724 int rows = 0;
725 if (!query.exec() || !query.isActive())
726 {
727 MythDB::DBError("Updating record", query);
728 }
729 else
730 {
731 rows = query.numRowsAffected();
732 }
733 if (rows > 0)
734 {
735 LOG(VB_EIT, LOG_DEBUG,
736 QString("EIT: Updated record: chanid:%1 old:%3 new:%4 rows:%5")
737 .arg(QString::number(chanid),
738 old_starttime.toString(Qt::ISODate),
739 new_starttime.toString(Qt::ISODate),
740 QString::number(rows)));
741 }
742 return rows;
743}
744
745// Update matched item with current data.
746//
748 MSqlQuery &query, uint chanid, const DBEvent &match) const
749{
750 QString ltitle = m_title;
751 QString lsubtitle = m_subtitle;
752 QString ldesc = m_description;
753 QString lcategory = m_category;
754 uint16_t lairdate = m_airdate;
755 QString lprogramId = m_programId;
756 QString lseriesId = m_seriesId;
757 QString linetref = m_inetref;
758 QDate loriginalairdate = m_originalairdate;
759
760 // Update starttime also in database table record so that
761 // tables program and record remain consistent.
762 if (m_starttime != match.m_starttime)
763 {
764 QDateTime const &old_starttime = match.m_starttime;
765 QDateTime const &new_starttime = m_starttime;
766 change_record(query, chanid, old_starttime, new_starttime);
767
768 LOG(VB_EIT, LOG_DEBUG,
769 QString("EIT: (U) change starttime from %1 to %2 for chanid:%3 program '%4' ")
770 .arg(old_starttime.toString(Qt::ISODate),
771 new_starttime.toString(Qt::ISODate),
772 QString::number(chanid),
773 m_title.left(35)));
774 }
775
776 if (ltitle.isEmpty() && !match.m_title.isEmpty())
777 ltitle = match.m_title;
778
779 if (lsubtitle.isEmpty() && !match.m_subtitle.isEmpty())
780 lsubtitle = match.m_subtitle;
781
782 if (ldesc.isEmpty() && !match.m_description.isEmpty())
783 ldesc = match.m_description;
784
785 if (lcategory.isEmpty() && !match.m_category.isEmpty())
786 lcategory = match.m_category;
787
788 if (!lairdate && match.m_airdate)
789 lairdate = match.m_airdate;
790
791 if (!loriginalairdate.isValid() && match.m_originalairdate.isValid())
792 loriginalairdate = match.m_originalairdate;
793
794 if (lprogramId.isEmpty() && !match.m_programId.isEmpty())
795 lprogramId = match.m_programId;
796
797 if (lseriesId.isEmpty() && !match.m_seriesId.isEmpty())
798 lseriesId = match.m_seriesId;
799
800 if (linetref.isEmpty() && !match.m_inetref.isEmpty())
801 linetref= match.m_inetref;
802
804 if (!m_categoryType && match.m_categoryType)
805 tmp = match.m_categoryType;
806
807 QString lcattype = myth_category_type_to_string(tmp);
808
809 unsigned char lsubtype = m_subtitleType | match.m_subtitleType;
810 unsigned char laudio = m_audioProps | match.m_audioProps;
811 unsigned char lvideo = m_videoProps | match.m_videoProps;
812
813 uint lseason = match.m_season;
814 uint lepisode = match.m_episode;
815 uint lepisodeTotal = match.m_totalepisodes;
816
818 {
819 lseason = m_season;
820 lepisode = m_episode;
821 lepisodeTotal = m_totalepisodes;
822 }
823
824 uint lpartnumber = match.m_partnumber;
825 uint lparttotal = match.m_parttotal;
826
828 {
829 lpartnumber = m_partnumber;
830 lparttotal = m_parttotal;
831 }
832
833 bool lpreviouslyshown = m_previouslyshown || match.m_previouslyshown;
834
835 uint32_t llistingsource = m_listingsource | match.m_listingsource;
836
837 QString lsyndicatedepisodenumber = m_syndicatedepisodenumber;
838 if (lsyndicatedepisodenumber.isEmpty() &&
839 !match.m_syndicatedepisodenumber.isEmpty())
840 lsyndicatedepisodenumber = match.m_syndicatedepisodenumber;
841
842 query.prepare(
843 "UPDATE program "
844 "SET title = :TITLE, subtitle = :SUBTITLE, "
845 " description = :DESC, "
846 " category = :CATEGORY, category_type = :CATTYPE, "
847 " starttime = :STARTTIME, endtime = :ENDTIME, "
848 " closecaptioned = :CC, subtitled = :HASSUBTITLES, "
849 " stereo = :STEREO, hdtv = :HDTV, "
850 " subtitletypes = :SUBTYPE, "
851 " audioprop = :AUDIOPROP, videoprop = :VIDEOPROP, "
852 " season = :SEASON, "
853 " episode = :EPISODE, totalepisodes = :TOTALEPS, "
854 " partnumber = :PARTNO, parttotal = :PARTTOTAL, "
855 " syndicatedepisodenumber = :SYNDICATENO, "
856 " airdate = :AIRDATE, originalairdate=:ORIGAIRDATE, "
857 " listingsource = :LSOURCE, "
858 " seriesid = :SERIESID, programid = :PROGRAMID, "
859 " previouslyshown = :PREVSHOWN, inetref = :INETREF "
860 "WHERE chanid = :CHANID AND "
861 " starttime = :OLDSTART ");
862
863 query.bindValue(":CHANID", chanid);
864 query.bindValue(":OLDSTART", match.m_starttime);
865 query.bindValue(":TITLE", denullify(ltitle));
866 query.bindValue(":SUBTITLE", denullify(lsubtitle));
867 query.bindValue(":DESC", denullify(ldesc));
868 query.bindValue(":CATEGORY", denullify(lcategory));
869 query.bindValue(":CATTYPE", lcattype);
870 query.bindValue(":STARTTIME", m_starttime);
871 query.bindValue(":ENDTIME", m_endtime);
872 query.bindValue(":CC", (lsubtype & SUB_HARDHEAR) != 0);
873 query.bindValue(":HASSUBTITLES",(lsubtype & SUB_NORMAL) != 0);
874 query.bindValue(":STEREO", (laudio & AUD_STEREO) != 0);
875 query.bindValue(":HDTV", (lvideo & VID_HDTV) != 0);
876 query.bindValue(":SUBTYPE", lsubtype);
877 query.bindValue(":AUDIOPROP", laudio);
878 query.bindValue(":VIDEOPROP", lvideo);
879 query.bindValue(":SEASON", lseason);
880 query.bindValue(":EPISODE", lepisode);
881 query.bindValue(":TOTALEPS", lepisodeTotal);
882 query.bindValue(":PARTNO", lpartnumber);
883 query.bindValue(":PARTTOTAL", lparttotal);
884 query.bindValue(":SYNDICATENO", denullify(lsyndicatedepisodenumber));
885 query.bindValue(":AIRDATE", lairdate ? QString::number(lairdate) : "0000");
886 query.bindValue(":ORIGAIRDATE", loriginalairdate);
887 query.bindValue(":LSOURCE", llistingsource);
888 query.bindValue(":SERIESID", denullify(lseriesId));
889 query.bindValue(":PROGRAMID", denullify(lprogramId));
890 query.bindValue(":PREVSHOWN", lpreviouslyshown);
891 query.bindValue(":INETREF", denullify(linetref));
892
893 if (!query.exec())
894 {
895 MythDB::DBError("UpdateDB", query);
896 return 0;
897 }
898
899 if (m_credits)
900 {
901 for (auto & credit : *m_credits)
902 credit.InsertDB(query, chanid, m_starttime);
903 }
904
905 for (const auto & rating : std::as_const(m_ratings))
906 {
907 query.prepare(
908 "INSERT IGNORE INTO programrating "
909 " ( chanid, starttime, `system`, rating) "
910 "VALUES (:CHANID, :START, :SYS, :RATING)");
911 query.bindValue(":CHANID", chanid);
912 query.bindValue(":START", m_starttime);
913 query.bindValue(":SYS", rating.m_system);
914 query.bindValue(":RATING", rating.m_rating);
915
916 if (!query.exec())
917 MythDB::DBError("programrating insert", query);
918 }
919
920 add_genres(query, m_genres, chanid, m_starttime);
921
922 return 1;
923}
924
925static bool delete_program(MSqlQuery &query, uint chanid, const QDateTime &st)
926{
927 query.prepare(
928 "DELETE from program "
929 "WHERE chanid = :CHANID AND "
930 " starttime = :STARTTIME");
931
932 query.bindValue(":CHANID", chanid);
933 query.bindValue(":STARTTIME", st);
934
935 if (!query.exec())
936 {
937 MythDB::DBError("delete_program", query);
938 return false;
939 }
940
941 query.prepare(
942 "DELETE from credits "
943 "WHERE chanid = :CHANID AND "
944 " starttime = :STARTTIME");
945
946 query.bindValue(":CHANID", chanid);
947 query.bindValue(":STARTTIME", st);
948
949 if (!query.exec())
950 {
951 MythDB::DBError("delete_credits", query);
952 return false;
953 }
954
955 query.prepare(
956 "DELETE from programrating "
957 "WHERE chanid = :CHANID AND "
958 " starttime = :STARTTIME");
959
960 query.bindValue(":CHANID", chanid);
961 query.bindValue(":STARTTIME", st);
962
963 if (!query.exec())
964 {
965 MythDB::DBError("delete_rating", query);
966 return false;
967 }
968
969 query.prepare(
970 "DELETE from programgenres "
971 "WHERE chanid = :CHANID AND "
972 " starttime = :STARTTIME");
973
974 query.bindValue(":CHANID", chanid);
975 query.bindValue(":STARTTIME", st);
976
977 if (!query.exec())
978 {
979 MythDB::DBError("delete_genres", query);
980 return false;
981 }
982
983 return true;
984}
985
986static bool program_exists(MSqlQuery &query, uint chanid, const QDateTime &st)
987{
988 query.prepare(
989 "SELECT title FROM program "
990 "WHERE chanid = :CHANID AND "
991 " starttime = :OLDSTART");
992 query.bindValue(":CHANID", chanid);
993 query.bindValue(":OLDSTART", st);
994 if (!query.exec())
995 {
996 MythDB::DBError("program_exists", query);
997 }
998 return query.next();
999}
1000
1001static bool change_program(MSqlQuery &query, uint chanid, const QDateTime &st,
1002 const QDateTime &new_st, const QDateTime &new_end)
1003{
1004 query.prepare(
1005 "UPDATE program "
1006 "SET starttime = :NEWSTART, "
1007 " endtime = :NEWEND "
1008 "WHERE chanid = :CHANID AND "
1009 " starttime = :OLDSTART");
1010
1011 query.bindValue(":CHANID", chanid);
1012 query.bindValue(":OLDSTART", st);
1013 query.bindValue(":NEWSTART", new_st);
1014 query.bindValue(":NEWEND", new_end);
1015
1016 if (!query.exec())
1017 {
1018 MythDB::DBError("change_program", query);
1019 return false;
1020 }
1021
1022 query.prepare(
1023 "UPDATE credits "
1024 "SET starttime = :NEWSTART "
1025 "WHERE chanid = :CHANID AND "
1026 " starttime = :OLDSTART");
1027
1028 query.bindValue(":CHANID", chanid);
1029 query.bindValue(":OLDSTART", st);
1030 query.bindValue(":NEWSTART", new_st);
1031
1032 if (!query.exec())
1033 {
1034 MythDB::DBError("change_credits", query);
1035 return false;
1036 }
1037
1038 query.prepare(
1039 "UPDATE programrating "
1040 "SET starttime = :NEWSTART "
1041 "WHERE chanid = :CHANID AND "
1042 " starttime = :OLDSTART");
1043
1044 query.bindValue(":CHANID", chanid);
1045 query.bindValue(":OLDSTART", st);
1046 query.bindValue(":NEWSTART", new_st);
1047
1048 if (!query.exec())
1049 {
1050 MythDB::DBError("change_rating", query);
1051 return false;
1052 }
1053
1054 query.prepare(
1055 "UPDATE programgenres "
1056 "SET starttime = :NEWSTART "
1057 "WHERE chanid = :CHANID AND "
1058 " starttime = :OLDSTART");
1059
1060 query.bindValue(":CHANID", chanid);
1061 query.bindValue(":OLDSTART", st);
1062 query.bindValue(":NEWSTART", new_st);
1063
1064 if (!query.exec())
1065 {
1066 MythDB::DBError("change_genres", query);
1067 return false;
1068 }
1069
1070 return true;
1071}
1072
1073// Move the program "prog" (3rd parameter) out of the way
1074// because it overlaps with our new program.
1076 MSqlQuery &query, uint chanid, const DBEvent &prog) const
1077{
1078 if (prog.m_starttime >= m_starttime && prog.m_endtime <= m_endtime)
1079 {
1080 // Old program completely inside our new program.
1081 // Delete the old program completely.
1082 LOG(VB_EIT, LOG_DEBUG,
1083 QString("EIT: delete '%1' %2 - %3")
1084 .arg(prog.m_title.left(35),
1085 prog.m_starttime.toString(Qt::ISODate),
1086 prog.m_endtime.toString(Qt::ISODate)));
1087 return delete_program(query, chanid, prog.m_starttime);
1088 }
1089 if (prog.m_starttime < m_starttime && prog.m_endtime > m_starttime)
1090 {
1091 // Old program starts before, but ends during or after our new program.
1092 // Adjust the end time of the old program to the start time
1093 // of our new program.
1094 // This will leave a hole after our new program when the end time of
1095 // the old program was after the end time of the new program!!
1096 LOG(VB_EIT, LOG_DEBUG,
1097 QString("EIT: change '%1' endtime to %2")
1098 .arg(prog.m_title.left(35),
1099 m_starttime.toString(Qt::ISODate)));
1100 return change_program(query, chanid, prog.m_starttime,
1101 prog.m_starttime, // Keep the start time
1102 m_starttime); // New end time is our start time
1103 }
1104 if (prog.m_starttime < m_endtime && prog.m_endtime > m_endtime)
1105 {
1106 // Old program starts during, but ends after our new program.
1107 // Adjust the starttime of the old program to the end time
1108 // of our new program.
1109 // If there is already a program starting just when our
1110 // new program ends we cannot move the old program
1111 // so then we have to delete the old program.
1112 if (program_exists(query, chanid, m_endtime))
1113 {
1114 LOG(VB_EIT, LOG_DEBUG,
1115 QString("EIT: delete '%1' %2 - %3")
1116 .arg(prog.m_title.left(35),
1117 prog.m_starttime.toString(Qt::ISODate),
1118 prog.m_endtime.toString(Qt::ISODate)));
1119 return delete_program(query, chanid, prog.m_starttime);
1120 }
1121 LOG(VB_EIT, LOG_DEBUG,
1122 QString("EIT: (M) change starttime from %1 to %2 for chanid:%3 program '%4' ")
1123 .arg(prog.m_starttime.toString(Qt::ISODate),
1124 m_endtime.toString(Qt::ISODate),
1125 QString::number(chanid),
1126 prog.m_title.left(35)));
1127
1128 // Update starttime in tables record and program so they stay consistent.
1129 change_record(query, chanid, prog.m_starttime, m_endtime);
1130 return change_program(query, chanid, prog.m_starttime,
1131 m_endtime, // New start time is our endtime
1132 prog.m_endtime); // Keep the end time
1133 }
1134 // must be non-conflicting...
1135 return true;
1136}
1137
1142 bool recording) const
1143{
1144 QString table = recording ? "recordedprogram" : "program";
1145
1146 query.prepare(QString(
1147 "REPLACE INTO %1 ("
1148 " chanid, title, subtitle, description, "
1149 " category, category_type, "
1150 " starttime, endtime, "
1151 " closecaptioned, stereo, hdtv, subtitled, "
1152 " subtitletypes, audioprop, videoprop, "
1153 " stars, partnumber, parttotal, "
1154 " syndicatedepisodenumber, "
1155 " airdate, originalairdate,listingsource, "
1156 " seriesid, programid, previouslyshown, "
1157 " season, episode, totalepisodes, "
1158 " inetref ) "
1159 "VALUES ("
1160 " :CHANID, :TITLE, :SUBTITLE, :DESCRIPTION, "
1161 " :CATEGORY, :CATTYPE, "
1162 " :STARTTIME, :ENDTIME, "
1163 " :CC, :STEREO, :HDTV, :HASSUBTITLES, "
1164 " :SUBTYPES, :AUDIOPROP, :VIDEOPROP, "
1165 " :STARS, :PARTNUMBER, :PARTTOTAL, "
1166 " :SYNDICATENO, "
1167 " :AIRDATE, :ORIGAIRDATE, :LSOURCE, "
1168 " :SERIESID, :PROGRAMID, :PREVSHOWN, "
1169 " :SEASON, :EPISODE, :TOTALEPISODES, "
1170 " :INETREF ) ").arg(table));
1171
1173 query.bindValue(":CHANID", chanid);
1174 query.bindValue(":TITLE", denullify(m_title));
1175 query.bindValue(":SUBTITLE", denullify(m_subtitle));
1176 query.bindValue(":DESCRIPTION", denullify(m_description));
1177 query.bindValue(":CATEGORY", denullify(m_category));
1178 query.bindValue(":CATTYPE", cattype);
1179 query.bindValue(":STARTTIME", m_starttime);
1180 query.bindValue(":ENDTIME", m_endtime);
1181 query.bindValue(":CC", (m_subtitleType & SUB_HARDHEAR) != 0);
1182 query.bindValue(":STEREO", (m_audioProps & AUD_STEREO) != 0);
1183 query.bindValue(":HDTV", (m_videoProps & VID_HDTV) != 0);
1184 query.bindValue(":HASSUBTITLES",(m_subtitleType & SUB_NORMAL) != 0);
1185 query.bindValue(":SUBTYPES", m_subtitleType);
1186 query.bindValue(":AUDIOPROP", m_audioProps);
1187 query.bindValue(":VIDEOPROP", m_videoProps);
1188 query.bindValue(":STARS", m_stars);
1189 query.bindValue(":PARTNUMBER", m_partnumber);
1190 query.bindValue(":PARTTOTAL", m_parttotal);
1191 query.bindValue(":SYNDICATENO", denullify(m_syndicatedepisodenumber));
1192 query.bindValue(":AIRDATE", m_airdate ? QString::number(m_airdate) : "0000");
1193 query.bindValue(":ORIGAIRDATE", m_originalairdate);
1194 query.bindValue(":LSOURCE", m_listingsource);
1195 query.bindValue(":SERIESID", denullify(m_seriesId));
1196 query.bindValue(":PROGRAMID", denullify(m_programId));
1197 query.bindValue(":PREVSHOWN", m_previouslyshown);
1198 query.bindValue(":SEASON", m_season);
1199 query.bindValue(":EPISODE", m_episode);
1200 query.bindValue(":TOTALEPISODES", m_totalepisodes);
1201 query.bindValue(":INETREF", denullify(m_inetref));
1202
1203 if (!query.exec())
1204 {
1205 MythDB::DBError("InsertDB", query);
1206 return 0;
1207 }
1208
1209 table = recording ? "recordedrating" : "programrating";
1210 for (const auto & rating : std::as_const(m_ratings))
1211 {
1212 query.prepare(QString(
1213 "INSERT IGNORE INTO %1 "
1214 " ( chanid, starttime, `system`, rating) "
1215 "VALUES (:CHANID, :START, :SYS, :RATING)").arg(table));
1216 query.bindValue(":CHANID", chanid);
1217 query.bindValue(":START", m_starttime);
1218 query.bindValue(":SYS", rating.m_system);
1219 query.bindValue(":RATING", rating.m_rating);
1220
1221 if (!query.exec())
1222 MythDB::DBError("programrating insert", query);
1223 }
1224
1225 if (m_credits)
1226 {
1227 for (auto & credit : *m_credits)
1228 credit.InsertDB(query, chanid, m_starttime, recording);
1229 }
1230
1231 add_genres(query, m_genres, chanid, m_starttime);
1232
1233 return 1;
1234}
1235
1237 DBEvent(other.m_listingsource)
1238{
1239 *this = other;
1240}
1241
1243{
1244 if (this == &other)
1245 return *this;
1246
1247 DBEvent::operator=(other);
1248
1249 m_channel = other.m_channel;
1250 m_startts = other.m_startts;
1251 m_endts = other.m_endts;
1253 m_showtype = other.m_showtype;
1254 m_colorcode = other.m_colorcode;
1255 m_clumpidx = other.m_clumpidx;
1256 m_clumpmax = other.m_clumpmax;
1257
1258 m_channel.squeeze();
1259 m_startts.squeeze();
1260 m_endts.squeeze();
1261 m_title_pronounce.squeeze();
1262 m_showtype.squeeze();
1263 m_colorcode.squeeze();
1264 m_clumpidx.squeeze();
1265 m_clumpmax.squeeze();
1266
1267 return *this;
1268}
1269
1271{
1273 m_channel.squeeze();
1274 m_startts.squeeze();
1275 m_endts.squeeze();
1276 m_title_pronounce.squeeze();
1277 m_showtype.squeeze();
1278 m_colorcode.squeeze();
1279 m_clumpidx.squeeze();
1280 m_clumpmax.squeeze();
1281}
1282
1297 bool recording) const
1298{
1299 QString table = recording ? "recordedprogram" : "program";
1300
1301 LOG(VB_XMLTV, LOG_DEBUG,
1302 QString("Inserting new %1 : %2 - %3 %4 %5")
1303 .arg(table,
1304 m_starttime.toString(Qt::ISODate),
1305 m_endtime.toString(Qt::ISODate),
1306 m_channel));
1307
1308 query.prepare(QString(
1309 "REPLACE INTO %1 ("
1310 " chanid, title, subtitle, description, "
1311 " category, category_type, "
1312 " starttime, endtime, "
1313 " closecaptioned, stereo, hdtv, subtitled, "
1314 " subtitletypes, audioprop, videoprop, "
1315 " partnumber, parttotal, "
1316 " syndicatedepisodenumber, "
1317 " airdate, originalairdate,listingsource, "
1318 " seriesid, programid, previouslyshown, "
1319 " stars, showtype, title_pronounce, colorcode, "
1320 " season, episode, totalepisodes, "
1321 " inetref ) "
1322 "VALUES("
1323 " :CHANID, :TITLE, :SUBTITLE, :DESCRIPTION, "
1324 " :CATEGORY, :CATTYPE, "
1325 " :STARTTIME, :ENDTIME, "
1326 " :CC, :STEREO, :HDTV, :HASSUBTITLES, "
1327 " :SUBTYPES, :AUDIOPROP, :VIDEOPROP, "
1328 " :PARTNUMBER, :PARTTOTAL, "
1329 " :SYNDICATENO, "
1330 " :AIRDATE, :ORIGAIRDATE, :LSOURCE, "
1331 " :SERIESID, :PROGRAMID, :PREVSHOWN, "
1332 " :STARS, :SHOWTYPE, :TITLEPRON, :COLORCODE, "
1333 " :SEASON, :EPISODE, :TOTALEPISODES, "
1334 " :INETREF )").arg(table));
1335
1337
1338 query.bindValue(":CHANID", chanid);
1339 query.bindValue(":TITLE", denullify(m_title));
1340 query.bindValue(":SUBTITLE", denullify(m_subtitle));
1341 query.bindValue(":DESCRIPTION", denullify(m_description));
1342 query.bindValue(":CATEGORY", denullify(m_category));
1343 query.bindValue(":CATTYPE", cattype);
1344 query.bindValue(":STARTTIME", m_starttime);
1345 query.bindValue(":ENDTIME", denullify(m_endtime));
1346 query.bindValue(":CC",
1347 (m_subtitleType & SUB_HARDHEAR) != 0);
1348 query.bindValue(":STEREO",
1349 (m_audioProps & AUD_STEREO) != 0);
1350 query.bindValue(":HDTV",
1351 (m_videoProps & VID_HDTV) != 0);
1352 query.bindValue(":HASSUBTITLES",
1353 (m_subtitleType & SUB_NORMAL) != 0);
1354 query.bindValue(":SUBTYPES", m_subtitleType);
1355 query.bindValue(":AUDIOPROP", m_audioProps);
1356 query.bindValue(":VIDEOPROP", m_videoProps);
1357 query.bindValue(":PARTNUMBER", m_partnumber);
1358 query.bindValue(":PARTTOTAL", m_parttotal);
1359 query.bindValue(":SYNDICATENO", denullify(m_syndicatedepisodenumber));
1360 query.bindValue(":AIRDATE", m_airdate ? QString::number(m_airdate):"0000");
1361 query.bindValue(":ORIGAIRDATE", m_originalairdate);
1362 query.bindValue(":LSOURCE", m_listingsource);
1363 query.bindValue(":SERIESID", denullify(m_seriesId));
1364 query.bindValue(":PROGRAMID", denullify(m_programId));
1365 query.bindValue(":PREVSHOWN", m_previouslyshown);
1366 query.bindValue(":STARS", m_stars);
1367 query.bindValue(":SHOWTYPE", denullify(m_showtype));
1368 query.bindValue(":TITLEPRON", denullify(m_title_pronounce));
1369 query.bindValue(":COLORCODE", denullify(m_colorcode));
1370 query.bindValue(":SEASON", m_season);
1371 query.bindValue(":EPISODE", m_episode);
1372 query.bindValue(":TOTALEPISODES", m_totalepisodes);
1373 query.bindValue(":INETREF", denullify(m_inetref));
1374
1375 if (!query.exec())
1376 {
1377 MythDB::DBError(table + " insert", query);
1378 return 0;
1379 }
1380
1381 table = recording ? "recordedrating" : "programrating";
1382 for (const auto & rating : m_ratings)
1383 {
1384 query.prepare(QString("INSERT IGNORE INTO %1 "
1385 " ( chanid, starttime, `system`, rating) "
1386 "VALUES (:CHANID, :START, :SYS, :RATING)")
1387 .arg(table));
1388 query.bindValue(":CHANID", chanid);
1389 query.bindValue(":START", m_starttime);
1390 query.bindValue(":SYS", rating.m_system);
1391 query.bindValue(":RATING", rating.m_rating);
1392
1393 if (!query.exec())
1394 MythDB::DBError(QString("%1 insert").arg(table), query);
1395 }
1396
1397 if (m_credits)
1398 {
1399 for (auto & credit : *m_credits)
1400 credit.InsertDB(query, chanid, m_starttime, recording);
1401 }
1402
1403 add_genres(query, m_genres, chanid, m_starttime);
1404
1405 return 1;
1406}
1407
1409 uint chanid, const QDateTime &from, const QDateTime &to,
1410 bool use_channel_time_offset)
1411{
1412 std::chrono::seconds secs = 0s;
1413 if (use_channel_time_offset)
1414 secs = ChannelUtil::GetTimeOffset(chanid);
1415
1416 QDateTime newFrom = from.addSecs(secs.count());
1417 QDateTime newTo = to.addSecs(secs.count());
1418
1420 query.prepare("DELETE FROM program "
1421 "WHERE starttime >= :FROM AND starttime < :TO "
1422 "AND chanid = :CHANID ;");
1423 query.bindValue(":FROM", newFrom);
1424 query.bindValue(":TO", newTo);
1425 query.bindValue(":CHANID", chanid);
1426 bool ok = query.exec();
1427
1428 query.prepare("DELETE FROM programrating "
1429 "WHERE starttime >= :FROM AND starttime < :TO "
1430 "AND chanid = :CHANID ;");
1431 query.bindValue(":FROM", newFrom);
1432 query.bindValue(":TO", newTo);
1433 query.bindValue(":CHANID", chanid);
1434 ok &= query.exec();
1435
1436 query.prepare("DELETE FROM credits "
1437 "WHERE starttime >= :FROM AND starttime < :TO "
1438 "AND chanid = :CHANID ;");
1439 query.bindValue(":FROM", newFrom);
1440 query.bindValue(":TO", newTo);
1441 query.bindValue(":CHANID", chanid);
1442 ok &= query.exec();
1443
1444 query.prepare("DELETE FROM programgenres "
1445 "WHERE starttime >= :FROM AND starttime < :TO "
1446 "AND chanid = :CHANID ;");
1447 query.bindValue(":FROM", newFrom);
1448 query.bindValue(":TO", newTo);
1449 query.bindValue(":CHANID", chanid);
1450 ok &= query.exec();
1451
1452 return ok;
1453}
1454
1456 uint sourceid, const QDateTime &from, const QDateTime &to,
1457 bool use_channel_time_offset)
1458{
1459 std::vector<uint> chanids = ChannelUtil::GetChanIDs(sourceid);
1460
1461 bool ok = true;
1462 auto cleardata = [&](uint chanid)
1463 { ok &= ClearDataByChannel(chanid, from, to, use_channel_time_offset); };
1464 std::ranges::for_each(chanids, cleardata);
1465 return ok;
1466}
1467
1468static bool start_time_less_than(const DBEvent *a, const DBEvent *b)
1469{
1470 return (a->m_starttime < b->m_starttime);
1471}
1472
1473void ProgramData::FixProgramList(QList<ProgInfo*> &fixlist)
1474{
1475 // QList doesn't always play well with std::ranges
1476 // NOLINTNEXTLINE(modernize-use-ranges)
1477 std::stable_sort(fixlist.begin(), fixlist.end(), start_time_less_than);
1478
1479 QList<ProgInfo*>::iterator it = fixlist.begin();
1480 while (true)
1481 {
1482 QList<ProgInfo*>::iterator cur = it;
1483 ++it;
1484
1485 // fill in miss stop times
1486 if ((*cur)->m_endts.isEmpty() || (*cur)->m_startts > (*cur)->m_endts)
1487 {
1488 if (it != fixlist.end())
1489 {
1490 (*cur)->m_endts = (*it)->m_startts;
1491 (*cur)->m_endtime = (*it)->m_starttime;
1492 }
1493 /* if its the last programme in the file then leave its
1494 endtime as 0000-00-00 00:00:00 so we can find it easily in
1495 fix_end_times() */
1496 }
1497
1498 if (it == fixlist.end())
1499 break;
1500
1501 // remove overlapping programs
1502 if ((*cur)->HasTimeConflict(**it))
1503 {
1504 QList<ProgInfo*>::iterator tokeep;
1505 QList<ProgInfo*>::iterator todelete;
1506
1507 if ((*cur)->m_endtime <= (*cur)->m_starttime)
1508 tokeep = it, todelete = cur; // NOLINT(bugprone-branch-clone)
1509 else if ((*it)->m_endtime <= (*it)->m_starttime)
1510 tokeep = cur, todelete = it; // NOLINT(bugprone-branch-clone)
1511 else if (!(*cur)->m_subtitle.isEmpty() &&
1512 (*it)->m_subtitle.isEmpty())
1513 tokeep = cur, todelete = it;
1514 else if (!(*it)->m_subtitle.isEmpty() &&
1515 (*cur)->m_subtitle.isEmpty())
1516 tokeep = it, todelete = cur;
1517 else if (!(*cur)->m_description.isEmpty() &&
1518 (*it)->m_description.isEmpty())
1519 tokeep = cur, todelete = it;
1520 else
1521 tokeep = it, todelete = cur;
1522
1523
1524 LOG(VB_XMLTV, LOG_DEBUG,
1525 QString("Removing conflicting program: %1 - %2 %3 %4")
1526 .arg((*todelete)->m_starttime.toString(Qt::ISODate),
1527 (*todelete)->m_endtime.toString(Qt::ISODate),
1528 (*todelete)->m_channel,
1529 (*todelete)->m_title));
1530
1531 LOG(VB_XMLTV, LOG_DEBUG,
1532 QString("Conflicted with : %1 - %2 %3 %4")
1533 .arg((*tokeep)->m_starttime.toString(Qt::ISODate),
1534 (*tokeep)->m_endtime.toString(Qt::ISODate),
1535 (*tokeep)->m_channel,
1536 (*tokeep)->m_title));
1537
1538 bool step_back = todelete == it;
1539 it = fixlist.erase(todelete);
1540 if (step_back)
1541 --it;
1542 }
1543 }
1544}
1545
1555 uint sourceid, QMap<QString, QList<ProgInfo> > &proglist)
1556{
1557 uint unchanged = 0;
1558 uint updated = 0;
1559
1561
1562 QMap<QString, QList<ProgInfo> >::const_iterator mapiter;
1563 for (mapiter = proglist.cbegin(); mapiter != proglist.cend(); ++mapiter)
1564 {
1565 if (mapiter.key().isEmpty())
1566 continue;
1567
1568 query.prepare(
1569 "SELECT chanid "
1570 "FROM channel "
1571 "WHERE deleted IS NULL AND "
1572 " sourceid = :ID AND "
1573 " xmltvid = :XMLTVID");
1574 query.bindValue(":ID", sourceid);
1575 query.bindValue(":XMLTVID", mapiter.key());
1576
1577 if (!query.exec())
1578 {
1579 MythDB::DBError("ProgramData::HandlePrograms", query);
1580 continue;
1581 }
1582
1583 std::vector<uint> chanids;
1584 while (query.next())
1585 chanids.push_back(query.value(0).toUInt());
1586
1587 if (chanids.empty())
1588 {
1589 LOG(VB_GENERAL, LOG_NOTICE,
1590 QString("Unknown xmltv channel identifier: %1"
1591 " - Skipping channel.").arg(mapiter.key()));
1592 continue;
1593 }
1594
1595 QList<ProgInfo> &list = proglist[mapiter.key()];
1596 QList<ProgInfo*> sortlist;
1597 // NOLINTNEXTLINE(modernize-loop-convert)
1598 for (auto it = list.begin(); it != list.end(); ++it)
1599 sortlist.push_back(&(*it));
1600
1601 FixProgramList(sortlist);
1602
1603 for (uint chanid : chanids)
1604 HandlePrograms(query, chanid, sortlist, unchanged, updated);
1605 }
1606
1607 LOG(VB_GENERAL, LOG_INFO,
1608 QString("Updated programs: %1 Unchanged programs: %2")
1609 .arg(updated) .arg(unchanged));
1610}
1611
1624 uint chanid,
1625 const QList<ProgInfo*> &sortlist,
1626 uint &unchanged,
1627 uint &updated)
1628{
1629 for (auto *pinfo : std::as_const(sortlist))
1630 {
1631 if (IsUnchanged(query, chanid, *pinfo))
1632 {
1633 unchanged++;
1634 continue;
1635 }
1636
1637 if (!DeleteOverlaps(query, chanid, *pinfo))
1638 continue;
1639
1640 updated += pinfo->InsertDB(query, chanid);
1641 }
1642}
1643
1645{
1646 int count = 0;
1647 QString chanid;
1648 QString starttime;
1649 QString endtime;
1650 QString querystr;
1651 MSqlQuery query1(MSqlQuery::InitCon());
1652 MSqlQuery query2(MSqlQuery::InitCon());
1653
1654 querystr = "SELECT chanid, starttime, endtime FROM program "
1655 "WHERE endtime = '0000-00-00 00:00:00' "
1656 "ORDER BY chanid, starttime;";
1657
1658 if (!query1.exec(querystr))
1659 {
1660 LOG(VB_GENERAL, LOG_ERR,
1661 QString("fix_end_times query failed: %1").arg(querystr));
1662 return -1;
1663 }
1664
1665 while (query1.next())
1666 {
1667 starttime = query1.value(1).toString();
1668 chanid = query1.value(0).toString();
1669 endtime = query1.value(2).toString();
1670
1671 querystr = QString("SELECT chanid, starttime, endtime FROM program "
1672 "WHERE starttime > '%1' "
1673 "AND chanid = '%2' "
1674 "ORDER BY starttime LIMIT 1;")
1675 .arg(starttime, chanid);
1676
1677 if (!query2.exec(querystr))
1678 {
1679 LOG(VB_GENERAL, LOG_ERR,
1680 QString("fix_end_times query failed: %1").arg(querystr));
1681 return -1;
1682 }
1683
1684 if (query2.next() && (endtime != query2.value(1).toString()))
1685 {
1686 count++;
1687 endtime = query2.value(1).toString();
1688 querystr = QString("UPDATE program SET "
1689 "endtime = '%2' WHERE (chanid = '%3' AND "
1690 "starttime = '%4');")
1691 .arg(endtime, chanid, starttime);
1692
1693 if (!query2.exec(querystr))
1694 {
1695 LOG(VB_GENERAL, LOG_ERR,
1696 QString("fix_end_times query failed: %1").arg(querystr));
1697 return -1;
1698 }
1699 }
1700 }
1701
1702 return count;
1703}
1704
1706 MSqlQuery &query, uint chanid, const ProgInfo &pi)
1707{
1708 query.prepare(
1709 "SELECT count(*) "
1710 "FROM program "
1711 "WHERE chanid = :CHANID AND "
1712 " starttime = :START AND "
1713 " endtime = :END AND "
1714 " title = :TITLE AND "
1715 " subtitle = :SUBTITLE AND "
1716 " description = :DESC AND "
1717 " category = :CATEGORY AND "
1718 " category_type = :CATEGORY_TYPE AND "
1719 " airdate = :AIRDATE AND "
1720 " stars >= (:STARS1 - 0.001) AND "
1721 " stars <= (:STARS2 + 0.001) AND "
1722 " previouslyshown = :PREVIOUSLYSHOWN AND "
1723 " title_pronounce = :TITLE_PRONOUNCE AND "
1724 " audioprop = :AUDIOPROP AND "
1725 " videoprop = :VIDEOPROP AND "
1726 " subtitletypes = :SUBTYPES AND "
1727 " partnumber = :PARTNUMBER AND "
1728 " parttotal = :PARTTOTAL AND "
1729 " seriesid = :SERIESID AND "
1730 " showtype = :SHOWTYPE AND "
1731 " colorcode = :COLORCODE AND "
1732 " syndicatedepisodenumber = :SYNDICATEDEPISODENUMBER AND "
1733 " programid = :PROGRAMID AND "
1734 " season = :SEASON AND "
1735 " episode = :EPISODE AND "
1736 " totalepisodes = :TOTALEPISODES AND "
1737 " inetref = :INETREF");
1738
1739 QString cattype = myth_category_type_to_string(pi.m_categoryType);
1740
1741 query.bindValue(":CHANID", chanid);
1742 query.bindValue(":START", pi.m_starttime);
1743 query.bindValue(":END", pi.m_endtime);
1744 query.bindValue(":TITLE", denullify(pi.m_title));
1745 query.bindValue(":SUBTITLE", denullify(pi.m_subtitle));
1746 query.bindValue(":DESC", denullify(pi.m_description));
1747 query.bindValue(":CATEGORY", denullify(pi.m_category));
1748 query.bindValue(":CATEGORY_TYPE", cattype);
1749 query.bindValue(":AIRDATE", pi.m_airdate);
1750 query.bindValue(":STARS1", pi.m_stars);
1751 query.bindValue(":STARS2", pi.m_stars);
1752 query.bindValue(":PREVIOUSLYSHOWN", pi.m_previouslyshown);
1753 query.bindValue(":TITLE_PRONOUNCE", denullify(pi.m_title_pronounce));
1754 query.bindValue(":AUDIOPROP", pi.m_audioProps);
1755 query.bindValue(":VIDEOPROP", pi.m_videoProps);
1756 query.bindValue(":SUBTYPES", pi.m_subtitleType);
1757 query.bindValue(":PARTNUMBER", pi.m_partnumber);
1758 query.bindValue(":PARTTOTAL", pi.m_parttotal);
1759 query.bindValue(":SERIESID", denullify(pi.m_seriesId));
1760 query.bindValue(":SHOWTYPE", denullify(pi.m_showtype));
1761 query.bindValue(":COLORCODE", denullify(pi.m_colorcode));
1762 query.bindValue(":SYNDICATEDEPISODENUMBER",
1764 query.bindValue(":PROGRAMID", denullify(pi.m_programId));
1765 query.bindValue(":SEASON", pi.m_season);
1766 query.bindValue(":EPISODE", pi.m_episode);
1767 query.bindValue(":TOTALEPISODES", pi.m_totalepisodes);
1768 query.bindValue(":INETREF", denullify(pi.m_inetref));
1769
1770 if (query.exec() && query.next())
1771 return query.value(0).toUInt() > 0;
1772
1773 return false;
1774}
1775
1777 MSqlQuery &query, uint chanid, const ProgInfo &pi)
1778{
1779 if (VERBOSE_LEVEL_CHECK(VB_XMLTV, LOG_DEBUG))
1780 {
1781 // Get overlaps..
1782 query.prepare(
1783 "SELECT title,starttime,endtime "
1784 "FROM program "
1785 "WHERE chanid = :CHANID AND "
1786 " starttime >= :START AND "
1787 " starttime < :END;");
1788 query.bindValue(":CHANID", chanid);
1789 query.bindValue(":START", pi.m_starttime);
1790 query.bindValue(":END", pi.m_endtime);
1791
1792 if (!query.exec())
1793 return false;
1794
1795 while (query.next())
1796 {
1797 LOG(VB_XMLTV, LOG_DEBUG,
1798 QString("Removing existing program: %1 - %2 %3 %4")
1799 .arg(MythDate::as_utc(query.value(1).toDateTime()).toString(Qt::ISODate),
1800 MythDate::as_utc(query.value(2).toDateTime()).toString(Qt::ISODate),
1801 pi.m_channel,
1802 query.value(0).toString()));
1803 }
1804
1805 if (query.at() == QSql::BeforeFirstRow)
1806 {
1807 // Successful query, no results
1808 return true;
1809 }
1810 }
1811
1812 if (!ClearDataByChannel(chanid, pi.m_starttime, pi.m_endtime, false))
1813 {
1814 LOG(VB_XMLTV, LOG_ERR,
1815 QString("Program delete failed : %1 - %2 %3 %4")
1816 .arg(pi.m_starttime.toString(Qt::ISODate),
1817 pi.m_endtime.toString(Qt::ISODate),
1818 pi.m_channel,
1819 pi.m_title));
1820 return false;
1821 }
1822
1823 return true;
1824}
static std::vector< uint > GetChanIDs(int sourceid=-1, bool onlyVisible=false)
static std::chrono::minutes GetTimeOffset(int chan_id)
Returns the listings time offset in minutes for given channel.
QString m_title
Definition: programdata.h:148
QString m_seriesId
Definition: programdata.h:165
uint m_totalepisodes
Definition: programdata.h:174
virtual void Squeeze(void)
bool HasTimeConflict(const DBEvent &other) const
QStringList m_genres
Definition: programdata.h:171
uint m_videoProps
Definition: programdata.h:162
uint16_t m_partnumber
Definition: programdata.h:157
int GetMatch(const std::vector< DBEvent > &programs, int &bestmatch) const
unsigned char m_subtitleType
Definition: programdata.h:160
DBCredits * m_credits
Definition: programdata.h:156
QString m_programId
Definition: programdata.h:166
ProgramInfo::CategoryType m_categoryType
Definition: programdata.h:164
QDateTime m_starttime
Definition: programdata.h:152
uint UpdateDB(MSqlQuery &query, uint chanid, int match_threshold) const
QDate m_originalairdate
origial broadcast date
Definition: programdata.h:155
uint32_t m_listingsource
Definition: programdata.h:169
float m_stars
Definition: programdata.h:163
unsigned char m_audioProps
Definition: programdata.h:161
QList< EventRating > m_ratings
Definition: programdata.h:170
QString m_category
Definition: programdata.h:151
uint16_t m_airdate
movie year / production year
Definition: programdata.h:154
uint m_season
Definition: programdata.h:172
void AddPerson(DBPerson::Role role, const QString &name, int priority=0, const QString &character="")
QString m_subtitle
Definition: programdata.h:149
QString m_inetref
Definition: programdata.h:167
bool m_previouslyshown
Definition: programdata.h:168
uint16_t m_parttotal
Definition: programdata.h:158
virtual uint InsertDB(MSqlQuery &query, uint chanid, bool recording=false) const
Insert Callback function when Allow Re-record is pressed in Watch Recordings.
DBEvent & operator=(const DBEvent &other)
uint GetOverlappingPrograms(MSqlQuery &query, uint chanid, std::vector< DBEvent > &programs) const
uint m_episode
Definition: programdata.h:173
QDateTime m_endtime
Definition: programdata.h:153
bool MoveOutOfTheWayDB(MSqlQuery &query, uint chanid, const DBEvent &prog) const
QString m_syndicatedepisodenumber
Definition: programdata.h:159
QString m_description
Definition: programdata.h:150
Role m_role
Definition: programdata.h:68
uint InsertPersonDB(MSqlQuery &query) const
QString m_character
Definition: programdata.h:71
uint InsertCreditsDB(MSqlQuery &query, uint personid, uint roleid, uint chanid, const QDateTime &starttime, bool recording=false) const
DBPerson(const DBPerson &other)
Definition: programdata.cpp:60
int m_priority
Definition: programdata.h:70
uint GetRoleDB(MSqlQuery &query) const
uint GetPersonDB(MSqlQuery &query) const
DBPerson & operator=(const DBPerson &rhs)
Definition: programdata.cpp:70
QString GetRole(void) const
QString toString(void) const
uint InsertDB(MSqlQuery &query, uint chanid, const QDateTime &starttime, bool recording=false) const
QString m_name
Definition: programdata.h:69
bool InsertRoleDB(MSqlQuery &query) 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:838
QVariant value(int i) const
Definition: mythdbcon.h:204
int numRowsAffected() const
Definition: mythdbcon.h:217
bool isActive(void) const
Definition: mythdbcon.h:215
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:619
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:889
int at(void) const
Definition: mythdbcon.h:221
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:813
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:551
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:225
ProgInfo & operator=(const ProgInfo &other)
void Squeeze(void) override
QString m_clumpidx
Definition: programdata.h:250
QString m_colorcode
Definition: programdata.h:249
uint InsertDB(MSqlQuery &query, uint chanid, bool recording=false) const override
Insert a single entry into the "program" database.
QString m_title_pronounce
Definition: programdata.h:247
QString m_showtype
Definition: programdata.h:248
QString m_channel
Definition: programdata.h:244
QString m_startts
Definition: programdata.h:245
QString m_endts
Definition: programdata.h:246
QString m_clumpmax
Definition: programdata.h:251
static void HandlePrograms(uint sourceid, QMap< QString, QList< ProgInfo > > &proglist)
Called from mythfilldatabase to bulk insert data into the program database.
static int fix_end_times(void)
static bool ClearDataBySource(uint sourceid, const QDateTime &from, const QDateTime &to, bool use_channel_time_offset)
static bool DeleteOverlaps(MSqlQuery &query, uint chanid, const ProgInfo &pi)
static void FixProgramList(QList< ProgInfo * > &fixlist)
static bool ClearDataByChannel(uint chanid, const QDateTime &from, const QDateTime &to, bool use_channel_time_offset)
static bool IsUnchanged(MSqlQuery &query, uint chanid, const ProgInfo &pi)
unsigned int uint
Definition: compat.h:60
@ kUnknown
Unprocessable file type.
Definition: imagetypes.h:35
unsigned short uint16_t
Definition: iso6937tables.h:3
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
QDateTime as_utc(const QDateTime &old_dt)
Returns copy of QDateTime with TimeSpec set to UTC.
Definition: mythdate.cpp:28
@ ISODate
Default UTC.
Definition: mythdate.h:17
def rating(profile, smoonURL, gate)
Definition: scan.py:36
static bool program_exists(MSqlQuery &query, uint chanid, const QDateTime &st)
static void add_genres(MSqlQuery &query, const QStringList &genres, uint chanid, const QDateTime &starttime)
Definition: programdata.cpp:39
static bool delete_program(MSqlQuery &query, uint chanid, const QDateTime &st)
static bool start_time_less_than(const DBEvent *a, const DBEvent *b)
static const std::array< const std::string, DBPerson::kGuest+1 > roles
Definition: programdata.cpp:22
static QString denullify(const QString &str)
Definition: programdata.cpp:29
static int score_match(const QString &a, const QString &b)
static bool change_program(MSqlQuery &query, uint chanid, const QDateTime &st, const QDateTime &new_st, const QDateTime &new_end)
static int score_words(const QStringList &al, const QStringList &bl)
static int change_record(MSqlQuery &query, uint chanid, const QDateTime &old_starttime, const QDateTime &new_starttime)
std::vector< DBPerson > DBCredits
Definition: programdata.h:73
QString myth_category_type_to_string(ProgramInfo::CategoryType category_type)
ProgramInfo::CategoryType string_to_myth_category_type(const QString &category_type)
@ kNoSearch
@ kSingleRecord