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