00001
00002
00003 #include <limits.h>
00004
00005
00006 #include <algorithm>
00007 using namespace std;
00008
00009
00010 #include "channelutil.h"
00011 #include "mythdb.h"
00012 #include "mythlogging.h"
00013 #include "programinfo.h"
00014 #include "programdata.h"
00015 #include "dvbdescriptors.h"
00016
00017 #define LOC QString("ProgramData: ")
00018
00019 static const char *roles[] =
00020 {
00021 "",
00022 "actor", "director", "producer", "executive_producer",
00023 "writer", "guest_star", "host", "adapter",
00024 "presenter", "commentator", "guest",
00025 };
00026
00027 static QString denullify(const QString &str)
00028 {
00029 return str.isNull() ? "" : str;
00030 }
00031
00032 DBPerson::DBPerson(const DBPerson &other) :
00033 role(other.role), name(other.name)
00034 {
00035 name.squeeze();
00036 }
00037
00038 DBPerson::DBPerson(Role _role, const QString &_name) :
00039 role(_role), name(_name)
00040 {
00041 name.squeeze();
00042 }
00043
00044 DBPerson::DBPerson(const QString &_role, const QString &_name) :
00045 role(kUnknown), name(_name)
00046 {
00047 if (!_role.isEmpty())
00048 {
00049 for (uint i = 0; i < sizeof(roles) / sizeof(char *); i++)
00050 {
00051 if (_role == QString(roles[i]))
00052 role = (Role) i;
00053 }
00054 }
00055 name.squeeze();
00056 }
00057
00058 QString DBPerson::GetRole(void) const
00059 {
00060 if ((role < kActor) || (role > kGuest))
00061 return "guest";
00062 return roles[role];
00063 }
00064
00065 uint DBPerson::InsertDB(MSqlQuery &query, uint chanid,
00066 const QDateTime &starttime) const
00067 {
00068 uint personid = GetPersonDB(query);
00069 if (!personid && InsertPersonDB(query))
00070 personid = GetPersonDB(query);
00071
00072 return InsertCreditsDB(query, personid, chanid, starttime);
00073 }
00074
00075 uint DBPerson::GetPersonDB(MSqlQuery &query) const
00076 {
00077 query.prepare(
00078 "SELECT person "
00079 "FROM people "
00080 "WHERE name = :NAME");
00081 query.bindValue(":NAME", name);
00082
00083 if (!query.exec())
00084 MythDB::DBError("get_person", query);
00085 else if (query.next())
00086 return query.value(0).toUInt();
00087
00088 return 0;
00089 }
00090
00091 uint DBPerson::InsertPersonDB(MSqlQuery &query) const
00092 {
00093 query.prepare(
00094 "INSERT IGNORE INTO people (name) "
00095 "VALUES (:NAME);");
00096 query.bindValue(":NAME", name);
00097
00098 if (query.exec())
00099 return 1;
00100
00101 MythDB::DBError("insert_person", query);
00102 return 0;
00103 }
00104
00105 uint DBPerson::InsertCreditsDB(MSqlQuery &query, uint personid, uint chanid,
00106 const QDateTime &starttime) const
00107 {
00108 if (!personid)
00109 return 0;
00110
00111 query.prepare(
00112 "REPLACE INTO credits "
00113 " ( person, chanid, starttime, role) "
00114 "VALUES (:PERSON, :CHANID, :STARTTIME, :ROLE) ");
00115 query.bindValue(":PERSON", personid);
00116 query.bindValue(":CHANID", chanid);
00117 query.bindValue(":STARTTIME", starttime);
00118 query.bindValue(":ROLE", GetRole());
00119
00120 if (query.exec())
00121 return 1;
00122
00123 MythDB::DBError("insert_credits", query);
00124 return 0;
00125 }
00126
00127 DBEvent &DBEvent::operator=(const DBEvent &other)
00128 {
00129 if (this == &other)
00130 return *this;
00131
00132 title = other.title;
00133 subtitle = other.subtitle;
00134 description = other.description;
00135 category = other.category;
00136 starttime = other.starttime;
00137 endtime = other.endtime;
00138 airdate = other.airdate;
00139 originalairdate = other.originalairdate;
00140
00141 if (credits != other.credits)
00142 {
00143 if (credits)
00144 {
00145 delete credits;
00146 credits = NULL;
00147 }
00148
00149 if (other.credits)
00150 {
00151 credits = new DBCredits;
00152 credits->insert(credits->end(),
00153 other.credits->begin(),
00154 other.credits->end());
00155 }
00156 }
00157
00158 partnumber = other.partnumber;
00159 parttotal = other.parttotal;
00160 syndicatedepisodenumber = other.syndicatedepisodenumber;
00161 subtitleType = other.subtitleType;
00162 audioProps = other.audioProps;
00163 videoProps = other.videoProps;
00164 stars = other.stars;
00165 categoryType = other.categoryType;
00166 seriesId = other.seriesId;
00167 programId = other.programId;
00168 previouslyshown = other.previouslyshown;
00169 ratings = other.ratings;
00170 listingsource = other.listingsource;
00171
00172 Squeeze();
00173
00174 return *this;
00175 }
00176
00177 void DBEvent::Squeeze(void)
00178 {
00179 title.squeeze();
00180 subtitle.squeeze();
00181 description.squeeze();
00182 category.squeeze();
00183 syndicatedepisodenumber.squeeze();
00184 seriesId.squeeze();
00185 programId.squeeze();
00186 }
00187
00188 void DBEvent::AddPerson(DBPerson::Role role, const QString &name)
00189 {
00190 if (!credits)
00191 credits = new DBCredits;
00192
00193 credits->push_back(DBPerson(role, name));
00194 }
00195
00196 void DBEvent::AddPerson(const QString &role, const QString &name)
00197 {
00198 if (!credits)
00199 credits = new DBCredits;
00200
00201 credits->push_back(DBPerson(role, name));
00202 }
00203
00204 bool DBEvent::HasTimeConflict(const DBEvent &o) const
00205 {
00206 return ((starttime <= o.starttime && o.starttime < endtime) ||
00207 (o.endtime <= endtime && starttime < o.endtime));
00208 }
00209
00210 uint DBEvent::UpdateDB(
00211 MSqlQuery &query, uint chanid, int match_threshold) const
00212 {
00213 vector<DBEvent> programs;
00214 uint count = GetOverlappingPrograms(query, chanid, programs);
00215 int match = INT_MIN;
00216 int i = -1;
00217
00218 if (!count)
00219 return InsertDB(query, chanid);
00220
00221
00222 match = GetMatch(programs, i);
00223
00224 if (match >= match_threshold)
00225 {
00226 LOG(VB_EIT, LOG_DEBUG,
00227 QString("EIT: accept match[%1]: %2 '%3' vs. '%4'")
00228 .arg(i).arg(match).arg(title).arg(programs[i].title));
00229 return UpdateDB(query, chanid, programs, i);
00230 }
00231 else
00232 {
00233 if (i >= 0)
00234 {
00235 LOG(VB_EIT, LOG_DEBUG,
00236 QString("EIT: reject match[%1]: %2 '%3' vs. '%4'")
00237 .arg(i).arg(match).arg(title).arg(programs[i].title));
00238 }
00239 return UpdateDB(query, chanid, programs, -1);
00240 }
00241 }
00242
00243 uint DBEvent::GetOverlappingPrograms(
00244 MSqlQuery &query, uint chanid, vector<DBEvent> &programs) const
00245 {
00246 uint count = 0;
00247 query.prepare(
00248 "SELECT title, subtitle, description, "
00249 " category, category_type, "
00250 " starttime, endtime, "
00251 " subtitletypes+0,audioprop+0, videoprop+0, "
00252 " seriesid, programid, "
00253 " partnumber, parttotal, "
00254 " syndicatedepisodenumber, "
00255 " airdate, originalairdate, "
00256 " previouslyshown,listingsource, "
00257 " stars+0 "
00258 "FROM program "
00259 "WHERE chanid = :CHANID AND "
00260 " manualid = 0 AND "
00261 " ( ( starttime >= :STIME1 AND starttime < :ETIME1 ) OR "
00262 " ( endtime > :STIME2 AND endtime <= :ETIME2 ) )");
00263 query.bindValue(":CHANID", chanid);
00264 query.bindValue(":STIME1", starttime);
00265 query.bindValue(":ETIME1", endtime);
00266 query.bindValue(":STIME2", starttime);
00267 query.bindValue(":ETIME2", endtime);
00268
00269 if (!query.exec())
00270 {
00271 MythDB::DBError("GetOverlappingPrograms 1", query);
00272 return 0;
00273 }
00274
00275 while (query.next())
00276 {
00277 MythCategoryType category_type =
00278 string_to_myth_category_type(query.value(4).toString());
00279
00280 DBEvent prog(
00281 query.value(0).toString(),
00282 query.value(1).toString(),
00283 query.value(2).toString(),
00284 query.value(3).toString(),
00285 category_type,
00286 query.value(5).toDateTime(), query.value(6).toDateTime(),
00287 query.value(7).toUInt(),
00288 query.value(8).toUInt(),
00289 query.value(9).toUInt(),
00290 query.value(19).toDouble(),
00291 query.value(10).toString(),
00292 query.value(11).toString(),
00293 query.value(18).toUInt());
00294
00295 prog.partnumber = query.value(12).toUInt();
00296 prog.parttotal = query.value(13).toUInt();
00297 prog.syndicatedepisodenumber = query.value(14).toString();
00298 prog.airdate = query.value(15).toUInt();
00299 prog.originalairdate = query.value(16).toDate();
00300 prog.previouslyshown = query.value(17).toBool();
00301 ;
00302
00303 programs.push_back(prog);
00304 count++;
00305 }
00306
00307 return count;
00308 }
00309
00310
00311 static int score_words(const QStringList &al, const QStringList &bl)
00312 {
00313 QStringList::const_iterator ait = al.begin();
00314 QStringList::const_iterator bit = bl.begin();
00315 int score = 0;
00316 for (; (ait != al.end()) && (bit != bl.end()); ++ait)
00317 {
00318 QStringList::const_iterator bit2 = bit;
00319 int dist = 0;
00320 int bscore = 0;
00321 for (; bit2 != bl.end(); ++bit2)
00322 {
00323 if (*ait == *bit)
00324 {
00325 bscore = max(1000, 2000 - (dist * 500));
00326
00327 if (ait->length() < 5)
00328 bscore /= 5 - ait->length();
00329 break;
00330 }
00331 dist++;
00332 }
00333 if (bscore && dist < 3)
00334 {
00335 for (int i = 0; (i < dist) && bit != bl.end(); i++)
00336 ++bit;
00337 }
00338 score += bscore;
00339 }
00340
00341 return score / al.size();
00342 }
00343
00344 static int score_match(const QString &a, const QString &b)
00345 {
00346 if (a.isEmpty() || b.isEmpty())
00347 return 0;
00348 else if (a == b)
00349 return 1000;
00350
00351 QString A = a.simplified().toUpper();
00352 QString B = b.simplified().toUpper();
00353 if (A == B)
00354 return 1000;
00355
00356 QStringList al, bl;
00357 al = A.split(" ", QString::SkipEmptyParts);
00358 if (!al.size())
00359 return 0;
00360
00361 bl = B.split(" ", QString::SkipEmptyParts);
00362 if (!bl.size())
00363 return 0;
00364
00365
00366 int score = (score_words(al, bl) + score_words(bl, al)) / 2;
00367
00368 return min(900, score);
00369 }
00370
00371 int DBEvent::GetMatch(const vector<DBEvent> &programs, int &bestmatch) const
00372 {
00373 bestmatch = -1;
00374 int match_val = INT_MIN;
00375 int overlap = 0;
00376 int duration = starttime.secsTo(endtime);
00377
00378 for (uint i = 0; i < programs.size(); i++)
00379 {
00380 int mv = 0;
00381 int duration_loop = programs[i].starttime.secsTo(programs[i].endtime);
00382
00383 mv -= abs(starttime.secsTo(programs[i].starttime));
00384 mv -= abs(endtime.secsTo(programs[i].endtime));
00385 mv -= abs(duration - duration_loop);
00386 mv += score_match(title, programs[i].title) * 10;
00387 mv += score_match(subtitle, programs[i].subtitle);
00388 mv += score_match(description, programs[i].description);
00389
00390
00391
00392 if (starttime < programs[i].starttime)
00393 overlap = programs[i].starttime.secsTo(endtime);
00394 else if (starttime > programs[i].starttime)
00395 overlap = starttime.secsTo(programs[i].endtime);
00396 else
00397 {
00398 if (endtime <= programs[i].endtime)
00399 overlap = starttime.secsTo(endtime);
00400 else
00401 overlap = starttime.secsTo(programs[i].endtime);
00402 }
00403
00404
00405
00406
00407 if (overlap > 0)
00408 {
00409
00410
00411
00412 int min_dur = max(2, min(duration, duration_loop));
00413 overlap = min(overlap, min_dur/2);
00414 mv *= overlap * 2;
00415 mv /= min_dur;
00416 }
00417 else
00418 {
00419 LOG(VB_GENERAL, LOG_ERR,
00420 QString("Unexpected result: shows don't "
00421 "overlap\n\t%1: %2 - %3\n\t%4: %5 - %6")
00422 .arg(title.left(30), 30)
00423 .arg(starttime.toString(Qt::ISODate))
00424 .arg(endtime.toString(Qt::ISODate))
00425 .arg(programs[i].title.left(30), 30)
00426 .arg(programs[i].starttime.toString(Qt::ISODate))
00427 .arg(programs[i].endtime.toString(Qt::ISODate))
00428 );
00429 }
00430
00431 if (mv > match_val)
00432 {
00433 LOG(VB_EIT, LOG_DEBUG,
00434 QString("GM : %1 new best match %2 with score %3")
00435 .arg(title.left(25))
00436 .arg(programs[i].title.left(25)).arg(mv));
00437 bestmatch = i;
00438 match_val = mv;
00439 }
00440 }
00441
00442 return match_val;
00443 }
00444
00445 uint DBEvent::UpdateDB(
00446 MSqlQuery &q, uint chanid, const vector<DBEvent> &p, int match) const
00447 {
00448
00449 bool ok = true;
00450 for (uint i = 0; i < p.size(); i++)
00451 {
00452 if (i != (uint)match)
00453 ok &= MoveOutOfTheWayDB(q, chanid, p[i]);
00454 }
00455
00456
00457 if (!ok)
00458 return 0;
00459
00460
00461 if ((match < 0) || ((uint)match >= p.size()))
00462 return InsertDB(q, chanid);
00463
00464
00465 return UpdateDB(q, chanid, p[match]);
00466 }
00467
00468 uint DBEvent::UpdateDB(
00469 MSqlQuery &query, uint chanid, const DBEvent &match) const
00470 {
00471 QString ltitle = title;
00472 QString lsubtitle = subtitle;
00473 QString ldesc = description;
00474 QString lcategory = category;
00475 uint16_t lairdate = airdate;
00476 QString lprogramId = programId;
00477 QString lseriesId = seriesId;
00478 QDate loriginalairdate = originalairdate;
00479
00480 if (match.title.length() >= ltitle.length())
00481 ltitle = match.title;
00482
00483 if (match.subtitle.length() >= lsubtitle.length())
00484 lsubtitle = match.subtitle;
00485
00486 if (match.description.length() >= ldesc.length())
00487 ldesc = match.description;
00488
00489 if (lcategory.isEmpty() && !match.category.isEmpty())
00490 lcategory = match.category;
00491
00492 if (!lairdate && !match.airdate)
00493 lairdate = match.airdate;
00494
00495 if (!loriginalairdate.isValid() && match.originalairdate.isValid())
00496 loriginalairdate = match.originalairdate;
00497
00498 if (lprogramId.isEmpty() && !match.programId.isEmpty())
00499 lprogramId = match.programId;
00500
00501 if (lseriesId.isEmpty() && !match.seriesId.isEmpty())
00502 lseriesId = match.seriesId;
00503
00504 uint tmp = categoryType;
00505 if (!categoryType && match.categoryType)
00506 tmp = match.categoryType;
00507
00508 QString lcattype = myth_category_type_to_string(tmp);
00509
00510 unsigned char lsubtype = subtitleType | match.subtitleType;
00511 unsigned char laudio = audioProps | match.audioProps;
00512 unsigned char lvideo = videoProps | match.videoProps;
00513
00514 uint lpartnumber =
00515 (!partnumber && match.partnumber) ? match.partnumber : partnumber;
00516 uint lparttotal =
00517 (!parttotal && match.parttotal ) ? match.parttotal : parttotal;
00518
00519 bool lpreviouslyshown = previouslyshown | match.previouslyshown;
00520
00521 uint32_t llistingsource = listingsource | match.listingsource;
00522
00523 QString lsyndicatedepisodenumber = syndicatedepisodenumber;
00524 if (lsyndicatedepisodenumber.isEmpty() &&
00525 !match.syndicatedepisodenumber.isEmpty())
00526 lsyndicatedepisodenumber = match.syndicatedepisodenumber;
00527
00528 query.prepare(
00529 "UPDATE program "
00530 "SET title = :TITLE, subtitle = :SUBTITLE, "
00531 " description = :DESC, "
00532 " category = :CATEGORY, category_type = :CATTYPE, "
00533 " starttime = :STARTTIME, endtime = :ENDTIME, "
00534 " closecaptioned = :CC, subtitled = :HASSUBTITLES, "
00535 " stereo = :STEREO, hdtv = :HDTV, "
00536 " subtitletypes = :SUBTYPE, "
00537 " audioprop = :AUDIOPROP, videoprop = :VIDEOPROP, "
00538 " partnumber = :PARTNO, parttotal = :PARTTOTAL, "
00539 " syndicatedepisodenumber = :SYNDICATENO, "
00540 " airdate = :AIRDATE, originalairdate=:ORIGAIRDATE, "
00541 " listingsource = :LSOURCE, "
00542 " seriesid = :SERIESID, programid = :PROGRAMID, "
00543 " previouslyshown = :PREVSHOWN "
00544 "WHERE chanid = :CHANID AND "
00545 " starttime = :OLDSTART ");
00546
00547 query.bindValue(":CHANID", chanid);
00548 query.bindValue(":OLDSTART", match.starttime);
00549 query.bindValue(":TITLE", denullify(ltitle));
00550 query.bindValue(":SUBTITLE", denullify(lsubtitle));
00551 query.bindValue(":DESC", denullify(ldesc));
00552 query.bindValue(":CATEGORY", denullify(lcategory));
00553 query.bindValue(":CATTYPE", lcattype);
00554 query.bindValue(":STARTTIME", starttime);
00555 query.bindValue(":ENDTIME", endtime);
00556 query.bindValue(":CC", lsubtype & SUB_HARDHEAR ? true : false);
00557 query.bindValue(":HASSUBTITLES",lsubtype & SUB_NORMAL ? true : false);
00558 query.bindValue(":STEREO", laudio & AUD_STEREO ? true : false);
00559 query.bindValue(":HDTV", lvideo & VID_HDTV ? true : false);
00560 query.bindValue(":SUBTYPE", lsubtype);
00561 query.bindValue(":AUDIOPROP", laudio);
00562 query.bindValue(":VIDEOPROP", lvideo);
00563 query.bindValue(":PARTNO", lpartnumber);
00564 query.bindValue(":PARTTOTAL", lparttotal);
00565 query.bindValue(":SYNDICATENO", denullify(lsyndicatedepisodenumber));
00566 query.bindValue(":AIRDATE", lairdate?QString::number(lairdate):"0000");
00567 query.bindValue(":ORIGAIRDATE", loriginalairdate);
00568 query.bindValue(":LSOURCE", llistingsource);
00569 query.bindValue(":SERIESID", denullify(lseriesId));
00570 query.bindValue(":PROGRAMID", denullify(lprogramId));
00571 query.bindValue(":PREVSHOWN", lpreviouslyshown);
00572
00573 if (!query.exec())
00574 {
00575 MythDB::DBError("InsertDB", query);
00576 return 0;
00577 }
00578
00579 if (credits)
00580 {
00581 for (uint i = 0; i < credits->size(); i++)
00582 (*credits)[i].InsertDB(query, chanid, starttime);
00583 }
00584
00585 return 1;
00586 }
00587
00588 static bool delete_program(MSqlQuery &query, uint chanid, const QDateTime &st)
00589 {
00590 query.prepare(
00591 "DELETE from program "
00592 "WHERE chanid = :CHANID AND "
00593 " starttime = :STARTTIME");
00594
00595 query.bindValue(":CHANID", chanid);
00596 query.bindValue(":STARTTIME", st);
00597
00598 if (!query.exec())
00599 {
00600 MythDB::DBError("delete_program", query);
00601 return false;
00602 }
00603
00604 query.prepare(
00605 "DELETE from credits "
00606 "WHERE chanid = :CHANID AND "
00607 " starttime = :STARTTIME");
00608
00609 query.bindValue(":CHANID", chanid);
00610 query.bindValue(":STARTTIME", st);
00611
00612 if (!query.exec())
00613 {
00614 MythDB::DBError("delete_credits", query);
00615 return false;
00616 }
00617
00618 return true;
00619 }
00620
00621 static bool change_program(MSqlQuery &query, uint chanid, const QDateTime &st,
00622 const QDateTime &new_st, const QDateTime &new_end)
00623 {
00624 query.prepare(
00625 "UPDATE program "
00626 "SET starttime = :NEWSTART, "
00627 " endtime = :NEWEND "
00628 "WHERE chanid = :CHANID AND "
00629 " starttime = :OLDSTART");
00630
00631 query.bindValue(":CHANID", chanid);
00632 query.bindValue(":OLDSTART", st);
00633 query.bindValue(":NEWSTART", new_st);
00634 query.bindValue(":NEWEND", new_end);
00635
00636 if (!query.exec())
00637 {
00638 MythDB::DBError("change_program", query);
00639 return false;
00640 }
00641
00642 query.prepare(
00643 "UPDATE credits "
00644 "SET starttime = :NEWSTART "
00645 "WHERE chanid = :CHANID AND "
00646 " starttime = :OLDSTART");
00647
00648 query.bindValue(":CHANID", chanid);
00649 query.bindValue(":OLDSTART", st);
00650 query.bindValue(":NEWSTART", new_st);
00651
00652 if (!query.exec())
00653 {
00654 MythDB::DBError("change_credits", query);
00655 return false;
00656 }
00657
00658 return true;
00659 }
00660
00661 bool DBEvent::MoveOutOfTheWayDB(
00662 MSqlQuery &query, uint chanid, const DBEvent &prog) const
00663 {
00664 if (prog.starttime >= starttime && prog.endtime <= endtime)
00665 {
00666
00667 return delete_program(query, chanid, prog.starttime);
00668 }
00669 else if (prog.starttime < starttime && prog.endtime > starttime)
00670 {
00671
00672 return change_program(query, chanid, prog.starttime,
00673 prog.starttime, starttime);
00674 }
00675 else if (prog.starttime < endtime && prog.endtime > endtime)
00676 {
00677
00678 return change_program(query, chanid, prog.starttime,
00679 endtime, prog.endtime);
00680 }
00681
00682 return true;
00683 }
00684
00685 uint DBEvent::InsertDB(MSqlQuery &query, uint chanid) const
00686 {
00687 query.prepare(
00688 "REPLACE INTO program ("
00689 " chanid, title, subtitle, description, "
00690 " category, category_type, "
00691 " starttime, endtime, "
00692 " closecaptioned, stereo, hdtv, subtitled, "
00693 " subtitletypes, audioprop, videoprop, "
00694 " stars, partnumber, parttotal, "
00695 " syndicatedepisodenumber, "
00696 " airdate, originalairdate,listingsource, "
00697 " seriesid, programid, previouslyshown ) "
00698 "VALUES ("
00699 " :CHANID, :TITLE, :SUBTITLE, :DESCRIPTION, "
00700 " :CATEGORY, :CATTYPE, "
00701 " :STARTTIME, :ENDTIME, "
00702 " :CC, :STEREO, :HDTV, :HASSUBTITLES, "
00703 " :SUBTYPES, :AUDIOPROP, :VIDEOPROP, "
00704 " :STARS, :PARTNUMBER, :PARTTOTAL, "
00705 " :SYNDICATENO, "
00706 " :AIRDATE, :ORIGAIRDATE, :LSOURCE, "
00707 " :SERIESID, :PROGRAMID, :PREVSHOWN) ");
00708
00709 QString cattype = myth_category_type_to_string(categoryType);
00710 QString empty("");
00711 query.bindValue(":CHANID", chanid);
00712 query.bindValue(":TITLE", denullify(title));
00713 query.bindValue(":SUBTITLE", denullify(subtitle));
00714 query.bindValue(":DESCRIPTION", denullify(description));
00715 query.bindValue(":CATEGORY", denullify(category));
00716 query.bindValue(":CATTYPE", cattype);
00717 query.bindValue(":STARTTIME", starttime);
00718 query.bindValue(":ENDTIME", endtime);
00719 query.bindValue(":CC", subtitleType & SUB_HARDHEAR ? true : false);
00720 query.bindValue(":STEREO", audioProps & AUD_STEREO ? true : false);
00721 query.bindValue(":HDTV", videoProps & VID_HDTV ? true : false);
00722 query.bindValue(":HASSUBTITLES",subtitleType & SUB_NORMAL ? true : false);
00723 query.bindValue(":SUBTYPES", subtitleType);
00724 query.bindValue(":AUDIOPROP", audioProps);
00725 query.bindValue(":VIDEOPROP", videoProps);
00726 query.bindValue(":STARS", stars);
00727 query.bindValue(":PARTNUMBER", partnumber);
00728 query.bindValue(":PARTTOTAL", parttotal);
00729 query.bindValue(":SYNDICATENO", denullify(syndicatedepisodenumber));
00730 query.bindValue(":AIRDATE", airdate ? QString::number(airdate):"0000");
00731 query.bindValue(":ORIGAIRDATE", originalairdate);
00732 query.bindValue(":LSOURCE", listingsource);
00733 query.bindValue(":SERIESID", denullify(seriesId));
00734 query.bindValue(":PROGRAMID", denullify(programId));
00735 query.bindValue(":PREVSHOWN", previouslyshown);
00736
00737 if (!query.exec())
00738 {
00739 MythDB::DBError("InsertDB", query);
00740 return 0;
00741 }
00742
00743 if (credits)
00744 {
00745 for (uint i = 0; i < credits->size(); i++)
00746 (*credits)[i].InsertDB(query, chanid, starttime);
00747 }
00748
00749 return 1;
00750 }
00751
00752 ProgInfo::ProgInfo(const ProgInfo &other) :
00753 DBEvent(other.listingsource)
00754 {
00755 *this = other;
00756 }
00757
00758 ProgInfo &ProgInfo::operator=(const ProgInfo &other)
00759 {
00760 if (this == &other)
00761 return *this;
00762
00763 DBEvent::operator=(other);
00764
00765 channel = other.channel;
00766 startts = other.startts;
00767 endts = other.endts;
00768 stars = other.stars;
00769 title_pronounce = other.title_pronounce;
00770 showtype = other.showtype;
00771 colorcode = other.colorcode;
00772 clumpidx = other.clumpidx;
00773 clumpmax = other.clumpmax;
00774
00775 channel.squeeze();
00776 startts.squeeze();
00777 endts.squeeze();
00778 stars.squeeze();
00779 title_pronounce.squeeze();
00780 showtype.squeeze();
00781 colorcode.squeeze();
00782 clumpidx.squeeze();
00783 clumpmax.squeeze();
00784
00785 return *this;
00786 }
00787
00788 void ProgInfo::Squeeze(void)
00789 {
00790 DBEvent::Squeeze();
00791 channel.squeeze();
00792 startts.squeeze();
00793 endts.squeeze();
00794 stars.squeeze();
00795 title_pronounce.squeeze();
00796 showtype.squeeze();
00797 colorcode.squeeze();
00798 clumpidx.squeeze();
00799 clumpmax.squeeze();
00800 }
00801
00802 uint ProgInfo::InsertDB(MSqlQuery &query, uint chanid) const
00803 {
00804 LOG(VB_XMLTV, LOG_INFO,
00805 QString("Inserting new program : %1 - %2 %3 %4")
00806 .arg(starttime.toString(Qt::ISODate))
00807 .arg(endtime.toString(Qt::ISODate))
00808 .arg(channel)
00809 .arg(title));
00810
00811 query.prepare(
00812 "REPLACE INTO program ("
00813 " chanid, title, subtitle, description, "
00814 " category, category_type, "
00815 " starttime, endtime, "
00816 " closecaptioned, stereo, hdtv, subtitled, "
00817 " subtitletypes, audioprop, videoprop, "
00818 " partnumber, parttotal, "
00819 " syndicatedepisodenumber, "
00820 " airdate, originalairdate,listingsource, "
00821 " seriesid, programid, previouslyshown, "
00822 " stars, showtype, title_pronounce, colorcode ) "
00823
00824 "VALUES("
00825 " :CHANID, :TITLE, :SUBTITLE, :DESCRIPTION, "
00826 " :CATEGORY, :CATTYPE, "
00827 " :STARTTIME, :ENDTIME, "
00828 " :CC, :STEREO, :HDTV, :HASSUBTITLES, "
00829 " :SUBTYPES, :AUDIOPROP, :VIDEOPROP, "
00830 " :PARTNUMBER, :PARTTOTAL, "
00831 " :SYNDICATENO, "
00832 " :AIRDATE, :ORIGAIRDATE, :LSOURCE, "
00833 " :SERIESID, :PROGRAMID, :PREVSHOWN, "
00834 " :STARS, :SHOWTYPE, :TITLEPRON, :COLORCODE)");
00835
00836 QString cattype = myth_category_type_to_string(categoryType);
00837
00838 query.bindValue(":CHANID", chanid);
00839 query.bindValue(":TITLE", denullify(title));
00840 query.bindValue(":SUBTITLE", denullify(subtitle));
00841 query.bindValue(":DESCRIPTION", denullify(description));
00842 query.bindValue(":CATEGORY", denullify(category));
00843 query.bindValue(":CATTYPE", cattype);
00844 query.bindValue(":STARTTIME", starttime);
00845 query.bindValue(":ENDTIME", endtime);
00846 query.bindValue(":CC",
00847 subtitleType & SUB_HARDHEAR ? true : false);
00848 query.bindValue(":STEREO",
00849 audioProps & AUD_STEREO ? true : false);
00850 query.bindValue(":HDTV",
00851 videoProps & VID_HDTV ? true : false);
00852 query.bindValue(":HASSUBTITLES",
00853 subtitleType & SUB_NORMAL ? true : false);
00854 query.bindValue(":SUBTYPES", subtitleType);
00855 query.bindValue(":AUDIOPROP", audioProps);
00856 query.bindValue(":VIDEOPROP", videoProps);
00857 query.bindValue(":PARTNUMBER", partnumber);
00858 query.bindValue(":PARTTOTAL", parttotal);
00859 query.bindValue(":SYNDICATENO", denullify(syndicatedepisodenumber));
00860 query.bindValue(":AIRDATE", airdate ? QString::number(airdate):"0000");
00861 query.bindValue(":ORIGAIRDATE", originalairdate);
00862 query.bindValue(":LSOURCE", listingsource);
00863 query.bindValue(":SERIESID", denullify(seriesId));
00864 query.bindValue(":PROGRAMID", denullify(programId));
00865 query.bindValue(":PREVSHOWN", previouslyshown);
00866 query.bindValue(":STARS", stars);
00867 query.bindValue(":SHOWTYPE", showtype);
00868 query.bindValue(":TITLEPRON", title_pronounce);
00869 query.bindValue(":COLORCODE", colorcode);
00870
00871 if (!query.exec())
00872 {
00873 MythDB::DBError("program insert", query);
00874 return 0;
00875 }
00876
00877 QList<EventRating>::const_iterator j = ratings.begin();
00878 for (; j != ratings.end(); ++j)
00879 {
00880 query.prepare(
00881 "INSERT INTO programrating "
00882 " ( chanid, starttime, system, rating) "
00883 "VALUES (:CHANID, :START, :SYS, :RATING)");
00884 query.bindValue(":CHANID", chanid);
00885 query.bindValue(":START", starttime);
00886 query.bindValue(":SYS", (*j).system);
00887 query.bindValue(":RATING", (*j).rating);
00888
00889 if (!query.exec())
00890 MythDB::DBError("programrating insert", query);
00891 }
00892
00893 if (credits)
00894 {
00895 for (uint i = 0; i < credits->size(); ++i)
00896 (*credits)[i].InsertDB(query, chanid, starttime);
00897 }
00898
00899 return 1;
00900 }
00901
00902 bool ProgramData::ClearDataByChannel(
00903 uint chanid, const QDateTime &from, const QDateTime &to,
00904 bool use_channel_time_offset)
00905 {
00906 int secs = 0;
00907 if (use_channel_time_offset)
00908 secs = ChannelUtil::GetTimeOffset(chanid) * 60;
00909
00910 QDateTime newFrom = from.addSecs(secs);
00911 QDateTime newTo = to.addSecs(secs);
00912
00913 MSqlQuery query(MSqlQuery::InitCon());
00914 query.prepare("DELETE FROM program "
00915 "WHERE starttime >= :FROM AND starttime < :TO "
00916 "AND chanid = :CHANID ;");
00917 query.bindValue(":FROM", newFrom);
00918 query.bindValue(":TO", newTo);
00919 query.bindValue(":CHANID", chanid);
00920 bool ok = query.exec();
00921
00922 query.prepare("DELETE FROM programrating "
00923 "WHERE starttime >= :FROM AND starttime < :TO "
00924 "AND chanid = :CHANID ;");
00925 query.bindValue(":FROM", newFrom);
00926 query.bindValue(":TO", newTo);
00927 query.bindValue(":CHANID", chanid);
00928 ok &= query.exec();
00929
00930 query.prepare("DELETE FROM credits "
00931 "WHERE starttime >= :FROM AND starttime < :TO "
00932 "AND chanid = :CHANID ;");
00933 query.bindValue(":FROM", newFrom);
00934 query.bindValue(":TO", newTo);
00935 query.bindValue(":CHANID", chanid);
00936 ok &= query.exec();
00937
00938 query.prepare("DELETE FROM programgenres "
00939 "WHERE starttime >= :FROM AND starttime < :TO "
00940 "AND chanid = :CHANID ;");
00941 query.bindValue(":FROM", newFrom);
00942 query.bindValue(":TO", newTo);
00943 query.bindValue(":CHANID", chanid);
00944 ok &= query.exec();
00945
00946 return ok;
00947 }
00948
00949 bool ProgramData::ClearDataBySource(
00950 uint sourceid, const QDateTime &from, const QDateTime &to,
00951 bool use_channel_time_offset)
00952 {
00953 vector<uint> chanids = ChannelUtil::GetChanIDs(sourceid);
00954
00955 bool ok = true;
00956 for (uint i = 0; i < chanids.size(); i++)
00957 ok &= ClearDataByChannel(chanids[i], from, to, use_channel_time_offset);
00958
00959 return ok;
00960 }
00961
00962 static bool start_time_less_than(const DBEvent *a, const DBEvent *b)
00963 {
00964 return (a->starttime < b->starttime);
00965 }
00966
00967 void ProgramData::FixProgramList(QList<ProgInfo*> &fixlist)
00968 {
00969 qStableSort(fixlist.begin(), fixlist.end(), start_time_less_than);
00970
00971 QList<ProgInfo*>::iterator it = fixlist.begin();
00972 while (1)
00973 {
00974 QList<ProgInfo*>::iterator cur = it;
00975 ++it;
00976
00977
00978 if ((*cur)->endts.isEmpty() || (*cur)->startts > (*cur)->endts)
00979 {
00980 if (it != fixlist.end())
00981 {
00982 (*cur)->endts = (*it)->startts;
00983 (*cur)->endtime = (*it)->starttime;
00984 }
00985 else
00986 {
00987 (*cur)->endtime = (*cur)->starttime;
00988 if ((*cur)->endtime < QDateTime((*cur)->endtime.date(), QTime(6, 0)))
00989 {
00990 (*cur)->endtime.setTime(QTime(6, 0));
00991 }
00992 else
00993 {
00994 (*cur)->endtime.setTime(QTime(0, 0));
00995 (*cur)->endtime.setDate((*cur)->endtime.date().addDays(1));
00996 }
00997
00998 QString datestr = (*cur)->endtime.toString("yyyyMMddhhmmss");
00999 QByteArray datearr = datestr.toAscii();
01000 (*cur)->endts = QString(datearr.constData());
01001 }
01002 }
01003
01004 if (it == fixlist.end())
01005 break;
01006
01007
01008 if ((*cur)->HasTimeConflict(**it))
01009 {
01010 QList<ProgInfo*>::iterator tokeep, todelete;
01011
01012 if ((*cur)->endtime <= (*cur)->starttime)
01013 tokeep = it, todelete = cur;
01014 else if ((*it)->endtime <= (*it)->starttime)
01015 tokeep = cur, todelete = it;
01016 else if (!(*cur)->subtitle.isEmpty() &&
01017 (*it)->subtitle.isEmpty())
01018 tokeep = cur, todelete = it;
01019 else if (!(*it)->subtitle.isEmpty() &&
01020 (*cur)->subtitle.isEmpty())
01021 tokeep = it, todelete = cur;
01022 else if (!(*cur)->description.isEmpty() &&
01023 (*it)->description.isEmpty())
01024 tokeep = cur, todelete = it;
01025 else
01026 tokeep = it, todelete = cur;
01027
01028
01029 LOG(VB_XMLTV, LOG_INFO,
01030 QString("Removing conflicting program: %1 - %2 %3 %4")
01031 .arg((*todelete)->starttime.toString(Qt::ISODate))
01032 .arg((*todelete)->endtime.toString(Qt::ISODate))
01033 .arg((*todelete)->channel)
01034 .arg((*todelete)->title));
01035
01036 LOG(VB_XMLTV, LOG_INFO,
01037 QString("Conflicted with : %1 - %2 %3 %4")
01038 .arg((*tokeep)->starttime.toString(Qt::ISODate))
01039 .arg((*tokeep)->endtime.toString(Qt::ISODate))
01040 .arg((*tokeep)->channel)
01041 .arg((*tokeep)->title));
01042
01043 bool step_back = todelete == it;
01044 it = fixlist.erase(todelete);
01045 if (step_back)
01046 --it;
01047 }
01048 }
01049 }
01050
01051 void ProgramData::HandlePrograms(
01052 uint sourceid, QMap<QString, QList<ProgInfo> > &proglist)
01053 {
01054 uint unchanged = 0, updated = 0;
01055
01056 MSqlQuery query(MSqlQuery::InitCon());
01057
01058 QMap<QString, QList<ProgInfo> >::const_iterator mapiter;
01059 for (mapiter = proglist.begin(); mapiter != proglist.end(); ++mapiter)
01060 {
01061 if (mapiter.key().isEmpty())
01062 continue;
01063
01064 query.prepare(
01065 "SELECT chanid "
01066 "FROM channel "
01067 "WHERE sourceid = :ID AND "
01068 " xmltvid = :XMLTVID");
01069 query.bindValue(":ID", sourceid);
01070 query.bindValue(":XMLTVID", mapiter.key());
01071
01072 if (!query.exec())
01073 {
01074 MythDB::DBError("ProgramData::HandlePrograms", query);
01075 continue;
01076 }
01077
01078 vector<uint> chanids;
01079 while (query.next())
01080 chanids.push_back(query.value(0).toUInt());
01081
01082 if (chanids.empty())
01083 {
01084 LOG(VB_GENERAL, LOG_NOTICE,
01085 QString("Unknown xmltv channel identifier: %1"
01086 " - Skipping channel.").arg(mapiter.key()));
01087 continue;
01088 }
01089
01090 QList<ProgInfo> &list = proglist[mapiter.key()];
01091 QList<ProgInfo*> sortlist;
01092 QList<ProgInfo>::iterator it = list.begin();
01093 for (; it != list.end(); ++it)
01094 sortlist.push_back(&(*it));
01095
01096 FixProgramList(sortlist);
01097
01098 for (uint i = 0; i < chanids.size(); ++i)
01099 {
01100 HandlePrograms(query, chanids[i], sortlist, unchanged, updated);
01101 }
01102 }
01103
01104 LOG(VB_GENERAL, LOG_INFO,
01105 QString("Updated programs: %1 Unchanged programs: %2")
01106 .arg(updated) .arg(unchanged));
01107 }
01108
01109 void ProgramData::HandlePrograms(MSqlQuery &query,
01110 uint chanid,
01111 const QList<ProgInfo*> &sortlist,
01112 uint &unchanged,
01113 uint &updated)
01114 {
01115 QList<ProgInfo*>::const_iterator it = sortlist.begin();
01116 for (; it != sortlist.end(); ++it)
01117 {
01118 if (IsUnchanged(query, chanid, **it))
01119 {
01120 unchanged++;
01121 continue;
01122 }
01123
01124 if (!DeleteOverlaps(query, chanid, **it))
01125 continue;
01126
01127 updated += (*it)->InsertDB(query, chanid);
01128 }
01129 }
01130
01131 int ProgramData::fix_end_times(void)
01132 {
01133 int count = 0;
01134 QString chanid, starttime, endtime, querystr;
01135 MSqlQuery query1(MSqlQuery::InitCon()), query2(MSqlQuery::InitCon());
01136
01137 querystr = "SELECT chanid, starttime, endtime FROM program "
01138 "WHERE (DATE_FORMAT(endtime,'%H%i') = '0000') "
01139 "ORDER BY chanid, starttime;";
01140
01141 if (!query1.exec(querystr))
01142 {
01143 LOG(VB_GENERAL, LOG_ERR,
01144 QString("fix_end_times query failed: %1").arg(querystr));
01145 return -1;
01146 }
01147
01148 while (query1.next())
01149 {
01150 starttime = query1.value(1).toString();
01151 chanid = query1.value(0).toString();
01152 endtime = query1.value(2).toString();
01153
01154 querystr = QString("SELECT chanid, starttime, endtime FROM program "
01155 "WHERE starttime BETWEEN '%1 00:00:00'"
01156 "AND '%2 23:59:59' AND chanid = '%3' "
01157 "ORDER BY starttime LIMIT 1;")
01158 .arg(endtime.left(10))
01159 .arg(endtime.left(10))
01160 .arg(chanid);
01161
01162 if (!query2.exec(querystr))
01163 {
01164 LOG(VB_GENERAL, LOG_ERR,
01165 QString("fix_end_times query failed: %1").arg(querystr));
01166 return -1;
01167 }
01168
01169 if (query2.next() && (endtime != query2.value(1).toString()))
01170 {
01171 count++;
01172 endtime = query2.value(1).toString();
01173 querystr = QString("UPDATE program SET starttime = '%1', "
01174 "endtime = '%2' WHERE (chanid = '%3' AND "
01175 "starttime = '%4');")
01176 .arg(starttime)
01177 .arg(endtime)
01178 .arg(chanid)
01179 .arg(starttime);
01180
01181 if (!query2.exec(querystr))
01182 {
01183 LOG(VB_GENERAL, LOG_ERR,
01184 QString("fix_end_times query failed: %1").arg(querystr));
01185 return -1;
01186 }
01187 }
01188 }
01189
01190 return count;
01191 }
01192
01193 bool ProgramData::IsUnchanged(
01194 MSqlQuery &query, uint chanid, const ProgInfo &pi)
01195 {
01196 query.prepare(
01197 "SELECT count(*) "
01198 "FROM program "
01199 "WHERE chanid = :CHANID AND "
01200 " starttime = :START AND "
01201 " endtime = :END AND "
01202 " title = :TITLE AND "
01203 " subtitle = :SUBTITLE AND "
01204 " description = :DESC AND "
01205 " category = :CATEGORY AND "
01206 " category_type = :CATEGORY_TYPE AND "
01207 " airdate = :AIRDATE AND "
01208 " stars >= (:STARS1 - 0.001) AND "
01209 " stars <= (:STARS2 + 0.001) AND "
01210 " previouslyshown = :PREVIOUSLYSHOWN AND "
01211 " title_pronounce = :TITLE_PRONOUNCE AND "
01212 " audioprop = :AUDIOPROP AND "
01213 " videoprop = :VIDEOPROP AND "
01214 " subtitletypes = :SUBTYPES AND "
01215 " partnumber = :PARTNUMBER AND "
01216 " parttotal = :PARTTOTAL AND "
01217 " seriesid = :SERIESID AND "
01218 " showtype = :SHOWTYPE AND "
01219 " colorcode = :COLORCODE AND "
01220 " syndicatedepisodenumber = :SYNDICATEDEPISODENUMBER AND "
01221 " programid = :PROGRAMID");
01222
01223 QString cattype = myth_category_type_to_string(pi.categoryType);
01224
01225 query.bindValue(":CHANID", chanid);
01226 query.bindValue(":START", pi.starttime);
01227 query.bindValue(":END", pi.endtime);
01228 query.bindValue(":TITLE", denullify(pi.title));
01229 query.bindValue(":SUBTITLE", denullify(pi.subtitle));
01230 query.bindValue(":DESC", denullify(pi.description));
01231 query.bindValue(":CATEGORY", denullify(pi.category));
01232 query.bindValue(":CATEGORY_TYPE", cattype);
01233 query.bindValue(":AIRDATE", pi.airdate);
01234 query.bindValue(":STARS1", pi.stars);
01235 query.bindValue(":STARS2", pi.stars);
01236 query.bindValue(":PREVIOUSLYSHOWN", pi.previouslyshown);
01237 query.bindValue(":TITLE_PRONOUNCE", pi.title_pronounce);
01238 query.bindValue(":AUDIOPROP", pi.audioProps);
01239 query.bindValue(":VIDEOPROP", pi.videoProps);
01240 query.bindValue(":SUBTYPES", pi.subtitleType);
01241 query.bindValue(":PARTNUMBER", pi.partnumber);
01242 query.bindValue(":PARTTOTAL", pi.parttotal);
01243 query.bindValue(":SERIESID", denullify(pi.seriesId));
01244 query.bindValue(":SHOWTYPE", pi.showtype);
01245 query.bindValue(":COLORCODE", pi.colorcode);
01246 query.bindValue(":SYNDICATEDEPISODENUMBER",
01247 denullify(pi.syndicatedepisodenumber));
01248 query.bindValue(":PROGRAMID", denullify(pi.programId));
01249
01250 if (query.exec() && query.next())
01251 return query.value(0).toUInt() > 0;
01252
01253 return false;
01254 }
01255
01256 bool ProgramData::DeleteOverlaps(
01257 MSqlQuery &query, uint chanid, const ProgInfo &pi)
01258 {
01259 if (VERBOSE_LEVEL_CHECK(VB_XMLTV, LOG_INFO))
01260 {
01261
01262 query.prepare(
01263 "SELECT title,starttime,endtime "
01264 "FROM program "
01265 "WHERE chanid = :CHANID AND "
01266 " starttime >= :START AND "
01267 " starttime < :END;");
01268 query.bindValue(":CHANID", chanid);
01269 query.bindValue(":START", pi.starttime);
01270 query.bindValue(":END", pi.endtime);
01271
01272 if (!query.exec())
01273 return false;
01274
01275 if (!query.next())
01276 return true;
01277
01278 do
01279 {
01280 LOG(VB_XMLTV, LOG_INFO,
01281 QString("Removing existing program: %1 - %2 %3 %4")
01282 .arg(query.value(1).toDateTime().toString(Qt::ISODate))
01283 .arg(query.value(2).toDateTime().toString(Qt::ISODate))
01284 .arg(pi.channel)
01285 .arg(query.value(0).toString()));
01286 } while (query.next());
01287 }
01288
01289 if (!ClearDataByChannel(chanid, pi.starttime, pi.endtime, false))
01290 {
01291 LOG(VB_XMLTV, LOG_ERR,
01292 QString("Program delete failed : %1 - %2 %3 %4")
01293 .arg(pi.starttime.toString(Qt::ISODate))
01294 .arg(pi.endtime.toString(Qt::ISODate))
01295 .arg(pi.channel)
01296 .arg(pi.title));
01297 return false;
01298 }
01299
01300 return true;
01301 }