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