Kstars

indiwebmanager.cpp
1/*
2 SPDX-FileCopyrightText: 2016 Jasem Mutlaq <mutlaqja@ikarustech.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "indiwebmanager.h"
8
9#include "auxiliary/ksnotification.h"
10#include "driverinfo.h"
11#include "drivermanager.h"
12#include "Options.h"
13#include "profileinfo.h"
14
15#include <QJsonArray>
16#include <QJsonDocument>
17#include <QJsonObject>
18#include <QNetworkReply>
19#include <QtConcurrent>
20
21#include "ekos_debug.h"
22
23namespace INDI
24{
25
26namespace WebManager
27{
28bool getWebManagerResponse(QNetworkAccessManager::Operation operation, const QUrl &url, QJsonDocument *reply,
29 QByteArray *data)
30{
32 QNetworkReply *response = nullptr;
33 QNetworkRequest request;
34
35 request.setUrl(url);
36
37 if (data)
38 {
39 request.setRawHeader("Content-Type", "application/json");
40 request.setRawHeader("Content-Length", QByteArray::number(data->size()));
41 }
42
43 switch (operation)
44 {
46 response = manager.get(request);
47 break;
48
50 if (data)
51 response = manager.post(request, *data);
52 else
53 response = manager.post(request, QByteArray());
54 break;
55
57 response = manager.deleteResource(request);
58 break;
59
61 response = manager.put(request, *data);
62 break;
63
64 default:
65 return false;
66 }
67
68 // Wait synchronously
70 QObject::connect(response, SIGNAL(finished()), &event, SLOT(quit()));
71 event.exec();
72
73 if (response->error() == QNetworkReply::NoError)
74 {
75 if (reply)
76 {
77 QJsonParseError parseError;
78 *reply = QJsonDocument::fromJson(response->readAll(), &parseError);
79
80 if (parseError.error != QJsonParseError::NoError)
81 {
82 qDebug() << Q_FUNC_INFO << "INDI: JSon error during parsing " << parseError.errorString();
83 return false;
84 }
85 }
86
87 return true;
88 }
89 else
90 {
91 qDebug() << Q_FUNC_INFO << "INDI: Error communicating with INDI Web Manager: " << response->errorString();
92 return false;
93 }
94}
95
96bool isOnline(const QSharedPointer<ProfileInfo> &pi)
97{
98 QTimer timer;
99 timer.setSingleShot(true);
100 QNetworkAccessManager manager;
101 QUrl url(QString("http://%1:%2/api/server/status").arg(pi->host).arg(pi->INDIWebManagerPort));
102 QNetworkReply *response = manager.get(QNetworkRequest(url));
103
104 // Wait synchronously
107 QObject::connect(response, SIGNAL(finished()), &event, SLOT(quit()));
108 timer.start(3000);
109 event.exec();
110
111 if (timer.isActive() && response->error() == QNetworkReply::NoError)
112 return true;
113 // Fallback to default if DNS lookup fails for .local
114 else if (pi->host.contains(".local"))
115 {
116 QUrl url(QString("http://10.250.250.1:8624/api/server/status"));
117 QNetworkReply *response = manager.get(QNetworkRequest(url));
118 // Wait synchronously
121 QObject::connect(response, SIGNAL(finished()), &event, SLOT(quit()));
122 timer.start(3000);
123 event.exec();
124
125 if (timer.isActive() && response->error() == QNetworkReply::NoError)
126 {
127 pi->host = "10.250.250.1";
128 return true;
129 }
130 }
131
132 return false;
133}
134
136{
137 QNetworkAccessManager manager;
138 QUrl url(QString("http://%1:%2/api/info/version").arg(pi->host).arg(pi->INDIWebManagerPort));
139
140 QJsonDocument json;
141 if (getWebManagerResponse(QNetworkAccessManager::GetOperation, url, &json))
142 {
143 QJsonObject version = json.object();
144 if (version.contains("version") == false)
145 return false;
146 qInfo(KSTARS_EKOS) << "Detected Web Manager version" << version["version"].toString();
147 return true;
148 }
149 return false;
150}
151
152bool syncCustomDrivers(const QSharedPointer<ProfileInfo> &pi)
153{
154 QNetworkAccessManager manager;
155 QUrl url(QString("http://%1:%2/api/profiles/custom").arg(pi->host).arg(pi->INDIWebManagerPort));
156
157 QStringList customDriversLabels;
158 QMapIterator<QString, QString> i(pi->drivers);
159 while (i.hasNext())
160 {
161 QString name = i.next().value();
162 auto driver = DriverManager::Instance()->findDriverByName(name);
163
164 if (driver.isNull())
165 driver = DriverManager::Instance()->findDriverByLabel(name);
166 if (driver && driver->getDriverSource() == CUSTOM_SOURCE)
167 customDriversLabels << driver->getLabel();
168 }
169
170 // Search for locked filter by filter color name
171 const QList<QVariantMap> &customDrivers = DriverManager::Instance()->getCustomDrivers();
172
173 for (auto &label : customDriversLabels)
174 {
175 auto pos = std::find_if(customDrivers.begin(), customDrivers.end(), [label](QVariantMap oneDriver)
176 {
177 return (oneDriver["Label"] == label);
178 });
179
180 if (pos == customDrivers.end())
181 continue;
182
183 QVariantMap driver = (*pos);
184 QJsonObject jsonDriver = QJsonObject::fromVariantMap(driver);
185
186 QByteArray data = QJsonDocument(jsonDriver).toJson();
187 getWebManagerResponse(QNetworkAccessManager::PostOperation, url, nullptr, &data);
188 }
189
190 return true;
191}
192
193bool areDriversRunning(const QSharedPointer<ProfileInfo> &pi)
194{
195 QUrl url(QString("http://%1:%2/api/server/drivers").arg(pi->host).arg(pi->INDIWebManagerPort));
196 QJsonDocument json;
197
198 if (getWebManagerResponse(QNetworkAccessManager::GetOperation, url, &json))
199 {
200 QJsonArray array = json.array();
201
202 if (array.isEmpty())
203 return false;
204
205 QStringList piExecDrivers;
206 QMapIterator<QString, QString> i(pi->drivers);
207 while (i.hasNext())
208 {
209 QString name = i.next().value();
210 auto driver = DriverManager::Instance()->findDriverByName(name);
211
212 if (driver.isNull())
213 driver = DriverManager::Instance()->findDriverByLabel(name);
214 if (driver)
215 piExecDrivers << driver->getExecutable();
216 }
217
218 if (array.count() < piExecDrivers.count())
219 return false;
220
221 // Get all the drivers running remotely
222 QStringList webManagerDrivers;
223 for (auto value : array)
224 {
225 QJsonObject driver = value.toObject();
226 // Old Web Manager API API
227 QString exec = driver["driver"].toString();
228 if (exec.isEmpty())
229 // New v0.1.5+ Web Manager API
230 exec = driver["binary"].toString();
231 webManagerDrivers << exec;
232 }
233
234 // Make sure all the profile drivers are running there
235 for (auto &oneDriverExec : piExecDrivers)
236 {
237 if (webManagerDrivers.contains(oneDriverExec) == false)
238 {
239 KSNotification::error(i18n("Driver %1 failed to start on the remote INDI server.", oneDriverExec));
240 qCritical(KSTARS_EKOS) << "Driver" << oneDriverExec << "failed to start on the remote INDI server!";
241 return false;
242 }
243 }
244
245 return true;
246 }
247
248 return false;
249}
250
251bool syncProfile(const QSharedPointer<ProfileInfo> &pi)
252{
253 QUrl url;
254 QJsonDocument jsonDoc;
255 QJsonParseError jsonError;
256 QByteArray data;
257 QJsonArray profileScripts;
258
259 //Add Profile
260 url = QUrl(QString("http://%1:%2/api/profiles/%3").arg(pi->host).arg(pi->INDIWebManagerPort).arg(pi->name));
261 getWebManagerResponse(QNetworkAccessManager::PostOperation, url, nullptr);
262
263 // If we have scripts, let's try to parse and send them.
264 if (pi->scripts.isEmpty() == false)
265 {
266 auto doc = QJsonDocument::fromJson(pi->scripts, &jsonError);
267
268 if (jsonError.error == QJsonParseError::NoError)
269 {
270 profileScripts = doc.array();
271 // Update profile info
272 url = QUrl(QString("http://%1:%2/api/profiles/%3").arg(pi->host).arg(pi->INDIWebManagerPort).arg(pi->name));
273 QJsonObject profileObject{ { "port", pi->port }, {"scripts", profileScripts} };
274 jsonDoc = QJsonDocument(profileObject);
275 data = jsonDoc.toJson();
276 getWebManagerResponse(QNetworkAccessManager::PutOperation, url, nullptr, &data);
277 }
278 }
279
280 // Add drivers
281 url = QUrl(QString("http://%1:%2/api/profiles/%3/drivers").arg(pi->host).arg(pi->INDIWebManagerPort).arg(pi->name));
282 QJsonArray driverArray;
283 QMapIterator<QString, QString> i(pi->drivers);
284
285 // In case both Guider + CCD are Multiple-Devices-Per-Driver type
286 // Then we should not define guider as a separate driver since that would start the driver executable twice
287 // when we only need it once
288 if (pi->drivers.contains("Guider"))
289 {
290 if (pi->drivers["Guider"] == pi->drivers["CCD"])
291 {
293 if ((guiderInfo = DriverManager::Instance()->findDriverByName(pi->drivers["Guider"])).isNull())
294 {
295 if ((guiderInfo = DriverManager::Instance()->findDriverByLabel(pi->drivers["Guider"])).isNull())
296 {
297 guiderInfo = DriverManager::Instance()->findDriverByExec(pi->drivers["Guider"]);
298 }
299 }
300
301 if (guiderInfo && guiderInfo->getAuxInfo().value("mdpd", false).toBool())
302 {
303 pi->drivers.remove("Guider");
304 i = QMapIterator<QString, QString>(pi->drivers);
305 }
306 }
307 }
308
309 // Regular Drivers
310 while (i.hasNext())
311 driverArray.append(QJsonObject({{"label", i.next().value()}}));
312
313 // Remote Drivers
314 if (pi->remotedrivers.isEmpty() == false)
315 {
316 for (auto &remoteDriver : pi->remotedrivers.split(","))
317 {
318 driverArray.append(QJsonObject({{"remote", remoteDriver}}));
319 }
320 }
321
322 QJsonArray sortedList;
323 for (const auto &oneRule : qAsConst(profileScripts))
324 {
325 auto matchingDriver = std::find_if(driverArray.begin(), driverArray.end(), [oneRule](const auto & oneDriver)
326 {
327 return oneDriver.toObject()["label"].toString() == oneRule.toObject()["Driver"].toString();
328 });
329
330 if (matchingDriver != driverArray.end())
331 {
332 sortedList.append(*matchingDriver);
333 }
334 }
335
336 // If we have any profile scripts drivers, let's re-sort managed drivers
337 // so that profile script drivers
338 if (!sortedList.isEmpty())
339 {
340 for (const auto oneDriver : driverArray)
341 {
342 if (sortedList.contains(oneDriver) == false)
343 sortedList.append(oneDriver);
344 }
345
346 driverArray = sortedList;
347 }
348
349 data = QJsonDocument(driverArray).toJson();
350 getWebManagerResponse(QNetworkAccessManager::PostOperation, url, nullptr, &data);
351
352 return true;
353}
354
355bool startProfile(const QSharedPointer<ProfileInfo> &pi)
356{
357 // First make sure profile is created and synced on web manager
358 syncProfile(pi);
359
360 // Start profile
361 QUrl url(QString("http://%1:%2/api/server/start/%3").arg(pi->host).arg(pi->INDIWebManagerPort).arg(pi->name));
362 getWebManagerResponse(QNetworkAccessManager::PostOperation, url, nullptr);
363
364 // Make sure drivers are running
365 // Try up to 3 times
366 for (int i = 0; i < 3; i++)
367 {
368 if (areDriversRunning(pi))
369 return true;
370 }
371
372 return false;
373}
374
375bool stopProfile(const QSharedPointer<ProfileInfo> &pi)
376{
377 // Stop profile
378 QUrl url(QString("http://%1:%2/api/server/stop").arg(pi->host).arg(pi->INDIWebManagerPort));
379 return getWebManagerResponse(QNetworkAccessManager::PostOperation, url, nullptr);
380}
381
382bool restartDriver(const QSharedPointer<ProfileInfo> &pi, const QString &label)
383{
384 QUrl url(QString("http://%1:%2/api/drivers/restart/%3").arg(pi->host).arg(pi->INDIWebManagerPort).arg(label));
385 return getWebManagerResponse(QNetworkAccessManager::PostOperation, url, nullptr);
386}
387}
388
389// Async version of the Web Manager
390namespace AsyncWebManager
391{
392
394{
395 return QtConcurrent::run(WebManager::isOnline, pi);
396}
397
398QFuture<bool> isStellarMate(const QSharedPointer<ProfileInfo> &pi)
399{
400 return QtConcurrent::run(WebManager::checkVersion, pi);
401}
402
403QFuture<bool> syncCustomDrivers(const QSharedPointer<ProfileInfo> &pi)
404{
405 return QtConcurrent::run(WebManager::syncCustomDrivers, pi);
406}
407
408QFuture<bool> areDriversRunning(const QSharedPointer<ProfileInfo> &pi)
409{
410 return QtConcurrent::run(WebManager::areDriversRunning, pi);
411}
412
413QFuture<bool> syncProfile(const QSharedPointer<ProfileInfo> &pi)
414{
415 return QtConcurrent::run(WebManager::syncProfile, pi);
416}
417
418QFuture<bool> startProfile(const QSharedPointer<ProfileInfo> &pi)
419{
420 return QtConcurrent::run(WebManager::startProfile, pi);
421}
422
423QFuture<bool> stopProfile(const QSharedPointer<ProfileInfo> &pi)
424{
425 return QtConcurrent::run(WebManager::stopProfile, pi);
426}
427}
428
429}
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
KDB_EXPORT KDbVersionInfo version()
QString name(GameStandardAction id)
KGuiItem quit()
NETWORKMANAGERQT_EXPORT bool checkVersion(const int x, const int y, const int z)
QByteArray number(double n, char format, int precision)
qsizetype size() const const
void quit()
QString errorString() const const
QByteArray readAll()
void append(const QJsonValue &value)
iterator begin()
bool contains(const QJsonValue &value) const const
qsizetype count() const const
iterator end()
bool isEmpty() const const
QJsonArray array() const const
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QJsonObject object() const const
QByteArray toJson(JsonFormat format) const const
QJsonObject fromVariantMap(const QVariantMap &map)
QString errorString() const const
iterator begin()
qsizetype count() const const
iterator end()
QNetworkReply * deleteResource(const QNetworkRequest &request)
QNetworkReply * get(const QNetworkRequest &request)
QNetworkReply * post(const QNetworkRequest &request, QHttpMultiPart *multiPart)
QNetworkReply * put(const QNetworkRequest &request, QHttpMultiPart *multiPart)
NetworkError error() const const
void setRawHeader(const QByteArray &headerName, const QByteArray &headerValue)
void setUrl(const QUrl &url)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString arg(Args &&... args) const const
bool isEmpty() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QFuture< T > run(Function function,...)
void setSingleShot(bool singleShot)
void timeout()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 26 2024 11:59:52 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.