00001
00002
00003
00004 #include <algorithm>
00005 using namespace std;
00006
00007
00008 #include <qdeepcopy.h>
00009
00010
00011 #include "mythdbcon.h"
00012 #include "eit.h"
00013 #include "dvbdescriptors.h"
00014 #include "programinfo.h"
00015
00016 DBPerson::DBPerson(Role _role, const QString &_name) :
00017 role(_role), name(QDeepCopy<QString>(_name))
00018 {
00019 }
00020
00021 QString DBPerson::GetRole(void) const
00022 {
00023 static const char* roles[] =
00024 {
00025 "actor", "director", "producer", "executive_producer",
00026 "writer", "guest_star", "host", "adapter",
00027 "presenter", "commentator", "guest",
00028 };
00029 if ((role < kActor) || (role > kGuest))
00030 return "guest";
00031 return roles[role];
00032 }
00033
00034 uint DBPerson::InsertDB(MSqlQuery &query, uint chanid,
00035 const QDateTime &starttime) const
00036 {
00037 uint personid = GetPersonDB(query);
00038 if (!personid && InsertPersonDB(query))
00039 personid = GetPersonDB(query);
00040
00041 return InsertCreditsDB(query, personid, chanid, starttime);
00042 }
00043
00044 uint DBPerson::GetPersonDB(MSqlQuery &query) const
00045 {
00046 query.prepare(
00047 "SELECT person "
00048 "FROM people "
00049 "WHERE name = :NAME");
00050 query.bindValue(":NAME", name.utf8());
00051
00052 if (!query.exec())
00053 MythContext::DBError("get_person", query);
00054 else if (query.next())
00055 return query.value(0).toUInt();
00056
00057 return 0;
00058 }
00059
00060 uint DBPerson::InsertPersonDB(MSqlQuery &query) const
00061 {
00062 query.prepare(
00063 "INSERT IGNORE INTO people (name) "
00064 "VALUES (:NAME);");
00065 query.bindValue(":NAME", name.utf8());
00066
00067 if (query.exec())
00068 return 1;
00069
00070 MythContext::DBError("insert_person", query);
00071 return 0;
00072 }
00073
00074 uint DBPerson::InsertCreditsDB(MSqlQuery &query, uint personid, uint chanid,
00075 const QDateTime &starttime) const
00076 {
00077 if (!personid)
00078 return 0;
00079
00080 query.prepare(
00081 "REPLACE INTO credits "
00082 " ( person, chanid, starttime, role) "
00083 "VALUES (:PERSON, :CHANID, :STARTTIME, :ROLE) ");
00084 query.bindValue(":PERSON", personid);
00085 query.bindValue(":CHANID", chanid);
00086 query.bindValue(":STARTTIME", starttime);
00087 query.bindValue(":ROLE", GetRole().utf8());
00088
00089 if (query.exec())
00090 return 1;
00091
00092 MythContext::DBError("insert_credits", query);
00093 return 0;
00094 }
00095
00096 void DBEvent::AddPerson(DBPerson::Role role, const QString &name)
00097 {
00098 if (!credits)
00099 credits = new DBCredits;
00100
00101 credits->push_back(DBPerson(role, name));
00102 }
00103
00104 uint DBEvent::UpdateDB(MSqlQuery &query, int match_threshold) const
00105 {
00106 vector<DBEvent> programs;
00107 uint count = GetOverlappingPrograms(query, programs);
00108 int match = INT_MIN;
00109 int i = -1;
00110
00111 if (count)
00112 match = GetMatch(programs, i);
00113
00114 if ((match < match_threshold) && (i >= 0))
00115 {
00116 VERBOSE(VB_EIT, QString("match[%1]: %2 '%3' vs. '%4'")
00117 .arg(i).arg(match).arg(title).arg(programs[i].title));
00118 }
00119
00120 if (match >= match_threshold)
00121 return UpdateDB(query, programs, i);
00122 else if (!count)
00123 return InsertDB(query);
00124 else
00125 return UpdateDB(query, programs, -1);
00126 }
00127
00128 uint DBEvent::GetOverlappingPrograms(MSqlQuery &query,
00129 vector<DBEvent> &programs) const
00130 {
00131 uint count = 0;
00132 query.prepare(
00133 "SELECT title, subtitle, description, "
00134 " category, category_type, "
00135 " starttime, endtime, "
00136 " subtitletypes+0,audioprop+0, videoprop+0, "
00137 " seriesid, programid, "
00138 " partnumber, parttotal, "
00139 " syndicatedepisodenumber, "
00140 " airdate, originalairdate, "
00141 " previouslyshown "
00142 "FROM program "
00143 "WHERE chanid = :CHANID AND "
00144 " manualid = 0 AND "
00145 " ( ( starttime >= :STIME1 AND starttime < :ETIME1 ) OR "
00146 " ( endtime > :STIME2 AND endtime <= :ETIME2 ) )");
00147 query.bindValue(":CHANID", chanid);
00148 query.bindValue(":STIME1", starttime);
00149 query.bindValue(":ETIME1", endtime);
00150 query.bindValue(":STIME2", starttime);
00151 query.bindValue(":ETIME2", endtime);
00152
00153 if (!query.exec() || !query.isActive())
00154 {
00155 MythContext::DBError("GetOverlappingPrograms 1", query);
00156 return 0;
00157 }
00158
00159 while (query.next())
00160 {
00161 MythCategoryType category_type =
00162 string_to_myth_category_type(query.value(4).toString());
00163
00164 DBEvent prog(chanid,
00165 QString::fromUtf8(query.value(0).toString()),
00166 QString::fromUtf8(query.value(1).toString()),
00167 QString::fromUtf8(query.value(2).toString()),
00168 QString::fromUtf8(query.value(3).toString()),
00169 category_type,
00170 query.value(5).toDateTime(), query.value(6).toDateTime(),
00171 fixup,
00172 query.value(7).toUInt(),
00173 query.value(8).toUInt(),
00174 query.value(9).toUInt(),
00175 QString::fromUtf8(query.value(10).toString()),
00176 QString::fromUtf8(query.value(11).toString())
00177 );
00178
00179 prog.partnumber = query.value(12).toUInt();
00180 prog.parttotal = query.value(13).toUInt();
00181 prog.syndicatedepisodenumber =
00182 QString::fromUtf8(query.value(14).toString());
00183 prog.airdate = query.value(15).toString();
00184 prog.originalairdate = query.value(16).toDate();
00185
00186 prog.previouslyshown = query.value(17).toBool();
00187
00188 programs.push_back(prog);
00189 count++;
00190 }
00191
00192 return count;
00193 }
00194
00195 static int score_match(const QString &a, const QString &b)
00196 {
00197 if (a.isEmpty() || b.isEmpty())
00198 return 0;
00199 else if (a == b)
00200 return 1000;
00201
00202 QString A = a.stripWhiteSpace().upper();
00203 QString B = b.stripWhiteSpace().upper();
00204 if (A == B)
00205 return 1000;
00206
00207 QStringList al, bl;
00208 al.split("", A);
00209 if (!al.size())
00210 return 0;
00211
00212 bl.split("", B);
00213 if (!bl.size())
00214 return 0;
00215
00216 QStringList::const_iterator ait = al.begin();
00217 QStringList::const_iterator bit = bl.begin();
00218 int score = 0;
00219 for (; (ait != al.end()) && (bit != bl.end()); ait++)
00220 {
00221 QStringList::const_iterator bit2 = bit;
00222 int dist = 0;
00223 int bscore = 0;
00224 for (; bit2 != bl.end(); bit2++)
00225 {
00226 if (*ait == *bit)
00227 {
00228 bscore = max(1000, 2000 - (dist * 500));
00229 break;
00230 }
00231 dist++;
00232 }
00233 if (bscore && dist < 3)
00234 {
00235 for (int i = 0; (i < dist) && bit != bl.end(); i++)
00236 bit++;
00237 }
00238 score += bscore;
00239 }
00240 score /= al.size();
00241
00242 return min(1000, score);
00243 }
00244
00245 int DBEvent::GetMatch(const vector<DBEvent> &programs, int &bestmatch) const
00246 {
00247 bestmatch = -1;
00248 int match_val = INT_MIN;
00249
00250 for (uint i = 0; i < programs.size(); i++)
00251 {
00252 int mv = 0;
00253 mv -= abs(starttime.secsTo(programs[i].starttime));
00254 mv -= abs(endtime.secsTo(programs[i].endtime));
00255 mv += score_match(title, programs[i].title) * 10;
00256 mv += score_match(subtitle, programs[i].subtitle);
00257 mv += score_match(description, programs[i].description);
00258
00259 if (mv > match_val)
00260 {
00261 bestmatch = i;
00262 match_val = mv;
00263 }
00264 }
00265
00266 return match_val;
00267 }
00268
00269 uint DBEvent::UpdateDB(MSqlQuery &q, const vector<DBEvent> &p, int match) const
00270 {
00271
00272 bool ok = true;
00273 for (uint i = 0; i < p.size(); i++)
00274 {
00275 if (i != (uint)match)
00276 ok &= MoveOutOfTheWayDB(q, p[i]);
00277 }
00278
00279
00280 if (!ok)
00281 return 0;
00282
00283
00284 if ((match < 0) || ((uint)match >= p.size()))
00285 return InsertDB(q);
00286
00287
00288 return UpdateDB(q, p[match]);
00289 }
00290
00291 uint DBEvent::UpdateDB(MSqlQuery &query, const DBEvent &match) const
00292 {
00293 QString ltitle = title;
00294 QString lsubtitle = subtitle;
00295 QString ldesc = description;
00296 QString lcategory = category;
00297 QString lairdate = airdate;
00298 QString lprogramId = AddAuthority(programId, query);
00299 QString lseriesId = AddAuthority(seriesId, query);
00300 QDate loriginalairdate = originalairdate;
00301
00302 if (match.title.length() >= ltitle.length())
00303 ltitle = match.title;
00304
00305 if (match.subtitle.length() >= lsubtitle.length())
00306 lsubtitle = match.subtitle;
00307
00308 if (match.description.length() >= ldesc.length())
00309 ldesc = match.description;
00310
00311 if (lcategory.isEmpty() && !match.category.isEmpty())
00312 lcategory = match.category;
00313
00314 if (lairdate.isEmpty() && !match.airdate.isEmpty() && match.airdate != '0')
00315 lairdate = match.airdate;
00316
00317 if (!loriginalairdate.isValid() && match.originalairdate.isValid())
00318 loriginalairdate = match.originalairdate;
00319
00320 if (lprogramId.isEmpty() && !match.programId.isEmpty())
00321 lprogramId = match.programId;
00322
00323 if (lseriesId.isEmpty() && !match.seriesId.isEmpty())
00324 lseriesId = match.seriesId;
00325
00326 uint tmp = category_type;
00327 if (!category_type && match.category_type)
00328 tmp = match.category_type;
00329
00330 QString lcattype = myth_category_type_to_string(tmp);
00331
00332 unsigned char lsubtype = subtitleType | match.subtitleType;
00333 unsigned char laudio = audioProps | match.audioProps;
00334 unsigned char lvideo = videoProps | match.videoProps;
00335
00336 uint lpartnumber =
00337 (!partnumber && match.partnumber) ? match.partnumber : partnumber;
00338 uint lparttotal =
00339 (!parttotal && match.parttotal ) ? match.parttotal : parttotal;
00340
00341 bool lpreviouslyshown = previouslyshown | match.previouslyshown;
00342
00343 QString lsyndicatedepisodenumber = syndicatedepisodenumber;
00344 if (lsyndicatedepisodenumber.isEmpty() &&
00345 !match.syndicatedepisodenumber.isEmpty())
00346 lsyndicatedepisodenumber = match.syndicatedepisodenumber;
00347
00348 query.prepare(
00349 "UPDATE program "
00350 "SET title = :TITLE, subtitle = :SUBTITLE, "
00351 " description = :DESC, "
00352 " category = :CAT, category_type = :CATTYPE, "
00353 " starttime = :STARTTIME, endtime = :ENDTIME, "
00354 " closecaptioned = :CC, subtitled = :SUBTITLED, "
00355 " stereo = :STEREO, hdtv = :HDTV, "
00356 " subtitletypes = :SUBTYPE, "
00357 " audioprop = :AUDIOPROP, videoprop = :VIDEOPROP, "
00358 " partnumber = :PARTNO, parttotal = :PARTTOTAL, "
00359 " syndicatedepisodenumber = :SYNDICATENO, "
00360 " airdate = :AIRDATE, originalairdate=:ORIGAIRDATE, "
00361 " listingsource = :LSOURCE, "
00362 " seriesid = :SERIESID, programid = :PROGRAMID, "
00363 " previouslyshown = :PREVSHOWN "
00364 "WHERE chanid = :CHANID AND "
00365 " starttime = :OLDSTART ");
00366
00367 query.bindValue(":CHANID", chanid);
00368 query.bindValue(":OLDSTART", match.starttime);
00369 query.bindValue(":TITLE", ltitle.utf8());
00370 query.bindValue(":SUBTITLE", lsubtitle.utf8());
00371 query.bindValue(":DESC", ldesc.utf8());
00372 query.bindValue(":CAT", lcategory.utf8());
00373 query.bindValue(":CATTYPE", lcattype.utf8());
00374 query.bindValue(":STARTTIME", starttime);
00375 query.bindValue(":ENDTIME", endtime);
00376 query.bindValue(":CC", lsubtype & SUB_HARDHEAR ? true : false);
00377 query.bindValue(":SUBTITLED", lsubtype & SUB_NORMAL ? true : false);
00378 query.bindValue(":STEREO", laudio & AUD_STEREO ? true : false);
00379 query.bindValue(":HDTV", lvideo & VID_HDTV ? true : false);
00380 query.bindValue(":SUBTYPE", lsubtype);
00381 query.bindValue(":AUDIOPROP", laudio);
00382 query.bindValue(":VIDEOPROP", lvideo);
00383 query.bindValue(":PARTNO", lpartnumber);
00384 query.bindValue(":PARTTOTAL", lparttotal);
00385 query.bindValue(":SYNDICATENO", lsyndicatedepisodenumber.utf8());
00386 query.bindValue(":AIRDATE", lairdate.isEmpty() ? "0000" : lairdate);
00387 query.bindValue(":ORIGAIRDATE", loriginalairdate);
00388 query.bindValue(":LSOURCE", 1);
00389 query.bindValue(":SERIESID", lseriesId.utf8());
00390 query.bindValue(":PROGRAMID", lprogramId.utf8());
00391 query.bindValue(":PREVSHOWN", lpreviouslyshown);
00392
00393 if (!query.exec())
00394 {
00395 MythContext::DBError("InsertDB", query);
00396 return 0;
00397 }
00398
00399 if (credits)
00400 {
00401 for (uint i = 0; i < credits->size(); i++)
00402 (*credits)[i].InsertDB(query, chanid, starttime);
00403 }
00404
00405 return 1;
00406 }
00407
00408 static bool delete_program(MSqlQuery &query, uint chanid, const QDateTime &st)
00409 {
00410 query.prepare(
00411 "DELETE from program "
00412 "WHERE chanid = :CHANID AND "
00413 " starttime = :STARTTIME");
00414
00415 query.bindValue(":CHANID", chanid);
00416 query.bindValue(":STARTTIME", st);
00417
00418 if (!query.exec())
00419 {
00420 MythContext::DBError("delete_program", query);
00421 return false;
00422 }
00423
00424 query.prepare(
00425 "DELETE from credits "
00426 "WHERE chanid = :CHANID AND "
00427 " starttime = :STARTTIME");
00428
00429 query.bindValue(":CHANID", chanid);
00430 query.bindValue(":STARTTIME", st);
00431
00432 if (!query.exec())
00433 {
00434 MythContext::DBError("delete_credits", query);
00435 return false;
00436 }
00437
00438 return true;
00439 }
00440
00441 static bool change_program(MSqlQuery &query, uint chanid, const QDateTime &st,
00442 const QDateTime &new_st, const QDateTime &new_end)
00443 {
00444 query.prepare(
00445 "UPDATE program "
00446 "SET starttime = :NEWSTART, "
00447 " endtime = :NEWEND "
00448 "WHERE chanid = :CHANID AND "
00449 " starttime = :OLDSTART");
00450
00451 query.bindValue(":CHANID", chanid);
00452 query.bindValue(":OLDSTART", st);
00453 query.bindValue(":NEWSTART", new_st);
00454 query.bindValue(":NEWEND", new_end);
00455
00456 if (!query.exec())
00457 {
00458 MythContext::DBError("change_program", query);
00459 return false;
00460 }
00461
00462 query.prepare(
00463 "UPDATE credits "
00464 "SET starttime = :NEWSTART "
00465 "WHERE chanid = :CHANID AND "
00466 " starttime = :OLDSTART");
00467
00468 query.bindValue(":CHANID", chanid);
00469 query.bindValue(":OLDSTART", st);
00470 query.bindValue(":NEWSTART", new_st);
00471
00472 if (!query.exec())
00473 {
00474 MythContext::DBError("change_credits", query);
00475 return false;
00476 }
00477
00478 return true;
00479 }
00480
00481 bool DBEvent::MoveOutOfTheWayDB(MSqlQuery &query, const DBEvent &prog) const
00482 {
00483 if (prog.starttime >= starttime && prog.endtime <= endtime)
00484 {
00485
00486 return delete_program(query, chanid, prog.starttime);
00487 }
00488 else if (prog.starttime < starttime && prog.endtime > starttime)
00489 {
00490
00491 return change_program(query, chanid, prog.starttime,
00492 prog.starttime, starttime);
00493 }
00494 else if (prog.starttime < endtime && prog.endtime > endtime)
00495 {
00496
00497 return change_program(query, chanid, prog.starttime,
00498 endtime, prog.endtime);
00499 }
00500
00501 return true;
00502 }
00503
00504 uint DBEvent::InsertDB(MSqlQuery &query) const
00505 {
00506 QString lprogramId = AddAuthority(programId, query);
00507 QString lseriesId = AddAuthority(seriesId, query);
00508
00509 query.prepare(
00510 "REPLACE INTO program ("
00511 " chanid, title, subtitle, description, "
00512 " category, category_type, "
00513 " starttime, endtime, "
00514 " closecaptioned, stereo, hdtv, subtitled, "
00515 " subtitletypes, audioprop, videoprop, "
00516 " partnumber, parttotal, "
00517 " syndicatedepisodenumber, "
00518 " airdate, originalairdate,listingsource, "
00519 " seriesid, programid, previouslyshown ) "
00520 "VALUES ("
00521 " :CHANID, :TITLE, :SUBTITLE, :DESCRIPTION, "
00522 " :CATEGORY, :CATTYPE, "
00523 " :STARTTIME, :ENDTIME, "
00524 " :CC, :STEREO, :HDTV, :SUBTITLED, "
00525 " :SUBTYPES, :AUDIOPROP, :VIDEOPROP, "
00526 " :PARTNUMBER, :PARTTOTAL, "
00527 " :SYNDICATENO, "
00528 " :AIRDATE, :ORIGAIRDATE, :LSOURCE, "
00529 " :SERIESID, :PROGRAMID, :PREVSHOWN) ");
00530
00531 QString cattype = myth_category_type_to_string(category_type);
00532
00533 query.bindValue(":CHANID", chanid);
00534 query.bindValue(":TITLE", title.utf8());
00535 query.bindValue(":SUBTITLE", subtitle.utf8());
00536 query.bindValue(":DESCRIPTION", description.utf8());
00537 query.bindValue(":CATEGORY", category.utf8());
00538 query.bindValue(":CATTYPE", cattype.utf8());
00539 query.bindValue(":STARTTIME", starttime);
00540 query.bindValue(":ENDTIME", endtime);
00541 query.bindValue(":CC", subtitleType & SUB_HARDHEAR ? true : false);
00542 query.bindValue(":STEREO", audioProps & AUD_STEREO ? true : false);
00543 query.bindValue(":HDTV", videoProps & VID_HDTV ? true : false);
00544 query.bindValue(":SUBTITLED", subtitleType & SUB_NORMAL ? true : false);
00545 query.bindValue(":SUBTYPES", subtitleType);
00546 query.bindValue(":AUDIOPROP", audioProps);
00547 query.bindValue(":VIDEOPROP", videoProps);
00548 query.bindValue(":PARTNUMBER", partnumber);
00549 query.bindValue(":PARTTOTAL", parttotal);
00550 query.bindValue(":SYNDICATENO", syndicatedepisodenumber.utf8());
00551 query.bindValue(":AIRDATE", airdate.isEmpty() ? "0000" : airdate);
00552 query.bindValue(":ORIGAIRDATE", originalairdate);
00553 query.bindValue(":LSOURCE", 1);
00554 query.bindValue(":SERIESID", lseriesId.utf8());
00555 query.bindValue(":PROGRAMID", lprogramId.utf8());
00556 query.bindValue(":PREVSHOWN", previouslyshown);
00557
00558 if (!query.exec())
00559 {
00560 MythContext::DBError("InsertDB", query);
00561 return 0;
00562 }
00563
00564 if (credits)
00565 {
00566 for (uint i = 0; i < credits->size(); i++)
00567 (*credits)[i].InsertDB(query, chanid, starttime);
00568 }
00569
00570 return 1;
00571 }
00572
00573
00574 QString DBEvent::AddAuthority(const QString& id, MSqlQuery &query) const
00575 {
00576 if (id.length() == 0 || id[0] != '/')
00577 return id;
00578
00579 query.prepare("SELECT default_authority "
00580 "FROM channel "
00581 "WHERE chanid = :CHANID");
00582
00583 query.bindValue(":CHANID", chanid);
00584
00585 if (!query.exec())
00586 {
00587 MythContext::DBError("AddAuthority", query);
00588 return id;
00589 }
00590
00591 if (query.next())
00592 return query.value(0).toString() + id;
00593 else
00594 return id;
00595 }