You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmData/nmNumericalResultPersistenc...

522 lines
15 KiB
C++

#include "nmNumericalResultPersistence.h"
#include <QCryptographicHash>
#include <QDataStream>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QtGlobal>
#include <float.h>
#include <new>
#include <vtkNew.h>
#include <vtkUnstructuredGrid.h>
#include <vtkXMLUnstructuredGridReader.h>
#include <vtkXMLUnstructuredGridWriter.h>
namespace {
const char g_aWellHistoryMagic[8] = { 'N', 'M', 'W', 'H', 'I', 'S', '3', 0 };
const quint32 g_nWellHistoryFormatVersion = 1;
const quint32 g_nEndianMarker = 0x01020304u;
const quint32 g_nMaximumCurveSeries = 64u;
const quint64 g_nMaximumCurvePoints = 50000000ull;
const qint64 g_nStreamChunkBytes = 4 * 1024 * 1024;
bool setError(QString* pError, const QString& sError)
{
if(pError != NULL)
{
*pError = sError;
}
return false;
}
bool writeAll(QFile& oFile, const char* pData, quint64 nBytes)
{
quint64 nWritten = 0;
while(nWritten < nBytes)
{
const qint64 nChunk = static_cast<qint64>(qMin(
static_cast<quint64>(g_nStreamChunkBytes),
nBytes - nWritten));
const qint64 nResult = oFile.write(pData + nWritten, nChunk);
if(nResult != nChunk)
{
return false;
}
nWritten += static_cast<quint64>(nResult);
}
return true;
}
bool readAll(QFile& oFile, char* pData, quint64 nBytes)
{
quint64 nRead = 0;
while(nRead < nBytes)
{
const qint64 nChunk = static_cast<qint64>(qMin(
static_cast<quint64>(g_nStreamChunkBytes),
nBytes - nRead));
const qint64 nResult = oFile.read(pData + nRead, nChunk);
if(nResult != nChunk)
{
return false;
}
nRead += static_cast<quint64>(nResult);
}
return true;
}
template<typename T>
bool writePod(QFile& oFile, const T& oValue)
{
return writeAll(oFile, reinterpret_cast<const char*>(&oValue),
static_cast<quint64>(sizeof(T)));
}
template<typename T>
bool readPod(QFile& oFile, T& oValue)
{
return readAll(oFile, reinterpret_cast<char*>(&oValue),
static_cast<quint64>(sizeof(T)));
}
bool safeMultiply(quint64 nLeft, quint64 nRight, quint64& nProduct)
{
if(nLeft != 0 && nRight > (~static_cast<quint64>(0)) / nLeft)
{
return false;
}
nProduct = nLeft * nRight;
return true;
}
bool writeCurve(QFile& oFile, const QVector<QVector<double> >& vecCurve)
{
const quint32 nSeriesCount = static_cast<quint32>(vecCurve.size());
if(!writePod(oFile, nSeriesCount))
{
return false;
}
for(int nSeries = 0; nSeries < vecCurve.size(); ++nSeries)
{
const QVector<double>& vecValues = vecCurve[nSeries];
const quint64 nPointCount = static_cast<quint64>(vecValues.size());
quint64 nBytes = 0;
if(!safeMultiply(nPointCount, sizeof(double), nBytes) ||
!writePod(oFile, nPointCount) ||
(nBytes > 0 && !writeAll(oFile,
reinterpret_cast<const char*>(vecValues.constData()),
nBytes)))
{
return false;
}
}
return true;
}
bool readCurve(QFile& oFile,
QVector<QVector<double> >& vecCurve,
quint64& nTotalPointCount)
{
quint32 nSeriesCount = 0;
if(!readPod(oFile, nSeriesCount) || nSeriesCount > g_nMaximumCurveSeries)
{
return false;
}
QVector<QVector<double> > vecLoaded;
vecLoaded.resize(static_cast<int>(nSeriesCount));
for(quint32 nSeries = 0; nSeries < nSeriesCount; ++nSeries)
{
quint64 nPointCount = 0;
quint64 nBytes = 0;
if(!readPod(oFile, nPointCount) ||
nPointCount > g_nMaximumCurvePoints ||
nTotalPointCount > g_nMaximumCurvePoints - nPointCount ||
!safeMultiply(nPointCount, sizeof(double), nBytes) ||
nBytes > static_cast<quint64>(oFile.bytesAvailable()) ||
nPointCount > static_cast<quint64>(INT_MAX))
{
return false;
}
nTotalPointCount += nPointCount;
vecLoaded[static_cast<int>(nSeries)].resize(
static_cast<int>(nPointCount));
if(nBytes > 0 && !readAll(oFile,
reinterpret_cast<char*>(
vecLoaded[static_cast<int>(nSeries)].data()), nBytes))
{
return false;
}
}
vecCurve.swap(vecLoaded);
return true;
}
bool hasExactMagic(const char* pActual, const char* pExpected, int nSize)
{
for(int nIndex = 0; nIndex < nSize; ++nIndex)
{
if(pActual[nIndex] != pExpected[nIndex])
{
return false;
}
}
return true;
}
}
nmNumericalFileReference::nmNumericalFileReference()
: m_nLength(0)
{
}
bool nmNumericalFileReference::isValid() const
{
QString sNormalizedPath;
if(!nmNumericalResultPersistence::normalizeRelativePath(
m_sRelativePath, sNormalizedPath) ||
sNormalizedPath != m_sRelativePath || m_sSha1.size() != 40)
{
return false;
}
for(int nIndex = 0; nIndex < m_sSha1.size(); ++nIndex)
{
const QChar oChar = m_sSha1[nIndex];
if(!((oChar >= '0' && oChar <= '9') ||
(oChar >= 'a' && oChar <= 'f')))
{
return false;
}
}
return true;
}
nmNumericalWindowPayloadReferences::nmNumericalWindowPayloadReferences()
: m_bHasCurrentGrid(false),
m_bHasSnapshot(false)
{
}
int nmNumericalResultPersistence::projectVersion()
{
// v4 强制保存井别,旧版本不再按默认油相解释流量字段。
return 4;
}
quint64 nmNumericalResultPersistence::maximumBinaryPayloadBytes()
{
return 512ull * 1024ull * 1024ull;
}
bool nmNumericalResultPersistence::normalizeRelativePath(
const QString& sPath,
QString& sNormalizedPath)
{
sNormalizedPath.clear();
if(sPath.isEmpty() || QDir::isAbsolutePath(sPath) ||
sPath.contains(':'))
{
return false;
}
QString sForwardPath = sPath;
sForwardPath.replace('\\', '/');
const QStringList listParts = sForwardPath.split(
'/', QString::KeepEmptyParts);
if(listParts.isEmpty())
{
return false;
}
for(int nIndex = 0; nIndex < listParts.size(); ++nIndex)
{
if(listParts[nIndex].isEmpty() || listParts[nIndex] == "." ||
listParts[nIndex] == "..")
{
return false;
}
}
sNormalizedPath = listParts.join("/");
return !sNormalizedPath.isEmpty();
}
bool nmNumericalResultPersistence::resolveReferencedPath(
const QString& sRootDirectory,
const QString& sRelativePath,
QString& sAbsolutePath)
{
QString sBaseCanonical;
QString sFileCanonical;
sAbsolutePath.clear();
QString sNormalizedPath;
if(!normalizeRelativePath(sRelativePath, sNormalizedPath))
{
return false;
}
QString sRoot = QDir::cleanPath(QFileInfo(sRootDirectory).absoluteFilePath());
QString sTarget = QDir::cleanPath(
QDir(sRoot).absoluteFilePath(sNormalizedPath));
sRoot.replace('\\', '/');
sTarget.replace('\\', '/');
const QString sRootPrefix = sRoot.endsWith('/') ? sRoot : sRoot + "/";
if(!sTarget.startsWith(sRootPrefix, Qt::CaseInsensitive))
{
return false;
}
// 已存在文件再核对规范路径,防止目录联接或符号链接逃逸成果根目录。
sBaseCanonical = QFileInfo(sRoot).canonicalFilePath();
sFileCanonical = QFileInfo(sTarget).canonicalFilePath();
if(!sBaseCanonical.isEmpty() && !sFileCanonical.isEmpty())
{
QString sCanonicalPrefix = QDir::fromNativeSeparators(sBaseCanonical);
if(!sCanonicalPrefix.endsWith('/'))
{
sCanonicalPrefix += "/";
}
if(!QDir::fromNativeSeparators(sFileCanonical).startsWith(
sCanonicalPrefix, Qt::CaseInsensitive))
{
return false;
}
}
sAbsolutePath = QDir::toNativeSeparators(sTarget);
return true;
}
bool nmNumericalResultPersistence::buildFileReference(
const QString& sAbsolutePath,
const QString& sRelativePath,
nmNumericalFileReference& oReference)
{
QString sNormalizedPath;
if(!normalizeRelativePath(sRelativePath, sNormalizedPath))
{
return false;
}
QFile oFile(sAbsolutePath);
if(!oFile.open(QIODevice::ReadOnly))
{
return false;
}
QCryptographicHash oHash(QCryptographicHash::Sha1);
while(!oFile.atEnd())
{
const QByteArray baChunk = oFile.read(g_nStreamChunkBytes);
if(baChunk.isEmpty() && oFile.error() != QFile::NoError)
{
oFile.close();
return false;
}
oHash.addData(baChunk);
}
const qint64 nSize = oFile.size();
oFile.close();
if(nSize < 0)
{
return false;
}
oReference.m_sRelativePath = sNormalizedPath;
oReference.m_nLength = static_cast<quint64>(nSize);
oReference.m_sSha1 = QString::fromLatin1(oHash.result().toHex());
return oReference.isValid();
}
bool nmNumericalResultPersistence::validateFileReference(
const QString& sRootDirectory,
const nmNumericalFileReference& oReference,
QString* pAbsolutePath,
QString* pError)
{
if(pError != NULL)
{
pError->clear();
}
if(!oReference.isValid())
{
return setError(pError, "Numerical file reference is invalid.");
}
QString sFilePath;
if(!resolveReferencedPath(sRootDirectory,
oReference.m_sRelativePath, sFilePath))
{
return setError(pError, "Numerical file reference leaves its root directory.");
}
const QFileInfo oInfo(sFilePath);
if(!oInfo.exists() || !oInfo.isFile() || oInfo.size() < 0 ||
static_cast<quint64>(oInfo.size()) != oReference.m_nLength)
{
return setError(pError, "Numerical file length does not match its manifest.");
}
nmNumericalFileReference oActual;
if(!buildFileReference(sFilePath, oReference.m_sRelativePath, oActual) ||
oActual.m_nLength != oReference.m_nLength ||
oActual.m_sSha1 != oReference.m_sSha1)
{
return setError(pError, "Numerical file SHA-1 does not match its manifest.");
}
if(pAbsolutePath != NULL)
{
*pAbsolutePath = sFilePath;
}
return true;
}
bool nmNumericalResultPersistence::writeWellHistory(
const QString& sFilePath,
const nmNumericalWellHistoryData& oHistory,
QString* pError)
{
if(pError != NULL)
{
pError->clear();
}
QFile oFile(sFilePath);
if(!oFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
{
return setError(pError, "Cannot create numerical well history file.");
}
const bool bWritten = writeAll(oFile, g_aWellHistoryMagic, 8) &&
writePod(oFile, g_nWellHistoryFormatVersion) &&
writePod(oFile, g_nEndianMarker) &&
writeCurve(oFile, oHistory.m_vecPressure) &&
writeCurve(oFile, oHistory.m_vecLogLog) &&
writeCurve(oFile, oHistory.m_vecSemiLog) &&
oFile.flush();
oFile.close();
if(!bWritten)
{
QFile::remove(sFilePath);
return setError(pError, "Cannot write complete numerical well history file.");
}
return true;
}
bool nmNumericalResultPersistence::readWellHistory(
const QString& sFilePath,
nmNumericalWellHistoryData& oHistory,
QString* pError)
{
if(pError != NULL)
{
pError->clear();
}
const QFileInfo oInfo(sFilePath);
if(!oInfo.exists() || oInfo.size() < 0 ||
static_cast<quint64>(oInfo.size()) > maximumBinaryPayloadBytes())
{
return setError(pError, "Numerical well history payload is missing or too large.");
}
try
{
QFile oFile(sFilePath);
if(!oFile.open(QIODevice::ReadOnly))
{
return setError(pError, "Cannot open numerical well history file.");
}
char aMagic[8] = { 0 };
quint32 nFormatVersion = 0;
quint32 nEndianMarker = 0;
quint64 nTotalPointCount = 0;
nmNumericalWellHistoryData oLoaded;
const bool bRead = readAll(oFile, aMagic, 8) &&
hasExactMagic(aMagic, g_aWellHistoryMagic, 8) &&
readPod(oFile, nFormatVersion) &&
nFormatVersion == g_nWellHistoryFormatVersion &&
readPod(oFile, nEndianMarker) &&
nEndianMarker == g_nEndianMarker &&
readCurve(oFile, oLoaded.m_vecPressure, nTotalPointCount) &&
readCurve(oFile, oLoaded.m_vecLogLog, nTotalPointCount) &&
readCurve(oFile, oLoaded.m_vecSemiLog, nTotalPointCount) &&
oFile.atEnd();
oFile.close();
if(!bRead)
{
return setError(pError, "Numerical well history file is damaged.");
}
oHistory = oLoaded;
return true;
}
catch(const std::bad_alloc&)
{
return setError(pError, "Not enough memory to load numerical well history.");
}
}
bool nmNumericalResultPersistence::writeGrid(
vtkUnstructuredGrid* pGrid,
const QString& sFilePath,
QString* pError)
{
if(pError != NULL)
{
pError->clear();
}
if(pGrid == NULL || pGrid->GetNumberOfPoints() <= 0 ||
pGrid->GetNumberOfCells() <= 0)
{
return setError(pError, "Numerical grid is empty.");
}
vtkNew<vtkXMLUnstructuredGridWriter> pWriter;
pWriter->SetInputData(pGrid);
pWriter->SetFileName(sFilePath.toLocal8Bit().constData());
pWriter->SetDataModeToBinary();
if(pWriter->Write() == 0 || !QFileInfo(sFilePath).isFile() ||
QFileInfo(sFilePath).size() <= 0)
{
QFile::remove(sFilePath);
return setError(pError, "Cannot write numerical VTU grid.");
}
return true;
}
bool nmNumericalResultPersistence::readGrid(
const QString& sFilePath,
vtkSmartPointer<vtkUnstructuredGrid>& pGrid,
QString* pError)
{
if(pError != NULL)
{
pError->clear();
}
pGrid = NULL;
if(!QFileInfo(sFilePath).isFile())
{
return setError(pError, "Numerical VTU grid is missing.");
}
try
{
vtkNew<vtkXMLUnstructuredGridReader> pReader;
pReader->SetFileName(sFilePath.toLocal8Bit().constData());
pReader->Update();
vtkUnstructuredGrid* pOutput = pReader->GetOutput();
if(pOutput == NULL || pOutput->GetNumberOfPoints() <= 0 ||
pOutput->GetNumberOfCells() <= 0)
{
return setError(pError, "Numerical VTU grid is invalid.");
}
pGrid = pOutput;
return pGrid != NULL;
}
catch(const std::bad_alloc&)
{
pGrid = NULL;
return setError(pError, "Not enough memory to load numerical VTU grid.");
}
}