Go to the documentation of this file.
27 #include <QJsonObject>
43 #define LOC QString("RecExt: ")
75 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
76 QString(
"setInfoUrl(%1)").arg(url.url()));
87 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
88 QString(
"setGameUrl(%1)").arg(url.url()));
143 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"past: %1.").arg(past.toString(
Qt::ISODate)));
144 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"eventStart: %1.").arg(eventStart.toString(
Qt::ISODate)));
145 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"future: %1.").arg(future.toString(
Qt::ISODate)));
146 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"result is %1.")
147 .arg(((past < eventStart) && (eventStart < future)) ?
"true" :
"false"));
149 return ((past < eventStart) && (eventStart < future));
161 static QRegularExpression re { R
"((\w+)\[(\d+)\])" };
162 QRegularExpressionMatch match;
164 for (
const QString& step : path)
166 if (step.contains(re, &match))
168 QString name = match.captured(1);
169 int index = match.captured(2).toInt();
170 if (!
object.contains(name) || !
object[name].isArray())
172 LOG(VB_GENERAL, LOG_ERR,
LOC +
173 QString(
"Invalid json at %1 in path %2 (not an array)")
174 .arg(name, path.join(
'/')));
177 QJsonArray array =
object[name].toArray();
178 if ((array.size() < index) || !array[index].isObject())
180 LOG(VB_GENERAL, LOG_ERR,
LOC +
181 QString(
"Invalid json at %1[%2] in path %3 (invalid array)")
182 .arg(name).arg(index).arg(path.join(
'/')));
185 object = array[index].toObject();
189 if (!
object.contains(step) || !
object[step].isObject())
191 LOG(VB_GENERAL, LOG_ERR,
LOC +
192 QString(
"Invalid json at %1 in path %2 (not an object)")
193 .arg(step, path.join(
'/')));
196 object =
object[step].toObject();
214 QString key = path.takeLast();
215 QJsonObject
object = _object;
218 if (
object.isEmpty() || !
object.contains(key) || !
object[key].isDouble())
220 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
221 QString(
"invalid key: %1.").arg(path.join(
'/')));
224 value =
object[key].toDouble();
238 QStringList list = key.split(
'/');
252 QString key = path.takeLast();
253 QJsonObject
object = _object;
256 if (
object.isEmpty() || !
object.contains(key) || !
object[key].isString())
258 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
259 QString(
"invalid key: %1.").arg(path.join(
'/')));
262 value =
object[key].toString();
276 QStringList list = key.split(
'/');
290 QString key = path.takeLast();
291 QJsonObject
object = _object;
294 if (
object.isEmpty() || !
object.contains(key) || !
object[key].isObject())
296 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
297 QString(
"invalid key: %1.").arg(path.join(
'/')));
300 value =
object[key].toObject();
314 QStringList list = key.split(
'/');
326 if (!
object.contains(key) || !
object[key].isArray())
328 value =
object[key].toArray();
354 QString norm = s.normalized(QString::NormalizationForm_D);
355 for (QChar c : std::as_const(norm))
357 switch (c.category())
359 case QChar::Mark_NonSpacing:
360 case QChar::Mark_SpacingCombining:
361 case QChar::Mark_Enclosing:
371 return result.simplified();
400 static const QString
espnInfoUrlFmt {
"http://site.api.espn.com/apis/site/v2/sports/%1/%2/scoreboard"};
401 static const QString
espnGameUrlFmt {
"http://sports.core.api.espn.com/v2/sports/%1/leagues/%2/events/%3/competitions/%3/status"};
405 FINAL, FORFEIT, CANCELLED, POSTPONED, SUSPENDED,
406 FORFEIT_HOME_TEAM, FORFEIT_AWAY_TEAM, ABANDONED, FULL_TIME,
407 PLAY_COMPLETE, OFFICIAL_EVENT_SHORTENED, RETIRED,
408 BYE, ESPNVOID, FINAL_SCORE_AFTER_EXTRA_TIME, FINAL_SCORE_AFTER_GOLDEN_GOAL,
409 FINAL_SCORE_AFTER_PENALTIES, END_EXTRA_TIME, FINAL_SCORE_ABANDONED,
422 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
423 QString(
"Looking for match of %1/%2")
426 QJsonObject json =
m_doc.object();
429 if (!json.contains(
"events") || !json[
"events"].isArray())
431 LOG(VB_GENERAL, LOG_INFO,
LOC +
432 QString(
"malformed json document, step %1.").arg(1));
437 QJsonArray eventArray = json[
"events"].toArray();
438 for (
const auto& eventValue : std::as_const(eventArray))
441 if (!eventValue.isObject())
443 LOG(VB_GENERAL, LOG_INFO,
LOC +
444 QString(
"malformed json document, step %1.").arg(2));
447 QJsonObject
event = eventValue.toObject();
452 QString gameTitle {};
453 QString gameShortTitle {};
459 LOG(VB_GENERAL, LOG_INFO,
LOC +
460 QString(
"malformed json document, step %1.").arg(3));
465 if ((teamNames.size() != 2) || (teamAbbrevs.size() != 2))
467 LOG(VB_GENERAL, LOG_INFO,
LOC +
468 QString(
"malformed json document, step %1.").arg(4));
474 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
475 QString(
"Found %1 at %2 (%3 @ %4). Teams don't match.")
476 .arg(teamNames[0], teamNames[1],
477 teamAbbrevs[0], teamAbbrevs[1]));
483 LOG(VB_GENERAL, LOG_INFO,
LOC +
484 QString(
"Found '%1 vs %2' starting time %3 wrong")
495 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
496 QString(
"Match: %1 at %2 (%3 @ %4), start %5.")
518 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
519 QString(
"Parsing game score for %1/%2")
522 QJsonObject json =
m_doc.object();
535 LOG(VB_GENERAL, LOG_INFO,
LOC +
536 QString(
"malformed json document, step %1.").arg(5));
539 auto stateId =
static_cast<GameStatus>(typeId.toInt());
544 if ((description == detail) || (description ==
"In Progress"))
547 extra = QString (
"%1: %2").arg(description, detail);
549 LOG(VB_GENERAL, LOG_INFO,
LOC +
550 QString(
"%1 at %2 (%3 @ %4), %5.")
567 QString url = _url.url();
572 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
573 QString(
"Using cached document for %1.").arg(url));
579 QString scheme = _url.scheme();
580 if (scheme == QStringLiteral(u
"file"))
582 QFile
file(_url.path(QUrl::FullyDecoded));
583 ok =
file.open(QIODevice::ReadOnly);
585 data =
file.readAll();
587 else if ((scheme == QStringLiteral(u
"http")) ||
588 (scheme == QStringLiteral(u
"https")))
594 LOG(VB_GENERAL, LOG_INFO,
LOC +
595 QString(
"\"%1\" couldn't download %2.")
600 QJsonParseError
error {};
601 QJsonDocument doc = QJsonDocument::fromJson(data, &
error);
602 if (
error.error != QJsonParseError::NoError)
604 LOG(VB_GENERAL, LOG_ERR,
LOC +
605 QString(
"Error parsing %1 at offset %2: %3")
606 .arg(url).arg(
error.offset).arg(
error.errorString()));
610 QJsonObject json = doc.object();
611 if (json.contains(
"code") && json[
"code"].isDouble() &&
612 json.contains(
"detail") && json[
"detail"].isString())
614 LOG(VB_GENERAL, LOG_INFO,
LOC +
615 QString(
"error downloading json document, code %1, detail %2.")
616 .arg(json[
"code"].toInt()).arg(json[
"detail"].
toString()));
633 query.addQueryItem(
"limit",
"500");
635 if (
info.league.endsWith(
"college-basketball"))
636 query.addQueryItem(
"group",
"50");
638 query.addQueryItem(
"dates", dt.toString(
"yyyyMMdd"));
679 LOG(VB_GENERAL, LOG_INFO,
LOC +
680 QString(
"Couldn't load %1").arg(game.
getInfoUrl().url()));
685 LOG(VB_GENERAL, LOG_INFO,
LOC +
686 QString(
"Found game '%1 vs %2' at %3")
698 LOG(VB_GENERAL, LOG_INFO,
LOC +
699 QString(
"Couldn't load %1").arg(game.
getInfoUrl().url()));
704 LOG(VB_GENERAL, LOG_INFO,
LOC +
705 QString(
"Found game '%1 vs %2' at %3")
717 LOG(VB_GENERAL, LOG_INFO,
LOC +
718 QString(
"Couldn't load %1").arg(game.
getInfoUrl().url()));
723 LOG(VB_GENERAL, LOG_INFO,
LOC +
724 QString(
"Found game '%1 vs %2' at %3")
767 QStringList teamNames {
"",
""};
768 QStringList teamAbbrevs {
"",
""};
771 !
getJsonString(gameObject,
"teams/home/team/name", teamNames[0]) ||
772 !
getJsonString(gameObject,
"teams/away/team/name", teamNames[1]) ||
773 !
getJsonString(gameObject,
"teams/home/team/abbreviation", teamAbbrevs[0]) ||
774 !
getJsonString(gameObject,
"teams/away/team/abbreviation", teamAbbrevs[1]))
776 LOG(VB_GENERAL, LOG_INFO,
LOC +
777 QString(
"malformed json document, step %1.").arg(1));
782 bool success = game.
teamsMatch(teamNames, teamAbbrevs);
783 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
784 QString(
"Found: %1 at %2 (%3 @ %4), starting %5. (%6)")
785 .arg(teamNames[0], teamNames[1],
786 teamAbbrevs[0], teamAbbrevs[1], dateStr,
787 success ?
"Success" :
"Teams don't match"));
808 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
809 QString(
"Looking for match of %1/%2")
812 QJsonObject json =
m_doc.object();
816 QJsonArray datesArray;
819 LOG(VB_GENERAL, LOG_INFO,
LOC +
820 QString(
"malformed json document, step %1.").arg(1));
825 for (
const auto& dateValue : std::as_const(datesArray))
827 if (!dateValue.isObject())
829 LOG(VB_GENERAL, LOG_INFO,
LOC +
830 QString(
"malformed json document, step %1.").arg(2));
833 QJsonObject dateObject = dateValue.toObject();
835 QJsonArray gamesArray;
838 LOG(VB_GENERAL, LOG_INFO,
LOC +
839 QString(
"malformed json document, step %1.").arg(3));
844 for (
const auto& gameValue : std::as_const(gamesArray))
846 if (!gameValue.isObject())
848 LOG(VB_GENERAL, LOG_INFO,
LOC +
849 QString(
"malformed json document, step %1.").arg(4));
852 QJsonObject gameObject = gameValue.toObject();
857 LOG(VB_GENERAL, LOG_INFO,
LOC +
858 QString(
"Found '%1 vs %2' starting %3 (%4)")
860 match ?
"match" :
"keep looking"));
877 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
878 QString(
"Parsing game score for %1/%2")
881 QJsonObject json =
m_doc.object();
886 QString abstractGameState;
887 QString detailedGameState;
889 QString inningOrdinal;
890 if (!
getJsonString(json,
"gameData/status/abstractGameState", abstractGameState) ||
891 !
getJsonString(json,
"gameData/status/detailedState", detailedGameState))
893 LOG(VB_GENERAL, LOG_INFO,
LOC +
894 QString(
"malformed json document (%d)").arg(1));
898 if (detailedGameState !=
"Scheduled")
900 if (!
getJsonInt( json,
"liveData/linescore/currentInning", period) ||
901 !
getJsonString(json,
"liveData/linescore/currentInningOrdinal", inningOrdinal) ||
902 !
getJsonString(json,
"liveData/linescore/inningState", inningState))
904 LOG(VB_GENERAL, LOG_INFO,
LOC +
905 QString(
"malformed json document (%d)").arg(2));
910 bool gameOver = (abstractGameState ==
"Final") ||
911 detailedGameState.contains(
"Suspended");
918 else if (detailedGameState ==
"In Progress")
919 extra = QString(
"%1 %2").arg(inningState, inningOrdinal);
921 extra = detailedGameState;
923 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
924 QString(
"%1 at %2 (%3 @ %4), %5.")
941 QString url = _url.url();
946 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
947 QString(
"Using cached document for %1.").arg(url));
953 QString scheme = _url.scheme();
954 if (scheme == QStringLiteral(u
"file"))
956 QFile
file(_url.path(QUrl::FullyDecoded));
957 ok =
file.open(QIODevice::ReadOnly);
959 data =
file.readAll();
961 else if ((scheme == QStringLiteral(u
"http")) ||
962 (scheme == QStringLiteral(u
"https")))
968 LOG(VB_GENERAL, LOG_INFO,
LOC +
969 QString(
"\"%1\" couldn't download %2.")
974 QJsonParseError
error {};
975 QJsonDocument doc = QJsonDocument::fromJson(data, &
error);
976 if (
error.error != QJsonParseError::NoError)
978 LOG(VB_GENERAL, LOG_ERR,
LOC +
979 QString(
"Error parsing %1 at offset %2: %3")
980 .arg(url).arg(
error.offset).arg(
error.errorString()));
999 QDateTime yesterday = dt.addDays(-1);
1000 QDateTime tomorrow = dt.addDays(+1);
1001 QUrl url {
"https://statsapi.mlb.com/api/v1/schedule"};
1003 query.addQueryItem(
"sportId",
"1");
1004 query.addQueryItem(
"hydrate",
"team");
1005 query.addQueryItem(
"startDate", QString(
"%1").arg(yesterday.toString(
"yyyy-MM-dd")));
1006 query.addQueryItem(
"endDate", QString(
"%1").arg(tomorrow.toString(
"yyyy-MM-dd")));
1007 url.setQuery(query);
1020 gameUrl.setPath(str);
1021 gameUrl.setQuery(QString());
1046 LOG(VB_GENERAL, LOG_INFO,
LOC +
1047 QString(
"Couldn't load %1").arg(game.
getInfoUrl().url()));
1050 LOG(VB_GENERAL, LOG_INFO,
LOC +
1051 QString(
"Loaded page %1").arg(game.
getInfoUrl().url()));
1085 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1086 QString(
"Recording of %1 at %2 not marked for auto extend.")
1090 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1091 QString(
"Adding %1 at %2 to new recordings list.")
1159 static const QRegularExpression year {R
"(\d{4})"};
1160 QRegularExpressionMatch match;
1161 QString title = _title;
1162 if (title.contains(year, &match))
1165 int matchYear = match.captured().toInt(&ok);
1168 : QDateTime::currentDateTimeUtc().date().year();
1170 if (!ok || ((matchYear != thisYear) && (matchYear != thisYear+1)))
1172 title = title.remove(match.capturedStart(), match.capturedLength());
1174 title = title.simplified();
1175 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1176 QString(
"Looking for %1 title '%2")
1181 "SELECT sl.title, api.provider, api.key1, api.key2" \
1182 " FROM sportslisting sl " \
1183 " INNER JOIN sportsapi api ON sl.api = api.id" \
1184 " WHERE api.provider = :PROVIDER AND :TITLE REGEXP sl.title");
1192 while (query.
next())
1196 info.showTitle = query.
value(0).toString();
1199 info.league = query.
value(3).toString();
1200 infoList.append(
info);
1202 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1203 QString(
"Info: '%1' matches '%2' '%3' '%4' '%5'")
1207 return !infoList.isEmpty();
1220 #
if QT_VERSION < QT_VERSION_CHECK(6,0,0)
1225 QString& team1, QString& team2)
1227 QString lString = string;
1229 for (
int i = 0; i < std::min(limit,parts.size()); i++)
1232 if (words.size() == 2)
1234 team1 = words[0].simplified();
1235 team2 = words[1].simplified();
1251 QString& team1, QString& team2)
1257 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1258 QString(
"can't find team names in subtitle or description '%1'").arg(description));
1287 name = name.simplified();
1289 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"Start: %1").arg(name));
1294 "SELECT sc.name, sc.pattern, sc.nth, sc.replacement" \
1295 " FROM sportscleanup sc " \
1296 " WHERE (provider=0 or provider=:PROVIDER) " \
1297 " AND (key1='all' or key1=:SPORT) " \
1298 " AND (:NAME REGEXP pattern)" \
1299 " ORDER BY sc.weight");
1300 query.
bindValue(
":PROVIDER",
static_cast<uint8_t
>(
info.dataProvider));
1310 while (query.
next())
1312 QString patternName = query.
value(0).toString();
1313 QString patternStr = query.
value(1).toString();
1314 int patternField = query.
value(2).toInt();
1315 QString replacement = query.
value(3).toString();
1317 QString original = name;
1318 QString tag {
"no match"};
1319 QRegularExpressionMatch match;
1321 if (name.contains(QRegularExpression(patternStr), &match) &&
1324 QString capturedText = match.captured(patternField);
1325 name = name.replace(match.capturedStart(patternField),
1326 match.capturedLength(patternField),
1328 name = name.simplified();
1336 tag = QString(
"matched '%1'").arg(capturedText);
1339 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1340 QString(
"pattern '%1', %2, now '%3'")
1341 .arg(patternName, tag, name));
1355 if (!name2.isEmpty())
1372 LOG(VB_GENERAL, LOG_INFO,
LOC +
1373 QString(
"Recording %1 rule %2 for '%3 @ %4' has finished. Stop recording.")
1377 MythEvent me(QString(
"STOP_RECORDING %1 %2")
1394 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1395 QString(
"Recording %1 rule %2 for '%3 @ %4' scheduled to end soon. Extending recording.")
1405 static const QString ae {
"(Auto Extend)"};
1420 if (!rr->
Save(
true))
1424 LOG(VB_GENERAL, LOG_ERR,
LOC +
1425 QString(
"Recording %1, couldn't save override rule for '%2 @ %3'.")
1432 LOG(VB_GENERAL, LOG_INFO,
LOC +
1433 QString(
"Recording %1, %2 override rule %3 for '%4 @ %5' ending %6 -> %7.")
1435 .arg(
exists ?
"updated" :
"created",
1454 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1455 QString(
"Recording %1 rule %2 for '%3 @ %4' ends %5.")
1488 LOG(VB_GENERAL, LOG_INFO,
LOC +
1489 QString(
"Couldn't get recording %1 from scheduler")
1496 LOG(VB_GENERAL, LOG_INFO,
LOC +
1497 QString(
"Invalid status for '%1 : %2', status %3.")
1508 LOG(VB_GENERAL, LOG_INFO,
LOC +
1509 QString(
"Unknown sport '%1' for provider %2")
1518 LOG(VB_GENERAL, LOG_INFO,
LOC +
1519 QString(
"unable to create data source of type %1.")
1532 LOG(VB_GENERAL, LOG_INFO,
LOC +
1533 QString(
"Unable to find '%1 : %2', provider %3")
1544 for (
auto it = infoList.begin(); !found && it != infoList.end(); it++)
1551 source->findInfoUrl(game,
info);
1554 LOG(VB_GENERAL, LOG_INFO,
LOC +
1555 QString(
"unable to find data page for recording '%1 : %2'.")
1584 LOG(VB_GENERAL, LOG_INFO,
LOC +
1585 QString(
"Couldn't get recording %1 from scheduler")
1592 auto ri = std::make_unique<RecordingInfo>(*_ri);
1597 LOG(VB_GENERAL, LOG_INFO,
LOC +
1598 QString(
"Invalid status for '%1 : %2', status %3.")
1599 .arg(ri->GetTitle(), ri->GetSubtitle(),
1607 if (
nullptr == source)
1609 LOG(VB_GENERAL, LOG_INFO,
LOC +
1610 QString(
"Couldn't create source of type %1")
1616 auto* page = source->loadPage(game, game.
getGameUrl());
1617 if (
nullptr == page)
1619 LOG(VB_GENERAL, LOG_INFO,
LOC +
1620 QString(
"Couldn't load source %1, teams %2 and %3, url %4")
1627 auto gameState = page->findGameScore(game);
1628 if (!gameState.isValid())
1630 LOG(VB_GENERAL, LOG_INFO,
LOC +
1631 QString(
"Game state for source %1, teams %2 and %3 is invalid")
1637 if (gameState.isFinished())
1644 if (ri->GetScheduledEndTime() <
1669 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1670 QString(
"Nothing left to do. Exiting."));
1674 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1675 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.
static bool parseProgramString(const QString &string, qsizetype limit, QString &team1, QString &team2)
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.
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 ///.
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.