swift
Loading...
Searching...
No Matches
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"
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
41using namespace swift::config;
42using namespace swift::misc;
43using namespace swift::misc::aviation;
44using namespace swift::misc::simulation;
45using namespace swift::misc::network;
46
47namespace swift::misc::simulation::xplane
48{
50 static void normalizePath(QString &path)
51 {
52 for (auto &e : path)
53 {
54 if (e == '/' || e == ':' || e == '\\')
55 {
56 e = '/';
57 }
58 }
59 }
60
62 static QString descriptionForFlyableModel(const CAircraftModel &model)
63 {
64 if (!model.getName().isEmpty())
65 {
66 if (model.getDistributor().hasDescription() &&
68 {
69 return u"[ACF] " % model.getName() % u" by " % model.getDistributor().getDescription();
70 }
71 else
72 {
73 return u"[ACF] " % model.getName();
74 }
75 }
76 else if (model.hasAircraftDesignator())
77 {
78 if (model.getDistributor().hasDescription())
79 {
80 return u"[ACF] " % model.getAircraftIcaoCodeDesignator() % u" by " %
82 }
83 else
84 {
85 return u"[ACF] " % model.getAircraftIcaoCodeDesignator();
86 }
87 }
88 return QStringLiteral("[ACF]");
89 }
90
94
96 {
97 // that should be safe as long as the worker uses deleteLater (which it does)
98 if (m_parserWorker)
99 {
100 m_parserWorker->waitForFinished();
101 }
102 }
103
105 const ModelConsolidationCallback &modelConsolidation,
106 const QStringList &modelDirectories)
107 {
108 const CSimulatorInfo simulator = CSimulatorInfo::xplane();
109 const QStringList modelDirs = this->getInitializedModelDirectories(modelDirectories, simulator);
110 const QStringList excludedDirectoryPatterns(
111 m_settings.getModelExcludeDirectoryPatternsOrDefault(simulator)); // copy
112
113 if (modelDirs.isEmpty())
114 {
116 emit this->loadingFinished(
117 CStatusMessage(this, CStatusMessage::SeverityError, u"XPlane model directories '%1' are empty")
118 << modelDirectories.join(", "),
119 simulator, ParsedData);
120 return;
121 }
122
123 if (mode.testFlag(LoadInBackground))
124 {
125 if (m_parserWorker && !m_parserWorker->isFinished())
126 {
127 return;
128 }
129 emit this->diskLoadingStarted(simulator, mode);
130
131 m_parserWorker = CWorker::fromTask(this, "CAircraftModelLoaderXPlane::performParsing",
132 [this, modelDirs, excludedDirectoryPatterns, modelConsolidation]() {
133 auto models =
134 this->performParsing(modelDirs, excludedDirectoryPatterns);
135 if (modelConsolidation)
136 {
137 modelConsolidation(models, true);
138 }
139 return models;
140 });
141 m_parserWorker->thenWithResult<CAircraftModelList>(this, [=, this](const auto &models) {
142 this->updateInstalledModels(models);
143 m_loadingMessages.freezeOrder();
144 emit this->loadingFinished(m_loadingMessages, simulator, ParsedData);
145 });
146 }
147 else if (mode.testFlag(LoadDirectly))
148 {
149 emit this->diskLoadingStarted(simulator, mode);
150 CAircraftModelList models(this->performParsing(modelDirs, excludedDirectoryPatterns));
151 this->updateInstalledModels(models);
152 }
153 }
154
156 {
157 return !m_parserWorker || m_parserWorker->isFinished();
158 }
159
161 {
163 const CStatusMessage m = CStatusMessage(this, CStatusMessage::SeverityInfo, u"XPlane updated '%1' models")
164 << models.size();
165 m_loadingMessages.push_back(m);
166 }
167
168 QString CAircraftModelLoaderXPlane::CSLPlane::getModelName() const
169 {
170 QString modelName = dirNames.join(' ') % u' ' % objectName;
171 if (objectVersion == OBJ7)
172 {
173 modelName += u' ' % textureName;
174 }
175 return std::move(modelName).trimmed();
176 }
177
178 CAircraftModelList CAircraftModelLoaderXPlane::performParsing(const QStringList &rootDirectories,
179 const QStringList &excludeDirectories)
180 {
181 CAircraftModelList allModels;
182 for (const QString &rootDirectory : rootDirectories)
183 {
184 allModels.push_back(parseCslPackages(rootDirectory, excludeDirectories));
185 allModels.push_back(parseFlyableAirplanes(rootDirectory, excludeDirectories));
186 }
187 return allModels;
188 }
189
191 void CAircraftModelLoaderXPlane::addUniqueModel(const CAircraftModel &model, CAircraftModelList &models)
192 {
193 if (models.containsModelString(model.getModelString()))
194 {
195 const CStatusMessage m =
197 u"XPlane model '%1' exists already! Potential model string conflict! Ignoring it.")
198 << model.getModelString();
200 }
201 models.push_back(model);
202 }
203
204 CAircraftModelList CAircraftModelLoaderXPlane::parseFlyableAirplanes(const QString &rootDirectory,
205 const QStringList &excludeDirectories)
206 {
207 Q_UNUSED(excludeDirectories)
208 if (rootDirectory.isEmpty())
209 {
210 return {};
211 }
212
213 QDir searchPath(rootDirectory, fileFilterFlyable());
214 QDirIterator aircraftIt(searchPath, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
215
216 emit loadingProgress(this->getSimulator(),
217 QStringLiteral("Parsing flyable airplanes in '%1'").arg(rootDirectory), -1);
218
219 CAircraftModelList installedModels;
220 while (aircraftIt.hasNext())
221 {
222 aircraftIt.next();
223 if (CFileUtils::isExcludedDirectory(aircraftIt.fileInfo(), excludeDirectories, Qt::CaseInsensitive))
224 {
225 continue;
226 }
227
228 using namespace swift::misc::simulation::xplane::qtfreeutils;
229 AcfProperties acfProperties = extractAcfProperties(aircraftIt.filePath().toStdString());
230
231 const CDistributor dist({}, QString::fromStdString(acfProperties.author), {}, {}, CSimulatorInfo::XPLANE);
232 CAircraftModel model;
234 model.setDescription(QString::fromStdString(acfProperties.modelDescription));
235 model.setName(QString::fromStdString(acfProperties.modelName));
236 model.setDistributor(dist);
237 model.setModelString(QString::fromStdString(acfProperties.modelString));
238 if (!model.hasDescription())
239 {
240 model.setDescription(descriptionForFlyableModel(model));
241 }
242 model.setModelType(CAircraftModel::TypeOwnSimulatorModel);
243 model.setSimulator(CSimulatorInfo::xplane());
244 model.setFileDetailsAndTimestamp(aircraftIt.fileInfo());
245 model.setModelMode(CAircraftModel::Exclude);
246 addUniqueModel(model, installedModels);
247
248 const QString baseModelString = model.getModelString();
249 QDirIterator liveryIt(
250 CFileUtils::appendFilePaths(aircraftIt.fileInfo().canonicalPath(), QStringLiteral("liveries")),
252 emit this->loadingProgress(
253 this->getSimulator(),
254 QStringLiteral("Parsing flyable liveries in '%1'").arg(aircraftIt.fileInfo().canonicalPath()), -1);
255 while (liveryIt.hasNext())
256 {
257 liveryIt.next();
258 model.setModelString(baseModelString % u' ' % liveryIt.fileName());
259 addUniqueModel(model, installedModels);
260 }
261 }
262 return installedModels;
263 }
264
265 CAircraftModelList CAircraftModelLoaderXPlane::parseCslPackages(const QString &rootDirectory,
266 const QStringList &excludeDirectories)
267 {
268 Q_UNUSED(excludeDirectories);
269 if (rootDirectory.isEmpty())
270 {
271 return {};
272 }
273
274 m_cslPackages.clear();
275
276 QDir searchPath(rootDirectory, fileFilterCsl());
277 QDirIterator it(searchPath, QDirIterator::Subdirectories);
278 while (it.hasNext())
279 {
280 QString packageFile = it.next();
281 if (CFileUtils::isExcludedDirectory(it.filePath(), excludeDirectories))
282 {
283 continue;
284 }
285
286 const QString packageFilePath = it.fileInfo().absolutePath();
287 QFile file(packageFile);
288 const bool res = file.open(QIODevice::ReadOnly);
289 SWIFT_VERIFY_X(res, Q_FUNC_INFO, "Could not open package file");
290 QString content;
291
292 QTextStream ts(&file);
293 content.append(ts.readAll());
294 file.close();
295
296 const auto package = parsePackageHeader(packageFilePath, content);
297 if (package.hasValidHeader()) m_cslPackages.push_back(package);
298 }
299
300 CAircraftModelList installedModels;
301
302 // Now we do a full run
303 for (auto &package : m_cslPackages)
304 {
305 const QString packageFile = CFileUtils::appendFilePaths(package.path, QStringLiteral("xsb_aircraft.txt"));
306 emit this->loadingProgress(this->getSimulator(), QStringLiteral("Parsing CSL '%1'").arg(packageFile), -1);
307
308 QFile file(packageFile);
309 const bool res = file.open(QIODevice::ReadOnly);
310 SWIFT_VERIFY_X(res, Q_FUNC_INFO, "Could not open package file");
311 QString content;
312
313 QTextStream ts(&file);
314 content.append(ts.readAll());
315 file.close();
316 parseFullPackage(content, package);
317
318 for (const auto &plane : std::as_const(package.planes))
319 {
320 if (installedModels.containsModelString(plane.getModelName()))
321 {
322 const CStatusMessage msg =
323 CStatusMessage(this).warning(
324 u"XPlane model '%1' exists already! Potential model string conflict! Ignoring it.")
325 << plane.getModelName();
326 m_loadingMessages.push_back(msg);
327 continue;
328 }
329
330 CAircraftModel model(plane.getModelName(), CAircraftModel::TypeOwnSimulatorModel);
331 const CAircraftIcaoCode icao(plane.icao);
332 const QFileInfo modelFileInfo(plane.filePath);
333 model.setFileDetailsAndTimestamp(modelFileInfo);
334 model.setAircraftIcaoCode(icao);
335
337 {
338 SWIFT_AUDIT_X(modelFileInfo.exists(), Q_FUNC_INFO, "Model does NOT exist");
339 }
340
341 CLivery livery;
342 livery.setCombinedCode(plane.livery);
343 CAirlineIcaoCode airline(plane.airline);
344 livery.setAirlineIcaoCode(airline);
345 model.setLivery(livery);
346
347 model.setSimulator(CSimulatorInfo::xplane());
348 QString modelDescription("[CSL]");
349 if (plane.objectVersion == CSLPlane::OBJ7)
350 {
351 modelDescription += "[OBJ7]";
352 }
353 else if (plane.objectVersion == CSLPlane::OBJ8)
354 {
355 modelDescription += "[OBJ8]";
356 }
357 model.setDescription(modelDescription);
358 installedModels.push_back(model);
359 }
360 }
361 return installedModels;
362 }
363
364 bool CAircraftModelLoaderXPlane::doPackageSub(QString &ioPath)
365 {
366 for (auto i = m_cslPackages.cbegin(); i != m_cslPackages.cend(); ++i)
367 {
368 if (strncmp(qPrintable(i->name), qPrintable(ioPath), static_cast<size_t>(i->name.size())) == 0)
369 {
370 ioPath.remove(0, i->name.size());
371 ioPath.insert(0, i->path);
372 return true;
373 }
374 }
375 return false;
376 }
377
378 bool CAircraftModelLoaderXPlane::parseExportCommand(const QStringList &tokens, CSLPackage &package,
379 const QString &path, int lineNum)
380 {
381 if (tokens.size() != 2)
382 {
383 const CStatusMessage m =
384 CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : EXPORT_NAME command requires 1 argument.")
385 << path << lineNum;
386 m_loadingMessages.push_back(m);
387 return false;
388 }
389
390 auto p = std::ranges::find_if(m_cslPackages, [&tokens](const CSLPackage &p) { return p.name == tokens[1]; });
391 if (p == m_cslPackages.cend())
392 {
393 package.path = path;
394 package.name = tokens[1];
395 return true;
396 }
397 else
398 {
399 const CStatusMessage m =
400 CStatusMessage(this).error(u"XPlane package name '%1' already in use by '%2' reqested by use by '%3'")
401 << tokens[1] << p->path << path;
402 m_loadingMessages.push_back(m);
403 return false;
404 }
405 }
406
407 bool CAircraftModelLoaderXPlane::parseDependencyCommand(const QStringList &tokens, CSLPackage &package,
408 const QString &path, int lineNum)
409 {
410 Q_UNUSED(package);
411 if (tokens.size() != 2)
412 {
413 const CStatusMessage m =
414 CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : DEPENDENCY command requires 1 argument.")
415 << path << lineNum;
416 m_loadingMessages.push_back(m);
417 return false;
418 }
419
420 if (std::count_if(m_cslPackages.cbegin(), m_cslPackages.cend(),
421 [&tokens](const CSLPackage &p) { return p.name == tokens[1]; }) == 0)
422 {
423 const CStatusMessage m = CStatusMessage(this).error(
424 u"XPlane required package %1 not found. Aborting processing of this package.")
425 << tokens[1];
426 m_loadingMessages.push_back(m);
427 return false;
428 }
429
430 return true;
431 }
432
435 QString readLineFrom(QTextStream &stream)
436 {
437 QString line;
438 do
439 {
440 line = stream.readLine();
441 }
442 while (line.isEmpty() && !stream.atEnd());
443 return line;
444 }
445
446 bool CAircraftModelLoaderXPlane::parseObjectCommand(const QStringList &, CSLPackage &package, const QString &path,
447 int lineNum)
448 {
449 package.planes.push_back(CSLPlane());
450
451 const auto m = CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : Unsupported legacy CSL format.")
452 << path << lineNum;
453 m_loadingMessages.push_back(m);
454 return false;
455 }
456
457 bool CAircraftModelLoaderXPlane::parseTextureCommand(const QStringList &, CSLPackage &package, const QString &path,
458 int lineNum)
459 {
460 if (!package.planes.isEmpty() && !package.planes.back().hasErrors)
461 {
462 const auto m = CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : Unsupported legacy CSL format.")
463 << path << lineNum;
464 m_loadingMessages.push_back(m);
465 }
466 return false;
467 }
468
469 bool CAircraftModelLoaderXPlane::parseAircraftCommand(const QStringList &, CSLPackage &package, const QString &path,
470 int lineNum)
471 {
472 package.planes.push_back(CSLPlane());
473
474 const auto m = CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : Unsupported legacy CSL format.")
475 << path << lineNum;
476 m_loadingMessages.push_back(m);
477 return false;
478 }
479
480 bool CAircraftModelLoaderXPlane::parseObj8AircraftCommand(const QStringList &tokens, CSLPackage &package,
481 const QString &path, int lineNum)
482 {
483 package.planes.push_back(CSLPlane());
484
485 // OBJ8_AIRCRAFT <path>
486 if (tokens.size() != 2)
487 {
488 const CStatusMessage m = CStatusMessage(this).warning(
489 u"%1/xsb_aircraft.txt Line %2 : OBJ8_AIRCARFT command requires 1 argument.")
490 << path << lineNum;
491 m_loadingMessages.push_back(m);
492 if (tokens.size() < 2)
493 {
494 return false;
495 }
496 }
497
498 package.planes.back().objectName = tokens[1];
499 package.planes.back().objectVersion = CSLPlane::OBJ8;
500 return true;
501 }
502
503 bool CAircraftModelLoaderXPlane::parseObj8Command(const QStringList &tokens, CSLPackage &package,
504 const QString &path, int lineNum)
505 {
506 // OBJ8 <group> <animate YES|NO> <filename>
507 if (tokens.size() != 4)
508 {
509 if (tokens.size() == 5 || tokens.size() == 6)
510 {
511 const CStatusMessage m =
512 CStatusMessage(this).error(
513 u"%1/xsb_aircraft.txt Line %2 : Unsupported IVAO CSL format - consider using CSL2XSB.")
514 << path << lineNum;
515 m_loadingMessages.push_back(m);
516 }
517 else
518 {
519 const CStatusMessage m =
520 CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : OBJ8 command takes 3 arguments.")
521 << path << lineNum;
522 m_loadingMessages.push_back(m);
523 }
524 return false;
525 }
526 if (package.planes.isEmpty())
527 {
528 m_loadingMessages.push_back(
529 CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
530 << path << lineNum);
531 return false;
532 }
533
534 if (tokens[1] != "SOLID")
535 {
536 return true;
537 }
538
539 QString relativePath(tokens[3]);
540 normalizePath(relativePath);
541 QString fullPath(relativePath);
542 if (!doPackageSub(fullPath))
543 {
544 const CStatusMessage m = CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : package not found.")
545 << path << lineNum;
546 m_loadingMessages.push_back(m);
547 return false;
548 }
549
550 // just the name of the dir containing xsbaircraft.txt
551 QString packageRootDir = package.path.mid(package.path.lastIndexOf('/') + 1);
552 package.planes.back().dirNames = QStringList { packageRootDir };
553
554 package.planes.back().filePath = fullPath;
555 return true;
556 }
557
558 bool CAircraftModelLoaderXPlane::parseHasGearCommand(const QStringList &tokens, CSLPackage &package,
559 const QString &path, int lineNum)
560 {
561 Q_UNUSED(tokens)
562 Q_UNUSED(package)
563 Q_UNUSED(path)
564 Q_UNUSED(lineNum)
565 return true;
566 }
567
568 bool CAircraftModelLoaderXPlane::parseIcaoCommand(const QStringList &tokens, CSLPackage &package,
569 const QString &path, int lineNum)
570 {
571 // ICAO <code>
572 if (tokens.size() != 2)
573 {
574 const CStatusMessage m =
575 CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : ICAO command requires 1 argument.")
576 << path << lineNum;
577 m_loadingMessages.push_back(m);
578 return false;
579 }
580 if (package.planes.isEmpty())
581 {
582 m_loadingMessages.push_back(
583 CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
584 << path << lineNum);
585 return false;
586 }
587
588 QString icao = tokens[1];
589 package.planes.back().icao = icao;
590 return true;
591 }
592
593 bool CAircraftModelLoaderXPlane::parseAirlineCommand(const QStringList &tokens, CSLPackage &package,
594 const QString &path, int lineNum)
595 {
596 // AIRLINE <code> <airline>
597 if (tokens.size() != 3)
598 {
599 const CStatusMessage m =
600 CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : AIRLINE command requires 2 arguments.")
601 << path << lineNum;
602 m_loadingMessages.push_back(m);
603 return false;
604 }
605 if (package.planes.isEmpty())
606 {
607 m_loadingMessages.push_back(
608 CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
609 << path << lineNum);
610 return false;
611 }
612
613 QString icao = tokens[1];
614 package.planes.back().icao = icao;
615 QString airline = tokens[2];
616 package.planes.back().airline = airline;
617 return true;
618 }
619
620 bool CAircraftModelLoaderXPlane::parseLiveryCommand(const QStringList &tokens, CSLPackage &package,
621 const QString &path, int lineNum)
622 {
623 // LIVERY <code> <airline> <livery>
624 if (tokens.size() != 4)
625 {
626 const CStatusMessage m =
627 CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : LIVERY command requires 3 arguments.")
628 << path << lineNum;
629 m_loadingMessages.push_back(m);
630 return false;
631 }
632 if (package.planes.isEmpty())
633 {
634 m_loadingMessages.push_back(
635 CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
636 << path << lineNum);
637 return false;
638 }
639
640 QString icao = tokens[1];
641 package.planes.back().icao = icao;
642 QString airline = tokens[2];
643 package.planes.back().airline = airline;
644 QString livery = tokens[3];
645 package.planes.back().livery = livery;
646 return true;
647 }
648
649 bool CAircraftModelLoaderXPlane::parseDummyCommand(const QStringList & /* tokens */, CSLPackage & /* package */,
650 const QString & /* path */, int /*lineNum*/)
651 {
652 return true;
653 }
654
655 CAircraftModelLoaderXPlane::CSLPackage CAircraftModelLoaderXPlane::parsePackageHeader(const QString &path,
656 const QString &content)
657 {
658 using command = std::function<bool(const QStringList &, CSLPackage &, const QString &, int)>;
659 using namespace std::placeholders;
660
661 const QMap<QString, command> commands { { "EXPORT_NAME",
662 [this](auto &&tokens, auto &&package, auto &&path, auto &&lineNum) {
663 return parseExportCommand(
664 std::forward<decltype(tokens)>(tokens),
665 std::forward<decltype(package)>(package),
666 std::forward<decltype(path)>(path),
667 std::forward<decltype(lineNum)>(lineNum));
668 } } };
669
670 CSLPackage package;
671 int lineNum = 0;
672
673 QString localCopy(content);
674 QTextStream in(&localCopy);
675 while (!in.atEnd())
676 {
677 ++lineNum;
678 QString line = in.readLine();
679 auto tokens = splitString(line, [](QChar c) { return c.isSpace(); });
680 if (!tokens.empty())
681 {
682 auto it = commands.find(tokens[0]);
683 if (it != commands.end())
684 {
685 bool result = it.value()(tokens, package, path, lineNum);
686 // Stop loop once we found EXPORT command
687 if (result) break;
688 }
689 }
690 }
691 return package;
692 }
693
694 void CAircraftModelLoaderXPlane::parseFullPackage(const QString &content, CSLPackage &package)
695 {
696 using command = std::function<bool(const QStringList &, CSLPackage &, const QString &, int)>;
697 using namespace std::placeholders;
698
699 const QMap<QString, command> commands {
700 { "EXPORT_NAME",
701 [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
702 return parseDummyCommand(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
703 std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
704 } },
705 { "DEPENDENCY",
706 [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
707 return parseDependencyCommand(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
708 std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
709 } },
710 { "OBJECT",
711 [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
712 return parseObjectCommand(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
713 std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
714 } },
715 { "TEXTURE",
716 [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
717 return parseTextureCommand(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
718 std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
719 } },
720 {
721 "AIRCRAFT",
722 [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
723 return parseAircraftCommand(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
724 std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
725 },
726 },
727 { "OBJ8_AIRCRAFT",
728 [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
729 return parseObj8AircraftCommand(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
730 std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
731 } },
732 { "OBJ8",
733 [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
734 return parseObj8Command(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
735 std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
736 } },
737 { "HASGEAR",
738 [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
739 return parseHasGearCommand(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
740 std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
741 } },
742 { "ICAO",
743 [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
744 return parseIcaoCommand(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
745 std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
746 } },
747 { "AIRLINE",
748 [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
749 return parseAirlineCommand(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
750 std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
751 } },
752 { "LIVERY",
753 [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
754 return parseLiveryCommand(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
755 std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
756 } },
757 { "VERT_OFFSET",
758 [this](auto &&PH1, auto &&PH2, auto &&PH3, auto &&PH4) {
759 return parseDummyCommand(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2),
760 std::forward<decltype(PH3)>(PH3), std::forward<decltype(PH4)>(PH4));
761 } },
762 };
763
764 int lineNum = 0;
765
766 QString localCopy(content);
767 QTextStream in(&localCopy);
768
769 while (!in.atEnd())
770 {
771 ++lineNum;
772 QString line = in.readLine();
773 if (line.isEmpty() || line[0] == '#') continue;
774 auto tokens = splitString(line, [](QChar c) { return c.isSpace(); });
775 if (!tokens.empty())
776 {
777 auto it = commands.find(tokens[0]);
778 if (it != commands.end())
779 {
780 bool result = it.value()(tokens, package, package.path, lineNum);
781 if (!result)
782 {
783 if (!package.planes.empty())
784 {
785 package.planes.back().hasErrors = true;
786 }
787 }
788 }
789 else
790 {
791 const CStatusMessage m =
792 CStatusMessage(this).error(u"%1/xsb_aircraft.txt Line %2 : Unrecognized CSL command: '%3'")
793 << package.path << lineNum << tokens[0];
794 m_loadingMessages.push_back(m);
795 }
796 }
797 }
798
799 // Remove all planes with errors
800 auto it = std::ranges::remove_if(package.planes, [](const CSLPlane &plane) { return plane.hasErrors; }).begin();
801 package.planes.erase(it, package.planes.end());
802 }
803
804 const QString &CAircraftModelLoaderXPlane::fileFilterFlyable()
805 {
806 static const QString f("*.acf");
807 return f;
808 }
809
810 const QString &CAircraftModelLoaderXPlane::fileFilterCsl()
811 {
812 static const QString f("xsb_aircraft.txt");
813 return f;
814 }
815
816} // namespace swift::misc::simulation::xplane
static bool isLocalDeveloperDebugBuild()
Local build for developers.
static QString appendFilePaths(const QString &path1, const QString &path2)
Append file paths.
static bool isExcludedDirectory(const QDir &directory, const QStringList &excludeDirectories, Qt::CaseSensitivity cs=osFileNameCaseSensitivity())
Directory to be excluded?
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:308
Streamable status message, e.g.
static constexpr auto SeverityInfo
Status severities.
static constexpr auto SeverityError
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:243
void thenWithResult(F functor)
Connects to a functor to which will be passed the result when the task is finished.
Definition worker.h:264
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).
@ TypeOwnSimulatorModel
represents own simulator model (AI model, model on disk)
const CDistributor & getDistributor() const
Get distributor.
bool hasAircraftDesignator() const
Has aircraft designator?
const QString & getModelString() const
Model key, either queried or loaded from simulator model.
const QString & getName() const
Name.
const QString & getAircraftIcaoCodeDesignator() const
Aircraft ICAO code designator.
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?
bool hasDescription() const
Has description.
Definition distributor.h:65
const QString & getDescription() const
Get description.
Definition distributor.h:56
Simple hardcoded info about the corresponding simulator.
static const CSimulatorInfo & xplane()
Const simulator info objects.
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.
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.
const CSimulatorInfo & getSimulator() const
Simulator.
void diskLoadingStarted(const CSimulatorInfo &simulator, IAircraftModelLoader::LoadMode loadMode)
Disk loading started.
settings::CMultiSimulatorSettings m_settings
settings
IAircraftModelLoader(const CSimulatorInfo &simulator, QObject *parent=nullptr)
Constructor.
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.
CStatusMessage clearCachedModels(const CSimulatorInfo &simulator)
Look like IMultiSimulatorModelCaches interface.
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.
bool isSpace(char32_t ucs4)
QList< T >::reference back()
bool empty() const const
bool isEmpty() const const
qsizetype size() const const
QMap< Key, T >::iterator end()
QMap< Key, T >::iterator find(const Key &key)
QObject(QObject *parent)
QObject * parent() const const
QString & append(QChar ch)
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
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.
#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