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 {
78 *reply = QJsonDocument::fromJson(response->readAll(), &parseError);
79
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
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);
185
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
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
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;
256 QByteArray data;
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
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} };
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));
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
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
347 }
348
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(StandardShortcut id)
const QList< QKeySequence > & 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()
qsizetype count() const const
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)
QNetworkReply * deleteResource(const QNetworkRequest &request)
QNetworkReply * get(const QNetworkRequest &request)
QNetworkReply * post(const QNetworkRequest &request, QHttpMultiPart *multiPart)
QNetworkReply * put(const QNetworkRequest &request, QHttpMultiPart *multiPart)
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
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 Tue Mar 26 2024 11:19:03 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.