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 : qAsConst(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 : qAsConst(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 : qAsConst(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 : qAsConst(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())
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 QString& team1, QString& team2)
1222 QString lString = string;
1224 for (
int i = 0; i < std::min(limit,static_cast<int>(parts.size())); i++)
1227 if (words.size() == 2)
1229 team1 = words[0].simplified();
1230 team2 = words[1].simplified();
1246 QString& team1, QString& team2)
1252 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1253 QString(
"can't find team names in subtitle or description '%1'").arg(description));
1282 name = name.simplified();
1284 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"Start: %1").arg(name));
1289 "SELECT sc.name, sc.pattern, sc.nth, sc.replacement" \
1290 " FROM sportscleanup sc " \
1291 " WHERE (provider=0 or provider=:PROVIDER) " \
1292 " AND (key1='all' or key1=:SPORT) " \
1293 " AND (:NAME REGEXP pattern)" \
1294 " ORDER BY sc.weight");
1305 while (query.
next())
1307 QString patternName = query.
value(0).toString();
1308 QString patternStr = query.
value(1).toString();
1309 int patternField = query.
value(2).toInt();
1310 QString replacement = query.
value(3).toString();
1312 QString original = name;
1313 QString tag {
"no match"};
1314 QRegularExpressionMatch match;
1316 if (name.contains(QRegularExpression(patternStr), &match) &&
1319 QString capturedText = match.captured(patternField);
1320 name = name.replace(match.capturedStart(patternField),
1321 match.capturedLength(patternField),
1323 name = name.simplified();
1331 tag = QString(
"matched '%1'").arg(capturedText);
1334 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1335 QString(
"pattern '%1', %2, now '%3'")
1336 .arg(patternName, tag, name));
1350 if (!name2.isEmpty())
1367 LOG(VB_GENERAL, LOG_INFO,
LOC +
1368 QString(
"Recording %1 rule %2 for '%3 @ %4' has finished. Stop recording.")
1372 MythEvent me(QString(
"STOP_RECORDING %1 %2")
1389 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1390 QString(
"Recording %1 rule %2 for '%3 @ %4' scheduled to end soon. Extending recording.")
1400 static const QString ae {
"(Auto Extend)"};
1415 if (!rr->
Save(
true))
1419 LOG(VB_GENERAL, LOG_ERR,
LOC +
1420 QString(
"Recording %1, couldn't save override rule for '%2 @ %3'.")
1427 LOG(VB_GENERAL, LOG_INFO,
LOC +
1428 QString(
"Recording %1, %2 override rule %3 for '%4 @ %5' ending %6 -> %7.")
1430 .arg(exists ?
"updated" :
"created",
1449 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1450 QString(
"Recording %1 rule %2 for '%3 @ %4' ends %5.")
1483 LOG(VB_GENERAL, LOG_INFO,
LOC +
1484 QString(
"Couldn't get recording %1 from scheduler")
1491 LOG(VB_GENERAL, LOG_INFO,
LOC +
1492 QString(
"Invalid status for '%1 : %2', status %3.")
1503 LOG(VB_GENERAL, LOG_INFO,
LOC +
1504 QString(
"Unknown sport '%1' for provider %2")
1513 LOG(VB_GENERAL, LOG_INFO,
LOC +
1514 QString(
"unable to create data source of type %1.")
1527 LOG(VB_GENERAL, LOG_INFO,
LOC +
1528 QString(
"Unable to find '%1 : %2', provider %3")
1539 for (
auto it = infoList.begin(); !found && it != infoList.end(); it++)
1546 source->findInfoUrl(game, info);
1549 LOG(VB_GENERAL, LOG_INFO,
LOC +
1550 QString(
"unable to find data page for recording '%1 : %2'.")
1579 LOG(VB_GENERAL, LOG_INFO,
LOC +
1580 QString(
"Couldn't get recording %1 from scheduler")
1587 auto ri = std::make_unique<RecordingInfo>(*_ri);
1592 LOG(VB_GENERAL, LOG_INFO,
LOC +
1593 QString(
"Invalid status for '%1 : %2', status %3.")
1594 .arg(ri->GetTitle(), ri->GetSubtitle(),
1602 if (
nullptr == source)
1604 LOG(VB_GENERAL, LOG_INFO,
LOC +
1605 QString(
"Couldn't create source of type %1")
1611 auto* page = source->loadPage(game, game.
getGameUrl());
1612 if (
nullptr == page)
1614 LOG(VB_GENERAL, LOG_INFO,
LOC +
1615 QString(
"Couldn't load source %1, teams %2 and %3, url %4")
1622 auto gameState = page->findGameScore(game);
1623 if (!gameState.isValid())
1625 LOG(VB_GENERAL, LOG_INFO,
LOC +
1626 QString(
"Game state for source %1, teams %2 and %3 is invalid")
1632 if (gameState.isFinished())
1639 if (ri->GetScheduledEndTime() <
1664 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1665 QString(
"Nothing left to do. Exiting."));
1669 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
1670 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.