13#include <QDirIterator>
20#include <QRegularExpression>
21#include <QStringBuilder>
41using namespace swift::config;
43using namespace swift::misc::aviation;
44using namespace swift::misc::simulation;
45using namespace swift::misc::network;
47namespace swift::misc::simulation::xplane
50 static void normalizePath(QString &path)
54 if (e ==
'/' || e ==
':' || e ==
'\\')
62 static QString descriptionForFlyableModel(
const CAircraftModel &model)
73 return u
"[ACF] " % model.
getName();
88 return QStringLiteral(
"[ACF]");
100 m_parserWorker->waitForFinished();
111 m_settings.getModelExcludeDirectoryPatternsOrDefault(simulator));
118 << modelDirectories.
join(
", "),
125 if (m_parserWorker && !m_parserWorker->isFinished())
131 m_parserWorker =
CWorker::fromTask(
this,
"CAircraftModelLoaderXPlane::performParsing",
132 [
this, modelDirs, excludedDirectoryPatterns, modelConsolidation]() {
134 this->performParsing(modelDirs, excludedDirectoryPatterns);
135 if (modelConsolidation)
137 modelConsolidation(models,
true);
150 CAircraftModelList models(this->performParsing(modelDirs, excludedDirectoryPatterns));
157 return !m_parserWorker || m_parserWorker->isFinished();
168 QString CAircraftModelLoaderXPlane::CSLPlane::getModelName()
const
171 if (objectVersion == OBJ7)
173 modelName += u
' ' % textureName;
175 return std::move(modelName).
trimmed();
182 for (
const QString &rootDirectory : rootDirectories)
184 allModels.
push_back(parseCslPackages(rootDirectory, excludeDirectories));
185 allModels.
push_back(parseFlyableAirplanes(rootDirectory, excludeDirectories));
197 u
"XPlane model '%1' exists already! Potential model string conflict! Ignoring it.")
204 CAircraftModelList CAircraftModelLoaderXPlane::parseFlyableAirplanes(
const QString &rootDirectory,
205 const QStringList &excludeDirectories)
207 Q_UNUSED(excludeDirectories)
213 QDir searchPath(rootDirectory, fileFilterFlyable());
217 QStringLiteral(
"Parsing flyable airplanes in '%1'").arg(rootDirectory), -1);
219 CAircraftModelList installedModels;
220 while (aircraftIt.hasNext())
228 using namespace swift::misc::simulation::xplane::qtfreeutils;
232 CAircraftModel model;
236 model.setDistributor(dist);
238 if (!model.hasDescription())
240 model.setDescription(descriptionForFlyableModel(model));
244 model.setFileDetailsAndTimestamp(aircraftIt.fileInfo());
245 model.setModelMode(CAircraftModel::Exclude);
246 addUniqueModel(model, installedModels);
248 const QString baseModelString = model.getModelString();
249 QDirIterator liveryIt(
254 QStringLiteral(
"Parsing flyable liveries in '%1'").arg(aircraftIt.fileInfo().canonicalPath()), -1);
255 while (liveryIt.hasNext())
258 model.setModelString(baseModelString % u
' ' % liveryIt.fileName());
259 addUniqueModel(model, installedModels);
262 return installedModels;
265 CAircraftModelList CAircraftModelLoaderXPlane::parseCslPackages(
const QString &rootDirectory,
266 const QStringList &excludeDirectories)
268 Q_UNUSED(excludeDirectories);
274 m_cslPackages.clear();
276 QDir searchPath(rootDirectory, fileFilterCsl());
280 QString packageFile = it.next();
286 const QString packageFilePath = it.fileInfo().absolutePath();
287 QFile file(packageFile);
292 QTextStream ts(&file);
293 content.
append(ts.readAll());
296 const auto package = parsePackageHeader(packageFilePath, content);
297 if (package.hasValidHeader()) m_cslPackages.
push_back(package);
300 CAircraftModelList installedModels;
303 for (
auto &package : m_cslPackages)
308 QFile file(packageFile);
313 QTextStream ts(&file);
314 content.
append(ts.readAll());
316 parseFullPackage(content, package);
318 for (
const auto &plane : std::as_const(package.planes))
322 const CStatusMessage msg =
323 CStatusMessage(
this).warning(
324 u
"XPlane model '%1' exists already! Potential model string conflict! Ignoring it.")
325 << plane.getModelName();
331 const CAircraftIcaoCode icao(plane.icao);
332 const QFileInfo modelFileInfo(plane.filePath);
333 model.setFileDetailsAndTimestamp(modelFileInfo);
334 model.setAircraftIcaoCode(icao);
338 SWIFT_AUDIT_X(modelFileInfo.exists(), Q_FUNC_INFO,
"Model does NOT exist");
343 CAirlineIcaoCode airline(plane.airline);
345 model.setLivery(livery);
348 QString modelDescription(
"[CSL]");
349 if (plane.objectVersion == CSLPlane::OBJ7)
351 modelDescription +=
"[OBJ7]";
353 else if (plane.objectVersion == CSLPlane::OBJ8)
355 modelDescription +=
"[OBJ8]";
357 model.setDescription(modelDescription);
361 return installedModels;
364 bool CAircraftModelLoaderXPlane::doPackageSub(QString &ioPath)
366 for (
auto i = m_cslPackages.cbegin(); i != m_cslPackages.cend(); ++i)
368 if (strncmp(qPrintable(i->name), qPrintable(ioPath),
static_cast<size_t>(i->name.size())) == 0)
370 ioPath.
remove(0, i->name.size());
371 ioPath.
insert(0, i->path);
378 bool CAircraftModelLoaderXPlane::parseExportCommand(
const QStringList &tokens, CSLPackage &package,
379 const QString &path,
int lineNum)
381 if (tokens.
size() != 2)
383 const CStatusMessage m =
384 CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : EXPORT_NAME command requires 1 argument.")
390 auto p = std::ranges::find_if(m_cslPackages, [&tokens](
const CSLPackage &p) {
return p.name == tokens[1]; });
391 if (p == m_cslPackages.cend())
394 package.name = tokens[1];
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;
407 bool CAircraftModelLoaderXPlane::parseDependencyCommand(
const QStringList &tokens, CSLPackage &package,
408 const QString &path,
int lineNum)
411 if (tokens.
size() != 2)
413 const CStatusMessage m =
414 CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : DEPENDENCY command requires 1 argument.")
420 if (std::count_if(m_cslPackages.cbegin(), m_cslPackages.cend(),
421 [&tokens](
const CSLPackage &p) { return p.name == tokens[1]; }) == 0)
423 const CStatusMessage m = CStatusMessage(
this).
error(
424 u
"XPlane required package %1 not found. Aborting processing of this package.")
435 QString readLineFrom(QTextStream &stream)
446 bool CAircraftModelLoaderXPlane::parseObjectCommand(
const QStringList &, CSLPackage &package,
const QString &path,
451 const auto m = CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : Unsupported legacy CSL format.")
457 bool CAircraftModelLoaderXPlane::parseTextureCommand(
const QStringList &, CSLPackage &package,
const QString &path,
460 if (!package.planes.isEmpty() && !package.planes.back().hasErrors)
462 const auto m = CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : Unsupported legacy CSL format.")
469 bool CAircraftModelLoaderXPlane::parseAircraftCommand(
const QStringList &, CSLPackage &package,
const QString &path,
472 package.planes.push_back(CSLPlane());
474 const auto m = CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : Unsupported legacy CSL format.")
480 bool CAircraftModelLoaderXPlane::parseObj8AircraftCommand(
const QStringList &tokens, CSLPackage &package,
481 const QString &path,
int lineNum)
483 package.planes.push_back(CSLPlane());
486 if (tokens.
size() != 2)
488 const CStatusMessage m = CStatusMessage(
this).
warning(
489 u
"%1/xsb_aircraft.txt Line %2 : OBJ8_AIRCARFT command requires 1 argument.")
492 if (tokens.
size() < 2)
498 package.planes.back().objectName = tokens[1];
499 package.planes.
back().objectVersion = CSLPlane::OBJ8;
503 bool CAircraftModelLoaderXPlane::parseObj8Command(
const QStringList &tokens, CSLPackage &package,
504 const QString &path,
int lineNum)
507 if (tokens.
size() != 4)
509 if (tokens.
size() == 5 || tokens.
size() == 6)
511 const CStatusMessage m =
512 CStatusMessage(
this).
error(
513 u
"%1/xsb_aircraft.txt Line %2 : Unsupported IVAO CSL format - consider using CSL2XSB.")
519 const CStatusMessage m =
520 CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : OBJ8 command takes 3 arguments.")
526 if (package.planes.isEmpty())
529 CStatusMessage(
this).error(u
"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
534 if (tokens[1] !=
"SOLID")
539 QString relativePath(tokens[3]);
540 normalizePath(relativePath);
541 QString fullPath(relativePath);
542 if (!doPackageSub(fullPath))
544 const CStatusMessage m = CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : package not found.")
551 QString packageRootDir = package.path.mid(package.path.lastIndexOf(
'/') + 1);
552 package.planes.back().dirNames = QStringList { packageRootDir };
554 package.planes.back().filePath = fullPath;
558 bool CAircraftModelLoaderXPlane::parseHasGearCommand(
const QStringList &tokens, CSLPackage &package,
559 const QString &path,
int lineNum)
568 bool CAircraftModelLoaderXPlane::parseIcaoCommand(
const QStringList &tokens, CSLPackage &package,
569 const QString &path,
int lineNum)
572 if (tokens.
size() != 2)
574 const CStatusMessage m =
575 CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : ICAO command requires 1 argument.")
580 if (package.planes.isEmpty())
583 CStatusMessage(
this).error(u
"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
588 QString icao = tokens[1];
589 package.planes.
back().icao = icao;
593 bool CAircraftModelLoaderXPlane::parseAirlineCommand(
const QStringList &tokens, CSLPackage &package,
594 const QString &path,
int lineNum)
597 if (tokens.
size() != 3)
599 const CStatusMessage m =
600 CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : AIRLINE command requires 2 arguments.")
605 if (package.planes.isEmpty())
608 CStatusMessage(
this).error(u
"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
613 QString icao = tokens[1];
614 package.planes.
back().icao = icao;
615 QString airline = tokens[2];
616 package.planes.
back().airline = airline;
620 bool CAircraftModelLoaderXPlane::parseLiveryCommand(
const QStringList &tokens, CSLPackage &package,
621 const QString &path,
int lineNum)
624 if (tokens.
size() != 4)
626 const CStatusMessage m =
627 CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : LIVERY command requires 3 arguments.")
632 if (package.planes.isEmpty())
635 CStatusMessage(
this).error(u
"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
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;
649 bool CAircraftModelLoaderXPlane::parseDummyCommand(
const QStringList & , CSLPackage & ,
650 const QString & ,
int )
655 CAircraftModelLoaderXPlane::CSLPackage CAircraftModelLoaderXPlane::parsePackageHeader(
const QString &path,
656 const QString &content)
658 using command = std::function<bool(
const QStringList &, CSLPackage &,
const QString &,
int)>;
659 using namespace std::placeholders;
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));
673 QString localCopy(content);
674 QTextStream in(&localCopy);
678 QString line = in.readLine();
682 auto it = commands.
find(tokens[0]);
683 if (it != commands.
end())
685 bool result = it.value()(tokens, package, path, lineNum);
694 void CAircraftModelLoaderXPlane::parseFullPackage(
const QString &content, CSLPackage &package)
696 using command = std::function<bool(
const QStringList &, CSLPackage &,
const QString &,
int)>;
697 using namespace std::placeholders;
699 const QMap<QString, command> commands {
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));
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));
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));
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));
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));
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));
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));
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));
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));
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));
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));
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));
766 QString localCopy(content);
767 QTextStream in(&localCopy);
772 QString line = in.readLine();
773 if (line.
isEmpty() || line[0] ==
'#')
continue;
777 auto it = commands.
find(tokens[0]);
778 if (it != commands.
end())
780 bool result = it.value()(tokens, package, package.path, lineNum);
783 if (!package.planes.empty())
785 package.planes.back().hasErrors =
true;
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];
800 auto it = std::ranges::remove_if(package.planes, [](
const CSLPlane &plane) { return plane.hasErrors; }).begin();
801 package.planes.erase(it, package.planes.end());
804 const QString &CAircraftModelLoaderXPlane::fileFilterFlyable()
806 static const QString f(
"*.acf");
810 const QString &CAircraftModelLoaderXPlane::fileFilterCsl()
812 static const QString f(
"xsb_aircraft.txt");
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.
void push_back(const T &value)
Appends an element at the end of the sequence.
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.
void thenWithResult(F functor)
Connects to a functor to which will be passed the result when the task is finished.
void setCombinedCode(const QString &code)
Combined code.
bool setAirlineIcaoCode(const CAirlineIcaoCode &airlineIcao)
Airline ICAO code.
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.
const QString & getDescription() const
Get description.
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.
~CAircraftModelLoaderXPlane()
Virtual destructor.
void updateInstalledModels(const CAircraftModelList &models)
Parsed or injected models.
CAircraftModelLoaderXPlane(QObject *parent=nullptr)
Constructor.
bool isLoadingFinished() const
Loading finished?
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 isEmpty() const const
qsizetype size() const const
QMap< Key, T >::iterator end()
QMap< Key, T >::iterator find(const Key &key)
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
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString trimmed() const const
QString join(QChar separator) const const
QString readLine(qint64 maxlen)
AcfProperties extractAcfProperties(const std::string &filePath)
Extract ACF properties from an aircraft file.
std::string author
Model author.
std::string modelDescription
Model description.
std::string aircraftIcaoCode
Aircraft ICAO code.
std::string modelString
Generated model string.
std::string modelName
Model name.
#define SWIFT_AUDIT_X(COND, WHERE, WHAT)
A weaker kind of verify.
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.