44#define LOC QString("RecExt: ")
76 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
77 QString(
"setInfoUrl(%1)").arg(url.url()));
88 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
89 QString(
"setGameUrl(%1)").arg(url.url()));
144 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"past: %1.").arg(past.toString(
Qt::ISODate)));
145 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"eventStart: %1.").arg(eventStart.toString(
Qt::ISODate)));
146 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"future: %1.").arg(future.toString(
Qt::ISODate)));
147 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"result is %1.")
148 .arg(((past < eventStart) && (eventStart < future)) ?
"true" :
"false"));
150 return ((past < eventStart) && (eventStart < future));
162 static QRegularExpression re { R
"((\w+)\[(\d+)\])" };
163 QRegularExpressionMatch match;
165 for (
const QString& step : path)
167 if (step.contains(re, &match))
169 QString name = match.captured(1);
170 int index = match.captured(2).toInt();
171 if (!
object.contains(name) || !
object[name].isArray())
173 LOG(VB_GENERAL, LOG_ERR,
LOC +
174 QString(
"Invalid json at %1 in path %2 (not an array)")
175 .arg(name, path.join(
'/')));
178 QJsonArray array =
object[name].toArray();
179 if ((array.size() < index) || !array[index].isObject())
181 LOG(VB_GENERAL, LOG_ERR,
LOC +
182 QString(
"Invalid json at %1[%2] in path %3 (invalid array)")
183 .arg(name).arg(index).arg(path.join(
'/')));
186 object = array[index].toObject();
190 if (!
object.contains(step) || !
object[step].isObject())
192 LOG(VB_GENERAL, LOG_ERR,
LOC +
193 QString(
"Invalid json at %1 in path %2 (not an object)")
194 .arg(step, path.join(
'/')));
197 object =
object[step].toObject();
215 QString key = path.takeLast();
216 QJsonObject
object = _object;
219 if (
object.isEmpty() || !
object.contains(key) || !
object[key].isDouble())
221 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
222 QString(
"invalid key: %1.").arg(path.join(
'/')));
225 value =
object[key].toDouble();
239 QStringList list = key.split(
'/');
253 QString key = path.takeLast();
254 QJsonObject
object = _object;
257 if (
object.isEmpty() || !
object.contains(key) || !
object[key].isString())
259 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
260 QString(
"invalid key: %1.").arg(path.join(
'/')));
263 value =
object[key].toString();
277 QStringList list = key.split(
'/');
291 QString key = path.takeLast();
292 QJsonObject
object = _object;
295 if (
object.isEmpty() || !
object.contains(key) || !
object[key].isObject())
297 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
298 QString(
"invalid key: %1.").arg(path.join(
'/')));
301 value =
object[key].toObject();
315 QStringList list = key.split(
'/');
327 if (!
object.contains(key) || !
object[key].isArray())
329 value =
object[key].toArray();
355 QString norm = s.normalized(QString::NormalizationForm_D);
356 for (QChar c : std::as_const(norm))
358 switch (c.category())
360 case QChar::Mark_NonSpacing:
361 case QChar::Mark_SpacingCombining:
362 case QChar::Mark_Enclosing:
372 return result.simplified();
401static const QString
espnInfoUrlFmt {
"http://site.api.espn.com/apis/site/v2/sports/%1/%2/scoreboard"};
402static const QString
espnGameUrlFmt {
"http://sports.core.api.espn.com/v2/sports/%1/leagues/%2/events/%3/competitions/%3/status"};
406 FINAL, FORFEIT, CANCELLED, POSTPONED, SUSPENDED,
407 FORFEIT_HOME_TEAM, FORFEIT_AWAY_TEAM, ABANDONED, FULL_TIME,
408 PLAY_COMPLETE, OFFICIAL_EVENT_SHORTENED, RETIRED,
409 BYE, ESPNVOID, FINAL_SCORE_AFTER_EXTRA_TIME, FINAL_SCORE_AFTER_GOLDEN_GOAL,
410 FINAL_SCORE_AFTER_PENALTIES, END_EXTRA_TIME, FINAL_SCORE_ABANDONED,
423 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
424 QString(
"Looking for match of %1/%2")
427 QJsonObject json =
m_doc.object();
430 if (!json.contains(
"events") || !json[
"events"].isArray())
432 LOG(VB_GENERAL, LOG_INFO,
LOC +
433 QString(
"malformed json document, step %1.").arg(1));
438 QJsonArray eventArray = json[
"events"].toArray();
439 for (
const auto& eventValue : std::as_const(eventArray))
442 if (!eventValue.isObject())
444 LOG(VB_GENERAL, LOG_INFO,
LOC +
445 QString(
"malformed json document, step %1.").arg(2));
448 QJsonObject
event = eventValue.toObject();
453 QString gameTitle {};
454 QString gameShortTitle {};
460 LOG(VB_GENERAL, LOG_INFO,
LOC +
461 QString(
"malformed json document, step %1.").arg(3));
466 if ((teamNames.size() != 2) || (teamAbbrevs.size() != 2))
468 LOG(VB_GENERAL, LOG_INFO,
LOC +
469 QString(
"malformed json document, step %1.").arg(4));
475 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
476 QString(
"Found %1 at %2 (%3 @ %4). Teams don't match.")
477 .arg(teamNames[0], teamNames[1],
478 teamAbbrevs[0], teamAbbrevs[1]));
484 LOG(VB_GENERAL, LOG_INFO,
LOC +
485 QString(
"Found '%1 vs %2' starting time %3 wrong")
496 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
497 QString(
"Match: %1 at %2 (%3 @ %4), start %5.")
519 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
520 QString(
"Parsing game score for %1/%2")
523 QJsonObject json =
m_doc.object();
536 LOG(VB_GENERAL, LOG_INFO,
LOC +
537 QString(
"malformed json document, step %1.").arg(5));
540 auto stateId =
static_cast<GameStatus>(typeId.toInt());
545 if ((description == detail) || (description ==
"In Progress"))
548 extra = QString (
"%1: %2").arg(description, detail);
550 LOG(VB_GENERAL, LOG_INFO,
LOC +
551 QString(
"%1 at %2 (%3 @ %4), %5.")
568 QString url = _url.url();
573 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
574 QString(
"Using cached document for %1.").arg(url));
580 QString scheme = _url.scheme();
581 if (scheme == QStringLiteral(u
"file"))
583 QFile
file(_url.path(QUrl::FullyDecoded));
584 ok =
file.open(QIODevice::ReadOnly);
586 data =
file.readAll();
588 else if ((scheme == QStringLiteral(u
"http")) ||
589 (scheme == QStringLiteral(u
"https")))
595 LOG(VB_GENERAL, LOG_INFO,
LOC +
596 QString(
"\"%1\" couldn't download %2.")
601 QJsonParseError
error {};
602 QJsonDocument doc = QJsonDocument::fromJson(data, &
error);
603 if (
error.error != QJsonParseError::NoError)
605 LOG(VB_GENERAL, LOG_ERR,
LOC +
606 QString(
"Error parsing %1 at offset %2: %3")
607 .arg(url).arg(
error.offset).arg(
error.errorString()));
611 QJsonObject json = doc.object();
612 if (json.contains(
"code") && json[
"code"].isDouble() &&
613 json.contains(
"detail") && json[
"detail"].isString())
615 LOG(VB_GENERAL, LOG_INFO,
LOC +
616 QString(
"error downloading json document, code %1, detail %2.")
617 .arg(json[
"code"].toInt()).arg(json[
"detail"].
toString()));
634 query.addQueryItem(
"limit",
"500");
636 if (
info.league.endsWith(
"college-basketball"))
637 query.addQueryItem(
"group",
"50");
639 query.addQueryItem(
"dates", dt.toString(
"yyyyMMdd"));
680 LOG(VB_GENERAL, LOG_INFO,
LOC +
681 QString(
"Couldn't load %1").arg(game.
getInfoUrl().url()));
686 LOG(VB_GENERAL, LOG_INFO,
LOC +
687 QString(
"Found game '%1 vs %2' at %3")
699 LOG(VB_GENERAL, LOG_INFO,
LOC +
700 QString(
"Couldn't load %1").arg(game.
getInfoUrl().url()));
705 LOG(VB_GENERAL, LOG_INFO,
LOC +
706 QString(
"Found game '%1 vs %2' at %3")
718 LOG(VB_GENERAL, LOG_INFO,
LOC +
719 QString(
"Couldn't load %1").arg(game.
getInfoUrl().url()));
724 LOG(VB_GENERAL, LOG_INFO,
LOC +
725 QString(
"Found game '%1 vs %2' at %3")
768 QStringList teamNames {
"",
""};
769 QStringList teamAbbrevs {
"",
""};
772 !
getJsonString(gameObject,
"teams/home/team/name", teamNames[0]) ||
773 !
getJsonString(gameObject,
"teams/away/team/name", teamNames[1]) ||
774 !
getJsonString(gameObject,
"teams/home/team/abbreviation", teamAbbrevs[0]) ||
775 !
getJsonString(gameObject,
"teams/away/team/abbreviation", teamAbbrevs[1]))
777 LOG(VB_GENERAL, LOG_INFO,
LOC +
778 QString(
"malformed json document, step %1.").arg(1));
783 bool success = game.
teamsMatch(teamNames, teamAbbrevs);
784 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
785 QString(
"Found: %1 at %2 (%3 @ %4), starting %5. (%6)")
786 .arg(teamNames[0], teamNames[1],
787 teamAbbrevs[0], teamAbbrevs[1], dateStr,
788 success ?
"Success" :
"Teams don't match"));
809 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
810 QString(
"Looking for match of %1/%2")
813 QJsonObject json =
m_doc.object();
817 QJsonArray datesArray;
820 LOG(VB_GENERAL, LOG_INFO,
LOC +
821 QString(
"malformed json document, step %1.").arg(1));
826 for (
const auto& dateValue : std::as_const(datesArray))
828 if (!dateValue.isObject())
830 LOG(VB_GENERAL, LOG_INFO,
LOC +
831 QString(
"malformed json document, step %1.").arg(2));
834 QJsonObject dateObject = dateValue.toObject();
836 QJsonArray gamesArray;
839 LOG(VB_GENERAL, LOG_INFO,
LOC +
840 QString(
"malformed json document, step %1.").arg(3));
845 for (
const auto& gameValue : std::as_const(gamesArray))
847 if (!gameValue.isObject())
849 LOG(VB_GENERAL, LOG_INFO,
LOC +
850 QString(
"malformed json document, step %1.").arg(4));
853 QJsonObject gameObject = gameValue.toObject();
858 LOG(VB_GENERAL, LOG_INFO,
LOC +
859 QString(
"Found '%1 vs %2' starting %3 (%4)")
861 match ?
"match" :
"keep looking"));
878 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
879 QString(
"Parsing game score for %1/%2")
882 QJsonObject json =
m_doc.object();
887 QString abstractGameState;
888 QString detailedGameState;
890 QString inningOrdinal;
891 if (!
getJsonString(json,
"gameData/status/abstractGameState", abstractGameState) ||
892 !
getJsonString(json,
"gameData/status/detailedState", detailedGameState))
894 LOG(VB_GENERAL, LOG_INFO,
LOC +
895 QString(
"malformed json document (%d)").arg(1));
899 if (detailedGameState !=
"Scheduled")
901 if (!
getJsonInt( json,
"liveData/linescore/currentInning", period) ||
902 !
getJsonString(json,
"liveData/linescore/currentInningOrdinal", inningOrdinal) ||
903 !
getJsonString(json,
"liveData/linescore/inningState", inningState))
905 LOG(VB_GENERAL, LOG_INFO,
LOC +
906 QString(
"malformed json document (%d)").arg(2));
911 bool gameOver = (abstractGameState ==
"Final") ||
912 detailedGameState.contains(
"Suspended");
919 else if (detailedGameState ==
"In Progress")
920 extra = QString(
"%1 %2").arg(inningState, inningOrdinal);
922 extra = detailedGameState;
924 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
925 QString(
"%1 at %2 (%3 @ %4), %5.")
942 QString url = _url.url();
947 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
948 QString(
"Using cached document for %1.").arg(url));
954 QString scheme = _url.scheme();
955 if (scheme == QStringLiteral(u
"file"))
957 QFile
file(_url.path(QUrl::FullyDecoded));
958 ok =
file.open(QIODevice::ReadOnly);
960 data =
file.readAll();
962 else if ((scheme == QStringLiteral(u
"http")) ||
963 (scheme == QStringLiteral(u
"https")))
969 LOG(VB_GENERAL, LOG_INFO,
LOC +
970 QString(
"\"%1\" couldn't download %2.")
975 QJsonParseError
error {};
976 QJsonDocument doc = QJsonDocument::fromJson(data, &
error);
977 if (
error.error != QJsonParseError::NoError)
979 LOG(VB_GENERAL, LOG_ERR,
LOC +
980 QString(
"Error parsing %1 at offset %2: %3")
981 .arg(url).arg(
error.offset).arg(
error.errorString()));
1000 QDateTime yesterday = dt.addDays(-1);
1001 QDateTime tomorrow = dt.addDays(+1);
1002 QUrl url {
"https://statsapi.mlb.com/api/v1/schedule"};
1004 query.addQueryItem(
"sportId",
"1");
1005 query.addQueryItem(
"hydrate",
"team");
1006 query.addQueryItem(
"startDate", QString(
"%1").arg(yesterday.toString(
"yyyy-MM-dd")));
1007 query.addQueryItem(
"endDate", QString(
"%1").arg(tomorrow.toString(
"yyyy-MM-dd")));
1008 url.setQuery(query);
1021 gameUrl.setPath(str);
1022 gameUrl.setQuery(QString());
1047 LOG(VB_GENERAL, LOG_INFO,
LOC +
1048 QString(
"Couldn't load %1").arg(game.
getInfoUrl().url()));
1051 LOG(VB_GENERAL, LOG_INFO,
LOC +
1052 QString(
"Loaded page %1").arg(game.
getInfoUrl().url()));
1086 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1087 QString(
"Recording of %1 at %2 not marked for auto extend.")
1091 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1092 QString(
"Adding %1 at %2 to new recordings list.")
1160 static const QRegularExpression year {R
"(\d{4})"};
1161 QRegularExpressionMatch match;
1162 QString title = _title;
1163 if (title.contains(year, &match))
1166 int matchYear = match.captured().toInt(&ok);
1169 : QDateTime::currentDateTimeUtc().date().year();
1171 if (!ok || ((matchYear != thisYear) && (matchYear != thisYear+1)))
1173 title = title.remove(match.capturedStart(), match.capturedLength());
1175 title = title.simplified();
1176 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1177 QString(
"Looking for %1 title '%2")
1182 "SELECT sl.title, api.provider, api.key1, api.key2" \
1183 " FROM sportslisting sl " \
1184 " INNER JOIN sportsapi api ON sl.api = api.id" \
1185 " WHERE api.provider = :PROVIDER AND :TITLE REGEXP sl.title");
1193 while (query.
next())
1197 info.showTitle = query.
value(0).toString();
1200 info.league = query.
value(3).toString();
1201 infoList.append(
info);
1203 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1204 QString(
"Info: '%1' matches '%2' '%3' '%4' '%5'")
1208 return !infoList.isEmpty();
1221#
if QT_VERSION < QT_VERSION_CHECK(6,0,0)
1226 QString& team1, QString& team2)
1228 QString lString = string;
1230 for (
int i = 0; i < std::min(limit,parts.size()); i++)
1233 if (words.size() == 2)
1235 team1 = words[0].simplified();
1236 team2 = words[1].simplified();
1252 QString& team1, QString& team2)
1258 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1259 QString(
"can't find team names in subtitle or description '%1'").arg(description));
1288 name = name.simplified();
1290 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"Start: %1").arg(name));
1295 "SELECT sc.name, sc.pattern, sc.nth, sc.replacement" \
1296 " FROM sportscleanup sc " \
1297 " WHERE (provider=0 or provider=:PROVIDER) " \
1298 " AND (key1='all' or key1=:SPORT) " \
1299 " AND (:NAME REGEXP pattern)" \
1300 " ORDER BY sc.weight");
1301 query.
bindValue(
":PROVIDER",
static_cast<uint8_t
>(
info.dataProvider));
1311 while (query.
next())
1313 QString patternName = query.
value(0).toString();
1314 QString patternStr = query.
value(1).toString();
1315 int patternField = query.
value(2).toInt();
1316 QString replacement = query.
value(3).toString();
1318 QString original = name;
1319 QString tag {
"no match"};
1320 QRegularExpressionMatch match;
1322 if (name.contains(QRegularExpression(patternStr), &match) &&
1325 QString capturedText = match.captured(patternField);
1326 name = name.replace(match.capturedStart(patternField),
1327 match.capturedLength(patternField),
1329 name = name.simplified();
1337 tag = QString(
"matched '%1'").arg(capturedText);
1340 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1341 QString(
"pattern '%1', %2, now '%3'")
1342 .arg(patternName, tag, name));
1356 if (!name2.isEmpty())
1373 LOG(VB_GENERAL, LOG_INFO,
LOC +
1374 QString(
"Recording %1 rule %2 for '%3 @ %4' has finished. Stop recording.")
1378 MythEvent me(QString(
"STOP_RECORDING %1 %2")
1395 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1396 QString(
"Recording %1 rule %2 for '%3 @ %4' scheduled to end soon. Extending recording.")
1406 static const QString ae {
"(Auto Extend)"};
1421 if (!rr->
Save(
true))
1425 LOG(VB_GENERAL, LOG_ERR,
LOC +
1426 QString(
"Recording %1, couldn't save override rule for '%2 @ %3'.")
1433 LOG(VB_GENERAL, LOG_INFO,
LOC +
1434 QString(
"Recording %1, %2 override rule %3 for '%4 @ %5' ending %6 -> %7.")
1436 .arg(
exists ?
"updated" :
"created",
1455 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1456 QString(
"Recording %1 rule %2 for '%3 @ %4' ends %5.")
1489 LOG(VB_GENERAL, LOG_INFO,
LOC +
1490 QString(
"Couldn't get recording %1 from scheduler")
1497 LOG(VB_GENERAL, LOG_INFO,
LOC +
1498 QString(
"Invalid status for '%1 : %2', status %3.")
1509 LOG(VB_GENERAL, LOG_INFO,
LOC +
1510 QString(
"Unknown sport '%1' for provider %2")
1519 LOG(VB_GENERAL, LOG_INFO,
LOC +
1520 QString(
"unable to create data source of type %1.")
1533 LOG(VB_GENERAL, LOG_INFO,
LOC +
1534 QString(
"Unable to find '%1 : %2', provider %3")
1545 for (
auto it = infoList.begin(); !found && it != infoList.end(); it++)
1552 source->findInfoUrl(game,
info);
1555 LOG(VB_GENERAL, LOG_INFO,
LOC +
1556 QString(
"unable to find data page for recording '%1 : %2'.")
1585 LOG(VB_GENERAL, LOG_INFO,
LOC +
1586 QString(
"Couldn't get recording %1 from scheduler")
1593 auto ri = std::make_unique<RecordingInfo>(*_ri);
1598 LOG(VB_GENERAL, LOG_INFO,
LOC +
1599 QString(
"Invalid status for '%1 : %2', status %3.")
1600 .arg(ri->GetTitle(), ri->GetSubtitle(),
1608 if (
nullptr == source)
1610 LOG(VB_GENERAL, LOG_INFO,
LOC +
1611 QString(
"Couldn't create source of type %1")
1617 auto* page = source->loadPage(game, game.
getGameUrl());
1618 if (
nullptr == page)
1620 LOG(VB_GENERAL, LOG_INFO,
LOC +
1621 QString(
"Couldn't load source %1, teams %2 and %3, url %4")
1628 auto gameState = page->findGameScore(game);
1629 if (!gameState.isValid())
1631 LOG(VB_GENERAL, LOG_INFO,
LOC +
1632 QString(
"Game state for source %1, teams %2 and %3 is invalid")
1638 if (gameState.isFinished())
1645 if (ri->GetScheduledEndTime() <
1670 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1671 QString(
"Nothing left to do. Exiting."));
1675 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1676 QString(
"%1 new recordings, %2 active recordings, %3 overrides.")
void setTeams(QString team1, QString team2)
QString m_team1Normalized
void setGameUrl(QUrl url)
Set the game status information URL.
void setTeamsNorm(QString team1, QString team2)
QDateTime getStartTime() const
void setInfo(const SportInfo &info)
void setAbbrevs(QStringList abbrevs)
QString getAbbrev2() const
QString getStartTimeAsString() const
bool teamsMatch(const QStringList &names, const QStringList &abbrevs) const
Do the supplied team names/abbrevs match this game.
QString m_team2Normalized
int getRecordedId() const
void setInfoUrl(QUrl url)
Set the game scheduling information URL.
QString getAbbrev1() const
void setStartTime(const QDateTime &time)
SportInfo getInfo() const
void setTextState(QString text)
QSqlQuery wrapper that fetches a DB connection from the connection pool.
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
QVariant value(int i) const
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
void quit(void)
calls exit(0)
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
QThread * qthread(void)
Returns the thread, this will always return the same pointer no matter how often you restart the thre...
void dispatch(const MythEvent &event)
static void DBError(const QString &where, const MSqlQuery &query)
bool download(const QString &url, const QString &dest, bool reload=false)
Downloads a URL to a file in blocking mode.
This class is used as a container for messages.
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
uint GetRecordingID(void) const
QString GetDescription(void) const
QString GetTitle(void) const
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
QDateTime GetScheduledStartTime(void) const
The scheduled start time of program.
RecStatus::Type GetRecordingStatus(void) const
QDateTime GetRecordingEndTime(void) const
Approximate time the recording should have ended, did end, or is intended to end.
QString GetSubtitle(void) const
virtual QDateTime getNow()
Get the current time. Overridden by the testing code.
virtual bool timeIsClose(const QDateTime &eventStart)
Base Classes ///.
static bool getJsonObject(const QJsonObject &object, QStringList &path, QJsonObject &value)
Retrieve a specific object from another json object.
static bool getJsonArray(const QJsonObject &object, const QString &key, QJsonArray &value)
Retrieve the specified array from a json object.
static QJsonObject walkJsonPath(QJsonObject &object, const QStringList &path)
Iterate through a json object and return the specified object.
static bool getJsonInt(const QJsonObject &object, QStringList &path, int &value)
Retrieve the specified integer from a json object.
virtual bool findGameInfo(ActiveGame &game)=0
static bool getJsonString(const QJsonObject &object, QStringList &path, QString &value)
Retrieve the specified string from a json object.
RecExtDataSource * getSource()
static void clearCache()
Clear the downloaded document cache.
static QHash< QString, QJsonDocument > s_downloadedJson
A cache of downloaded documents.
GameState findGameScore(ActiveGame &game) override
Parse the previously downloaded data page for a given game.
static const QList< GameStatus > kFinalStatuses
A list of the ESPN status that mean the game is over.
bool findGameInfo(ActiveGame &game) override
Parse a previously downloaded data page for a given sport.
RecExtDataPage * loadPage(const ActiveGame &game, const QUrl &_url) override
Download the data page for a game, and do some minimal validation.
QUrl findInfoUrl(ActiveGame &game, SportInfo &info) override
Find the right URL for a specific recording.
RecExtDataPage * newPage(const QJsonDocument &doc) override
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 ...
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.
GameState findGameScore(ActiveGame &game) override
Parse the previously downloaded data page for a given game.
bool parseGameObject(const QJsonObject &gameObject, ActiveGame &game)
MLB ///.
bool findGameInfo(ActiveGame &game) override
Parse a previously downloaded data page for a given sport.
QUrl findInfoUrl(ActiveGame &game, SportInfo &info) override
Find the right URL for a specific recording.
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.
RecExtDataPage * newPage(const QJsonDocument &doc) override
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...
RecExtDataPage * loadPage(const ActiveGame &game, const QUrl &_url) override
Download the data page for a game, and do some minimal validation.
static QString toString(RecStatus::Type recstatus, uint id)
Converts "recstatus" into a short (unreadable) string.
virtual RecExtDataSource * createDataSource(AutoExtendType type)
Create a RecExtDataSource object for the specified service.
static void clearDownloadedInfo()
Clear all downloaded info.
void processNewRecordings()
Process the list of newly started sports recordings.
QMutex m_newRecordingsLock
New recordings are added by the scheduler process and removed by this process.
uint m_forcedYearforTesting
Testing data.
bool findKnownSport(const QString &_title, AutoExtendType type, SportInfoList &info) const
Retrieve the db record for a sporting event on a specific provider.
bool m_running
Whether the RecordingExtender process is running.
void addNewRecording(int recordedID)
Add an item to the list of new recordings.
static QString ruleIdAsString(const RecordingRule *rr)
Quick helper function for printing recording rule numbers.
void expireOverrides()
Delete the list of the override rules that have been created by this instance of RecordingExtender.
static void unchangedRecording(const RecordingInfo *ri, RecordingRule *rr, const ActiveGame &game)
Log that this recording hasn't changed.
static void nameCleanup(const SportInfo &info, QString &name1, QString &name2)
Clean up two team names for comparison against the ESPN API.
QList< int > m_overrideRules
Recordings that have had an override rule creates.
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...
~RecordingExtender() override
void processActiveRecordings()
Process the currently active sports recordings.
Scheduler * m_scheduler
Pointer to the scheduler.
static QMutex s_createLock
Interlock the scheduler thread crating this process, and this process determining whether it should c...
static bool parseProgramInfo(const QString &subtitle, const QString &description, QString &team1, QString &team2)
Parse a RecordingInfo to find the team names.
QList< int > m_newRecordings
Newly started recordings to process.
void run(void) override
The main execution loop for the Recording Extender.
QList< ActiveGame > m_activeGames
Currently ongoing games to track.
static void finishRecording(const RecordingInfo *ri, RecordingRule *rr, const ActiveGame &game)
Stop the current recording early.
void extendRecording(const RecordingInfo *ri, RecordingRule *rr, const ActiveGame &game)
Extend the current recording by XX minutes.
void checkDone()
Is there any remaining work? Check for both newly created recording and for active recordings.
static RecordingExtender * s_singleton
The single instance of a RecordingExtender.
Holds information on a TV Program one might wish to record.
RecordingRule * GetRecordingRule(void)
Returns the "record" field, creating it if necessary.
Internal representation of a recording rule, mirrors the record table.
int m_recordID
Unique Recording Rule ID.
bool Save(bool sendSig=true)
AutoExtendType m_autoExtend
QMap< QString, ProgramInfo * > GetRecording(void) const override
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
static const QString espnInfoUrlFmt
ESPN ///.
static bool ValidRecordingStatus(RecStatus::Type recstatus)
Does this recording status indicate that the recording is still ongoing.
static constexpr int kExtensionTimeInSec
static QString normalizeString(const QString &s)
Remove all diacritical marks, etc., etc., from a string leaving just the base characters.
static const QString espnGameUrlFmt
static const QRegularExpression kSentencePattern
static constexpr std::chrono::minutes kExtensionTime
static bool parseProgramString(const QString &string, qsizetype limit, QString &team1, QString &team2)
static constexpr int64_t kLookForwardTime
static constexpr int64_t kLookBackTime
Does the specified time fall within -3/+1 hour from now?
static const QRegularExpression kVersusPattern
QList< SportInfo > SportInfoList