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 "../fitsviewer/fpack.h"
16#include "Options.h"
17
18#include <QtConcurrent>
19#include <QFutureWatcher>
20#include <KFormat>
21
22namespace EkosLive
23{
24
25Cloud::Cloud(Ekos::Manager * manager, QVector<QSharedPointer<NodeManager>> &nodeManagers):
26 m_Manager(manager), m_NodeManagers(nodeManagers)
27{
28 for (auto &nodeManager : m_NodeManagers)
29 {
30 if (nodeManager->cloud() == nullptr)
31 continue;
32
33 connect(nodeManager->cloud(), &Node::connected, this, &Cloud::onConnected);
34 connect(nodeManager->cloud(), &Node::disconnected, this, &Cloud::onDisconnected);
35 connect(nodeManager->cloud(), &Node::onTextReceived, this, &Cloud::onTextReceived);
36 }
37
38 connect(&watcher, &QFutureWatcher<bool>::finished, this, &Cloud::sendImage, Qt::UniqueConnection);
39 connect(this, &Cloud::newImage, this, &Cloud::uploadImage);
40 connect(Options::self(), &Options::EkosLiveCloudChanged, this, &Cloud::updateOptions);
41}
42
43///////////////////////////////////////////////////////////////////////////////////////////
44///
45///////////////////////////////////////////////////////////////////////////////////////////
46bool Cloud::isConnected() const
47{
48 return std::any_of(m_NodeManagers.begin(), m_NodeManagers.end(), [](auto & nodeManager)
49 {
50 return nodeManager->cloud() && nodeManager->cloud()->isConnected();
51 });
52}
53
54void Cloud::onConnected()
55{
56 auto node = qobject_cast<Node*>(sender());
57 if (!node)
58 return;
59
60 qCInfo(KSTARS_EKOS) << "Connected to Cloud Websocket server at" << node->url().toDisplayString();
61
62 emit connected();
63}
64
65void Cloud::onDisconnected()
66{
67 qCInfo(KSTARS_EKOS) << "Disconnected from Cloud Websocket server.";
68 m_sendBlobs = true;
69
70 for (auto &oneFile : temporaryFiles)
71 QFile::remove(oneFile);
72 temporaryFiles.clear();
73
74 emit disconnected();
75}
76
77void Cloud::onTextReceived(const QString &message)
78{
79 qCInfo(KSTARS_EKOS) << "Cloud Text Websocket Message" << message;
80 QJsonParseError error;
81 auto serverMessage = QJsonDocument::fromJson(message.toLatin1(), &error);
82 if (error.error != QJsonParseError::NoError)
83 {
84 qCWarning(KSTARS_EKOS) << "Ekos Live Parsing Error" << error.errorString();
85 return;
86 }
87
88 const QJsonObject msgObj = serverMessage.object();
89 const QString command = msgObj["type"].toString();
90 if (command == commands[SET_BLOBS])
91 m_sendBlobs = msgObj["payload"].toBool();
92 else if (command == commands[LOGOUT])
93 {
94 for (auto &nodeManager : m_NodeManagers)
95 {
96 if (nodeManager->cloud() == nullptr)
97 continue;
98
99 nodeManager->cloud()->disconnectServer();
100 }
101 }
102}
103
104void Cloud::upload(const QSharedPointer<FITSData> &data, const QString &uuid)
105{
106 if (Options::ekosLiveCloud() == false || m_sendBlobs == false)
107 return;
108
109 m_UUID = uuid;
110 m_ImageData = data;
111 sendImage();
112}
113
114void Cloud::upload(const QString &filename, const QString &uuid)
115{
116 if (Options::ekosLiveCloud() == false || m_sendBlobs == false)
117 return;
118
119 watcher.waitForFinished();
120 m_UUID = uuid;
121 m_ImageData.reset(new FITSData(), &QObject::deleteLater);
122 QFuture<bool> result = m_ImageData->loadFromFile(filename);
123 watcher.setFuture(result);
124}
125
126void Cloud::sendImage()
127{
128 QtConcurrent::run(this, &Cloud::asyncUpload);
129}
130
131void Cloud::asyncUpload()
132{
133 // Send complete metadata
134 // Add file name and size
135 QJsonObject metadata;
136 // Skip empty or useless metadata
137 for (const auto &oneRecord : m_ImageData->getRecords())
138 {
139 if (oneRecord.key.isEmpty() || oneRecord.value.toString().isEmpty())
140 continue;
141 metadata.insert(oneRecord.key.toLower(), QJsonValue::fromVariant(oneRecord.value));
142 }
143
144 // Filename only without path
145 QString filepath = m_ImageData->filename();
146 QString filenameOnly = QFileInfo(filepath).fileName();
147
148 // Add filename and size as wells
149 metadata.insert("uuid", m_UUID);
150 metadata.insert("filename", filenameOnly);
151 metadata.insert("filesize", static_cast<int>(m_ImageData->size()));
152 // Must set Content-Disposition so
153 metadata.insert("Content-Disposition", QString("attachment;filename=%1.fz").arg(filenameOnly));
154
155 QByteArray image;
157 meta = meta.leftJustified(METADATA_PACKET, 0);
158 image += meta;
159
160 QString compressedFile = QDir::tempPath() + QString("/ekoslivecloud%1").arg(m_UUID);
161 m_ImageData->saveImage(compressedFile + QStringLiteral("[compress R]"));
162 // Upload the compressed image
163 QFile compressedImage(compressedFile);
164 if (compressedImage.open(QIODevice::ReadOnly))
165 {
166 image += compressedImage.readAll();
167 emit newImage(image);
168 qCInfo(KSTARS_EKOS) << "Uploaded" << compressedFile << " to the cloud";
169 }
170
171 // Remove from disk if temporary
172 if (compressedFile != filepath && compressedFile.startsWith(QDir::tempPath()))
173 QFile::remove(compressedFile);
174
175 m_ImageData.reset();
176}
177
178void Cloud::uploadImage(const QByteArray &image)
179{
180 for (auto &nodeManager : m_NodeManagers)
181 {
182 if (nodeManager->cloud() == nullptr)
183 continue;
184
185 nodeManager->cloud()->sendBinaryMessage(image);
186 }
187}
188
189void Cloud::updateOptions()
190{
191 // In case cloud storage is toggled, inform cloud
192 // websocket channel of this change.
193 QJsonObject payload = {{"name", "ekosLiveCloud"}, {"value", Options::ekosLiveCloud()}};
194 QJsonObject message =
195 {
196 {"type", commands[OPTION_SET]},
197 {"payload", payload}
198 };
199
200 for (auto &nodeManager : m_NodeManagers)
201 {
202 if (nodeManager->cloud() == nullptr)
203 continue;
204
205 nodeManager->cloud()->sendTextMessage(QJsonDocument(message).toJson(QJsonDocument::Compact));
206 }
207}
208
209}
Generic record interfaces and implementations.
Definition cloud.cpp:23
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
void setFuture(const QFuture< T > &future)
void waitForFinished()
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)
void clear()
void deleteLater()
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
UniqueConnection
QFuture< T > run(Function function,...)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 26 2024 11:59:51 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.