swift
aircraftmodelloaderxplane.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 <string.h>
7 
8 #include <algorithm>
9 #include <functional>
10 
11 #include <QChar>
12 #include <QDateTime>
13 #include <QDir>
14 #include <QDirIterator>
15 #include <QFile>
16 #include <QFileInfo>
17 #include <QFlags>
18 #include <QIODevice>
19 #include <QList>
20 #include <QMap>
21 #include <QRegularExpression>
22 #include <QStringBuilder>
23 #include <QTextStream>
24 
25 #include "config/buildconfig.h"
28 #include "misc/aviation/livery.h"
29 #include "misc/directoryutils.h"
30 #include "misc/fileutils.h"
31 #include "misc/logmessage.h"
37 #include "misc/statusmessage.h"
38 #include "misc/stringutils.h"
39 #include "misc/verify.h"
40 #include "misc/worker.h"
41 
42 using namespace swift::config;
43 using namespace swift::misc;
44 using namespace swift::misc::aviation;
45 using namespace swift::misc::simulation;
46 using namespace swift::misc::network;
47 
48 namespace swift::misc::simulation::xplane
49 {
51  static void normalizePath(QString &path)
52  {
53  for (auto &e : path)
54  {
55  if (e == '/' || e == ':' || e == '\\') { e = '/'; }
56  }
57  }
58 
60  static QString descriptionForFlyableModel(const CAircraftModel &model)
61  {
62  if (!model.getName().isEmpty())
63  {
64  if (model.getDistributor().hasDescription() &&
65  !model.getName().contains(model.getDistributor().getDescription()))
66  {
67  return u"[ACF] " % model.getName() % u" by " % model.getDistributor().getDescription();
68  }
69  else { return u"[ACF] " % model.getName(); }
70  }
71  else if (model.hasAircraftDesignator())
72  {
73  if (model.getDistributor().hasDescription())
74  {
75  return u"[ACF] " % model.getAircraftIcaoCodeDesignator() % u" by " %
77  }
78  else { return u"[ACF] " % model.getAircraftIcaoCodeDesignator(); }
79  }
80  return QStringLiteral("[ACF]");
81  }
82 
83  CAircraftModelLoaderXPlane::CAircraftModelLoaderXPlane(QObject *parent)
84  : IAircraftModelLoader(CSimulatorInfo::xplane(), parent)
85  {}
86 
88  {
89  // that should be safe as long as the worker uses deleteLater (which it does)
90  if (m_parserWorker) { m_parserWorker->waitForFinished(); }
91  }
92 
94  const ModelConsolidationCallback &modelConsolidation,
95  const QStringList &modelDirectories)
96  {
97  const CSimulatorInfo simulator = CSimulatorInfo::xplane();
98  const QStringList modelDirs = this->getInitializedModelDirectories(modelDirectories, simulator);
99  const QStringList excludedDirectoryPatterns(
101 
102  if (modelDirs.isEmpty())
103  {
105  emit this->loadingFinished(
106  CStatusMessage(this, CStatusMessage::SeverityError, u"XPlane model directories '%1' are empty")
107  << modelDirectories.join(", "),
108  simulator, ParsedData);
109  return;
110  }
111 
112  if (mode.testFlag(LoadInBackground))
113  {
114  if (m_parserWorker && !m_parserWorker->isFinished()) { return; }
115  emit this->diskLoadingStarted(simulator, mode);
116 
117  m_parserWorker = CWorker::fromTask(this, "CAircraftModelLoaderXPlane::performParsing",
118  [this, modelDirs, excludedDirectoryPatterns, modelConsolidation]() {
119  auto models =
120  this->performParsing(modelDirs, excludedDirectoryPatterns);
121  if (modelConsolidation) { modelConsolidation(models, true); }
122  return models;
123  });
124  m_parserWorker->thenWithResult<CAircraftModelList>(this, [=](const auto &models) {
125  this->updateInstalledModels(models);
127  emit this->loadingFinished(m_loadingMessages, simulator, ParsedData);
128  });
129  }
130  else if (mode.testFlag(LoadDirectly))
131  {
132  emit this->diskLoadingStarted(simulator, mode);
133  CAircraftModelList models(this->performParsing(modelDirs, excludedDirectoryPatterns));
134  this->updateInstalledModels(models);
135  }
136  }
137 
139  {
140  return !m_parserWorker || m_parserWorker->isFinished();
141  }
142 
144  {
146  const CStatusMessage m = CStatusMessage(this, CStatusMessage::SeverityInfo, u"XPlane updated '%1' models")
147  << models.size();
149  }
150 
151  QString CAircraftModelLoaderXPlane::CSLPlane::getModelName() const
152  {
153  QString modelName = dirNames.join(' ') % u' ' % objectName;
154  if (objectVersion == OBJ7) { modelName += u' ' % textureName; }
155  return std::move(modelName).trimmed();
156  }
157 
158  CAircraftModelList CAircraftModelLoaderXPlane::performParsing(const QStringList &rootDirectories,
159  const QStringList &excludeDirectories)
160  {
161  CAircraftModelList allModels;
162  for (const QString &rootDirectory : rootDirectories)
163  {
164  allModels.push_back(parseCslPackages(rootDirectory, excludeDirectories));
165  allModels.push_back(parseFlyableAirplanes(rootDirectory, excludeDirectories));
166  }
167  return allModels;
168  }
169 
171  void CAircraftModelLoaderXPlane::addUniqueModel(const CAircraftModel &model, CAircraftModelList &models)
172  {
173  if (models.containsModelString(model.getModelString()))
174  {
175  const CStatusMessage m =
176  CStatusMessage(this).warning(
177  u"XPlane model '%1' exists already! Potential model string conflict! Ignoring it.")
178  << model.getModelString();
180  }
181  models.push_back(model);
182  }
183 
184  CAircraftModelList CAircraftModelLoaderXPlane::parseFlyableAirplanes(const QString &rootDirectory,
185  const QStringList &excludeDirectories)
186  {
187  Q_UNUSED(excludeDirectories)
188  if (rootDirectory.isEmpty()) { return {}; }
189 
190  QDir searchPath(rootDirectory, fileFilterFlyable());
191  QDirIterator aircraftIt(searchPath, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
192 
193  emit loadingProgress(this->getSimulator(),
194  QStringLiteral("Parsing flyable airplanes in '%1'").arg(rootDirectory), -1);
195 
196  CAircraftModelList installedModels;
197  while (aircraftIt.hasNext())
198  {
199  aircraftIt.next();
200  if (CFileUtils::isExcludedDirectory(aircraftIt.fileInfo(), excludeDirectories, Qt::CaseInsensitive))
201  {
202  continue;
203  }
204 
205  using namespace swift::misc::simulation::xplane::qtfreeutils;
206  AcfProperties acfProperties = extractAcfProperties(aircraftIt.filePath().toStdString());
207 
208  const CDistributor dist({}, QString::fromStdString(acfProperties.author), {}, {}, CSimulatorInfo::XPLANE);
209  CAircraftModel model;
210  model.setAircraftIcaoCode(QString::fromStdString(acfProperties.aircraftIcaoCode));
211  model.setDescription(QString::fromStdString(acfProperties.modelDescription));
212  model.setName(QString::fromStdString(acfProperties.modelName));
213  model.setDistributor(dist);
214  model.setModelString(QString::fromStdString(acfProperties.modelString));
215  if (!model.hasDescription()) { model.setDescription(descriptionForFlyableModel(model)); }
216  model.setModelType(CAircraftModel::TypeOwnSimulatorModel);
217  model.setSimulator(CSimulatorInfo::xplane());
218  model.setFileDetailsAndTimestamp(aircraftIt.fileInfo());
219  model.setModelMode(CAircraftModel::Exclude);
220  addUniqueModel(model, installedModels);
221 
222  const QString baseModelString = model.getModelString();
223  QDirIterator liveryIt(
224  CFileUtils::appendFilePaths(aircraftIt.fileInfo().canonicalPath(), QStringLiteral("liveries")),
225  QDir::Dirs | QDir::NoDotAndDotDot);
226  emit this->loadingProgress(
227  this->getSimulator(),
228  QStringLiteral("Parsing flyable liveries in '%1'").arg(aircraftIt.fileInfo().canonicalPath()), -1);
229  while (liveryIt.hasNext())
230  {
231  liveryIt.next();
232  model.setModelString(baseModelString % u' ' % liveryIt.fileName());
233  addUniqueModel(model, installedModels);
234  }
235  }
236  return installedModels;
237  }
238 
239  CAircraftModelList CAircraftModelLoaderXPlane::parseCslPackages(const QString &rootDirectory,
240  const QStringList &excludeDirectories)
241  {
242  Q_UNUSED(excludeDirectories);
243  if (rootDirectory.isEmpty()) { return {}; }
244 
245  m_cslPackages.clear();
246 
247  QDir searchPath(rootDirectory, fileFilterCsl());
248  QDirIterator it(searchPath, QDirIterator::Subdirectories);
249  while (it.hasNext())
250  {
251  QString packageFile = it.next();
252  if (CFileUtils::isExcludedDirectory(it.filePath(), excludeDirectories)) { continue; }
253 
254  const QString packageFilePath = it.fileInfo().absolutePath();
255  QFile file(packageFile);
256  file.open(QIODevice::ReadOnly);
257  QString content;
258 
259  QTextStream ts(&file);
260  content.append(ts.readAll());
261  file.close();
262 
263  const auto package = parsePackageHeader(packageFilePath, content);
264  if (package.hasValidHeader()) m_cslPackages.push_back(package);
265  }
266 
267  CAircraftModelList installedModels;
268 
269  // Now we do a full run
270  for (auto &package : m_cslPackages)
271  {
272  const QString packageFile = CFileUtils::appendFilePaths(package.path, QStringLiteral("xsb_aircraft.txt"));
273  emit this->loadingProgress(this->getSimulator(), QStringLiteral("Parsing CSL '%1'").arg(packageFile), -1);
274 
275  QFile file(packageFile);
276  file.open(QIODevice::ReadOnly);
277  QString content;
278 
279  QTextStream ts(&file);
280  content.append(ts.readAll());
281  file.close();
282  parseFullPackage(content, package);
283 
284  for (const auto &plane : std::as_const(package.planes))
285  {
286  if (installedModels.containsModelString(plane.getModelName()))
287  {
288  const CStatusMessage msg =
289  CStatusMessage(this).warning(
290  u"XPlane model '%1' exists already! Potential model string conflict! Ignoring it.")
291  << plane.getModelName();
293  continue;
294  }
295 
296  CAircraftModel model(plane.getModelName(), CAircraftModel::TypeOwnSimulatorModel);
297  const CAircraftIcaoCode icao(plane.icao);
298  const QFileInfo modelFileInfo(plane.filePath);
299  model.setFileDetailsAndTimestamp(modelFileInfo);
300  model.setAircraftIcaoCode(icao);
301 
302  if (CBuildConfig::isLocalDeveloperDebugBuild())
303  {
304  SWIFT_AUDIT_X(modelFileInfo.exists(), Q_FUNC_INFO, "Model does NOT exist");
305  }
306 
307  CLivery livery;
308  livery.setCombinedCode(plane.livery);
309  CAirlineIcaoCode airline(plane.airline);
310  livery.setAirlineIcaoCode(airline);
311  model.setLivery(livery);
312 
313  model.setSimulator(CSimulatorInfo::xplane());
314  QString modelDescription("[CSL]");
315  if (plane.objectVersion == CSLPlane::OBJ7) { modelDescription += "[OBJ7]"; }
316  else if (plane.objectVersion == CSLPlane::OBJ8) { modelDescription += "[OBJ8]"; }
317  model.setDescription(modelDescription);
318  installedModels.push_back(model);
319  }
320  }
321  return installedModels;
322  }
323 
324  bool CAircraftModelLoaderXPlane::doPackageSub(QString &ioPath)
325  {
326  for (auto i = m_cslPackages.cbegin(); i != m_cslPackages.cend(); ++i)
327  {
328  if (strncmp(qPrintable(i->name), qPrintable(ioPath), static_cast<size_t>(i->name.size())) == 0)
329  {
330  ioPath.remove(0, i->name.size());
331  ioPath.insert(0, i->path);
332  return true;
333  }
334  }
335  return false;
336  }
337 
338  bool CAircraftModelLoaderXPlane::parseExportCommand(const QStringList &tokens, CSLPackage &package,
339  const QString &path, int lineNum)
340  {
341  if (tokens.size() != 2)
342  {
343  const CStatusMessage m =
344  CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : EXPORT_NAME command requires 1 argument.")
345  << path << lineNum;
347  return false;
348  }
349 
350  auto p = std::find_if(m_cslPackages.cbegin(), m_cslPackages.cend(),
351  [&tokens](const CSLPackage &p) { return p.name == tokens[1]; });
352  if (p == m_cslPackages.cend())
353  {
354  package.path = path;
355  package.name = tokens[1];
356  return true;
357  }
358  else
359  {
360  const CStatusMessage m =
361  CStatusMessage(this).error(u"XPlane package name '%1' already in use by '%2' reqested by use by '%3'")
362  << tokens[1] << p->path << path;
364  return false;
365  }
366  }
367 
368  bool CAircraftModelLoaderXPlane::parseDependencyCommand(const QStringList &tokens, CSLPackage &package,
369  const QString &path, int lineNum)
370  {
371  Q_UNUSED(package);
372  if (tokens.size() != 2)
373  {
374  const CStatusMessage m =
375  CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : DEPENDENCY command requires 1 argument.")
376  << path << lineNum;
378  return false;
379  }
380 
381  if (std::count_if(m_cslPackages.cbegin(), m_cslPackages.cend(),
382  [&tokens](const CSLPackage &p) { return p.name == tokens[1]; }) == 0)
383  {
384  const CStatusMessage m = CStatusMessage(this).error(
385  u"XPlane required package %1 not found. Aborting processing of this package.")
386  << tokens[1];
388  return false;
389  }
390 
391  return true;
392  }
393 
396  QString readLineFrom(QTextStream &stream)
397  {
398  QString line;
399  do {
400  line = stream.readLine();
401  }
402  while (line.isEmpty() && !stream.atEnd());
403  return line;
404  }
405 
406  bool CAircraftModelLoaderXPlane::parseObjectCommand(const QStringList &, CSLPackage &package, const QString &path,
407  int lineNum)
408  {
409  package.planes.push_back(CSLPlane());
410 
411  const auto m = CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : Unsupported legacy CSL format.")
412  << path << lineNum;
414  return false;
415  }
416 
417  bool CAircraftModelLoaderXPlane::parseTextureCommand(const QStringList &, CSLPackage &package, const QString &path,
418  int lineNum)
419  {
420  if (!package.planes.isEmpty() && !package.planes.back().hasErrors)
421  {
422  const auto m = CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : Unsupported legacy CSL format.")
423  << path << lineNum;
425  }
426  return false;
427  }
428 
429  bool CAircraftModelLoaderXPlane::parseAircraftCommand(const QStringList &, CSLPackage &package, const QString &path,
430  int lineNum)
431  {
432  package.planes.push_back(CSLPlane());
433 
434  const auto m = CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : Unsupported legacy CSL format.")
435  << path << lineNum;
437  return false;
438  }
439 
440  bool CAircraftModelLoaderXPlane::parseObj8AircraftCommand(const QStringList &tokens, CSLPackage &package,
441  const QString &path, int lineNum)
442  {
443  package.planes.push_back(CSLPlane());
444 
445  // OBJ8_AIRCRAFT <path>
446  if (tokens.size() != 2)
447  {
448  const CStatusMessage m = CStatusMessage(this).warning(
449  u"%1/xsb_aircraft.txt Line %2 : OBJ8_AIRCARFT command requires 1 argument.")
450  << path << lineNum;
452  if (tokens.size() < 2) { return false; }
453  }
454 
455  package.planes.back().objectName = tokens[1];
456  package.planes.back().objectVersion = CSLPlane::OBJ8;
457  return true;
458  }
459 
460  bool CAircraftModelLoaderXPlane::parseObj8Command(const QStringList &tokens, CSLPackage &package,
461  const QString &path, int lineNum)
462  {
463  // OBJ8 <group> <animate YES|NO> <filename>
464  if (tokens.size() != 4)
465  {
466  if (tokens.size() == 5 || tokens.size() == 6)
467  {
468  const CStatusMessage m =
469  CStatusMessage(this).error(
470  u"%1/xsb_aircraft.txt Line %2 : Unsupported IVAO CSL format - consider using CSL2XSB.")
471  << path << lineNum;
473  }
474  else
475  {
476  const CStatusMessage m =
477  CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : OBJ8 command takes 3 arguments.")
478  << path << lineNum;
480  }
481  return false;
482  }
483  if (package.planes.isEmpty())
484  {
486  CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
487  << path << lineNum);
488  return false;
489  }
490 
491  if (tokens[1] != "SOLID") { return true; }
492 
493  QString relativePath(tokens[3]);
494  normalizePath(relativePath);
495  QString fullPath(relativePath);
496  if (!doPackageSub(fullPath))
497  {
498  const CStatusMessage m = CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : package not found.")
499  << path << lineNum;
501  return false;
502  }
503 
504  // just the name of the dir containing xsbaircraft.txt
505  QString packageRootDir = package.path.mid(package.path.lastIndexOf('/') + 1);
506  package.planes.back().dirNames = QStringList { packageRootDir };
507 
508  package.planes.back().filePath = fullPath;
509  return true;
510  }
511 
512  bool CAircraftModelLoaderXPlane::parseHasGearCommand(const QStringList &tokens, CSLPackage &package,
513  const QString &path, int lineNum)
514  {
515  Q_UNUSED(tokens)
516  Q_UNUSED(package)
517  Q_UNUSED(path)
518  Q_UNUSED(lineNum)
519  return true;
520  }
521 
522  bool CAircraftModelLoaderXPlane::parseIcaoCommand(const QStringList &tokens, CSLPackage &package,
523  const QString &path, int lineNum)
524  {
525  // ICAO <code>
526  if (tokens.size() != 2)
527  {
528  const CStatusMessage m =
529  CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : ICAO command requires 1 argument.")
530  << path << lineNum;
532  return false;
533  }
534  if (package.planes.isEmpty())
535  {
537  CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
538  << path << lineNum);
539  return false;
540  }
541 
542  QString icao = tokens[1];
543  package.planes.back().icao = icao;
544  return true;
545  }
546 
547  bool CAircraftModelLoaderXPlane::parseAirlineCommand(const QStringList &tokens, CSLPackage &package,
548  const QString &path, int lineNum)
549  {
550  // AIRLINE <code> <airline>
551  if (tokens.size() != 3)
552  {
553  const CStatusMessage m =
554  CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : AIRLINE command requires 2 arguments.")
555  << path << lineNum;
557  return false;
558  }
559  if (package.planes.isEmpty())
560  {
562  CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
563  << path << lineNum);
564  return false;
565  }
566 
567  QString icao = tokens[1];
568  package.planes.back().icao = icao;
569  QString airline = tokens[2];
570  package.planes.back().airline = airline;
571  return true;
572  }
573 
574  bool CAircraftModelLoaderXPlane::parseLiveryCommand(const QStringList &tokens, CSLPackage &package,
575  const QString &path, int lineNum)
576  {
577  // LIVERY <code> <airline> <livery>
578  if (tokens.size() != 4)
579  {
580  const CStatusMessage m =
581  CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : LIVERY command requires 3 arguments.")
582  << path << lineNum;
584  return false;
585  }
586  if (package.planes.isEmpty())
587  {
589  CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
590  << path << lineNum);
591  return false;
592  }
593 
594  QString icao = tokens[1];
595  package.planes.back().icao = icao;
596  QString airline = tokens[2];
597  package.planes.back().airline = airline;
598  QString livery = tokens[3];
599  package.planes.back().livery = livery;
600  return true;
601  }
602 
603  bool CAircraftModelLoaderXPlane::parseDummyCommand(const QStringList & /* tokens */, CSLPackage & /* package */,
604  const QString & /* path */, int /*lineNum*/)
605  {
606  return true;
607  }
608 
609  CAircraftModelLoaderXPlane::CSLPackage CAircraftModelLoaderXPlane::parsePackageHeader(const QString &path,
610  const QString &content)
611  {
612  using command = std::function<bool(const QStringList &, CSLPackage &, const QString &, int)>;
613  using namespace std::placeholders;
614 
615  const QMap<QString, command> commands {
616  { "EXPORT_NAME", std::bind(&CAircraftModelLoaderXPlane::parseExportCommand, this, _1, _2, _3, _4) }
617  };
618 
619  CSLPackage package;
620  int lineNum = 0;
621 
622  QString localCopy(content);
623  QTextStream in(&localCopy);
624  while (!in.atEnd())
625  {
626  ++lineNum;
627  QString line = in.readLine();
628  auto tokens = splitString(line, [](QChar c) { return c.isSpace(); });
629  if (!tokens.empty())
630  {
631  auto it = commands.find(tokens[0]);
632  if (it != commands.end())
633  {
634  bool result = it.value()(tokens, package, path, lineNum);
635  // Stop loop once we found EXPORT command
636  if (result) break;
637  }
638  }
639  }
640  return package;
641  }
642 
643  void CAircraftModelLoaderXPlane::parseFullPackage(const QString &content, CSLPackage &package)
644  {
645  using command = std::function<bool(const QStringList &, CSLPackage &, const QString &, int)>;
646  using namespace std::placeholders;
647 
648  const QMap<QString, command> commands {
649  { "EXPORT_NAME", std::bind(&CAircraftModelLoaderXPlane::parseDummyCommand, this, _1, _2, _3, _4) },
650  { "DEPENDENCY", std::bind(&CAircraftModelLoaderXPlane::parseDependencyCommand, this, _1, _2, _3, _4) },
651  { "OBJECT", std::bind(&CAircraftModelLoaderXPlane::parseObjectCommand, this, _1, _2, _3, _4) },
652  { "TEXTURE", std::bind(&CAircraftModelLoaderXPlane::parseTextureCommand, this, _1, _2, _3, _4) },
653  { "AIRCRAFT", std::bind(&CAircraftModelLoaderXPlane::parseAircraftCommand, this, _1, _2, _3, _4) },
654  { "OBJ8_AIRCRAFT", std::bind(&CAircraftModelLoaderXPlane::parseObj8AircraftCommand, this, _1, _2, _3, _4) },
655  { "OBJ8", std::bind(&CAircraftModelLoaderXPlane::parseObj8Command, this, _1, _2, _3, _4) },
656  { "HASGEAR", std::bind(&CAircraftModelLoaderXPlane::parseHasGearCommand, this, _1, _2, _3, _4) },
657  { "ICAO", std::bind(&CAircraftModelLoaderXPlane::parseIcaoCommand, this, _1, _2, _3, _4) },
658  { "AIRLINE", std::bind(&CAircraftModelLoaderXPlane::parseAirlineCommand, this, _1, _2, _3, _4) },
659  { "LIVERY", std::bind(&CAircraftModelLoaderXPlane::parseLiveryCommand, this, _1, _2, _3, _4) },
660  { "VERT_OFFSET", std::bind(&CAircraftModelLoaderXPlane::parseDummyCommand, this, _1, _2, _3, _4) },
661  };
662 
663  int lineNum = 0;
664 
665  QString localCopy(content);
666  QTextStream in(&localCopy);
667 
668  while (!in.atEnd())
669  {
670  ++lineNum;
671  QString line = in.readLine();
672  if (line.isEmpty() || line[0] == '#') continue;
673  auto tokens = splitString(line, [](QChar c) { return c.isSpace(); });
674  if (!tokens.empty())
675  {
676  auto it = commands.find(tokens[0]);
677  if (it != commands.end())
678  {
679  bool result = it.value()(tokens, package, package.path, lineNum);
680  if (!result)
681  {
682  if (!package.planes.empty()) { package.planes.back().hasErrors = true; }
683  }
684  }
685  else
686  {
687  const CStatusMessage m =
688  CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : Unrecognized CSL command: '%3'")
689  << package.path << lineNum << tokens[0];
691  }
692  }
693  }
694 
695  // Remove all planes with errors
696  auto it = std::remove_if(package.planes.begin(), package.planes.end(),
697  [](const CSLPlane &plane) { return plane.hasErrors; });
698  package.planes.erase(it, package.planes.end());
699  }
700 
701  const QString &CAircraftModelLoaderXPlane::fileFilterFlyable()
702  {
703  static const QString f("*.acf");
704  return f;
705  }
706 
707  const QString &CAircraftModelLoaderXPlane::fileFilterCsl()
708  {
709  static const QString f("xsb_aircraft.txt");
710  return f;
711  }
712 
713 } // namespace swift::misc::simulation::xplane
static QString appendFilePaths(const QString &path1, const QString &path2)
Append file paths.
Definition: fileutils.cpp:95
static bool isExcludedDirectory(const QDir &directory, const QStringList &excludeDirectories, Qt::CaseSensitivity cs=osFileNameCaseSensitivity())
Directory to be excluded?
Definition: fileutils.cpp:242
Derived & warning(const char16_t(&format)[N])
Set the severity to warning, providing a format string.
Derived & error(const char16_t(&format)[N])
Set the severity to error, providing a format string.
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
int remove(const T &object)
Remove all elements equal to the given object, if it is contained.
Definition: sequence.h:435
Streamable status message, e.g.
constexpr static auto SeverityError
Status severities.
constexpr static auto SeverityInfo
Status severities.
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
void freezeOrder()
Current order of list will be new order values.
Value object for ICAO classification.
Value object for ICAO classification.
Value object encapsulating information about an airpot.
Definition: livery.h:29
void setCombinedCode(const QString &code)
Combined code.
Definition: livery.h:107
bool setAirlineIcaoCode(const CAirlineIcaoCode &airlineIcao)
Airline ICAO code.
Definition: livery.cpp:81
Aircraft model (used by another pilot, my models on disk)
Definition: aircraftmodel.h:71
const QString & getAircraftIcaoCodeDesignator() const
Aircraft ICAO code designator.
@ TypeOwnSimulatorModel
represents own simulator model (AI model, model on disk)
Definition: aircraftmodel.h:84
const QString & getModelString() const
Model key, either queried or loaded from simulator model.
const QString & getName() const
Name.
bool hasAircraftDesignator() const
Has aircraft designator?
const CDistributor & getDistributor() const
Get distributor.
bool setAircraftIcaoCode(const aviation::CAircraftIcaoCode &aircraftIcaoCode)
Set aircraft ICAO code.
Value object encapsulating a list of aircraft models.
bool containsModelString(const QString &modelString, Qt::CaseSensitivity sensitivity=Qt::CaseInsensitive) const
Contains model string?
Value object encapsulating information of software distributor.
Definition: distributor.h:33
const QString & getDescription() const
Get description.
Definition: distributor.h:56
bool hasDescription() const
Has description.
Definition: distributor.h:65
Simple hardcoded info about the corresponding simulator.
Definition: simulatorinfo.h:41
static const CSimulatorInfo & xplane()
Const simulator info objects.
Load the aircraft for a simulator.
void loadingProgress(const CSimulatorInfo &simulator, const QString &message, int progressPercentage)
Loading progress, normally from disk.
std::function< int(swift::misc::simulation::CAircraftModelList &, bool)> ModelConsolidationCallback
Callback to consolidate data, normally with DB data.
const CSimulatorInfo & getSimulator() const
Simulator.
QStringList getInitializedModelDirectories(const QStringList &modelDirectories, const CSimulatorInfo &simulator) const
Get model directories from settings if empty, otherwise checked and UNC path fixed.
void loadingFinished(const CStatusMessageList &status, const CSimulatorInfo &simulator, IAircraftModelLoader::LoadFinishedInfo info)
Parsing is finished or cache has been loaded.
void diskLoadingStarted(const CSimulatorInfo &simulator, IAircraftModelLoader::LoadMode loadMode)
Disk loading started.
settings::CMultiSimulatorSettings m_settings
settings
CStatusMessageList m_loadingMessages
loading messages
@ LoadInBackground
load in background, asyncronously
@ LoadDirectly
load synchronously (blocking), normally for testing
void setModelsForSimulator(const CAircraftModelList &models, const CSimulatorInfo &simulator)
Set models.
Definition: modelcaches.h:597
CStatusMessage clearCachedModels(const CSimulatorInfo &simulator)
Look like IMultiSimulatorModelCaches interface.
Definition: modelcaches.h:554
QStringList getModelExcludeDirectoryPatternsOrDefault(const CSimulatorInfo &simulator) const
Model exclude patterns per simulator.
virtual void startLoadingFromDisk(LoadMode mode, const ModelConsolidationCallback &modelConsolidation, const QStringList &modelDirectories)
Start the loading process from disk.
void updateInstalledModels(const CAircraftModelList &models)
Parsed or injected models.
Free functions in swift::misc.
QStringList splitString(const QString &s, F predicate)
Split a string into multiple strings, using a predicate function to identify the split points.
Definition: stringutils.h:120
AcfProperties extractAcfProperties(const std::string &filePath)
Extract ACF properties from an aircraft file.
Definition: qtfreeutils.h:170
#define SWIFT_AUDIT_X(COND, WHERE, WHAT)
A weaker kind of verify.
Definition: verify.h:38