Kstars

ekosliveclient.cpp
1 /*
2  SPDX-FileCopyrightText: 2018 Jasem Mutlaq <[email protected]>
3 
4  SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "Options.h"
8 
9 #include "ekosliveclient.h"
10 #include "ekos_debug.h"
11 #include "ekos/manager.h"
12 #include "ekos/capture/capture.h"
13 #include "ekos/mount/mount.h"
14 #include "ekos/focus/focus.h"
15 
16 #include "kspaths.h"
17 #include "kstarsdata.h"
18 #include "filedownloader.h"
19 #include "QProgressIndicator.h"
20 
21 #include "indi/indilistener.h"
22 #include "indi/indicamera.h"
23 #include "indi/indifilterwheel.h"
24 
25 #include <config-kstars.h>
26 
27 #ifdef HAVE_KEYCHAIN
28 #include <qt5keychain/keychain.h>
29 #endif
30 
31 #include <QJsonDocument>
32 #include <QJsonObject>
33 #include <QNetworkAccessManager>
34 #include <QNetworkReply>
35 
36 namespace EkosLive
37 {
38 Client::Client(Ekos::Manager *manager) : QDialog(manager), m_Manager(manager)
39 {
40  setupUi(this);
41 
42  connect(closeB, SIGNAL(clicked()), this, SLOT(close()));
43 
44  networkManager = new QNetworkAccessManager(this);
45  connect(networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(onResult(QNetworkReply*)));
46 
47  QPixmap im;
48  if (im.load(KSPaths::locate(QStandardPaths::AppLocalDataLocation, "ekoslive.png")))
49  leftBanner->setPixmap(im);
50 
51  pi = new QProgressIndicator(this);
52  bottomLayout->insertWidget(1, pi);
53 
54  connectionState->setPixmap(QIcon::fromTheme("state-offline").pixmap(QSize(64, 64)));
55 
56  username->setText(Options::ekosLiveUsername());
57  connect(username, &QLineEdit::editingFinished, [ = ]()
58  {
59  Options::setEkosLiveUsername(username->text());
60  });
61 
62  connect(connectB, &QPushButton::clicked, [ = ]()
63  {
64  if (m_isConnected)
65  disconnectAuthServer();
66  else
67  connectAuthServer();
68  });
69 
70  connect(password, &QLineEdit::returnPressed, [ = ]()
71  {
72  if (!m_isConnected)
73  connectAuthServer();
74  });
75 
76  rememberCredentialsCheck->setChecked(Options::rememberCredentials());
77  connect(rememberCredentialsCheck, &QCheckBox::toggled, [ = ](bool toggled)
78  {
79  Options::setRememberCredentials(toggled);
80  });
81  autoStartCheck->setChecked(Options::autoStartEkosLive());
82  connect(autoStartCheck, &QCheckBox::toggled, [ = ](bool toggled)
83  {
84  Options::setAutoStartEkosLive(toggled);
85  });
86 
87  m_serviceURL.setUrl("https://live.stellarmate.com");
88  m_wsURL.setUrl("wss://live.stellarmate.com");
89 
90  if (Options::ekosLiveOnline())
91  ekosLiveOnlineR->setChecked(true);
92  else
93  ekosLiveOfflineR->setChecked(true);
94 
95  connect(ekosLiveOnlineR, &QRadioButton::toggled, [&](bool toggled)
96  {
97  Options::setEkosLiveOnline(toggled);
98  if (toggled)
99  {
100  m_serviceURL.setUrl("https://live.stellarmate.com");
101  m_wsURL.setUrl("wss://live.stellarmate.com");
102  m_Message->setURL(m_wsURL);
103  m_Media->setURL(m_wsURL);
104  m_Cloud->setURL(m_wsURL);
105  }
106  else
107  {
108  m_serviceURL.setUrl("http://localhost:3000");
109  m_wsURL.setUrl("ws://localhost:3000");
110  m_Message->setURL(m_wsURL);
111  m_Media->setURL(m_wsURL);
112  m_Cloud->setURL(m_wsURL);
113  }
114  }
115  );
116 
117  if (Options::ekosLiveOnline() == false)
118  {
119  m_serviceURL.setUrl("http://localhost:3000");
120  m_wsURL.setUrl("ws://localhost:3000");
121  }
122 
123 #ifdef HAVE_KEYCHAIN
124  QKeychain::ReadPasswordJob *job = new QKeychain::ReadPasswordJob(QLatin1String("kstars"));
125  job->setAutoDelete(false);
126  job->setKey(QLatin1String("ekoslive"));
127  connect(job, &QKeychain::Job::finished, [&](QKeychain::Job * job)
128  {
129  if (job->error() == false)
130  {
131  //QJsonObject data = QJsonDocument::fromJson(dynamic_cast<QKeychain::ReadPasswordJob*>(job)->textData().toLatin1()).object();
132  //const QString usernameText = data["username"].toString();
133  //const QString passwordText = data["password"].toString();
134 
135  const auto passwordText = dynamic_cast<QKeychain::ReadPasswordJob*>(job)->textData().toLatin1();
136 
137  // Only set and attempt connection if the data is not empty
138  //if (usernameText.isEmpty() == false && passwordText.isEmpty() == false)
139  if (passwordText.isEmpty() == false && username->text().isEmpty() == false)
140  {
141  //username->setText(usernameText);
142  password->setText(passwordText);
143 
144  if (autoStartCheck->isChecked())
145  connectAuthServer();
146  }
147 
148  }
149  job->deleteLater();
150  });
151  job->start();
152 #endif
153 
154  m_Message = new Message(m_Manager);
155  m_Message->setURL(m_wsURL);
156  connect(m_Message, &Message::connected, this, &Client::onConnected);
157  connect(m_Message, &Message::disconnected, this, &Client::onDisconnected);
158  connect(m_Message, &Message::expired, [&]()
159  {
160  // If token expired, disconnect and reconnect again.
161  disconnectAuthServer();
162  connectAuthServer();
163  });
164 
165  m_Media = new Media(m_Manager);
166  connect(m_Message, &Message::optionsChanged, m_Media, &Media::setOptions);
167  m_Media->setURL(m_wsURL);
168 
169  m_Cloud = new Cloud(m_Manager);
170  connect(m_Message, &Message::optionsChanged, m_Cloud, &Cloud::setOptions);
171  m_Cloud->setURL(m_wsURL);
172 }
173 
174 Client::~Client()
175 {
176  m_Message->disconnectServer();
177  m_Media->disconnectServer();
178  m_Cloud->disconnectServer();
179 }
180 
181 void Client::onConnected()
182 {
183  pi->stopAnimation();
184 
185  m_isConnected = true;
186 
187  connectB->setText(i18n("Disconnect"));
188  connectionState->setPixmap(QIcon::fromTheme("state-ok").pixmap(QSize(64, 64)));
189 
190  if (rememberCredentialsCheck->isChecked())
191  {
192 #ifdef HAVE_KEYCHAIN
193  // QJsonObject credentials =
194  // {
195  // {"username", username->text()},
196  // {"password", password->text()}
197  // };
198 
199  QKeychain::WritePasswordJob *job = new QKeychain::WritePasswordJob(QLatin1String("kstars"));
200  job->setAutoDelete(true);
201  job->setKey(QLatin1String("ekoslive"));
202  //job->setTextData(QJsonDocument(credentials).toJson());
203  job->setTextData(password->text());
204  job->start();
205 #endif
206  }
207 }
208 
209 void Client::onDisconnected()
210 {
211  connectionState->setPixmap(QIcon::fromTheme("state-offline").pixmap(QSize(64, 64)));
212  m_isConnected = false;
213  connectB->setText(i18n("Connect"));
214 }
215 
216 void Client::connectAuthServer()
217 {
218  if (username->text().isEmpty() || password->text().isEmpty())
219  {
220  KSNotification::error(i18n("Username or password is missing."));
221  return;
222  }
223 
224  pi->startAnimation();
225  authenticate();
226 }
227 
228 void Client::disconnectAuthServer()
229 {
230  token.clear();
231 
232  m_Message->disconnectServer();
233  m_Media->disconnectServer();
234  m_Cloud->disconnectServer();
235 
236  modeLabel->setEnabled(true);
237  ekosLiveOnlineR->setEnabled(true);
238  ekosLiveOfflineR->setEnabled(true);
239 }
240 
241 void Client::authenticate()
242 {
243  QNetworkRequest request;
244  request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
245 
246  QUrl authURL(m_serviceURL);
247  authURL.setPath("/api/authenticate");
248 
249  request.setUrl(authURL);
250 
251  QJsonObject json = { {"username", username->text()},
252  {"password", password->text()}
253  };
254 
255  auto postData = QJsonDocument(json).toJson(QJsonDocument::Compact);
256 
257  networkManager->post(request, postData);
258 }
259 
260 void Client::onResult(QNetworkReply *reply)
261 {
262  if (reply->error() != QNetworkReply::NoError)
263  {
264  // If connection refused, retry up to 3 times
265  if (reply->error() == QNetworkReply::ConnectionRefusedError && m_AuthReconnectTries++ < RECONNECT_MAX_TRIES)
266  {
267  reply->deleteLater();
268  QTimer::singleShot(RECONNECT_INTERVAL, this, &Client::connectAuthServer);
269  return;
270  }
271 
272  m_AuthReconnectTries = 0;
273  pi->stopAnimation();
274  connectionState->setPixmap(QIcon::fromTheme("state-error").pixmap(QSize(64, 64)));
275  KSNotification::error(i18n("Error authentication with Ekos Live server: %1", reply->errorString()));
276  reply->deleteLater();
277  return;
278  }
279 
280  m_AuthReconnectTries = 0;
282  auto response = QJsonDocument::fromJson(reply->readAll(), &error);
283 
284  if (error.error != QJsonParseError::NoError)
285  {
286  pi->stopAnimation();
287  connectionState->setPixmap(QIcon::fromTheme("state-error").pixmap(QSize(64, 64)));
288  KSNotification::error(i18n("Error parsing server response: %1", error.errorString()));
289  reply->deleteLater();
290  return;
291  }
292 
293  authResponse = response.object();
294 
295  if (authResponse["success"].toBool() == false)
296  {
297  pi->stopAnimation();
298  connectionState->setPixmap(QIcon::fromTheme("state-error").pixmap(QSize(64, 64)));
299  KSNotification::error(authResponse["message"].toString());
300  reply->deleteLater();
301  return;
302  }
303 
304  token = authResponse["token"].toString();
305 
306  m_Message->setAuthResponse(authResponse);
307  m_Message->connectServer();
308 
309  m_Media->setAuthResponse(authResponse);
310  m_Media->connectServer();
311 
312  // If we are using EkosLive Offline
313  // We need to check for internet connection before we connect to the online web server
314  if (ekosLiveOnlineR->isChecked() || (ekosLiveOfflineR->isChecked() &&
315  networkManager->networkAccessible() == QNetworkAccessManager::Accessible))
316  {
317  m_Cloud->setAuthResponse(authResponse);
318  m_Cloud->connectServer();
319  }
320 
321  modeLabel->setEnabled(false);
322  ekosLiveOnlineR->setEnabled(false);
323  ekosLiveOfflineR->setEnabled(false);
324 
325  reply->deleteLater();
326 }
327 
328 void Client::setConnected(bool enabled)
329 {
330  // Return if there is no change.
331  if (enabled == m_isConnected)
332  return;
333 
334  connectB->click();
335 }
336 
337 void Client::setConfig(bool onlineService, bool rememberCredentials, bool autoConnect)
338 {
339  ekosLiveOnlineR->setChecked(onlineService);
340  ekosLiveOfflineR->setChecked(!onlineService);
341 
342  rememberCredentialsCheck->setChecked(rememberCredentials);
343 
344  autoStartCheck->setChecked(autoConnect);
345 }
346 
347 void Client::setUser(const QString &user, const QString &pass)
348 {
349  username->setText(user);
350  Options::setEkosLiveUsername(user);
351 
352  password->setText(pass);
353 }
354 
355 }
QString errorString() const const
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
void setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value)
QNetworkReply::NetworkError error() const const
void clicked(bool checked)
QIcon fromTheme(const QString &name)
void editingFinished()
void toggled(bool checked)
The QProgressIndicator class lets an application display a progress indicator to show that a long tas...
void deleteLater()
QString i18n(const char *text, const TYPE &arg...)
char * toString(const T &value)
void returnPressed()
QAction * close(const QObject *recvr, const char *slot, QObject *parent)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
bool load(const QString &fileName, const char *format, Qt::ImageConversionFlags flags)
Generic record interfaces and implementations.
Definition: cloud.cpp:23
void setUrl(const QUrl &url)
QByteArray toJson() const const
QByteArray readAll()
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Fri Aug 12 2022 04:00:53 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.