swift
vpilotrulesreader.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 <QDateTime>
8 #include <QDir>
9 #include <QDomDocument>
10 #include <QDomNamedNodeMap>
11 #include <QDomNode>
12 #include <QDomNodeList>
13 #include <QFile>
14 #include <QFileInfo>
15 #include <QFileInfoList>
16 #include <QIODevice>
17 #include <QReadLocker>
18 #include <QStandardPaths>
19 #include <QTimer>
20 #include <QWriteLocker>
21 #include <QtGlobal>
22 
23 #include "misc/logmessage.h"
25 #include "misc/statusmessage.h"
26 #include "misc/stringutils.h"
27 #include "misc/worker.h"
28 
29 using namespace swift::misc;
30 
31 namespace swift::misc::simulation::fscommon
32 {
33  CVPilotRulesReader::CVPilotRulesReader(bool standardDirectory, QObject *parent) : QObject(parent)
34  {
35  if (standardDirectory) { this->addDirectory(CVPilotRulesReader::standardMappingsDirectory()); }
36  }
37 
39 
40  QStringList CVPilotRulesReader::getFiles() const
41  {
42  QReadLocker l(&m_lockData);
43  return m_fileList;
44  }
45 
47  {
48  QReadLocker l(&m_lockData);
49  return !m_fileList.isEmpty();
50  }
51 
52  void CVPilotRulesReader::addFilename(const QString &fileName)
53  {
54  QWriteLocker l(&m_lockData);
55  if (this->m_fileList.contains(fileName)) { return; }
56  this->m_fileList.append(fileName);
57  }
58 
59  void CVPilotRulesReader::addDirectory(const QString &directory)
60  {
61  QDir dir(directory);
62  if (!dir.exists()) { return; }
63  QStringList nameFilters({ "*.vmr" });
64  QFileInfoList entries = dir.entryInfoList(nameFilters, QDir::Files | QDir::Readable);
65  for (const QFileInfo &file : entries) { this->addFilename(file.absoluteFilePath()); }
66  }
67 
69  {
70  QReadLocker l(&m_lockData);
71  return m_loadedFiles;
72  }
73 
75  {
76  QReadLocker l(&m_lockData);
77  return m_rules;
78  }
79 
80  int CVPilotRulesReader::getModelsCount() const { return this->m_cachedVPilotModels.getThreadLocal().size(); }
81 
83  {
84  // already cached?
85  CAircraftModelList vPilotModels(this->m_cachedVPilotModels.get());
86  if (!vPilotModels.isEmpty() || m_rules.isEmpty()) { return vPilotModels; }
87 
88  // important: that can take a while and should normally
89  // run in background
90  const CVPilotModelRuleSet rules(getRules()); // thread safe copy
91  vPilotModels = rules.toAircraftModels(); // long lasting operation
92  this->ps_setCache(vPilotModels);
93  return vPilotModels;
94  }
95 
96  CAircraftModelList CVPilotRulesReader::getAsModelsFromCache() const { return this->m_cachedVPilotModels.get(); }
97 
99  {
100  QReadLocker l(&m_lockData);
101  return m_rules.size();
102  }
103 
105  {
106  QWriteLocker l(&m_lockData);
107  m_shutdown = true;
108  }
109 
111  {
113  static QString directory;
114  if (directory.isEmpty())
115  {
116  directory = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation).constFirst();
117  if (!directory.endsWith('/')) { directory.append('/'); }
118  directory.append("vPilot Files/Model Matching Rule Sets");
119  }
120  return directory;
121  }
122 
123  bool CVPilotRulesReader::read(bool convertToModels)
124  {
125  int loadedFiles = 0;
126  QStringList filesWithProblems;
127  CVPilotModelRuleSet rules;
128  const QStringList fileList(getFiles());
129  for (const QString &fn : fileList)
130  {
131  if (m_shutdown) { return false; }
132  loadedFiles++;
133  bool s = this->loadFile(fn, rules);
134  if (!s) { filesWithProblems.append(fn); }
135  }
136 
137  {
138  QWriteLocker l(&m_lockData);
139  this->m_loadedFiles = loadedFiles;
140  this->m_fileListWithProblems = filesWithProblems;
141  this->m_rules = rules;
142  if (m_shutdown) { return false; }
143  }
144 
145  const bool success = loadedFiles > 0;
146  if (convertToModels)
147  {
148  const CAircraftModelList vPilotModels(rules.toAircraftModels()); // long lasting operation
149  this->ps_setCache(vPilotModels);
150  }
151 
152  emit readFinished(success);
153  return success;
154  }
155 
157  {
158  // set a thread safe flag
159  {
160  QWriteLocker l(&m_lockData);
161  if (m_asyncLoadInProgress || m_shutdown) { return nullptr; }
162  m_asyncLoadInProgress = true;
163  }
165  this, "CVPilotRulesReader", [this, convertToModels]() { this->read(convertToModels); });
166  worker->then(this, &CVPilotRulesReader::ps_readInBackgroundFinished);
167  return worker;
168  }
169 
170  void CVPilotRulesReader::ps_readInBackgroundFinished()
171  {
172  QWriteLocker l(&m_lockData);
173  m_asyncLoadInProgress = false;
174  }
175 
176  void CVPilotRulesReader::ps_onVPilotCacheChanged()
177  {
178  // void
179  }
180 
181  void CVPilotRulesReader::ps_setCache(const CAircraftModelList &models)
182  {
183  if (this->m_cachedVPilotModels.isOwnerThread())
184  {
185  CStatusMessage m;
186  {
187  QWriteLocker l(&m_lockData);
188  m = this->m_cachedVPilotModels.set(models);
189  }
190  if (m.isFailure()) { CLogMessage::preformatted(m); }
191  }
192  else
193  {
194  QTimer::singleShot(0, this, [this, models]() { this->ps_setCache(models); });
195  }
196  }
197 
198  bool CVPilotRulesReader::loadFile(const QString &fileName, CVPilotModelRuleSet &ruleSet)
199  {
200  QFile f(fileName);
201  if (!f.exists()) { return false; }
202  if (!f.open(QFile::ReadOnly | QFile::Text)) { return false; }
203  QByteArray fc = f.readAll();
204  if (fc.isEmpty()) { return false; }
205  QDomDocument doc;
206  if (!doc.setContent(fc)) { return false; }
207  QDomNodeList rules = doc.elementsByTagName("ModelMatchRule");
208  if (rules.isEmpty()) { return false; }
209 
210  QDomNodeList mmRuleSet = doc.elementsByTagName("ModelMatchRuleSet");
211  if (mmRuleSet.size() < 1) { return true; }
212 
213  const QDomNamedNodeMap attributes = mmRuleSet.at(0).attributes();
214  QString folder = attributes.namedItem("Folder").nodeValue().trimmed();
215  if (folder.isEmpty()) { folder = QFileInfo(fileName).fileName().replace(".vmr", ""); }
216 
217  // "2/1/2014 12:00:00 AM", "5/26/2014 2:00:00 PM"
218  const QString updated = attributes.namedItem("UpdatedOn").nodeValue();
219  QDateTime qt = fromStringUtc(updated, "M/d/yyyy h:mm:ss AP");
220  qint64 updatedTimestamp = qt.toMSecsSinceEpoch();
221 
222  int rulesSize = rules.size();
223  for (int i = 0; i < rulesSize; i++)
224  {
225  const QDomNamedNodeMap ruleAttributes = rules.at(i).attributes();
226  const QString typeCode = ruleAttributes.namedItem("TypeCode").nodeValue();
227  const QString modelName = ruleAttributes.namedItem("ModelName").nodeValue();
228  // remark, callsign prefix is airline ICAO code
229  const QString callsignPrefix = ruleAttributes.namedItem("CallsignPrefix").nodeValue();
230  if (modelName.isEmpty()) { continue; }
231 
232  // split if we have multiple models
233  if (modelName.contains("//"))
234  {
235  // multiple models
236  const QStringList models = modelName.split("//");
237  for (const QString &model : models)
238  {
239  if (model.isEmpty()) { continue; }
240  CVPilotModelRule rule(model, folder, typeCode, callsignPrefix, updatedTimestamp);
241  ruleSet.push_back(rule);
242  }
243  }
244  else
245  {
246  // single model
247  CVPilotModelRule rule(modelName, folder, typeCode, callsignPrefix, updatedTimestamp);
248  ruleSet.push_back(rule);
249  }
250  }
251  return true;
252  }
253 
254 } // namespace swift::misc::simulation::fscommon
size_type size() const
Returns number of elements in the collection.
Definition: collection.h:185
bool isEmpty() const
Synonym for empty.
Definition: collection.h:191
static void preformatted(const CStatusMessage &statusMessage)
Sends a verbatim, preformatted message to the log.
bool isEmpty() const
Synonym for empty.
Definition: sequence.h:285
Streamable status message, e.g.
bool isFailure() const
Operation considered unsuccessful.
void then(T *context, F functor)
Connects to a functor or method which will be called when the task is finished.
Definition: worker.h:70
Class for doing some arbitrary parcel of work in its own thread.
Definition: worker.h:188
static CWorker * fromTask(QObject *owner, const QString &name, F &&task)
Returns a new worker object which lives in a new thread.
Definition: worker.h:201
Value object encapsulating a list of aircraft models.
Value object reading a set of vPilot rules.
simulation::CAircraftModelList toAircraftModels() const
To aircraft models.
swift::misc::simulation::CAircraftModelList getAsModels()
Get as models.
CVPilotRulesReader(bool standardDirectory=true, QObject *parent=nullptr)
Constructor.
void addFilename(const QString &fileName)
File names.
swift::misc::simulation::CAircraftModelList getAsModelsFromCache() const
Get as models from cache.
static const QString & standardMappingsDirectory()
The standard directory for vPilot mappings.
void addDirectory(const QString &directory)
Directory with .vmr files.
swift::misc::CWorker * readInBackground(bool convertToModels)
Load data in background thread.
CVPilotModelRuleSet getRules() const
Loaded rules.
Free functions in swift::misc.
SWIFT_MISC_EXPORT QDateTime fromStringUtc(const QString &dateTimeString, const QString &format)
Same as QDateTime::fromString but QDateTime will be set to UTC.
auto singleShot(int msec, QObject *target, F &&task)
Starts a single-shot timer which will call a task in the thread of the given object when it times out...
Definition: threadutils.h:30