14 #include <QDirIterator>
21 #include <QRegularExpression>
22 #include <QStringBuilder>
23 #include <QTextStream>
42 using namespace swift::config;
44 using namespace swift::misc::aviation;
45 using namespace swift::misc::simulation;
46 using namespace swift::misc::network;
48 namespace swift::misc::simulation::xplane
51 static void normalizePath(QString &path)
55 if (e ==
'/' || e ==
':' || e ==
'\\') { e =
'/'; }
60 static QString descriptionForFlyableModel(
const CAircraftModel &model)
69 else {
return u
"[ACF] " % model.
getName(); }
80 return QStringLiteral(
"[ACF]");
83 CAircraftModelLoaderXPlane::CAircraftModelLoaderXPlane(QObject *parent)
90 if (m_parserWorker) { m_parserWorker->waitForFinished(); }
95 const QStringList &modelDirectories)
99 const QStringList excludedDirectoryPatterns(
102 if (modelDirs.isEmpty())
107 << modelDirectories.join(
", "),
114 if (m_parserWorker && !m_parserWorker->isFinished()) {
return; }
117 m_parserWorker =
CWorker::fromTask(
this,
"CAircraftModelLoaderXPlane::performParsing",
118 [
this, modelDirs, excludedDirectoryPatterns, modelConsolidation]() {
120 this->performParsing(modelDirs, excludedDirectoryPatterns);
121 if (modelConsolidation) { modelConsolidation(models,
true); }
133 CAircraftModelList models(this->performParsing(modelDirs, excludedDirectoryPatterns));
140 return !m_parserWorker || m_parserWorker->isFinished();
151 QString CAircraftModelLoaderXPlane::CSLPlane::getModelName()
const
153 QString modelName = dirNames.join(
' ') % u
' ' % objectName;
154 if (objectVersion == OBJ7) { modelName += u
' ' % textureName; }
155 return std::move(modelName).trimmed();
158 CAircraftModelList CAircraftModelLoaderXPlane::performParsing(
const QStringList &rootDirectories,
159 const QStringList &excludeDirectories)
162 for (
const QString &rootDirectory : rootDirectories)
164 allModels.
push_back(parseCslPackages(rootDirectory, excludeDirectories));
165 allModels.
push_back(parseFlyableAirplanes(rootDirectory, excludeDirectories));
177 u
"XPlane model '%1' exists already! Potential model string conflict! Ignoring it.")
184 CAircraftModelList CAircraftModelLoaderXPlane::parseFlyableAirplanes(
const QString &rootDirectory,
185 const QStringList &excludeDirectories)
187 Q_UNUSED(excludeDirectories)
188 if (rootDirectory.isEmpty()) {
return {}; }
190 QDir searchPath(rootDirectory, fileFilterFlyable());
191 QDirIterator aircraftIt(searchPath, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
194 QStringLiteral(
"Parsing flyable airplanes in '%1'").arg(rootDirectory), -1);
197 while (aircraftIt.hasNext())
205 using namespace swift::misc::simulation::xplane::qtfreeutils;
208 const CDistributor dist({}, QString::fromStdString(acfProperties.
author), {}, {}, CSimulatorInfo::XPLANE);
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)); }
218 model.setFileDetailsAndTimestamp(aircraftIt.fileInfo());
219 model.setModelMode(CAircraftModel::Exclude);
220 addUniqueModel(model, installedModels);
222 const QString baseModelString = model.getModelString();
223 QDirIterator liveryIt(
225 QDir::Dirs | QDir::NoDotAndDotDot);
228 QStringLiteral(
"Parsing flyable liveries in '%1'").arg(aircraftIt.fileInfo().canonicalPath()), -1);
229 while (liveryIt.hasNext())
232 model.setModelString(baseModelString % u
' ' % liveryIt.fileName());
233 addUniqueModel(model, installedModels);
236 return installedModels;
239 CAircraftModelList CAircraftModelLoaderXPlane::parseCslPackages(
const QString &rootDirectory,
240 const QStringList &excludeDirectories)
242 Q_UNUSED(excludeDirectories);
243 if (rootDirectory.isEmpty()) {
return {}; }
245 m_cslPackages.clear();
247 QDir searchPath(rootDirectory, fileFilterCsl());
248 QDirIterator it(searchPath, QDirIterator::Subdirectories);
251 QString packageFile = it.next();
254 const QString packageFilePath = it.fileInfo().absolutePath();
255 QFile file(packageFile);
256 file.open(QIODevice::ReadOnly);
259 QTextStream ts(&file);
260 content.append(ts.readAll());
263 const auto package = parsePackageHeader(packageFilePath, content);
264 if (package.hasValidHeader()) m_cslPackages.push_back(package);
270 for (
auto &package : m_cslPackages)
275 QFile file(packageFile);
276 file.open(QIODevice::ReadOnly);
279 QTextStream ts(&file);
280 content.append(ts.readAll());
282 parseFullPackage(content, package);
284 for (
const auto &plane : std::as_const(package.planes))
290 u
"XPlane model '%1' exists already! Potential model string conflict! Ignoring it.")
291 << plane.getModelName();
298 const QFileInfo modelFileInfo(plane.filePath);
299 model.setFileDetailsAndTimestamp(modelFileInfo);
300 model.setAircraftIcaoCode(icao);
302 if (CBuildConfig::isLocalDeveloperDebugBuild())
304 SWIFT_AUDIT_X(modelFileInfo.exists(), Q_FUNC_INFO,
"Model does NOT exist");
311 model.setLivery(livery);
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);
321 return installedModels;
324 bool CAircraftModelLoaderXPlane::doPackageSub(QString &ioPath)
326 for (
auto i = m_cslPackages.cbegin(); i != m_cslPackages.cend(); ++i)
328 if (strncmp(qPrintable(i->name), qPrintable(ioPath),
static_cast<size_t>(i->name.size())) == 0)
330 ioPath.
remove(0, i->name.size());
331 ioPath.insert(0, i->path);
338 bool CAircraftModelLoaderXPlane::parseExportCommand(
const QStringList &tokens, CSLPackage &package,
339 const QString &path,
int lineNum)
341 if (tokens.size() != 2)
344 CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : EXPORT_NAME command requires 1 argument.")
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())
355 package.name = tokens[1];
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;
368 bool CAircraftModelLoaderXPlane::parseDependencyCommand(
const QStringList &tokens, CSLPackage &package,
369 const QString &path,
int lineNum)
372 if (tokens.size() != 2)
375 CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : DEPENDENCY command requires 1 argument.")
381 if (std::count_if(m_cslPackages.cbegin(), m_cslPackages.cend(),
382 [&tokens](
const CSLPackage &p) { return p.name == tokens[1]; }) == 0)
385 u
"XPlane required package %1 not found. Aborting processing of this package.")
396 QString readLineFrom(QTextStream &stream)
400 line = stream.readLine();
402 while (line.isEmpty() && !stream.atEnd());
406 bool CAircraftModelLoaderXPlane::parseObjectCommand(
const QStringList &, CSLPackage &package,
const QString &path,
409 package.planes.push_back(CSLPlane());
411 const auto m =
CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : Unsupported legacy CSL format.")
417 bool CAircraftModelLoaderXPlane::parseTextureCommand(
const QStringList &, CSLPackage &package,
const QString &path,
420 if (!package.planes.isEmpty() && !package.planes.back().hasErrors)
422 const auto m =
CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : Unsupported legacy CSL format.")
429 bool CAircraftModelLoaderXPlane::parseAircraftCommand(
const QStringList &, CSLPackage &package,
const QString &path,
432 package.planes.push_back(CSLPlane());
434 const auto m =
CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : Unsupported legacy CSL format.")
440 bool CAircraftModelLoaderXPlane::parseObj8AircraftCommand(
const QStringList &tokens, CSLPackage &package,
441 const QString &path,
int lineNum)
443 package.planes.push_back(CSLPlane());
446 if (tokens.size() != 2)
449 u
"%1/xsb_aircraft.txt Line %2 : OBJ8_AIRCARFT command requires 1 argument.")
452 if (tokens.size() < 2) {
return false; }
455 package.planes.back().objectName = tokens[1];
456 package.planes.back().objectVersion = CSLPlane::OBJ8;
460 bool CAircraftModelLoaderXPlane::parseObj8Command(
const QStringList &tokens, CSLPackage &package,
461 const QString &path,
int lineNum)
464 if (tokens.size() != 4)
466 if (tokens.size() == 5 || tokens.size() == 6)
470 u
"%1/xsb_aircraft.txt Line %2 : Unsupported IVAO CSL format - consider using CSL2XSB.")
477 CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : OBJ8 command takes 3 arguments.")
483 if (package.planes.isEmpty())
486 CStatusMessage(
this).error(u
"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
491 if (tokens[1] !=
"SOLID") {
return true; }
493 QString relativePath(tokens[3]);
494 normalizePath(relativePath);
495 QString fullPath(relativePath);
496 if (!doPackageSub(fullPath))
505 QString packageRootDir = package.path.mid(package.path.lastIndexOf(
'/') + 1);
506 package.planes.back().dirNames = QStringList { packageRootDir };
508 package.planes.back().filePath = fullPath;
512 bool CAircraftModelLoaderXPlane::parseHasGearCommand(
const QStringList &tokens, CSLPackage &package,
513 const QString &path,
int lineNum)
522 bool CAircraftModelLoaderXPlane::parseIcaoCommand(
const QStringList &tokens, CSLPackage &package,
523 const QString &path,
int lineNum)
526 if (tokens.size() != 2)
529 CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : ICAO command requires 1 argument.")
534 if (package.planes.isEmpty())
537 CStatusMessage(
this).error(u
"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
542 QString icao = tokens[1];
543 package.planes.back().icao = icao;
547 bool CAircraftModelLoaderXPlane::parseAirlineCommand(
const QStringList &tokens, CSLPackage &package,
548 const QString &path,
int lineNum)
551 if (tokens.size() != 3)
554 CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : AIRLINE command requires 2 arguments.")
559 if (package.planes.isEmpty())
562 CStatusMessage(
this).error(u
"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
567 QString icao = tokens[1];
568 package.planes.back().icao = icao;
569 QString airline = tokens[2];
570 package.planes.back().airline = airline;
574 bool CAircraftModelLoaderXPlane::parseLiveryCommand(
const QStringList &tokens, CSLPackage &package,
575 const QString &path,
int lineNum)
578 if (tokens.size() != 4)
581 CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : LIVERY command requires 3 arguments.")
586 if (package.planes.isEmpty())
589 CStatusMessage(
this).error(u
"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
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;
603 bool CAircraftModelLoaderXPlane::parseDummyCommand(
const QStringList & , CSLPackage & ,
604 const QString & ,
int )
609 CAircraftModelLoaderXPlane::CSLPackage CAircraftModelLoaderXPlane::parsePackageHeader(
const QString &path,
610 const QString &content)
612 using command = std::function<bool(
const QStringList &, CSLPackage &,
const QString &,
int)>;
613 using namespace std::placeholders;
616 {
"EXPORT_NAME", std::bind(&CAircraftModelLoaderXPlane::parseExportCommand,
this, _1, _2, _3, _4) }
622 QString localCopy(content);
623 QTextStream in(&localCopy);
627 QString line = in.readLine();
628 auto tokens =
splitString(line, [](QChar c) {
return c.isSpace(); });
631 auto it = commands.find(tokens[0]);
632 if (it != commands.end())
634 bool result = it.value()(tokens, package, path, lineNum);
643 void CAircraftModelLoaderXPlane::parseFullPackage(
const QString &content, CSLPackage &package)
645 using command = std::function<bool(
const QStringList &, CSLPackage &,
const QString &,
int)>;
646 using namespace std::placeholders;
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) },
665 QString localCopy(content);
666 QTextStream in(&localCopy);
671 QString line = in.readLine();
672 if (line.isEmpty() || line[0] ==
'#')
continue;
673 auto tokens =
splitString(line, [](QChar c) {
return c.isSpace(); });
676 auto it = commands.find(tokens[0]);
677 if (it != commands.end())
679 bool result = it.value()(tokens, package, package.path, lineNum);
682 if (!package.planes.empty()) { package.planes.back().hasErrors =
true; }
688 CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : Unrecognized CSL command: '%3'")
689 << package.path << lineNum << tokens[0];
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());
701 const QString &CAircraftModelLoaderXPlane::fileFilterFlyable()
703 static const QString f(
"*.acf");
707 const QString &CAircraftModelLoaderXPlane::fileFilterCsl()
709 static const QString f(
"xsb_aircraft.txt");
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.
int remove(const T &object)
Remove all elements equal to the given object, if it is contained.
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.
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.
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)
const QString & getAircraftIcaoCodeDesignator() const
Aircraft ICAO code designator.
@ TypeOwnSimulatorModel
represents own simulator model (AI model, model on disk)
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.
const QString & getDescription() const
Get description.
bool hasDescription() const
Has description.
Simple hardcoded info about the corresponding simulator.
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.
CStatusMessage clearCachedModels(const CSimulatorInfo &simulator)
Look like IMultiSimulatorModelCaches interface.
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.
virtual ~CAircraftModelLoaderXPlane()
Virtual destructor.
void updateInstalledModels(const CAircraftModelList &models)
Parsed or injected models.
virtual 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.
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.