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:
Roland Winklmeier
2015-05-08 13:49:24 +02:00
parent 5e4fe4d5d7
commit 6275e06b4f
2 changed files with 397 additions and 0 deletions

View 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

View 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