swift
autopublishdata.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2019 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #include <QDateTime>
7 #include <QDir>
8 #include <QFile>
9 #include <QFileInfo>
10 #include <QStringBuilder>
11 #include <QStringList>
12 
13 #include "misc/fileutils.h"
14 #include "misc/json.h"
15 #include "misc/logcategories.h"
16 #include "misc/setbuilder.h"
17 #include "misc/swiftdirectories.h"
18 
19 using namespace swift::misc::physical_quantities;
20 
21 namespace swift::misc::simulation
22 {
23  void CAutoPublishData::insert(const QString &modelString, const physical_quantities::CLength &cg)
24  {
25  if (cg.isNull() || modelString.isEmpty()) { return; }
26  m_modelStringVsCG.insert(modelString.toUpper(), cg);
27  }
28 
29  void CAutoPublishData::insert(const QString &modelString, const CSimulatorInfo &simulator)
30  {
31  if (!simulator.isSingleSimulator() || modelString.isEmpty()) { return; }
32  m_modelStringVsSimulatorInfo.insert(modelString.toUpper(), simulator);
33  }
34 
35  void CAutoPublishData::clear()
36  {
37  m_modelStringVsCG.clear();
38  m_modelStringVsSimulatorInfo.clear();
39  }
40 
41  bool CAutoPublishData::isEmpty() const
42  {
43  return m_modelStringVsCG.isEmpty() && m_modelStringVsSimulatorInfo.isEmpty();
44  }
45 
46  QString CAutoPublishData::toDatabaseJson() const
47  {
48  // used simple string JSON generation as it is faster
49  QString json;
50 
51  for (const auto [string, cg] : makePairsRange(m_modelStringVsCG))
52  {
53  json += QStringLiteral("{ \"type\": \"cg\", \"modelstring\": \"%1\", \"cgft\": %2 },\n")
54  .arg(string, cg.valueRoundedAsString(CLengthUnit::ft(), 1));
55  }
56 
57  for (const auto [string, sim] : makePairsRange(m_modelStringVsSimulatorInfo))
58  {
59  json +=
60  QStringLiteral("{ \"type\": \"simulatorupdate\", \"modelstring\": \"%1\", \"simulator\": \"%2\" },\n")
61  .arg(string, sim.toQString(false));
62  }
63 
64  if (json.isEmpty()) { return {}; }
65  json.chop(2); // remove 2 chars at end
66  return u"[\n" % json % u"\n]\n";
67  }
68 
69  int CAutoPublishData::fromDatabaseJson(const QString &jsonData, bool clear)
70  {
71  if (clear) { this->clear(); }
72  if (jsonData.isEmpty()) { return 0; }
73  const QJsonArray array = json::jsonArrayFromString(jsonData);
74  if (array.isEmpty()) { return 0; }
75 
76  for (const QJsonValue &value : array)
77  {
78  const QJsonObject obj = value.toObject();
79  const QString t = obj["type"].toString();
80  const QString m = obj["modelstring"].toString();
81  if (m.isEmpty()) { continue; }
82 
83  if (t.startsWith("simulator", Qt::CaseInsensitive))
84  {
85  const QString simulator = obj["simulator"].toString();
86  const CSimulatorInfo si(simulator);
87  if (si.isSingleSimulator()) { this->insert(m, si); }
88  }
89  else if (t.startsWith("cg", Qt::CaseInsensitive))
90  {
91  const double cgFt = obj["cgft"].toDouble(-1);
92  if (cgFt < 0) { continue; }
93  this->insert(m, CLength(cgFt, CLengthUnit::ft()));
94  }
95  }
96 
97  return m_modelStringVsCG.size() + m_modelStringVsSimulatorInfo.size();
98  }
99 
100  bool CAutoPublishData::writeJsonToFile() const
101  {
102  if (this->isEmpty()) { return false; }
103  const QString fn =
104  fileBaseName() % u'_' % QDateTime::currentDateTimeUtc().toString("yyyyMMddHHmmss") % fileAppendix();
105  return this->writeJsonToFile(CFileUtils::appendFilePaths(CSwiftDirectories::logDirectory(), fn));
106  }
107 
108  bool CAutoPublishData::writeJsonToFile(const QString &pathAndFile) const
109  {
110  if (this->isEmpty()) { return false; }
111  const QString json = this->toDatabaseJson();
112  return CFileUtils::writeStringToFile(json, pathAndFile);
113  }
114 
115  bool CAutoPublishData::readFromJsonFile(const QString &fileAndPath, bool clear)
116  {
117  const QString json = CFileUtils::readFileToString(fileAndPath);
118  if (json.isEmpty()) { return false; }
119  this->fromDatabaseJson(json, clear);
120  return true;
121  }
122 
123  int CAutoPublishData::readFromJsonFiles(const QString &dirPath)
124  {
125  const QStringList fileList = findAndCleanupPublishFiles(dirPath);
126  if (fileList.isEmpty()) { return 0; }
127  this->clear();
128 
129  int c = 0;
130  for (const QString &file : fileList)
131  {
132  // read from file
133  if (this->readFromJsonFile(CFileUtils::appendFilePaths(dirPath, file), false)) { c++; }
134  }
135  return c;
136  }
137 
138  CStatusMessageList CAutoPublishData::analyzeAgainstDBData(const CAircraftModelList &dbModels)
139  {
141  if (dbModels.isEmpty()) { return CStatusMessage(this).validationError(u"No DB data"); }
142  if (this->isEmpty()) { return CStatusMessage(this).validationWarning(u"No data"); }
143 
144  CStatusMessageList msgs;
145  msgs.push_back(CStatusMessage(cats).validationInfo(u"DB models: %1") << dbModels.size());
146 
147  CSetBuilder<QString> unchangedCG;
148  for (const QString &modelString : m_modelStringVsCG.keys())
149  {
150  const CAircraftModel dbModel = dbModels.findFirstByModelStringOrDefault(modelString);
151  if (dbModel.hasValidDbKey())
152  {
153  if (dbModel.getCG() == m_modelStringVsCG[modelString]) { unchangedCG.insert(modelString); }
154  }
155  }
156 
157  CSetBuilder<QString> unchangedSim;
158  for (const QString &modelString : m_modelStringVsSimulatorInfo.keys())
159  {
160  const CAircraftModel dbModel = dbModels.findFirstByModelStringOrDefault(modelString);
161  if (dbModel.hasValidDbKey())
162  {
163  if (dbModel.getSimulator().matchesAny(m_modelStringVsSimulatorInfo[modelString]))
164  {
165  unchangedSim.insert(modelString);
166  }
167  }
168  }
169 
170  // remove
171  if (!unchangedCG.isEmpty())
172  {
173  QList<QString> unchangedCGList = std::move(unchangedCG);
174  msgs.push_back(CStatusMessage(cats).validationInfo(u"Removing unchanged CGs: %1")
175  << unchangedCGList.size());
176  for (const QString &m : unchangedCGList) { m_modelStringVsCG.remove(m); }
177  }
178 
179  if (!unchangedSim.isEmpty())
180  {
181  QList<QString> unchangedSimList = std::move(unchangedSim);
182  msgs.push_back(CStatusMessage(cats).validationInfo(u"Removing unchanged simulators: %1")
183  << unchangedSimList.size());
184  for (const QString &m : unchangedSimList) { m_modelStringVsSimulatorInfo.remove(m); }
185  }
186 
187  msgs.push_back(CStatusMessage(this).validationInfo(this->getSummary()));
188  return msgs;
189  }
190 
191  QString CAutoPublishData::getSummary() const
192  {
193  return QStringLiteral("Changed CGs: %1 | sim.entries: %2")
194  .arg(m_modelStringVsCG.size())
195  .arg(m_modelStringVsSimulatorInfo.size());
196  }
197 
198  QSet<QString> CAutoPublishData::allModelStrings() const
199  {
200  QSet<QString> allStrings(m_modelStringVsCG.keyBegin(), m_modelStringVsCG.keyEnd());
201  allStrings.unite(QSet<QString>(m_modelStringVsSimulatorInfo.keyBegin(), m_modelStringVsSimulatorInfo.keyEnd()));
202  return allStrings;
203  }
204 
205  void CAutoPublishData::testData()
206  {
207  this->clear();
208 
209  const CLength cg1(10, CLengthUnit::ft());
210  const CLength cg2(40, CLengthUnit::ft());
211  const CLength cg3(30, CLengthUnit::ft());
212 
213  this->insert("testModelString1", cg1);
214  this->insert("testModelString2", cg2);
215  this->insert("testModelString3", cg3);
216 
217  this->insert("testModelString1", CSimulatorInfo::fs9());
218  this->insert("testModelString2", CSimulatorInfo::xplane());
219  this->insert("testModelString3", CSimulatorInfo::fg());
220  this->insert("testModelString4", CSimulatorInfo::p3d());
221  this->insert("testModelString5", CSimulatorInfo::fsx());
222  this->insert("testModelString6", CSimulatorInfo::fsx());
223  this->insert("testModelString7", CSimulatorInfo::msfs());
224  this->insert("testModelString8", CSimulatorInfo::msfs2024());
225  }
226 
227  const QString &CAutoPublishData::fileBaseName()
228  {
229  static const QString fn("autopublish");
230  return fn;
231  }
232 
233  const QString &CAutoPublishData::fileAppendix()
234  {
235  static const QString a(".json");
236  return a;
237  }
238 
239  bool CAutoPublishData::existAutoPublishFiles(const QString &dirPath)
240  {
241  return findAndCleanupPublishFiles(dirPath).size() > 0;
242  }
243 
244  int CAutoPublishData::deleteAutoPublishFiles(const QString &dirPath)
245  {
246  const QStringList fileList = findAndCleanupPublishFiles(dirPath);
247  if (fileList.isEmpty()) { return 0; }
248 
249  int c = 0;
250  for (const QString &file : fileList)
251  {
252  const QString fn = CFileUtils::appendFilePaths(dirPath, file);
253  QFile f(fn);
254  if (!f.exists()) { continue; }
255  if (f.remove()) { c++; }
256  }
257  return c;
258  }
259 
260  QStringList CAutoPublishData::findAndCleanupPublishFiles(const QString &dirPath)
261  {
262  QDir dir(dirPath);
263  if (!dir.exists()) { return {}; }
264 
265  const QString filter = fileBaseName() % u'*' % fileAppendix();
266  const QStringList filters({ filter });
267  dir.setNameFilters(filters);
268  dir.setFilter(QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks);
269  const QStringList fileList = dir.entryList();
270  if (fileList.isEmpty()) { return fileList; }
271 
272  // avoid outdated files (e.g. if format changes)
273  QStringList correctedList;
274  const QDateTime deadline = QDateTime::currentDateTimeUtc().addDays(-30);
275  for (const QString &fn : fileList)
276  {
277  const QFileInfo fi(
278  fn.contains(dir.absolutePath()) ? fn : CFileUtils::appendFilePathsAndFixUnc(dir.absolutePath(), fn));
279  if (!fi.exists()) { continue; }
280  const QDateTime created = fi.birthTime().toUTC();
281  if (deadline < created) { correctedList << fn; }
282  else
283  {
284  QFile deleteFile(fn);
285  deleteFile.remove();
286  }
287  }
288 
289  return correctedList;
290  }
291 
292 } // namespace swift::misc::simulation
static bool writeStringToFile(const QString &content, const QString &fileNameAndPath)
Write string to text file.
Definition: fileutils.cpp:40
static QString appendFilePathsAndFixUnc(const QString &path1, const QString &path2)
Append file paths.
Definition: fileutils.cpp:108
static QString appendFilePaths(const QString &path1, const QString &path2)
Append file paths.
Definition: fileutils.cpp:95
static QString readFileToString(const QString &fileNameAndPath)
Read file into string.
Definition: fileutils.cpp:68
static const QString & webservice()
Webservice.
static const QString & mapping()
Mapping.
A sequence of log categories.
Derived & validationError(const char16_t(&format)[N])
Set the severity to error, providing a format string, and adding the validation category.
Derived & validationWarning(const char16_t(&format)[N])
Set the severity to warning, providing a format string, and adding the validation category.
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
Build a QSet more efficiently when calling insert() in a for loop.
Definition: setbuilder.h:25
bool isEmpty() const
True if no elements have been inserted.
Definition: setbuilder.h:34
void insert(const T &value)
Add an element to the set. Runs in amortized constant time.
Definition: setbuilder.h:29
Streamable status message, e.g.
Status messages, e.g. from Core -> GUI.
static const QString & logDirectory()
Directory for log files.
bool hasValidDbKey() const
Has valid DB key.
Definition: datastore.h:102
Physical unit length (length)
Definition: length.h:18
static CLengthUnit ft()
Foot ft.
Definition: units.h:159
Aircraft model (used by another pilot, my models on disk)
Definition: aircraftmodel.h:71
CSimulatorInfo getSimulator() const
Simulator info.
const physical_quantities::CLength & getCG() const
Get center of gravity.
Value object encapsulating a list of aircraft models.
CAircraftModel findFirstByModelStringOrDefault(const QString &modelString, Qt::CaseSensitivity sensitivity=Qt::CaseInsensitive) const
Find first by model string.
Simple hardcoded info about the corresponding simulator.
Definition: simulatorinfo.h:41
bool isSingleSimulator() const
Single simulator selected.
bool matchesAny(const CSimulatorInfo &otherInfo) const
Matches any simulator.
QJsonArray jsonArrayFromString(const QString &json)
JSON Array from string.
Definition: json.cpp:431
auto makePairsRange(const T &container)
Returns a const CRange for iterating over the keys and values of a Qt associative container.
Definition: range.h:374