MythTV master
netgrabbermanager.cpp
Go to the documentation of this file.
1#include "netgrabbermanager.h"
2
3// qt
4#include <QCoreApplication>
5#include <QDir>
6#include <QFile>
7#include <QString>
8#include <utility>
9
11#include "libmythbase/mythcorecontext.h" // for GetDurSetting TODO: excise database from MythCoreContext
16
17#include "netutils.h"
18
19#define LOC QString("NetContent: ")
20
21// ---------------------------------------------------
22
24{
25 wait();
26}
27
29{
30 RunProlog();
31 QMutexLocker locker(&m_lock);
32
33 QString commandline = m_commandline;
34 MythSystemLegacy getTree(commandline, QStringList("-T"),
36 getTree.Run(15min);
37 uint status = getTree.Wait();
38
39 if( status == GENERIC_EXIT_CMD_NOT_FOUND )
40 {
41 LOG(VB_GENERAL, LOG_ERR, LOC +
42 QString("Internet Content Source %1 cannot run, file missing.")
43 .arg(m_title));
44 }
45 else if( status == GENERIC_EXIT_OK )
46 {
47 LOG(VB_GENERAL, LOG_INFO, LOC +
48 QString("Internet Content Source %1 completed download, "
49 "beginning processing...").arg(m_title));
50
51 QByteArray result = getTree.ReadAll();
52
53 QDomDocument domDoc;
54#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
55 domDoc.setContent(result, true);
56#else
57 domDoc.setContent(result, QDomDocument::ParseOption::UseNamespaceProcessing);
58#endif
59 QDomElement root = domDoc.documentElement();
60 QDomElement channel = root.firstChildElement("channel");
61
63
64 while (!channel.isNull())
65 {
66 parseDBTree(m_title, QString(), QString(), channel, GetType());
67 channel = channel.nextSiblingElement("channel");
68 }
70 LOG(VB_GENERAL, LOG_INFO, LOC +
71 QString("Internet Content Source %1 completed processing, "
72 "marking as updated.").arg(m_title));
73 }
74 else
75 {
76 LOG(VB_GENERAL, LOG_ERR, LOC +
77 QString("Internet Content Source %1 crashed while grabbing tree.")
78 .arg(m_title));
79 }
80
81 // NOLINTNEXTLINE(readability-misleading-indentation)
82 emit finished();
83 RunEpilog();
84}
85
86void GrabberScript::parseDBTree(const QString &feedtitle, const QString &path,
87 const QString &pathThumb, QDomElement& domElem,
88 const ArticleType type)
89{
90 QMutexLocker locker(&m_lock);
91
92 Parse parse;
94
95 // File Handling
96 QDomElement fileitem = domElem.firstChildElement("item");
97 while (!fileitem.isNull())
98 { // Fill the article list...
99 articles.append(Parse::ParseItem(fileitem));
100 fileitem = fileitem.nextSiblingElement("item");
101 }
102
103 while (!articles.isEmpty())
104 { // Insert the articles in the DB...
105 insertTreeArticleInDB(feedtitle, path,
106 pathThumb, articles.takeFirst(), type);
107 }
108
109 // Directory Handling
110 QDomElement diritem = domElem.firstChildElement("directory");
111 while (!diritem.isNull())
112 {
113 QDomElement subfolder = diritem;
114 QString dirname = diritem.attribute("name");
115 QString dirthumb = diritem.attribute("thumbnail");
116 dirname.replace("/", "|");
117 QString pathToUse;
118
119 if (path.isEmpty())
120 pathToUse = dirname;
121 else
122 pathToUse = QString("%1/%2").arg(path, dirname);
123
124 parseDBTree(feedtitle,
125 pathToUse,
126 dirthumb,
127 subfolder,
128 type);
129 diritem = diritem.nextSiblingElement("directory");
130 }
131}
132
134 : m_timer(new QTimer()),
135 m_updateFreq(gCoreContext->GetDurSetting<std::chrono::hours>("netsite.updateFreq", 24h))
136{
137 connect( m_timer, &QTimer::timeout,
139}
140
142{
143 delete m_timer;
144}
145
147{
148 m_timer->start(m_updateFreq);
149}
150
152{
153 m_timer->stop();
154}
155
157{
158 auto *gdt = new GrabberDownloadThread(this);
159 if (m_refreshAll)
160 gdt->refreshAll();
161 gdt->start(QThread::LowPriority);
162
163 m_timer->start(m_updateFreq);
164}
165
167{
168 QMutexLocker locker(&m_lock);
169 doUpdate();
170}
171
173{
174 m_refreshAll = true;
175}
176
178 MThread("GrabberDownload"),
179 m_parent(parent)
180{
181}
182
184{
185 cancel();
186 wait();
187}
188
190{
191 m_mutex.lock();
192 qDeleteAll(m_scripts);
193 m_scripts.clear();
194 m_mutex.unlock();
195}
196
198{
199 m_mutex.lock();
200 m_refreshAll = true;
201 if (!isRunning())
202 start();
203 m_mutex.unlock();
204}
205
207{
208 RunProlog();
209
211 auto updateFreq = gCoreContext->GetDurSetting<std::chrono::hours>(
212 "netsite.updateFreq", 24h);
213
214 while (!m_scripts.isEmpty())
215 {
216 GrabberScript *script = m_scripts.takeFirst();
217 if (script && (needsUpdate(script, updateFreq) || m_refreshAll))
218 {
219 LOG(VB_GENERAL, LOG_INFO, LOC +
220 QString("Internet Content Source %1 Updating...")
221 .arg(script->GetTitle()));
222 script->run();
223 }
224 delete script;
225 }
226 emit finished();
227 if (m_parent)
228 QCoreApplication::postEvent(m_parent, new GrabberUpdateEvent());
229
230 RunEpilog();
231}
232
234{
235 m_videoList.clear();
236}
237
239{
240 resetSearch();
241
242 delete m_searchProcess;
243 m_searchProcess = nullptr;
244}
245
246
247void Search::executeSearch(const QString &script, const QString &query,
248 const QString &pagenum)
249{
250 resetSearch();
251
252 LOG(VB_GENERAL, LOG_DEBUG, "Search::executeSearch");
254
256 this, qOverload<>(&Search::slotProcessSearchExit));
258 this, qOverload<uint>(&Search::slotProcessSearchExit));
259
260 const QString& cmd = script;
261
262 QStringList args;
263
264 if (!pagenum.isEmpty())
265 {
266 args.append(QString("-p"));
267 args.append(pagenum);
268 }
269
270 args.append("-S");
271 const QString& term = query;
273
274 LOG(VB_GENERAL, LOG_INFO, LOC +
275 QString("Internet Search Query: %1 %2").arg(cmd, args.join(" ")));
276
278 m_searchProcess->SetCommand(cmd, args, flags);
279 m_searchProcess->Run(40s);
280}
281
283{
284 qDeleteAll(m_videoList);
285 m_videoList.clear();
286}
287
289{
290 Parse parse;
292
293 QDomNodeList entries = m_document.elementsByTagName("channel");
294
295 if (entries.count() == 0)
296 {
297 m_numResults = 0;
298 m_numReturned = 0;
299 m_numIndex = 0;
300 return;
301 }
302
303 QDomNode itemNode = entries.item(0);
304
305 QDomNode Node = itemNode.namedItem(QString("numresults"));
306 if (!Node.isNull())
307 {
308 m_numResults = Node.toElement().text().toUInt();
309 }
310 else
311 {
312 QDomNodeList count = m_document.elementsByTagName("item");
313
314 if (count.count() == 0)
315 m_numResults = 0;
316 else
317 m_numResults = count.count();
318 }
319
320 Node = itemNode.namedItem(QString("returned"));
321 if (!Node.isNull())
322 {
323 m_numReturned = Node.toElement().text().toUInt();
324 }
325 else
326 {
327 QDomNodeList items = m_document.elementsByTagName("item");
328
329 if (items.count() == 0)
330 m_numReturned = 0;
331 else
332 m_numReturned = items.count();
333 }
334
335 Node = itemNode.namedItem(QString("startindex"));
336 if (!Node.isNull())
337 {
338 m_numIndex = Node.toElement().text().toUInt();
339 }
340 else
341 {
342 m_numIndex = 0;
343 }
344
345 Node = itemNode.namedItem(QString("nextpagetoken"));
346 if (!Node.isNull())
347 {
348 m_nextPageToken = Node.toElement().text();
349 }
350 else
351 {
352 m_nextPageToken = "";
353 }
354
355 Node = itemNode.namedItem(QString("prevpagetoken"));
356 if (!Node.isNull())
357 {
358 m_prevPageToken = Node.toElement().text();
359 }
360 else
361 {
362 m_prevPageToken = "";
363 }
364}
365
367{
368 if (exitcode == GENERIC_EXIT_TIMEOUT)
369 {
370 LOG(VB_GENERAL, LOG_WARNING, LOC + "Internet Search Timeout");
371
372 if (m_searchProcess)
373 {
374 m_searchProcess->Term(true);
375 m_searchProcess->deleteLater();
376 m_searchProcess = nullptr;
377 }
378 emit searchTimedOut(this);
379 return;
380 }
381
382 if (exitcode != GENERIC_EXIT_OK)
383 {
384 m_document.setContent(QString());
385 }
386 else
387 {
388 LOG(VB_GENERAL, LOG_INFO, LOC +
389 "Internet Search Successfully Completed");
390
392#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
393 m_document.setContent(m_data, true);
394#else
395 m_document.setContent(m_data, QDomDocument::ParseOption::UseNamespaceProcessing);
396#endif
397 }
398
399 m_searchProcess->deleteLater();
400 m_searchProcess = nullptr;
401 emit finishedSearch(this);
402}
403
405{
407}
408
409void Search::SetData(QByteArray data)
410{
411 m_data = std::move(data);
412#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
413 m_document.setContent(m_data, true);
414#else
415 m_document.setContent(m_data, QDomDocument::ParseOption::UseNamespaceProcessing);
416#endif
417}
void run() override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
QList< GrabberScript * > m_scripts
GrabberDownloadThread(QObject *parent)
QRecursiveMutex m_lock
std::chrono::hours m_updateFreq
~GrabberManager() override
~GrabberScript() override
const ArticleType & GetType() const
void parseDBTree(const QString &feedtitle, const QString &path, const QString &pathThumb, QDomElement &domElem, ArticleType type)
QRecursiveMutex m_lock
void finished(void)
const QString & GetTitle() const
void run(void) override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:49
bool isRunning(void) const
Definition: mthread.cpp:263
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:196
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:283
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:209
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:300
std::enable_if_t< std::chrono::__is_duration< T >::value, T > GetDurSetting(const QString &key, T defaultval=T::zero())
void SetCommand(const QString &command, uint flags)
Resets an existing MythSystemLegacy object to a new command.
uint Wait(std::chrono::seconds timeout=0s)
static QString ShellEscape(const QString &in)
void error(uint status)
void finished(void)
void Term(bool force=false)
void Run(std::chrono::seconds timeout=0s)
Runs a command inside the /bin/sh shell. Returns immediately.
QByteArray & ReadAll()
static ResultItem * ParseItem(const QDomElement &item)
Definition: rssparse.cpp:734
static ResultItem::resultList parseRSS(const QDomDocument &domDoc)
Definition: rssparse.cpp:711
QList< ResultItem * > resultList
Definition: rssparse.h:114
void SetData(QByteArray data)
QByteArray m_data
void resetSearch(void)
~Search() override
void executeSearch(const QString &script, const QString &query, const QString &pagenum="")
QString m_nextPageToken
void finishedSearch(Search *item)
QDomDocument m_document
uint m_numResults
void process(void)
QString m_prevPageToken
MythSystemLegacy * m_searchProcess
void slotProcessSearchExit(void)
ResultItem::resultList m_videoList
uint m_numReturned
void searchTimedOut(Search *item)
unsigned int uint
Definition: compat.h:68
@ GENERIC_EXIT_CMD_NOT_FOUND
Command not found.
Definition: exitcodes.h:15
@ GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:13
@ GENERIC_EXIT_TIMEOUT
Process timed out.
Definition: exitcodes.h:27
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
GrabberDownloadThread * gdt
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
@ kMSStdOut
allow access to stdout
Definition: mythsystem.h:41
@ kMSRunShell
run process through shell
Definition: mythsystem.h:43
@ kMSRunBackground
run child in the background
Definition: mythsystem.h:38
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
STL namespace.
#define LOC
bool markTreeUpdated(GrabberScript *script, const QDateTime &curTime)
Definition: netutils.cpp:314
bool needsUpdate(GrabberScript *script, std::chrono::hours updateFreq)
Definition: netutils.cpp:330
bool insertTreeArticleInDB(const QString &feedtitle, const QString &path, const QString &paththumb, ResultItem *item, ArticleType type)
Definition: netutils.cpp:399
bool clearTreeItems(const QString &feedcommand)
Definition: netutils.cpp:360
GrabberScript::scriptList findAllDBTreeGrabbers()
Definition: netutils.cpp:115
ArticleType
Definition: rssparse.h:20