Kstars

indiwebmanager.cpp
1 /*
2  SPDX-FileCopyrightText: 2016 Jasem Mutlaq <[email protected]>
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 
23 namespace INDI
24 {
25 
26 namespace WebManager
27 {
28 bool getWebManagerResponse(QNetworkAccessManager::Operation operation, const QUrl &url, QJsonDocument *reply,
29  QByteArray *data)
30 {
31  QNetworkAccessManager manager;
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 
96 bool isOnline(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 
135 bool checkVersion(ProfileInfo *pi)
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 
152 bool syncCustomDrivers(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  DriverInfo *driver = DriverManager::Instance()->findDriverByName(name);
163 
164  if (driver == nullptr)
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 
193 bool areDriversRunning(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  DriverInfo *driver = DriverManager::Instance()->findDriverByName(name);
211 
212  if (driver == nullptr)
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 
251 bool syncProfile(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  {
292  DriverInfo *guiderInfo = nullptr;
293  if ((guiderInfo = DriverManager::Instance()->findDriverByName(pi->drivers["Guider"])) == nullptr)
294  {
295  if ((guiderInfo = DriverManager::Instance()->findDriverByLabel(pi->drivers["Guider"])) == nullptr)
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 
355 bool startProfile(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 
375 bool stopProfile(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 
382 bool restartDriver(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
390 namespace AsyncWebManager
391 {
392 
393 QFuture<bool> isOnline(ProfileInfo *pi)
394 {
395  return QtConcurrent::run(WebManager::isOnline, pi);
396 }
397 
398 QFuture<bool> isStellarMate(ProfileInfo *pi)
399 {
400  return QtConcurrent::run(WebManager::checkVersion, pi);
401 }
402 
403 QFuture<bool> syncCustomDrivers(ProfileInfo *pi)
404 {
405  return QtConcurrent::run(WebManager::syncCustomDrivers, pi);
406 }
407 
408 QFuture<bool> areDriversRunning(ProfileInfo *pi)
409 {
410  return QtConcurrent::run(WebManager::areDriversRunning, pi);
411 }
412 
413 QFuture<bool> syncProfile(ProfileInfo *pi)
414 {
415  return QtConcurrent::run(WebManager::syncProfile, pi);
416 }
417 
418 QFuture<bool> startProfile(ProfileInfo *pi)
419 {
420  return QtConcurrent::run(WebManager::startProfile, pi);
421 }
422 
423 QFuture<bool> stopProfile(ProfileInfo *pi)
424 {
425  return QtConcurrent::run(WebManager::stopProfile, pi);
426 }
427 }
428 
429 }
QString errorString() const const
QJsonObject object() const const
QFuture< T > run(Function function,...)
bool isActive() const const
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
void setSingleShot(bool singleShot)
int count(const T &value) const const
QNetworkReply::NetworkError error() const const
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
QJsonObject fromVariantMap(const QVariantMap &map)
QByteArray number(int n, int base)
NETWORKMANAGERQT_EXPORT bool checkVersion(const int x, const int y, const int z)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
KCOREADDONS_EXPORT unsigned int version()
QNetworkReply * get(const QNetworkRequest &request)
void start(int msec)
QString i18n(const char *text, const TYPE &arg...)
QNetworkReply * post(const QNetworkRequest &request, QIODevice *data)
void quit()
QNetworkReply * put(const QNetworkRequest &request, QIODevice *data)
void timeout()
bool isEmpty() const const
QJsonArray::iterator begin()
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
QString errorString() const const
QJsonArray::iterator end()
void setRawHeader(const QByteArray &headerName, const QByteArray &headerValue)
const QList< QKeySequence > & quit()
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
bool isEmpty() const const
QString name(StandardShortcut id)
int count() const const
QJsonArray array() const const
void setUrl(const QUrl &url)
QByteArray toJson() const const
QList::iterator begin()
int size() const const
QByteArray readAll()
void append(const QJsonValue &value)
QList::iterator end()
QNetworkReply * deleteResource(const QNetworkRequest &request)
bool contains(const QJsonValue &value) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Mon Aug 8 2022 04:13:21 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.