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 matched item with current data.
612 //
614  MSqlQuery &query, uint chanid, const DBEvent &match) const
615 {
616  QString ltitle = m_title;
617  QString lsubtitle = m_subtitle;
618  QString ldesc = m_description;
619  QString lcategory = m_category;
620  uint16_t lairdate = m_airdate;
621  QString lprogramId = m_programId;
622  QString lseriesId = m_seriesId;
623  QString linetref = m_inetref;
624  QDate loriginalairdate = m_originalairdate;
625 
626  if (ltitle.isEmpty() && !match.m_title.isEmpty())
627  ltitle = match.m_title;
628 
629  if (lsubtitle.isEmpty() && !match.m_subtitle.isEmpty())
630  lsubtitle = match.m_subtitle;
631 
632  if (ldesc.isEmpty() && !match.m_description.isEmpty())
633  ldesc = match.m_description;
634 
635  if (lcategory.isEmpty() && !match.m_category.isEmpty())
636  lcategory = match.m_category;
637 
638  if (!lairdate && match.m_airdate)
639  lairdate = match.m_airdate;
640 
641  if (!loriginalairdate.isValid() && match.m_originalairdate.isValid())
642  loriginalairdate = match.m_originalairdate;
643 
644  if (lprogramId.isEmpty() && !match.m_programId.isEmpty())
645  lprogramId = match.m_programId;
646 
647  if (lseriesId.isEmpty() && !match.m_seriesId.isEmpty())
648  lseriesId = match.m_seriesId;
649 
650  if (linetref.isEmpty() && !match.m_inetref.isEmpty())
651  linetref= match.m_inetref;
652 
654  if (!m_categoryType && match.m_categoryType)
655  tmp = match.m_categoryType;
656 
657  QString lcattype = myth_category_type_to_string(tmp);
658 
659  unsigned char lsubtype = m_subtitleType | match.m_subtitleType;
660  unsigned char laudio = m_audioProps | match.m_audioProps;
661  unsigned char lvideo = m_videoProps | match.m_videoProps;
662 
663  uint lseason = match.m_season;
664  uint lepisode = match.m_episode;
665  uint lepisodeTotal = match.m_totalepisodes;
666 
668  {
669  lseason = m_season;
670  lepisode = m_episode;
671  lepisodeTotal = m_totalepisodes;
672  }
673 
674  uint lpartnumber = match.m_partnumber;
675  uint lparttotal = match.m_parttotal;
676 
677  if (m_partnumber || m_parttotal)
678  {
679  lpartnumber = m_partnumber;
680  lparttotal = m_parttotal;
681  }
682 
683  bool lpreviouslyshown = m_previouslyshown || match.m_previouslyshown;
684 
685  uint32_t llistingsource = m_listingsource | match.m_listingsource;
686 
687  QString lsyndicatedepisodenumber = m_syndicatedepisodenumber;
688  if (lsyndicatedepisodenumber.isEmpty() &&
689  !match.m_syndicatedepisodenumber.isEmpty())
690  lsyndicatedepisodenumber = match.m_syndicatedepisodenumber;
691 
692  query.prepare(
693  "UPDATE program "
694  "SET title = :TITLE, subtitle = :SUBTITLE, "
695  " description = :DESC, "
696  " category = :CATEGORY, category_type = :CATTYPE, "
697  " starttime = :STARTTIME, endtime = :ENDTIME, "
698  " closecaptioned = :CC, subtitled = :HASSUBTITLES, "
699  " stereo = :STEREO, hdtv = :HDTV, "
700  " subtitletypes = :SUBTYPE, "
701  " audioprop = :AUDIOPROP, videoprop = :VIDEOPROP, "
702  " season = :SEASON, "
703  " episode = :EPISODE, totalepisodes = :TOTALEPS, "
704  " partnumber = :PARTNO, parttotal = :PARTTOTAL, "
705  " syndicatedepisodenumber = :SYNDICATENO, "
706  " airdate = :AIRDATE, originalairdate=:ORIGAIRDATE, "
707  " listingsource = :LSOURCE, "
708  " seriesid = :SERIESID, programid = :PROGRAMID, "
709  " previouslyshown = :PREVSHOWN, inetref = :INETREF "
710  "WHERE chanid = :CHANID AND "
711  " starttime = :OLDSTART ");
712 
713  query.bindValue(":CHANID", chanid);
714  query.bindValue(":OLDSTART", match.m_starttime);
715  query.bindValue(":TITLE", denullify(ltitle));
716  query.bindValue(":SUBTITLE", denullify(lsubtitle));
717  query.bindValue(":DESC", denullify(ldesc));
718  query.bindValue(":CATEGORY", denullify(lcategory));
719  query.bindValue(":CATTYPE", lcattype);
720  query.bindValue(":STARTTIME", m_starttime);
721  query.bindValue(":ENDTIME", m_endtime);
722  query.bindValue(":CC", (lsubtype & SUB_HARDHEAR) != 0);
723  query.bindValue(":HASSUBTITLES",(lsubtype & SUB_NORMAL) != 0);
724  query.bindValue(":STEREO", (laudio & AUD_STEREO) != 0);
725  query.bindValue(":HDTV", (lvideo & VID_HDTV) != 0);
726  query.bindValue(":SUBTYPE", lsubtype);
727  query.bindValue(":AUDIOPROP", laudio);
728  query.bindValue(":VIDEOPROP", lvideo);
729  query.bindValue(":SEASON", lseason);
730  query.bindValue(":EPISODE", lepisode);
731  query.bindValue(":TOTALEPS", lepisodeTotal);
732  query.bindValue(":PARTNO", lpartnumber);
733  query.bindValue(":PARTTOTAL", lparttotal);
734  query.bindValue(":SYNDICATENO", denullify(lsyndicatedepisodenumber));
735  query.bindValue(":AIRDATE", lairdate ? QString::number(lairdate) : "0000");
736  query.bindValue(":ORIGAIRDATE", loriginalairdate);
737  query.bindValue(":LSOURCE", llistingsource);
738  query.bindValue(":SERIESID", denullify(lseriesId));
739  query.bindValue(":PROGRAMID", denullify(lprogramId));
740  query.bindValue(":PREVSHOWN", lpreviouslyshown);
741  query.bindValue(":INETREF", linetref);
742 
743  if (!query.exec())
744  {
745  MythDB::DBError("UpdateDB", query);
746  return 0;
747  }
748 
749  if (m_credits)
750  {
751  for (size_t i = 0; i < m_credits->size(); i++)
752  (*m_credits)[i].InsertDB(query, chanid, m_starttime);
753  }
754 
755  QList<EventRating>::const_iterator j = m_ratings.begin();
756  for (; j != m_ratings.end(); ++j)
757  {
758  query.prepare(
759  "INSERT IGNORE INTO programrating "
760  " ( chanid, starttime, system, rating) "
761  "VALUES (:CHANID, :START, :SYS, :RATING)");
762  query.bindValue(":CHANID", chanid);
763  query.bindValue(":START", m_starttime);
764  query.bindValue(":SYS", (*j).m_system);
765  query.bindValue(":RATING", (*j).m_rating);
766 
767  if (!query.exec())
768  MythDB::DBError("programrating insert", query);
769  }
770 
771  add_genres(query, m_genres, chanid, m_starttime);
772 
773  return 1;
774 }
775 
776 static bool delete_program(MSqlQuery &query, uint chanid, const QDateTime &st)
777 {
778  query.prepare(
779  "DELETE from program "
780  "WHERE chanid = :CHANID AND "
781  " starttime = :STARTTIME");
782 
783  query.bindValue(":CHANID", chanid);
784  query.bindValue(":STARTTIME", st);
785 
786  if (!query.exec())
787  {
788  MythDB::DBError("delete_program", query);
789  return false;
790  }
791 
792  query.prepare(
793  "DELETE from credits "
794  "WHERE chanid = :CHANID AND "
795  " starttime = :STARTTIME");
796 
797  query.bindValue(":CHANID", chanid);
798  query.bindValue(":STARTTIME", st);
799 
800  if (!query.exec())
801  {
802  MythDB::DBError("delete_credits", query);
803  return false;
804  }
805 
806  query.prepare(
807  "DELETE from programrating "
808  "WHERE chanid = :CHANID AND "
809  " starttime = :STARTTIME");
810 
811  query.bindValue(":CHANID", chanid);
812  query.bindValue(":STARTTIME", st);
813 
814  if (!query.exec())
815  {
816  MythDB::DBError("delete_rating", query);
817  return false;
818  }
819 
820  query.prepare(
821  "DELETE from programgenres "
822  "WHERE chanid = :CHANID AND "
823  " starttime = :STARTTIME");
824 
825  query.bindValue(":CHANID", chanid);
826  query.bindValue(":STARTTIME", st);
827 
828  if (!query.exec())
829  {
830  MythDB::DBError("delete_genres", query);
831  return false;
832  }
833 
834  return true;
835 }
836 
837 static bool program_exists(MSqlQuery &query, uint chanid, const QDateTime &st)
838 {
839  query.prepare(
840  "SELECT title FROM program "
841  "WHERE chanid = :CHANID AND "
842  " starttime = :OLDSTART");
843  query.bindValue(":CHANID", chanid);
844  query.bindValue(":OLDSTART", st);
845  if (!query.exec())
846  {
847  MythDB::DBError("program_exists", query);
848  }
849  return query.next();
850 }
851 
852 static bool change_program(MSqlQuery &query, uint chanid, const QDateTime &st,
853  const QDateTime &new_st, const QDateTime &new_end)
854 {
855  query.prepare(
856  "UPDATE program "
857  "SET starttime = :NEWSTART, "
858  " endtime = :NEWEND "
859  "WHERE chanid = :CHANID AND "
860  " starttime = :OLDSTART");
861 
862  query.bindValue(":CHANID", chanid);
863  query.bindValue(":OLDSTART", st);
864  query.bindValue(":NEWSTART", new_st);
865  query.bindValue(":NEWEND", new_end);
866 
867  if (!query.exec())
868  {
869  MythDB::DBError("change_program", query);
870  return false;
871  }
872 
873  query.prepare(
874  "UPDATE credits "
875  "SET starttime = :NEWSTART "
876  "WHERE chanid = :CHANID AND "
877  " starttime = :OLDSTART");
878 
879  query.bindValue(":CHANID", chanid);
880  query.bindValue(":OLDSTART", st);
881  query.bindValue(":NEWSTART", new_st);
882 
883  if (!query.exec())
884  {
885  MythDB::DBError("change_credits", query);
886  return false;
887  }
888 
889  query.prepare(
890  "UPDATE programrating "
891  "SET starttime = :NEWSTART "
892  "WHERE chanid = :CHANID AND "
893  " starttime = :OLDSTART");
894 
895  query.bindValue(":CHANID", chanid);
896  query.bindValue(":OLDSTART", st);
897  query.bindValue(":NEWSTART", new_st);
898 
899  if (!query.exec())
900  {
901  MythDB::DBError("change_rating", query);
902  return false;
903  }
904 
905  query.prepare(
906  "UPDATE programgenres "
907  "SET starttime = :NEWSTART "
908  "WHERE chanid = :CHANID AND "
909  " starttime = :OLDSTART");
910 
911  query.bindValue(":CHANID", chanid);
912  query.bindValue(":OLDSTART", st);
913  query.bindValue(":NEWSTART", new_st);
914 
915  if (!query.exec())
916  {
917  MythDB::DBError("change_genres", query);
918  return false;
919  }
920 
921  return true;
922 }
923 
924 // Move the program "prog" (3rd parameter) out of the way
925 // because it overlaps with our new program.
927  MSqlQuery &query, uint chanid, const DBEvent &prog) const
928 {
929  if (prog.m_starttime >= m_starttime && prog.m_endtime <= m_endtime)
930  {
931  // Old program completely inside our new program.
932  // Delete the old program completely.
933  LOG(VB_EIT, LOG_DEBUG,
934  QString("EIT: delete '%1' %2 - %3")
935  .arg(prog.m_title.left(35))
936  .arg(prog.m_starttime.toString(Qt::ISODate))
937  .arg(prog.m_endtime.toString(Qt::ISODate)));
938  return delete_program(query, chanid, prog.m_starttime);
939  }
940  if (prog.m_starttime < m_starttime && prog.m_endtime > m_starttime)
941  {
942  // Old program starts before, but ends during or after our new program.
943  // Adjust the end time of the old program to the start time
944  // of our new program.
945  // This will leave a hole after our new program when the end time of
946  // the old program was after the end time of the new program!!
947  LOG(VB_EIT, LOG_DEBUG,
948  QString("EIT: change '%1' endtime to %2")
949  .arg(prog.m_title.left(35))
950  .arg(m_starttime.toString(Qt::ISODate)));
951  return change_program(query, chanid, prog.m_starttime,
952  prog.m_starttime, // Keep the start time
953  m_starttime); // New end time is our start time
954  }
955  if (prog.m_starttime < m_endtime && prog.m_endtime > m_endtime)
956  {
957  // Old program starts during, but ends after our new program.
958  // Adjust the starttime of the old program to the end time
959  // of our new program.
960  // If there is already a program starting just when our
961  // new program ends we cannot move the old program
962  // so then we have to delete the old program.
963  if (program_exists(query, chanid, m_endtime))
964  {
965  LOG(VB_EIT, LOG_DEBUG,
966  QString("EIT: delete '%1' %2 - %3")
967  .arg(prog.m_title.left(35))
968  .arg(prog.m_starttime.toString(Qt::ISODate))
969  .arg(prog.m_endtime.toString(Qt::ISODate)));
970  return delete_program(query, chanid, prog.m_starttime);
971  }
972  LOG(VB_EIT, LOG_DEBUG,
973  QString("EIT: change '%1' starttime to %2")
974  .arg(prog.m_title.left(35))
975  .arg(m_endtime.toString(Qt::ISODate)));
976 
977  return change_program(query, chanid, prog.m_starttime,
978  m_endtime, // New start time is our endtime
979  prog.m_endtime); // Keep the end time
980  }
981  // must be non-conflicting...
982  return true;
983 }
984 
988 uint DBEvent::InsertDB(MSqlQuery &query, uint chanid) const
989 {
990  query.prepare(
991  "REPLACE INTO program ("
992  " chanid, title, subtitle, description, "
993  " category, category_type, "
994  " starttime, endtime, "
995  " closecaptioned, stereo, hdtv, subtitled, "
996  " subtitletypes, audioprop, videoprop, "
997  " stars, partnumber, parttotal, "
998  " syndicatedepisodenumber, "
999  " airdate, originalairdate,listingsource, "
1000  " seriesid, programid, previouslyshown, "
1001  " season, episode, totalepisodes, "
1002  " inetref ) "
1003  "VALUES ("
1004  " :CHANID, :TITLE, :SUBTITLE, :DESCRIPTION, "
1005  " :CATEGORY, :CATTYPE, "
1006  " :STARTTIME, :ENDTIME, "
1007  " :CC, :STEREO, :HDTV, :HASSUBTITLES, "
1008  " :SUBTYPES, :AUDIOPROP, :VIDEOPROP, "
1009  " :STARS, :PARTNUMBER, :PARTTOTAL, "
1010  " :SYNDICATENO, "
1011  " :AIRDATE, :ORIGAIRDATE, :LSOURCE, "
1012  " :SERIESID, :PROGRAMID, :PREVSHOWN, "
1013  " :SEASON, :EPISODE, :TOTALEPISODES, "
1014  " :INETREF ) ");
1015 
1016  QString cattype = myth_category_type_to_string(m_categoryType);
1017  QString empty("");
1018  query.bindValue(":CHANID", chanid);
1019  query.bindValue(":TITLE", denullify(m_title));
1020  query.bindValue(":SUBTITLE", denullify(m_subtitle));
1021  query.bindValue(":DESCRIPTION", denullify(m_description));
1022  query.bindValue(":CATEGORY", denullify(m_category));
1023  query.bindValue(":CATTYPE", cattype);
1024  query.bindValue(":STARTTIME", m_starttime);
1025  query.bindValue(":ENDTIME", m_endtime);
1026  query.bindValue(":CC", (m_subtitleType & SUB_HARDHEAR) != 0);
1027  query.bindValue(":STEREO", (m_audioProps & AUD_STEREO) != 0);
1028  query.bindValue(":HDTV", (m_videoProps & VID_HDTV) != 0);
1029  query.bindValue(":HASSUBTITLES",(m_subtitleType & SUB_NORMAL) != 0);
1030  query.bindValue(":SUBTYPES", m_subtitleType);
1031  query.bindValue(":AUDIOPROP", m_audioProps);
1032  query.bindValue(":VIDEOPROP", m_videoProps);
1033  query.bindValue(":STARS", m_stars);
1034  query.bindValue(":PARTNUMBER", m_partnumber);
1035  query.bindValue(":PARTTOTAL", m_parttotal);
1036  query.bindValue(":SYNDICATENO", denullify(m_syndicatedepisodenumber));
1037  query.bindValue(":AIRDATE", m_airdate ? QString::number(m_airdate) : "0000");
1038  query.bindValue(":ORIGAIRDATE", m_originalairdate);
1039  query.bindValue(":LSOURCE", m_listingsource);
1040  query.bindValue(":SERIESID", denullify(m_seriesId));
1041  query.bindValue(":PROGRAMID", denullify(m_programId));
1042  query.bindValue(":PREVSHOWN", m_previouslyshown);
1043  query.bindValue(":SEASON", m_season);
1044  query.bindValue(":EPISODE", m_episode);
1045  query.bindValue(":TOTALEPISODES", m_totalepisodes);
1046  query.bindValue(":INETREF", m_inetref);
1047 
1048  if (!query.exec())
1049  {
1050  MythDB::DBError("InsertDB", query);
1051  return 0;
1052  }
1053 
1054  QList<EventRating>::const_iterator j = m_ratings.begin();
1055  for (; j != m_ratings.end(); ++j)
1056  {
1057  query.prepare(
1058  "INSERT IGNORE INTO programrating "
1059  " ( chanid, starttime, system, rating) "
1060  "VALUES (:CHANID, :START, :SYS, :RATING)");
1061  query.bindValue(":CHANID", chanid);
1062  query.bindValue(":START", m_starttime);
1063  query.bindValue(":SYS", (*j).m_system);
1064  query.bindValue(":RATING", (*j).m_rating);
1065 
1066  if (!query.exec())
1067  MythDB::DBError("programrating insert", query);
1068  }
1069 
1070  if (m_credits)
1071  {
1072  for (size_t i = 0; i < m_credits->size(); i++)
1073  (*m_credits)[i].InsertDB(query, chanid, m_starttime);
1074  }
1075 
1076  add_genres(query, m_genres, chanid, m_starttime);
1077 
1078  return 1;
1079 }
1080 
1082  DBEvent(other.m_listingsource)
1083 {
1084  *this = other;
1085 }
1086 
1088 {
1089  if (this == &other)
1090  return *this;
1091 
1092  DBEvent::operator=(other);
1093 
1094  m_channel = other.m_channel;
1095  m_startts = other.m_startts;
1096  m_endts = other.m_endts;
1098  m_showtype = other.m_showtype;
1099  m_colorcode = other.m_colorcode;
1100  m_clumpidx = other.m_clumpidx;
1101  m_clumpmax = other.m_clumpmax;
1102 
1103  m_channel.squeeze();
1104  m_startts.squeeze();
1105  m_endts.squeeze();
1106  m_title_pronounce.squeeze();
1107  m_showtype.squeeze();
1108  m_colorcode.squeeze();
1109  m_clumpidx.squeeze();
1110  m_clumpmax.squeeze();
1111 
1112  return *this;
1113 }
1114 
1116 {
1117  DBEvent::Squeeze();
1118  m_channel.squeeze();
1119  m_startts.squeeze();
1120  m_endts.squeeze();
1121  m_title_pronounce.squeeze();
1122  m_showtype.squeeze();
1123  m_colorcode.squeeze();
1124  m_clumpidx.squeeze();
1125  m_clumpmax.squeeze();
1126 }
1127 
1140 uint ProgInfo::InsertDB(MSqlQuery &query, uint chanid) const
1141 {
1142  LOG(VB_XMLTV, LOG_INFO,
1143  QString("Inserting new program : %1 - %2 %3 %4")
1144  .arg(m_starttime.toString(Qt::ISODate))
1145  .arg(m_endtime.toString(Qt::ISODate))
1146  .arg(m_channel)
1147  .arg(m_title));
1148 
1149  query.prepare(
1150  "REPLACE INTO program ("
1151  " chanid, title, subtitle, description, "
1152  " category, category_type, "
1153  " starttime, endtime, "
1154  " closecaptioned, stereo, hdtv, subtitled, "
1155  " subtitletypes, audioprop, videoprop, "
1156  " partnumber, parttotal, "
1157  " syndicatedepisodenumber, "
1158  " airdate, originalairdate,listingsource, "
1159  " seriesid, programid, previouslyshown, "
1160  " stars, showtype, title_pronounce, colorcode, "
1161  " season, episode, totalepisodes, "
1162  " inetref ) "
1163 
1164  "VALUES("
1165  " :CHANID, :TITLE, :SUBTITLE, :DESCRIPTION, "
1166  " :CATEGORY, :CATTYPE, "
1167  " :STARTTIME, :ENDTIME, "
1168  " :CC, :STEREO, :HDTV, :HASSUBTITLES, "
1169  " :SUBTYPES, :AUDIOPROP, :VIDEOPROP, "
1170  " :PARTNUMBER, :PARTTOTAL, "
1171  " :SYNDICATENO, "
1172  " :AIRDATE, :ORIGAIRDATE, :LSOURCE, "
1173  " :SERIESID, :PROGRAMID, :PREVSHOWN, "
1174  " :STARS, :SHOWTYPE, :TITLEPRON, :COLORCODE, "
1175  " :SEASON, :EPISODE, :TOTALEPISODES, "
1176  " :INETREF )");
1177 
1178  QString cattype = myth_category_type_to_string(m_categoryType);
1179 
1180  query.bindValue(":CHANID", chanid);
1181  query.bindValue(":TITLE", denullify(m_title));
1182  query.bindValue(":SUBTITLE", denullify(m_subtitle));
1183  query.bindValue(":DESCRIPTION", denullify(m_description));
1184  query.bindValue(":CATEGORY", denullify(m_category));
1185  query.bindValue(":CATTYPE", cattype);
1186  query.bindValue(":STARTTIME", m_starttime);
1187  query.bindValue(":ENDTIME", denullify(m_endtime));
1188  query.bindValue(":CC",
1189  (m_subtitleType & SUB_HARDHEAR) != 0);
1190  query.bindValue(":STEREO",
1191  (m_audioProps & AUD_STEREO) != 0);
1192  query.bindValue(":HDTV",
1193  (m_videoProps & VID_HDTV) != 0);
1194  query.bindValue(":HASSUBTITLES",
1195  (m_subtitleType & SUB_NORMAL) != 0);
1196  query.bindValue(":SUBTYPES", m_subtitleType);
1197  query.bindValue(":AUDIOPROP", m_audioProps);
1198  query.bindValue(":VIDEOPROP", m_videoProps);
1199  query.bindValue(":PARTNUMBER", m_partnumber);
1200  query.bindValue(":PARTTOTAL", m_parttotal);
1201  query.bindValue(":SYNDICATENO", denullify(m_syndicatedepisodenumber));
1202  query.bindValue(":AIRDATE", m_airdate ? QString::number(m_airdate):"0000");
1203  query.bindValue(":ORIGAIRDATE", m_originalairdate);
1204  query.bindValue(":LSOURCE", m_listingsource);
1205  query.bindValue(":SERIESID", denullify(m_seriesId));
1206  query.bindValue(":PROGRAMID", denullify(m_programId));
1207  query.bindValue(":PREVSHOWN", m_previouslyshown);
1208  query.bindValue(":STARS", m_stars);
1209  query.bindValue(":SHOWTYPE", m_showtype);
1210  query.bindValue(":TITLEPRON", m_title_pronounce);
1211  query.bindValue(":COLORCODE", m_colorcode);
1212  query.bindValue(":SEASON", m_season);
1213  query.bindValue(":EPISODE", m_episode);
1214  query.bindValue(":TOTALEPISODES", m_totalepisodes);
1215  query.bindValue(":INETREF", m_inetref);
1216 
1217  if (!query.exec())
1218  {
1219  MythDB::DBError("program insert", query);
1220  return 0;
1221  }
1222 
1223  QList<EventRating>::const_iterator j = m_ratings.begin();
1224  for (; j != m_ratings.end(); ++j)
1225  {
1226  query.prepare(
1227  "INSERT IGNORE INTO programrating "
1228  " ( chanid, starttime, system, rating) "
1229  "VALUES (:CHANID, :START, :SYS, :RATING)");
1230  query.bindValue(":CHANID", chanid);
1231  query.bindValue(":START", m_starttime);
1232  query.bindValue(":SYS", (*j).m_system);
1233  query.bindValue(":RATING", (*j).m_rating);
1234 
1235  if (!query.exec())
1236  MythDB::DBError("programrating insert", query);
1237  }
1238 
1239  if (m_credits)
1240  {
1241  for (size_t i = 0; i < m_credits->size(); ++i)
1242  (*m_credits)[i].InsertDB(query, chanid, m_starttime);
1243  }
1244 
1245  add_genres(query, m_genres, chanid, m_starttime);
1246 
1247  return 1;
1248 }
1249 
1251  uint chanid, const QDateTime &from, const QDateTime &to,
1252  bool use_channel_time_offset)
1253 {
1254  int secs = 0;
1255  if (use_channel_time_offset)
1256  secs = ChannelUtil::GetTimeOffset(chanid) * 60;
1257 
1258  QDateTime newFrom = from.addSecs(secs);
1259  QDateTime newTo = to.addSecs(secs);
1260 
1261  MSqlQuery query(MSqlQuery::InitCon());
1262  query.prepare("DELETE FROM program "
1263  "WHERE starttime >= :FROM AND starttime < :TO "
1264  "AND chanid = :CHANID ;");
1265  query.bindValue(":FROM", newFrom);
1266  query.bindValue(":TO", newTo);
1267  query.bindValue(":CHANID", chanid);
1268  bool ok = query.exec();
1269 
1270  query.prepare("DELETE FROM programrating "
1271  "WHERE starttime >= :FROM AND starttime < :TO "
1272  "AND chanid = :CHANID ;");
1273  query.bindValue(":FROM", newFrom);
1274  query.bindValue(":TO", newTo);
1275  query.bindValue(":CHANID", chanid);
1276  ok &= query.exec();
1277 
1278  query.prepare("DELETE FROM credits "
1279  "WHERE starttime >= :FROM AND starttime < :TO "
1280  "AND chanid = :CHANID ;");
1281  query.bindValue(":FROM", newFrom);
1282  query.bindValue(":TO", newTo);
1283  query.bindValue(":CHANID", chanid);
1284  ok &= query.exec();
1285 
1286  query.prepare("DELETE FROM programgenres "
1287  "WHERE starttime >= :FROM AND starttime < :TO "
1288  "AND chanid = :CHANID ;");
1289  query.bindValue(":FROM", newFrom);
1290  query.bindValue(":TO", newTo);
1291  query.bindValue(":CHANID", chanid);
1292  ok &= query.exec();
1293 
1294  return ok;
1295 }
1296 
1298  uint sourceid, const QDateTime &from, const QDateTime &to,
1299  bool use_channel_time_offset)
1300 {
1301  vector<uint> chanids = ChannelUtil::GetChanIDs(sourceid);
1302 
1303  bool ok = true;
1304  for (size_t i = 0; i < chanids.size(); i++)
1305  ok &= ClearDataByChannel(chanids[i], from, to, use_channel_time_offset);
1306 
1307  return ok;
1308 }
1309 
1310 static bool start_time_less_than(const DBEvent *a, const DBEvent *b)
1311 {
1312  return (a->m_starttime < b->m_starttime);
1313 }
1314 
1315 void ProgramData::FixProgramList(QList<ProgInfo*> &fixlist)
1316 {
1317  std::stable_sort(fixlist.begin(), fixlist.end(), start_time_less_than);
1318 
1319  QList<ProgInfo*>::iterator it = fixlist.begin();
1320  while (true)
1321  {
1322  QList<ProgInfo*>::iterator cur = it;
1323  ++it;
1324 
1325  // fill in miss stop times
1326  if ((*cur)->m_endts.isEmpty() || (*cur)->m_startts > (*cur)->m_endts)
1327  {
1328  if (it != fixlist.end())
1329  {
1330  (*cur)->m_endts = (*it)->m_startts;
1331  (*cur)->m_endtime = (*it)->m_starttime;
1332  }
1333  /* if its the last programme in the file then leave its
1334  endtime as 0000-00-00 00:00:00 so we can find it easily in
1335  fix_end_times() */
1336  }
1337 
1338  if (it == fixlist.end())
1339  break;
1340 
1341  // remove overlapping programs
1342  if ((*cur)->HasTimeConflict(**it))
1343  {
1344  QList<ProgInfo*>::iterator tokeep, todelete;
1345 
1346  if ((*cur)->m_endtime <= (*cur)->m_starttime)
1347  tokeep = it, todelete = cur;
1348  else if ((*it)->m_endtime <= (*it)->m_starttime)
1349  tokeep = cur, todelete = it;
1350  else if (!(*cur)->m_subtitle.isEmpty() &&
1351  (*it)->m_subtitle.isEmpty())
1352  tokeep = cur, todelete = it;
1353  else if (!(*it)->m_subtitle.isEmpty() &&
1354  (*cur)->m_subtitle.isEmpty())
1355  tokeep = it, todelete = cur;
1356  else if (!(*cur)->m_description.isEmpty() &&
1357  (*it)->m_description.isEmpty())
1358  tokeep = cur, todelete = it;
1359  else
1360  tokeep = it, todelete = cur;
1361 
1362 
1363  LOG(VB_XMLTV, LOG_INFO,
1364  QString("Removing conflicting program: %1 - %2 %3 %4")
1365  .arg((*todelete)->m_starttime.toString(Qt::ISODate))
1366  .arg((*todelete)->m_endtime.toString(Qt::ISODate))
1367  .arg((*todelete)->m_channel)
1368  .arg((*todelete)->m_title));
1369 
1370  LOG(VB_XMLTV, LOG_INFO,
1371  QString("Conflicted with : %1 - %2 %3 %4")
1372  .arg((*tokeep)->m_starttime.toString(Qt::ISODate))
1373  .arg((*tokeep)->m_endtime.toString(Qt::ISODate))
1374  .arg((*tokeep)->m_channel)
1375  .arg((*tokeep)->m_title));
1376 
1377  bool step_back = todelete == it;
1378  it = fixlist.erase(todelete);
1379  if (step_back)
1380  --it;
1381  }
1382  }
1383 }
1384 
1394  uint sourceid, QMap<QString, QList<ProgInfo> > &proglist)
1395 {
1396  uint unchanged = 0, updated = 0;
1397 
1398  MSqlQuery query(MSqlQuery::InitCon());
1399 
1400  QMap<QString, QList<ProgInfo> >::const_iterator mapiter;
1401  for (mapiter = proglist.begin(); mapiter != proglist.end(); ++mapiter)
1402  {
1403  if (mapiter.key().isEmpty())
1404  continue;
1405 
1406  query.prepare(
1407  "SELECT chanid "
1408  "FROM channel "
1409  "WHERE sourceid = :ID AND "
1410  " xmltvid = :XMLTVID");
1411  query.bindValue(":ID", sourceid);
1412  query.bindValue(":XMLTVID", mapiter.key());
1413 
1414  if (!query.exec())
1415  {
1416  MythDB::DBError("ProgramData::HandlePrograms", query);
1417  continue;
1418  }
1419 
1420  vector<uint> chanids;
1421  while (query.next())
1422  chanids.push_back(query.value(0).toUInt());
1423 
1424  if (chanids.empty())
1425  {
1426  LOG(VB_GENERAL, LOG_NOTICE,
1427  QString("Unknown xmltv channel identifier: %1"
1428  " - Skipping channel.").arg(mapiter.key()));
1429  continue;
1430  }
1431 
1432  QList<ProgInfo> &list = proglist[mapiter.key()];
1433  QList<ProgInfo*> sortlist;
1434  QList<ProgInfo>::iterator it = list.begin();
1435  for (; it != list.end(); ++it)
1436  sortlist.push_back(&(*it));
1437 
1438  FixProgramList(sortlist);
1439 
1440  for (size_t i = 0; i < chanids.size(); ++i)
1441  {
1442  HandlePrograms(query, chanids[i], sortlist, unchanged, updated);
1443  }
1444  }
1445 
1446  LOG(VB_GENERAL, LOG_INFO,
1447  QString("Updated programs: %1 Unchanged programs: %2")
1448  .arg(updated) .arg(unchanged));
1449 }
1450 
1463  uint chanid,
1464  const QList<ProgInfo*> &sortlist,
1465  uint &unchanged,
1466  uint &updated)
1467 {
1468  QList<ProgInfo*>::const_iterator it = sortlist.begin();
1469  for (; it != sortlist.end(); ++it)
1470  {
1471  if (IsUnchanged(query, chanid, **it))
1472  {
1473  unchanged++;
1474  continue;
1475  }
1476 
1477  if (!DeleteOverlaps(query, chanid, **it))
1478  continue;
1479 
1480  updated += (*it)->InsertDB(query, chanid);
1481  }
1482 }
1483 
1485 {
1486  int count = 0;
1487  QString chanid, starttime, endtime, querystr;
1488  MSqlQuery query1(MSqlQuery::InitCon()), query2(MSqlQuery::InitCon());
1489 
1490  querystr = "SELECT chanid, starttime, endtime FROM program "
1491  "WHERE endtime = '0000-00-00 00:00:00' "
1492  "ORDER BY chanid, starttime;";
1493 
1494  if (!query1.exec(querystr))
1495  {
1496  LOG(VB_GENERAL, LOG_ERR,
1497  QString("fix_end_times query failed: %1").arg(querystr));
1498  return -1;
1499  }
1500 
1501  while (query1.next())
1502  {
1503  starttime = query1.value(1).toString();
1504  chanid = query1.value(0).toString();
1505  endtime = query1.value(2).toString();
1506 
1507  querystr = QString("SELECT chanid, starttime, endtime FROM program "
1508  "WHERE starttime > '%1' "
1509  "AND chanid = '%2' "
1510  "ORDER BY starttime LIMIT 1;")
1511  .arg(starttime)
1512  .arg(chanid);
1513 
1514  if (!query2.exec(querystr))
1515  {
1516  LOG(VB_GENERAL, LOG_ERR,
1517  QString("fix_end_times query failed: %1").arg(querystr));
1518  return -1;
1519  }
1520 
1521  if (query2.next() && (endtime != query2.value(1).toString()))
1522  {
1523  count++;
1524  endtime = query2.value(1).toString();
1525  querystr = QString("UPDATE program SET "
1526  "endtime = '%2' WHERE (chanid = '%3' AND "
1527  "starttime = '%4');")
1528  .arg(endtime)
1529  .arg(chanid)
1530  .arg(starttime);
1531 
1532  if (!query2.exec(querystr))
1533  {
1534  LOG(VB_GENERAL, LOG_ERR,
1535  QString("fix_end_times query failed: %1").arg(querystr));
1536  return -1;
1537  }
1538  }
1539  }
1540 
1541  return count;
1542 }
1543 
1545  MSqlQuery &query, uint chanid, const ProgInfo &pi)
1546 {
1547  query.prepare(
1548  "SELECT count(*) "
1549  "FROM program "
1550  "WHERE chanid = :CHANID AND "
1551  " starttime = :START AND "
1552  " endtime = :END AND "
1553  " title = :TITLE AND "
1554  " subtitle = :SUBTITLE AND "
1555  " description = :DESC AND "
1556  " category = :CATEGORY AND "
1557  " category_type = :CATEGORY_TYPE AND "
1558  " airdate = :AIRDATE AND "
1559  " stars >= (:STARS1 - 0.001) AND "
1560  " stars <= (:STARS2 + 0.001) AND "
1561  " previouslyshown = :PREVIOUSLYSHOWN AND "
1562  " title_pronounce = :TITLE_PRONOUNCE AND "
1563  " audioprop = :AUDIOPROP AND "
1564  " videoprop = :VIDEOPROP AND "
1565  " subtitletypes = :SUBTYPES AND "
1566  " partnumber = :PARTNUMBER AND "
1567  " parttotal = :PARTTOTAL AND "
1568  " seriesid = :SERIESID AND "
1569  " showtype = :SHOWTYPE AND "
1570  " colorcode = :COLORCODE AND "
1571  " syndicatedepisodenumber = :SYNDICATEDEPISODENUMBER AND "
1572  " programid = :PROGRAMID AND "
1573  " inetref = :INETREF");
1574 
1575  QString cattype = myth_category_type_to_string(pi.m_categoryType);
1576 
1577  query.bindValue(":CHANID", chanid);
1578  query.bindValue(":START", pi.m_starttime);
1579  query.bindValue(":END", pi.m_endtime);
1580  query.bindValue(":TITLE", denullify(pi.m_title));
1581  query.bindValue(":SUBTITLE", denullify(pi.m_subtitle));
1582  query.bindValue(":DESC", denullify(pi.m_description));
1583  query.bindValue(":CATEGORY", denullify(pi.m_category));
1584  query.bindValue(":CATEGORY_TYPE", cattype);
1585  query.bindValue(":AIRDATE", pi.m_airdate);
1586  query.bindValue(":STARS1", pi.m_stars);
1587  query.bindValue(":STARS2", pi.m_stars);
1588  query.bindValue(":PREVIOUSLYSHOWN", pi.m_previouslyshown);
1589  query.bindValue(":TITLE_PRONOUNCE", pi.m_title_pronounce);
1590  query.bindValue(":AUDIOPROP", pi.m_audioProps);
1591  query.bindValue(":VIDEOPROP", pi.m_videoProps);
1592  query.bindValue(":SUBTYPES", pi.m_subtitleType);
1593  query.bindValue(":PARTNUMBER", pi.m_partnumber);
1594  query.bindValue(":PARTTOTAL", pi.m_parttotal);
1595  query.bindValue(":SERIESID", denullify(pi.m_seriesId));
1596  query.bindValue(":SHOWTYPE", pi.m_showtype);
1597  query.bindValue(":COLORCODE", pi.m_colorcode);
1598  query.bindValue(":SYNDICATEDEPISODENUMBER",
1600  query.bindValue(":PROGRAMID", denullify(pi.m_programId));
1601  query.bindValue(":INETREF", pi.m_inetref);
1602 
1603  if (query.exec() && query.next())
1604  return query.value(0).toUInt() > 0;
1605 
1606  return false;
1607 }
1608 
1610  MSqlQuery &query, uint chanid, const ProgInfo &pi)
1611 {
1612  if (VERBOSE_LEVEL_CHECK(VB_XMLTV, LOG_INFO))
1613  {
1614  // Get overlaps..
1615  query.prepare(
1616  "SELECT title,starttime,endtime "
1617  "FROM program "
1618  "WHERE chanid = :CHANID AND "
1619  " starttime >= :START AND "
1620  " starttime < :END;");
1621  query.bindValue(":CHANID", chanid);
1622  query.bindValue(":START", pi.m_starttime);
1623  query.bindValue(":END", pi.m_endtime);
1624 
1625  if (!query.exec())
1626  return false;
1627 
1628  if (!query.next())
1629  return true;
1630 
1631  do
1632  {
1633  LOG(VB_XMLTV, LOG_INFO,
1634  QString("Removing existing program: %1 - %2 %3 %4")
1635  .arg(MythDate::as_utc(query.value(1).toDateTime()).toString(Qt::ISODate))
1636  .arg(MythDate::as_utc(query.value(2).toDateTime()).toString(Qt::ISODate))
1637  .arg(pi.m_channel)
1638  .arg(query.value(0).toString()));
1639  } while (query.next());
1640  }
1641 
1642  if (!ClearDataByChannel(chanid, pi.m_starttime, pi.m_endtime, false))
1643  {
1644  LOG(VB_XMLTV, LOG_ERR,
1645  QString("Program delete failed : %1 - %2 %3 %4")
1646  .arg(pi.m_starttime.toString(Qt::ISODate))
1647  .arg(pi.m_endtime.toString(Qt::ISODate))
1648  .arg(pi.m_channel)
1649  .arg(pi.m_title));
1650  return false;
1651  }
1652 
1653  return true;
1654 }
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
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
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
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