Kstars

cloud.cpp
1/*
2 SPDX-FileCopyrightText: 2018 Jasem Mutlaq <mutlaqja@ikarustech.com>
3
4 Cloud Channel
5
6 SPDX-License-Identifier: GPL-2.0-or-later
7*/
8
9#include "cloud.h"
10#include "commands.h"
11#include "fitsviewer/fitsdata.h"
12
13#include "ekos_debug.h"
14#include "version.h"
15#include "Options.h"
16
17#include <QtConcurrent>
18#include <QFutureWatcher>
19#include <KFormat>
20
21namespace EkosLive
22{
23
24Cloud::Cloud(Ekos::Manager * manager, QVector<QSharedPointer<NodeManager>> &nodeManagers):
25 m_Manager(manager), m_NodeManagers(nodeManagers)
26{
27 for (auto &nodeManager : m_NodeManagers)
28 {
29 if (nodeManager->cloud() == nullptr)
30 continue;
31
32 connect(nodeManager->cloud(), &Node::connected, this, &Cloud::onConnected);
33 connect(nodeManager->cloud(), &Node::disconnected, this, &Cloud::onDisconnected);
34 connect(nodeManager->cloud(), &Node::onTextReceived, this, &Cloud::onTextReceived);
35 }
36
37 connect(this, &Cloud::newImage, this, &Cloud::uploadImage);
38 connect(Options::self(), &Options::EkosLiveCloudChanged, this, &Cloud::updateOptions);
39}
40
41///////////////////////////////////////////////////////////////////////////////////////////
42///
43///////////////////////////////////////////////////////////////////////////////////////////
44bool Cloud::isConnected() const
45{
46 return std::any_of(m_NodeManagers.begin(), m_NodeManagers.end(), [](auto & nodeManager)
47 {
48 return nodeManager->cloud() && nodeManager->cloud()->isConnected();
49 });
50}
51
52///////////////////////////////////////////////////////////////////////////////////////////
53///
54///////////////////////////////////////////////////////////////////////////////////////////
55
56void Cloud::onConnected()
57{
58 auto node = qobject_cast<Node*>(sender());
59 if (!node)
60 return;
61
62 qCInfo(KSTARS_EKOS) << "Connected to Cloud Websocket server at" << node->url().toDisplayString();
63
64 emit connected();
65}
66
67///////////////////////////////////////////////////////////////////////////////////////////
68///
69///////////////////////////////////////////////////////////////////////////////////////////
70void Cloud::onDisconnected()
71{
72 qCInfo(KSTARS_EKOS) << "Disconnected from Cloud Websocket server.";
73 m_sendBlobs = true;
74
75 for (auto &oneFile : temporaryFiles)
76 QFile::remove(oneFile);
77 temporaryFiles.clear();
78
79 emit disconnected();
80}
81
82///////////////////////////////////////////////////////////////////////////////////////////
83///
84///////////////////////////////////////////////////////////////////////////////////////////
85void Cloud::onTextReceived(const QString &message)
86{
87 qCInfo(KSTARS_EKOS) << "Cloud Text Websocket Message" << message;
88 QJsonParseError error;
89 auto serverMessage = QJsonDocument::fromJson(message.toLatin1(), &error);
90 if (error.error != QJsonParseError::NoError)
91 {
92 qCWarning(KSTARS_EKOS) << "Ekos Live Parsing Error" << error.errorString();
93 return;
94 }
95
96 const QJsonObject payload = serverMessage.object();
97 const QString command = payload["type"].toString();
98 if (command == commands[SET_BLOBS])
99 m_sendBlobs = payload["payload"].toBool();
100 else if (command == commands[OPTION_SET])
101 {
102 const QJsonArray options = payload["options"].toArray();
103 for (const auto &oneOption : options)
104 {
105 auto name = oneOption[QStringLiteral("name")].toString().toLatin1();
106 auto value = oneOption[QStringLiteral("value")].toVariant();
107
108 Options::self()->setProperty(name, value);
109
110 // Special case
111 if (name == "ekosLiveCloud")
112 updateOptions();
113 }
114
115 Options::self()->save();
116 }
117 else if (command == commands[LOGOUT])
118 {
119 for (auto &nodeManager : m_NodeManagers)
120 {
121 if (nodeManager->cloud() == nullptr)
122 continue;
123
124 nodeManager->cloud()->disconnectServer();
125 }
126 }
127}
128
129///////////////////////////////////////////////////////////////////////////////////////////
130///
131///////////////////////////////////////////////////////////////////////////////////////////
132void Cloud::sendData(const QSharedPointer<FITSData> &data, const QString &uuid)
133{
134 if (Options::ekosLiveCloud() == false || m_sendBlobs == false)
135 return;
136
137#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
138 QtConcurrent::run(&Cloud::dispatch, this, data, uuid);
139#else
140 QtConcurrent::run(this, &Cloud::dispatch, data, uuid);
141#endif
142}
143
144///////////////////////////////////////////////////////////////////////////////////////////
145///
146///////////////////////////////////////////////////////////////////////////////////////////
147void Cloud::dispatch(const QSharedPointer<FITSData> &data, const QString &uuid)
148{
149 // Send complete metadata
150 // Add file name and size
151 QJsonObject metadata;
152 // Skip empty or useless metadata
153 for (const auto &oneRecord : data->getRecords())
154 {
155 if (oneRecord.key.isEmpty() || oneRecord.value.toString().isEmpty())
156 continue;
157 metadata.insert(oneRecord.key.toLower(), QJsonValue::fromVariant(oneRecord.value));
158 }
159
160 // Filename only without path
161 QString filepath = data->filename();
162 QString filenameOnly = QFileInfo(filepath).fileName();
163
164 // Add filename and size as wells
165 metadata.insert("uuid", uuid);
166 metadata.insert("filename", filenameOnly);
167 metadata.insert("filesize", static_cast<int>(data->size()));
168 // Must set Content-Disposition so
169 metadata.insert("Content-Disposition", QString("attachment;filename=%1.fz").arg(filenameOnly));
170
171 QByteArray image;
173 meta = meta.leftJustified(METADATA_PACKET, 0);
174 image += meta;
175
176 QString compressedFile = QDir::tempPath() + QString("/ekoslivecloud%1").arg(uuid);
177 data->saveImage(compressedFile + QStringLiteral("[compress R]"));
178 // Upload the compressed image
179 QFile compressedImage(compressedFile);
180 if (compressedImage.open(QIODevice::ReadOnly))
181 {
182 image += compressedImage.readAll();
183 emit newImage(image);
184 qCInfo(KSTARS_EKOS) << "Uploaded" << compressedFile << " to the cloud";
185 }
186
187 // Remove from disk if temporary
188 if (compressedFile != filepath && compressedFile.startsWith(QDir::tempPath()))
189 QFile::remove(compressedFile);
190}
191
192///////////////////////////////////////////////////////////////////////////////////////////
193///
194///////////////////////////////////////////////////////////////////////////////////////////
195void Cloud::uploadImage(const QByteArray &image)
196{
197 for (auto &nodeManager : m_NodeManagers)
198 {
199 if (nodeManager->cloud() == nullptr)
200 continue;
201
202 nodeManager->cloud()->sendBinaryMessage(image);
203 }
204}
205
206///////////////////////////////////////////////////////////////////////////////////////////
207///
208///////////////////////////////////////////////////////////////////////////////////////////
209void Cloud::updateOptions()
210{
211 // In case cloud storage is toggled, inform cloud
212 // websocket channel of this change.
213 QJsonObject payload = {{"name", "ekosLiveCloud"}, {"value", Options::ekosLiveCloud()}};
214 QJsonObject message =
215 {
216 {"type", commands[OPTION_SET]},
217 {"payload", payload}
218 };
219
220 for (auto &nodeManager : m_NodeManagers)
221 {
222 if (nodeManager->cloud() == nullptr)
223 continue;
224
225 nodeManager->cloud()->sendTextMessage(QJsonDocument(message).toJson(QJsonDocument::Compact));
226 }
227}
228
229}
Generic record interfaces and implementations.
Definition cloud.cpp:22
QByteArray leftJustified(qsizetype width, char fill, bool truncate) const const
QString tempPath()
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
bool remove()
QString fileName() const const
QByteArray readAll()
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QByteArray toJson(JsonFormat format) const const
iterator insert(QLatin1StringView key, const QJsonValue &value)
QJsonValue fromVariant(const QVariant &variant)
T qobject_cast(QObject *object)
QObject * sender() const const
QString arg(Args &&... args) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray toLatin1() const const
QFuture< T > run(Function function,...)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:15:11 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.