Go to the documentation of this file.
37 #define LOC QString("RecExt: ")
69 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
70 QString(
"setInfoUrl(%1)").arg(url.url()));
81 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
82 QString(
"setGameUrl(%1)").arg(url.url()));
137 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"past: %1.").arg(past.toString(
Qt::ISODate)));
138 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"eventStart: %1.").arg(eventStart.toString(
Qt::ISODate)));
139 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"future: %1.").arg(future.toString(
Qt::ISODate)));
140 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"result is %1.")
141 .arg(((past < eventStart) && (eventStart < future)) ?
"true" :
"false"));
143 return ((past < eventStart) && (eventStart < future));
155 static QRegularExpression re { R
"((\w+)\[(\d+)\])" };
156 QRegularExpressionMatch match;
158 for (
const QString& step : path)
160 if (step.contains(re, &match))
162 QString name = match.captured(1);
163 int index = match.captured(2).toInt();
164 if (!
object.contains(name) || !
object[name].isArray())
166 LOG(VB_GENERAL, LOG_ERR,
LOC +
167 QString(
"Invalid json at %1 in path %2 (not an array)")
168 .arg(name, path.join(
'/')));
171 QJsonArray array =
object[name].toArray();
172 if ((array.size() < index) || !array[index].isObject())
174 LOG(VB_GENERAL, LOG_ERR,
LOC +
175 QString(
"Invalid json at %1[%2] in path %3 (invalid array)")
176 .arg(name).arg(index).arg(path.join(
'/')));
179 object = array[index].toObject();
183 if (!
object.contains(step) || !
object[step].isObject())
185 LOG(VB_GENERAL, LOG_ERR,
LOC +
186 QString(
"Invalid json at %1 in path %2 (not an object)")
187 .arg(step, path.join(
'/')));
190 object =
object[step].toObject();
208 QString key = path.takeLast();
209 QJsonObject
object = _object;
212 if (
object.isEmpty() || !
object.contains(key) || !
object[key].isDouble())
214 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
215 QString(
"invalid key: %1.").arg(path.join(
'/')));
218 value =
object[key].toDouble();
232 QStringList list = key.split(
'/');
246 QString key = path.takeLast();
247 QJsonObject
object = _object;
250 if (
object.isEmpty() || !
object.contains(key) || !
object[key].isString())
252 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
253 QString(
"invalid key: %1.").arg(path.join(
'/')));
256 value =
object[key].toString();
270 QStringList list = key.split(
'/');
284 QString key = path.takeLast();
285 QJsonObject
object = _object;
288 if (
object.isEmpty() || !
object.contains(key) || !
object[key].isObject())
290 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
291 QString(
"invalid key: %1.").arg(path.join(
'/')));
294 value =
object[key].toObject();
308 QStringList list = key.split(
'/');
320 if (!
object.contains(key) || !
object[key].isArray())
322 value =
object[key].toArray();
348 QString norm = s.normalized(QString::NormalizationForm_D);
349 for (QChar c : qAsConst(norm))
351 switch (c.category())
353 case QChar::Mark_NonSpacing:
354 case QChar::Mark_SpacingCombining:
355 case QChar::Mark_Enclosing:
365 return result.simplified();
394 static const QString
espnInfoUrlFmt {
"http://site.api.espn.com/apis/site/v2/sports/%1/%2/scoreboard"};
395 static const QString
espnGameUrlFmt {
"http://sports.core.api.espn.com/v2/sports/%1/leagues/%2/events/%3/competitions/%3/status"};
399 FINAL, FORFEIT, CANCELLED, POSTPONED, SUSPENDED,
400 FORFEIT_HOME_TEAM, FORFEIT_AWAY_TEAM, ABANDONED, FULL_TIME,
401 PLAY_COMPLETE, OFFICIAL_EVENT_SHORTENED, RETIRED,
402 BYE, ESPNVOID, FINAL_SCORE_AFTER_EXTRA_TIME, FINAL_SCORE_AFTER_GOLDEN_GOAL,
403 FINAL_SCORE_AFTER_PENALTIES, END_EXTRA_TIME, FINAL_SCORE_ABANDONED,
416 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
417 QString(
"Looking for match of %1/%2")
420 QJsonObject json =
m_doc.object();
423 if (!json.contains(
"events") || !json[
"events"].isArray())
425 LOG(VB_GENERAL, LOG_INFO,
LOC +
426 QString(
"malformed json document, step %1.").arg(1));
431 QJsonArray eventArray = json[
"events"].toArray();
432 for (
const auto& eventValue : qAsConst(eventArray))
435 if (!eventValue.isObject())
437 LOG(VB_GENERAL, LOG_INFO,
LOC +
438 QString(
"malformed json document, step %1.").arg(2));
441 QJsonObject
event = eventValue.toObject();
446 QString gameTitle {};
447 QString gameShortTitle {};
453 LOG(VB_GENERAL, LOG_INFO,
LOC +
454 QString(
"malformed json document, step %1.").arg(3));
459 if ((teamNames.size() != 2) || (teamAbbrevs.size() != 2))
461 LOG(VB_GENERAL, LOG_INFO,
LOC +
462 QString(
"malformed json document, step %1.").arg(4));
468 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
469 QString(
"Found %1 at %2 (%3 @ %4). Teams don't match.")
470 .arg(teamNames[0], teamNames[1],
471 teamAbbrevs[0], teamAbbrevs[1]));
477 LOG(VB_GENERAL, LOG_INFO,
LOC +
478 QString(
"Found '%1 vs %2' starting time %3 wrong")
489 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
490 QString(
"Match: %1 at %2 (%3 @ %4), start %5.")
512 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
513 QString(
"Parsing game score for %1/%2")
516 QJsonObject json =
m_doc.object();
529 LOG(VB_GENERAL, LOG_INFO,
LOC +
530 QString(
"malformed json document, step %1.").arg(5));
533 auto stateId =
static_cast<GameStatus>(typeId.toInt());
538 if ((description == detail) || (description ==
"In Progress"))
541 extra = QString (
"%1: %2").arg(description, detail);
543 LOG(VB_GENERAL, LOG_INFO,
LOC +
544 QString(
"%1 at %2 (%3 @ %4), %5.")
561 QString url = _url.url();
566 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
567 QString(
"Using cached document for %1.").arg(url));
573 QString scheme = _url.scheme();
574 if (scheme == QStringLiteral(u
"file"))
576 QFile
file(_url.path(QUrl::FullyDecoded));
577 ok =
file.open(QIODevice::ReadOnly);
579 data =
file.readAll();
581 else if ((scheme == QStringLiteral(u
"http")) ||
582 (scheme == QStringLiteral(u
"https")))
588 LOG(VB_GENERAL, LOG_INFO,
LOC +
589 QString(
"\"%1\" couldn't download %2.")
594 QJsonParseError
error {};
595 QJsonDocument doc = QJsonDocument::fromJson(data, &
error);
596 if (
error.error != QJsonParseError::NoError)
598 LOG(VB_GENERAL, LOG_ERR,
LOC +
599 QString(
"Error parsing %1 at offset %2: %3")
600 .arg(url).arg(
error.offset).arg(
error.errorString()));
604 QJsonObject json = doc.object();
605 if (json.contains(
"code") && json[
"code"].isDouble() &&
606 json.contains(
"detail") && json[
"detail"].isString())
608 LOG(VB_GENERAL, LOG_INFO,
LOC +
609 QString(
"error downloading json document, code %1, detail %2.")
610 .arg(json[
"code"].toInt()).arg(json[
"detail"].
toString()));
627 query.addQueryItem(
"limit",
"500");
629 if (info.
league.endsWith(
"college-basketball"))
630 query.addQueryItem(
"group",
"50");
632 query.addQueryItem(
"dates", dt.toString(
"yyyyMMdd"));
673 LOG(VB_GENERAL, LOG_INFO,
LOC +
674 QString(
"Couldn't load %1").arg(game.
getInfoUrl().url()));
679 LOG(VB_GENERAL, LOG_INFO,
LOC +
680 QString(
"Found game '%1 vs %2' at %3")
692 LOG(VB_GENERAL, LOG_INFO,
LOC +
693 QString(
"Couldn't load %1").arg(game.
getInfoUrl().url()));
698 LOG(VB_GENERAL, LOG_INFO,
LOC +
699 QString(
"Found game '%1 vs %2' at %3")
711 LOG(VB_GENERAL, LOG_INFO,
LOC +
712 QString(
"Couldn't load %1").arg(game.
getInfoUrl().url()));
717 LOG(VB_GENERAL, LOG_INFO,
LOC +
718 QString(
"Found game '%1 vs %2' at %3")
761 QStringList teamNames {
"",
""};
762 QStringList teamAbbrevs {
"",
""};
765 !
getJsonString(gameObject,
"teams/home/team/name", teamNames[0]) ||
766 !
getJsonString(gameObject,
"teams/away/team/name", teamNames[1]) ||
767 !
getJsonString(gameObject,
"teams/home/team/abbreviation", teamAbbrevs[0]) ||
768 !
getJsonString(gameObject,
"teams/away/team/abbreviation", teamAbbrevs[1]))
770 LOG(VB_GENERAL, LOG_INFO,
LOC +
771 QString(
"malformed json document, step %1.").arg(1));
776 bool success = game.
teamsMatch(teamNames, teamAbbrevs);
777 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
778 QString(
"Found: %1 at %2 (%3 @ %4), starting %5. (%6)")
779 .arg(teamNames[0], teamNames[1],
780 teamAbbrevs[0], teamAbbrevs[1], dateStr,
781 success ?
"Success" :
"Teams don't match"));
802 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
803 QString(
"Looking for match of %1/%2")
806 QJsonObject json =
m_doc.object();
810 QJsonArray datesArray;
813 LOG(VB_GENERAL, LOG_INFO,
LOC +
814 QString(
"malformed json document, step %1.").arg(1));
819 for (
const auto& dateValue : qAsConst(datesArray))
821 if (!dateValue.isObject())
823 LOG(VB_GENERAL, LOG_INFO,
LOC +
824 QString(
"malformed json document, step %1.").arg(2));
827 QJsonObject dateObject = dateValue.toObject();
829 QJsonArray gamesArray;
832 LOG(VB_GENERAL, LOG_INFO,
LOC +
833 QString(
"malformed json document, step %1.").arg(3));
838 for (
const auto& gameValue : qAsConst(gamesArray))
840 if (!gameValue.isObject())
842 LOG(VB_GENERAL, LOG_INFO,
LOC +
843 QString(
"malformed json document, step %1.").arg(4));
846 QJsonObject gameObject = gameValue.toObject();
851 LOG(VB_GENERAL, LOG_INFO,
LOC +
852 QString(
"Found '%1 vs %2' starting %3 (%4)")
854 match ?
"match" :
"keep looking"));
871 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
872 QString(
"Parsing game score for %1/%2")
875 QJsonObject json =
m_doc.object();
880 QString abstractGameState;
881 QString detailedGameState;
883 QString inningOrdinal;
884 if (!
getJsonString(json,
"gameData/status/abstractGameState", abstractGameState) ||
885 !
getJsonString(json,
"gameData/status/detailedState", detailedGameState))
887 LOG(VB_GENERAL, LOG_INFO,
LOC +
888 QString(
"malformed json document (%d)").arg(1));
892 if (detailedGameState !=
"Scheduled")
894 if (!
getJsonInt( json,
"liveData/linescore/currentInning", period) ||
895 !
getJsonString(json,
"liveData/linescore/currentInningOrdinal", inningOrdinal) ||
896 !
getJsonString(json,
"liveData/linescore/inningState", inningState))
898 LOG(VB_GENERAL, LOG_INFO,
LOC +
899 QString(
"malformed json document (%d)").arg(2));
904 bool gameOver = (abstractGameState ==
"Final") ||
905 detailedGameState.contains(
"Suspended");
912 else if (detailedGameState ==
"In Progress")
913 extra = QString(
"%1 %2").arg(inningState, inningOrdinal);
915 extra = detailedGameState;
917 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
918 QString(
"%1 at %2 (%3 @ %4), %5.")
935 QString url = _url.url();
940 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
941 QString(
"Using cached document for %1.").arg(url));
947 QString scheme = _url.scheme();
948 if (scheme == QStringLiteral(u
"file"))
950 QFile
file(_url.path(QUrl::FullyDecoded));
951 ok =
file.open(QIODevice::ReadOnly);
953 data =
file.readAll();
955 else if ((scheme == QStringLiteral(u
"http")) ||
956 (scheme == QStringLiteral(u
"https")))
962 LOG(VB_GENERAL, LOG_INFO,
LOC +
963 QString(
"\"%1\" couldn't download %2.")
968 QJsonParseError
error {};
969 QJsonDocument doc = QJsonDocument::fromJson(data, &
error);
970 if (
error.error != QJsonParseError::NoError)
972 LOG(VB_GENERAL, LOG_ERR,
LOC +
973 QString(
"Error parsing %1 at offset %2: %3")
974 .arg(url).arg(
error.offset).arg(
error.errorString()));
994 QDateTime yesterday = dt.addDays(-1);
995 QDateTime tomorrow = dt.addDays(+1);
996 QUrl url {
"https://statsapi.mlb.com/api/v1/schedule"};
998 query.addQueryItem(
"sportId",
"1");
999 query.addQueryItem(
"hydrate",
"team");
1000 query.addQueryItem(
"startDate", QString(
"%1").arg(yesterday.toString(
"yyyy-MM-dd")));
1001 query.addQueryItem(
"endDate", QString(
"%1").arg(tomorrow.toString(
"yyyy-MM-dd")));
1002 url.setQuery(query);
1015 gameUrl.setPath(str);
1016 gameUrl.setQuery(QString());
1041 LOG(VB_GENERAL, LOG_INFO,
LOC +
1042 QString(
"Couldn't load %1").arg(game.
getInfoUrl().url()));
1045 LOG(VB_GENERAL, LOG_INFO,
LOC +
1046 QString(
"Loaded page %1").arg(game.
getInfoUrl().url()));
1080 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1081 QString(
"Recording of %1 at %2 not marked for auto extend.")
1085 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1086 QString(
"Adding %1 at %2 to new recordings list.")
1154 static const QRegularExpression year {R
"(\d{4})"};
1155 QRegularExpressionMatch match;
1156 QString title = _title;
1157 if (title.contains(year, &match))
1160 int matchYear = match.captured().toInt(&ok);
1163 : QDateTime::currentDateTimeUtc().date().year();
1165 if (!ok || ((matchYear != thisYear) && (matchYear != thisYear+1)))
1167 title = title.remove(match.capturedStart(), match.capturedLength());
1169 title = title.simplified();
1170 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1171 QString(
"Looking for %1 title '%2")
1176 "SELECT sl.title, api.provider, api.key1, api.key2" \
1177 " FROM sportslisting sl " \
1178 " INNER JOIN sportsapi api ON sl.api = api.id" \
1179 " WHERE api.provider = :PROVIDER AND :TITLE REGEXP sl.title");
1187 while (query.
next())
1195 infoList.append(info);
1197 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1198 QString(
"Info: '%1' matches '%2' '%3' '%4' '%5'")
1202 return !infoList.isEmpty();
1215 QString& team1, QString& team2)
1217 QString lString = string;
1219 for (
int i = 0; i < std::min(limit,static_cast<int>(parts.size())); i++)
1222 if (words.size() == 2)
1224 team1 = words[0].simplified();
1225 team2 = words[1].simplified();
1241 QString& team1, QString& team2)
1247 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1248 QString(
"can't find team names in subtitle or description '%1'").arg(description));
1277 name = name.simplified();
1279 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"Start: %1").arg(name));
1284 "SELECT sc.name, sc.pattern, sc.nth, sc.replacement" \
1285 " FROM sportscleanup sc " \
1286 " WHERE (provider=0 or provider=:PROVIDER) " \
1287 " AND (key1='all' or key1=:SPORT) " \
1288 " AND (:NAME REGEXP pattern)" \
1289 " ORDER BY sc.weight");
1300 while (query.
next())
1302 QString patternName = query.
value(0).toString();
1303 QString patternStr = query.
value(1).toString();
1304 int patternField = query.
value(2).toInt();
1305 QString replacement = query.
value(3).toString();
1307 QString original = name;
1308 QString tag {
"no match"};
1309 QRegularExpressionMatch match;
1311 if (name.contains(QRegularExpression(patternStr), &match) &&
1314 QString capturedText = match.captured(patternField);
1315 name = name.replace(match.capturedStart(patternField),
1316 match.capturedLength(patternField),
1318 name = name.simplified();
1326 tag = QString(
"matched '%1'").arg(capturedText);
1329 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1330 QString(
"pattern '%1', %2, now '%3'")
1331 .arg(patternName, tag, name));
1345 if (!name2.isEmpty())
1362 LOG(VB_GENERAL, LOG_INFO,
LOC +
1363 QString(
"Recording %1 rule %2 for '%3 @ %4' has finished. Stop recording.")
1367 MythEvent me(QString(
"STOP_RECORDING %1 %2")
1384 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1385 QString(
"Recording %1 rule %2 for '%3 @ %4' scheduled to end soon. Extending recording.")
1395 static const QString ae {
"(Auto Extend)"};
1410 if (!rr->
Save(
true))
1414 LOG(VB_GENERAL, LOG_ERR,
LOC +
1415 QString(
"Recording %1, couldn't save override rule for '%2 @ %3'.")
1422 LOG(VB_GENERAL, LOG_INFO,
LOC +
1423 QString(
"Recording %1, %2 override rule %3 for '%4 @ %5' ending %6 -> %7.")
1425 .arg(exists ?
"updated" :
"created",
1444 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1445 QString(
"Recording %1 rule %2 for '%3 @ %4' ends %5.")
1478 LOG(VB_GENERAL, LOG_INFO,
LOC +
1479 QString(
"Couldn't get recording %1 from scheduler")
1486 LOG(VB_GENERAL, LOG_INFO,
LOC +
1487 QString(
"Invalid status for '%1 : %2', status %3.")
1498 LOG(VB_GENERAL, LOG_INFO,
LOC +
1499 QString(
"Unknown sport '%1' for provider %2")
1508 LOG(VB_GENERAL, LOG_INFO,
LOC +
1509 QString(
"unable to create data source of type %1.")
1522 LOG(VB_GENERAL, LOG_INFO,
LOC +
1523 QString(
"Unable to find '%1 : %2', provider %3")
1534 for (
auto it = infoList.begin(); !found && it != infoList.end(); it++)
1541 source->findInfoUrl(game, info);
1544 LOG(VB_GENERAL, LOG_INFO,
LOC +
1545 QString(
"unable to find data page for recording '%1 : %2'.")
1574 LOG(VB_GENERAL, LOG_INFO,
LOC +
1575 QString(
"Couldn't get recording %1 from scheduler")
1582 auto ri = std::make_unique<RecordingInfo>(*_ri);
1587 LOG(VB_GENERAL, LOG_INFO,
LOC +
1588 QString(
"Invalid status for '%1 : %2', status %3.")
1589 .arg(ri->GetTitle(), ri->GetSubtitle(),
1597 if (
nullptr == source)
1599 LOG(VB_GENERAL, LOG_INFO,
LOC +
1600 QString(
"Couldn't create source of type %1")
1606 auto* page = source->loadPage(game, game.
getGameUrl());
1607 if (
nullptr == page)
1609 LOG(VB_GENERAL, LOG_INFO,
LOC +
1610 QString(
"Couldn't load source %1, teams %2 and %3, url %4")
1617 auto gameState = page->findGameScore(game);
1618 if (!gameState.isValid())
1620 LOG(VB_GENERAL, LOG_INFO,
LOC +
1621 QString(
"Game state for source %1, teams %2 and %3 is invalid")
1627 if (gameState.isFinished())
1634 if (ri->GetScheduledEndTime() <
1659 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1660 QString(
"Nothing left to do. Exiting."));
1664 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1665 QString(
"%1 new recordings, %2 active recordings, %3 overrides.")
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
static void clearDownloadedInfo()
Clear all downloaded info.
QSqlQuery wrapper that fetches a DB connection from the connection pool.
QString m_team1Normalized
void setTeamsNorm(QString team1, QString team2)
bool findGameInfo(ActiveGame &game) override
Parse a previously downloaded data page for a given sport.
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
static constexpr int64_t kLookBackTime
Does the specified time fall within -3/+1 hour from now?
void quit(void)
calls exit(0)
static bool ValidRecordingStatus(RecStatus::Type recstatus)
Does this recording status indicate that the recording is still ongoing.
void setAbbrevs(QStringList abbrevs)
static void clearCache()
Clear the downloaded document cache.
bool parseGameObject(const QJsonObject &gameObject, ActiveGame &game)
MLB ///.
static void error(const char *str,...)
void addNewRecording(int recordedID)
Add an item to the list of new recordings.
RecExtDataPage * newPage(const QJsonDocument &doc) override
bool Save(bool sendSig=true)
void setTeams(QString team1, QString team2)
QUrl makeGameUrl(const ActiveGame &game, const QString &str) override
Create a URL for one specific game in the MLB API that is built from the various known bits of data a...
SportInfo getInfo() const
bool teamsMatch(const QStringList &names, const QStringList &abbrevs) const
Do the supplied team names/abbrevs match this game.
Holds information on a TV Program one might wish to record.
static QMutex s_createLock
Interlock the scheduler thread crating this process, and this process determining whether it should c...
uint m_forcedYearforTesting
Testing data.
uint GetRecordingID(void) const
bool findGameInfo(ActiveGame &game) override
Parse a previously downloaded data page for a given sport.
This class is used as a container for messages.
static void usleep(std::chrono::microseconds time)
QVariant value(int i) const
Internal representation of a recording rule, mirrors the record table.
void setTextState(QString text)
void processActiveRecordings()
Process the currently active sports recordings.
void setGameUrl(QUrl url)
Set the game status information URL.
void setStartTime(const QDateTime &time)
static QJsonObject walkJsonPath(QJsonObject &object, const QStringList &path)
Iterate through a json object and return the specified object.
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
void processNewRecordings()
Process the list of newly started sports recordings.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
QString getAbbrev2() const
void setInfoUrl(QUrl url)
Set the game scheduling information URL.
QDateTime GetRecordingEndTime(void) const
Approximate time the recording should have ended, did end, or is intended to end.
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
static bool parseProgramString(const QString &string, int limit, QString &team1, QString &team2)
QList< ActiveGame > m_activeGames
Currently ongoing games to track.
static QString toString(RecStatus::Type recstatus, uint id)
Converts "recstatus" into a short (unreadable) string.
bool findKnownSport(const QString &_title, AutoExtendType type, SportInfoList &info) const
Retrieve the db record for a sporting event on a specific provider.
void run(void) override
The main execution loop for the Recording Extender.
QUrl findInfoUrl(ActiveGame &game, SportInfo &info) override
Find the right URL for a specific recording.
static constexpr int64_t kLookForwardTime
void setInfo(const SportInfo &info)
static bool getJsonString(const QJsonObject &object, QStringList &path, QString &value)
Retrieve the specified string from a json object.
QList< SportInfo > SportInfoList
QDateTime GetScheduledStartTime(void) const
The scheduled start time of program.
RecStatus::Type GetRecordingStatus(void) const
bool m_running
Whether the RecordingExtender process is running.
QString getStartTimeAsString() const
static QString ruleIdAsString(const RecordingRule *rr)
Quick helper function for printing recording rule numbers.
static bool getJsonInt(const QJsonObject &object, QStringList &path, int &value)
Retrieve the specified integer from a json object.
QUrl findInfoUrl(ActiveGame &game, SportInfo &info) override
Find the right URL for a specific recording.
static const QRegularExpression kSentencePattern
static const QString espnInfoUrlFmt
ESPN ///.
AutoExtendType dataProvider
virtual RecExtDataSource * createDataSource(AutoExtendType type)
Create a RecExtDataSource object for the specified service.
virtual bool findGameInfo(ActiveGame &game)=0
QList< int > m_newRecordings
Newly started recordings to process.
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
QString GetTitle(void) const
QString GetDescription(void) const
static void DBError(const QString &where, const MSqlQuery &query)
static bool getJsonArray(const QJsonObject &object, const QString &key, QJsonArray &value)
Retrieve the specified array from a json object.
QString m_team2Normalized
static bool parseProgramInfo(const QString &subtitle, const QString &description, QString &team1, QString &team2)
Parse a RecordingInfo to find the team names.
QThread * qthread(void)
Returns the thread, this will always return the same pointer no matter how often you restart the thre...
RecExtDataPage * newPage(const QJsonDocument &doc) override
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
bool download(const QString &url, const QString &dest, bool reload=false)
Downloads a URL to a file in blocking mode.
QMutex m_newRecordingsLock
New recordings are added by the scheduler process and removed by this process.
RecExtDataSource * getSource()
QMap< QString, ProgramInfo * > GetRecording(void) const override
void checkDone()
Is there any remaining work? Check for both newly created recording and for active recordings.
static const QList< GameStatus > kFinalStatuses
A list of the ESPN status that mean the game is over.
static QString normalizeString(const QString &s)
Remove all diacritical marks, etc., etc., from a string leaving just the base characters.
static const QString espnGameUrlFmt
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
RecExtDataPage * loadPage(const ActiveGame &game, const QUrl &_url) override
Download the data page for a game, and do some minimal validation.
static QHash< QString, QJsonDocument > s_downloadedJson
A cache of downloaded documents.
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
QUrl makeGameUrl(const ActiveGame &game, const QString &str) override
Create a URL for one specific game in the ESPN API that is built from the various known bits of data ...
int m_recordID
Unique Recording Rule ID.
Scheduler * m_scheduler
Pointer to the scheduler.
static void create(Scheduler *scheduler, RecordingInfo &ri)
Create an instance of the RecordingExtender if necessary, and add this recording to the list of new r...
QDateTime getStartTime() const
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
QString getAbbrev1() const
static void nameCleanup(const SportInfo &info, QString &name1, QString &name2)
Clean up two team names for comparison against the ESPN API.
AutoExtendType m_autoExtend
virtual bool timeIsClose(const QDateTime &eventStart)
Base Classes ///.
int getRecordedId() const
GameState findGameScore(ActiveGame &game) override
Parse the previously downloaded data page for a given game.
static bool getJsonObject(const QJsonObject &object, QStringList &path, QJsonObject &value)
Retrieve a specific object from another json object.
virtual QDateTime getNow()
Get the current time. Overridden by the testing code.
static RecordingExtender * s_singleton
The single instance of a RecordingExtender.
static void finishRecording(const RecordingInfo *ri, RecordingRule *rr, const ActiveGame &game)
Stop the current recording early.
RecordingRule * GetRecordingRule(void)
Returns the "record" field, creating it if necessary.
static constexpr int kExtensionTimeInSec
QUrl makeInfoUrl(const SportInfo &info, const QDateTime &dt) override
Create a URL for the MLB API that is built from the various known bits of data accumulated so far.
void expireOverrides()
Delete the list of the override rules that have been created by this instance of RecordingExtender.
QList< int > m_overrideRules
Recordings that have had an override rule creates.
QUrl makeInfoUrl(const SportInfo &info, const QDateTime &dt) override
Create a URL for the ESPN API that is built from the various known bits of data accumulated so far.
void extendRecording(const RecordingInfo *ri, RecordingRule *rr, const ActiveGame &game)
Extend the current recording by XX minutes.
void dispatch(const MythEvent &event)
GameState findGameScore(ActiveGame &game) override
Parse the previously downloaded data page for a given game.
static constexpr std::chrono::minutes kExtensionTime
~RecordingExtender() override
static const QRegularExpression kVersusPattern
RecExtDataPage * loadPage(const ActiveGame &game, const QUrl &_url) override
Download the data page for a game, and do some minimal validation.
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
QString GetSubtitle(void) const
static void unchangedRecording(const RecordingInfo *ri, RecordingRule *rr, const ActiveGame &game)
Log that this recording hasn't changed.