|
MythTV
0.26-pre
|
00001 // -*- Mode: c++ -*- 00002 00003 #include <limits.h> 00004 00005 // C++ includes 00006 #include <algorithm> 00007 using namespace std; 00008 00009 // MythTV headers 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 // move overlapping programs out of the way and update existing if possible 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 // lower score for short words 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 // score words symmetrically 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 /* determine overlap of both programs 00391 * we don't know which one starts first */ 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 /* scale the score depending on the overlap length 00405 * full score is preserved if the overlap is at least 1/2 of the length 00406 * of the shorter program */ 00407 if (overlap > 0) 00408 { 00409 /* crappy providers apparently have events without duration 00410 * ensure that the minimal duration is 2 second to avoid 00411 * muliplying and more importantly dividing by zero */ 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 // adjust/delete overlaps; 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 // if we failed to move programs out of the way, don't insert new ones.. 00457 if (!ok) 00458 return 0; 00459 00460 // if no match, insert current item 00461 if ((match < 0) || ((uint)match >= p.size())) 00462 return InsertDB(q, chanid); 00463 00464 // update matched item with current data 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 // inside current program 00667 return delete_program(query, chanid, prog.starttime); 00668 } 00669 else if (prog.starttime < starttime && prog.endtime > starttime) 00670 { 00671 // starts before, but ends during our program 00672 return change_program(query, chanid, prog.starttime, 00673 prog.starttime, starttime); 00674 } 00675 else if (prog.starttime < endtime && prog.endtime > endtime) 00676 { 00677 // starts during, but ends after our program 00678 return change_program(query, chanid, prog.starttime, 00679 endtime, prog.endtime); 00680 } 00681 // must be non-conflicting... 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 // fill in miss stop times 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 // remove overlapping programs 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 // Get overlaps.. 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 }
1.7.6.1