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 using namespace std;
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 char *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 = QStringLiteral("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
42  QStringList::const_iterator it = genres.constBegin();
43  for (; (it != genres.end()) &&
44  ((it - genres.constBegin()) < relevance.size()); ++it)
45  {
46  query.prepare(
47  "INSERT INTO programgenres "
48  " ( chanid, starttime, genre, relevance) "
49  "VALUES (:CHANID, :START, :genre, :relevance)");
50  query.bindValue(":CHANID", chanid);
51  query.bindValue(":START", starttime);
52  query.bindValue(":genre", *it);
53  query.bindValue(":relevance", relevance.at(it - genres.constBegin()));
54 
55  if (!query.exec())
56  MythDB::DBError("programgenres insert", query);
57  }
58 }
59 
61  m_role(other.m_role), m_name(other.m_name)
62 {
63  m_name.squeeze();
64 }
65 
66 DBPerson::DBPerson(Role role, const QString &name) :
67  m_role(role), m_name(name)
68 {
69  m_name.squeeze();
70 }
71 
72 DBPerson::DBPerson(const QString &role, const QString &name) :
73  m_role(kUnknown), m_name(name)
74 {
75  if (!role.isEmpty())
76  {
77  for (size_t i = 0; i < sizeof(roles) / sizeof(char *); i++)
78  {
79  if (role == QString(roles[i]))
80  m_role = (Role) i;
81  }
82  }
83  m_name.squeeze();
84 }
85 
86 QString DBPerson::GetRole(void) const
87 {
88  if ((m_role < kActor) || (m_role > kGuest))
89  return "guest";
90  return roles[m_role];
91 }
92 
94  const QDateTime &starttime) const
95 {
96  uint personid = GetPersonDB(query);
97  if (!personid && InsertPersonDB(query))
98  personid = GetPersonDB(query);
99 
100  return InsertCreditsDB(query, personid, chanid, starttime);
101 }
102 
104 {
105  query.prepare(
106  "SELECT person "
107  "FROM people "
108  "WHERE name = :NAME");
109  query.bindValue(":NAME", m_name);
110 
111  if (!query.exec())
112  MythDB::DBError("get_person", query);
113  else if (query.next())
114  return query.value(0).toUInt();
115 
116  return 0;
117 }
118 
120 {
121  query.prepare(
122  "INSERT IGNORE INTO people (name) "
123  "VALUES (:NAME);");
124  query.bindValue(":NAME", m_name);
125 
126  if (query.exec())
127  return 1;
128 
129  MythDB::DBError("insert_person", query);
130  return 0;
131 }
132 
134  const QDateTime &starttime) const
135 {
136  if (!personid)
137  return 0;
138 
139  query.prepare(
140  "REPLACE INTO credits "
141  " ( person, chanid, starttime, role) "
142  "VALUES (:PERSON, :CHANID, :STARTTIME, :ROLE) ");
143  query.bindValue(":PERSON", personid);
144  query.bindValue(":CHANID", chanid);
145  query.bindValue(":STARTTIME", starttime);
146  query.bindValue(":ROLE", GetRole());
147 
148  if (query.exec())
149  return 1;
150 
151  MythDB::DBError("insert_credits", query);
152  return 0;
153 }
154 
156 {
157  if (this == &other)
158  return *this;
159 
160  m_title = other.m_title;
161  m_subtitle = other.m_subtitle;
163  m_category = other.m_category;
164  m_starttime = other.m_starttime;
165  m_endtime = other.m_endtime;
166  m_airdate = other.m_airdate;
168 
169  if (m_credits != other.m_credits)
170  {
171  if (m_credits)
172  {
173  delete m_credits;
174  m_credits = nullptr;
175  }
176 
177  if (other.m_credits)
178  {
179  m_credits = new DBCredits;
180  m_credits->insert(m_credits->end(),
181  other.m_credits->begin(),
182  other.m_credits->end());
183  }
184  }
185 
186  m_partnumber = other.m_partnumber;
187  m_parttotal = other.m_parttotal;
190  m_audioProps = other.m_audioProps;
191  m_videoProps = other.m_videoProps;
192  m_stars = other.m_stars;
194  m_seriesId = other.m_seriesId;
195  m_programId = other.m_programId;
196  m_inetref = other.m_inetref;
198  m_ratings = other.m_ratings;
200  m_season = other.m_season;
201  m_episode = other.m_episode;
203  m_genres = other.m_genres;
204 
205  Squeeze();
206 
207  return *this;
208 }
209 
211 {
212  m_title.squeeze();
213  m_subtitle.squeeze();
214  m_description.squeeze();
215  m_category.squeeze();
216  m_syndicatedepisodenumber.squeeze();
217  m_seriesId.squeeze();
218  m_programId.squeeze();
219  m_inetref.squeeze();
220 }
221 
222 void DBEvent::AddPerson(DBPerson::Role role, const QString &name)
223 {
224  if (!m_credits)
225  m_credits = new DBCredits;
226 
227  m_credits->push_back(DBPerson(role, name.simplified()));
228 }
229 
230 void DBEvent::AddPerson(const QString &role, const QString &name)
231 {
232  if (!m_credits)
233  m_credits = new DBCredits;
234 
235  m_credits->push_back(DBPerson(role, name.simplified()));
236 }
237 
238 bool DBEvent::HasTimeConflict(const DBEvent &o) const
239 {
240  return ((m_starttime <= o.m_starttime && o.m_starttime < m_endtime) ||
241  (o.m_endtime <= m_endtime && m_starttime < o.m_endtime));
242 }
243 
244 // Processing new EIT entry starts here
246  MSqlQuery &query, uint chanid, int match_threshold) const
247 {
248  // List the program that we are going to add
249  LOG(VB_EIT, LOG_DEBUG,
250  QString("EIT: new program: %1 %2 '%3' chanid %4")
251  .arg(m_starttime.toString(Qt::ISODate))
252  .arg(m_endtime.toString(Qt::ISODate))
253  .arg(m_title.left(35))
254  .arg(chanid));
255 
256  // Do not insert or update when the program is in the past
257  QDateTime now = QDateTime::currentDateTimeUtc();
258  if (m_endtime < now)
259  {
260  LOG(VB_EIT, LOG_DEBUG,
261  QString("EIT: skip '%1' endtime is in the past")
262  .arg(m_title.left(35)));
263  return 0;
264  }
265 
266  // Get all programs already in the database that overlap
267  // with our new program.
268  vector<DBEvent> programs;
269  uint count = GetOverlappingPrograms(query, chanid, programs);
270  int match = INT_MIN;
271  int i = -1;
272 
273  // If there are no programs already in the database that overlap
274  // with our new program then we can simply insert it in the database.
275  if (!count)
276  return InsertDB(query, chanid);
277 
278  // List all overlapping programs with start- and endtime.
279  for (uint j=0; j<count; j++)
280  {
281  LOG(VB_EIT, LOG_DEBUG,
282  QString("EIT: overlap[%1] : %2 %3 '%4'")
283  .arg(j)
284  .arg(programs[j].m_starttime.toString(Qt::ISODate))
285  .arg(programs[j].m_endtime.toString(Qt::ISODate))
286  .arg(programs[j].m_title.left(35)));
287  }
288 
289  // Determine which of the overlapping programs is a match with
290  // our new program; if we have a match then our new program is considered
291  // to be an update of the matching program.
292  // The 2nd parameter "i" is the index of the best matching program.
293  match = GetMatch(programs, i);
294 
295  // Update an existing program or insert a new program.
296  if (match >= match_threshold)
297  {
298  // We have a good match; update program[i] in the database
299  // with the new program data and move the overlapping programs
300  // out of the way.
301  LOG(VB_EIT, LOG_DEBUG,
302  QString("EIT: accept match[%1]: %2 '%3' vs. '%4'")
303  .arg(i).arg(match).arg(m_title.left(35))
304  .arg(programs[i].m_title.left(35)));
305  return UpdateDB(query, chanid, programs, i);
306  }
307 
308  // If we are here then either we have a match but the match is
309  // not good enough (the "i >= 0" case) or we did not find
310  // a match at all.
311  if (i >= 0)
312  {
313  LOG(VB_EIT, LOG_DEBUG,
314  QString("EIT: reject match[%1]: %2 '%3' vs. '%4'")
315  .arg(i).arg(match).arg(m_title.left(35))
316  .arg(programs[i].m_title.left(35)));
317  }
318 
319  // Move the overlapping programs out of the way and
320  // insert the new program.
321  return UpdateDB(query, chanid, programs, -1);
322 }
323 
324 // Get all programs in the database that overlap with our new program.
325 // We check for three ways in which we can have an overlap:
326 // (1) Start of old program is inside our new program:
327 // old program starts at or after our program AND
328 // old program starts before end of our program;
329 // e.g. new program s-------------e
330 // old program s-------------e
331 // or old program s-----e
332 // This is the STIME1/ETIME1 comparison.
333 // (2) End of old program is inside our new program:
334 // old program ends after our program starts AND
335 // old program ends before end of our program
336 // e.g. new program s-------------e
337 // old program s-------------e
338 // or old program s-----e
339 // This is the STIME2/ETIME2 comparison.
340 // (3) We can have a new program is "inside" the old program:
341 // old program starts before our program AND
342 // old program ends after end of our program
343 // e.g. new program s---------e
344 // old program s-----------------e
345 // This is the STIME3/ETIME3 comparison.
346 //
348  MSqlQuery &query, uint chanid, vector<DBEvent> &programs) const
349 {
350  uint count = 0;
351  query.prepare(
352  "SELECT title, subtitle, description, "
353  " category, category_type, "
354  " starttime, endtime, "
355  " subtitletypes+0,audioprop+0, videoprop+0, "
356  " seriesid, programid, "
357  " partnumber, parttotal, "
358  " syndicatedepisodenumber, "
359  " airdate, originalairdate, "
360  " previouslyshown,listingsource, "
361  " stars+0, "
362  " season, episode, totalepisodes, "
363  " inetref "
364  "FROM program "
365  "WHERE chanid = :CHANID AND "
366  " manualid = 0 AND "
367  " ( ( starttime >= :STIME1 AND starttime < :ETIME1 ) OR "
368  " ( endtime > :STIME2 AND endtime <= :ETIME2 ) OR "
369  " ( starttime < :STIME3 AND endtime > :ETIME3 ) )");
370  query.bindValue(":CHANID", chanid);
371  query.bindValue(":STIME1", m_starttime);
372  query.bindValue(":ETIME1", m_endtime);
373  query.bindValue(":STIME2", m_starttime);
374  query.bindValue(":ETIME2", m_endtime);
375  query.bindValue(":STIME3", m_starttime);
376  query.bindValue(":ETIME3", m_endtime);
377 
378  if (!query.exec())
379  {
380  MythDB::DBError("GetOverlappingPrograms 1", query);
381  return 0;
382  }
383 
384  while (query.next())
385  {
386  ProgramInfo::CategoryType category_type =
387  string_to_myth_category_type(query.value(4).toString());
388 
389  DBEvent prog(
390  query.value(0).toString(),
391  query.value(1).toString(),
392  query.value(2).toString(),
393  query.value(3).toString(),
394  category_type,
395  MythDate::as_utc(query.value(5).toDateTime()),
396  MythDate::as_utc(query.value(6).toDateTime()),
397  query.value(7).toUInt(),
398  query.value(8).toUInt(),
399  query.value(9).toUInt(),
400  query.value(19).toDouble(),
401  query.value(10).toString(),
402  query.value(11).toString(),
403  query.value(18).toUInt(),
404  query.value(20).toUInt(), // Season
405  query.value(21).toUInt(), // Episode
406  query.value(22).toUInt()); // Total Episodes
407 
408  prog.m_inetref = query.value(23).toString();
409  prog.m_partnumber = query.value(12).toUInt();
410  prog.m_parttotal = query.value(13).toUInt();
411  prog.m_syndicatedepisodenumber = query.value(14).toString();
412  prog.m_airdate = query.value(15).toUInt();
413  prog.m_originalairdate = query.value(16).toDate();
414  prog.m_previouslyshown = query.value(17).toBool();
415 
416  programs.push_back(prog);
417  count++;
418  }
419 
420  return count;
421 }
422 
423 
424 static int score_words(const QStringList &al, const QStringList &bl)
425 {
426  QStringList::const_iterator ait = al.begin();
427  QStringList::const_iterator bit = bl.begin();
428  int score = 0;
429  for (; (ait != al.end()) && (bit != bl.end()); ++ait)
430  {
431  QStringList::const_iterator bit2 = bit;
432  int dist = 0;
433  int bscore = 0;
434  for (; bit2 != bl.end(); ++bit2)
435  {
436  if (*ait == *bit)
437  {
438  bscore = max(1000, 2000 - (dist * 500));
439  // lower score for short words
440  if (ait->length() < 5)
441  bscore /= 5 - ait->length();
442  break;
443  }
444  dist++;
445  }
446  if (bscore && dist < 3)
447  {
448  for (int i = 0; (i < dist) && bit != bl.end(); i++)
449  ++bit;
450  }
451  score += bscore;
452  }
453 
454  return score / al.size();
455 }
456 
457 static int score_match(const QString &a, const QString &b)
458 {
459  if (a.isEmpty() || b.isEmpty())
460  return 0;
461  if (a == b)
462  return 1000;
463 
464  QString A = a.simplified().toUpper();
465  QString B = b.simplified().toUpper();
466  if (A == B)
467  return 1000;
468 
469  QStringList al, bl;
470  al = A.split(" ", QString::SkipEmptyParts);
471  if (al.isEmpty())
472  return 0;
473 
474  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;
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 (size_t i = 0; i < m_credits->size(); i++)
816  (*m_credits)[i].InsertDB(query, chanid, m_starttime);
817  }
818 
819  QList<EventRating>::const_iterator j = m_ratings.begin();
820  for (; j != m_ratings.end(); ++j)
821  {
822  query.prepare(
823  "INSERT IGNORE INTO programrating "
824  " ( chanid, starttime, system, rating) "
825  "VALUES (:CHANID, :START, :SYS, :RATING)");
826  query.bindValue(":CHANID", chanid);
827  query.bindValue(":START", m_starttime);
828  query.bindValue(":SYS", (*j).m_system);
829  query.bindValue(":RATING", (*j).m_rating);
830 
831  if (!query.exec())
832  MythDB::DBError("programrating insert", query);
833  }
834 
835  add_genres(query, m_genres, chanid, m_starttime);
836 
837  return 1;
838 }
839 
840 static bool delete_program(MSqlQuery &query, uint chanid, const QDateTime &st)
841 {
842  query.prepare(
843  "DELETE from program "
844  "WHERE chanid = :CHANID AND "
845  " starttime = :STARTTIME");
846 
847  query.bindValue(":CHANID", chanid);
848  query.bindValue(":STARTTIME", st);
849 
850  if (!query.exec())
851  {
852  MythDB::DBError("delete_program", query);
853  return false;
854  }
855 
856  query.prepare(
857  "DELETE from credits "
858  "WHERE chanid = :CHANID AND "
859  " starttime = :STARTTIME");
860 
861  query.bindValue(":CHANID", chanid);
862  query.bindValue(":STARTTIME", st);
863 
864  if (!query.exec())
865  {
866  MythDB::DBError("delete_credits", query);
867  return false;
868  }
869 
870  query.prepare(
871  "DELETE from programrating "
872  "WHERE chanid = :CHANID AND "
873  " starttime = :STARTTIME");
874 
875  query.bindValue(":CHANID", chanid);
876  query.bindValue(":STARTTIME", st);
877 
878  if (!query.exec())
879  {
880  MythDB::DBError("delete_rating", query);
881  return false;
882  }
883 
884  query.prepare(
885  "DELETE from programgenres "
886  "WHERE chanid = :CHANID AND "
887  " starttime = :STARTTIME");
888 
889  query.bindValue(":CHANID", chanid);
890  query.bindValue(":STARTTIME", st);
891 
892  if (!query.exec())
893  {
894  MythDB::DBError("delete_genres", query);
895  return false;
896  }
897 
898  return true;
899 }
900 
901 static bool program_exists(MSqlQuery &query, uint chanid, const QDateTime &st)
902 {
903  query.prepare(
904  "SELECT title FROM program "
905  "WHERE chanid = :CHANID AND "
906  " starttime = :OLDSTART");
907  query.bindValue(":CHANID", chanid);
908  query.bindValue(":OLDSTART", st);
909  if (!query.exec())
910  {
911  MythDB::DBError("program_exists", query);
912  }
913  return query.next();
914 }
915 
916 static bool change_program(MSqlQuery &query, uint chanid, const QDateTime &st,
917  const QDateTime &new_st, const QDateTime &new_end)
918 {
919  query.prepare(
920  "UPDATE program "
921  "SET starttime = :NEWSTART, "
922  " endtime = :NEWEND "
923  "WHERE chanid = :CHANID AND "
924  " starttime = :OLDSTART");
925 
926  query.bindValue(":CHANID", chanid);
927  query.bindValue(":OLDSTART", st);
928  query.bindValue(":NEWSTART", new_st);
929  query.bindValue(":NEWEND", new_end);
930 
931  if (!query.exec())
932  {
933  MythDB::DBError("change_program", query);
934  return false;
935  }
936 
937  query.prepare(
938  "UPDATE credits "
939  "SET starttime = :NEWSTART "
940  "WHERE chanid = :CHANID AND "
941  " starttime = :OLDSTART");
942 
943  query.bindValue(":CHANID", chanid);
944  query.bindValue(":OLDSTART", st);
945  query.bindValue(":NEWSTART", new_st);
946 
947  if (!query.exec())
948  {
949  MythDB::DBError("change_credits", query);
950  return false;
951  }
952 
953  query.prepare(
954  "UPDATE programrating "
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_rating", query);
966  return false;
967  }
968 
969  query.prepare(
970  "UPDATE programgenres "
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_genres", query);
982  return false;
983  }
984 
985  return true;
986 }
987 
988 // Move the program "prog" (3rd parameter) out of the way
989 // because it overlaps with our new program.
991  MSqlQuery &query, uint chanid, const DBEvent &prog) const
992 {
993  if (prog.m_starttime >= m_starttime && prog.m_endtime <= m_endtime)
994  {
995  // Old program completely inside our new program.
996  // Delete the old program completely.
997  LOG(VB_EIT, LOG_DEBUG,
998  QString("EIT: delete '%1' %2 - %3")
999  .arg(prog.m_title.left(35))
1000  .arg(prog.m_starttime.toString(Qt::ISODate))
1001  .arg(prog.m_endtime.toString(Qt::ISODate)));
1002  return delete_program(query, chanid, prog.m_starttime);
1003  }
1004  if (prog.m_starttime < m_starttime && prog.m_endtime > m_starttime)
1005  {
1006  // Old program starts before, but ends during or after our new program.
1007  // Adjust the end time of the old program to the start time
1008  // of our new program.
1009  // This will leave a hole after our new program when the end time of
1010  // the old program was after the end time of the new program!!
1011  LOG(VB_EIT, LOG_DEBUG,
1012  QString("EIT: change '%1' endtime to %2")
1013  .arg(prog.m_title.left(35))
1014  .arg(m_starttime.toString(Qt::ISODate)));
1015  return change_program(query, chanid, prog.m_starttime,
1016  prog.m_starttime, // Keep the start time
1017  m_starttime); // New end time is our start time
1018  }
1019  if (prog.m_starttime < m_endtime && prog.m_endtime > m_endtime)
1020  {
1021  // Old program starts during, but ends after our new program.
1022  // Adjust the starttime of the old program to the end time
1023  // of our new program.
1024  // If there is already a program starting just when our
1025  // new program ends we cannot move the old program
1026  // so then we have to delete the old program.
1027  if (program_exists(query, chanid, m_endtime))
1028  {
1029  LOG(VB_EIT, LOG_DEBUG,
1030  QString("EIT: delete '%1' %2 - %3")
1031  .arg(prog.m_title.left(35))
1032  .arg(prog.m_starttime.toString(Qt::ISODate))
1033  .arg(prog.m_endtime.toString(Qt::ISODate)));
1034  return delete_program(query, chanid, prog.m_starttime);
1035  }
1036  LOG(VB_EIT, LOG_DEBUG,
1037  QString("EIT: (M) change starttime from %1 to %2 for chanid:%3 program '%4' ")
1038  .arg(prog.m_starttime.toString(Qt::ISODate))
1039  .arg(m_endtime.toString(Qt::ISODate))
1040  .arg(chanid)
1041  .arg(prog.m_title.left(35)));
1042 
1043  // Update starttime in tables record and program so they stay consistent.
1044  change_record(query, chanid, prog.m_starttime, m_endtime);
1045  return change_program(query, chanid, prog.m_starttime,
1046  m_endtime, // New start time is our endtime
1047  prog.m_endtime); // Keep the end time
1048  }
1049  // must be non-conflicting...
1050  return true;
1051 }
1052 
1056 uint DBEvent::InsertDB(MSqlQuery &query, uint chanid) const
1057 {
1058  query.prepare(
1059  "REPLACE INTO program ("
1060  " chanid, title, subtitle, description, "
1061  " category, category_type, "
1062  " starttime, endtime, "
1063  " closecaptioned, stereo, hdtv, subtitled, "
1064  " subtitletypes, audioprop, videoprop, "
1065  " stars, partnumber, parttotal, "
1066  " syndicatedepisodenumber, "
1067  " airdate, originalairdate,listingsource, "
1068  " seriesid, programid, previouslyshown, "
1069  " season, episode, totalepisodes, "
1070  " inetref ) "
1071  "VALUES ("
1072  " :CHANID, :TITLE, :SUBTITLE, :DESCRIPTION, "
1073  " :CATEGORY, :CATTYPE, "
1074  " :STARTTIME, :ENDTIME, "
1075  " :CC, :STEREO, :HDTV, :HASSUBTITLES, "
1076  " :SUBTYPES, :AUDIOPROP, :VIDEOPROP, "
1077  " :STARS, :PARTNUMBER, :PARTTOTAL, "
1078  " :SYNDICATENO, "
1079  " :AIRDATE, :ORIGAIRDATE, :LSOURCE, "
1080  " :SERIESID, :PROGRAMID, :PREVSHOWN, "
1081  " :SEASON, :EPISODE, :TOTALEPISODES, "
1082  " :INETREF ) ");
1083 
1084  QString cattype = myth_category_type_to_string(m_categoryType);
1085  QString empty("");
1086  query.bindValue(":CHANID", chanid);
1087  query.bindValue(":TITLE", denullify(m_title));
1088  query.bindValue(":SUBTITLE", denullify(m_subtitle));
1089  query.bindValue(":DESCRIPTION", denullify(m_description));
1090  query.bindValue(":CATEGORY", denullify(m_category));
1091  query.bindValue(":CATTYPE", cattype);
1092  query.bindValue(":STARTTIME", m_starttime);
1093  query.bindValue(":ENDTIME", m_endtime);
1094  query.bindValue(":CC", (m_subtitleType & SUB_HARDHEAR) != 0);
1095  query.bindValue(":STEREO", (m_audioProps & AUD_STEREO) != 0);
1096  query.bindValue(":HDTV", (m_videoProps & VID_HDTV) != 0);
1097  query.bindValue(":HASSUBTITLES",(m_subtitleType & SUB_NORMAL) != 0);
1098  query.bindValue(":SUBTYPES", m_subtitleType);
1099  query.bindValue(":AUDIOPROP", m_audioProps);
1100  query.bindValue(":VIDEOPROP", m_videoProps);
1101  query.bindValue(":STARS", m_stars);
1102  query.bindValue(":PARTNUMBER", m_partnumber);
1103  query.bindValue(":PARTTOTAL", m_parttotal);
1104  query.bindValue(":SYNDICATENO", denullify(m_syndicatedepisodenumber));
1105  query.bindValue(":AIRDATE", m_airdate ? QString::number(m_airdate) : "0000");
1106  query.bindValue(":ORIGAIRDATE", m_originalairdate);
1107  query.bindValue(":LSOURCE", m_listingsource);
1108  query.bindValue(":SERIESID", denullify(m_seriesId));
1109  query.bindValue(":PROGRAMID", denullify(m_programId));
1110  query.bindValue(":PREVSHOWN", m_previouslyshown);
1111  query.bindValue(":SEASON", m_season);
1112  query.bindValue(":EPISODE", m_episode);
1113  query.bindValue(":TOTALEPISODES", m_totalepisodes);
1114  query.bindValue(":INETREF", m_inetref);
1115 
1116  if (!query.exec())
1117  {
1118  MythDB::DBError("InsertDB", query);
1119  return 0;
1120  }
1121 
1122  QList<EventRating>::const_iterator j = m_ratings.begin();
1123  for (; j != m_ratings.end(); ++j)
1124  {
1125  query.prepare(
1126  "INSERT IGNORE INTO programrating "
1127  " ( chanid, starttime, system, rating) "
1128  "VALUES (:CHANID, :START, :SYS, :RATING)");
1129  query.bindValue(":CHANID", chanid);
1130  query.bindValue(":START", m_starttime);
1131  query.bindValue(":SYS", (*j).m_system);
1132  query.bindValue(":RATING", (*j).m_rating);
1133 
1134  if (!query.exec())
1135  MythDB::DBError("programrating insert", query);
1136  }
1137 
1138  if (m_credits)
1139  {
1140  for (size_t i = 0; i < m_credits->size(); i++)
1141  (*m_credits)[i].InsertDB(query, chanid, m_starttime);
1142  }
1143 
1144  add_genres(query, m_genres, chanid, m_starttime);
1145 
1146  return 1;
1147 }
1148 
1150  DBEvent(other.m_listingsource)
1151 {
1152  *this = other;
1153 }
1154 
1156 {
1157  if (this == &other)
1158  return *this;
1159 
1160  DBEvent::operator=(other);
1161 
1162  m_channel = other.m_channel;
1163  m_startts = other.m_startts;
1164  m_endts = other.m_endts;
1166  m_showtype = other.m_showtype;
1167  m_colorcode = other.m_colorcode;
1168  m_clumpidx = other.m_clumpidx;
1169  m_clumpmax = other.m_clumpmax;
1170 
1171  m_channel.squeeze();
1172  m_startts.squeeze();
1173  m_endts.squeeze();
1174  m_title_pronounce.squeeze();
1175  m_showtype.squeeze();
1176  m_colorcode.squeeze();
1177  m_clumpidx.squeeze();
1178  m_clumpmax.squeeze();
1179 
1180  return *this;
1181 }
1182 
1184 {
1185  DBEvent::Squeeze();
1186  m_channel.squeeze();
1187  m_startts.squeeze();
1188  m_endts.squeeze();
1189  m_title_pronounce.squeeze();
1190  m_showtype.squeeze();
1191  m_colorcode.squeeze();
1192  m_clumpidx.squeeze();
1193  m_clumpmax.squeeze();
1194 }
1195 
1208 uint ProgInfo::InsertDB(MSqlQuery &query, uint chanid) const
1209 {
1210  LOG(VB_XMLTV, LOG_INFO,
1211  QString("Inserting new program : %1 - %2 %3 %4")
1212  .arg(m_starttime.toString(Qt::ISODate))
1213  .arg(m_endtime.toString(Qt::ISODate))
1214  .arg(m_channel)
1215  .arg(m_title));
1216 
1217  query.prepare(
1218  "REPLACE INTO program ("
1219  " chanid, title, subtitle, description, "
1220  " category, category_type, "
1221  " starttime, endtime, "
1222  " closecaptioned, stereo, hdtv, subtitled, "
1223  " subtitletypes, audioprop, videoprop, "
1224  " partnumber, parttotal, "
1225  " syndicatedepisodenumber, "
1226  " airdate, originalairdate,listingsource, "
1227  " seriesid, programid, previouslyshown, "
1228  " stars, showtype, title_pronounce, colorcode, "
1229  " season, episode, totalepisodes, "
1230  " inetref ) "
1231 
1232  "VALUES("
1233  " :CHANID, :TITLE, :SUBTITLE, :DESCRIPTION, "
1234  " :CATEGORY, :CATTYPE, "
1235  " :STARTTIME, :ENDTIME, "
1236  " :CC, :STEREO, :HDTV, :HASSUBTITLES, "
1237  " :SUBTYPES, :AUDIOPROP, :VIDEOPROP, "
1238  " :PARTNUMBER, :PARTTOTAL, "
1239  " :SYNDICATENO, "
1240  " :AIRDATE, :ORIGAIRDATE, :LSOURCE, "
1241  " :SERIESID, :PROGRAMID, :PREVSHOWN, "
1242  " :STARS, :SHOWTYPE, :TITLEPRON, :COLORCODE, "
1243  " :SEASON, :EPISODE, :TOTALEPISODES, "
1244  " :INETREF )");
1245 
1246  QString cattype = myth_category_type_to_string(m_categoryType);
1247 
1248  query.bindValue(":CHANID", chanid);
1249  query.bindValue(":TITLE", denullify(m_title));
1250  query.bindValue(":SUBTITLE", denullify(m_subtitle));
1251  query.bindValue(":DESCRIPTION", denullify(m_description));
1252  query.bindValue(":CATEGORY", denullify(m_category));
1253  query.bindValue(":CATTYPE", cattype);
1254  query.bindValue(":STARTTIME", m_starttime);
1255  query.bindValue(":ENDTIME", denullify(m_endtime));
1256  query.bindValue(":CC",
1257  (m_subtitleType & SUB_HARDHEAR) != 0);
1258  query.bindValue(":STEREO",
1259  (m_audioProps & AUD_STEREO) != 0);
1260  query.bindValue(":HDTV",
1261  (m_videoProps & VID_HDTV) != 0);
1262  query.bindValue(":HASSUBTITLES",
1263  (m_subtitleType & SUB_NORMAL) != 0);
1264  query.bindValue(":SUBTYPES", m_subtitleType);
1265  query.bindValue(":AUDIOPROP", m_audioProps);
1266  query.bindValue(":VIDEOPROP", m_videoProps);
1267  query.bindValue(":PARTNUMBER", m_partnumber);
1268  query.bindValue(":PARTTOTAL", m_parttotal);
1269  query.bindValue(":SYNDICATENO", denullify(m_syndicatedepisodenumber));
1270  query.bindValue(":AIRDATE", m_airdate ? QString::number(m_airdate):"0000");
1271  query.bindValue(":ORIGAIRDATE", m_originalairdate);
1272  query.bindValue(":LSOURCE", m_listingsource);
1273  query.bindValue(":SERIESID", denullify(m_seriesId));
1274  query.bindValue(":PROGRAMID", denullify(m_programId));
1275  query.bindValue(":PREVSHOWN", m_previouslyshown);
1276  query.bindValue(":STARS", m_stars);
1277  query.bindValue(":SHOWTYPE", m_showtype);
1278  query.bindValue(":TITLEPRON", m_title_pronounce);
1279  query.bindValue(":COLORCODE", m_colorcode);
1280  query.bindValue(":SEASON", m_season);
1281  query.bindValue(":EPISODE", m_episode);
1282  query.bindValue(":TOTALEPISODES", m_totalepisodes);
1283  query.bindValue(":INETREF", m_inetref);
1284 
1285  if (!query.exec())
1286  {
1287  MythDB::DBError("program insert", query);
1288  return 0;
1289  }
1290 
1291  QList<EventRating>::const_iterator j = m_ratings.begin();
1292  for (; j != m_ratings.end(); ++j)
1293  {
1294  query.prepare(
1295  "INSERT IGNORE INTO programrating "
1296  " ( chanid, starttime, system, rating) "
1297  "VALUES (:CHANID, :START, :SYS, :RATING)");
1298  query.bindValue(":CHANID", chanid);
1299  query.bindValue(":START", m_starttime);
1300  query.bindValue(":SYS", (*j).m_system);
1301  query.bindValue(":RATING", (*j).m_rating);
1302 
1303  if (!query.exec())
1304  MythDB::DBError("programrating insert", query);
1305  }
1306 
1307  if (m_credits)
1308  {
1309  for (size_t i = 0; i < m_credits->size(); ++i)
1310  (*m_credits)[i].InsertDB(query, chanid, m_starttime);
1311  }
1312 
1313  add_genres(query, m_genres, chanid, m_starttime);
1314 
1315  return 1;
1316 }
1317 
1319  uint chanid, const QDateTime &from, const QDateTime &to,
1320  bool use_channel_time_offset)
1321 {
1322  int secs = 0;
1323  if (use_channel_time_offset)
1324  secs = ChannelUtil::GetTimeOffset(chanid) * 60;
1325 
1326  QDateTime newFrom = from.addSecs(secs);
1327  QDateTime newTo = to.addSecs(secs);
1328 
1329  MSqlQuery query(MSqlQuery::InitCon());
1330  query.prepare("DELETE FROM program "
1331  "WHERE starttime >= :FROM AND starttime < :TO "
1332  "AND chanid = :CHANID ;");
1333  query.bindValue(":FROM", newFrom);
1334  query.bindValue(":TO", newTo);
1335  query.bindValue(":CHANID", chanid);
1336  bool ok = query.exec();
1337 
1338  query.prepare("DELETE FROM programrating "
1339  "WHERE starttime >= :FROM AND starttime < :TO "
1340  "AND chanid = :CHANID ;");
1341  query.bindValue(":FROM", newFrom);
1342  query.bindValue(":TO", newTo);
1343  query.bindValue(":CHANID", chanid);
1344  ok &= query.exec();
1345 
1346  query.prepare("DELETE FROM credits "
1347  "WHERE starttime >= :FROM AND starttime < :TO "
1348  "AND chanid = :CHANID ;");
1349  query.bindValue(":FROM", newFrom);
1350  query.bindValue(":TO", newTo);
1351  query.bindValue(":CHANID", chanid);
1352  ok &= query.exec();
1353 
1354  query.prepare("DELETE FROM programgenres "
1355  "WHERE starttime >= :FROM AND starttime < :TO "
1356  "AND chanid = :CHANID ;");
1357  query.bindValue(":FROM", newFrom);
1358  query.bindValue(":TO", newTo);
1359  query.bindValue(":CHANID", chanid);
1360  ok &= query.exec();
1361 
1362  return ok;
1363 }
1364 
1366  uint sourceid, const QDateTime &from, const QDateTime &to,
1367  bool use_channel_time_offset)
1368 {
1369  vector<uint> chanids = ChannelUtil::GetChanIDs(sourceid);
1370 
1371  bool ok = true;
1372  for (size_t i = 0; i < chanids.size(); i++)
1373  ok &= ClearDataByChannel(chanids[i], from, to, use_channel_time_offset);
1374 
1375  return ok;
1376 }
1377 
1378 static bool start_time_less_than(const DBEvent *a, const DBEvent *b)
1379 {
1380  return (a->m_starttime < b->m_starttime);
1381 }
1382 
1383 void ProgramData::FixProgramList(QList<ProgInfo*> &fixlist)
1384 {
1385  std::stable_sort(fixlist.begin(), fixlist.end(), start_time_less_than);
1386 
1387  QList<ProgInfo*>::iterator it = fixlist.begin();
1388  while (true)
1389  {
1390  QList<ProgInfo*>::iterator cur = it;
1391  ++it;
1392 
1393  // fill in miss stop times
1394  if ((*cur)->m_endts.isEmpty() || (*cur)->m_startts > (*cur)->m_endts)
1395  {
1396  if (it != fixlist.end())
1397  {
1398  (*cur)->m_endts = (*it)->m_startts;
1399  (*cur)->m_endtime = (*it)->m_starttime;
1400  }
1401  /* if its the last programme in the file then leave its
1402  endtime as 0000-00-00 00:00:00 so we can find it easily in
1403  fix_end_times() */
1404  }
1405 
1406  if (it == fixlist.end())
1407  break;
1408 
1409  // remove overlapping programs
1410  if ((*cur)->HasTimeConflict(**it))
1411  {
1412  QList<ProgInfo*>::iterator tokeep, todelete;
1413 
1414  if ((*cur)->m_endtime <= (*cur)->m_starttime)
1415  tokeep = it, todelete = cur;
1416  else if ((*it)->m_endtime <= (*it)->m_starttime)
1417  tokeep = cur, todelete = it;
1418  else if (!(*cur)->m_subtitle.isEmpty() &&
1419  (*it)->m_subtitle.isEmpty())
1420  tokeep = cur, todelete = it;
1421  else if (!(*it)->m_subtitle.isEmpty() &&
1422  (*cur)->m_subtitle.isEmpty())
1423  tokeep = it, todelete = cur;
1424  else if (!(*cur)->m_description.isEmpty() &&
1425  (*it)->m_description.isEmpty())
1426  tokeep = cur, todelete = it;
1427  else
1428  tokeep = it, todelete = cur;
1429 
1430 
1431  LOG(VB_XMLTV, LOG_INFO,
1432  QString("Removing conflicting program: %1 - %2 %3 %4")
1433  .arg((*todelete)->m_starttime.toString(Qt::ISODate))
1434  .arg((*todelete)->m_endtime.toString(Qt::ISODate))
1435  .arg((*todelete)->m_channel)
1436  .arg((*todelete)->m_title));
1437 
1438  LOG(VB_XMLTV, LOG_INFO,
1439  QString("Conflicted with : %1 - %2 %3 %4")
1440  .arg((*tokeep)->m_starttime.toString(Qt::ISODate))
1441  .arg((*tokeep)->m_endtime.toString(Qt::ISODate))
1442  .arg((*tokeep)->m_channel)
1443  .arg((*tokeep)->m_title));
1444 
1445  bool step_back = todelete == it;
1446  it = fixlist.erase(todelete);
1447  if (step_back)
1448  --it;
1449  }
1450  }
1451 }
1452 
1462  uint sourceid, QMap<QString, QList<ProgInfo> > &proglist)
1463 {
1464  uint unchanged = 0, updated = 0;
1465 
1466  MSqlQuery query(MSqlQuery::InitCon());
1467 
1468  QMap<QString, QList<ProgInfo> >::const_iterator mapiter;
1469  for (mapiter = proglist.begin(); mapiter != proglist.end(); ++mapiter)
1470  {
1471  if (mapiter.key().isEmpty())
1472  continue;
1473 
1474  query.prepare(
1475  "SELECT chanid "
1476  "FROM channel "
1477  "WHERE 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  QList<ProgInfo>::iterator it = list.begin();
1503  for (; it != list.end(); ++it)
1504  sortlist.push_back(&(*it));
1505 
1506  FixProgramList(sortlist);
1507 
1508  for (size_t i = 0; i < chanids.size(); ++i)
1509  {
1510  HandlePrograms(query, chanids[i], sortlist, unchanged, updated);
1511  }
1512  }
1513 
1514  LOG(VB_GENERAL, LOG_INFO,
1515  QString("Updated programs: %1 Unchanged programs: %2")
1516  .arg(updated) .arg(unchanged));
1517 }
1518 
1531  uint chanid,
1532  const QList<ProgInfo*> &sortlist,
1533  uint &unchanged,
1534  uint &updated)
1535 {
1536  QList<ProgInfo*>::const_iterator it = sortlist.begin();
1537  for (; it != sortlist.end(); ++it)
1538  {
1539  if (IsUnchanged(query, chanid, **it))
1540  {
1541  unchanged++;
1542  continue;
1543  }
1544 
1545  if (!DeleteOverlaps(query, chanid, **it))
1546  continue;
1547 
1548  updated += (*it)->InsertDB(query, chanid);
1549  }
1550 }
1551 
1553 {
1554  int count = 0;
1555  QString chanid, starttime, endtime, querystr;
1556  MSqlQuery query1(MSqlQuery::InitCon()), query2(MSqlQuery::InitCon());
1557 
1558  querystr = "SELECT chanid, starttime, endtime FROM program "
1559  "WHERE endtime = '0000-00-00 00:00:00' "
1560  "ORDER BY chanid, starttime;";
1561 
1562  if (!query1.exec(querystr))
1563  {
1564  LOG(VB_GENERAL, LOG_ERR,
1565  QString("fix_end_times query failed: %1").arg(querystr));
1566  return -1;
1567  }
1568 
1569  while (query1.next())
1570  {
1571  starttime = query1.value(1).toString();
1572  chanid = query1.value(0).toString();
1573  endtime = query1.value(2).toString();
1574 
1575  querystr = QString("SELECT chanid, starttime, endtime FROM program "
1576  "WHERE starttime > '%1' "
1577  "AND chanid = '%2' "
1578  "ORDER BY starttime LIMIT 1;")
1579  .arg(starttime)
1580  .arg(chanid);
1581 
1582  if (!query2.exec(querystr))
1583  {
1584  LOG(VB_GENERAL, LOG_ERR,
1585  QString("fix_end_times query failed: %1").arg(querystr));
1586  return -1;
1587  }
1588 
1589  if (query2.next() && (endtime != query2.value(1).toString()))
1590  {
1591  count++;
1592  endtime = query2.value(1).toString();
1593  querystr = QString("UPDATE program SET "
1594  "endtime = '%2' WHERE (chanid = '%3' AND "
1595  "starttime = '%4');")
1596  .arg(endtime)
1597  .arg(chanid)
1598  .arg(starttime);
1599 
1600  if (!query2.exec(querystr))
1601  {
1602  LOG(VB_GENERAL, LOG_ERR,
1603  QString("fix_end_times query failed: %1").arg(querystr));
1604  return -1;
1605  }
1606  }
1607  }
1608 
1609  return count;
1610 }
1611 
1613  MSqlQuery &query, uint chanid, const ProgInfo &pi)
1614 {
1615  query.prepare(
1616  "SELECT count(*) "
1617  "FROM program "
1618  "WHERE chanid = :CHANID AND "
1619  " starttime = :START AND "
1620  " endtime = :END AND "
1621  " title = :TITLE AND "
1622  " subtitle = :SUBTITLE AND "
1623  " description = :DESC AND "
1624  " category = :CATEGORY AND "
1625  " category_type = :CATEGORY_TYPE AND "
1626  " airdate = :AIRDATE AND "
1627  " stars >= (:STARS1 - 0.001) AND "
1628  " stars <= (:STARS2 + 0.001) AND "
1629  " previouslyshown = :PREVIOUSLYSHOWN AND "
1630  " title_pronounce = :TITLE_PRONOUNCE AND "
1631  " audioprop = :AUDIOPROP AND "
1632  " videoprop = :VIDEOPROP AND "
1633  " subtitletypes = :SUBTYPES AND "
1634  " partnumber = :PARTNUMBER AND "
1635  " parttotal = :PARTTOTAL AND "
1636  " seriesid = :SERIESID AND "
1637  " showtype = :SHOWTYPE AND "
1638  " colorcode = :COLORCODE AND "
1639  " syndicatedepisodenumber = :SYNDICATEDEPISODENUMBER AND "
1640  " programid = :PROGRAMID AND "
1641  " season = :SEASON AND "
1642  " episode = :EPISODE AND "
1643  " totalepisodes = :TOTALEPISODES AND "
1644  " inetref = :INETREF");
1645 
1646  QString cattype = myth_category_type_to_string(pi.m_categoryType);
1647 
1648  query.bindValue(":CHANID", chanid);
1649  query.bindValue(":START", pi.m_starttime);
1650  query.bindValue(":END", pi.m_endtime);
1651  query.bindValue(":TITLE", denullify(pi.m_title));
1652  query.bindValue(":SUBTITLE", denullify(pi.m_subtitle));
1653  query.bindValue(":DESC", denullify(pi.m_description));
1654  query.bindValue(":CATEGORY", denullify(pi.m_category));
1655  query.bindValue(":CATEGORY_TYPE", cattype);
1656  query.bindValue(":AIRDATE", pi.m_airdate);
1657  query.bindValue(":STARS1", pi.m_stars);
1658  query.bindValue(":STARS2", pi.m_stars);
1659  query.bindValue(":PREVIOUSLYSHOWN", pi.m_previouslyshown);
1660  query.bindValue(":TITLE_PRONOUNCE", pi.m_title_pronounce);
1661  query.bindValue(":AUDIOPROP", pi.m_audioProps);
1662  query.bindValue(":VIDEOPROP", pi.m_videoProps);
1663  query.bindValue(":SUBTYPES", pi.m_subtitleType);
1664  query.bindValue(":PARTNUMBER", pi.m_partnumber);
1665  query.bindValue(":PARTTOTAL", pi.m_parttotal);
1666  query.bindValue(":SERIESID", denullify(pi.m_seriesId));
1667  query.bindValue(":SHOWTYPE", pi.m_showtype);
1668  query.bindValue(":COLORCODE", pi.m_colorcode);
1669  query.bindValue(":SYNDICATEDEPISODENUMBER",
1671  query.bindValue(":PROGRAMID", denullify(pi.m_programId));
1672  query.bindValue(":SEASON", pi.m_season);
1673  query.bindValue(":EPISODE", pi.m_episode);
1674  query.bindValue(":TOTALEPISODES", pi.m_totalepisodes);
1675  query.bindValue(":INETREF", pi.m_inetref);
1676 
1677  if (query.exec() && query.next())
1678  return query.value(0).toUInt() > 0;
1679 
1680  return false;
1681 }
1682 
1684  MSqlQuery &query, uint chanid, const ProgInfo &pi)
1685 {
1686  if (VERBOSE_LEVEL_CHECK(VB_XMLTV, LOG_INFO))
1687  {
1688  // Get overlaps..
1689  query.prepare(
1690  "SELECT title,starttime,endtime "
1691  "FROM program "
1692  "WHERE chanid = :CHANID AND "
1693  " starttime >= :START AND "
1694  " starttime < :END;");
1695  query.bindValue(":CHANID", chanid);
1696  query.bindValue(":START", pi.m_starttime);
1697  query.bindValue(":END", pi.m_endtime);
1698 
1699  if (!query.exec())
1700  return false;
1701 
1702  if (!query.next())
1703  return true;
1704 
1705  do
1706  {
1707  LOG(VB_XMLTV, LOG_INFO,
1708  QString("Removing existing program: %1 - %2 %3 %4")
1709  .arg(MythDate::as_utc(query.value(1).toDateTime()).toString(Qt::ISODate))
1710  .arg(MythDate::as_utc(query.value(2).toDateTime()).toString(Qt::ISODate))
1711  .arg(pi.m_channel)
1712  .arg(query.value(0).toString()));
1713  } while (query.next());
1714  }
1715 
1716  if (!ClearDataByChannel(chanid, pi.m_starttime, pi.m_endtime, false))
1717  {
1718  LOG(VB_XMLTV, LOG_ERR,
1719  QString("Program delete failed : %1 - %2 %3 %4")
1720  .arg(pi.m_starttime.toString(Qt::ISODate))
1721  .arg(pi.m_endtime.toString(Qt::ISODate))
1722  .arg(pi.m_channel)
1723  .arg(pi.m_title));
1724  return false;
1725  }
1726 
1727  return true;
1728 }
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:782
static bool ClearDataBySource(uint sourceid, const QDateTime &from, const QDateTime &to, bool use_channel_time_offset)
QDateTime m_endtime
Definition: programdata.h:138
static int score_words(const QStringList &al, const QStringList &bl)
QString m_description
Definition: programdata.h:135
QString m_clumpidx
Definition: programdata.h:233
DBPerson(const DBPerson &)
Definition: programdata.cpp:60
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:863
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:139
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:136
uint InsertPersonDB(MSqlQuery &query) const
QString m_title_pronounce
Definition: programdata.h:230
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:125
QString m_title
Definition: programdata.h:133
uint32_t m_listingsource
Definition: programdata.h:154
QString m_syndicatedepisodenumber
Definition: programdata.h:144
QString m_channel
Definition: programdata.h:227
static bool change_program(MSqlQuery &query, uint chanid, const QDateTime &st, const QDateTime &new_st, const QDateTime &new_end)
unsigned int uint
Definition: compat.h:140
static int fix_end_times(void)
static int score_match(const QString &a, const QString &b)
static const char * roles[]
Definition: programdata.cpp:20
static bool start_time_less_than(const DBEvent *a, const DBEvent *b)
void Squeeze(void) override
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:60
static guint32 * tmp
Definition: goom_core.c:35
DBCredits * m_credits
Definition: programdata.h:141
QString m_subtitle
Definition: programdata.h:134
uint m_season
Definition: programdata.h:157
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:86
static bool program_exists(MSqlQuery &query, uint chanid, const QDateTime &st)
bool m_previouslyshown
Definition: programdata.h:153
ProgInfo & operator=(const ProgInfo &)
unsigned char b
Definition: ParseText.cpp:329
QDate m_originalairdate
origial broadcast date
Definition: programdata.h:140
QVariant value(int i) const
Definition: mythdbcon.h:198
QStringList m_genres
Definition: programdata.h:156
unsigned char m_audioProps
Definition: programdata.h:146
QString m_inetref
Definition: programdata.h:152
static QString denullify(const QString &str)
Definition: programdata.cpp:28
QString m_colorcode
Definition: programdata.h:232
unsigned char m_subtitleType
Definition: programdata.h:145
#define VERBOSE_LEVEL_CHECK(_MASK_, _LEVEL_)
Definition: mythlogging.h:24
QDateTime m_starttime
Definition: programdata.h:137
bool isActive(void) const
Definition: mythdbcon.h:204
unsigned short uint16_t
Definition: iso6937tables.h:1
QList< EventRating > m_ratings
Definition: programdata.h:155
int GetMatch(const vector< DBEvent > &programs, int &bestmatch) const
uint GetOverlappingPrograms(MSqlQuery &, uint chanid, vector< DBEvent > &programs) const
static MSqlQueryInfo InitCon(ConnectionReuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
static bool delete_program(MSqlQuery &query, uint chanid, const QDateTime &st)
uint m_episode
Definition: programdata.h:158
const char * name
Definition: ParseText.cpp:328
vector< DBPerson > DBCredits
Definition: programdata.h:62
static void FixProgramList(QList< ProgInfo * > &fixlist)
static void add_genres(MSqlQuery &query, const QStringList &genres, uint chanid, const QDateTime &starttime)
Definition: programdata.cpp:38
QString myth_category_type_to_string(ProgramInfo::CategoryType category_type)
QString m_seriesId
Definition: programdata.h:150
QString m_startts
Definition: programdata.h:228
static bool DeleteOverlaps(MSqlQuery &query, uint chanid, const ProgInfo &pi)
bool MoveOutOfTheWayDB(MSqlQuery &, uint chanid, const DBEvent &prog) const
uint m_totalepisodes
Definition: programdata.h:159
unsigned char m_videoProps
Definition: programdata.h:147
float m_stars
Definition: programdata.h:148
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:807
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
DBEvent & operator=(const DBEvent &)
uint UpdateDB(MSqlQuery &query, uint chanid, int match_threshold) const
ProgramInfo::CategoryType m_categoryType
Definition: programdata.h:149
static vector< uint > GetChanIDs(int sourceid=-1, bool onlyVisible=false)
uint GetPersonDB(MSqlQuery &query) const
QString m_clumpmax
Definition: programdata.h:234
QString m_showtype
Definition: programdata.h:231
int numRowsAffected() const
Definition: mythdbcon.h:206
uint InsertDB(MSqlQuery &query, uint chanid, const QDateTime &starttime) const
Definition: programdata.cpp:93
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:603
QString m_endts
Definition: programdata.h:229
Unprocessable file type.
Definition: imagetypes.h:34
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:179
virtual uint InsertDB(MSqlQuery &, uint chanid) const
Insert Callback function when Allow Re-record is pressed in Watch Recordings.
uint16_t m_partnumber
Definition: programdata.h:142
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.
bool HasTimeConflict(const DBEvent &other) const
virtual void Squeeze(void)
Role m_role
Definition: programdata.h:59
Default UTC.
Definition: mythdate.h:14
uint16_t m_parttotal
Definition: programdata.h:143
void AddPerson(DBPerson::Role, const QString &name)
QString m_programId
Definition: programdata.h:151