MythTV master
mythudplistener.cpp
Go to the documentation of this file.
1// Qt
2#include <QCoreApplication>
3#include <QDomDocument>
4#include <QHostAddress>
5
6// MythTV
9#include "mythmainwindow.h"
10#include "mythudplistener.h"
11
12// Std
13#include <thread>
14
15#define LOC QString("UDPListener: ")
16
18{
20}
21
23{
24 DoEnable(false);
25}
26
28{
29 if (Enable)
30 {
31 if (m_socketPool)
32 return;
33
34 LOG(VB_GENERAL, LOG_INFO, LOC + "Enabling");
35 m_socketPool = new ServerPool(this);
37 QList<QHostAddress> addrs = ServerPool::DefaultListen();
39 auto port = static_cast<uint16_t>(gCoreContext->GetNumSetting("UDPNotifyPort", 0));
40 if (!m_socketPool->bind(addrs, port, false))
41 {
42 delete m_socketPool;
43 m_socketPool = nullptr;
44 }
45 }
46 else
47 {
48 if (!m_socketPool)
49 return;
50
51 LOG(VB_GENERAL, LOG_INFO, LOC + "Disabling");
53 delete m_socketPool;
54 m_socketPool = nullptr;
55 }
56}
57
58void MythUDPListener::Process(const QByteArray& Buffer, const QHostAddress& /*Sender*/,
59 quint16 /*SenderPort*/)
60{
61 QDomDocument doc;
62#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
63 QString errormsg;
64 int line = 0;
65 int column = 0;
66 if (!doc.setContent(Buffer, false, &errormsg, &line, &column))
67 {
68 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Error parsing xml: Line: %1 Column: %2 Error: %3")
69 .arg(line).arg(column).arg(errormsg));
70 return;
71 }
72#else
73 auto parseResult = doc.setContent(Buffer);
74 if (!parseResult)
75 {
76 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Error parsing xml: Line: %1 Column: %2 Error: %3")
77 .arg(parseResult.errorLine).arg(parseResult.errorColumn).arg(parseResult.errorMessage));
78 return;
79 }
80#endif
81
82 auto element = doc.documentElement();
83 bool notification = false;
84 if (!element.isNull())
85 {
86 if (element.tagName() != "mythmessage" && element.tagName() != "mythnotification")
87 {
88 LOG(VB_GENERAL, LOG_ERR, LOC + "Unknown UDP packet (not <mythmessage> XML)");
89 return;
90 }
91
92 if (element.tagName() == "mythnotification")
93 notification = true;
94
95 if (auto version = element.attribute("version", ""); version.isEmpty())
96 {
97 LOG(VB_GENERAL, LOG_ERR, LOC + "<mythmessage> missing 'version' attribute");
98 return;
99 }
100 }
101
102 QString msg;
103 std::chrono::seconds timeout = 0s;
104 QString image;
105 QString origin;
106 QString description;
107 QString extra;
108 QString progress_text;
109 float progress = -1.0F;
110 bool fullscreen = false;
111 bool error = false;
112 int visibility = 0;
113 QString type = "normal";
114
115 auto node = element.firstChild();
116 while (!node.isNull())
117 {
118 auto dom = node.toElement();
119 if (!dom.isNull())
120 {
121 auto tagname = dom.tagName();
122 if (tagname == "text") {
123 msg = dom.text();
124 } else if (tagname == "timeout") {
125 timeout = std::chrono::seconds(dom.text().toUInt());
126 } else if (notification && tagname == "image") {
127 image = dom.text();
128 } else if (notification && tagname == "origin") {
129 origin = dom.text();
130 } else if (notification && tagname == "description") {
131 description = dom.text();
132 } else if (notification && tagname == "extra") {
133 extra = dom.text();
134 } else if (notification && tagname == "progress_text") {
135 progress_text = dom.text();
136 } else if (notification && tagname == "fullscreen") {
137 fullscreen = dom.text().toLower() == "true";
138 } else if (notification && tagname == "error") {
139 error = dom.text().toLower() == "true";
140 } else if (tagname == "visibility") {
141 visibility = dom.text().toInt();
142 } else if (tagname == "type") {
143 type = dom.text();
144 } else if (notification && tagname == "progress") {
145 bool ok = false;
146 if (progress = dom.text().toFloat(&ok); !ok)
147 progress = -1.0F;
148 } else {
149 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Unknown element: %1")
150 .arg(tagname));
151 }
152 }
153 node = node.nextSibling();
154 }
155
156 if (!msg.isEmpty() || !image.isEmpty() || !extra.isEmpty())
157 {
158 LOG(VB_GENERAL, LOG_INFO, QString("Received %1 '%2', timeout %3")
159 .arg(notification ? "notification" : "message",
160 msg, QString::number(timeout.count())));
161 if (timeout > 1000s)
162 timeout = notification ? 5s : 0s;
163 if (notification)
164 {
165 origin = origin.isEmpty() ? tr("UDP Listener") : origin;
168 msg, origin, description, image, extra,
169 progress_text, progress, timeout,
170 fullscreen, static_cast<VNMask>(visibility));
171 }
172 else
173 {
174 QStringList args(QString::number(timeout.count()));
175 qApp->postEvent(GetMythMainWindow(), new MythEvent(MythEvent::kMythUserMessage, msg, args));
176 }
177 }
178}
179
181{
183 {
184 emit Instance().m_listener->EnableUDPListener(Enable);
185 }
186 else
187 {
188 LOG(VB_GENERAL, LOG_ERR, LOC +
189 "EnableUDPListener called after MythUDPListener instance is deleted");
190 }
191}
192
194{
195 static MythUDP s_instance;
196 return s_instance;
197}
198
200 : m_listener(new MythUDPListener),
201 m_thread(new MThread("UDP"))
202{
203 m_listener->moveToThread(m_thread->qthread());
204 m_thread->start();
205 while (!m_thread->qthread()->isRunning())
206 {
207 std::this_thread::sleep_for(5us);
208 }
209}
210
212{
213 if (m_thread)
214 {
215 m_thread->quit();
216 m_thread->wait();
217 }
218 delete m_thread;
219 delete m_listener;
220}
221
223{
224 if (Instance().m_thread)
225 {
228 delete Instance().m_thread;
229 Instance().m_thread = nullptr;
230 }
231
232 delete Instance().m_listener;
233 Instance().m_listener = nullptr;
234}
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:49
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:267
void quit(void)
calls exit(0)
Definition: mthread.cpp:279
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:284
QThread * qthread(void)
Returns the thread, this will always return the same pointer no matter how often you restart the thre...
Definition: mthread.cpp:217
int GetNumSetting(const QString &key, int defaultval=0)
This class is used as a container for messages.
Definition: mythevent.h:17
static const Type kMythUserMessage
Definition: mythevent.h:80
static Type TypeFromString(const QString &Type)
static const Type kError
void DoEnable(bool Enable=true)
static void Process(const QByteArray &Buffer, const QHostAddress &, quint16)
ServerPool * m_socketPool
void EnableUDPListener(bool Enable=true)
~MythUDPListener() override
MThread * m_thread
static MythUDP & Instance()
MythUDPListener * m_listener
static void EnableUDPListener(bool Enable=true)
static void StopUDPListener()
Manages a collection of sockets listening on different ports.
Definition: serverpool.h:60
bool bind(QList< QHostAddress > addrs, quint16 port, bool requireall=true)
Definition: serverpool.cpp:495
void newDatagram(QByteArray, QHostAddress, quint16)
static QList< QHostAddress > DefaultListen(void)
Definition: serverpool.cpp:305
void close(void)
Definition: serverpool.cpp:374
static QList< QHostAddress > DefaultBroadcast(void)
Definition: serverpool.cpp:339
unsigned short uint16_t
Definition: iso6937tables.h:3
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythMainWindow * GetMythMainWindow(void)
unsigned int VNMask
void ShowNotification(const QString &msg, const QString &from, const QString &detail, const VNMask visibility, const MythNotification::Priority priority)
#define LOC
string version
Definition: giantbomb.py:185
def error(message)
Definition: smolt.py:409