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 QString errormsg;
62 QDomDocument doc;
63#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
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 {
146 bool ok = false;
147 if (progress = dom.text().toFloat(&ok); !ok)
148 progress = -1.0F;
149 }
150 else
151 {
152 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Unknown element: %1")
153 .arg(tagname));
154 }
155 }
156 node = node.nextSibling();
157 }
158
159 if (!msg.isEmpty() || !image.isEmpty() || !extra.isEmpty())
160 {
161 LOG(VB_GENERAL, LOG_INFO, QString("Received %1 '%2', timeout %3")
162 .arg(notification ? "notification" : "message",
163 msg, QString::number(timeout.count())));
164 if (timeout > 1000s)
165 timeout = notification ? 5s : 0s;
166 if (notification)
167 {
168 origin = origin.isEmpty() ? tr("UDP Listener") : origin;
171 msg, origin, description, image, extra,
172 progress_text, progress, timeout,
173 fullscreen, static_cast<VNMask>(visibility));
174 }
175 else
176 {
177 QStringList args(QString::number(timeout.count()));
178 qApp->postEvent(GetMythMainWindow(), new MythEvent(MythEvent::kMythUserMessage, msg, args));
179 }
180 }
181}
182
184{
186 {
187 emit Instance().m_listener->EnableUDPListener(Enable);
188 }
189 else
190 {
191 LOG(VB_GENERAL, LOG_ERR, LOC +
192 "EnableUDPListener called after MythUDPListener instance is deleted");
193 }
194}
195
197{
198 static MythUDP s_instance;
199 return s_instance;
200}
201
203 : m_listener(new MythUDPListener),
204 m_thread(new MThread("UDP"))
205{
206 m_listener->moveToThread(m_thread->qthread());
207 m_thread->start();
208 while (!m_thread->qthread()->isRunning())
209 {
210 std::this_thread::sleep_for(5us);
211 }
212}
213
215{
216 if (m_thread)
217 {
218 m_thread->quit();
219 m_thread->wait();
220 }
221 delete m_thread;
222 delete m_listener;
223}
224
226{
227 if (Instance().m_thread)
228 {
231 delete Instance().m_thread;
232 Instance().m_thread = nullptr;
233 }
234
235 delete Instance().m_listener;
236 Instance().m_listener = nullptr;
237}
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:283
void quit(void)
calls exit(0)
Definition: mthread.cpp:295
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:300
QThread * qthread(void)
Returns the thread, this will always return the same pointer no matter how often you restart the thre...
Definition: mthread.cpp:233
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
bool progress
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