MythTV master
recordingextender.h
Go to the documentation of this file.
1/*
2 * Class RecordingExtender
3 *
4 * Copyright (c) David Hampton 2021
5 *
6 * Based on the ideas in the standalone Myth Recording PHP code from
7 * Derek Battams <derek@battams.ca>.
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 */
23
24#ifndef RECORDING_EXTENDER_H_
25#define RECORDING_EXTENDER_H_
26
27#include <memory>
28
29#include <QMutex>
30#include <QJsonDocument>
31#include <QUrl>
32
33#include "libmythbase/mthread.h"
35
36class Scheduler;
37
38//
39// Database info
40//
42{
43 QString showTitle; // Title in listings (RE match)
45 QString sport;
46 QString league;
47};
48using SportInfoList = QList<SportInfo>;
49
51{
52 public:
53 ActiveGame(int recordedid, QString title) :
54 m_recordedid(recordedid), m_title(std::move(title)) {}
55 ActiveGame(int recordedid, QString title, SportInfo info) :
56 m_recordedid(recordedid), m_title(std::move(title)),
57 m_info(std::move(info)) {}
58 int getRecordedId() const { return m_recordedid; }
59 QString getTitle() const { return m_title; }
60 SportInfo getInfo() const { return m_info; }
61 QString getTeam1() const { return m_team1; }
62 QString getTeam2() const { return m_team2; }
63 QString getTeam1Norm() const { return m_team1Normalized; }
64 QString getTeam2Norm() const { return m_team2Normalized; }
65 QString getAbbrev1() const { return m_abbrev1; }
66 QString getAbbrev2() const { return m_abbrev2; }
67 QUrl getInfoUrl() const { return m_infoUrl; }
68 QUrl getGameUrl() const { return m_gameUrl; }
69 QDateTime getStartTime() const { return m_startTime; }
70 QString getStartTimeAsString() const
71 { return m_startTime.toString(Qt::ISODate); }
72
73 void setInfo(const SportInfo& info) { m_info = info; }
74 void setTeams(QString team1, QString team2)
75 { m_team1 = std::move(team1); m_team2 = std::move(team2); }
76 void setTeamsNorm(QString team1, QString team2)
77 { m_team1Normalized = std::move(team1); m_team2Normalized = std::move(team2); }
78 void setAbbrev1(const QString& abbrev) { m_abbrev1 = abbrev; }
79 void setAbbrev2(const QString& abbrev) { m_abbrev2 = abbrev; }
80 void setAbbrevs(QStringList abbrevs)
81 { m_abbrev1 = abbrevs[0]; m_abbrev2 = abbrevs[1]; }
82 void setInfoUrl(QUrl url);
83 void setGameUrl(QUrl url);
84 void setStartTime(const QDateTime& time) { m_startTime = time; }
85
86 bool isValid() const
87 {
88 return !m_team1.isEmpty() && !m_team2.isEmpty() &&
89 m_startTime.isValid() && m_infoUrl.isValid();
90 }
91
92 bool teamsMatch(const QStringList& names, const QStringList& abbrevs) const;
93
94 private:
95 int m_recordedid {0};
96 QString m_title;
98
99 QString m_team1;
100 QString m_team2;
103 QString m_abbrev1;
104 QString m_abbrev2;
107 QDateTime m_startTime;
108};
109
110//
111// The status of a single game.
112//
114{
115 public:
116 GameState() = default;
117 GameState(const QString& n1, const QString& n2,
118 const QString& a1, const QString& a2,
119 int p, bool finished) :
120 m_team1(n1.simplified()), m_team2(n2.simplified()),
121 m_abbrev1(a1.simplified()), m_abbrev2(a2.simplified()),
122 m_period(p), m_finished(finished) {};
123 GameState(const ActiveGame& game, int p, bool finished) :
124 m_team1(game.getTeam1()), m_team2(game.getTeam2()),
125 m_abbrev1(game.getAbbrev1()), m_abbrev2(game.getAbbrev2()),
126 m_period(p), m_finished(finished) {};
127 bool isValid() {return !m_team1.isEmpty() && !m_team2.isEmpty();};
128 QString getTeam1() { return m_team1; }
129 QString getTeam2() { return m_team2; }
130 QString getAbbrev1() { return m_abbrev1; }
131 QString getAbbrev2() { return m_abbrev2; }
132 QString getTextState() { return m_textState; }
133 void setTextState(QString text) { m_textState = std::move(text); }
134 int getPeriod() const { return m_period; }
135 bool isFinished() const { return m_finished; }
136 bool matchName(const QString& a, const QString& b)
137 { return (((a == m_team1) && (b == m_team2)) ||
138 ((a == m_team2) && (b == m_team1))); }
139 bool matchAbbrev(const QString& a, const QString& b)
140 { return (((a == m_abbrev1) && (b == m_abbrev2)) ||
141 ((a == m_abbrev2) && (b == m_abbrev1))); }
142 bool match(const QString& team1, const QString& team2)
143 {
144 if (matchName(team1, team2))
145 return true;
146 return matchAbbrev(team1, team2);
147 }
148 private:
149 QString m_team1;
150 QString m_team2;
151 QString m_abbrev1;
152 QString m_abbrev2;
153 QString m_textState;
154 int m_period {0};
155 bool m_finished {false};
156};
157//using GameStateList = QList<GameState>;
158
159class RecExtDataSource;
161
162class RecExtDataPage : public QObject
163{
164 Q_OBJECT;
165 public:
167 {return qobject_cast<RecExtDataSource*>(parent()); }
169 virtual QDateTime getNow() {return MythDate::current(); }
170 virtual bool timeIsClose(const QDateTime& eventStart);
171 virtual bool findGameInfo(ActiveGame& game) = 0;
173 static QJsonObject walkJsonPath(QJsonObject& object, const QStringList& path);
176 QJsonDocument getDoc() { return m_doc; }
177 static bool getJsonInt (const QJsonObject& object, QStringList& path, int& value);
178 static bool getJsonInt (const QJsonObject& object, const QString& key, int& value);
179 static bool getJsonString(const QJsonObject& object, QStringList& path, QString& value);
180 static bool getJsonString(const QJsonObject& object, const QString& key, QString& value);
181 static bool getJsonObject(const QJsonObject& object, QStringList& path, QJsonObject& value);
182 static bool getJsonObject(const QJsonObject& object, const QString& key, QJsonObject& value);
183 static bool getJsonArray (const QJsonObject& object, const QString& key, QJsonArray& value);
184
187 RecExtDataPage(QObject* parent, QJsonDocument doc) :
188 QObject(parent), m_doc(std::move(doc)) {};
189 protected:
190 QJsonDocument m_doc;
191};
192
194{
195 // Games status codes.
196 enum GameStatus : std::uint8_t {
197 TBD = 0,
200 FINAL = 3,
227 BYE = 39,
229 ESPNVOID = 41, // VOID makes win32 die horribly
240 };
241 static const QList<GameStatus> kFinalStatuses;
242
243 public:
244 RecExtEspnDataPage(QObject* parent, QJsonDocument doc) :
245 RecExtDataPage(parent, std::move(doc)) {};
246 bool findGameInfo(ActiveGame& game) override;
247 GameState findGameScore(ActiveGame& game) override;
248};
249
251{
252 public:
253 RecExtMlbDataPage(QObject* parent, QJsonDocument doc) :
254 RecExtDataPage(parent, std::move(doc)) {};
255 bool findGameInfo(ActiveGame& game) override;
256 GameState findGameScore(ActiveGame& game) override;
257 bool parseGameObject(const QJsonObject& gameObject, ActiveGame& game);
258};
259
260class RecExtDataSource : public QObject
261{
262 Q_OBJECT;
263
264 public:
266 { return qobject_cast<RecordingExtender*>(parent()); }
267 virtual RecExtDataPage* newPage(const QJsonDocument& doc) = 0;
268 static void clearCache();
269 virtual QUrl makeInfoUrl(const SportInfo& info, const QDateTime& dt) = 0;
270 virtual QUrl makeGameUrl(const ActiveGame& game, const QString& str) = 0;
271 virtual QUrl findInfoUrl(ActiveGame& game, SportInfo& info) = 0;
272 virtual RecExtDataPage* loadPage(const ActiveGame& game, const QUrl& _url) = 0;
273
274 protected:
275 explicit RecExtDataSource(QObject *parent) : QObject(parent) {};
276
277 QUrl m_url;
278 static QHash<QString,QJsonDocument> s_downloadedJson;
279};
280
282{
283 public:
284 explicit RecExtEspnDataSource(QObject *parent) : RecExtDataSource(parent) {};
285 RecExtDataPage* newPage(const QJsonDocument& doc) override
286 { return new RecExtEspnDataPage(this, doc); }
287 QUrl makeInfoUrl(const SportInfo& info, const QDateTime& dt) override;
288 QUrl makeGameUrl(const ActiveGame& game, const QString& str) override;
289 QUrl findInfoUrl(ActiveGame& game, SportInfo& info) override;
290 RecExtDataPage* loadPage(const ActiveGame& game, const QUrl& _url) override;
291};
292
294{
295 public:
296 explicit RecExtMlbDataSource(QObject *parent) : RecExtDataSource(parent) {}
297 RecExtDataPage* newPage(const QJsonDocument& doc) override
298 { return new RecExtMlbDataPage(this, doc); }
299 QUrl makeInfoUrl(const SportInfo& info, const QDateTime& dt) override;
300 QUrl makeGameUrl(const ActiveGame& game, const QString& str) override;
301 QUrl findInfoUrl(ActiveGame& game, SportInfo& info) override;
302 RecExtDataPage* loadPage(const ActiveGame& game, const QUrl& _url) override;
303};
304
305//
306//
307//
308class RecordingExtender : public QObject, public MThread
309{
310 Q_OBJECT;
312
313 public:
314 static void create(Scheduler *scheduler, RecordingInfo& ri);
315 void run(void) override; // MThread
316 ~RecordingExtender() override;
317 static void nameCleanup(const SportInfo& info, QString& name1, QString& name2);
318
319 private:
320 RecordingExtender () : MThread("RecordingExtender") {}
321
322 void addNewRecording(int recordedID);
323
324 // Get provider information from database.
325 bool findKnownSport(const QString& _title,
327 SportInfoList& info) const;
328 static void clearDownloadedInfo();
329
330 // Process recordings
332 static bool parseProgramInfo(const QString& subtitle, const QString& description,
333 QString& team1, QString& team2);
334 static QString ruleIdAsString(const RecordingRule *rr);
335 static void finishRecording(const RecordingInfo* ri, RecordingRule *rr, const ActiveGame& game);
336 void extendRecording(const RecordingInfo* ri, RecordingRule *rr, const ActiveGame& game);
337 static void unchangedRecording(const RecordingInfo* ri, RecordingRule *rr, const ActiveGame& game);
340 void checkDone();
341
342 // Cleanup
343 static void nameCleanup(const SportInfo& info, QString& name);
344 void expireOverrides();
345
350 static QMutex s_createLock;
352 bool m_running {true};
360 QList<int> m_newRecordings;
362 QList<ActiveGame> m_activeGames;
364 QList<int> m_overrideRules;
365
368};
369
370#endif // RECORDING_EXTENDER_H_
void setTeams(QString team1, QString team2)
ActiveGame(int recordedid, QString title, SportInfo info)
QString m_team1Normalized
void setGameUrl(QUrl url)
Set the game status information URL.
void setTeamsNorm(QString team1, QString team2)
QUrl getGameUrl() const
QDateTime getStartTime() const
QString getTitle() const
void setInfo(const SportInfo &info)
QString getTeam2Norm() const
QString getTeam1Norm() const
QString getTeam2() const
void setAbbrevs(QStringList abbrevs)
QString getAbbrev2() const
QString getStartTimeAsString() const
void setAbbrev1(const QString &abbrev)
SportInfo m_info
bool teamsMatch(const QStringList &names, const QStringList &abbrevs) const
Do the supplied team names/abbrevs match this game.
QString m_team2Normalized
int getRecordedId() const
QString getTeam1() const
void setInfoUrl(QUrl url)
Set the game scheduling information URL.
bool isValid() const
QString getAbbrev1() const
void setStartTime(const QDateTime &time)
ActiveGame(int recordedid, QString title)
void setAbbrev2(const QString &abbrev)
SportInfo getInfo() const
QUrl getInfoUrl() const
QDateTime m_startTime
QString m_abbrev1
QString getAbbrev1()
bool match(const QString &team1, const QString &team2)
QString getAbbrev2()
QString m_textState
int getPeriod() const
GameState(const QString &n1, const QString &n2, const QString &a1, const QString &a2, int p, bool finished)
QString getTeam1()
GameState(const ActiveGame &game, int p, bool finished)
bool matchAbbrev(const QString &a, const QString &b)
GameState()=default
QString m_abbrev2
QString getTextState()
bool isFinished() const
void setTextState(QString text)
QString getTeam2()
bool matchName(const QString &a, const QString &b)
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:49
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.
virtual GameState findGameScore(ActiveGame &game)=0
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.
RecExtDataPage(QObject *parent, QJsonDocument doc)
Create a new RecExtDataPage object.
QJsonDocument getDoc()
Get the JSON document associated with this object.
QJsonDocument m_doc
RecExtDataSource * getSource()
virtual QUrl makeInfoUrl(const SportInfo &info, const QDateTime &dt)=0
static void clearCache()
Clear the downloaded document cache.
RecordingExtender * getExtender()
RecExtDataSource(QObject *parent)
virtual RecExtDataPage * loadPage(const ActiveGame &game, const QUrl &_url)=0
virtual QUrl findInfoUrl(ActiveGame &game, SportInfo &info)=0
virtual RecExtDataPage * newPage(const QJsonDocument &doc)=0
virtual QUrl makeGameUrl(const ActiveGame &game, const QString &str)=0
static QHash< QString, QJsonDocument > s_downloadedJson
A cache of downloaded documents.
RecExtEspnDataPage(QObject *parent, QJsonDocument doc)
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
RecExtEspnDataSource(QObject *parent)
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.
RecExtMlbDataPage(QObject *parent, QJsonDocument doc)
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.
RecExtMlbDataSource(QObject *parent)
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.
friend class TestRecordingExtender
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...
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.
Definition: recordinginfo.h:36
Internal representation of a recording rule, mirrors the record table.
Definition: recordingrule.h:30
unsigned int uint
Definition: freesurround.h:24
@ ISODate
Default UTC.
Definition: mythdate.h:17
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
dictionary info
Definition: azlyrics.py:7
STL namespace.
QList< SportInfo > SportInfoList
AutoExtendType
QString league
AutoExtendType dataProvider
QString showTitle