Ticket #11914: programdata.cpp.v10

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