00001 #include <unistd.h>
00002
00003
00004 #include <sys/types.h>
00005 #include <sys/stat.h>
00006
00007
00008 #include <qmap.h>
00009 #include <qdir.h>
00010 #include <qfile.h>
00011 #include <qstring.h>
00012 #include <qregexp.h>
00013 #include <qfileinfo.h>
00014 #include <qdeepcopy.h>
00015
00016
00017 #include "datadirect.h"
00018 #include "sourceutil.h"
00019 #include "channelutil.h"
00020 #include "frequencytables.h"
00021 #include "mythwidgets.h"
00022 #include "mythcontext.h"
00023 #include "mythdbcon.h"
00024 #include "util.h"
00025
00026 #define SHOW_WGET_OUTPUT 0
00027
00028 #define LOC QString("DataDirect: ")
00029 #define LOC_WARN QString("DataDirect, Warning: ")
00030 #define LOC_ERR QString("DataDirect, Error: ")
00031
00032 static QMutex lineup_type_lock;
00033 static QMap<QString,uint> lineupid_to_srcid;
00034 static QMap<uint,QString> srcid_to_type;
00035
00036 static void set_lineup_type(const QString &lineupid, const QString &type);
00037 static QString get_lineup_type(uint sourceid);
00038 static QString get_setting(QString line, QString key);
00039 static bool has_setting(QString line, QString key);
00040 static QString html_escape(QString str);
00041 static void get_atsc_stuff(QString channum, int sourceid, int freqid,
00042 int &major, int &minor, long long &freq);
00043 static QString process_dd_station(uint sourceid,
00044 QString chan_major, QString chan_minor,
00045 QString &tvformat, uint &freqid);
00046 static uint update_channel_basic(uint sourceid, bool insert,
00047 QString xmltvid, QString callsign,
00048 QString name, uint freqid,
00049 QString chan_major, QString chan_minor);
00050
00051 DataDirectStation::DataDirectStation(void) :
00052 stationid(""), callsign(""),
00053 stationname(""), affiliate(""),
00054 fccchannelnumber("")
00055 {
00056 }
00057
00058 DataDirectLineup::DataDirectLineup() :
00059 lineupid(""), name(""), displayname(""), type(""), postal(""), device("")
00060 {
00061 }
00062
00063 DataDirectLineupMap::DataDirectLineupMap() :
00064 lineupid(""), stationid(""), channel(""), channelMinor("")
00065 {
00066 }
00067
00068 DataDirectSchedule::DataDirectSchedule() :
00069 programid(""), stationid(""),
00070 time(QDateTime()), duration(QTime()),
00071 repeat(false), isnew(false),
00072 stereo(false), subtitled(false),
00073 hdtv(false), closecaptioned(false),
00074 tvrating(""),
00075 partnumber(0), parttotal(0)
00076 {
00077 }
00078
00079 DataDirectProgram::DataDirectProgram() :
00080 programid(""), seriesid(""), title(""),
00081 subtitle(""), description(""), mpaaRating(""),
00082 starRating(""), duration(QTime()), year(""),
00083 showtype(""), colorcode(""), originalAirDate(QDate()),
00084 syndicatedEpisodeNumber("")
00085 {
00086 }
00087
00088 DataDirectProductionCrew::DataDirectProductionCrew() :
00089 programid(""), role(""), givenname(""), surname(""), fullname("")
00090 {
00091 }
00092
00093 DataDirectGenre::DataDirectGenre() :
00094 programid(""), gclass(""), relevance("")
00095 {
00096 }
00097
00098
00099
00100
00101 bool DDStructureParser::startElement(const QString &pnamespaceuri,
00102 const QString &plocalname,
00103 const QString &pqname,
00104 const QXmlAttributes &pxmlatts)
00105 {
00106 (void)pnamespaceuri;
00107 (void)plocalname;
00108
00109 currtagname = pqname;
00110 if (currtagname == "xtvd")
00111 {
00112 QString beg = pxmlatts.value("from");
00113 QDateTime begts = QDateTime::fromString(beg, Qt::ISODate);
00114 parent.SetDDProgramsStartAt(begts);
00115
00116 QString end = pxmlatts.value("to");
00117 QDateTime endts = QDateTime::fromString(end, Qt::ISODate);
00118 parent.SetDDProgramsEndAt(endts);
00119 }
00120 else if (currtagname == "station")
00121 {
00122 curr_station.Reset();
00123 curr_station.stationid = pxmlatts.value("id");
00124 }
00125 else if (currtagname == "lineup")
00126 {
00127 curr_lineup.Reset();
00128 curr_lineup.name = pxmlatts.value("name");
00129 curr_lineup.type = pxmlatts.value("type");
00130 curr_lineup.device = pxmlatts.value("device");
00131 curr_lineup.postal = pxmlatts.value("postalCode");
00132 curr_lineup.lineupid = pxmlatts.value("id");
00133 curr_lineup.displayname = curr_lineup.name + "-" + curr_lineup.type +
00134 "-" + curr_lineup.device + "-" +
00135 curr_lineup.postal + "-" +
00136 curr_lineup.lineupid;
00137
00138 if (curr_lineup.lineupid.isEmpty())
00139 {
00140 curr_lineup.lineupid = curr_lineup.name + curr_lineup.postal +
00141 curr_lineup.device + curr_lineup.type;
00142 }
00143 }
00144 else if (currtagname == "map")
00145 {
00146 int tmpindex;
00147 curr_lineupmap.Reset();
00148 curr_lineupmap.lineupid = curr_lineup.lineupid;
00149 curr_lineupmap.stationid = pxmlatts.value("station");
00150 curr_lineupmap.channel = pxmlatts.value("channel");
00151 tmpindex = pxmlatts.index("channelMinor");
00152 if (tmpindex != -1)
00153 curr_lineupmap.channelMinor = pxmlatts.value(tmpindex);
00154 }
00155 else if (currtagname == "schedule")
00156 {
00157 curr_schedule.Reset();
00158 curr_schedule.programid = pxmlatts.value("program");
00159 curr_schedule.stationid = pxmlatts.value("station");
00160
00161 QString timestr = pxmlatts.value("time");
00162 QDateTime UTCdt = QDateTime::fromString(timestr, Qt::ISODate);
00163
00164 curr_schedule.time = MythUTCToLocal(UTCdt);
00165 QString durstr;
00166
00167 durstr = pxmlatts.value("duration");
00168 curr_schedule.duration = QTime(durstr.mid(2, 2).toInt(),
00169 durstr.mid(5, 2).toInt(), 0, 0);
00170
00171 curr_schedule.repeat = (pxmlatts.value("repeat") == "true");
00172 curr_schedule.isnew = (pxmlatts.value("new") == "true");
00173 curr_schedule.stereo = (pxmlatts.value("stereo") == "true");
00174 curr_schedule.subtitled = (pxmlatts.value("subtitled") == "true");
00175 curr_schedule.hdtv = (pxmlatts.value("hdtv") == "true");
00176 curr_schedule.closecaptioned = (pxmlatts.value("closeCaptioned") ==
00177 "true");
00178 curr_schedule.tvrating = pxmlatts.value("tvRating");
00179 }
00180 else if (currtagname == "part")
00181 {
00182 curr_schedule.partnumber = pxmlatts.value("number").toInt();
00183 curr_schedule.parttotal = pxmlatts.value("total").toInt();
00184 }
00185 else if (currtagname == "program")
00186 {
00187 curr_program.Reset();
00188 curr_program.programid = pxmlatts.value("id");
00189 }
00190 else if (currtagname == "crew")
00191 {
00192 curr_program.Reset();
00193 lastprogramid = pxmlatts.value("program");
00194 }
00195 else if (currtagname == "programGenre")
00196 {
00197 curr_genre.Reset();
00198 lastprogramid = pxmlatts.value("program");
00199 }
00200
00201 return true;
00202 }
00203
00204 bool DDStructureParser::endElement(const QString &pnamespaceuri,
00205 const QString &plocalname,
00206 const QString &pqname)
00207 {
00208 (void)pnamespaceuri;
00209 (void)plocalname;
00210
00211 MSqlQuery query(MSqlQuery::DDCon());
00212
00213 if (pqname == "station")
00214 {
00215 parent.stations[curr_station.stationid] = curr_station;
00216
00217 query.prepare(
00218 "INSERT INTO dd_station "
00219 " ( stationid, callsign, stationname, "
00220 " affiliate, fccchannelnumber) "
00221 "VALUES "
00222 " (:STATIONID, :CALLSIGN, :STATIONNAME, "
00223 " :AFFILIATE, :FCCCHANNUM)");
00224
00225 query.bindValue(":STATIONID", curr_station.stationid);
00226 query.bindValue(":CALLSIGN", curr_station.callsign);
00227 query.bindValue(":STATIONNAME", curr_station.stationname);
00228 query.bindValue(":AFFILIATE", curr_station.affiliate);
00229 query.bindValue(":FCCCHANNUM", curr_station.fccchannelnumber);
00230
00231 if (!query.exec())
00232 MythContext::DBError("Inserting into dd_station", query);
00233 }
00234 else if (pqname == "lineup")
00235 {
00236 set_lineup_type(curr_lineup.lineupid, curr_lineup.type);
00237
00238 parent.lineups.push_back(curr_lineup);
00239
00240 query.prepare(
00241 "INSERT INTO dd_lineup "
00242 " ( lineupid, name, type, device, postal) "
00243 "VALUES "
00244 " (:LINEUPID, :NAME, :TYPE, :DEVICE, :POSTAL)");
00245
00246 query.bindValue(":LINEUPID", curr_lineup.lineupid);
00247 query.bindValue(":NAME", curr_lineup.name);
00248 query.bindValue(":TYPE", curr_lineup.type);
00249 query.bindValue(":DEVICE", curr_lineup.device);
00250 query.bindValue(":POSTAL", curr_lineup.postal);
00251
00252 if (!query.exec())
00253 MythContext::DBError("Inserting into dd_lineup", query);
00254 }
00255 else if (pqname == "map")
00256 {
00257 parent.lineupmaps[curr_lineupmap.lineupid].push_back(curr_lineupmap);
00258
00259 query.prepare(
00260 "INSERT INTO dd_lineupmap "
00261 " ( lineupid, stationid, channel, channelMinor) "
00262 "VALUES "
00263 " (:LINEUPID, :STATIONID, :CHANNEL, :CHANNELMINOR)");
00264
00265 query.bindValue(":LINEUPID", curr_lineupmap.lineupid);
00266 query.bindValue(":STATIONID", curr_lineupmap.stationid);
00267 query.bindValue(":CHANNEL", curr_lineupmap.channel);
00268 query.bindValue(":CHANNELMINOR",curr_lineupmap.channelMinor);
00269 if (!query.exec())
00270 MythContext::DBError("Inserting into dd_lineupmap", query);
00271 }
00272 else if (pqname == "schedule")
00273 {
00274 QDateTime endtime = curr_schedule.time.addSecs(
00275 QTime().secsTo(curr_schedule.duration));
00276
00277 query.prepare(
00278 "INSERT INTO dd_schedule "
00279 " ( programid, stationid, scheduletime, "
00280 " duration, isrepeat, stereo, "
00281 " subtitled, hdtv, closecaptioned, "
00282 " tvrating, partnumber, parttotal, "
00283 " endtime, isnew) "
00284 "VALUES "
00285 " (:PROGRAMID, :STATIONID, :TIME, "
00286 " :DURATION, :ISREPEAT, :STEREO, "
00287 " :SUBTITLED, :HDTV, :CAPTIONED, "
00288 " :TVRATING, :PARTNUMBER, :PARTTOTAL, "
00289 " :ENDTIME, :ISNEW)");
00290
00291 query.bindValue(":PROGRAMID", curr_schedule.programid);
00292 query.bindValue(":STATIONID", curr_schedule.stationid);
00293 query.bindValue(":TIME", curr_schedule.time);
00294 query.bindValue(":DURATION", curr_schedule.duration);
00295 query.bindValue(":ISREPEAT", curr_schedule.repeat);
00296 query.bindValue(":STEREO", curr_schedule.stereo);
00297 query.bindValue(":SUBTITLED", curr_schedule.subtitled);
00298 query.bindValue(":HDTV", curr_schedule.hdtv);
00299 query.bindValue(":CAPTIONED", curr_schedule.closecaptioned);
00300 query.bindValue(":TVRATING", curr_schedule.tvrating);
00301 query.bindValue(":PARTNUMBER", curr_schedule.partnumber);
00302 query.bindValue(":PARTTOTAL", curr_schedule.parttotal);
00303 query.bindValue(":ENDTIME", endtime);
00304 query.bindValue(":ISNEW", curr_schedule.isnew);
00305
00306 if (!query.exec())
00307 MythContext::DBError("Inserting into dd_schedule", query);
00308 }
00309 else if (pqname == "program")
00310 {
00311 float staravg = 0.0;
00312 if (!curr_program.starRating.isEmpty())
00313 {
00314 int fullstarcount = curr_program.starRating.contains("*");
00315 int halfstarcount = curr_program.starRating.contains("+");
00316 staravg = (fullstarcount + (halfstarcount * .5)) / 4;
00317 }
00318
00319 QString cat_type = "";
00320 QString prefix = curr_program.programid.left(2);
00321
00322 if (prefix == "MV")
00323 cat_type = "movie";
00324 else if (prefix == "SP")
00325 cat_type = "sports";
00326 else if (prefix == "EP" ||
00327 curr_program.showtype.contains("series", false))
00328 cat_type = "series";
00329 else
00330 cat_type = "tvshow";
00331
00332 query.prepare(
00333 "INSERT INTO dd_program "
00334 " ( programid, title, subtitle, "
00335 " description, showtype, category_type, "
00336 " mpaarating, starrating, stars, "
00337 " runtime, year, seriesid, "
00338 " colorcode, syndicatedepisodenumber, originalairdate) "
00339 "VALUES "
00340 " (:PROGRAMID, :TITLE, :SUBTITLE, "
00341 " :DESCRIPTION, :SHOWTYPE, :CATTYPE, "
00342 " :MPAARATING, :STARRATING, :STARS, "
00343 " :RUNTIME, :YEAR, :SERIESID, "
00344 " :COLORCODE, :SYNDNUM, :ORIGAIRDATE) ");
00345
00346 query.bindValue(":PROGRAMID", curr_program.programid);
00347 query.bindValue(":TITLE", curr_program.title.utf8());
00348 query.bindValue(":SUBTITLE", curr_program.subtitle.utf8());
00349 query.bindValue(":DESCRIPTION", curr_program.description.utf8());
00350 query.bindValue(":SHOWTYPE", curr_program.showtype.utf8());
00351 query.bindValue(":CATTYPE", cat_type);
00352 query.bindValue(":MPAARATING", curr_program.mpaaRating);
00353 query.bindValue(":STARRATING", curr_program.starRating);
00354 query.bindValue(":STARS", staravg);
00355 query.bindValue(":RUNTIME", curr_program.duration);
00356 query.bindValue(":YEAR", curr_program.year);
00357 query.bindValue(":SERIESID", curr_program.seriesid);
00358 query.bindValue(":COLORCODE", curr_program.colorcode);
00359 query.bindValue(":SYNDNUM", curr_program.syndicatedEpisodeNumber);
00360 query.bindValue(":ORIGAIRDATE", curr_program.originalAirDate);
00361
00362 if (!query.exec())
00363 MythContext::DBError("Inserting into dd_program", query);
00364 }
00365 else if (pqname == "member")
00366 {
00367 QString roleunderlines = curr_productioncrew.role.replace(" ", "_");
00368
00369 QString fullname = curr_productioncrew.givenname;
00370 if (!fullname.isEmpty())
00371 fullname += " ";
00372 fullname += curr_productioncrew.surname;
00373
00374 query.prepare(
00375 "INSERT INTO dd_productioncrew "
00376 " ( programid, role, givenname, surname, fullname) "
00377 "VALUES (:PROGRAMID, :ROLE, :GIVENNAME, :SURNAME, :FULLNAME)");
00378
00379 query.bindValue(":PROGRAMID", lastprogramid);
00380 query.bindValue(":ROLE", roleunderlines.utf8());
00381 query.bindValue(":GIVENNAME", curr_productioncrew.givenname.utf8());
00382 query.bindValue(":SURNAME", curr_productioncrew.surname.utf8());
00383 query.bindValue(":FULLNAME", fullname.utf8());
00384
00385 if (!query.exec())
00386 MythContext::DBError("Inserting into dd_productioncrew", query);
00387
00388 curr_productioncrew.givenname = "";
00389 curr_productioncrew.surname = "";
00390 }
00391 else if (pqname == "genre")
00392 {
00393 query.prepare(
00394 "INSERT INTO dd_genre "
00395 " ( programid, class, relevance) "
00396 "VALUES (:PROGRAMID, :CLASS, :RELEVANCE)");
00397
00398 query.bindValue(":PROGRAMID", lastprogramid);
00399 query.bindValue(":CLASS", curr_genre.gclass.utf8());
00400 query.bindValue(":RELEVANCE", curr_genre.relevance);
00401
00402 if (!query.exec())
00403 MythContext::DBError("Inserting into dd_genre", query);
00404 }
00405
00406 return true;
00407 }
00408
00409 bool DDStructureParser::startDocument()
00410 {
00411 parent.CreateTempTables();
00412 return true;
00413 }
00414
00415 bool DDStructureParser::endDocument()
00416 {
00417 return true;
00418 }
00419
00420 bool DDStructureParser::characters(const QString& pchars)
00421 {
00422
00423 if (pchars.stripWhiteSpace().isEmpty())
00424 return true;
00425
00426 if (currtagname == "message")
00427 {
00428 if (pchars.contains("expire"))
00429 {
00430 QString ExtractDateFromMessage = pchars.right(20);
00431 QDateTime EDFM = QDateTime::fromString(ExtractDateFromMessage,
00432 Qt::ISODate);
00433 QString ExpirationDate = EDFM.toString(Qt::LocalDate);
00434 QString ExpirationDateMessage = "Your subscription expires on " +
00435 ExpirationDate;
00436
00437 QDateTime curTime = QDateTime::currentDateTime();
00438 if (curTime.daysTo(EDFM) <= 5)
00439 {
00440 VERBOSE(VB_IMPORTANT, LOC + QString("WARNING: ") +
00441 ExpirationDateMessage);
00442 }
00443 else
00444 {
00445 VERBOSE(VB_IMPORTANT, LOC + ExpirationDateMessage);
00446 }
00447
00448 MSqlQuery query(MSqlQuery::DDCon());
00449
00450 QString querystr = QString(
00451 "UPDATE settings "
00452 "SET data ='%1' "
00453 "WHERE value='DataDirectMessage'")
00454 .arg(ExpirationDateMessage);
00455
00456 query.prepare(querystr);
00457
00458 if (!query.exec())
00459 {
00460 MythContext::DBError("Updating DataDirect Status Message",
00461 query);
00462 }
00463 }
00464 }
00465 if (currtagname == "callSign")
00466 curr_station.callsign = pchars;
00467 else if (currtagname == "name")
00468 curr_station.stationname = pchars;
00469 else if (currtagname == "affiliate")
00470 curr_station.affiliate = pchars;
00471 else if (currtagname == "fccChannelNumber")
00472 curr_station.fccchannelnumber = pchars;
00473 else if (currtagname == "title")
00474 curr_program.title = pchars;
00475 else if (currtagname == "subtitle")
00476 curr_program.subtitle = pchars;
00477 else if (currtagname == "description")
00478 curr_program.description = pchars;
00479 else if (currtagname == "showType")
00480 curr_program.showtype = pchars;
00481 else if (currtagname == "series")
00482 curr_program.seriesid = pchars;
00483 else if (currtagname == "colorCode")
00484 curr_program.colorcode = pchars;
00485 else if (currtagname == "mpaaRating")
00486 curr_program.mpaaRating = pchars;
00487 else if (currtagname == "starRating")
00488 curr_program.starRating = pchars;
00489 else if (currtagname == "year")
00490 curr_program.year = pchars;
00491 else if (currtagname == "syndicatedEpisodeNumber")
00492 curr_program.syndicatedEpisodeNumber = pchars;
00493 else if (currtagname == "runTime")
00494 {
00495 QString runtimestr = pchars;
00496 QTime runtime = QTime(runtimestr.mid(2,2).toInt(),
00497 runtimestr.mid(5,2).toInt(), 0, 0);
00498 curr_program.duration = runtime;
00499 }
00500 else if (currtagname == "originalAirDate")
00501 {
00502 QDate airdate = QDate::fromString(pchars, Qt::ISODate);
00503 curr_program.originalAirDate = airdate;
00504 }
00505 else if (currtagname == "role")
00506 curr_productioncrew.role = pchars;
00507 else if (currtagname == "givenname")
00508 curr_productioncrew.givenname = pchars;
00509 else if (currtagname == "surname")
00510 curr_productioncrew.surname = pchars;
00511 else if (currtagname == "class")
00512 curr_genre.gclass = pchars;
00513 else if (currtagname == "relevance")
00514 curr_genre.relevance = pchars;
00515
00516 return true;
00517 }
00518
00519 DataDirectProcessor::DataDirectProcessor(uint lp, QString user, QString pass) :
00520 listings_provider(lp % DD_PROVIDER_COUNT),
00521 userid(user), password(pass),
00522 tmpDir("/tmp"), cachedata(false),
00523 inputfilename(""), tmpPostFile(QString::null),
00524 tmpResultFile(QString::null), cookieFile(QString::null),
00525 cookieFileDT()
00526 {
00527 DataDirectURLs urls0(
00528 "Tribune Media Zap2It",
00529 "http://datadirect.webservices.zap2it.com/tvlistings/xtvdService",
00530 "http://labs.zap2it.com",
00531 "/ztvws/ztvws_login/1,1059,TMS01-1,00.html");
00532 DataDirectURLs urls1(
00533 "Schedules Direct",
00534 "http://webservices.schedulesdirect.tmsdatadirect.com"
00535 "/schedulesdirect/tvlistings/xtvdService",
00536 "http://schedulesdirect.org",
00537 "/login/index.php");
00538 providers.push_back(urls0);
00539 providers.push_back(urls1);
00540 }
00541
00542 DataDirectProcessor::~DataDirectProcessor()
00543 {
00544 VERBOSE(VB_GENERAL, LOC + "Deleting temporary files");
00545
00546 if (!tmpPostFile.isEmpty())
00547 unlink(tmpPostFile.ascii());
00548
00549 if (!tmpResultFile.isEmpty())
00550 unlink(tmpResultFile.ascii());
00551
00552 if (!cookieFile.isEmpty())
00553 unlink(cookieFile.ascii());
00554
00555 QDir d(tmpDir, "mythtv_dd_cache_*", QDir::Name,
00556 QDir::Files | QDir::NoSymLinks);
00557
00558 for (uint i = 0; i < d.count(); i++)
00559 {
00560
00561 unlink((tmpDir + "/" + d[i]).ascii());
00562 }
00563
00564 if (tmpDir != "/tmp")
00565 rmdir(tmpDir.ascii());
00566 }
00567
00568 QString DataDirectProcessor::CreateTempDirectory(void)
00569 {
00570 if (tmpDir == "/tmp")
00571 tmpDir = createTempFile("/tmp/mythtv_ddp_XXXXXX", true);
00572 return QDeepCopy<QString>(tmpDir);
00573 }
00574
00575 void DataDirectProcessor::UpdateStationViewTable(QString lineupid)
00576 {
00577 MSqlQuery query(MSqlQuery::DDCon());
00578
00579 if (!query.exec("TRUNCATE TABLE dd_v_station;"))
00580 MythContext::DBError("Truncating temporary table dd_v_station", query);
00581
00582 query.prepare(
00583 "INSERT INTO dd_v_station "
00584 " ( stationid, callsign, stationname, "
00585 " affiliate, fccchannelnumber, channel, "
00586 " channelMinor) "
00587 "SELECT dd_station.stationid, callsign, stationname, "
00588 " affiliate, fccchannelnumber, channel, "
00589 " channelMinor "
00590 "FROM dd_station, dd_lineupmap "
00591 "WHERE ((dd_station.stationid = dd_lineupmap.stationid) AND "
00592 " (dd_lineupmap.lineupid = :LINEUP))");
00593
00594 query.bindValue(":LINEUP", lineupid);
00595
00596 if (!query.exec())
00597 MythContext::DBError("Populating temporary table dd_v_station", query);
00598 }
00599
00600 void DataDirectProcessor::UpdateProgramViewTable(uint sourceid)
00601 {
00602 MSqlQuery query(MSqlQuery::DDCon());
00603
00604 if (!query.exec("TRUNCATE TABLE dd_v_program;"))
00605 MythContext::DBError("Truncating temporary table dd_v_program", query);
00606
00607 QString qstr =
00608 "INSERT INTO dd_v_program "
00609 " ( chanid, starttime, endtime, "
00610 " title, subtitle, description, "
00611 " airdate, stars, previouslyshown, "
00612 " stereo, subtitled, hdtv, "
00613 " closecaptioned, partnumber, parttotal, "
00614 " seriesid, originalairdate, showtype, "
00615 " category_type, colorcode, syndicatedepisodenumber, "
00616 " tvrating, mpaarating, programid ) "
00617 "SELECT chanid, scheduletime, endtime, "
00618 " title, subtitle, description, "
00619 " year, stars, isrepeat, "
00620 " stereo, subtitled, hdtv, "
00621 " closecaptioned, partnumber, parttotal, "
00622 " seriesid, originalairdate, showtype, "
00623 " category_type, colorcode, syndicatedepisodenumber, "
00624 " tvrating, mpaarating, dd_program.programid "
00625 "FROM channel, dd_schedule, dd_program "
00626 "WHERE ((dd_schedule.programid = dd_program.programid) AND "
00627 " (channel.xmltvid = dd_schedule.stationid) AND "
00628 " (channel.sourceid = :SOURCEID))";
00629
00630 query.prepare(qstr);
00631
00632 query.bindValue(":SOURCEID", sourceid);
00633
00634 if (!query.exec())
00635 MythContext::DBError("Populating temporary table dd_v_program", query);
00636
00637 if (!query.exec("ANALYZE TABLE dd_v_program;"))
00638 MythContext::DBError("Analyzing table dd_v_program", query);
00639
00640 if (!query.exec("ANALYZE TABLE dd_productioncrew;"))
00641 MythContext::DBError("Analyzing table dd_productioncrew", query);
00642 }
00643
00644 int DataDirectProcessor::UpdateChannelsSafe(
00645 uint sourceid,
00646 bool insert_channels,
00647 bool filter_new_channels)
00648 {
00649 int new_channels = 0;
00650
00651 if (!SourceUtil::GetConnectionCount(sourceid))
00652 {
00653 VERBOSE(VB_IMPORTANT, LOC +
00654 "Not inserting channels into disconnected source "
00655 <<sourceid<<".");
00656 return -1;
00657 }
00658
00659 if (!SourceUtil::IsProperlyConnected(sourceid, true))
00660 return -1;
00661
00662
00663
00664
00665 MSqlQuery query(MSqlQuery::DDCon());
00666 query.prepare(
00667 "SELECT dd_v_station.stationid, dd_v_station.callsign, "
00668 " dd_v_station.stationname, dd_v_station.fccchannelnumber, "
00669 " dd_v_station.channel, dd_v_station.channelMinor "
00670 "FROM dd_v_station LEFT JOIN channel ON "
00671 " dd_v_station.stationid = channel.xmltvid AND "
00672 " channel.sourceid = :SOURCEID "
00673 "WHERE channel.chanid IS NULL");
00674 query.bindValue(":SOURCEID", sourceid);
00675
00676 if (!query.exec())
00677 {
00678 MythContext::DBError("Selecting new channels", query);
00679 return -1;
00680 }
00681
00682 bool is_encoder = (SourceUtil::IsEncoder(sourceid, true) ||
00683 SourceUtil::IsUnscanable(sourceid));
00684
00685 while (query.next())
00686 {
00687 QString xmltvid = query.value(0).toString();
00688 QString callsign = query.value(1).toString();
00689 QString name = query.value(2).toString();
00690 uint freqid = query.value(3).toUInt();
00691 QString chan_major = query.value(4).toString();
00692 QString chan_minor = query.value(5).toString();
00693
00694 if (filter_new_channels && is_encoder &&
00695 (query.value(5).toUInt() > 0))
00696 {
00697 #if 0
00698 VERBOSE(VB_GENERAL, LOC + QString(
00699 "Not adding channel %1-%2 '%3' (%4),\n\t\t\t"
00700 "looks like a digital channel on an analog source.")
00701 .arg(chan_major).arg(chan_minor).arg(name).arg(callsign));
00702 #endif
00703 continue;
00704 }
00705
00706 uint mods =
00707 update_channel_basic(sourceid, insert_channels && is_encoder,
00708 xmltvid, callsign, name, freqid,
00709 chan_major, chan_minor);
00710
00711 (void) mods;
00712 #if 0
00713 if (!insert_channels && !mods)
00714 {
00715 VERBOSE(VB_GENERAL, LOC + QString("Not adding channel '%1' (%2).")
00716 .arg(name).arg(callsign));
00717 }
00718 #endif
00719 new_channels++;
00720 }
00721
00722 return new_channels;
00723 }
00724
00725 bool DataDirectProcessor::UpdateChannelsUnsafe(
00726 uint sourceid, bool filter_new_channels)
00727 {
00728 if (filter_new_channels &&
00729 !SourceUtil::IsProperlyConnected(sourceid, false))
00730 {
00731 return false;
00732 }
00733
00734 MSqlQuery dd_station_info(MSqlQuery::DDCon());
00735 dd_station_info.prepare(
00736 "SELECT callsign, stationname, stationid,"
00737 " fccchannelnumber, channel, channelMinor "
00738 "FROM dd_v_station");
00739 if (!dd_station_info.exec())
00740 return false;
00741
00742 if (dd_station_info.size() == 0)
00743 return true;
00744
00745 MSqlQuery chan_update_q(MSqlQuery::DDCon());
00746 chan_update_q.prepare(
00747 "UPDATE channel "
00748 "SET callsign = :CALLSIGN, name = :NAME, "
00749 " channum = :CHANNUM, freqid = :FREQID, "
00750 " atsc_major_chan = :MAJORCHAN, "
00751 " atsc_minor_chan = :MINORCHAN "
00752 "WHERE xmltvid = :STATIONID AND sourceid = :SOURCEID");
00753
00754 bool is_encoder = (SourceUtil::IsEncoder(sourceid, true) ||
00755 SourceUtil::IsUnscanable(sourceid));
00756
00757 while (dd_station_info.next())
00758 {
00759 uint freqid = dd_station_info.value(3).toUInt();
00760 QString chan_major = dd_station_info.value(4).toString();
00761 QString chan_minor = dd_station_info.value(5).toString();
00762 QString tvformat = QString::null;
00763 QString channum = process_dd_station(
00764 sourceid, chan_major, chan_minor, tvformat, freqid);
00765
00766 if (filter_new_channels && is_encoder &&
00767 (dd_station_info.value(5).toUInt() > 0))
00768 {
00769 #if 0
00770 VERBOSE(VB_GENERAL, LOC + QString(
00771 "Not adding channel %1-%2 '%3' (%4),\n\t\t\t"
00772 "looks like a digital channel on an analog source.")
00773 .arg(chan_major).arg(chan_minor)
00774 .arg(dd_station_info.value(1).toString())
00775 .arg(dd_station_info.value(0).toString()));
00776 #endif
00777 continue;
00778 }
00779
00780 chan_update_q.bindValue(":CALLSIGN", dd_station_info.value(0));
00781 chan_update_q.bindValue(":NAME", dd_station_info.value(1));
00782 chan_update_q.bindValue(":STATIONID", dd_station_info.value(2));
00783 chan_update_q.bindValue(":CHANNUM", channum);
00784 chan_update_q.bindValue(":SOURCEID", sourceid);
00785 chan_update_q.bindValue(":FREQID", freqid);
00786 chan_update_q.bindValue(":MAJORCHAN", chan_major.toUInt());
00787 chan_update_q.bindValue(":MINORCHAN", chan_minor.toUInt());
00788
00789 if (!chan_update_q.exec())
00790 {
00791 MythContext::DBError("Updating channel table",
00792 chan_update_q.lastQuery());
00793 }
00794 }
00795
00796 return true;
00797 }
00798
00799 void DataDirectProcessor::DataDirectProgramUpdate(void)
00800 {
00801 MSqlQuery query(MSqlQuery::DDCon());
00802
00803
00804 if (!query.exec("INSERT IGNORE INTO program (chanid, starttime, endtime, "
00805 "title, subtitle, description, "
00806 "showtype, category, category_type, "
00807 "airdate, stars, previouslyshown, stereo, subtitled, "
00808 "subtitletypes, videoprop, audioprop, "
00809 "hdtv, closecaptioned, partnumber, parttotal, seriesid, "
00810 "originalairdate, colorcode, syndicatedepisodenumber, "
00811 "programid) "
00812 "SELECT dd_v_program.chanid, "
00813 "DATE_ADD(starttime, INTERVAL channel.tmoffset MINUTE), "
00814 "DATE_ADD(endtime, INTERVAL channel.tmoffset MINUTE), "
00815 "title, subtitle, description, "
00816 "showtype, dd_genre.class, category_type, "
00817 "airdate, stars, previouslyshown, stereo, subtitled, "
00818 "(subtitled << 1 ) | closecaptioned, hdtv, stereo, "
00819 "hdtv, closecaptioned, partnumber, parttotal, seriesid, "
00820 "originalairdate, colorcode, syndicatedepisodenumber, "
00821 "dd_v_program.programid FROM (dd_v_program, channel) "
00822 "LEFT JOIN dd_genre ON ("
00823 "dd_v_program.programid = dd_genre.programid AND "
00824 "dd_genre.relevance = '0') "
00825 "WHERE dd_v_program.chanid = channel.chanid;"))
00826 MythContext::DBError("Inserting into program table", query);
00827
00828
00829
00830
00831 if (!query.exec("INSERT IGNORE INTO programrating (chanid, starttime, "
00832 "system, rating) SELECT dd_v_program.chanid, "
00833 "DATE_ADD(starttime, INTERVAL channel.tmoffset MINUTE), "
00834 " 'MPAA', "
00835 "mpaarating FROM dd_v_program, channel WHERE "
00836 "mpaarating != '' AND dd_v_program.chanid = "
00837 "channel.chanid"))
00838 MythContext::DBError("Inserting into programrating table", query);
00839
00840 if (!query.exec("INSERT IGNORE INTO programrating (chanid, starttime, "
00841 "system, rating) SELECT dd_v_program.chanid, "
00842 "DATE_ADD(starttime, INTERVAL channel.tmoffset MINUTE), "
00843 "'VCHIP', "
00844 "tvrating FROM dd_v_program, channel WHERE tvrating != ''"
00845 " AND dd_v_program.chanid = channel.chanid"))
00846 MythContext::DBError("Inserting into programrating table", query);
00847
00848
00849
00850
00851 if (!query.exec("INSERT IGNORE INTO people (name) SELECT fullname "
00852 "FROM dd_productioncrew;"))
00853 MythContext::DBError("Inserting into people table", query);
00854
00855
00856
00857
00858 if (!query.exec("INSERT IGNORE INTO credits (chanid, starttime, person, "
00859 "role) SELECT dd_v_program.chanid, "
00860 "DATE_ADD(starttime, INTERVAL channel.tmoffset MINUTE), "
00861 "person, role "
00862 "FROM dd_productioncrew, dd_v_program, channel, people "
00863 "WHERE "
00864 "((dd_productioncrew.programid = dd_v_program.programid) "
00865 "AND (dd_productioncrew.fullname = people.name)) "
00866 "AND dd_v_program.chanid = channel.chanid;"))
00867 MythContext::DBError("Inserting into credits table", query);
00868
00869
00870
00871
00872 if (!query.exec("INSERT IGNORE INTO programgenres (chanid, starttime, "
00873 "relevance, genre) SELECT dd_v_program.chanid, "
00874 "DATE_ADD(starttime, INTERVAL channel.tmoffset MINUTE), "
00875 "relevance, class FROM dd_v_program, dd_genre, channel "
00876 "WHERE (dd_v_program.programid = dd_genre.programid) "
00877 "AND dd_v_program.chanid = channel.chanid"))
00878 MythContext::DBError("Inserting into programgenres table",query);
00879
00880
00881 }
00882
00883 void DataDirectProcessor::FixProgramIDs(void)
00884 {
00885 VERBOSE(VB_GENERAL, "DataDirectProcessor::FixProgramIDs() -- begin");
00886
00887 MSqlQuery query(MSqlQuery::DDCon());
00888 query.prepare(
00889 "UPDATE recorded "
00890 "SET programid=CONCAT(SUBSTRING(programid, 1, 2), "
00891 " '00', SUBSTRING(programid, 3)) "
00892 "WHERE length(programid) = 12");
00893
00894 if (!query.exec())
00895 {
00896 MythContext::DBError("Fixing program ids in recorded", query);
00897 return;
00898 }
00899
00900 query.prepare(
00901 "UPDATE oldrecorded "
00902 "SET programid=CONCAT(SUBSTRING(programid, 1, 2), "
00903 " '00', SUBSTRING(programid, 3)) "
00904 "WHERE length(programid) = 12");
00905
00906 if (!query.exec())
00907 {
00908 MythContext::DBError("Fixing program ids in oldrecorded", query);
00909 return;
00910 }
00911
00912 query.prepare(
00913 "UPDATE program "
00914 "SET programid=CONCAT(SUBSTRING(programid, 1, 2), "
00915 " '00', SUBSTRING(programid, 3)) "
00916 "WHERE length(programid) = 12");
00917
00918 if (!query.exec())
00919 {
00920 MythContext::DBError("Fixing program ids in program", query);
00921 return;
00922 }
00923
00924 gContext->SaveSetting("MythFillFixProgramIDsHasRunOnce", "1");
00925
00926 VERBOSE(VB_GENERAL, "DataDirectProcessor::FixProgramIDs() -- end");
00927 }
00928
00929 FILE *DataDirectProcessor::DDPost(
00930 QString ddurl,
00931 QString postFilename, QString inputFile,
00932 QString userid, QString password,
00933 QDateTime pstartDate, QDateTime pendDate,
00934 QString &err_txt, bool &is_pipe)
00935 {
00936 if (!inputFile.isEmpty())
00937 {
00938 err_txt = QString("Unable to open '%1'").arg(inputFile);
00939 is_pipe = false;
00940 return fopen(inputFile.ascii(), "r");
00941 }
00942
00943 QFile postfile(postFilename);
00944 if (!postfile.open(IO_WriteOnly))
00945 {
00946 err_txt = "Unable to open post data output file.";
00947 return NULL;
00948 }
00949
00950 QString startdatestr = pstartDate.toString(Qt::ISODate) + "Z";
00951 QString enddatestr = pendDate.toString(Qt::ISODate) + "Z";
00952 QTextStream poststream(&postfile);
00953 poststream << "<?xml version='1.0' encoding='utf-8'?>\n";
00954 poststream << "<SOAP-ENV:Envelope\n";
00955 poststream <<
00956 "xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/'\n";
00957 poststream << "xmlns:xsd='http://www.w3.org/2001/XMLSchema'\n";
00958 poststream << "xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'\n";
00959 poststream <<
00960 "xmlns:SOAP-ENC='http://schemas.xmlsoap.org/soap/encoding/'>\n";
00961 poststream << "<SOAP-ENV:Body>\n";
00962 poststream << "<ns1:download xmlns:ns1='urn:TMSWebServices'>\n";
00963 poststream << "<startTime xsi:type='xsd:dateTime'>";
00964 poststream << startdatestr << "</startTime>\n";
00965 poststream << "<endTime xsi:type='xsd:dateTime'>";
00966 poststream << enddatestr << "</endTime>\n";
00967 poststream << "</ns1:download>\n";
00968 poststream << "</SOAP-ENV:Body>\n";
00969 poststream << "</SOAP-ENV:Envelope>\n";
00970 postfile.close();
00971
00972
00973 password.replace('\'', "'\\''");
00974 userid.replace('\'', "'\\''");
00975 QString command = QString(
00976 "wget --http-user='%1' --http-passwd='%2' --post-file='%3' "
00977 "--header='Accept-Encoding:gzip' %4 --output-document=- ")
00978 .arg(userid).arg(password).arg(postFilename).arg(ddurl);
00979
00980
00981
00982
00983 command += " | gzip -df";
00984
00985 if (SHOW_WGET_OUTPUT)
00986 VERBOSE(VB_GENERAL, "command: "<<command<<endl);
00987
00988 err_txt = command;
00989
00990 is_pipe = true;
00991 return popen(command.ascii(), "r");
00992 }
00993
00994 bool DataDirectProcessor::GrabNextSuggestedTime(void)
00995 {
00996 VERBOSE(VB_GENERAL, "Grabbing next suggested grabbing time");
00997
00998 QString ddurl = providers[listings_provider].webServiceURL;
00999
01000 QFile postfile(GetPostFilename());
01001 if (!postfile.open(IO_WriteOnly))
01002 {
01003 VERBOSE(VB_IMPORTANT, LOC_ERR + QString("Opening '%1'")
01004 .arg(GetPostFilename()) + ENO);
01005 return false;
01006 }
01007
01008 QTextStream poststream(&postfile);
01009 poststream << "<?xml version='1.0' encoding='utf-8'?>\n";
01010 poststream << "<SOAP-ENV:Envelope\n";
01011 poststream
01012 << "xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/'\n";
01013 poststream << "xmlns:xsd='http://www.w3.org/2001/XMLSchema'\n";
01014 poststream << "xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'\n";
01015 poststream
01016 << "xmlns:SOAP-ENC='http://schemas.xmlsoap.org/soap/encoding/'>\n";
01017 poststream << "<SOAP-ENV:Body>\n";
01018 poststream << "<tms:acknowledge xmlns:tms='urn:TMSWebServices'>\n";
01019 poststream << "</SOAP-ENV:Body>\n";
01020 poststream << "</SOAP-ENV:Envelope>\n";
01021 postfile.close();
01022
01023 QString command = QString("wget --http-user='%1' --http-passwd='%2' "
01024 "--post-file='%3' %4 --output-document='%5'")
01025 .arg(GetUserID().replace('\'', "'\\''"))
01026 .arg(GetPassword().replace('\'', "'\\''")).arg(GetPostFilename())
01027 .arg(ddurl).arg(GetResultFilename());
01028
01029 if (SHOW_WGET_OUTPUT)
01030 VERBOSE(VB_GENERAL, "command: "<<command<<endl);
01031 else
01032 command += " 2> /dev/null ";
01033
01034 myth_system(command.ascii());
01035
01036 QDateTime NextSuggestedTime;
01037 QDateTime BlockedTime;
01038
01039 QFile file(GetResultFilename());
01040
01041 bool GotNextSuggestedTime = false;
01042 bool GotBlockedTime = false;
01043
01044 if (file.open(IO_ReadOnly))
01045 {
01046 QTextStream stream(&file);
01047 QString line;
01048 while (!stream.atEnd())
01049 {
01050 line = stream.readLine();
01051 if (line.contains("<suggestedTime>", false))
01052 {
01053 QString tmpStr = line;
01054 tmpStr.replace(
01055 QRegExp(".*<suggestedTime>([^<]*)</suggestedTime>.*"),
01056 "\\1");
01057
01058 GotNextSuggestedTime = TRUE;
01059 QDateTime UTCdt = QDateTime::fromString(tmpStr, Qt::ISODate);
01060 NextSuggestedTime = MythUTCToLocal(UTCdt);
01061 VERBOSE(VB_GENERAL, LOC + QString("NextSuggestedTime is: ")
01062 + NextSuggestedTime.toString(Qt::ISODate));
01063 }
01064
01065 if (line.contains("<blockedTime>", false))
01066 {
01067 QString tmpStr = line;
01068 tmpStr.replace(
01069 QRegExp(".*<blockedTime>([^<]*)</blockedTime>.*"), "\\1");
01070
01071 GotBlockedTime = TRUE;
01072 QDateTime UTCdt = QDateTime::fromString(tmpStr, Qt::ISODate);
01073 BlockedTime = MythUTCToLocal(UTCdt);
01074 VERBOSE(VB_GENERAL, LOC + QString("BlockedTime is: ")
01075 + BlockedTime.toString(Qt::ISODate));
01076 }
01077 }
01078 file.close();
01079 }
01080
01081 if (GotNextSuggestedTime)
01082 {
01083 int daysToSuggested =
01084 QDateTime::currentDateTime().daysTo(NextSuggestedTime);
01085 int desiredPeriod = gContext->GetNumSetting("MythFillPeriod", 1);
01086
01087
01088 if (daysToSuggested > desiredPeriod)
01089 {
01090 QDateTime newTime =
01091 NextSuggestedTime.addDays(desiredPeriod - daysToSuggested);
01092 VERBOSE(VB_IMPORTANT, LOC + QString(
01093 "Provider suggested running again at %1, "
01094 "but MythFillPeriod is %2. Next run time "
01095 "will be adjusted to be %3.")
01096 .arg(NextSuggestedTime.toString(Qt::ISODate))
01097 .arg(desiredPeriod)
01098 .arg(newTime.toString(Qt::ISODate)));
01099 NextSuggestedTime = newTime;
01100 }
01101
01102 MSqlQuery query(MSqlQuery::DDCon());
01103 QString querystr =
01104 QString("UPDATE settings SET data = '%1' WHERE value = '%2';");
01105
01106 query.prepare(querystr.arg(NextSuggestedTime.toString(Qt::ISODate))
01107 .arg("MythFillSuggestedRunTime"));
01108
01109 if (!query.exec())
01110 MythContext::DBError("Updating DataDirect Suggested RunTime",
01111 query);
01112 }
01113 return GotNextSuggestedTime;
01114 }
01115
01116 static inline bool close_fp(FILE *&fp, bool fp_is_pipe)
01117 {
01118 int err;
01119
01120 if (fp_is_pipe)
01121 err = pclose(fp);
01122 else
01123 err = fclose(fp);
01124
01125 if (err<0)
01126 VERBOSE(VB_IMPORTANT, "Failed to close file." + ENO);
01127
01128 fp = NULL;
01129
01130 return err>=0;
01131 }
01132
01133 bool DataDirectProcessor::GrabData(const QDateTime pstartDate,
01134 const QDateTime pendDate)
01135 {
01136 QString msg = (pstartDate.addSecs(1) == pendDate) ? "channel" : "listing";
01137 VERBOSE(VB_GENERAL, "Grabbing " << msg << " data");
01138
01139 QString err = "";
01140 QString ddurl = providers[listings_provider].webServiceURL;
01141 QString inputfile = inputfilename;
01142 QString cache_dd_data = QString::null;
01143
01144 if (cachedata)
01145 {
01146 cache_dd_data = tmpDir + QString("/mythtv_dd_cache_%1_%2_UTC_%3_to_%4")
01147 .arg(GetListingsProvider())
01148 .arg(GetUserID().ascii())
01149 .arg(pstartDate.toString("yyyyMMddhhmmss"))
01150 .arg(pendDate.toString("yyyyMMddhhmmss"));
01151
01152 if (QFile(cache_dd_data).exists() && inputfilename.isEmpty())
01153 {
01154 VERBOSE(VB_GENERAL, LOC + "Copying from DD cache");
01155 inputfile = cache_dd_data;
01156 }
01157 }
01158
01159 bool fp_is_pipe;
01160 FILE *fp = DDPost(ddurl, GetPostFilename(), inputfile,
01161 GetUserID(), GetPassword(),
01162 pstartDate, pendDate, err, fp_is_pipe);
01163 if (!fp)
01164 {
01165 VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to get data " +
01166 QString("(%1) -- ").arg(err) + ENO);
01167 return false;
01168 }
01169
01170 if (cachedata && (inputfile != cache_dd_data))
01171 {
01172 QFile in, out(cache_dd_data);
01173 bool ok = out.open(IO_WriteOnly);
01174 if (!ok)
01175 {
01176 VERBOSE(VB_IMPORTANT, LOC_WARN +
01177 "Can not open DD cache file in '" +
01178 tmpDir + "' for writing!");
01179 }
01180 else
01181 {
01182 VERBOSE(VB_GENERAL, LOC + "Saving listings to DD cache");
01183 ok = in.open(IO_ReadOnly, fp);
01184 out.close();
01185 }
01186
01187 if (ok)
01188 {
01189 ok = copy(out, in);
01190 in.close();
01191
01192 close_fp(fp, fp_is_pipe);
01193
01194 if (ok)
01195 {
01196 fp = fopen(cache_dd_data.ascii(), "r");
01197 fp_is_pipe = false;
01198 }
01199 else
01200 {
01201 VERBOSE(VB_IMPORTANT,
01202 LOC_ERR + "Failed to save DD cache! "
01203 "redownloading data...");
01204 cachedata = false;
01205 fp = DDPost(ddurl, GetPostFilename(), inputfile,
01206 GetUserID(), GetPassword(),
01207 pstartDate, pendDate, err, fp_is_pipe);
01208 }
01209 }
01210 }
01211
01212 if (!fp)
01213 {
01214 VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to get data 2 " +
01215 QString("(%1) -- ").arg(err) + ENO);
01216 return false;
01217 }
01218
01219 bool ok = true;
01220 QFile f;
01221 if (f.open(IO_ReadOnly, fp))
01222 {
01223 DDStructureParser ddhandler(*this);
01224 QXmlInputSource xmlsource(&f);
01225 QXmlSimpleReader xmlsimplereader;
01226 xmlsimplereader.setContentHandler(&ddhandler);
01227 xmlsimplereader.parse(xmlsource);
01228 f.close();
01229 }
01230 else
01231 {
01232 VERBOSE(VB_GENERAL, LOC_ERR + "Error opening DataDirect file");
01233 ok = false;
01234 }
01235
01236
01237 close_fp(fp, fp_is_pipe);
01238
01239 return ok;
01240 }
01241
01242 bool DataDirectProcessor::GrabLineupsOnly(void)
01243 {
01244 const QDateTime start = QDateTime(QDate::currentDate().addDays(2),
01245 QTime::QTime(23, 59, 0));
01246 const QDateTime end = start.addSecs(1);
01247
01248 return GrabData(start, end);
01249 }
01250
01251 bool DataDirectProcessor::GrabAllData(void)
01252 {
01253 return GrabData(QDateTime(QDate::currentDate()).addDays(-2),
01254 QDateTime(QDate::currentDate()).addDays(15));
01255 }
01256
01257 void DataDirectProcessor::CreateATempTable(const QString &ptablename,
01258 const QString &ptablestruct)
01259 {
01260 MSqlQuery query(MSqlQuery::DDCon());
01261 QString querystr;
01262 querystr = "CREATE TEMPORARY TABLE IF NOT EXISTS " + ptablename + " " +
01263 ptablestruct + ";";
01264
01265 if (!query.exec(querystr))
01266 MythContext::DBError("Creating temporary table", query);
01267
01268 querystr = "TRUNCATE TABLE " + ptablename + ";";
01269
01270 if (!query.exec(querystr))
01271 MythContext::DBError("Truncating temporary table", query);
01272 }
01273
01274 void DataDirectProcessor::CreateTempTables()
01275 {
01276 QMap<QString,QString> dd_tables;
01277
01278 dd_tables["dd_station"] =
01279 "( stationid char(12), callsign char(10), "
01280 " stationname varchar(40), affiliate varchar(25), "
01281 " fccchannelnumber char(15) )";
01282
01283 dd_tables["dd_lineup"] =
01284 "( lineupid char(100), name char(42), "
01285 " type char(20), postal char(6), "
01286 " device char(30) )";
01287
01288 dd_tables["dd_lineupmap"] =
01289 "( lineupid char(100), stationid char(12), "
01290 " channel char(5), channelMinor char(3) )";
01291
01292
01293 dd_tables["dd_v_station"] =
01294 "( stationid char(12), callsign char(10), "
01295 " stationname varchar(40), affiliate varchar(25), "
01296 " fccchannelnumber char(15), channel char(5), "
01297 " channelMinor char(3) )";
01298
01299 dd_tables["dd_schedule"] =
01300 "( programid char(40), stationid char(12), "
01301 " scheduletime datetime, duration time, "
01302 " isrepeat bool, stereo bool, "
01303 " subtitled bool, hdtv bool, "
01304 " closecaptioned bool, tvrating char(5), "
01305 " partnumber int, parttotal int, "
01306 " endtime datetime, isnew bool, "
01307 "INDEX progidx (programid) )";
01308
01309 dd_tables["dd_program"] =
01310 "( programid char(40) NOT NULL, seriesid char(12), "
01311 " title varchar(120), subtitle varchar(150), "
01312 " description text, mpaarating char(5), "
01313 " starrating char(5), runtime time, "
01314 " year char(4), showtype char(30), "
01315 " category_type char(64), colorcode char(20), "
01316 " originalairdate date, syndicatedepisodenumber char(20), "
01317 " stars float unsigned, "
01318 "PRIMARY KEY (programid))";
01319
01320 dd_tables["dd_v_program"] =
01321 "( chanid int unsigned NOT NULL, starttime datetime NOT NULL, "
01322 " endtime datetime, title varchar(128), "
01323 " subtitle varchar(128), description text, "
01324 " category varchar(64), category_type varchar(64), "
01325 " airdate year, stars float unsigned, "
01326 " previouslyshown tinyint, isrepeat bool, "
01327 " stereo bool, subtitled bool, "
01328 " hdtv bool, closecaptioned bool, "
01329 " partnumber int, parttotal int, "
01330 " seriesid char(12), originalairdate date, "
01331 " showtype varchar(30), colorcode varchar(20), "
01332 " syndicatedepisodenumber varchar(20), programid char(40), "
01333 " tvrating char(5), mpaarating char(5), "
01334 "INDEX progidx (programid))";
01335
01336 dd_tables["dd_productioncrew"] =
01337 "( programid char(40), role char(30), "
01338 " givenname char(20), surname char(20), "
01339 " fullname char(41), "
01340 "INDEX progidx (programid), "
01341 "INDEX nameidx (fullname))";
01342
01343 dd_tables["dd_genre"] =
01344 "( programid char(40) NOT NULL, class char(30), "
01345 " relevance char(1), "
01346 "INDEX progidx (programid))";
01347
01348 QMap<QString,QString>::const_iterator it;
01349 for (it = dd_tables.begin(); it != dd_tables.end(); ++it)
01350 CreateATempTable(it.key(), *it);
01351 }
01352
01353 bool DataDirectProcessor::GrabLoginCookiesAndLineups(bool parse_lineups)
01354 {
01355 VERBOSE(VB_GENERAL, "Grabbing login cookies and lineups");
01356
01357 PostList list;
01358 list.push_back(PostItem("username", GetUserID()));
01359 list.push_back(PostItem("password", GetPassword()));
01360 list.push_back(PostItem("action", "Login"));
01361
01362 QString labsURL = providers[listings_provider].webURL;
01363 QString loginPage = providers[listings_provider].loginPage;
01364
01365 bool ok = Post(labsURL + loginPage, list, GetResultFilename(), "",
01366 GetCookieFilename());
01367
01368 bool got_cookie = QFileInfo(GetCookieFilename()).size() > 100;
01369
01370 ok &= got_cookie && (!parse_lineups || ParseLineups(GetResultFilename()));
01371 if (ok)
01372 cookieFileDT = QDateTime::currentDateTime();
01373
01374 return ok;
01375 }
01376
01377 bool DataDirectProcessor::GrabLineupForModify(const QString &lineupid)
01378 {
01379 VERBOSE(VB_GENERAL, QString("Grabbing lineup %1 for modification")
01380 .arg(lineupid));
01381
01382 RawLineupMap::const_iterator it = rawlineups.find(lineupid);
01383 if (it == rawlineups.end())
01384 return false;
01385
01386 PostList list;
01387 list.push_back(PostItem("udl_id", GetRawUDLID(lineupid)));
01388 list.push_back(PostItem("zipcode", GetRawZipCode(lineupid)));
01389 list.push_back(PostItem("lineup_id", lineupid));
01390 list.push_back(PostItem("submit", "Modify"));
01391
01392 QString labsURL = providers[listings_provider].webURL;
01393 bool ok = Post(labsURL + (*it).get_action, list, GetResultFilename(),
01394 GetCookieFilename(), "");
01395
01396 return ok && ParseLineup(lineupid, GetResultFilename());
01397 }
01398
01399 void DataDirectProcessor::SetAll(const QString &lineupid, bool val)
01400 {
01401 VERBOSE(VB_GENERAL, QString("%1 all channels in lineup %2")
01402 .arg((val) ? "Selecting" : "Deselecting").arg(lineupid));
01403
01404 RawLineupMap::iterator lit = rawlineups.find(lineupid);
01405 if (lit == rawlineups.end())
01406 return;
01407
01408 RawLineupChannels &ch = (*lit).channels;
01409 for (RawLineupChannels::iterator it = ch.begin(); it != ch.end(); ++it)
01410 (*it).chk_checked = val;
01411 }
01412
01413 static QString get_cache_filename(const QString &lineupid)
01414 {
01415 return QString("/tmp/.mythtv_cached_lineup_") + lineupid;
01416 }
01417
01418 QDateTime DataDirectProcessor::GetLineupCacheAge(const QString &lineupid) const
01419 {
01420 QDateTime cache_dt(QDate(1971, 1, 1));
01421 QFile lfile(get_cache_filename(lineupid));
01422 if (!lfile.exists())
01423 {
01424 VERBOSE(VB_GENERAL, "GrabLineupCacheAge("<<lineupid<<") failed -- "
01425 <<QString("file '%1' doesn't exist")
01426 .arg(get_cache_filename(lineupid)));
01427 return cache_dt;
01428 }
01429 if (lfile.size() < 8)
01430 {
01431 VERBOSE(VB_IMPORTANT, "GrabLineupCacheAge("<<lineupid<<") failed -- "
01432 <<QString("file '%1' size %2 too small")
01433 .arg(get_cache_filename(lineupid)).arg(lfile.size()));
01434 return cache_dt;
01435 }
01436 if (!lfile.open(IO_ReadOnly))
01437 {
01438 VERBOSE(VB_IMPORTANT, "GrabLineupCacheAge("<<lineupid<<") failed -- "
01439 <<QString("can not open file '%1'")
01440 .arg(get_cache_filename(lineupid)));
01441 return cache_dt;
01442 }
01443
01444 QString tmp;
01445 QTextStream io(&lfile);
01446 io >> tmp;
01447 cache_dt = QDateTime::fromString(tmp, Qt::ISODate);
01448
01449 VERBOSE(VB_GENERAL, "GrabLineupCacheAge("<<lineupid<<") -> "
01450 <<cache_dt.toString(Qt::ISODate));
01451
01452 return cache_dt;
01453 }
01454
01455 bool DataDirectProcessor::GrabLineupsFromCache(const QString &lineupid)
01456 {
01457 QFile lfile(get_cache_filename(lineupid));
01458 if (!lfile.exists() || (lfile.size() < 8) || !lfile.open(IO_ReadOnly))
01459 {
01460 VERBOSE(VB_IMPORTANT, "GrabLineupFromCache("<<lineupid<<") -- failed");
01461 return false;
01462 }
01463
01464 QString tmp;
01465 uint size;
01466 QTextStream io(&lfile);
01467 io >> tmp;
01468 io >> size;
01469
01470 for (uint i = 0; i < 14; i++)
01471 io.readLine();
01472
01473 DDLineupChannels &channels = lineupmaps[lineupid];
01474 channels.clear();
01475
01476 for (uint i = 0; i < size; i++)
01477 {
01478 io.readLine();
01479
01480 DataDirectLineupMap chan;
01481 chan.lineupid = lineupid;
01482 chan.stationid = io.readLine();
01483 chan.channel = io.readLine();
01484 chan.channelMinor = io.readLine();
01485
01486 chan.mapFrom = QDate();
01487 tmp = io.readLine();
01488 if (!tmp.isEmpty())
01489 chan.mapFrom.fromString(tmp, Qt::ISODate);
01490
01491 chan.mapTo = QDate();
01492 tmp = io.readLine();
01493 if (!tmp.isEmpty())
01494 chan.mapTo.fromString(tmp, Qt::ISODate);
01495
01496 channels.push_back(chan);
01497
01498 DDStation station;
01499 station.stationid = chan.stationid;
01500 station.callsign = io.readLine();
01501 station.stationname = io.readLine();
01502 station.affiliate = io.readLine();
01503 station.fccchannelnumber = io.readLine();
01504 tmp = io.readLine();
01505
01506 stations[station.stationid] = station;
01507 }
01508
01509 VERBOSE(VB_GENERAL, "GrabLineupFromCache("<<lineupid<<") -- success");
01510
01511 return true;
01512 }
01513
01514 bool DataDirectProcessor::SaveLineupToCache(const QString &lineupid) const
01515 {
01516 QString fn = get_cache_filename(lineupid);
01517 QFile lfile(fn.ascii());
01518 if (!lfile.open(IO_WriteOnly))
01519 {
01520 VERBOSE(VB_IMPORTANT, "SaveLineupToCache("<<lineupid<<") -- failed");
01521 return false;
01522 }
01523
01524 QTextStream io(&lfile);
01525 io << QDateTime::currentDateTime().toString(Qt::ISODate) << endl;
01526
01527 const DDLineupChannels channels = GetDDLineup(lineupid);
01528 io << channels.size() << endl;
01529
01530 io << endl;
01531 io << "# start record" << endl;
01532 io << "# stationid" << endl;
01533 io << "# channel" << endl;
01534 io << "# channelMinor" << endl;
01535 io << "# mapped from date" << endl;
01536 io << "# mapped to date" << endl;
01537 io << "# callsign" << endl;
01538 io << "# stationname" << endl;
01539 io << "# affiliate" << endl;
01540 io << "# fccchannelnumber" << endl;
01541 io << "# end record" << endl;
01542 io << endl;
01543
01544 DDLineupChannels::const_iterator it;
01545 for (it = channels.begin(); it != channels.end(); ++it)
01546 {
01547 io << "# start record" << endl;
01548 io << (*it).stationid << endl;
01549 io << (*it).channel << endl;
01550 io << (*it).channelMinor << endl;
01551 io << (*it).mapFrom.toString(Qt::ISODate) << endl;
01552 io << (*it).mapTo.toString(Qt::ISODate) << endl;
01553
01554 DDStation station = GetDDStation((*it).stationid);
01555 io << station.callsign << endl;
01556 io << station.stationname << endl;
01557 io << station.affiliate << endl;
01558 io << station.fccchannelnumber << endl;
01559 io << "# end record" << endl;
01560 }
01561
01562 VERBOSE(VB_GENERAL, "SaveLineupToCache("<<lineupid<<") -- success");
01563
01564 chmod(fn.ascii(), 0666);
01565
01566 return true;
01567 }
01568
01569 bool DataDirectProcessor::GrabFullLineup(const QString &lineupid,
01570 bool restore, bool onlyGrabSelected,
01571 uint cache_age_allowed_in_seconds)
01572 {
01573 if (cache_age_allowed_in_seconds)
01574 {
01575 QDateTime exp_time = GetLineupCacheAge(lineupid)
01576 .addSecs(cache_age_allowed_in_seconds);
01577 bool valid = exp_time > QDateTime::currentDateTime();
01578 if (valid && GrabLineupsFromCache(lineupid))
01579 return true;
01580 }
01581
01582 bool ok = GrabLoginCookiesAndLineups();
01583 if (!ok)
01584 return false;
01585
01586 ok = GrabLineupForModify(lineupid);
01587 if (!ok)
01588 return false;
01589
01590 RawLineupMap::iterator lit = rawlineups.find(lineupid);
01591 if (lit == rawlineups.end())
01592 return false;
01593
01594 const RawLineupChannels orig_channels = (*lit).channels;
01595
01596 if (!onlyGrabSelected)
01597 {
01598 SetAll(lineupid, true);
01599 if (!SaveLineupChanges(lineupid))
01600 return false;
01601 }
01602
01603 ok = GrabLineupsOnly();
01604
01605 if (ok)
01606 SaveLineupToCache(lineupid);
01607
01608 (*lit).channels = orig_channels;
01609 if (restore && !onlyGrabSelected)
01610 ok &= SaveLineupChanges(lineupid);
01611
01612 return ok;
01613 }
01614
01615 bool DataDirectProcessor::SaveLineup(const QString &lineupid,
01616 const QMap<QString,bool> &xmltvids)
01617 {
01618 QMap<QString,bool> callsigns;
01619 RawLineupMap::iterator lit = rawlineups.find(lineupid);
01620 if (lit == rawlineups.end())
01621 return false;
01622
01623
01624 if ((!cookieFileDT.isValid() ||
01625 cookieFileDT.addSecs(5*60) < QDateTime::currentDateTime()) &&
01626 !GrabLoginCookiesAndLineups(false))
01627 {
01628 return false;
01629 }
01630
01631
01632 DDLineupMap::const_iterator ddit = lineupmaps.find(lineupid);
01633 DDLineupChannels::const_iterator it;
01634 for (it = (*ddit).begin(); it != (*ddit).end(); ++it)
01635 {
01636 if (xmltvids.find((*it).stationid) != xmltvids.end())
01637 callsigns[GetDDStation((*it).stationid).callsign] = true;
01638 }
01639
01640
01641 RawLineupChannels &ch = (*lit).channels;
01642 RawLineupChannels::iterator cit;
01643 for (cit = ch.begin(); cit != ch.end(); ++cit)
01644 {
01645 bool chk = callsigns.find((*cit).lbl_callsign) != callsigns.end();
01646 (*cit).chk_checked = chk;
01647 }
01648
01649
01650 return SaveLineupChanges(lineupid);
01651 }
01652
01653 bool DataDirectProcessor::SaveLineupChanges(const QString &lineupid)
01654 {
01655 RawLineupMap::const_iterator lit = rawlineups.find(lineupid);
01656 if (lit == rawlineups.end())
01657 return false;
01658
01659 const RawLineup &lineup = *lit;
01660 const RawLineupChannels &ch = lineup.channels;
01661 RawLineupChannels::const_iterator it;
01662
01663 PostList list;
01664 for (it = ch.begin(); it != ch.end(); ++it)
01665 {
01666 if ((*it).chk_checked)
01667 list.push_back(PostItem((*it).chk_name, (*it).chk_value));
01668 }
01669 list.push_back(PostItem("action", "Update"));
01670
01671 VERBOSE(VB_GENERAL, QString("Saving lineup %1 with %2 channels")
01672 .arg(lineupid).arg(list.size() - 1));
01673
01674 QString labsURL = providers[listings_provider].webURL;
01675 return Post(labsURL + lineup.set_action, list, "",
01676 GetCookieFilename(), "");
01677 }
01678
01679 bool DataDirectProcessor::UpdateListings(uint sourceid)
01680 {
01681 MSqlQuery query(MSqlQuery::DDCon());
01682 query.prepare(
01683 "SELECT xmltvid "
01684 "FROM channel "
01685 "WHERE sourceid = :SOURCEID");
01686 query.bindValue(":SOURCEID", sourceid);
01687
01688 if (!query.exec() || !query.isActive())
01689 {
01690 MythContext::DBError("Selecting existing channels", query);
01691 return false;
01692 }
01693
01694 QString a, b, c, lineupid;
01695 if (!SourceUtil::GetListingsLoginData(sourceid, a, b, c, lineupid))
01696 return false;
01697
01698 QMap<QString,bool> xmltvids;
01699 while (query.next())
01700 {
01701 if (!query.value(0).toString().isEmpty())
01702 xmltvids[query.value(0).toString()] = true;
01703 }
01704
01705 VERBOSE(VB_GENERAL, "Saving updated DataDirect listing");
01706 bool ok = SaveLineup(lineupid, xmltvids);
01707
01708 if (!ok)
01709 VERBOSE(VB_GENERAL, "Failed to update DataDirect listings.");
01710
01711 return ok;
01712 }
01713
01714 QDateTime DataDirectProcessor::GetDDProgramsStartAt(bool localtime) const
01715 {
01716 if (localtime)
01717 return MythUTCToLocal(actuallistingsfrom);
01718 return actuallistingsfrom;
01719 }
01720
01721 QDateTime DataDirectProcessor::GetDDProgramsEndAt(bool localtime) const
01722 {
01723 if (localtime)
01724 return MythUTCToLocal(actuallistingsto);
01725 return actuallistingsto;
01726 }
01727
01728 QString DataDirectProcessor::GetRawUDLID(const QString &lineupid) const
01729 {
01730 RawLineupMap::const_iterator it = rawlineups.find(lineupid);
01731 if (it == rawlineups.end())
01732 return QString::null;
01733 return (*it).udl_id;
01734 }
01735
01736 QString DataDirectProcessor::GetRawZipCode(const QString &lineupid) const
01737 {
01738 RawLineupMap::const_iterator it = rawlineups.find(lineupid);
01739 if (it == rawlineups.end())
01740 return QString::null;
01741 return (*it).zipcode;
01742 }
01743
01744 RawLineup DataDirectProcessor::GetRawLineup(const QString &lineupid) const
01745 {
01746 RawLineup tmp;
01747 RawLineupMap::const_iterator it = rawlineups.find(lineupid);
01748 if (it == rawlineups.end())
01749 return tmp;
01750 return (*it);
01751 }
01752
01753 QString DataDirectProcessor::GetPostFilename(void) const
01754 {
01755 if (tmpPostFile.isEmpty())
01756 tmpPostFile = createTempFile(tmpDir + "/mythtv_post_XXXXXX");
01757 return QDeepCopy<QString>(tmpPostFile);
01758 }
01759
01760 QString DataDirectProcessor::GetResultFilename(void) const
01761 {
01762 if (tmpResultFile.isEmpty())
01763 tmpResultFile = createTempFile(tmpDir + "/mythtv_result_XXXXXX");
01764 return QDeepCopy<QString>(tmpResultFile);
01765 }
01766
01767 QString DataDirectProcessor::GetCookieFilename(void) const
01768 {
01769 if (cookieFile.isEmpty())
01770 cookieFile = createTempFile(tmpDir + "/mythtv_cookies_XXXXXX");
01771 return QDeepCopy<QString>(cookieFile);
01772 }
01773
01774 void DataDirectProcessor::SetUserID(const QString &uid)
01775 {
01776 userid = QDeepCopy<QString>(uid);
01777 }
01778
01779 void DataDirectProcessor::SetPassword(const QString &pwd)
01780 {
01781 password = QDeepCopy<QString>(pwd);
01782 }
01783
01784 void DataDirectProcessor::SetInputFile(const QString &file)
01785 {
01786 inputfilename = QDeepCopy<QString>(file);
01787 }
01788
01789 bool DataDirectProcessor::Post(QString url, const PostList &list,
01790 QString documentFile,
01791 QString inCookieFile, QString outCookieFile)
01792 {
01793 QString dfile = QString("'%1' ").arg(documentFile);
01794 QString command = "wget ";
01795
01796 if (!inCookieFile.isEmpty())
01797 command += QString("--load-cookies=%1 ").arg(inCookieFile);
01798
01799 if (!outCookieFile.isEmpty())
01800 {
01801 command += "--keep-session-cookies ";
01802 command += QString("--save-cookies=%1 ").arg(outCookieFile);
01803 }
01804
01805 QString post_data = "";
01806 for (uint i = 0; i < list.size(); i++)
01807 {
01808 post_data += ((i) ? "&" : "") + list[i].key + "=";
01809 post_data += html_escape(list[i].value);
01810 }
01811
01812 if (post_data.length())
01813 command += "--post-data='" + post_data + "' ";
01814
01815 command += url;
01816 command += " ";
01817
01818 command += "--output-document=";
01819 command += (documentFile.isEmpty()) ? "- " : dfile;
01820
01821 if (SHOW_WGET_OUTPUT)
01822 VERBOSE(VB_GENERAL, "command: "<<command<<endl);
01823 else
01824 {
01825 command += (documentFile.isEmpty()) ? "&> " : "2> ";
01826 command += "/dev/null ";
01827 }
01828
01829 myth_system(command.ascii());
01830
01831 if (documentFile.isEmpty())
01832 return true;
01833
01834 QFileInfo fi(documentFile);
01835 return fi.size();
01836 }
01837
01838 bool DataDirectProcessor::ParseLineups(const QString &documentFile)
01839 {
01840 QFile file(documentFile);
01841 if (!file.open(IO_ReadOnly))
01842 {
01843 VERBOSE(VB_IMPORTANT, LOC_ERR +
01844 QString("Failed to open '%1'").arg(documentFile));
01845 return false;
01846 }
01847
01848 QTextStream stream(&file);
01849 bool in_form = false;
01850 QString get_action = QString::null;
01851 QMap<QString,QString> name_value;
01852
01853 rawlineups.clear();
01854
01855 while (!stream.atEnd())
01856 {
01857 QString line = stream.readLine();
01858 QString llow = line.lower();
01859 int frm = llow.find("<form");
01860 if (frm >= 0)
01861 {
01862 in_form = true;
01863 get_action = get_setting(line.mid(frm + 5), "action");
01864 name_value.clear();
01865
01866 }
01867
01868 if (!in_form)
01869 continue;
01870
01871 int inp = llow.find("<input");
01872 if (inp >= 0)
01873 {
01874 QString input_line = line.mid(inp + 6);
01875
01876 QString name = get_setting(input_line, "name");
01877 QString value = get_setting(input_line, "value");
01878
01879
01880 if (!name.isEmpty() && !value.isEmpty())
01881 name_value[name] = value;
01882 }
01883
01884 if (llow.contains("</form>"))
01885 {
01886 in_form = false;
01887 if (!get_action.isEmpty() &&
01888 !name_value["udl_id"].isEmpty() &&
01889 !name_value["zipcode"].isEmpty() &&
01890 !name_value["lineup_id"].isEmpty())
01891 {
01892 RawLineup item(get_action, name_value["udl_id"],
01893 name_value["zipcode"]);
01894
01895 rawlineups[name_value["lineup_id"]] = item;
01896 #if 0
01897 VERBOSE(VB_IMPORTANT, LOC +
01898 QString("<%1> \t--> <%2,%3,%4>")
01899 .arg(name_value["lineup_id"])
01900 .arg(item.udl_id).arg(item.zipcode)
01901 .arg(item.get_action));
01902 #endif
01903 }
01904 }
01905 }
01906 return true;
01907 }
01908
01909 bool DataDirectProcessor::ParseLineup(const QString &lineupid,
01910 const QString &documentFile)
01911 {
01912 QFile file(documentFile);
01913 if (!file.open(IO_ReadOnly))
01914 {
01915 VERBOSE(VB_IMPORTANT, LOC_ERR +
01916 QString("Failed to open '%1'").arg(documentFile));
01917
01918 return false;
01919 }
01920
01921 QTextStream stream(&file);
01922 bool in_form = false;
01923 int in_label = 0;
01924 QMap<QString,QString> settings;
01925
01926 RawLineup &lineup = rawlineups[lineupid];
01927 RawLineupChannels &ch = lineup.channels;
01928
01929 while (!stream.atEnd())
01930 {
01931 QString line = stream.readLine();
01932 QString llow = line.lower();
01933 int frm = llow.find("<form");
01934 if (frm >= 0)
01935 {
01936 in_form = true;
01937 lineup.set_action = get_setting(line.mid(frm + 5), "action");
01938
01939 }
01940
01941 if (!in_form)
01942 continue;
01943
01944 int inp = llow.find("<input");
01945 if (inp >= 0)
01946 {
01947 QString in_line = line.mid(inp + 6);
01948 settings.clear();
01949 settings["chk_name"] = get_setting(in_line, "name");
01950 settings["chk_id"] = get_setting(in_line, "id");
01951 settings["chk_value"] = get_setting(in_line, "value");
01952 settings["chk_checked"] = has_setting(in_line, "checked")?"1":"0";
01953 }
01954
01955 int lbl = llow.find("<label");
01956 if (lbl >= 0)
01957 {
01958 QString lbl_line = line.mid(inp + 6);
01959 QString name = get_setting(lbl_line, "for");
01960 in_label = (name == settings["chk_name"]) ? 1 : 0;
01961 }
01962
01963 if (in_label)
01964 {
01965 int start = (lbl >= 0) ? lbl + 6 : 0;
01966 int beg = llow.find("<td>", start), end = -1;
01967 if (beg)
01968 end = llow.find("</td>", beg + 4);
01969
01970 if (end >= 0)
01971 {
01972 QString key = (in_label == 1) ? "lbl_ch" : "lbl_callsign";
01973 QString val = line.mid(beg + 4, end - beg - 4);
01974 settings[key] = val.replace(" ", "", false);
01975 in_label++;
01976 }
01977 }
01978
01979 in_label = (llow.find("</label") >= 0) ? 0 : in_label;
01980
01981 if (!in_label &&
01982 !settings["chk_name"].isEmpty() &&
01983 !settings["chk_id"].isEmpty() &&
01984 !settings["chk_value"].isEmpty() &&
01985 !settings["chk_checked"].isEmpty() &&
01986 !settings["lbl_ch"].isEmpty() &&
01987 !settings["lbl_callsign"].isEmpty())
01988 {
01989 RawLineupChannel chan(
01990 settings["chk_name"], settings["chk_id"],
01991 settings["chk_value"], settings["chk_checked"] == "1",
01992 settings["lbl_ch"], settings["lbl_callsign"]);
01993
01994 #if 0
01995 VERBOSE(VB_IMPORTANT, LOC +
01996 QString("name: %1 id: %2 value: %3 "
01997 "checked: %4 ch: %5 call: %6")
01998 .arg(settings["chk_name"]).arg(settings["chk_id"])
01999 .arg(settings["chk_value"]).arg(settings["chk_checked"])
02000 .arg(settings["lbl_ch"],4).arg(settings["lbl_callsign"]));
02001 #endif
02002
02003 ch.push_back(chan);
02004 settings.clear();
02005 }
02006
02007 if (llow.contains("</form>"))
02008 {
02009 in_form = false;
02010 }
02011 }
02012 return true;
02013 }
02014
02015 static QString html_escape(QString str)
02016 {
02017 QString new_str = "";
02018 for (uint i = 0; i < str.length(); i++)
02019 {
02020 if (str[i].isLetterOrNumber())
02021 new_str += str[i];
02022 else
02023 new_str += QString("\%%1").arg((int)str[i].latin1(),0,16);
02024 }
02025
02026 return new_str;
02027 }
02028
02029 static QString get_setting(QString line, QString key)
02030 {
02031 QString llow = line.lower();
02032 QString kfind = key + "=\"";
02033 int beg = llow.find(kfind), end = -1;
02034
02035 if (beg >= 0)
02036 {
02037 end = llow.find("\"", beg + kfind.length());
02038 return line.mid(beg + kfind.length(), end - beg - kfind.length());
02039 }
02040
02041 kfind = key + "=";
02042 beg = llow.find(kfind);
02043 if (beg < 0)
02044 return QString::null;
02045
02046 uint i = beg + kfind.length();
02047 while (i < line.length() && !line[i].isSpace() && line[i] != '>')
02048 i++;
02049
02050 if (i < line.length() && (line[i].isSpace() || line[i] == '>'))
02051 return line.mid(beg + kfind.length(), i - beg - kfind.length());
02052
02053 return QString::null;
02054 }
02055
02056 static bool has_setting(QString line, QString key)
02057 {
02058 return (line.lower().find(key) >= 0);
02059 }
02060
02061 static void get_atsc_stuff(QString channum, int sourceid, int freqid,
02062 int &major, int &minor, long long &freq)
02063 {
02064 major = freqid;
02065 minor = 0;
02066
02067 int chansep = channum.find(QRegExp("\\D"));
02068 if (chansep < 0)
02069 return;
02070
02071 major = channum.left(chansep).toInt();
02072 minor = channum.right(channum.length() - (chansep + 1)).toInt();
02073
02074 freq = get_center_frequency("atsc", "vsb8", "us", freqid);
02075 }
02076
02077 static QString process_dd_station(
02078 uint sourceid, QString chan_major, QString chan_minor,
02079 QString &tvformat, uint &freqid)
02080 {
02081 QString channum = chan_major;
02082 bool ok;
02083 uint minor = chan_minor.toUInt(&ok);
02084
02085 tvformat = "Default";
02086
02087 if (minor && ok)
02088 {
02089 tvformat = "atsc";
02090 channum += SourceUtil::GetChannelSeparator(sourceid) + chan_minor;
02091 }
02092 else if (!freqid && (get_lineup_type(sourceid) == "LocalBroadcast"))
02093 freqid = chan_major.toInt();
02094 else
02095 freqid = channum.toInt();
02096
02097 return channum;
02098 }
02099
02100 static uint update_channel_basic(uint sourceid, bool insert,
02101 QString xmltvid, QString callsign,
02102 QString name, uint freqid,
02103 QString chan_major, QString chan_minor)
02104 {
02105 callsign = (callsign.isEmpty()) ? name : callsign;
02106
02107 QString tvformat;
02108 QString channum = process_dd_station(
02109 sourceid, chan_major, chan_minor, tvformat, freqid);
02110
02111
02112 MSqlQuery query(MSqlQuery::DDCon());
02113 query.prepare("SELECT chanid, callsign, name "
02114 "FROM channel "
02115 "WHERE sourceid = :SOURCEID AND "
02116 " ( xmltvid = '0' OR xmltvid = '') AND "
02117 " ( channum = :CHANNUM OR "
02118 " ( freqid = :FREQID AND "
02119 " freqid != '0' AND "
02120 " freqid != '' AND "
02121 " atsc_minor_chan = '0') OR "
02122 " ( atsc_major_chan = :MAJORCHAN AND "
02123 " atsc_minor_chan = :MINORCHAN ) )");
02124 query.bindValue(":SOURCEID", sourceid);
02125 query.bindValue(":CHANNUM", channum);
02126 query.bindValue(":FREQID", freqid);
02127 query.bindValue(":MAJORCHAN", chan_major.toUInt());
02128 query.bindValue(":MINORCHAN", chan_minor.toUInt());
02129
02130 if (!query.exec() || !query.isActive())
02131 {
02132 MythContext::DBError(
02133 "Getting chanid of existing channel", query);
02134 return 0;
02135 }
02136
02137 if (query.next())
02138 {
02139
02140
02141 MSqlQuery chan_update_q(MSqlQuery::DDCon());
02142 chan_update_q.prepare(
02143 "UPDATE channel "
02144 "SET xmltvid = :XMLTVID, name = :NAME, callsign = :CALLSIGN "
02145 "WHERE chanid = :CHANID AND sourceid = :SOURCEID");
02146
02147 uint i = 0;
02148 do
02149 {
02150 uint chanid = query.value(0).toInt();
02151
02152 QString new_callsign = query.value(1).toString();
02153 new_callsign =
02154 (new_callsign.find(ChannelUtil::GetUnknownCallsign()) == 0) ?
02155 callsign : new_callsign;
02156
02157 QString new_name = query.value(2).toString();
02158 new_name = (new_name.isEmpty()) ? name : new_name;
02159 new_name = (new_name.isEmpty()) ? new_callsign : new_name;
02160
02161 chan_update_q.bindValue(":CHANID", chanid);
02162 chan_update_q.bindValue(":NAME", new_name);
02163 chan_update_q.bindValue(":CALLSIGN", new_callsign);
02164 chan_update_q.bindValue(":XMLTVID", xmltvid);
02165 chan_update_q.bindValue(":SOURCEID", sourceid);
02166
02167 #if 0
02168 VERBOSE(VB_GENERAL, LOC +
02169 QString("Updating channel %1: '%2' (%3).")
02170 .arg(chanid).arg(name).arg(callsign));
02171 #endif
02172
02173 if (!chan_update_q.exec() || !chan_update_q.isActive())
02174 {
02175 MythContext::DBError(
02176 "Updating XMLTVID of existing channel", chan_update_q);
02177 continue;
02178 }
02179 i++;
02180 }
02181 while (query.next());
02182
02183 return i;
02184 }
02185
02186 if (!insert)
02187 return 0;
02188
02189
02190 int mplexid = -1, majorC, minorC, chanid = 0;
02191 long long freq = -1;
02192 get_atsc_stuff(channum, sourceid, freqid,
02193 majorC, minorC, freq);
02194
02195 if (minorC > 0 && freq >= 0)
02196 mplexid = ChannelUtil::CreateMultiplex(sourceid, "atsc", freq, "8vsb");
02197
02198 if ((mplexid > 0) || (minorC == 0))
02199 chanid = ChannelUtil::CreateChanID(sourceid, channum);
02200
02201 VERBOSE(VB_GENERAL, LOC + QString("Adding channel %1 '%2' (%3).")
02202 .arg(channum).arg(name).arg(callsign));
02203
02204 if (chanid > 0)
02205 {
02206 QString icon = "";
02207 int serviceid = 0;
02208 bool oag = false;
02209 bool hidden = false;
02210 bool hidden_in_guide = false;
02211 QString freq_id= QString::number(freqid);
02212
02213 ChannelUtil::CreateChannel(
02214 mplexid, sourceid, chanid,
02215 callsign, name, channum,
02216 serviceid, majorC, minorC,
02217 oag, hidden, hidden_in_guide,
02218 freq_id, icon, tvformat,
02219 xmltvid);
02220 }
02221
02222 return 1;
02223 }
02224
02225 static void set_lineup_type(const QString &lineupid, const QString &type)
02226 {
02227 QMutexLocker locker(&lineup_type_lock);
02228 if (lineupid_to_srcid[lineupid])
02229 return;
02230
02231
02232 uint srcid = 0;
02233 MSqlQuery query(MSqlQuery::InitCon());
02234 query.prepare(
02235 "SELECT sourceid "
02236 "FROM videosource "
02237 "WHERE lineupid = :LINEUPID");
02238 query.bindValue(":LINEUPID", lineupid);
02239
02240 if (!query.exec() || !query.isActive())
02241 MythContext::DBError("end_element", query);
02242 else if (query.next())
02243 srcid = query.value(0).toUInt();
02244
02245 if (srcid)
02246 {
02247 lineupid_to_srcid[QDeepCopy<QString>(lineupid)] = srcid;
02248
02249
02250 srcid_to_type[srcid] = QDeepCopy<QString>(type);
02251
02252 VERBOSE(VB_GENERAL, "sourceid "<<srcid<<" has lineup type: "<<type);
02253 }
02254 }
02255
02256 static QString get_lineup_type(uint sourceid)
02257 {
02258 QMutexLocker locker(&lineup_type_lock);
02259 return QDeepCopy<QString>(srcid_to_type[sourceid]);
02260 }
02261
02262