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();
424static const QString
espnInfoUrlFmt {
"http://site.api.espn.com/apis/site/v2/sports/%1/%2/scoreboard"};
425static const QString
espnGameUrlFmt {
"http://sports.core.api.espn.com/v2/sports/%1/leagues/%2/events/%3/competitions/%3/status"};
429 FINAL, FORFEIT, CANCELLED, POSTPONED, SUSPENDED,
430 FORFEIT_HOME_TEAM, FORFEIT_AWAY_TEAM, ABANDONED, FULL_TIME,
431 PLAY_COMPLETE, OFFICIAL_EVENT_SHORTENED, RETIRED,
432 BYE, ESPNVOID, FINAL_SCORE_AFTER_EXTRA_TIME, FINAL_SCORE_AFTER_GOLDEN_GOAL,
433 FINAL_SCORE_AFTER_PENALTIES, END_EXTRA_TIME, FINAL_SCORE_ABANDONED,
446 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
447 QString(
"Looking for match of %1/%2")
450 QJsonObject json =
m_doc.object();
453 if (!json.contains(
"events") || !json[
"events"].isArray())
455 LOG(VB_GENERAL, LOG_INFO,
LOC +
456 QString(
"malformed json document, step %1.").arg(1));
461 QJsonArray eventArray = json[
"events"].toArray();
462 for (
const auto& eventValue : std::as_const(eventArray))
465 if (!eventValue.isObject())
467 LOG(VB_GENERAL, LOG_INFO,
LOC +
468 QString(
"malformed json document, step %1.").arg(2));
471 QJsonObject
event = eventValue.toObject();
476 QString gameTitle {};
477 QString gameShortTitle {};
483 LOG(VB_GENERAL, LOG_INFO,
LOC +
484 QString(
"malformed json document, step %1.").arg(3));
489 if ((teamNames.size() != 2) || (teamAbbrevs.size() != 2))
491 LOG(VB_GENERAL, LOG_INFO,
LOC +
492 QString(
"malformed json document, step %1.").arg(4));
498 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
499 QString(
"Found %1 at %2 (%3 @ %4). Teams don't match.")
500 .arg(teamNames[0], teamNames[1],
501 teamAbbrevs[0], teamAbbrevs[1]));
507 LOG(VB_GENERAL, LOG_INFO,
LOC +
508 QString(
"Found '%1 vs %2' starting time %3 wrong")
519 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
520 QString(
"Match: %1 at %2 (%3 @ %4), start %5.")
542 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
543 QString(
"Parsing game score for %1/%2")
546 QJsonObject json =
m_doc.object();
559 LOG(VB_GENERAL, LOG_INFO,
LOC +
560 QString(
"malformed json document, step %1.").arg(5));
563 auto stateId =
static_cast<GameStatus>(typeId.toInt());
568 if ((description == detail) || (description ==
"In Progress"))
571 extra = QString (
"%1: %2").arg(description, detail);
573 LOG(VB_GENERAL, LOG_INFO,
LOC +
574 QString(
"%1 at %2 (%3 @ %4), %5.")
591 QString url = _url.url();
596 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
597 QString(
"Using cached document for %1.").arg(url));
603 QString scheme = _url.scheme();
604 if (scheme == QStringLiteral(u
"file"))
606 QFile
file(_url.path(QUrl::FullyDecoded));
607 ok =
file.open(QIODevice::ReadOnly);
609 data =
file.readAll();
611 else if ((scheme == QStringLiteral(u
"http")) ||
612 (scheme == QStringLiteral(u
"https")))
618 LOG(VB_GENERAL, LOG_INFO,
LOC +
619 QString(
"\"%1\" couldn't download %2.")
624 QJsonParseError
error {};
625 QJsonDocument doc = QJsonDocument::fromJson(data, &
error);
626 if (
error.error != QJsonParseError::NoError)
628 LOG(VB_GENERAL, LOG_ERR,
LOC +
629 QString(
"Error parsing %1 at offset %2: %3")
630 .arg(url).arg(
error.offset).arg(
error.errorString()));
634 QJsonObject json = doc.object();
635 if (json.contains(
"code") && json[
"code"].isDouble() &&
636 json.contains(
"detail") && json[
"detail"].isString())
638 LOG(VB_GENERAL, LOG_INFO,
LOC +
639 QString(
"error downloading json document, code %1, detail %2.")
640 .arg(json[
"code"].toInt()).arg(json[
"detail"].
toString()));
657 query.addQueryItem(
"limit",
"500");
659 if (
info.league.endsWith(
"college-basketball"))
660 query.addQueryItem(
"group",
"50");
662 query.addQueryItem(
"dates", dt.toString(
"yyyyMMdd"));
703 LOG(VB_GENERAL, LOG_INFO,
LOC +
704 QString(
"Couldn't load %1").arg(game.
getInfoUrl().url()));
709 LOG(VB_GENERAL, LOG_INFO,
LOC +
710 QString(
"Found game '%1 vs %2' at %3")
722 LOG(VB_GENERAL, LOG_INFO,
LOC +
723 QString(
"Couldn't load %1").arg(game.
getInfoUrl().url()));
728 LOG(VB_GENERAL, LOG_INFO,
LOC +
729 QString(
"Found game '%1 vs %2' at %3")
741 LOG(VB_GENERAL, LOG_INFO,
LOC +
742 QString(
"Couldn't load %1").arg(game.
getInfoUrl().url()));
747 LOG(VB_GENERAL, LOG_INFO,
LOC +
748 QString(
"Found game '%1 vs %2' at %3")
791 QStringList teamNames {
"",
""};
792 QStringList teamAbbrevs {
"",
""};
795 !
getJsonString(gameObject,
"teams/home/team/name", teamNames[0]) ||
796 !
getJsonString(gameObject,
"teams/away/team/name", teamNames[1]) ||
797 !
getJsonString(gameObject,
"teams/home/team/abbreviation", teamAbbrevs[0]) ||
798 !
getJsonString(gameObject,
"teams/away/team/abbreviation", teamAbbrevs[1]))
800 LOG(VB_GENERAL, LOG_INFO,
LOC +
801 QString(
"malformed json document, step %1.").arg(1));
806 bool success = game.
teamsMatch(teamNames, teamAbbrevs);
807 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
808 QString(
"Found: %1 at %2 (%3 @ %4), starting %5. (%6)")
809 .arg(teamNames[0], teamNames[1],
810 teamAbbrevs[0], teamAbbrevs[1], dateStr,
811 success ?
"Success" :
"Teams don't match"));
832 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
833 QString(
"Looking for match of %1/%2")
836 QJsonObject json =
m_doc.object();
840 QJsonArray datesArray;
843 LOG(VB_GENERAL, LOG_INFO,
LOC +
844 QString(
"malformed json document, step %1.").arg(1));
849 for (
const auto& dateValue : std::as_const(datesArray))
851 if (!dateValue.isObject())
853 LOG(VB_GENERAL, LOG_INFO,
LOC +
854 QString(
"malformed json document, step %1.").arg(2));
857 QJsonObject dateObject = dateValue.toObject();
859 QJsonArray gamesArray;
862 LOG(VB_GENERAL, LOG_INFO,
LOC +
863 QString(
"malformed json document, step %1.").arg(3));
868 for (
const auto& gameValue : std::as_const(gamesArray))
870 if (!gameValue.isObject())
872 LOG(VB_GENERAL, LOG_INFO,
LOC +
873 QString(
"malformed json document, step %1.").arg(4));
876 QJsonObject gameObject = gameValue.toObject();
881 LOG(VB_GENERAL, LOG_INFO,
LOC +
882 QString(
"Found '%1 vs %2' starting %3 (%4)")
884 match ?
"match" :
"keep looking"));
901 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
902 QString(
"Parsing game score for %1/%2")
905 QJsonObject json =
m_doc.object();
910 QString abstractGameState;
911 QString detailedGameState;
913 QString inningOrdinal;
914 if (!
getJsonString(json,
"gameData/status/abstractGameState", abstractGameState) ||
915 !
getJsonString(json,
"gameData/status/detailedState", detailedGameState))
917 LOG(VB_GENERAL, LOG_INFO,
LOC +
918 QString(
"malformed json document (%d)").arg(1));
922 if (detailedGameState !=
"Scheduled")
924 if (!
getJsonInt( json,
"liveData/linescore/currentInning", period) ||
925 !
getJsonString(json,
"liveData/linescore/currentInningOrdinal", inningOrdinal) ||
926 !
getJsonString(json,
"liveData/linescore/inningState", inningState))
928 LOG(VB_GENERAL, LOG_INFO,
LOC +
929 QString(
"malformed json document (%d)").arg(2));
934 bool gameOver = (abstractGameState ==
"Final") ||
935 detailedGameState.contains(
"Suspended");
942 else if (detailedGameState ==
"In Progress")
943 extra = QString(
"%1 %2").arg(inningState, inningOrdinal);
945 extra = detailedGameState;
947 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
948 QString(
"%1 at %2 (%3 @ %4), %5.")
965 QString url = _url.url();
970 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
971 QString(
"Using cached document for %1.").arg(url));
977 QString scheme = _url.scheme();
978 if (scheme == QStringLiteral(u
"file"))
980 QFile
file(_url.path(QUrl::FullyDecoded));
981 ok =
file.open(QIODevice::ReadOnly);
983 data =
file.readAll();
985 else if ((scheme == QStringLiteral(u
"http")) ||
986 (scheme == QStringLiteral(u
"https")))
992 LOG(VB_GENERAL, LOG_INFO,
LOC +
993 QString(
"\"%1\" couldn't download %2.")
998 QJsonParseError
error {};
999 QJsonDocument doc = QJsonDocument::fromJson(data, &
error);
1000 if (
error.error != QJsonParseError::NoError)
1002 LOG(VB_GENERAL, LOG_ERR,
LOC +
1003 QString(
"Error parsing %1 at offset %2: %3")
1004 .arg(url).arg(
error.offset).arg(
error.errorString()));
1018 const QDateTime& dt)
1023 QDateTime yesterday = dt.addDays(-1);
1024 QDateTime tomorrow = dt.addDays(+1);
1025 QUrl url {
"https://statsapi.mlb.com/api/v1/schedule"};
1027 query.addQueryItem(
"sportId",
"1");
1028 query.addQueryItem(
"hydrate",
"team");
1029 query.addQueryItem(
"startDate", QString(
"%1").arg(yesterday.toString(
"yyyy-MM-dd")));
1030 query.addQueryItem(
"endDate", QString(
"%1").arg(tomorrow.toString(
"yyyy-MM-dd")));
1031 url.setQuery(query);
1044 gameUrl.setPath(str);
1045 gameUrl.setQuery(QString());
1070 LOG(VB_GENERAL, LOG_INFO,
LOC +
1071 QString(
"Couldn't load %1").arg(game.
getInfoUrl().url()));
1074 LOG(VB_GENERAL, LOG_INFO,
LOC +
1075 QString(
"Loaded page %1").arg(game.
getInfoUrl().url()));
1109 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1110 QString(
"Recording of %1 at %2 not marked for auto extend.")
1114 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1115 QString(
"Adding %1 at %2 to new recordings list.")
1183 static const QRegularExpression year {R
"(\d{4})"};
1184 QRegularExpressionMatch match;
1185 QString title = _title;
1186 if (title.contains(year, &match))
1189 int matchYear = match.captured().toInt(&ok);
1192 : QDateTime::currentDateTimeUtc().date().year();
1194 if (!ok || ((matchYear != thisYear) && (matchYear != thisYear+1)))
1196 title = title.remove(match.capturedStart(), match.capturedLength());
1198 title = title.simplified();
1199 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1200 QString(
"Looking for %1 title '%2")
1205 "SELECT sl.title, api.provider, api.key1, api.key2" \
1206 " FROM sportslisting sl " \
1207 " INNER JOIN sportsapi api ON sl.api = api.id" \
1208 " WHERE api.provider = :PROVIDER AND :TITLE REGEXP sl.title");
1216 while (query.
next())
1220 info.showTitle = query.
value(0).toString();
1223 info.league = query.
value(3).toString();
1224 infoList.append(
info);
1226 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1227 QString(
"Info: '%1' matches '%2' '%3' '%4' '%5'")
1231 return !infoList.isEmpty();
1244#
if QT_VERSION < QT_VERSION_CHECK(6,0,0)
1249 QString& team1, QString& team2)
1251 QString lString = string;
1253 for (
int i = 0; i < std::min(limit,parts.size()); i++)
1256 if (words.size() == 2)
1258 team1 = words[0].simplified();
1259 team2 = words[1].simplified();
1275 QString& team1, QString& team2)
1281 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1282 QString(
"can't find team names in subtitle or description '%1'").arg(description));
1311 name = name.simplified();
1313 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"Start: %1").arg(name));
1318 "SELECT sc.name, sc.pattern, sc.nth, sc.replacement" \
1319 " FROM sportscleanup sc " \
1320 " WHERE (provider=0 or provider=:PROVIDER) " \
1321 " AND (key1='all' or key1=:SPORT) " \
1322 " AND (:NAME REGEXP pattern)" \
1323 " ORDER BY sc.weight");
1324 query.
bindValue(
":PROVIDER",
static_cast<uint8_t
>(
info.dataProvider));
1334 while (query.
next())
1336 QString patternName = query.
value(0).toString();
1337 QString patternStr = query.
value(1).toString();
1338 int patternField = query.
value(2).toInt();
1339 QString replacement = query.
value(3).toString();
1341 QString original = name;
1342 QString tag {
"no match"};
1343 QRegularExpressionMatch match;
1345 if (name.contains(QRegularExpression(patternStr), &match) &&
1348 QString capturedText = match.captured(patternField);
1349 name = name.replace(match.capturedStart(patternField),
1350 match.capturedLength(patternField),
1352 name = name.simplified();
1360 tag = QString(
"matched '%1'").arg(capturedText);
1363 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1364 QString(
"pattern '%1', %2, now '%3'")
1365 .arg(patternName, tag, name));
1379 if (!name2.isEmpty())
1396 LOG(VB_GENERAL, LOG_INFO,
LOC +
1397 QString(
"Recording %1 rule %2 for '%3 @ %4' has finished. Stop recording.")
1401 MythEvent me(QString(
"STOP_RECORDING %1 %2")
1418 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1419 QString(
"Recording %1 rule %2 for '%3 @ %4' scheduled to end soon. Extending recording.")
1429 static const QString ae {
"(Auto Extend)"};
1444 if (!rr->
Save(
true))
1448 LOG(VB_GENERAL, LOG_ERR,
LOC +
1449 QString(
"Recording %1, couldn't save override rule for '%2 @ %3'.")
1456 LOG(VB_GENERAL, LOG_INFO,
LOC +
1457 QString(
"Recording %1, %2 override rule %3 for '%4 @ %5' ending %6 -> %7.")
1459 .arg(
exists ?
"updated" :
"created",
1478 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1479 QString(
"Recording %1 rule %2 for '%3 @ %4' ends %5.")
1512 LOG(VB_GENERAL, LOG_INFO,
LOC +
1513 QString(
"Couldn't get recording %1 from scheduler")
1520 LOG(VB_GENERAL, LOG_INFO,
LOC +
1521 QString(
"Invalid status for '%1 : %2', status %3.")
1532 LOG(VB_GENERAL, LOG_INFO,
LOC +
1533 QString(
"Unknown sport '%1' for provider %2")
1542 LOG(VB_GENERAL, LOG_INFO,
LOC +
1543 QString(
"unable to create data source of type %1.")
1556 LOG(VB_GENERAL, LOG_INFO,
LOC +
1557 QString(
"Unable to find '%1 : %2', provider %3")
1568 for (
auto it = infoList.begin(); !found && it != infoList.end(); it++)
1575 source->findInfoUrl(game,
info);
1578 LOG(VB_GENERAL, LOG_INFO,
LOC +
1579 QString(
"unable to find data page for recording '%1 : %2'.")
1608 LOG(VB_GENERAL, LOG_INFO,
LOC +
1609 QString(
"Couldn't get recording %1 from scheduler")
1616 auto ri = std::make_unique<RecordingInfo>(*_ri);
1621 LOG(VB_GENERAL, LOG_INFO,
LOC +
1622 QString(
"Invalid status for '%1 : %2', status %3.")
1623 .arg(ri->GetTitle(), ri->GetSubtitle(),
1631 if (
nullptr == source)
1633 LOG(VB_GENERAL, LOG_INFO,
LOC +
1634 QString(
"Couldn't create source of type %1")
1640 auto* page = source->loadPage(game, game.
getGameUrl());
1641 if (
nullptr == page)
1643 LOG(VB_GENERAL, LOG_INFO,
LOC +
1644 QString(
"Couldn't load source %1, teams %2 and %3, url %4")
1651 auto gameState = page->findGameScore(game);
1652 if (!gameState.isValid())
1654 LOG(VB_GENERAL, LOG_INFO,
LOC +
1655 QString(
"Game state for source %1, teams %2 and %3 is invalid")
1661 if (gameState.isFinished())
1668 if (ri->GetScheduledEndTime() <
1693 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1694 QString(
"Nothing left to do. Exiting."));
1698 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1699 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