swift
databasewriter.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2015 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #include <QByteArray>
7 #include <QHttpMultiPart>
8 #include <QNetworkReply>
9 #include <QNetworkRequest>
10 #include <QScopedPointer>
11 #include <QScopedPointerDeleteLater>
12 #include <QString>
13 #include <QStringBuilder>
14 #include <QUrl>
15 #include <QtGlobal>
16 
17 #include "core/application.h"
18 #include "core/data/globalsetup.h"
19 #include "core/db/databaseutils.h"
21 #include "misc/logcategories.h"
24 #include "misc/statusmessage.h"
25 
26 using namespace swift::misc;
27 using namespace swift::misc::db;
28 using namespace swift::misc::network;
29 using namespace swift::misc::simulation;
30 using namespace swift::core::db;
31 
32 namespace swift::core::db
33 {
34  CDatabaseWriter::CDatabaseWriter(const network::CUrl &baseUrl, QObject *parent)
35  : QObject(parent), m_modelPublishUrl(CDatabaseWriter::getModelPublishUrl(baseUrl)),
36  m_autoPublishUrl(CDatabaseWriter::getAutoPublishUrl(baseUrl))
37  {
38  // void
39  }
40 
42  {
43  return this->asyncPublishModels(CAircraftModelList({ model }), extraInfo);
44  }
45 
47  {
48  CStatusMessageList msgs;
49  if (m_shutdown || !sApp)
50  {
51  msgs.push_back(CStatusMessage(CStatusMessage::SeverityWarning, u"Database writer shutting down"));
52  return msgs;
53  }
54 
55  if (this->isModelReplyOverdue())
56  {
57  const bool killed = this->killPendingModelReply();
58  if (killed)
59  {
60  const CStatusMessage msg(CStatusMessage::SeverityWarning, u"Aborted outdated pending reply");
61  msgs.push_back(CStatusMessage(msg));
62  // need to let a potential receiver know it has failed
63  emit this->publishedModels(CAircraftModelList(), CAircraftModelList(), msg, false, false);
64  }
65  }
66 
67  if (m_pendingModelPublishReply)
68  {
69  msgs.push_back(CStatusMessage(CStatusMessage::SeverityWarning, u"Another write operation in progress"));
70  return msgs;
71  }
72 
73  const bool compress = models.size() > 3;
74  QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType, this);
75  multiPart->append(CDatabaseUtils::getJsonTextMultipart(models.toDatabaseJson(), compress));
76 
78 
79  QUrl url(m_modelPublishUrl.toQUrl());
80  if (compress) { url.setQuery(CDatabaseUtils::getCompressedQuery()); }
81  QNetworkRequest request(url);
82  const QByteArray eInfo = extraInfo.toLatin1();
83  request.setRawHeader(QByteArray("swift-extrainfo"), eInfo);
84  const int logId = m_writeLog.addPendingUrl(url);
85  m_pendingModelPublishReply =
86  sApp->postToNetwork(request, logId, multiPart, { this, &CDatabaseWriter::postedModelsResponse });
87  m_modelReplyPendingSince = QDateTime::currentMSecsSinceEpoch();
88  return msgs;
89  }
90 
92  {
93  CStatusMessageList msgs;
94  if (m_shutdown || !sApp)
95  {
96  msgs.push_back(CStatusMessage(CStatusMessage::SeverityWarning, u"Database writer shutting down"));
97  return msgs;
98  }
99 
100  if (data.isEmpty())
101  {
102  msgs.push_back(CStatusMessage(CStatusMessage::SeverityWarning, u"No auto update data"));
103  return msgs;
104  }
105 
106  const QString json = data.toDatabaseJson();
107  const bool compress = json.size() > 2048;
108  QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType, this);
109  multiPart->append(CDatabaseUtils::getJsonTextMultipart(json, compress));
111  {
112  // add debug flag
113  multiPart->append(CDatabaseUtils::getMultipartWithDebugFlag());
114  }
115 
116  // QUrl url("https://192.168.0.153/service/publishauto.php");
117  QUrl url(m_autoPublishUrl.toQUrl());
118  QUrlQuery query;
119  if (compress) { query = CDatabaseUtils::getCompressedQuery(); }
120  url.setQuery(query);
121 
122  QNetworkRequest request(url);
123  const int logId = m_writeLog.addPendingUrl(url);
124  m_pendingAutoPublishReply =
125  sApp->postToNetwork(request, logId, multiPart, { this, &CDatabaseWriter::postedAutoPublishResponse });
126  m_autoPublishReplyPendingSince = QDateTime::currentMSecsSinceEpoch();
127  return msgs;
128  }
129 
131  {
132  m_shutdown = true;
133  this->killPendingModelReply();
134  }
135 
136  const QString &CDatabaseWriter::getName()
137  {
138  static const QString n("Database writer");
139  return n;
140  }
141 
143  {
144  static const QStringList cats { CLogCategories::swiftDbWebservice(), CLogCategories::webservice() };
145  return cats;
146  }
147 
148  void CDatabaseWriter::postedModelsResponse(QNetworkReply *nwReplyPtr)
149  {
150  QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
151  if (m_shutdown || !sApp)
152  {
153  nwReply->abort();
154  return;
155  }
156 
157  m_pendingModelPublishReply = nullptr;
158  const QUrl url(nwReply->url());
159  const QString urlString(url.toString());
160  if (nwReply->error() == QNetworkReply::NoError)
161  {
162  const QString responseData(nwReply->readAll().trimmed());
163  nwReply->close(); // close asap
164  if (responseData.isEmpty())
165  {
166  const CStatusMessageList msgs(
167  { CStatusMessage(this, CStatusMessage::SeverityError, u"No response data from " % urlString) });
168  emit this->publishedModels(CAircraftModelList(), CAircraftModelList(), msgs, false, false);
169  return;
170  }
171 
172  CAircraftModelList modelsPublished;
173  CAircraftModelList modelsSkipped;
174  CStatusMessageList msgs;
175  bool directWrite;
176  const bool sendingSuccessful =
177  parseSwiftPublishResponse(responseData, modelsPublished, modelsSkipped, msgs, directWrite);
178  const int c = CDatabaseUtils::fillInMissingAircraftAndLiveryEntities(modelsPublished);
179 
180  emit this->publishedModels(modelsPublished, modelsSkipped, msgs, sendingSuccessful, directWrite);
181 
182  if (!modelsPublished.isEmpty()) { emit this->publishedModelsSimplified(modelsPublished, directWrite); }
183  Q_UNUSED(c);
184  }
185  else
186  {
187  const QString error = nwReply->errorString();
188  nwReply->close(); // close asap
189  const CStatusMessageList msgs(
190  { CStatusMessage(this, CStatusMessage::SeverityError, u"HTTP error: " % error) });
191  emit this->publishedModels(CAircraftModelList(), CAircraftModelList(), msgs, false, false);
192  }
193  }
194 
195  void CDatabaseWriter::postedAutoPublishResponse(QNetworkReply *nwReplyPtr)
196  {
197  static const CLogCategoryList cats(CLogCategoryList(this).join({ CLogCategories::swiftDbWebservice() }));
198  QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
199  if (m_shutdown || !sApp)
200  {
201  nwReply->abort();
202  return;
203  }
204 
205  m_pendingAutoPublishReply = nullptr;
206  const QUrl url(nwReply->url());
207  const QString urlString(url.toString());
208  const QString responseData(nwReply->readAll().trimmed());
209  const QString error = nwReply->errorString();
210  nwReply->close(); // close asap
211 
212  CStatusMessageList msgs;
213  const bool ok = CDatastoreUtility::parseAutoPublishResponse(responseData, msgs);
214 
215  if (nwReply->error() == QNetworkReply::NoError)
216  {
217  // no error
218  }
219  else { msgs.push_back(CStatusMessage(cats, CStatusMessage::SeverityError, u"HTTP error: " % error)); }
220 
221  emit this->autoPublished(ok, urlString, msgs);
222  }
223 
224  bool CDatabaseWriter::killPendingModelReply()
225  {
226  if (!m_pendingModelPublishReply) { return false; }
227  m_pendingModelPublishReply->abort();
228  m_pendingModelPublishReply = nullptr;
229  m_modelReplyPendingSince = -1;
230  return true;
231  }
232 
233  bool CDatabaseWriter::isModelReplyOverdue() const
234  {
235  if (m_modelReplyPendingSince < 0 || !m_pendingModelPublishReply) { return false; }
236  const qint64 ms = QDateTime::currentMSecsSinceEpoch() - m_modelReplyPendingSince;
237  return ms > 7500;
238  }
239 
240  CUrl CDatabaseWriter::getModelPublishUrl(const network::CUrl &baseUrl)
241  {
242  return baseUrl.withAppendedPath("service/publishmodels.php");
243  }
244 
245  CUrl CDatabaseWriter::getAutoPublishUrl(const CUrl &baseUrl)
246  {
247  return baseUrl.withAppendedPath("service/publishauto.php");
248  }
249 
250  QList<QByteArray> CDatabaseWriter::splitData(const QByteArray &data, int size)
251  {
252  if (data.size() <= size) { return QList<QByteArray>({ data }); }
253  int pos = 0, arrsize = data.size();
254  QList<QByteArray> arrays;
255  while (pos < arrsize)
256  {
257  const QByteArray arr = data.mid(pos, size);
258  arrays << arr;
259  pos += arr.size();
260  }
261  return arrays;
262  }
263 
264  bool CDatabaseWriter::parseSwiftPublishResponse(const QString &jsonResponse, CAircraftModelList &publishedModels,
265  CAircraftModelList &skippedModels, CStatusMessageList &messages,
266  bool &directWrite)
267  {
268  directWrite = false;
269 
270  if (jsonResponse.isEmpty())
271  {
272  messages.push_back(CStatusMessage(static_cast<CDatabaseWriter *>(nullptr), CStatusMessage::SeverityError,
273  u"Empty JSON data for published models"));
274  return false;
275  }
276 
277  const QJsonDocument jsonDoc(QJsonDocument::fromJson(jsonResponse.toUtf8()));
278 
279  // array of messages only
280  if (jsonDoc.isArray())
281  {
282  const CStatusMessageList msgs(CStatusMessageList::fromDatabaseJson(jsonDoc.array()));
283  messages.push_back(msgs);
284  return true;
285  }
286 
287  // no object -> most likely some fucked up HTML string with the PHP error
288  if (!jsonDoc.isObject())
289  {
290  const QString phpError(CNetworkUtils::removeHtmlPartsFromPhpErrorMessage(jsonResponse));
291  messages.push_back(
292  CStatusMessage(static_cast<CDatabaseWriter *>(nullptr), CStatusMessage::SeverityError, phpError));
293  return false;
294  }
295 
296  // fully blown object
297  QJsonObject json(jsonDoc.object());
298  bool hasData = false;
299  if (json.contains("msgs"))
300  {
301  const QJsonValue msgJson(json.take("msgs"));
302  const CStatusMessageList msgs(CStatusMessageList::fromDatabaseJson(msgJson.toArray()));
303  if (!msgs.isEmpty())
304  {
305  messages.push_back(msgs);
306  hasData = true;
307  }
308  }
309 
310  // direct write means models written, otherwise CRs
311  if (json.contains("directWrite"))
312  {
313  const QJsonValue dw(json.take("directWrite"));
314  directWrite = dw.toBool(false);
315  }
316 
317  if (json.contains("publishedModels"))
318  {
319  const QJsonValue publishedJson(json.take("publishedModels"));
320  const CAircraftModelList published = CAircraftModelList::fromDatabaseJson(publishedJson.toArray(), "");
321  if (!published.isEmpty())
322  {
323  publishedModels.push_back(published);
324  hasData = true;
325  }
326  }
327 
328  if (json.contains("skippedModels"))
329  {
330  const QJsonValue skippedJson(json.take("skippedModels"));
331  const CAircraftModelList skipped = CAircraftModelList::fromDatabaseJson(skippedJson.toArray(), "");
332  if (!skipped.isEmpty())
333  {
334  skippedModels.push_back(skipped);
335  hasData = true;
336  }
337  }
338 
339  if (!hasData)
340  {
341  messages.push_back(CStatusMessage(static_cast<CDatabaseWriter *>(nullptr), CStatusMessage::SeverityError,
342  u"Received response, but no JSON data"));
343  }
344 
345  return hasData;
346  }
347 } // namespace swift::core::db
SWIFT_CORE_EXPORT swift::core::CApplication * sApp
Single instance of application object.
Definition: application.cpp:71
data::CGlobalSetup getGlobalSetup() const
Global setup.
QNetworkReply * postToNetwork(const QNetworkRequest &request, int logId, const QByteArray &data, const CallbackSlot &callback)
Post to network.
bool dbDebugFlag() const
Debug flag.
Definition: globalsetup.cpp:68
static QHttpPart getMultipartWithDebugFlag()
Multipart with DEBUG FLAG for server.
static const QUrlQuery & getCompressedQuery()
Mark as compressed.
static int fillInMissingAircraftAndLiveryEntities(swift::misc::simulation::CAircraftModelList &models)
Fill in missing data if only the id is provided, but no data.
static QHttpPart getJsonTextMultipart(const QJsonObject &json, bool compress)
Multipart for JSON.
Write to the swift DB.
swift::misc::CStatusMessageList asyncPublishModel(const swift::misc::simulation::CAircraftModel &model, const QString &extraInfo)
Write model to DB.
const QString & getName()
Name of the worker.
swift::misc::CStatusMessageList asyncPublishModels(const swift::misc::simulation::CAircraftModelList &models, const QString &extraInfo)
Write models to DB.
static const QStringList & getLogCategories()
Log categories.
swift::misc::CStatusMessageList asyncAutoPublish(const swift::misc::simulation::CAutoPublishData &data)
Write auto publis data.
void publishedModelsSimplified(const swift::misc::simulation::CAircraftModelList &modelsPublished, bool directWrite)
Published models, simplified version of publishedModels.
void autoPublished(bool success, const QString &url, const swift::misc::CStatusMessageList &msgs)
Auto publishing completed.
void publishedModels(const swift::misc::simulation::CAircraftModelList &modelsPublished, const swift::misc::simulation::CAircraftModelList &modelsSkipped, const swift::misc::CStatusMessageList &messages, bool sendingSuccessful, bool directWrite)
Published models, the response to.
static const QString & webservice()
Webservice.
static const QString & swiftDbWebservice()
Webservice with swift DB.
A sequence of log categories.
size_type size() const
Returns number of elements in the sequence.
Definition: sequence.h:273
void push_back(const T &value)
Appends an element at the end of the sequence.
Definition: sequence.h:305
bool isEmpty() const
Synonym for empty.
Definition: sequence.h:285
Streamable status message, e.g.
constexpr static auto SeverityError
Status severities.
constexpr static auto SeverityWarning
Status severities.
Status messages, e.g. from Core -> GUI.
static CStatusMessageList fromDatabaseJson(const QJsonArray &array)
From our database JSON format.
static CAircraftModelList fromDatabaseJson(const QJsonArray &array)
From DB JSON with default prefixes.
Value object encapsulating information of a location, kind of simplified CValueObject compliant versi...
Definition: url.h:27
CUrl withAppendedPath(const QString &path) const
Append path.
Definition: url.cpp:115
QUrl toQUrl() const
To QUrl.
Definition: url.cpp:101
int addPendingUrl(const CUrl &url, int maxNumber=10)
Add a pending URL.
Definition: urlloglist.cpp:14
Aircraft model (used by another pilot, my models on disk)
Definition: aircraftmodel.h:71
Value object encapsulating a list of aircraft models.
QJsonArray toDatabaseJson() const
To database JSON.
Objects that can be use for auto-publishing. Auto publishing means we sent those data to the DB.
QString toDatabaseJson() const
Simple database JSON.
Classes interacting with the swift database (aka "datastore").
Free functions in swift::misc.