13 #include <QFileInfoList>
19 #include <QStringView>
20 #include <QTextStream>
33 using namespace swift::config;
35 using namespace swift::misc::simulation;
36 using namespace swift::misc::simulation::fscommon;
37 using namespace swift::misc::network;
39 namespace swift::misc::simulation::fscommon
42 using LoaderResponse = std::tuple<CAircraftCfgEntriesList, CAircraftModelList, CStatusMessageList>;
44 CAircraftCfgParser::CAircraftCfgParser(
const CSimulatorInfo &simInfo, QObject *parent)
56 if (m_parserWorker) { m_parserWorker->waitForFinished(); }
60 const QStringList &modelDirectories)
63 u
"Aircraft config parser loaded data");
65 u
"Aircraft config parser did NOT load data");
69 const QStringList excludedDirectoryPatterns(
74 if (m_parserWorker && !m_parserWorker->isFinished()) {
return; }
78 [
this, modelDirs, excludedDirectoryPatterns, simulator, modelConsolidation]() {
81 this->performParsing(modelDirs, excludedDirectoryPatterns, msgs);
86 if (modelConsolidation) { modelConsolidation(models,
true); }
88 return std::make_tuple(aircraftCfgEntriesList, models, msgs);
90 m_parserWorker->thenWithResult<LoaderResponse>(
this, [
this, simulator](
const LoaderResponse &tuple) {
94 m_parsedCfgEntriesList = std::get<0>(tuple);
96 const bool hasData = !models.
isEmpty();
110 m_parsedCfgEntriesList = this->performParsing(modelDirs, excludedDirectoryPatterns, msgs);
114 const bool hasData = !models.
isEmpty();
124 const QStringList &excludeDirectories,
128 for (
const QString &dir : directories)
130 entries.
push_back(this->performParsing(dir, excludeDirectories, messages));
136 const QStringList &excludeDirectories,
154 static const QString NoNameFilter;
155 QDir dir(directory, NoNameFilter, QDir::Name, QDir::Files | QDir::AllDirs | QDir::NoDotAndDotDot);
166 const QString currentDir = dir.absolutePath();
172 const QFileInfoList files =
173 dir.entryInfoList(QDir::Files | QDir::AllDirs | QDir::NoDotAndDotDot, QDir::DirsLast);
178 QDir::Files | QDir::NoDotAndDotDot);
179 const int airFilesCount = dirForAir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot, QDir::DirsLast).size();
180 const bool hasAirFiles = airFilesCount > 0;
188 for (
const auto &fileInfo : files)
191 if (fileInfo.isDir())
193 const QString nextDir = fileInfo.absoluteFilePath();
194 if (currentDir.startsWith(nextDir, Qt::CaseInsensitive)) {
continue; }
195 if (dir == currentDir) {
continue; }
208 if (
getSimulator().isP3D() && !hasAirFiles) {
continue; }
214 const QString fileName = fileInfo.absoluteFilePath();
248 if (!file.open(QFile::ReadOnly | QFile::Text))
257 QTextStream in(&file);
258 QList<CAircraftCfgEntries> tempEntries;
263 QString fltSection(
"[FLTSIM.0]");
264 static const QString fltSectionStr(
"[FLTSIM.%1]");
266 int fltsimCounter = 0;
267 FileSection currentSection = Unknown;
268 const bool isRotorcraftPath = fileName.contains(
"rotorcraft", Qt::CaseInsensitive);
272 const QString lineFixed(in.readLine().trimmed());
273 if (lineFixed.isEmpty()) {
continue; }
274 if (lineFixed.startsWith(
"["))
276 if (lineFixed.startsWith(
"[GENERAL]", Qt::CaseInsensitive))
278 currentSection = General;
281 if (lineFixed.startsWith(fltSection, Qt::CaseInsensitive))
285 tempEntries.append(e);
286 currentSection = Fltsim;
287 fltSection = fltSectionStr.arg(++fltsimCounter);
290 currentSection = Unknown;
293 switch (currentSection)
297 if (lineFixed.startsWith(
"//")) {
break; }
298 if (atcType.isEmpty() || atcModel.isEmpty())
300 const QString c = getFixedIniLineContent(lineFixed);
301 if (lineFixed.startsWith(
"atc_type", Qt::CaseInsensitive)) { atcType = c; }
306 else if (lineFixed.startsWith(
"icao_type_designator", Qt::CaseInsensitive)) { atcModel = c; }
312 if (lineFixed.startsWith(
"//")) {
break; }
314 if (lineFixed.startsWith(
"atc_", Qt::CaseInsensitive))
316 if (lineFixed.startsWith(
"atc_parking_codes", Qt::CaseInsensitive))
320 else if (lineFixed.startsWith(
"atc_airline", Qt::CaseInsensitive))
324 else if (lineFixed.startsWith(
"atc_id_color", Qt::CaseInsensitive))
329 else if (lineFixed.startsWith(
"ui_", Qt::CaseInsensitive))
331 if (lineFixed.startsWith(
"ui_manufacturer", Qt::CaseInsensitive))
335 else if (lineFixed.startsWith(
"ui_typerole", Qt::CaseInsensitive))
337 bool r = getFixedIniLineContent(lineFixed).toLower().contains(
"rotor");
340 else if (lineFixed.startsWith(
"ui_type", Qt::CaseInsensitive))
342 e.
setUiType(getFixedIniLineContent(lineFixed));
344 else if (lineFixed.startsWith(
"ui_variation", Qt::CaseInsensitive))
349 else if (lineFixed.startsWith(
"description", Qt::CaseInsensitive))
353 else if (lineFixed.startsWith(
"texture", Qt::CaseInsensitive))
355 e.
setTexture(getFixedIniLineContent(lineFixed));
357 else if (lineFixed.startsWith(
"createdBy", Qt::CaseInsensitive))
361 else if (lineFixed.startsWith(
"sim", Qt::CaseInsensitive))
363 e.
setSimName(getFixedIniLineContent(lineFixed));
365 else if (lineFixed.startsWith(
"title", Qt::CaseInsensitive))
367 e.
setTitle(getFixedIniLineContent(lineFixed));
378 const QFileInfo fileInfo(fnFixed);
379 QDateTime fileTimestamp(fileInfo.lastModified());
380 if (!fileTimestamp.isValid() || fileInfo.birthTime() > fileTimestamp) { fileTimestamp = fileInfo.birthTime(); }
381 Q_ASSERT_X(fileTimestamp.isValid(), Q_FUNC_INFO,
"Missing file timestamp");
386 if (e.getTitle().isEmpty())
389 .
info(u
"FS model in %1, index %2 has no title")
390 << fileName << e.getIndex();
404 QString CAircraftCfgParser::fixedStringContent(
const QSettings &settings,
const QString &key)
406 return fixedStringContent(settings.value(key));
409 QString CAircraftCfgParser::fixedStringContent(
const QVariant &qv)
411 if (qv.isNull() || !qv.isValid())
415 else if (
static_cast<QMetaType::Type
>(qv.type()) == QMetaType::QStringList)
417 const QStringList l = qv.toStringList();
418 return l.join(
",").trimmed();
420 else if (
static_cast<QMetaType::Type
>(qv.type()) == QMetaType::QString) {
return qv.toString().trimmed(); }
425 QString CAircraftCfgParser::getFixedIniLineContent(
const QString &line)
427 if (line.isEmpty()) {
return {}; }
430 const int indexComment = line.indexOf(
';');
431 QString content = QStringView { line }.left(indexComment - 1).trimmed().toString();
433 const int index = line.indexOf(
'=');
434 if (index < 0) {
return {}; }
435 if (line.length() < index + 1) {
return {}; }
437 content = QStringView { content }.mid(index + 1).trimmed().toString();
445 if (content.size() > 2 && content.startsWith(
'"') && content.endsWith(
'"'))
449 content.remove(0, 1);
461 if (content.startsWith(
'"')) { content.remove(0, 1); }
465 content.replace(
"\\n",
" ");
466 content.replace(
"\\t",
" ");
471 const QStringList &CAircraftCfgParser::fileNameFilters(
bool isMSFS,
bool isMSFS2024)
473 if (CBuildConfig::buildWordSize() == 32 || isMSFS || isMSFS2024)
475 static const QStringList f({
"aircraft.cfg" });
480 static const QStringList f({
"aircraft.cfg",
"sim.cfg" });
485 bool CAircraftCfgParser::isExcludedSubDirectory(
const QString &checkDirectory)
487 if (checkDirectory.isEmpty()) {
return false; }
489 if (dir == u
"texture" || dir.startsWith(
"texture.")) {
return true; }
490 if (dir == u
"sound" || dir ==
"soundai") {
return true; }
491 if (dir == u
"panel") {
return true; }
492 if (dir == u
"model") {
return true; }
497 Q_DECLARE_METATYPE(swift::misc::simulation::fscommon::LoaderResponse)
static QString lastPathSegment(const QString &path)
Last path segment a/b/c => c.
static QString fixWindowsUncPath(const QString &filePath)
UNC file paths on Qt start with "/", but UNC file paths only work when they start with "//".
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 & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
void push_back(const T &value)
Appends an element at the end of the sequence.
void push_front(const T &value)
Insert as first element.
bool isEmpty() const
Synonym for empty.
Streamable status message, e.g.
constexpr static auto SeverityError
Status severities.
constexpr static auto SeverityInfo
Status severities.
Status messages, e.g. from Core -> GUI.
bool isSuccess() const
All messages are marked as success.
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.
void setUtcTimestamp(const QDateTime ×tamp)
Set timestamp.
Value object encapsulating a list of aircraft models.
Simple hardcoded info about the corresponding simulator.
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.
std::atomic< bool > m_cancelLoading
flag, requesting to cancel loading
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
CStatusMessage setCachedModels(const CAircraftModelList &models, const CSimulatorInfo &simulator)
Look like IMultiSimulatorModelCaches interface.
void setModelsForSimulator(const CAircraftModelList &models, const CSimulatorInfo &simulator)
Set models.
Set of aircraft.cfg entries representing an aircraft (FSX)
void setSimName(const QString &simName)
Simulator name.
void setDescription(const QString &description)
Description.
void setAtcAirline(const QString &airline)
Airline.
void setCreatedBy(const QString &createdBy)
Created by.
void setTitle(const QString &title)
Title.
void setAtcModel(const QString &atcModel)
ATC model.
void setTexture(const QString &texture)
Texture.
void setUiType(const QString &type)
UI type (e.g. A321-231 IAE)
void setRotorcraft(bool isRotorcraft)
Is Rotorcraft?
void setUiVariation(const QString &variation)
UI variation (e.g. White,Green)
void setAtcIdColor(const QString &color)
ATC color (e.g. 0xffffffff)
void setAtcType(const QString &atcType)
ATC type.
void setUiManufacturer(const QString &manufacturer)
UI manufacturer (e.g. Airbus)
void setAtcParkingCode(const QString &parkingCode)
Parking code.
Utility, providing FS aircraft.cfg entries.
swift::misc::simulation::CAircraftModelList toAircraftModelList(bool ignoreDuplicatesAndEmptyModelStrings, CStatusMessageList &msgs) const
As aircraft models.
Utility, parsing the aircraft.cfg files.
virtual bool isLoadingFinished() const
Loading finished?
CAircraftCfgParser(const CSimulatorInfo &simInfo, QObject *parent=nullptr)
Constructor.
static CAircraftCfgParser * createModelLoader(const CSimulatorInfo &simInfo, QObject *parent=nullptr)
Create an parser object for given simulator.
static CAircraftCfgEntriesList performParsingOfSingleFile(const QString &fileName, bool &ok, CStatusMessageList &msgs)
Parse a single file.
virtual ~CAircraftCfgParser()
Virtual destructor.
virtual void startLoadingFromDisk(LoadMode mode, const ModelConsolidationCallback &modelConsolidation, const QStringList &modelDirectories)
Start the loading process from disk.
static const QString & airFileFilter()
.air file filter
QStringList getModelExcludeDirectoryPatternsOrDefault(const CSimulatorInfo &simulator) const
Model exclude patterns per simulator.
Free functions in swift::misc.
SWIFT_MISC_EXPORT bool hasBalancedQuotes(const QString &in, char quote='"')
Has balanced quotes.