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 overlap = programs[i].m_starttime.secsTo(m_endtime);
588 else if (m_starttime > programs[i].m_starttime)
589 overlap = m_starttime.secsTo(programs[i].m_endtime);
590 else
591 {
592 if (m_endtime <= programs[i].m_endtime)
593 overlap = m_starttime.secsTo(m_endtime);
594 else
595 overlap = m_starttime.secsTo(programs[i].m_endtime);
596 }
597
598 /* scale the score depending on the overlap length
599 * full score is preserved if the overlap is at least 1/2 of the length
600 * of the shorter program */
601 if (overlap > 0)
602 {
603 /* crappy providers apparently have events without duration
604 * ensure that the minimal duration is 2 second to avoid
605 * multiplying and more importantly dividing by zero */
606 int min_dur = std::max(2, std::min(duration, duration_loop));
607 overlap = std::min(overlap, min_dur/2);
608 mv *= overlap * 2;
609 mv /= min_dur;
610 }
611 else
612 {
613 LOG(VB_GENERAL, LOG_ERR,
614 QString("Unexpected result: shows don't "
615 "overlap\n\t%1: %2 - %3\n\t%4: %5 - %6")
616 .arg(m_title.left(35), 35)
617 .arg(m_starttime.toString(Qt::ISODate),
618 m_endtime.toString(Qt::ISODate))
619 .arg(programs[i].m_title.left(35), 35)
620 .arg(programs[i].m_starttime.toString(Qt::ISODate),
621 programs[i].m_endtime.toString(Qt::ISODate))
622 );
623 }
624
625 if (mv > match_val)
626 {
627 LOG(VB_EIT, LOG_DEBUG,
628 QString("GM : '%1' new best match '%2' with score %3")
629 .arg(m_title.left(35),
630 programs[i].m_title.left(35),
631 QString::number(mv)));
632 bestmatch = i;
633 match_val = mv;
634 }
635 }
636
637 return match_val;
638}
639
641 MSqlQuery &q, uint chanid, const std::vector<DBEvent> &p, int match) const
642{
643 // Adjust/delete overlaps;
644 bool ok = true;
645 for (size_t i = 0; i < p.size(); ++i)
646 {
647 if (i != (uint)match)
648 ok &= MoveOutOfTheWayDB(q, chanid, p[i]);
649 }
650
651 // If we failed to move programs out of the way, don't insert new ones..
652 if (!ok)
653 {
654 LOG(VB_EIT, LOG_DEBUG,
655 QString("EIT: cannot insert '%1' MoveOutOfTheWayDB failed")
656 .arg(m_title.left(35)));
657 return 0;
658 }
659
660 // No match, insert current item
661 if ((match < 0) || ((uint)match >= p.size()))
662 {
663 LOG(VB_EIT, LOG_DEBUG,
664 QString("EIT: insert '%1'")
665 .arg(m_title.left(35)));
666 return InsertDB(q, chanid);
667 }
668
669 // Changing a starttime of a program that is being recorded can
670 // start another recording of the same program.
671 // Therefore we skip updates that change a starttime in the past
672 // unless the endtime is later.
673 if (m_starttime != p[match].m_starttime)
674 {
675 QDateTime now = QDateTime::currentDateTimeUtc();
676 if (m_starttime < now && m_endtime <= p[match].m_endtime)
677 {
678 LOG(VB_EIT, LOG_DEBUG,
679 QString("EIT: skip '%1' starttime is in the past")
680 .arg(m_title.left(35)));
681 return 0;
682 }
683 }
684
685 // Update matched item with current data
686 LOG(VB_EIT, LOG_DEBUG,
687 QString("EIT: update '%1' with '%2'")
688 .arg(p[match].m_title.left(35),
689 m_title.left(35)));
690 return UpdateDB(q, chanid, p[match]);
691}
692
693// Update starttime in table record for single recordings
694// when the starttime of a program is changed.
695//
696// Return the number of rows affected:
697// 0 if program is not found in table record
698// 1 if program is found and updated
699//
700static int change_record(MSqlQuery &query, uint chanid,
701 const QDateTime &old_starttime,
702 const QDateTime &new_starttime)
703{
704 query.prepare("UPDATE record "
705 "SET starttime = :NEWSTARTTIME, "
706 " startdate = :NEWSTARTDATE "
707 "WHERE chanid = :CHANID "
708 "AND type = :TYPE "
709 "AND search = :SEARCH "
710 "AND starttime = :OLDSTARTTIME "
711 "AND startdate = :OLDSTARTDATE ");
712 query.bindValue(":CHANID", chanid);
713 query.bindValue(":TYPE", kSingleRecord);
714 query.bindValue(":SEARCH", kNoSearch);
715 query.bindValue(":OLDSTARTTIME", old_starttime.time());
716 query.bindValue(":OLDSTARTDATE", old_starttime.date());
717 query.bindValue(":NEWSTARTTIME", new_starttime.time());
718 query.bindValue(":NEWSTARTDATE", new_starttime.date());
719
720 int rows = 0;
721 if (!query.exec() || !query.isActive())
722 {
723 MythDB::DBError("Updating record", query);
724 }
725 else
726 {
727 rows = query.numRowsAffected();
728 }
729 if (rows > 0)
730 {
731 LOG(VB_EIT, LOG_DEBUG,
732 QString("EIT: Updated record: chanid:%1 old:%3 new:%4 rows:%5")
733 .arg(QString::number(chanid),
734 old_starttime.toString(Qt::ISODate),
735 new_starttime.toString(Qt::ISODate),
736 QString::number(rows)));
737 }
738 return rows;
739}
740
741// Update matched item with current data.
742//
744 MSqlQuery &query, uint chanid, const DBEvent &match) const
745{
746 QString ltitle = m_title;
747 QString lsubtitle = m_subtitle;
748 QString ldesc = m_description;
749 QString lcategory = m_category;
750 uint16_t lairdate = m_airdate;
751 QString lprogramId = m_programId;
752 QString lseriesId = m_seriesId;
753 QString linetref = m_inetref;
754 QDate loriginalairdate = m_originalairdate;
755
756 // Update starttime also in database table record so that
757 // tables program and record remain consistent.
758 if (m_starttime != match.m_starttime)
759 {
760 QDateTime const &old_starttime = match.m_starttime;
761 QDateTime const &new_starttime = m_starttime;
762 change_record(query, chanid, old_starttime, new_starttime);
763
764 LOG(VB_EIT, LOG_DEBUG,
765 QString("EIT: (U) change starttime from %1 to %2 for chanid:%3 program '%4' ")
766 .arg(old_starttime.toString(Qt::ISODate),
767 new_starttime.toString(Qt::ISODate),
768 QString::number(chanid),
769 m_title.left(35)));
770 }
771
772 if (ltitle.isEmpty() && !match.m_title.isEmpty())
773 ltitle = match.m_title;
774
775 if (lsubtitle.isEmpty() && !match.m_subtitle.isEmpty())
776 lsubtitle = match.m_subtitle;
777
778 if (ldesc.isEmpty() && !match.m_description.isEmpty())
779 ldesc = match.m_description;
780
781 if (lcategory.isEmpty() && !match.m_category.isEmpty())
782 lcategory = match.m_category;
783
784 if (!lairdate && match.m_airdate)
785 lairdate = match.m_airdate;
786
787 if (!loriginalairdate.isValid() && match.m_originalairdate.isValid())
788 loriginalairdate = match.m_originalairdate;
789
790 if (lprogramId.isEmpty() && !match.m_programId.isEmpty())
791 lprogramId = match.m_programId;
792
793 if (lseriesId.isEmpty() && !match.m_seriesId.isEmpty())
794 lseriesId = match.m_seriesId;
795
796 if (linetref.isEmpty() && !match.m_inetref.isEmpty())
797 linetref= match.m_inetref;
798
800 if (!m_categoryType && match.m_categoryType)
801 tmp = match.m_categoryType;
802
803 QString lcattype = myth_category_type_to_string(tmp);
804
805 unsigned char lsubtype = m_subtitleType | match.m_subtitleType;
806 unsigned char laudio = m_audioProps | match.m_audioProps;
807 unsigned char lvideo = m_videoProps | match.m_videoProps;
808
809 uint lseason = match.m_season;
810 uint lepisode = match.m_episode;
811 uint lepisodeTotal = match.m_totalepisodes;
812
814 {
815 lseason = m_season;
816 lepisode = m_episode;
817 lepisodeTotal = m_totalepisodes;
818 }
819
820 uint lpartnumber = match.m_partnumber;
821 uint lparttotal = match.m_parttotal;
822
824 {
825 lpartnumber = m_partnumber;
826 lparttotal = m_parttotal;
827 }
828
829 bool lpreviouslyshown = m_previouslyshown || match.m_previouslyshown;
830
831 uint32_t llistingsource = m_listingsource | match.m_listingsource;
832
833 QString lsyndicatedepisodenumber = m_syndicatedepisodenumber;
834 if (lsyndicatedepisodenumber.isEmpty() &&
835 !match.m_syndicatedepisodenumber.isEmpty())
836 lsyndicatedepisodenumber = match.m_syndicatedepisodenumber;
837
838 query.prepare(
839 "UPDATE program "
840 "SET title = :TITLE, subtitle = :SUBTITLE, "
841 " description = :DESC, "
842 " category = :CATEGORY, category_type = :CATTYPE, "
843 " starttime = :STARTTIME, endtime = :ENDTIME, "
844 " closecaptioned = :CC, subtitled = :HASSUBTITLES, "
845 " stereo = :STEREO, hdtv = :HDTV, "
846 " subtitletypes = :SUBTYPE, "
847 " audioprop = :AUDIOPROP, videoprop = :VIDEOPROP, "
848 " season = :SEASON, "
849 " episode = :EPISODE, totalepisodes = :TOTALEPS, "
850 " partnumber = :PARTNO, parttotal = :PARTTOTAL, "
851 " syndicatedepisodenumber = :SYNDICATENO, "
852 " airdate = :AIRDATE, originalairdate=:ORIGAIRDATE, "
853 " listingsource = :LSOURCE, "
854 " seriesid = :SERIESID, programid = :PROGRAMID, "
855 " previouslyshown = :PREVSHOWN, inetref = :INETREF "
856 "WHERE chanid = :CHANID AND "
857 " starttime = :OLDSTART ");
858
859 query.bindValue(":CHANID", chanid);
860 query.bindValue(":OLDSTART", match.m_starttime);
861 query.bindValue(":TITLE", denullify(ltitle));
862 query.bindValue(":SUBTITLE", denullify(lsubtitle));
863 query.bindValue(":DESC", denullify(ldesc));
864 query.bindValue(":CATEGORY", denullify(lcategory));
865 query.bindValue(":CATTYPE", lcattype);
866 query.bindValue(":STARTTIME", m_starttime);
867 query.bindValue(":ENDTIME", m_endtime);
868 query.bindValue(":CC", (lsubtype & SUB_HARDHEAR) != 0);
869 query.bindValue(":HASSUBTITLES",(lsubtype & SUB_NORMAL) != 0);
870 query.bindValue(":STEREO", (laudio & AUD_STEREO) != 0);
871 query.bindValue(":HDTV", (lvideo & VID_HDTV) != 0);
872 query.bindValue(":SUBTYPE", lsubtype);
873 query.bindValue(":AUDIOPROP", laudio);
874 query.bindValue(":VIDEOPROP", lvideo);
875 query.bindValue(":SEASON", lseason);
876 query.bindValue(":EPISODE", lepisode);
877 query.bindValue(":TOTALEPS", lepisodeTotal);
878 query.bindValue(":PARTNO", lpartnumber);
879 query.bindValue(":PARTTOTAL", lparttotal);
880 query.bindValue(":SYNDICATENO", denullify(lsyndicatedepisodenumber));
881 query.bindValue(":AIRDATE", lairdate ? QString::number(lairdate) : "0000");
882 query.bindValue(":ORIGAIRDATE", loriginalairdate);
883 query.bindValue(":LSOURCE", llistingsource);
884 query.bindValue(":SERIESID", denullify(lseriesId));
885 query.bindValue(":PROGRAMID", denullify(lprogramId));
886 query.bindValue(":PREVSHOWN", lpreviouslyshown);
887 query.bindValue(":INETREF", denullify(linetref));
888
889 if (!query.exec())
890 {
891 MythDB::DBError("UpdateDB", query);
892 return 0;
893 }
894
895 if (m_credits)
896 {
897 for (auto & credit : *m_credits)
898 credit.InsertDB(query, chanid, m_starttime);
899 }
900
901 for (const auto & rating : std::as_const(m_ratings))
902 {
903 query.prepare(
904 "INSERT IGNORE INTO programrating "
905 " ( chanid, starttime, `system`, rating) "
906 "VALUES (:CHANID, :START, :SYS, :RATING)");
907 query.bindValue(":CHANID", chanid);
908 query.bindValue(":START", m_starttime);
909 query.bindValue(":SYS", rating.m_system);
910 query.bindValue(":RATING", rating.m_rating);
911
912 if (!query.exec())
913 MythDB::DBError("programrating insert", query);
914 }
915
916 add_genres(query, m_genres, chanid, m_starttime);
917
918 return 1;
919}
920
921static bool delete_program(MSqlQuery &query, uint chanid, const QDateTime &st)
922{
923 query.prepare(
924 "DELETE from program "
925 "WHERE chanid = :CHANID AND "
926 " starttime = :STARTTIME");
927
928 query.bindValue(":CHANID", chanid);
929 query.bindValue(":STARTTIME", st);
930
931 if (!query.exec())
932 {
933 MythDB::DBError("delete_program", query);
934 return false;
935 }
936
937 query.prepare(
938 "DELETE from credits "
939 "WHERE chanid = :CHANID AND "
940 " starttime = :STARTTIME");
941
942 query.bindValue(":CHANID", chanid);
943 query.bindValue(":STARTTIME", st);
944
945 if (!query.exec())
946 {
947 MythDB::DBError("delete_credits", query);
948 return false;
949 }
950
951 query.prepare(
952 "DELETE from programrating "
953 "WHERE chanid = :CHANID AND "
954 " starttime = :STARTTIME");
955
956 query.bindValue(":CHANID", chanid);
957 query.bindValue(":STARTTIME", st);
958
959 if (!query.exec())
960 {
961 MythDB::DBError("delete_rating", query);
962 return false;
963 }
964
965 query.prepare(
966 "DELETE from programgenres "
967 "WHERE chanid = :CHANID AND "
968 " starttime = :STARTTIME");
969
970 query.bindValue(":CHANID", chanid);
971 query.bindValue(":STARTTIME", st);
972
973 if (!query.exec())
974 {
975 MythDB::DBError("delete_genres", query);
976 return false;
977 }
978
979 return true;
980}
981
982static bool program_exists(MSqlQuery &query, uint chanid, const QDateTime &st)
983{
984 query.prepare(
985 "SELECT title FROM program "
986 "WHERE chanid = :CHANID AND "
987 " starttime = :OLDSTART");
988 query.bindValue(":CHANID", chanid);
989 query.bindValue(":OLDSTART", st);
990 if (!query.exec())
991 {
992 MythDB::DBError("program_exists", query);
993 }
994 return query.next();
995}
996
997static bool change_program(MSqlQuery &query, uint chanid, const QDateTime &st,
998 const QDateTime &new_st, const QDateTime &new_end)
999{
1000 query.prepare(
1001 "UPDATE program "
1002 "SET starttime = :NEWSTART, "
1003 " endtime = :NEWEND "
1004 "WHERE chanid = :CHANID AND "
1005 " starttime = :OLDSTART");
1006
1007 query.bindValue(":CHANID", chanid);
1008 query.bindValue(":OLDSTART", st);
1009 query.bindValue(":NEWSTART", new_st);
1010 query.bindValue(":NEWEND", new_end);
1011
1012 if (!query.exec())
1013 {
1014 MythDB::DBError("change_program", query);
1015 return false;
1016 }
1017
1018 query.prepare(
1019 "UPDATE credits "
1020 "SET starttime = :NEWSTART "
1021 "WHERE chanid = :CHANID AND "
1022 " starttime = :OLDSTART");
1023
1024 query.bindValue(":CHANID", chanid);
1025 query.bindValue(":OLDSTART", st);
1026 query.bindValue(":NEWSTART", new_st);
1027
1028 if (!query.exec())
1029 {
1030 MythDB::DBError("change_credits", query);
1031 return false;
1032 }
1033
1034 query.prepare(
1035 "UPDATE programrating "
1036 "SET starttime = :NEWSTART "
1037 "WHERE chanid = :CHANID AND "
1038 " starttime = :OLDSTART");
1039
1040 query.bindValue(":CHANID", chanid);
1041 query.bindValue(":OLDSTART", st);
1042 query.bindValue(":NEWSTART", new_st);
1043
1044 if (!query.exec())
1045 {
1046 MythDB::DBError("change_rating", query);
1047 return false;
1048 }
1049
1050 query.prepare(
1051 "UPDATE programgenres "
1052 "SET starttime = :NEWSTART "
1053 "WHERE chanid = :CHANID AND "
1054 " starttime = :OLDSTART");
1055
1056 query.bindValue(":CHANID", chanid);
1057 query.bindValue(":OLDSTART", st);
1058 query.bindValue(":NEWSTART", new_st);
1059
1060 if (!query.exec())
1061 {
1062 MythDB::DBError("change_genres", query);
1063 return false;
1064 }
1065
1066 return true;
1067}
1068
1069// Move the program "prog" (3rd parameter) out of the way
1070// because it overlaps with our new program.
1072 MSqlQuery &query, uint chanid, const DBEvent &prog) const
1073{
1074 if (prog.m_starttime >= m_starttime && prog.m_endtime <= m_endtime)
1075 {
1076 // Old program completely inside our new program.
1077 // Delete the old program completely.
1078 LOG(VB_EIT, LOG_DEBUG,
1079 QString("EIT: delete '%1' %2 - %3")
1080 .arg(prog.m_title.left(35),
1081 prog.m_starttime.toString(Qt::ISODate),
1082 prog.m_endtime.toString(Qt::ISODate)));
1083 return delete_program(query, chanid, prog.m_starttime);
1084 }
1085 if (prog.m_starttime < m_starttime && prog.m_endtime > m_starttime)
1086 {
1087 // Old program starts before, but ends during or after our new program.
1088 // Adjust the end time of the old program to the start time
1089 // of our new program.
1090 // This will leave a hole after our new program when the end time of
1091 // the old program was after the end time of the new program!!
1092 LOG(VB_EIT, LOG_DEBUG,
1093 QString("EIT: change '%1' endtime to %2")
1094 .arg(prog.m_title.left(35),
1095 m_starttime.toString(Qt::ISODate)));
1096 return change_program(query, chanid, prog.m_starttime,
1097 prog.m_starttime, // Keep the start time
1098 m_starttime); // New end time is our start time
1099 }
1100 if (prog.m_starttime < m_endtime && prog.m_endtime > m_endtime)
1101 {
1102 // Old program starts during, but ends after our new program.
1103 // Adjust the starttime of the old program to the end time
1104 // of our new program.
1105 // If there is already a program starting just when our
1106 // new program ends we cannot move the old program
1107 // so then we have to delete the old program.
1108 if (program_exists(query, chanid, m_endtime))
1109 {
1110 LOG(VB_EIT, LOG_DEBUG,
1111 QString("EIT: delete '%1' %2 - %3")
1112 .arg(prog.m_title.left(35),
1113 prog.m_starttime.toString(Qt::ISODate),
1114 prog.m_endtime.toString(Qt::ISODate)));
1115 return delete_program(query, chanid, prog.m_starttime);
1116 }
1117 LOG(VB_EIT, LOG_DEBUG,
1118 QString("EIT: (M) change starttime from %1 to %2 for chanid:%3 program '%4' ")
1119 .arg(prog.m_starttime.toString(Qt::ISODate),
1120 m_endtime.toString(Qt::ISODate),
1121 QString::number(chanid),
1122 prog.m_title.left(35)));
1123
1124 // Update starttime in tables record and program so they stay consistent.
1125 change_record(query, chanid, prog.m_starttime, m_endtime);
1126 return change_program(query, chanid, prog.m_starttime,
1127 m_endtime, // New start time is our endtime
1128 prog.m_endtime); // Keep the end time
1129 }
1130 // must be non-conflicting...
1131 return true;
1132}
1133
1138 bool recording) const
1139{
1140 QString table = recording ? "recordedprogram" : "program";
1141
1142 query.prepare(QString(
1143 "REPLACE INTO %1 ("
1144 " chanid, title, subtitle, description, "
1145 " category, category_type, "
1146 " starttime, endtime, "
1147 " closecaptioned, stereo, hdtv, subtitled, "
1148 " subtitletypes, audioprop, videoprop, "
1149 " stars, partnumber, parttotal, "
1150 " syndicatedepisodenumber, "
1151 " airdate, originalairdate,listingsource, "
1152 " seriesid, programid, previouslyshown, "
1153 " season, episode, totalepisodes, "
1154 " inetref ) "
1155 "VALUES ("
1156 " :CHANID, :TITLE, :SUBTITLE, :DESCRIPTION, "
1157 " :CATEGORY, :CATTYPE, "
1158 " :STARTTIME, :ENDTIME, "
1159 " :CC, :STEREO, :HDTV, :HASSUBTITLES, "
1160 " :SUBTYPES, :AUDIOPROP, :VIDEOPROP, "
1161 " :STARS, :PARTNUMBER, :PARTTOTAL, "
1162 " :SYNDICATENO, "
1163 " :AIRDATE, :ORIGAIRDATE, :LSOURCE, "
1164 " :SERIESID, :PROGRAMID, :PREVSHOWN, "
1165 " :SEASON, :EPISODE, :TOTALEPISODES, "
1166 " :INETREF ) ").arg(table));
1167
1169 query.bindValue(":CHANID", chanid);
1170 query.bindValue(":TITLE", denullify(m_title));
1171 query.bindValue(":SUBTITLE", denullify(m_subtitle));
1172 query.bindValue(":DESCRIPTION", denullify(m_description));
1173 query.bindValue(":CATEGORY", denullify(m_category));
1174 query.bindValue(":CATTYPE", cattype);
1175 query.bindValue(":STARTTIME", m_starttime);
1176 query.bindValue(":ENDTIME", m_endtime);
1177 query.bindValue(":CC", (m_subtitleType & SUB_HARDHEAR) != 0);
1178 query.bindValue(":STEREO", (m_audioProps & AUD_STEREO) != 0);
1179 query.bindValue(":HDTV", (m_videoProps & VID_HDTV) != 0);
1180 query.bindValue(":HASSUBTITLES",(m_subtitleType & SUB_NORMAL) != 0);
1181 query.bindValue(":SUBTYPES", m_subtitleType);
1182 query.bindValue(":AUDIOPROP", m_audioProps);
1183 query.bindValue(":VIDEOPROP", m_videoProps);
1184 query.bindValue(":STARS", m_stars);
1185 query.bindValue(":PARTNUMBER", m_partnumber);
1186 query.bindValue(":PARTTOTAL", m_parttotal);
1187 query.bindValue(":SYNDICATENO", denullify(m_syndicatedepisodenumber));
1188 query.bindValue(":AIRDATE", m_airdate ? QString::number(m_airdate) : "0000");
1189 query.bindValue(":ORIGAIRDATE", m_originalairdate);
1190 query.bindValue(":LSOURCE", m_listingsource);
1191 query.bindValue(":SERIESID", denullify(m_seriesId));
1192 query.bindValue(":PROGRAMID", denullify(m_programId));
1193 query.bindValue(":PREVSHOWN", m_previouslyshown);
1194 query.bindValue(":SEASON", m_season);
1195 query.bindValue(":EPISODE", m_episode);
1196 query.bindValue(":TOTALEPISODES", m_totalepisodes);
1197 query.bindValue(":INETREF", denullify(m_inetref));
1198
1199 if (!query.exec())
1200 {
1201 MythDB::DBError("InsertDB", query);
1202 return 0;
1203 }
1204
1205 table = recording ? "recordedrating" : "programrating";
1206 for (const auto & rating : std::as_const(m_ratings))
1207 {
1208 query.prepare(QString(
1209 "INSERT IGNORE INTO %1 "
1210 " ( chanid, starttime, `system`, rating) "
1211 "VALUES (:CHANID, :START, :SYS, :RATING)").arg(table));
1212 query.bindValue(":CHANID", chanid);
1213 query.bindValue(":START", m_starttime);
1214 query.bindValue(":SYS", rating.m_system);
1215 query.bindValue(":RATING", rating.m_rating);
1216
1217 if (!query.exec())
1218 MythDB::DBError("programrating insert", query);
1219 }
1220
1221 if (m_credits)
1222 {
1223 for (auto & credit : *m_credits)
1224 credit.InsertDB(query, chanid, m_starttime, recording);
1225 }
1226
1227 add_genres(query, m_genres, chanid, m_starttime);
1228
1229 return 1;
1230}
1231
1233 DBEvent(other.m_listingsource)
1234{
1235 *this = other;
1236}
1237
1239{
1240 if (this == &other)
1241 return *this;
1242
1243 DBEvent::operator=(other);
1244
1245 m_channel = other.m_channel;
1246 m_startts = other.m_startts;
1247 m_endts = other.m_endts;
1249 m_showtype = other.m_showtype;
1250 m_colorcode = other.m_colorcode;
1251 m_clumpidx = other.m_clumpidx;
1252 m_clumpmax = other.m_clumpmax;
1253
1254 m_channel.squeeze();
1255 m_startts.squeeze();
1256 m_endts.squeeze();
1257 m_title_pronounce.squeeze();
1258 m_showtype.squeeze();
1259 m_colorcode.squeeze();
1260 m_clumpidx.squeeze();
1261 m_clumpmax.squeeze();
1262
1263 return *this;
1264}
1265
1267{
1269 m_channel.squeeze();
1270 m_startts.squeeze();
1271 m_endts.squeeze();
1272 m_title_pronounce.squeeze();
1273 m_showtype.squeeze();
1274 m_colorcode.squeeze();
1275 m_clumpidx.squeeze();
1276 m_clumpmax.squeeze();
1277}
1278
1293 bool recording) const
1294{
1295 QString table = recording ? "recordedprogram" : "program";
1296
1297 LOG(VB_XMLTV, LOG_DEBUG,
1298 QString("Inserting new %1 : %2 - %3 %4 %5")
1299 .arg(table,
1300 m_starttime.toString(Qt::ISODate),
1301 m_endtime.toString(Qt::ISODate),
1302 m_channel));
1303
1304 query.prepare(QString(
1305 "REPLACE INTO %1 ("
1306 " chanid, title, subtitle, description, "
1307 " category, category_type, "
1308 " starttime, endtime, "
1309 " closecaptioned, stereo, hdtv, subtitled, "
1310 " subtitletypes, audioprop, videoprop, "
1311 " partnumber, parttotal, "
1312 " syndicatedepisodenumber, "
1313 " airdate, originalairdate,listingsource, "
1314 " seriesid, programid, previouslyshown, "
1315 " stars, showtype, title_pronounce, colorcode, "
1316 " season, episode, totalepisodes, "
1317 " inetref ) "
1318 "VALUES("
1319 " :CHANID, :TITLE, :SUBTITLE, :DESCRIPTION, "
1320 " :CATEGORY, :CATTYPE, "
1321 " :STARTTIME, :ENDTIME, "
1322 " :CC, :STEREO, :HDTV, :HASSUBTITLES, "
1323 " :SUBTYPES, :AUDIOPROP, :VIDEOPROP, "
1324 " :PARTNUMBER, :PARTTOTAL, "
1325 " :SYNDICATENO, "
1326 " :AIRDATE, :ORIGAIRDATE, :LSOURCE, "
1327 " :SERIESID, :PROGRAMID, :PREVSHOWN, "
1328 " :STARS, :SHOWTYPE, :TITLEPRON, :COLORCODE, "
1329 " :SEASON, :EPISODE, :TOTALEPISODES, "
1330 " :INETREF )").arg(table));
1331
1333
1334 query.bindValue(":CHANID", chanid);
1335 query.bindValue(":TITLE", denullify(m_title));
1336 query.bindValue(":SUBTITLE", denullify(m_subtitle));
1337 query.bindValue(":DESCRIPTION", denullify(m_description));
1338 query.bindValue(":CATEGORY", denullify(m_category));
1339 query.bindValue(":CATTYPE", cattype);
1340 query.bindValue(":STARTTIME", m_starttime);
1341 query.bindValue(":ENDTIME", denullify(m_endtime));
1342 query.bindValue(":CC",
1343 (m_subtitleType & SUB_HARDHEAR) != 0);
1344 query.bindValue(":STEREO",
1345 (m_audioProps & AUD_STEREO) != 0);
1346 query.bindValue(":HDTV",
1347 (m_videoProps & VID_HDTV) != 0);
1348 query.bindValue(":HASSUBTITLES",
1349 (m_subtitleType & SUB_NORMAL) != 0);
1350 query.bindValue(":SUBTYPES", m_subtitleType);
1351 query.bindValue(":AUDIOPROP", m_audioProps);
1352 query.bindValue(":VIDEOPROP", m_videoProps);
1353 query.bindValue(":PARTNUMBER", m_partnumber);
1354 query.bindValue(":PARTTOTAL", m_parttotal);
1355 query.bindValue(":SYNDICATENO", denullify(m_syndicatedepisodenumber));
1356 query.bindValue(":AIRDATE", m_airdate ? QString::number(m_airdate):"0000");
1357 query.bindValue(":ORIGAIRDATE", m_originalairdate);
1358 query.bindValue(":LSOURCE", m_listingsource);
1359 query.bindValue(":SERIESID", denullify(m_seriesId));
1360 query.bindValue(":PROGRAMID", denullify(m_programId));
1361 query.bindValue(":PREVSHOWN", m_previouslyshown);
1362 query.bindValue(":STARS", m_stars);
1363 query.bindValue(":SHOWTYPE", denullify(m_showtype));
1364 query.bindValue(":TITLEPRON", denullify(m_title_pronounce));
1365 query.bindValue(":COLORCODE", denullify(m_colorcode));
1366 query.bindValue(":SEASON", m_season);
1367 query.bindValue(":EPISODE", m_episode);
1368 query.bindValue(":TOTALEPISODES", m_totalepisodes);
1369 query.bindValue(":INETREF", denullify(m_inetref));
1370
1371 if (!query.exec())
1372 {
1373 MythDB::DBError(table + " insert", query);
1374 return 0;
1375 }
1376
1377 table = recording ? "recordedrating" : "programrating";
1378 for (const auto & rating : m_ratings)
1379 {
1380 query.prepare(QString("INSERT IGNORE INTO %1 "
1381 " ( chanid, starttime, `system`, rating) "
1382 "VALUES (:CHANID, :START, :SYS, :RATING)")
1383 .arg(table));
1384 query.bindValue(":CHANID", chanid);
1385 query.bindValue(":START", m_starttime);
1386 query.bindValue(":SYS", rating.m_system);
1387 query.bindValue(":RATING", rating.m_rating);
1388
1389 if (!query.exec())
1390 MythDB::DBError(QString("%1 insert").arg(table), query);
1391 }
1392
1393 if (m_credits)
1394 {
1395 for (auto & credit : *m_credits)
1396 credit.InsertDB(query, chanid, m_starttime, recording);
1397 }
1398
1399 add_genres(query, m_genres, chanid, m_starttime);
1400
1401 return 1;
1402}
1403
1405 uint chanid, const QDateTime &from, const QDateTime &to,
1406 bool use_channel_time_offset)
1407{
1408 std::chrono::seconds secs = 0s;
1409 if (use_channel_time_offset)
1410 secs = ChannelUtil::GetTimeOffset(chanid);
1411
1412 QDateTime newFrom = from.addSecs(secs.count());
1413 QDateTime newTo = to.addSecs(secs.count());
1414
1416 query.prepare("DELETE FROM program "
1417 "WHERE starttime >= :FROM AND starttime < :TO "
1418 "AND chanid = :CHANID ;");
1419 query.bindValue(":FROM", newFrom);
1420 query.bindValue(":TO", newTo);
1421 query.bindValue(":CHANID", chanid);
1422 bool ok = query.exec();
1423
1424 query.prepare("DELETE FROM programrating "
1425 "WHERE starttime >= :FROM AND starttime < :TO "
1426 "AND chanid = :CHANID ;");
1427 query.bindValue(":FROM", newFrom);
1428 query.bindValue(":TO", newTo);
1429 query.bindValue(":CHANID", chanid);
1430 ok &= query.exec();
1431
1432 query.prepare("DELETE FROM credits "
1433 "WHERE starttime >= :FROM AND starttime < :TO "
1434 "AND chanid = :CHANID ;");
1435 query.bindValue(":FROM", newFrom);
1436 query.bindValue(":TO", newTo);
1437 query.bindValue(":CHANID", chanid);
1438 ok &= query.exec();
1439
1440 query.prepare("DELETE FROM programgenres "
1441 "WHERE starttime >= :FROM AND starttime < :TO "
1442 "AND chanid = :CHANID ;");
1443 query.bindValue(":FROM", newFrom);
1444 query.bindValue(":TO", newTo);
1445 query.bindValue(":CHANID", chanid);
1446 ok &= query.exec();
1447
1448 return ok;
1449}
1450
1452 uint sourceid, const QDateTime &from, const QDateTime &to,
1453 bool use_channel_time_offset)
1454{
1455 std::vector<uint> chanids = ChannelUtil::GetChanIDs(sourceid);
1456
1457 bool ok = true;
1458 auto cleardata = [&](uint chanid)
1459 { ok &= ClearDataByChannel(chanid, from, to, use_channel_time_offset); };
1460 std::for_each(chanids.cbegin(), chanids.cend(), cleardata);
1461 return ok;
1462}
1463
1464static bool start_time_less_than(const DBEvent *a, const DBEvent *b)
1465{
1466 return (a->m_starttime < b->m_starttime);
1467}
1468
1469void ProgramData::FixProgramList(QList<ProgInfo*> &fixlist)
1470{
1471 std::stable_sort(fixlist.begin(), fixlist.end(), start_time_less_than);
1472
1473 QList<ProgInfo*>::iterator it = fixlist.begin();
1474 while (true)
1475 {
1476 QList<ProgInfo*>::iterator cur = it;
1477 ++it;
1478
1479 // fill in miss stop times
1480 if ((*cur)->m_endts.isEmpty() || (*cur)->m_startts > (*cur)->m_endts)
1481 {
1482 if (it != fixlist.end())
1483 {
1484 (*cur)->m_endts = (*it)->m_startts;
1485 (*cur)->m_endtime = (*it)->m_starttime;
1486 }
1487 /* if its the last programme in the file then leave its
1488 endtime as 0000-00-00 00:00:00 so we can find it easily in
1489 fix_end_times() */
1490 }
1491
1492 if (it == fixlist.end())
1493 break;
1494
1495 // remove overlapping programs
1496 if ((*cur)->HasTimeConflict(**it))
1497 {
1498 QList<ProgInfo*>::iterator tokeep;
1499 QList<ProgInfo*>::iterator todelete;
1500
1501 if ((*cur)->m_endtime <= (*cur)->m_starttime)
1502 tokeep = it, todelete = cur; // NOLINT(bugprone-branch-clone)
1503 else if ((*it)->m_endtime <= (*it)->m_starttime)
1504 tokeep = cur, todelete = it; // NOLINT(bugprone-branch-clone)
1505 else if (!(*cur)->m_subtitle.isEmpty() &&
1506 (*it)->m_subtitle.isEmpty())
1507 tokeep = cur, todelete = it;
1508 else if (!(*it)->m_subtitle.isEmpty() &&
1509 (*cur)->m_subtitle.isEmpty())
1510 tokeep = it, todelete = cur;
1511 else if (!(*cur)->m_description.isEmpty() &&
1512 (*it)->m_description.isEmpty())
1513 tokeep = cur, todelete = it;
1514 else
1515 tokeep = it, todelete = cur;
1516
1517
1518 LOG(VB_XMLTV, LOG_DEBUG,
1519 QString("Removing conflicting program: %1 - %2 %3 %4")
1520 .arg((*todelete)->m_starttime.toString(Qt::ISODate),
1521 (*todelete)->m_endtime.toString(Qt::ISODate),
1522 (*todelete)->m_channel,
1523 (*todelete)->m_title));
1524
1525 LOG(VB_XMLTV, LOG_DEBUG,
1526 QString("Conflicted with : %1 - %2 %3 %4")
1527 .arg((*tokeep)->m_starttime.toString(Qt::ISODate),
1528 (*tokeep)->m_endtime.toString(Qt::ISODate),
1529 (*tokeep)->m_channel,
1530 (*tokeep)->m_title));
1531
1532 bool step_back = todelete == it;
1533 it = fixlist.erase(todelete);
1534 if (step_back)
1535 --it;
1536 }
1537 }
1538}
1539
1549 uint sourceid, QMap<QString, QList<ProgInfo> > &proglist)
1550{
1551 uint unchanged = 0;
1552 uint updated = 0;
1553
1555
1556 QMap<QString, QList<ProgInfo> >::const_iterator mapiter;
1557 for (mapiter = proglist.cbegin(); mapiter != proglist.cend(); ++mapiter)
1558 {
1559 if (mapiter.key().isEmpty())
1560 continue;
1561
1562 query.prepare(
1563 "SELECT chanid "
1564 "FROM channel "
1565 "WHERE deleted IS NULL AND "
1566 " sourceid = :ID AND "
1567 " xmltvid = :XMLTVID");
1568 query.bindValue(":ID", sourceid);
1569 query.bindValue(":XMLTVID", mapiter.key());
1570
1571 if (!query.exec())
1572 {
1573 MythDB::DBError("ProgramData::HandlePrograms", query);
1574 continue;
1575 }
1576
1577 std::vector<uint> chanids;
1578 while (query.next())
1579 chanids.push_back(query.value(0).toUInt());
1580
1581 if (chanids.empty())
1582 {
1583 LOG(VB_GENERAL, LOG_NOTICE,
1584 QString("Unknown xmltv channel identifier: %1"
1585 " - Skipping channel.").arg(mapiter.key()));
1586 continue;
1587 }
1588
1589 QList<ProgInfo> &list = proglist[mapiter.key()];
1590 QList<ProgInfo*> sortlist;
1591 // NOLINTNEXTLINE(modernize-loop-convert)
1592 for (auto it = list.begin(); it != list.end(); ++it)
1593 sortlist.push_back(&(*it));
1594
1595 FixProgramList(sortlist);
1596
1597 for (uint chanid : chanids)
1598 HandlePrograms(query, chanid, sortlist, unchanged, updated);
1599 }
1600
1601 LOG(VB_GENERAL, LOG_INFO,
1602 QString("Updated programs: %1 Unchanged programs: %2")
1603 .arg(updated) .arg(unchanged));
1604}
1605
1618 uint chanid,
1619 const QList<ProgInfo*> &sortlist,
1620 uint &unchanged,
1621 uint &updated)
1622{
1623 for (auto *pinfo : std::as_const(sortlist))
1624 {
1625 if (IsUnchanged(query, chanid, *pinfo))
1626 {
1627 unchanged++;
1628 continue;
1629 }
1630
1631 if (!DeleteOverlaps(query, chanid, *pinfo))
1632 continue;
1633
1634 updated += pinfo->InsertDB(query, chanid);
1635 }
1636}
1637
1639{
1640 int count = 0;
1641 QString chanid;
1642 QString starttime;
1643 QString endtime;
1644 QString querystr;
1645 MSqlQuery query1(MSqlQuery::InitCon());
1646 MSqlQuery query2(MSqlQuery::InitCon());
1647
1648 querystr = "SELECT chanid, starttime, endtime FROM program "
1649 "WHERE endtime = '0000-00-00 00:00:00' "
1650 "ORDER BY chanid, starttime;";
1651
1652 if (!query1.exec(querystr))
1653 {
1654 LOG(VB_GENERAL, LOG_ERR,
1655 QString("fix_end_times query failed: %1").arg(querystr));
1656 return -1;
1657 }
1658
1659 while (query1.next())
1660 {
1661 starttime = query1.value(1).toString();
1662 chanid = query1.value(0).toString();
1663 endtime = query1.value(2).toString();
1664
1665 querystr = QString("SELECT chanid, starttime, endtime FROM program "
1666 "WHERE starttime > '%1' "
1667 "AND chanid = '%2' "
1668 "ORDER BY starttime LIMIT 1;")
1669 .arg(starttime, chanid);
1670
1671 if (!query2.exec(querystr))
1672 {
1673 LOG(VB_GENERAL, LOG_ERR,
1674 QString("fix_end_times query failed: %1").arg(querystr));
1675 return -1;
1676 }
1677
1678 if (query2.next() && (endtime != query2.value(1).toString()))
1679 {
1680 count++;
1681 endtime = query2.value(1).toString();
1682 querystr = QString("UPDATE program SET "
1683 "endtime = '%2' WHERE (chanid = '%3' AND "
1684 "starttime = '%4');")
1685 .arg(endtime, chanid, starttime);
1686
1687 if (!query2.exec(querystr))
1688 {
1689 LOG(VB_GENERAL, LOG_ERR,
1690 QString("fix_end_times query failed: %1").arg(querystr));
1691 return -1;
1692 }
1693 }
1694 }
1695
1696 return count;
1697}
1698
1700 MSqlQuery &query, uint chanid, const ProgInfo &pi)
1701{
1702 query.prepare(
1703 "SELECT count(*) "
1704 "FROM program "
1705 "WHERE chanid = :CHANID AND "
1706 " starttime = :START AND "
1707 " endtime = :END AND "
1708 " title = :TITLE AND "
1709 " subtitle = :SUBTITLE AND "
1710 " description = :DESC AND "
1711 " category = :CATEGORY AND "
1712 " category_type = :CATEGORY_TYPE AND "
1713 " airdate = :AIRDATE AND "
1714 " stars >= (:STARS1 - 0.001) AND "
1715 " stars <= (:STARS2 + 0.001) AND "
1716 " previouslyshown = :PREVIOUSLYSHOWN AND "
1717 " title_pronounce = :TITLE_PRONOUNCE AND "
1718 " audioprop = :AUDIOPROP AND "
1719 " videoprop = :VIDEOPROP AND "
1720 " subtitletypes = :SUBTYPES AND "
1721 " partnumber = :PARTNUMBER AND "
1722 " parttotal = :PARTTOTAL AND "
1723 " seriesid = :SERIESID AND "
1724 " showtype = :SHOWTYPE AND "
1725 " colorcode = :COLORCODE AND "
1726 " syndicatedepisodenumber = :SYNDICATEDEPISODENUMBER AND "
1727 " programid = :PROGRAMID AND "
1728 " season = :SEASON AND "
1729 " episode = :EPISODE AND "
1730 " totalepisodes = :TOTALEPISODES AND "
1731 " inetref = :INETREF");
1732
1733 QString cattype = myth_category_type_to_string(pi.m_categoryType);
1734
1735 query.bindValue(":CHANID", chanid);
1736 query.bindValue(":START", pi.m_starttime);
1737 query.bindValue(":END", pi.m_endtime);
1738 query.bindValue(":TITLE", denullify(pi.m_title));
1739 query.bindValue(":SUBTITLE", denullify(pi.m_subtitle));
1740 query.bindValue(":DESC", denullify(pi.m_description));
1741 query.bindValue(":CATEGORY", denullify(pi.m_category));
1742 query.bindValue(":CATEGORY_TYPE", cattype);
1743 query.bindValue(":AIRDATE", pi.m_airdate);
1744 query.bindValue(":STARS1", pi.m_stars);
1745 query.bindValue(":STARS2", pi.m_stars);
1746 query.bindValue(":PREVIOUSLYSHOWN", pi.m_previouslyshown);
1747 query.bindValue(":TITLE_PRONOUNCE", denullify(pi.m_title_pronounce));
1748 query.bindValue(":AUDIOPROP", pi.m_audioProps);
1749 query.bindValue(":VIDEOPROP", pi.m_videoProps);
1750 query.bindValue(":SUBTYPES", pi.m_subtitleType);
1751 query.bindValue(":PARTNUMBER", pi.m_partnumber);
1752 query.bindValue(":PARTTOTAL", pi.m_parttotal);
1753 query.bindValue(":SERIESID", denullify(pi.m_seriesId));
1754 query.bindValue(":SHOWTYPE", denullify(pi.m_showtype));
1755 query.bindValue(":COLORCODE", denullify(pi.m_colorcode));
1756 query.bindValue(":SYNDICATEDEPISODENUMBER",
1758 query.bindValue(":PROGRAMID", denullify(pi.m_programId));
1759 query.bindValue(":SEASON", pi.m_season);
1760 query.bindValue(":EPISODE", pi.m_episode);
1761 query.bindValue(":TOTALEPISODES", pi.m_totalepisodes);
1762 query.bindValue(":INETREF", denullify(pi.m_inetref));
1763
1764 if (query.exec() && query.next())
1765 return query.value(0).toUInt() > 0;
1766
1767 return false;
1768}
1769
1771 MSqlQuery &query, uint chanid, const ProgInfo &pi)
1772{
1773 if (VERBOSE_LEVEL_CHECK(VB_XMLTV, LOG_DEBUG))
1774 {
1775 // Get overlaps..
1776 query.prepare(
1777 "SELECT title,starttime,endtime "
1778 "FROM program "
1779 "WHERE chanid = :CHANID AND "
1780 " starttime >= :START AND "
1781 " starttime < :END;");
1782 query.bindValue(":CHANID", chanid);
1783 query.bindValue(":START", pi.m_starttime);
1784 query.bindValue(":END", pi.m_endtime);
1785
1786 if (!query.exec())
1787 return false;
1788
1789 while (query.next())
1790 {
1791 LOG(VB_XMLTV, LOG_DEBUG,
1792 QString("Removing existing program: %1 - %2 %3 %4")
1793 .arg(MythDate::as_utc(query.value(1).toDateTime()).toString(Qt::ISODate),
1794 MythDate::as_utc(query.value(2).toDateTime()).toString(Qt::ISODate),
1795 pi.m_channel,
1796 query.value(0).toString()));
1797 }
1798
1799 if (query.at() == QSql::BeforeFirstRow)
1800 {
1801 // Successful query, no results
1802 return true;
1803 }
1804 }
1805
1806 if (!ClearDataByChannel(chanid, pi.m_starttime, pi.m_endtime, false))
1807 {
1808 LOG(VB_XMLTV, LOG_ERR,
1809 QString("Program delete failed : %1 - %2 %3 %4")
1810 .arg(pi.m_starttime.toString(Qt::ISODate),
1811 pi.m_endtime.toString(Qt::ISODate),
1812 pi.m_channel,
1813 pi.m_title));
1814 return false;
1815 }
1816
1817 return true;
1818}
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:837
QVariant value(int i) const
Definition: mythdbcon.h:204
int numRowsAffected() const
Definition: mythdbcon.h:217
bool isActive(void) const
Definition: mythdbcon.h:215
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
int at(void) const
Definition: mythdbcon.h:221
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
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:226
ProgInfo & operator=(const ProgInfo &other)
void Squeeze(void) override
QString m_clumpidx
Definition: programdata.h:249
QString m_colorcode
Definition: programdata.h:248
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:246
QString m_showtype
Definition: programdata.h:247
QString m_channel
Definition: programdata.h:243
QString m_startts
Definition: programdata.h:244
QString m_endts
Definition: programdata.h:245
QString m_clumpmax
Definition: programdata.h:250
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: freesurround.h:24
static guint32 * tmp
Definition: goom_core.cpp:26
@ 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
STL namespace.
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