13 #include <QDirIterator>
20 #include <QRegularExpression>
21 #include <QStringBuilder>
22 #include <QTextStream>
41 using namespace swift::config;
43 using namespace swift::misc::aviation;
44 using namespace swift::misc::simulation;
45 using namespace swift::misc::network;
47 namespace swift::misc::simulation::xplane
50 static void normalizePath(
QString &path)
54 if (e ==
'/' || e ==
':' || e ==
'\\') { e =
'/'; }
68 else {
return u
"[ACF] " % model.
getName(); }
79 return QStringLiteral(
"[ACF]");
82 CAircraftModelLoaderXPlane::CAircraftModelLoaderXPlane(
QObject *parent)
89 if (m_parserWorker) { m_parserWorker->waitForFinished(); }
106 << modelDirectories.
join(
", "),
113 if (m_parserWorker && !m_parserWorker->isFinished()) {
return; }
116 m_parserWorker =
CWorker::fromTask(
this,
"CAircraftModelLoaderXPlane::performParsing",
117 [
this, modelDirs, excludedDirectoryPatterns, modelConsolidation]() {
119 this->performParsing(modelDirs, excludedDirectoryPatterns);
120 if (modelConsolidation) { modelConsolidation(models,
true); }
132 CAircraftModelList models(this->performParsing(modelDirs, excludedDirectoryPatterns));
139 return !m_parserWorker || m_parserWorker->isFinished();
150 QString CAircraftModelLoaderXPlane::CSLPlane::getModelName()
const
152 QString modelName = dirNames.join(
' ') % u
' ' % objectName;
153 if (objectVersion == OBJ7) { modelName += u
' ' % textureName; }
154 return std::move(modelName).
trimmed();
161 for (
const QString &rootDirectory : rootDirectories)
163 allModels.
push_back(parseCslPackages(rootDirectory, excludeDirectories));
164 allModels.
push_back(parseFlyableAirplanes(rootDirectory, excludeDirectories));
176 u
"XPlane model '%1' exists already! Potential model string conflict! Ignoring it.")
186 Q_UNUSED(excludeDirectories)
187 if (rootDirectory.
isEmpty()) {
return {}; }
189 QDir searchPath(rootDirectory, fileFilterFlyable());
193 QStringLiteral(
"Parsing flyable airplanes in '%1'").arg(rootDirectory), -1);
196 while (aircraftIt.hasNext())
204 using namespace swift::misc::simulation::xplane::qtfreeutils;
212 model.setDistributor(dist);
214 if (!model.hasDescription()) { model.setDescription(descriptionForFlyableModel(model)); }
217 model.setFileDetailsAndTimestamp(aircraftIt.fileInfo());
218 model.setModelMode(CAircraftModel::Exclude);
219 addUniqueModel(model, installedModels);
221 const QString baseModelString = model.getModelString();
227 QStringLiteral(
"Parsing flyable liveries in '%1'").arg(aircraftIt.fileInfo().canonicalPath()), -1);
228 while (liveryIt.hasNext())
231 model.setModelString(baseModelString % u
' ' % liveryIt.fileName());
232 addUniqueModel(model, installedModels);
235 return installedModels;
241 Q_UNUSED(excludeDirectories);
242 if (rootDirectory.
isEmpty()) {
return {}; }
244 m_cslPackages.clear();
246 QDir searchPath(rootDirectory, fileFilterCsl());
250 QString packageFile = it.next();
253 const QString packageFilePath = it.fileInfo().absolutePath();
254 QFile file(packageFile);
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);
281 content.
append(ts.readAll());
283 parseFullPackage(content, package);
285 for (
const auto &plane : std::as_const(package.planes))
291 u
"XPlane model '%1' exists already! Potential model string conflict! Ignoring it.")
292 << plane.getModelName();
299 const QFileInfo modelFileInfo(plane.filePath);
300 model.setFileDetailsAndTimestamp(modelFileInfo);
301 model.setAircraftIcaoCode(icao);
303 if (CBuildConfig::isLocalDeveloperDebugBuild())
305 SWIFT_AUDIT_X(modelFileInfo.exists(), Q_FUNC_INFO,
"Model does NOT exist");
312 model.setLivery(livery);
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);
322 return installedModels;
325 bool CAircraftModelLoaderXPlane::doPackageSub(
QString &ioPath)
327 for (
auto i = m_cslPackages.cbegin(); i != m_cslPackages.cend(); ++i)
329 if (strncmp(qPrintable(i->name), qPrintable(ioPath),
static_cast<size_t>(i->name.size())) == 0)
331 ioPath.
remove(0, i->name.size());
332 ioPath.
insert(0, i->path);
339 bool CAircraftModelLoaderXPlane::parseExportCommand(
const QStringList &tokens, CSLPackage &package,
340 const QString &path,
int lineNum)
342 if (tokens.
size() != 2)
345 CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : EXPORT_NAME command requires 1 argument.")
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())
356 package.name = tokens[1];
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;
369 bool CAircraftModelLoaderXPlane::parseDependencyCommand(
const QStringList &tokens, CSLPackage &package,
370 const QString &path,
int lineNum)
373 if (tokens.
size() != 2)
376 CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : DEPENDENCY command requires 1 argument.")
382 if (std::count_if(m_cslPackages.cbegin(), m_cslPackages.cend(),
383 [&tokens](
const CSLPackage &p) { return p.name == tokens[1]; }) == 0)
386 u
"XPlane required package %1 not found. Aborting processing of this package.")
407 bool CAircraftModelLoaderXPlane::parseObjectCommand(
const QStringList &, CSLPackage &package,
const QString &path,
412 const auto m =
CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : Unsupported legacy CSL format.")
418 bool CAircraftModelLoaderXPlane::parseTextureCommand(
const QStringList &, CSLPackage &package,
const QString &path,
421 if (!package.planes.isEmpty() && !package.planes.back().hasErrors)
423 const auto m =
CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : Unsupported legacy CSL format.")
430 bool CAircraftModelLoaderXPlane::parseAircraftCommand(
const QStringList &, CSLPackage &package,
const QString &path,
433 package.planes.push_back(CSLPlane());
435 const auto m =
CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : Unsupported legacy CSL format.")
441 bool CAircraftModelLoaderXPlane::parseObj8AircraftCommand(
const QStringList &tokens, CSLPackage &package,
442 const QString &path,
int lineNum)
444 package.planes.push_back(CSLPlane());
447 if (tokens.
size() != 2)
450 u
"%1/xsb_aircraft.txt Line %2 : OBJ8_AIRCARFT command requires 1 argument.")
453 if (tokens.
size() < 2) {
return false; }
456 package.planes.back().objectName = tokens[1];
457 package.planes.
back().objectVersion = CSLPlane::OBJ8;
461 bool CAircraftModelLoaderXPlane::parseObj8Command(
const QStringList &tokens, CSLPackage &package,
462 const QString &path,
int lineNum)
465 if (tokens.
size() != 4)
467 if (tokens.
size() == 5 || tokens.
size() == 6)
471 u
"%1/xsb_aircraft.txt Line %2 : Unsupported IVAO CSL format - consider using CSL2XSB.")
478 CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : OBJ8 command takes 3 arguments.")
484 if (package.planes.isEmpty())
487 CStatusMessage(
this).error(u
"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
492 if (tokens[1] !=
"SOLID") {
return true; }
494 QString relativePath(tokens[3]);
495 normalizePath(relativePath);
496 QString fullPath(relativePath);
497 if (!doPackageSub(fullPath))
506 QString packageRootDir = package.path.
mid(package.path.lastIndexOf(
'/') + 1);
509 package.planes.
back().filePath = fullPath;
513 bool CAircraftModelLoaderXPlane::parseHasGearCommand(
const QStringList &tokens, CSLPackage &package,
514 const QString &path,
int lineNum)
523 bool CAircraftModelLoaderXPlane::parseIcaoCommand(
const QStringList &tokens, CSLPackage &package,
524 const QString &path,
int lineNum)
527 if (tokens.
size() != 2)
530 CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : ICAO command requires 1 argument.")
535 if (package.planes.isEmpty())
538 CStatusMessage(
this).error(u
"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
544 package.planes.
back().icao = icao;
548 bool CAircraftModelLoaderXPlane::parseAirlineCommand(
const QStringList &tokens, CSLPackage &package,
549 const QString &path,
int lineNum)
552 if (tokens.
size() != 3)
555 CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : AIRLINE command requires 2 arguments.")
560 if (package.planes.isEmpty())
563 CStatusMessage(
this).error(u
"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
569 package.planes.
back().icao = icao;
571 package.planes.
back().airline = airline;
575 bool CAircraftModelLoaderXPlane::parseLiveryCommand(
const QStringList &tokens, CSLPackage &package,
576 const QString &path,
int lineNum)
579 if (tokens.
size() != 4)
582 CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : LIVERY command requires 3 arguments.")
587 if (package.planes.isEmpty())
590 CStatusMessage(
this).error(u
"%1/xsb_aircraft.txt Line %2 : invalid position for command.")
596 package.planes.
back().icao = icao;
598 package.planes.
back().airline = airline;
600 package.planes.
back().livery = livery;
604 bool CAircraftModelLoaderXPlane::parseDummyCommand(
const QStringList & , CSLPackage & ,
610 CAircraftModelLoaderXPlane::CSLPackage CAircraftModelLoaderXPlane::parsePackageHeader(
const QString &path,
613 using command = std::function<bool(
const QStringList &, CSLPackage &,
const QString &,
int)>;
614 using namespace std::placeholders;
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));
637 auto it = commands.find(tokens[0]);
638 if (it != commands.end())
640 bool result = it.
value()(tokens, package, path, lineNum);
649 void CAircraftModelLoaderXPlane::parseFullPackage(
const QString &content, CSLPackage &package)
651 using command = std::function<bool(
const QStringList &, CSLPackage &,
const QString &,
int)>;
652 using namespace std::placeholders;
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));
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));
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));
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));
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));
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));
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));
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));
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));
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));
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));
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));
728 if (line.
isEmpty() || line[0] ==
'#')
continue;
732 auto it = commands.find(tokens[0]);
733 if (it != commands.end())
735 bool result = it.
value()(tokens, package, package.path, lineNum);
738 if (!package.planes.empty()) { package.planes.back().hasErrors =
true; }
744 CStatusMessage(
this).
error(u
"%1/xsb_aircraft.txt Line %2 : Unrecognized CSL command: '%3'")
745 << package.path << lineNum << tokens[0];
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());
757 const QString &CAircraftModelLoaderXPlane::fileFilterFlyable()
759 static const QString f(
"*.acf");
763 const QString &CAircraftModelLoaderXPlane::fileFilterCsl()
765 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.
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.
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.
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
T value(qsizetype i) 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 mid(qsizetype position, qsizetype n) &&
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.