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["name"].toString().toLatin1();
106 auto value = oneOption["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 QtConcurrent::run(this, &Cloud::dispatch, data, uuid);
138}
139
140///////////////////////////////////////////////////////////////////////////////////////////
141///
142///////////////////////////////////////////////////////////////////////////////////////////
143void Cloud::dispatch(const QSharedPointer<FITSData> &data, const QString &uuid)
144{
145 // Send complete metadata
146 // Add file name and size
147 QJsonObject metadata;
148 // Skip empty or useless metadata
149 for (const auto &oneRecord : data->getRecords())
150 {
151 if (oneRecord.key.isEmpty() || oneRecord.value.toString().isEmpty())
152 continue;
153 metadata.insert(oneRecord.key.toLower(), QJsonValue::fromVariant(oneRecord.value));
154 }
155
156 // Filename only without path
157 QString filepath = data->filename();
158 QString filenameOnly = QFileInfo(filepath).fileName();
159
160 // Add filename and size as wells
161 metadata.insert("uuid", uuid);
162 metadata.insert("filename", filenameOnly);
163 metadata.insert("filesize", static_cast<int>(data->size()));
164 // Must set Content-Disposition so
165 metadata.insert("Content-Disposition", QString("attachment;filename=%1.fz").arg(filenameOnly));
166
167 QByteArray image;
169 meta = meta.leftJustified(METADATA_PACKET, 0);
170 image += meta;
171
172 QString compressedFile = QDir::tempPath() + QString("/ekoslivecloud%1").arg(uuid);
173 data->saveImage(compressedFile + QStringLiteral("[compress R]"));
174 // Upload the compressed image
175 QFile compressedImage(compressedFile);
176 if (compressedImage.open(QIODevice::ReadOnly))
177 {
178 image += compressedImage.readAll();
179 emit newImage(image);
180 qCInfo(KSTARS_EKOS) << "Uploaded" << compressedFile << " to the cloud";
181 }
182
183 // Remove from disk if temporary
184 if (compressedFile != filepath && compressedFile.startsWith(QDir::tempPath()))
185 QFile::remove(compressedFile);
186}
187
188///////////////////////////////////////////////////////////////////////////////////////////
189///
190///////////////////////////////////////////////////////////////////////////////////////////
191void Cloud::uploadImage(const QByteArray &image)
192{
193 for (auto &nodeManager : m_NodeManagers)
194 {
195 if (nodeManager->cloud() == nullptr)
196 continue;
197
198 nodeManager->cloud()->sendBinaryMessage(image);
199 }
200}
201
202///////////////////////////////////////////////////////////////////////////////////////////
203///
204///////////////////////////////////////////////////////////////////////////////////////////
205void Cloud::updateOptions()
206{
207 // In case cloud storage is toggled, inform cloud
208 // websocket channel of this change.
209 QJsonObject payload = {{"name", "ekosLiveCloud"}, {"value", Options::ekosLiveCloud()}};
210 QJsonObject message =
211 {
212 {"type", commands[OPTION_SET]},
213 {"payload", payload}
214 };
215
216 for (auto &nodeManager : m_NodeManagers)
217 {
218 if (nodeManager->cloud() == nullptr)
219 continue;
220
221 nodeManager->cloud()->sendTextMessage(QJsonDocument(message).toJson(QJsonDocument::Compact));
222 }
223}
224
225}
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 Sep 6 2024 11:56:57 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.