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 <algorithm>
7 #include <cstring>
8 #include <functional>
9 
10 #include <QChar>
11 #include <QDateTime>
12 #include <QDir>
13 #include <QDirIterator>
14 #include <QFile>
15 #include <QFileInfo>
16 #include <QFlags>
17 #include <QIODevice>
18 #include <QList>
19 #include <QMap>
20 #include <QRegularExpression>
21 #include <QStringBuilder>
22 #include <QTextStream>
23 
24 #include "config/buildconfig.h"
27 #include "misc/aviation/livery.h"
28 #include "misc/directoryutils.h"
29 #include "misc/fileutils.h"
30 #include "misc/logmessage.h"
36 #include "misc/statusmessage.h"
37 #include "misc/stringutils.h"
38 #include "misc/verify.h"
39 #include "misc/worker.h"
40 
41 using namespace swift::config;
42 using namespace swift::misc;
43 using namespace swift::misc::aviation;
44 using namespace swift::misc::simulation;
45 using namespace swift::misc::network;
46 
47 namespace swift::misc::simulation::xplane
48 {
50  static void normalizePath(QString &path)
51  {
52  for (auto &e : path)
53  {
54  if (e == '/' || e == ':' || e == '\\') { e = '/'; }
55  }
56  }
57 
59  static QString descriptionForFlyableModel(const CAircraftModel &model)
60  {
61  if (!model.getName().isEmpty())
62  {
63  if (model.getDistributor().hasDescription() &&
64  !model.getName().contains(model.getDistributor().getDescription()))
65  {
66  return u"[ACF] " % model.getName() % u" by " % model.getDistributor().getDescription();
67  }
68  else { return u"[ACF] " % model.getName(); }
69  }
70  else if (model.hasAircraftDesignator())
71  {
72  if (model.getDistributor().hasDescription())
73  {
74  return u"[ACF] " % model.getAircraftIcaoCodeDesignator() % u" by " %
76  }
77  else { return u"[ACF] " % model.getAircraftIcaoCodeDesignator(); }
78  }
79  return QStringLiteral("[ACF]");
80  }
81 
82  CAircraftModelLoaderXPlane::CAircraftModelLoaderXPlane(QObject *parent)
83  : IAircraftModelLoader(CSimulatorInfo::xplane(), parent)
84  {}
85 
87  {
88  // that should be safe as long as the worker uses deleteLater (which it does)
89  if (m_parserWorker) { m_parserWorker->waitForFinished(); }
90  }
91 
93  const ModelConsolidationCallback &modelConsolidation,
94  const QStringList &modelDirectories)
95  {
96  const CSimulatorInfo simulator = CSimulatorInfo::xplane();
97  const QStringList modelDirs = this->getInitializedModelDirectories(modelDirectories, simulator);
98  const QStringList excludedDirectoryPatterns(
100 
101  if (modelDirs.isEmpty())
102  {
104  emit this->loadingFinished(
105  CStatusMessage(this, CStatusMessage::SeverityError, u"XPlane model directories '%1' are empty")
106  << modelDirectories.join(", "),
107  simulator, ParsedData);
108  return;
109  }
110 
111  if (mode.testFlag(LoadInBackground))
112  {
113  if (m_parserWorker && !m_parserWorker->isFinished()) { return; }
114  emit this->diskLoadingStarted(simulator, mode);
115 
116  m_parserWorker = CWorker::fromTask(this, "CAircraftModelLoaderXPlane::performParsing",
117  [this, modelDirs, excludedDirectoryPatterns, modelConsolidation]() {
118  auto models =
119  this->performParsing(modelDirs, excludedDirectoryPatterns);
120  if (modelConsolidation) { modelConsolidation(models, true); }
121  return models;
122  });
123  m_parserWorker->thenWithResult<CAircraftModelList>(this, [=](const auto &models) {
124  this->updateInstalledModels(models);
126  emit this->loadingFinished(m_loadingMessages, simulator, ParsedData);
127  });
128  }
129  else if (mode.testFlag(LoadDirectly))
130  {
131  emit this->diskLoadingStarted(simulator, mode);
132  CAircraftModelList models(this->performParsing(modelDirs, excludedDirectoryPatterns));
133  this->updateInstalledModels(models);
134  }
135  }
136 
138  {
139  return !m_parserWorker || m_parserWorker->isFinished();
140  }
141 
143  {
145  const CStatusMessage m = CStatusMessage(this, CStatusMessage::SeverityInfo, u"XPlane updated '%1' models")
146  << models.size();
148  }
149 
150  QString CAircraftModelLoaderXPlane::CSLPlane::getModelName() const
151  {
152  QString modelName = dirNames.join(' ') % u' ' % objectName;
153  if (objectVersion == OBJ7) { modelName += u' ' % textureName; }
154  return std::move(modelName).trimmed();
155  }
156 
157  CAircraftModelList CAircraftModelLoaderXPlane::performParsing(const QStringList &rootDirectories,
158  const QStringList &excludeDirectories)
159  {
160  CAircraftModelList allModels;
161  for (const QString &rootDirectory : rootDirectories)
162  {
163  allModels.push_back(parseCslPackages(rootDirectory, excludeDirectories));
164  allModels.push_back(parseFlyableAirplanes(rootDirectory, excludeDirectories));
165  }
166  return allModels;
167  }
168 
170  void CAircraftModelLoaderXPlane::addUniqueModel(const CAircraftModel &model, CAircraftModelList &models)
171  {
172  if (models.containsModelString(model.getModelString()))
173  {
174  const CStatusMessage m =
175  CStatusMessage(this).warning(
176  u"XPlane model '%1' exists already! Potential model string conflict! Ignoring it.")
177  << model.getModelString();
179  }
180  models.push_back(model);
181  }
182 
183  CAircraftModelList CAircraftModelLoaderXPlane::parseFlyableAirplanes(const QString &rootDirectory,
184  const QStringList &excludeDirectories)
185  {
186  Q_UNUSED(excludeDirectories)
187  if (rootDirectory.isEmpty()) { return {}; }
188 
189  QDir searchPath(rootDirectory, fileFilterFlyable());
191 
192  emit loadingProgress(this->getSimulator(),
193  QStringLiteral("Parsing flyable airplanes in '%1'").arg(rootDirectory), -1);
194 
195  CAircraftModelList installedModels;
196  while (aircraftIt.hasNext())
197  {
198  aircraftIt.next();
199  if (CFileUtils::isExcludedDirectory(aircraftIt.fileInfo(), excludeDirectories, Qt::CaseInsensitive))
200  {
201  continue;
202  }
203 
204  using namespace swift::misc::simulation::xplane::qtfreeutils;
205  AcfProperties acfProperties = extractAcfProperties(aircraftIt.filePath().toStdString());
206 
207  const CDistributor dist({}, QString::fromStdString(acfProperties.author), {}, {}, CSimulatorInfo::XPLANE);
208  CAircraftModel model;
210  model.setDescription(QString::fromStdString(acfProperties.modelDescription));
211  model.setName(QString::fromStdString(acfProperties.modelName));
212  model.setDistributor(dist);
213  model.setModelString(QString::fromStdString(acfProperties.modelString));
214  if (!model.hasDescription()) { model.setDescription(descriptionForFlyableModel(model)); }
215  model.setModelType(CAircraftModel::TypeOwnSimulatorModel);
216  model.setSimulator(CSimulatorInfo::xplane());
217  model.setFileDetailsAndTimestamp(aircraftIt.fileInfo());
218  model.setModelMode(CAircraftModel::Exclude);
219  addUniqueModel(model, installedModels);
220 
221  const QString baseModelString = model.getModelString();
222  QDirIterator liveryIt(
223  CFileUtils::appendFilePaths(aircraftIt.fileInfo().canonicalPath(), QStringLiteral("liveries")),
225  emit this->loadingProgress(
226  this->getSimulator(),
227  QStringLiteral("Parsing flyable liveries in '%1'").arg(aircraftIt.fileInfo().canonicalPath()), -1);
228  while (liveryIt.hasNext())
229  {
230  liveryIt.next();
231  model.setModelString(baseModelString % u' ' % liveryIt.fileName());
232  addUniqueModel(model, installedModels);
233  }
234  }
235  return installedModels;
236  }
237 
238  CAircraftModelList CAircraftModelLoaderXPlane::parseCslPackages(const QString &rootDirectory,
239  const QStringList &excludeDirectories)
240  {
241  Q_UNUSED(excludeDirectories);
242  if (rootDirectory.isEmpty()) { return {}; }
243 
244  m_cslPackages.clear();
245 
246  QDir searchPath(rootDirectory, fileFilterCsl());
248  while (it.hasNext())
249  {
250  QString packageFile = it.next();
251  if (CFileUtils::isExcludedDirectory(it.filePath(), excludeDirectories)) { continue; }
252 
253  const QString packageFilePath = it.fileInfo().absolutePath();
254  QFile file(packageFile);
255  const bool res = file.open(QIODevice::ReadOnly);
256  SWIFT_VERIFY_X(res, Q_FUNC_INFO, "Could not open package file");
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  const bool res = file.open(QIODevice::ReadOnly);
277  SWIFT_VERIFY_X(res, Q_FUNC_INFO, "Could not open package file");
278  QString content;
279 
280  QTextStream ts(&file);
281  content.append(ts.readAll());
282  file.close();
283  parseFullPackage(content, package);
284 
285  for (const auto &plane : std::as_const(package.planes))
286  {
287  if (installedModels.containsModelString(plane.getModelName()))
288  {
289  const CStatusMessage msg =
290  CStatusMessage(this).warning(
291  u"XPlane model '%1' exists already! Potential model string conflict! Ignoring it.")
292  << plane.getModelName();
294  continue;
295  }
296 
297  CAircraftModel model(plane.getModelName(), CAircraftModel::TypeOwnSimulatorModel);
298  const CAircraftIcaoCode icao(plane.icao);
299  const QFileInfo modelFileInfo(plane.filePath);
300  model.setFileDetailsAndTimestamp(modelFileInfo);
301  model.setAircraftIcaoCode(icao);
302 
303  if (CBuildConfig::isLocalDeveloperDebugBuild())
304  {
305  SWIFT_AUDIT_X(modelFileInfo.exists(), Q_FUNC_INFO, "Model does NOT exist");
306  }
307 
308  CLivery livery;
309  livery.setCombinedCode(plane.livery);
310  CAirlineIcaoCode airline(plane.airline);
311  livery.setAirlineIcaoCode(airline);
312  model.setLivery(livery);
313 
314  model.setSimulator(CSimulatorInfo::xplane());
315  QString modelDescription("[CSL]");
316  if (plane.objectVersion == CSLPlane::OBJ7) { modelDescription += "[OBJ7]"; }
317  else if (plane.objectVersion == CSLPlane::OBJ8) { modelDescription += "[OBJ8]"; }
318  model.setDescription(modelDescription);
319  installedModels.push_back(model);
320  }
321  }
322  return installedModels;
323  }
324 
325  bool CAircraftModelLoaderXPlane::doPackageSub(QString &ioPath)
326  {
327  for (auto i = m_cslPackages.cbegin(); i != m_cslPackages.cend(); ++i)
328  {
329  if (strncmp(qPrintable(i->name), qPrintable(ioPath), static_cast<size_t>(i->name.size())) == 0)
330  {
331  ioPath.remove(0, i->name.size());
332  ioPath.insert(0, i->path);
333  return true;
334  }
335  }
336  return false;
337  }
338 
339  bool CAircraftModelLoaderXPlane::parseExportCommand(const QStringList &tokens, CSLPackage &package,
340  const QString &path, int lineNum)
341  {
342  if (tokens.size() != 2)
343  {
344  const CStatusMessage m =
345  CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : EXPORT_NAME command requires 1 argument.")
346  << path << lineNum;
348  return false;
349  }
350 
351  auto p = std::find_if(m_cslPackages.cbegin(), m_cslPackages.cend(),
352  [&tokens](const CSLPackage &p) { return p.name == tokens[1]; });
353  if (p == m_cslPackages.cend())
354  {
355  package.path = path;
356  package.name = tokens[1];
357  return true;
358  }
359  else
360  {
361  const CStatusMessage m =
362  CStatusMessage(this).error(u"XPlane package name '%1' already in use by '%2' reqested by use by '%3'")
363  << tokens[1] << p->path << path;
365  return false;
366  }
367  }
368 
369  bool CAircraftModelLoaderXPlane::parseDependencyCommand(const QStringList &tokens, CSLPackage &package,
370  const QString &path, int lineNum)
371  {
372  Q_UNUSED(package);
373  if (tokens.size() != 2)
374  {
375  const CStatusMessage m =
376  CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : DEPENDENCY command requires 1 argument.")
377  << path << lineNum;
379  return false;
380  }
381 
382  if (std::count_if(m_cslPackages.cbegin(), m_cslPackages.cend(),
383  [&tokens](const CSLPackage &p) { return p.name == tokens[1]; }) == 0)
384  {
385  const CStatusMessage m = CStatusMessage(this).error(
386  u"XPlane required package %1 not found. Aborting processing of this package.")
387  << tokens[1];
389  return false;
390  }
391 
392  return true;
393  }
394 
397  QString readLineFrom(QTextStream &stream)
398  {
399  QString line;
400  do {
401  line = stream.readLine();
402  }
403  while (line.isEmpty() && !stream.atEnd());
404  return line;
405  }
406 
407  bool CAircraftModelLoaderXPlane::parseObjectCommand(const QStringList &, CSLPackage &package, const QString &path,
408  int lineNum)
409  {
410  package.planes.push_back(CSLPlane());
411 
412  const auto m = CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : Unsupported legacy CSL format.")
413  << path << lineNum;
415  return false;
416  }
417 
418  bool CAircraftModelLoaderXPlane::parseTextureCommand(const QStringList &, CSLPackage &package, const QString &path,
419  int lineNum)
420  {
421  if (!package.planes.isEmpty() && !package.planes.back().hasErrors)
422  {
423  const auto m = CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : Unsupported legacy CSL format.")
424  << path << lineNum;
426  }
427  return false;
428  }
429 
430  bool CAircraftModelLoaderXPlane::parseAircraftCommand(const QStringList &, CSLPackage &package, const QString &path,
431  int lineNum)
432  {
433  package.planes.push_back(CSLPlane());
434 
435  const auto m = CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : Unsupported legacy CSL format.")
436  << path << lineNum;
438  return false;
439  }
440 
441  bool CAircraftModelLoaderXPlane::parseObj8AircraftCommand(const QStringList &tokens, CSLPackage &package,
442  const QString &path, int lineNum)
443  {
444  package.planes.push_back(CSLPlane());
445 
446  // OBJ8_AIRCRAFT <path>
447  if (tokens.size() != 2)
448  {
449  const CStatusMessage m = CStatusMessage(this).warning(
450  u"%1/xsb_aircraft.txt Line %2 : OBJ8_AIRCARFT command requires 1 argument.")
451  << path << lineNum;
453  if (tokens.size() < 2) { return false; }
454  }
455 
456  package.planes.back().objectName = tokens[1];
457  package.planes.back().objectVersion = CSLPlane::OBJ8;
458  return true;
459  }
460 
461  bool CAircraftModelLoaderXPlane::parseObj8Command(const QStringList &tokens, CSLPackage &package,
462  const QString &path, int lineNum)
463  {
464  // OBJ8 <group> <animate YES|NO> <filename>
465  if (tokens.size() != 4)
466  {
467  if (tokens.size() == 5 || tokens.size() == 6)
468  {
469  const CStatusMessage m =
470  CStatusMessage(this).error(
471  u"%1/xsb_aircraft.txt Line %2 : Unsupported IVAO CSL format - consider using CSL2XSB.")
472  << path << lineNum;
474  }
475  else
476  {
477  const CStatusMessage m =
478  CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : OBJ8 command takes 3 arguments.")
479  << path << lineNum;
481  }
482  return false;
483  }
484  if (package.planes.isEmpty())
485  {
487  CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
488  << path << lineNum);
489  return false;
490  }
491 
492  if (tokens[1] != "SOLID") { return true; }
493 
494  QString relativePath(tokens[3]);
495  normalizePath(relativePath);
496  QString fullPath(relativePath);
497  if (!doPackageSub(fullPath))
498  {
499  const CStatusMessage m = CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : package not found.")
500  << path << lineNum;
502  return false;
503  }
504 
505  // just the name of the dir containing xsbaircraft.txt
506  QString packageRootDir = package.path.mid(package.path.lastIndexOf('/') + 1);
507  package.planes.back().dirNames = QStringList { packageRootDir };
508 
509  package.planes.back().filePath = fullPath;
510  return true;
511  }
512 
513  bool CAircraftModelLoaderXPlane::parseHasGearCommand(const QStringList &tokens, CSLPackage &package,
514  const QString &path, int lineNum)
515  {
516  Q_UNUSED(tokens)
517  Q_UNUSED(package)
518  Q_UNUSED(path)
519  Q_UNUSED(lineNum)
520  return true;
521  }
522 
523  bool CAircraftModelLoaderXPlane::parseIcaoCommand(const QStringList &tokens, CSLPackage &package,
524  const QString &path, int lineNum)
525  {
526  // ICAO <code>
527  if (tokens.size() != 2)
528  {
529  const CStatusMessage m =
530  CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : ICAO command requires 1 argument.")
531  << path << lineNum;
533  return false;
534  }
535  if (package.planes.isEmpty())
536  {
538  CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
539  << path << lineNum);
540  return false;
541  }
542 
543  QString icao = tokens[1];
544  package.planes.back().icao = icao;
545  return true;
546  }
547 
548  bool CAircraftModelLoaderXPlane::parseAirlineCommand(const QStringList &tokens, CSLPackage &package,
549  const QString &path, int lineNum)
550  {
551  // AIRLINE <code> <airline>
552  if (tokens.size() != 3)
553  {
554  const CStatusMessage m =
555  CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : AIRLINE command requires 2 arguments.")
556  << path << lineNum;
558  return false;
559  }
560  if (package.planes.isEmpty())
561  {
563  CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
564  << path << lineNum);
565  return false;
566  }
567 
568  QString icao = tokens[1];
569  package.planes.back().icao = icao;
570  QString airline = tokens[2];
571  package.planes.back().airline = airline;
572  return true;
573  }
574 
575  bool CAircraftModelLoaderXPlane::parseLiveryCommand(const QStringList &tokens, CSLPackage &package,
576  const QString &path, int lineNum)
577  {
578  // LIVERY <code> <airline> <livery>
579  if (tokens.size() != 4)
580  {
581  const CStatusMessage m =
582  CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : LIVERY command requires 3 arguments.")
583  << path << lineNum;
585  return false;
586  }
587  if (package.planes.isEmpty())
588  {
590  CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
591  << path << lineNum);
592  return false;
593  }
594 
595  QString icao = tokens[1];
596  package.planes.back().icao = icao;
597  QString airline = tokens[2];
598  package.planes.back().airline = airline;
599  QString livery = tokens[3];
600  package.planes.back().livery = livery;
601  return true;
602  }
603 
604  bool CAircraftModelLoaderXPlane::parseDummyCommand(const QStringList & /* tokens */, CSLPackage & /* package */,
605  const QString & /* path */, int /*lineNum*/)
606  {
607  return true;
608  }
609 
610  CAircraftModelLoaderXPlane::CSLPackage CAircraftModelLoaderXPlane::parsePackageHeader(const QString &path,
611  const QString &content)
612  {
613  using command = std::function<bool(const QStringList &, CSLPackage &, const QString &, int)>;
614  using namespace std::placeholders;
615 
616  const QMap<QString, command> commands { { "EXPORT_NAME",
617  [this](auto &&tokens, auto &&package, auto &&path, auto &&lineNum) {
618  return parseExportCommand(
619  std::forward<decltype(tokens)>(tokens),
620  std::forward<decltype(package)>(package),
621  std::forward<decltype(path)>(path),
622  std::forward<decltype(lineNum)>(lineNum));
623  } } };
624 
625  CSLPackage package;
626  int lineNum = 0;
627 
628  QString localCopy(content);
629  QTextStream in(&localCopy);
630  while (!in.atEnd())
631  {
632  ++lineNum;
633  QString line = in.readLine();
634  auto tokens = splitString(line, [](QChar c) { return c.isSpace(); });
635  if (!tokens.empty())
636  {
637  auto it = commands.find(tokens[0]);
638  if (it != commands.end())
639  {
640  bool result = it.value()(tokens, package, path, lineNum);
641  // Stop loop once we found EXPORT command
642  if (result) break;
643  }
644  }
645  }
646  return package;
647  }
648 
649  void CAircraftModelLoaderXPlane::parseFullPackage(const QString &content, CSLPackage &package)
650  {
651  using command = std::function<bool(const QStringList &, CSLPackage &, const QString &, int)>;
652  using namespace std::placeholders;
653 
654  const QMap<QString, command> commands {
655  { "EXPORT_NAME",
656  [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
657  return parseDummyCommand(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
658  std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
659  } },
660  { "DEPENDENCY",
661  [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
662  return parseDependencyCommand(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
663  std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
664  } },
665  { "OBJECT",
666  [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
667  return parseObjectCommand(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
668  std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
669  } },
670  { "TEXTURE",
671  [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
672  return parseTextureCommand(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
673  std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
674  } },
675  {
676  "AIRCRAFT",
677  [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
678  return parseAircraftCommand(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
679  std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
680  },
681  },
682  { "OBJ8_AIRCRAFT",
683  [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
684  return parseObj8AircraftCommand(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
685  std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
686  } },
687  { "OBJ8",
688  [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
689  return parseObj8Command(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
690  std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
691  } },
692  { "HASGEAR",
693  [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
694  return parseHasGearCommand(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
695  std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
696  } },
697  { "ICAO",
698  [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
699  return parseIcaoCommand(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
700  std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
701  } },
702  { "AIRLINE",
703  [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
704  return parseAirlineCommand(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
705  std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
706  } },
707  { "LIVERY",
708  [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
709  return parseLiveryCommand(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
710  std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
711  } },
712  { "VERT_OFFSET",
713  [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
714  return parseDummyCommand(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
715  std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
716  } },
717  };
718 
719  int lineNum = 0;
720 
721  QString localCopy(content);
722  QTextStream in(&localCopy);
723 
724  while (!in.atEnd())
725  {
726  ++lineNum;
727  QString line = in.readLine();
728  if (line.isEmpty() || line[0] == '#') continue;
729  auto tokens = splitString(line, [](QChar c) { return c.isSpace(); });
730  if (!tokens.empty())
731  {
732  auto it = commands.find(tokens[0]);
733  if (it != commands.end())
734  {
735  bool result = it.value()(tokens, package, package.path, lineNum);
736  if (!result)
737  {
738  if (!package.planes.empty()) { package.planes.back().hasErrors = true; }
739  }
740  }
741  else
742  {
743  const CStatusMessage m =
744  CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : Unrecognized CSL command: '%3'")
745  << package.path << lineNum << tokens[0];
747  }
748  }
749  }
750 
751  // Remove all planes with errors
752  auto it = std::remove_if(package.planes.begin(), package.planes.end(),
753  [](const CSLPlane &plane) { return plane.hasErrors; });
754  package.planes.erase(it, package.planes.end());
755  }
756 
757  const QString &CAircraftModelLoaderXPlane::fileFilterFlyable()
758  {
759  static const QString f("*.acf");
760  return f;
761  }
762 
763  const QString &CAircraftModelLoaderXPlane::fileFilterCsl()
764  {
765  static const QString f("xsb_aircraft.txt");
766  return f;
767  }
768 
769 } // 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:239
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
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:225
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:79
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.
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:119
bool isSpace(char32_t ucs4)
QList< T >::reference back()
bool empty() const const
bool isEmpty() const const
qsizetype size() const const
T value(qsizetype i) const const
QString & append(QChar ch)
QChar & back()
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString fromStdString(const std::string &str)
QString & insert(qsizetype position, QChar ch)
bool isEmpty() const const
QString mid(qsizetype position, qsizetype n) &&
void push_back(QChar ch)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString trimmed() const const
QString join(QChar separator) const const
CaseInsensitive
bool atEnd() const const
QString readLine(qint64 maxlen)
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
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.
Definition: verify.h:26