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