mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-03-22 23:05:36 +08:00
refs #409 CAircraftCfgParser to parse aircraft.cfg files
Previously value class CAircraftCfgEntriesList was in charge of parsing the cfg files. Moving it into its own class breaks up some coupling issues.
This commit is contained in:
278
src/blackmisc/simulation/fscommon/aircraftcfgparser.cpp
Normal file
278
src/blackmisc/simulation/fscommon/aircraftcfgparser.cpp
Normal file
@@ -0,0 +1,278 @@
|
||||
/* Copyright (C) 2015
|
||||
* swift project Community / Contributors
|
||||
*
|
||||
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
|
||||
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
|
||||
* including this file, may be copied, modified, propagated, or distributed except according to the terms
|
||||
* contained in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "aircraftcfgparser.h"
|
||||
#include "blackmisc/predicates.h"
|
||||
#include "blackmisc/logmessage.h"
|
||||
|
||||
using namespace BlackMisc;
|
||||
using namespace BlackMisc::Simulation;
|
||||
using namespace BlackMisc::Network;
|
||||
|
||||
namespace BlackMisc
|
||||
{
|
||||
namespace Simulation
|
||||
{
|
||||
namespace FsCommon
|
||||
{
|
||||
|
||||
bool CAircraftCfgParser::changeRootDirectory(const QString &directory)
|
||||
{
|
||||
if (m_rootDirectory == directory) { return false; }
|
||||
if (directory.isEmpty() || !existsDir(directory)) { return false; }
|
||||
|
||||
m_rootDirectory = directory;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CAircraftCfgParser::parse(ParserMode mode)
|
||||
{
|
||||
if (mode == ModeAsync)
|
||||
{
|
||||
if (m_parserWorker && !m_parserWorker->isFinished()) return;
|
||||
|
||||
auto rootDirectory = m_rootDirectory;
|
||||
auto excludedDirectories = m_excludedDirectories;
|
||||
|
||||
m_parserWorker = BlackMisc::CWorker::fromTask(this, "CAircraftCfgParser::changeDirectory",
|
||||
[this, rootDirectory, excludedDirectories]()
|
||||
{
|
||||
auto aircraftCfgEntriesList = parseImpl(rootDirectory, excludedDirectories);
|
||||
QMetaObject::invokeMethod(this, "ps_updateCfgEntriesList",
|
||||
Q_ARG(BlackMisc::Simulation::FsCommon::CAircraftCfgEntriesList, aircraftCfgEntriesList));
|
||||
});
|
||||
}
|
||||
else if (mode == ModeBlocking)
|
||||
{
|
||||
m_parsedCfgEntriesList = parseImpl(m_rootDirectory, m_excludedDirectories);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void CAircraftCfgParser::ps_updateCfgEntriesList(const CAircraftCfgEntriesList &cfgEntriesList)
|
||||
{
|
||||
m_parsedCfgEntriesList = cfgEntriesList;
|
||||
emit parsingFinished();
|
||||
}
|
||||
|
||||
CAircraftCfgEntriesList CAircraftCfgParser::parseImpl(const QString &directory, const QStringList &excludeDirectories)
|
||||
{
|
||||
if (m_cancelParsing) { return CAircraftCfgEntriesList(); }
|
||||
|
||||
// excluded?
|
||||
for (const auto &excludeDir : excludeDirectories)
|
||||
{
|
||||
if (directory.contains(excludeDir, Qt::CaseInsensitive))
|
||||
{
|
||||
CLogMessage(this).debug() << "Skipping directory " << directory;
|
||||
return CAircraftCfgEntriesList();
|
||||
}
|
||||
}
|
||||
|
||||
// set directory with name filters, get aircraft.cfg and sub directories
|
||||
QDir dir(directory, "aircraft.cfg", QDir::Name, QDir::Files | QDir::AllDirs);
|
||||
if (!dir.exists()) return CAircraftCfgEntriesList(); // can happen if there are shortcuts or linked dirs not available
|
||||
|
||||
QString currentDir = dir.absolutePath();
|
||||
|
||||
CAircraftCfgEntriesList result;
|
||||
|
||||
// Dirs last is crucial,since I will break recursion on "aircraft.cfg" level
|
||||
QFileInfoList files = dir.entryInfoList(QDir::Files | QDir::AllDirs | QDir::NoDotAndDotDot, QDir::DirsLast);
|
||||
for (const auto &file : files)
|
||||
{
|
||||
if (m_cancelParsing) { return CAircraftCfgEntriesList(); }
|
||||
if (file.isDir())
|
||||
{
|
||||
QString nextDir = file.absoluteFilePath();
|
||||
if (currentDir.startsWith(nextDir, Qt::CaseInsensitive)) { continue; } // do not go up
|
||||
if (dir == currentDir) { continue; } // do not recursively call same directory
|
||||
result.push_back(parseImpl(nextDir, excludeDirectories));
|
||||
}
|
||||
else
|
||||
{
|
||||
// due to the filter we expect only "aircraft.cfg" here
|
||||
// remark: in a 1st version I have used QSettings to parse to file as ini file
|
||||
// unfortunately some files are malformed which could end up in wrong data
|
||||
|
||||
QString fileName = file.absoluteFilePath();
|
||||
QFile file(fileName);
|
||||
if (!file.open(QFile::ReadOnly | QFile::Text))
|
||||
{
|
||||
CLogMessage(this).warning("Unable to read file %1") << fileName;
|
||||
continue;
|
||||
}
|
||||
QTextStream in(&file);
|
||||
QList<CAircraftCfgEntries> tempEntries;
|
||||
|
||||
// parse through the file
|
||||
QString atcType;
|
||||
QString atcModel;
|
||||
QString fltSection("[FLTSIM.0]");
|
||||
int fltsimCounter = 0;
|
||||
FileSection currentSection = Unknown;
|
||||
bool isRotorcraftPath = fileName.toLower().contains("rotorcraft");
|
||||
|
||||
while (!in.atEnd())
|
||||
{
|
||||
const QString lineFixed(in.readLine().trimmed());
|
||||
if (lineFixed.isEmpty()) { continue; }
|
||||
if (lineFixed.startsWith("["))
|
||||
{
|
||||
if (lineFixed.startsWith("[GENERAL]", Qt::CaseInsensitive)) { currentSection = General; continue; }
|
||||
if (lineFixed.startsWith(fltSection, Qt::CaseInsensitive))
|
||||
{
|
||||
CAircraftCfgEntries e(fileName, fltsimCounter);
|
||||
if (isRotorcraftPath)
|
||||
{
|
||||
e.setRotorcraft(true);
|
||||
}
|
||||
tempEntries.append(e);
|
||||
currentSection = Fltsim;
|
||||
fltSection = QString("[FLTSIM.%1]").arg(++fltsimCounter);
|
||||
continue;
|
||||
}
|
||||
currentSection = Unknown;
|
||||
continue;
|
||||
}
|
||||
switch (currentSection)
|
||||
{
|
||||
case General:
|
||||
{
|
||||
if (lineFixed.startsWith("//")) { break; }
|
||||
if (atcType.isEmpty() || atcModel.isEmpty())
|
||||
{
|
||||
QString c = getFixedIniLineContent(lineFixed);
|
||||
if (lineFixed.startsWith("atc_type", Qt::CaseInsensitive)) { atcType = c; }
|
||||
else if (lineFixed.startsWith("atc_model", Qt::CaseInsensitive)) { atcModel = c; }
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Fltsim:
|
||||
{
|
||||
if (lineFixed.startsWith("//")) { break; }
|
||||
CAircraftCfgEntries &e = tempEntries[tempEntries.size() - 1];
|
||||
if (lineFixed.startsWith("atc_parking_codes", Qt::CaseInsensitive))
|
||||
{
|
||||
e.setAtcParkingCode(getFixedIniLineContent(lineFixed));
|
||||
}
|
||||
else if (lineFixed.startsWith("description", Qt::CaseInsensitive))
|
||||
{
|
||||
e.setDescription(getFixedIniLineContent(lineFixed));
|
||||
}
|
||||
else if (lineFixed.startsWith("ui_manufacturer", Qt::CaseInsensitive))
|
||||
{
|
||||
e.setUiManufacturer(getFixedIniLineContent(lineFixed));
|
||||
}
|
||||
else if (lineFixed.startsWith("ui_typerole", Qt::CaseInsensitive))
|
||||
{
|
||||
bool r = getFixedIniLineContent(lineFixed).toLower().contains("rotor");
|
||||
e.setRotorcraft(r);
|
||||
}
|
||||
else if (lineFixed.startsWith("ui_type", Qt::CaseInsensitive))
|
||||
{
|
||||
e.setUiType(getFixedIniLineContent(lineFixed));
|
||||
}
|
||||
else if (lineFixed.startsWith("texture", Qt::CaseInsensitive))
|
||||
{
|
||||
e.setTexture(getFixedIniLineContent(lineFixed));
|
||||
}
|
||||
else if (lineFixed.startsWith("title", Qt::CaseInsensitive))
|
||||
{
|
||||
e.setTitle(getFixedIniLineContent(lineFixed));
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
case Unknown: break;
|
||||
}
|
||||
} // all lines
|
||||
file.close();
|
||||
|
||||
// store all entries
|
||||
|
||||
for (const CAircraftCfgEntries &e : tempEntries)
|
||||
{
|
||||
if (e.getTitle().isEmpty())
|
||||
{
|
||||
CLogMessage(this).info("FS model in %1, index %2 has no title") << fileName << e.getIndex();
|
||||
continue;
|
||||
}
|
||||
CAircraftCfgEntries newEntries(e);
|
||||
newEntries.setAtcModel(atcModel);
|
||||
newEntries.setAtcType(atcType);
|
||||
result.push_back(newEntries);
|
||||
}
|
||||
return result; // do not go any deeper in file tree, we found aircraft.cfg
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CAircraftCfgParser::existsDir(const QString &directory) const
|
||||
{
|
||||
if (directory.isEmpty()) { return false; }
|
||||
QDir dir(directory);
|
||||
//! \todo not available network dir can make this hang here
|
||||
return dir.exists();
|
||||
}
|
||||
|
||||
QString CAircraftCfgParser::fixedStringContent(const QSettings &settings, const QString &key)
|
||||
{
|
||||
return fixedStringContent(settings.value(key));
|
||||
}
|
||||
|
||||
QString CAircraftCfgParser::fixedStringContent(const QVariant &qv)
|
||||
{
|
||||
if (qv.isNull() || !qv.isValid())
|
||||
{
|
||||
return ""; // normal when there is no settings value
|
||||
}
|
||||
else if (static_cast<QMetaType::Type>(qv.type()) == QMetaType::QStringList)
|
||||
{
|
||||
QStringList l = qv.toStringList();
|
||||
return l.join(",").trimmed();
|
||||
}
|
||||
else if (static_cast<QMetaType::Type>(qv.type()) == QMetaType::QString)
|
||||
{
|
||||
return qv.toString().trimmed();
|
||||
}
|
||||
Q_ASSERT(false);
|
||||
return "";
|
||||
}
|
||||
|
||||
QString CAircraftCfgParser::getFixedIniLineContent(const QString &line)
|
||||
{
|
||||
if (line.isEmpty()) { return ""; }
|
||||
int index = line.indexOf('=');
|
||||
if (index < 0) { return ""; }
|
||||
if (line.length() < index + 1) { return ""; }
|
||||
|
||||
QString content(line.mid(index + 1).trimmed());
|
||||
|
||||
// fix "" strings, some are malformed and just contain " at beginning, end
|
||||
if (content.endsWith('"')) { content.remove(content.size() - 1 , 1); }
|
||||
if (content.startsWith('"')) { content.remove(0 , 1); }
|
||||
|
||||
// fix C style linebreaks
|
||||
content.replace("\\n", " ");
|
||||
content.replace("\\t", " ");
|
||||
|
||||
// return
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
} // namespace
|
||||
119
src/blackmisc/simulation/fscommon/aircraftcfgparser.h
Normal file
119
src/blackmisc/simulation/fscommon/aircraftcfgparser.h
Normal file
@@ -0,0 +1,119 @@
|
||||
/* Copyright (C) 2013
|
||||
* swift project Community / Contributors
|
||||
*
|
||||
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
|
||||
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
|
||||
* including this file, may be copied, modified, propagated, or distributed except according to the terms
|
||||
* contained in the LICENSE file.
|
||||
*/
|
||||
|
||||
//! \file
|
||||
|
||||
#ifndef BLACKMISC_SIMULATION_FSCOMMON_AIRCRAFTCFPARSER_H
|
||||
#define BLACKMISC_SIMULATION_FSCOMMON_AIRCRAFTCFGPARSER_H
|
||||
|
||||
#include "blackmisc/blackmiscexport.h"
|
||||
#include "blackmisc/worker.h"
|
||||
#include "blackmisc/simulation/fscommon/aircraftcfgentrieslist.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace BlackMisc
|
||||
{
|
||||
namespace Simulation
|
||||
{
|
||||
namespace FsCommon
|
||||
{
|
||||
|
||||
//! Utility, parsing the aircraft.cfg files
|
||||
class BLACKMISC_EXPORT CAircraftCfgParser : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
//! Parser mode
|
||||
enum ParserMode
|
||||
{
|
||||
ModeBlocking,
|
||||
ModeAsync
|
||||
};
|
||||
|
||||
CAircraftCfgParser() { }
|
||||
|
||||
//! Constructor
|
||||
CAircraftCfgParser(const QString &rootDirectory, const QStringList &exludes = {}) :
|
||||
m_rootDirectory(rootDirectory),
|
||||
m_excludedDirectories(exludes)
|
||||
{ }
|
||||
|
||||
//! Virtual destructor
|
||||
virtual ~CAircraftCfgParser() {}
|
||||
|
||||
//! Change the directory
|
||||
bool changeRootDirectory(const QString &directory);
|
||||
|
||||
//! Parse all cfg files
|
||||
void parse(ParserMode mode = ModeAsync);
|
||||
|
||||
//! Has current directory been parsed?
|
||||
bool isParsingFinished() const { return m_parserWorker->isFinished(); }
|
||||
|
||||
//! Cancel read
|
||||
void cancelParsing() { m_cancelParsing = true; }
|
||||
|
||||
//! Current root directory
|
||||
QString getRootDirectory() const { return this->m_rootDirectory; }
|
||||
|
||||
//! Get parsed aircraft cfg entries list
|
||||
CAircraftCfgEntriesList getAircraftCfgEntriesList() const { return m_parsedCfgEntriesList; }
|
||||
|
||||
signals:
|
||||
|
||||
//! Parsing is finished
|
||||
void parsingFinished();
|
||||
|
||||
private slots:
|
||||
|
||||
void ps_updateCfgEntriesList(const BlackMisc::Simulation::FsCommon::CAircraftCfgEntriesList &cfgEntriesList);
|
||||
|
||||
CAircraftCfgEntriesList parseImpl(const QString &directory, const QStringList &excludeDirectories = {});
|
||||
|
||||
private:
|
||||
|
||||
//! Section within file
|
||||
|
||||
enum FileSection
|
||||
{
|
||||
General,
|
||||
Fltsim,
|
||||
Unknown
|
||||
};
|
||||
|
||||
//! Does the directory exist?
|
||||
bool existsDir(const QString &directory = "") const;
|
||||
|
||||
//! Fix the content read
|
||||
static QString fixedStringContent(const QVariant &qv);
|
||||
|
||||
//! Value from settings, fixed string
|
||||
static QString fixedStringContent(const QSettings &settings, const QString &key);
|
||||
|
||||
//! Content after "="
|
||||
static QString getFixedIniLineContent(const QString &line);
|
||||
|
||||
QString m_rootDirectory; //!< root directory parsing aircraft.cfg files
|
||||
QStringList m_excludedDirectories;
|
||||
|
||||
CAircraftCfgEntriesList m_parsedCfgEntriesList;
|
||||
QPointer<BlackMisc::CWorker> m_parserWorker;
|
||||
|
||||
std::atomic<bool> m_cancelParsing = { false };
|
||||
|
||||
};
|
||||
} // namespace
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
|
||||
#endif // guard
|
||||
Reference in New Issue
Block a user