Files
pilotclient/src/blackmisc/simulation/fscommon/vpilotrulesreader.cpp

287 lines
11 KiB
C++

/* 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 "blackmisc/logmessage.h"
#include "blackmisc/simulation/fscommon/vpilotmodelrule.h"
#include "blackmisc/simulation/fscommon/vpilotrulesreader.h"
#include "blackmisc/statusmessage.h"
#include "blackmisc/worker.h"
#include <QByteArray>
#include <QDateTime>
#include <QDir>
#include <QDomDocument>
#include <QDomNamedNodeMap>
#include <QDomNode>
#include <QDomNodeList>
#include <QFile>
#include <QFileInfo>
#include <QFileInfoList>
#include <QFlags>
#include <QIODevice>
#include <QReadLocker>
#include <QStandardPaths>
#include <QTimer>
#include <QWriteLocker>
#include <QtGlobal>
using namespace BlackMisc;
using namespace BlackMisc::Network;
namespace BlackMisc
{
namespace Simulation
{
namespace FsCommon
{
CVPilotRulesReader::CVPilotRulesReader(bool standardDirectory, QObject *parent) :
QObject(parent)
{
if (standardDirectory) { this->addDirectory(CVPilotRulesReader::standardMappingsDirectory()); }
}
CVPilotRulesReader::~CVPilotRulesReader()
{
gracefulShutdown();
}
QStringList CVPilotRulesReader::getFiles() const
{
QReadLocker l(&m_lockData);
return m_fileList;
}
bool CVPilotRulesReader::hasFiles() const
{
QReadLocker l(&m_lockData);
return !m_fileList.isEmpty();
}
void CVPilotRulesReader::addFilename(const QString &fileName)
{
QWriteLocker l(&m_lockData);
if (this->m_fileList.contains(fileName)) { return; }
this->m_fileList.append(fileName);
}
void CVPilotRulesReader::addDirectory(const QString &directory)
{
QDir dir(directory);
if (!dir.exists()) { return; }
QStringList nameFilters({"*.vmr"});
QFileInfoList entries = dir.entryInfoList(nameFilters, QDir::Files | QDir::Readable);
for (const QFileInfo &file : entries)
{
this->addFilename(file.absoluteFilePath());
}
}
int CVPilotRulesReader::countFilesLoaded() const
{
QReadLocker l(&m_lockData);
return m_loadedFiles;
}
CVPilotModelRuleSet CVPilotRulesReader::getRules() const
{
QReadLocker l(&m_lockData);
return m_rules;
}
int CVPilotRulesReader::getModelsCount() const
{
return this->m_cachedVPilotModels.getThreadLocal().size();
}
CAircraftModelList CVPilotRulesReader::getAsModels()
{
// already cached?
CAircraftModelList vPilotModels(this->m_cachedVPilotModels.get());
if (!vPilotModels.isEmpty() || m_rules.isEmpty()) { return vPilotModels; }
// important: that can take a while and should normally
// run in background
const CVPilotModelRuleSet rules(getRules()); // thread safe copy
vPilotModels = rules.toAircraftModels(); // long lasting operation
this->ps_setCache(vPilotModels);
return vPilotModels;
}
CAircraftModelList CVPilotRulesReader::getAsModelsFromCache() const
{
return this->m_cachedVPilotModels.get();
}
int CVPilotRulesReader::countRulesLoaded() const
{
QReadLocker l(&m_lockData);
return m_rules.size();
}
void CVPilotRulesReader::gracefulShutdown()
{
QWriteLocker l(&m_lockData);
m_shutdown = true;
}
const QString &CVPilotRulesReader::standardMappingsDirectory()
{
static QString directory;
if (directory.isEmpty())
{
directory = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation).first();
if (!directory.endsWith('/')) { directory.append('/'); }
directory.append("vPilot Files/Model Matching Rule Sets");
}
return directory;
}
bool CVPilotRulesReader::read(bool convertToModels)
{
int loadedFiles = 0;
QStringList filesWithProblems;
CVPilotModelRuleSet rules;
const QStringList fileList(getFiles());
for (const QString &fn : fileList)
{
if (m_shutdown) { return false; }
loadedFiles++;
bool s = this->loadFile(fn, rules);
if (!s) { this->m_fileListWithProblems.append(fn); }
}
{
QWriteLocker l(&m_lockData);
this->m_loadedFiles = loadedFiles;
this->m_fileListWithProblems = filesWithProblems;
this->m_rules = rules;
if (m_shutdown) { return false; }
}
const bool success = loadedFiles > 0;
if (convertToModels)
{
const CAircraftModelList vPilotModels(rules.toAircraftModels()); // long lasting operation
this->ps_setCache(vPilotModels);
}
emit readFinished(success);
return success;
}
CWorker *CVPilotRulesReader::readInBackground(bool convertToModels)
{
// set a thread safe flag
{
QWriteLocker l(&m_lockData);
if (m_asyncLoadInProgress || m_shutdown) { return nullptr; }
m_asyncLoadInProgress = true;
}
BlackMisc::CWorker *worker = BlackMisc::CWorker::fromTask(this, "CVPilotRulesReader", [this, convertToModels]()
{
this->read(convertToModels);
});
worker->then(this, &CVPilotRulesReader::ps_readInBackgroundFinished);
return worker;
}
void CVPilotRulesReader::ps_readInBackgroundFinished()
{
QWriteLocker l(&m_lockData);
m_asyncLoadInProgress = false;
}
void CVPilotRulesReader::ps_onVPilotCacheChanged()
{
// void
}
void CVPilotRulesReader::ps_setCache(const CAircraftModelList &models)
{
if (this->m_cachedVPilotModels.isOwnerThread())
{
CStatusMessage m;
{
QWriteLocker l(&m_lockData);
m = this->m_cachedVPilotModels.set(models);
}
if (m.isFailure())
{
CLogMessage::preformatted(m);
}
}
else
{
QTimer::singleShot(0, this, [this, models]() { this->ps_setCache(models); });
}
}
bool CVPilotRulesReader::loadFile(const QString &fileName, CVPilotModelRuleSet &ruleSet)
{
QFile f(fileName);
if (!f.exists()) { return false; }
if (!f.open(QFile::ReadOnly | QFile::Text)) { return false; }
QByteArray fc = f.readAll();
if (fc.isEmpty()) { return false; }
QDomDocument doc;
if (!doc.setContent(fc)) { return false; }
QDomNodeList rules = doc.elementsByTagName("ModelMatchRule");
if (rules.isEmpty()) { return false; }
QDomNodeList mmRuleSet = doc.elementsByTagName("ModelMatchRuleSet");
if (mmRuleSet.size() < 1) { return true; }
const QDomNamedNodeMap attributes = mmRuleSet.at(0).attributes();
QString folder = attributes.namedItem("Folder").nodeValue().trimmed();
if (folder.isEmpty())
{
folder = QFileInfo(fileName).fileName().replace(".vmr", "");
}
// "2/1/2014 12:00:00 AM", "5/26/2014 2:00:00 PM"
const QString updated = attributes.namedItem("UpdatedOn").nodeValue();
QDateTime qt = QDateTime::fromString(updated, "M/d/yyyy h:mm:ss AP");
qint64 updatedTimestamp = qt.toMSecsSinceEpoch();
int rulesSize = rules.size();
for (int i = 0; i < rulesSize; i++)
{
const QDomNamedNodeMap ruleAttributes = rules.at(i).attributes();
const QString typeCode = ruleAttributes.namedItem("TypeCode").nodeValue();
const QString modelName = ruleAttributes.namedItem("ModelName").nodeValue();
// remark, callsign prefix is airline ICAO code
const QString callsignPrefix = ruleAttributes.namedItem("CallsignPrefix").nodeValue();
if (modelName.isEmpty()) { continue; }
// split if we have multiple models
if (modelName.contains("//"))
{
// multiple models
const QStringList models = modelName.split("//");
for (const QString &model : models)
{
if (model.isEmpty()) { continue; }
CVPilotModelRule rule(model, folder, typeCode, callsignPrefix, updatedTimestamp);
ruleSet.push_back(rule);
}
}
else
{
// single model
CVPilotModelRule rule(modelName, folder, typeCode, callsignPrefix, updatedTimestamp);
ruleSet.push_back(rule);
}
}
return true;
}
} // namespace
} // namespace
} // namespace