|
|
|
|
|
#include "nmNumericalResultPersistence.h"
|
|
|
|
|
|
|
|
|
|
|
|
#include "nmDataWellBase.h"
|
|
|
|
|
|
|
|
|
|
|
|
#include <QByteArray>
|
|
|
|
|
|
#include <QCryptographicHash>
|
|
|
|
|
|
#include <QDir>
|
|
|
|
|
|
#include <QFile>
|
|
|
|
|
|
#include <QFileInfo>
|
|
|
|
|
|
#include <QUuid>
|
|
|
|
|
|
#include <QtGlobal>
|
|
|
|
|
|
|
|
|
|
|
|
#include <float.h>
|
|
|
|
|
|
#include <limits.h>
|
|
|
|
|
|
#include <new>
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
|
|
#include <vtkNew.h>
|
|
|
|
|
|
#include <vtkUnstructuredGrid.h>
|
|
|
|
|
|
#include <vtkXMLUnstructuredGridReader.h>
|
|
|
|
|
|
#include <vtkXMLUnstructuredGridWriter.h>
|
|
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
|
|
const char g_aWellHistoryMagic[8] = { 'N', 'M', 'W', 'H', 'I', 'S', '5', 0 };
|
|
|
|
|
|
const char g_aWellGaugeMagic[8] = { 'N', 'M', 'W', 'G', 'A', 'U', '5', 0 };
|
|
|
|
|
|
const quint32 g_nWellHistoryFormatVersion = 2;
|
|
|
|
|
|
const quint32 g_nWellGaugeFormatVersion = 1;
|
|
|
|
|
|
const quint32 g_nEndianMarker = 0x01020304u;
|
|
|
|
|
|
const quint32 g_nMaximumCurveSeries = 64u;
|
|
|
|
|
|
const quint64 g_nMaximumCurvePoints = 50000000ull;
|
|
|
|
|
|
const quint32 g_nMaximumGaugeRecordCount = 10000u;
|
|
|
|
|
|
const quint32 g_nMaximumStringBytes = 16u * 1024u * 1024u;
|
|
|
|
|
|
const qint64 g_nStreamChunkBytes = 4 * 1024 * 1024;
|
|
|
|
|
|
const int g_nDoubleChunkCount = 8192;
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool writeUInt32(QFile& oFile, quint32 nValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
char aBytes[4];
|
|
|
|
|
|
for(int nIndex = 0; nIndex < 4; ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
aBytes[nIndex] = static_cast<char>((nValue >> (nIndex * 8)) & 0xffu);
|
|
|
|
|
|
}
|
|
|
|
|
|
return writeAll(oFile, aBytes, 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool readUInt32(QFile& oFile, quint32& nValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
unsigned char aBytes[4];
|
|
|
|
|
|
if(!readAll(oFile, reinterpret_cast<char*>(aBytes), 4))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
nValue = 0;
|
|
|
|
|
|
for(int nIndex = 0; nIndex < 4; ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
nValue |= static_cast<quint32>(aBytes[nIndex]) << (nIndex * 8);
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool writeUInt64(QFile& oFile, quint64 nValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
char aBytes[8];
|
|
|
|
|
|
for(int nIndex = 0; nIndex < 8; ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
aBytes[nIndex] = static_cast<char>((nValue >> (nIndex * 8)) & 0xffu);
|
|
|
|
|
|
}
|
|
|
|
|
|
return writeAll(oFile, aBytes, 8);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool readUInt64(QFile& oFile, quint64& nValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
unsigned char aBytes[8];
|
|
|
|
|
|
if(!readAll(oFile, reinterpret_cast<char*>(aBytes), 8))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
nValue = 0;
|
|
|
|
|
|
for(int nIndex = 0; nIndex < 8; ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
nValue |= static_cast<quint64>(aBytes[nIndex]) << (nIndex * 8);
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool isFiniteValue(double dValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
#if defined(_MSC_VER)
|
|
|
|
|
|
return _finite(dValue) != 0;
|
|
|
|
|
|
#else
|
|
|
|
|
|
return qIsFinite(dValue);
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void appendDouble(QByteArray& baBytes, double dValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
quint64 nBits = 0;
|
|
|
|
|
|
memcpy(&nBits, &dValue, sizeof(nBits));
|
|
|
|
|
|
for(int nIndex = 0; nIndex < 8; ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
baBytes.append(static_cast<char>((nBits >> (nIndex * 8)) & 0xffu));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool readDouble(const char* pBytes, double& dValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
quint64 nBits = 0;
|
|
|
|
|
|
for(int nIndex = 0; nIndex < 8; ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
nBits |= static_cast<quint64>(
|
|
|
|
|
|
static_cast<unsigned char>(pBytes[nIndex])) << (nIndex * 8);
|
|
|
|
|
|
}
|
|
|
|
|
|
memcpy(&dValue, &nBits, sizeof(dValue));
|
|
|
|
|
|
return isFiniteValue(dValue);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool isSha1(const QString& sSha1)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(sSha1.size() != 40)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
for(int nIndex = 0; nIndex < sSha1.size(); ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
const QChar oChar = sSha1[nIndex];
|
|
|
|
|
|
if(!((oChar >= '0' && oChar <= '9') ||
|
|
|
|
|
|
(oChar >= 'a' && oChar <= 'f')))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool writeString(QFile& oFile, const QString& sValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
const QByteArray baValue = sValue.toUtf8();
|
|
|
|
|
|
return static_cast<quint64>(baValue.size()) <= g_nMaximumStringBytes &&
|
|
|
|
|
|
writeUInt32(oFile, static_cast<quint32>(baValue.size())) &&
|
|
|
|
|
|
(baValue.isEmpty() || writeAll(oFile, baValue.constData(),
|
|
|
|
|
|
static_cast<quint64>(baValue.size())));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool readString(QFile& oFile, QString& sValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
quint32 nBytes = 0;
|
|
|
|
|
|
if(!readUInt32(oFile, nBytes) || nBytes > g_nMaximumStringBytes ||
|
|
|
|
|
|
static_cast<quint64>(nBytes) >
|
|
|
|
|
|
static_cast<quint64>(oFile.bytesAvailable()) ||
|
|
|
|
|
|
nBytes > static_cast<quint32>(INT_MAX))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
QByteArray baValue;
|
|
|
|
|
|
baValue.resize(static_cast<int>(nBytes));
|
|
|
|
|
|
if(nBytes > 0 && !readAll(oFile, baValue.data(), nBytes))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
sValue = QString::fromUtf8(baValue.constData(), baValue.size());
|
|
|
|
|
|
return sValue.toUtf8() == baValue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool writeDoubleVector(QFile& oFile, const QVector<double>& vecValues)
|
|
|
|
|
|
{
|
|
|
|
|
|
QByteArray baChunk;
|
|
|
|
|
|
baChunk.reserve(g_nDoubleChunkCount * static_cast<int>(sizeof(double)));
|
|
|
|
|
|
for(int nStart = 0; nStart < vecValues.size();
|
|
|
|
|
|
nStart += g_nDoubleChunkCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
baChunk.clear();
|
|
|
|
|
|
const int nEnd = qMin(nStart + g_nDoubleChunkCount, vecValues.size());
|
|
|
|
|
|
for(int nIndex = nStart; nIndex < nEnd; ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!isFiniteValue(vecValues[nIndex]))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
appendDouble(baChunk, vecValues[nIndex]);
|
|
|
|
|
|
}
|
|
|
|
|
|
if(!baChunk.isEmpty() && !writeAll(oFile, baChunk.constData(),
|
|
|
|
|
|
static_cast<quint64>(baChunk.size())))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool readDoubleVector(QFile& oFile, QVector<double>& vecValues,
|
|
|
|
|
|
quint64 nPointCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(nPointCount > static_cast<quint64>(INT_MAX))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
vecValues.resize(static_cast<int>(nPointCount));
|
|
|
|
|
|
QByteArray baChunk;
|
|
|
|
|
|
for(quint64 nStart = 0; nStart < nPointCount;
|
|
|
|
|
|
nStart += static_cast<quint64>(g_nDoubleChunkCount))
|
|
|
|
|
|
{
|
|
|
|
|
|
const int nCount = static_cast<int>(qMin(
|
|
|
|
|
|
static_cast<quint64>(g_nDoubleChunkCount),
|
|
|
|
|
|
nPointCount - nStart));
|
|
|
|
|
|
baChunk.resize(nCount * static_cast<int>(sizeof(double)));
|
|
|
|
|
|
if(!readAll(oFile, baChunk.data(),
|
|
|
|
|
|
static_cast<quint64>(baChunk.size())))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
for(int nOffset = 0; nOffset < nCount; ++nOffset)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!readDouble(baChunk.constData() +
|
|
|
|
|
|
nOffset * static_cast<int>(sizeof(double)),
|
|
|
|
|
|
vecValues[static_cast<int>(nStart) + nOffset]))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(static_cast<quint32>(vecCurve.size()) > g_nMaximumCurveSeries)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
const quint32 nSeriesCount = static_cast<quint32>(vecCurve.size());
|
|
|
|
|
|
if(!writeUInt32(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) ||
|
|
|
|
|
|
nPointCount > g_nMaximumCurvePoints ||
|
|
|
|
|
|
!writeUInt64(oFile, nPointCount) ||
|
|
|
|
|
|
(nBytes > 0 && !writeDoubleVector(oFile, vecValues)))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool readCurve(QFile& oFile,
|
|
|
|
|
|
QVector<QVector<double> >& vecCurve,
|
|
|
|
|
|
quint64& nTotalPointCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
quint32 nSeriesCount = 0;
|
|
|
|
|
|
if(!readUInt32(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(!readUInt64(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;
|
|
|
|
|
|
if(nBytes > 0 && !readDoubleVector(oFile,
|
|
|
|
|
|
vecLoaded[static_cast<int>(nSeries)], nPointCount))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
vecCurve.swap(vecLoaded);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool writePointVector(QFile& oFile, const QVector<QPointF>& vecPoints)
|
|
|
|
|
|
{
|
|
|
|
|
|
const quint64 nPointCount = static_cast<quint64>(vecPoints.size());
|
|
|
|
|
|
if(nPointCount > g_nMaximumCurvePoints ||
|
|
|
|
|
|
!writeUInt64(oFile, nPointCount))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
QByteArray baChunk;
|
|
|
|
|
|
baChunk.reserve(g_nDoubleChunkCount * 2 * static_cast<int>(sizeof(double)));
|
|
|
|
|
|
for(int nStart = 0; nStart < vecPoints.size(); nStart += g_nDoubleChunkCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
baChunk.clear();
|
|
|
|
|
|
const int nEnd = qMin(nStart + g_nDoubleChunkCount, vecPoints.size());
|
|
|
|
|
|
for(int nIndex = nStart; nIndex < nEnd; ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!isFiniteValue(vecPoints[nIndex].x()) ||
|
|
|
|
|
|
!isFiniteValue(vecPoints[nIndex].y()))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
appendDouble(baChunk, vecPoints[nIndex].x());
|
|
|
|
|
|
appendDouble(baChunk, vecPoints[nIndex].y());
|
|
|
|
|
|
}
|
|
|
|
|
|
if(!baChunk.isEmpty() && !writeAll(oFile, baChunk.constData(),
|
|
|
|
|
|
static_cast<quint64>(baChunk.size())))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool readPointVector(QFile& oFile, QVector<QPointF>& vecPoints,
|
|
|
|
|
|
quint64& nTotalPointCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
quint64 nPointCount = 0;
|
|
|
|
|
|
quint64 nDoubleCount = 0;
|
|
|
|
|
|
quint64 nBytes = 0;
|
|
|
|
|
|
if(!readUInt64(oFile, nPointCount) ||
|
|
|
|
|
|
nPointCount > g_nMaximumCurvePoints ||
|
|
|
|
|
|
nTotalPointCount > g_nMaximumCurvePoints - nPointCount ||
|
|
|
|
|
|
!safeMultiply(nPointCount, 2, nDoubleCount) ||
|
|
|
|
|
|
!safeMultiply(nDoubleCount, sizeof(double), nBytes) ||
|
|
|
|
|
|
nBytes > static_cast<quint64>(oFile.bytesAvailable()) ||
|
|
|
|
|
|
nPointCount > static_cast<quint64>(INT_MAX))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
nTotalPointCount += nPointCount;
|
|
|
|
|
|
vecPoints.resize(static_cast<int>(nPointCount));
|
|
|
|
|
|
QByteArray baChunk;
|
|
|
|
|
|
for(quint64 nStart = 0; nStart < nPointCount;
|
|
|
|
|
|
nStart += static_cast<quint64>(g_nDoubleChunkCount))
|
|
|
|
|
|
{
|
|
|
|
|
|
const int nCount = static_cast<int>(qMin(
|
|
|
|
|
|
static_cast<quint64>(g_nDoubleChunkCount),
|
|
|
|
|
|
nPointCount - nStart));
|
|
|
|
|
|
baChunk.resize(nCount * 2 * static_cast<int>(sizeof(double)));
|
|
|
|
|
|
if(!readAll(oFile, baChunk.data(),
|
|
|
|
|
|
static_cast<quint64>(baChunk.size())))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
for(int nOffset = 0; nOffset < nCount; ++nOffset)
|
|
|
|
|
|
{
|
|
|
|
|
|
double dX = 0.0;
|
|
|
|
|
|
double dY = 0.0;
|
|
|
|
|
|
const char* pPoint = baChunk.constData() +
|
|
|
|
|
|
nOffset * 2 * static_cast<int>(sizeof(double));
|
|
|
|
|
|
if(!readDouble(pPoint, dX) ||
|
|
|
|
|
|
!readDouble(pPoint + sizeof(double), dY))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
vecPoints[static_cast<int>(nStart) + nOffset] = QPointF(dX, dY);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool isGaugeStatusValid(NM_GAUGE_RECORD_STATUS eStatus)
|
|
|
|
|
|
{
|
|
|
|
|
|
return eStatus >= NM_GaugeRecord_Usable &&
|
|
|
|
|
|
eStatus <= NM_GaugeRecord_InvalidIdentity;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool isCanonicalInstanceId(const QString& sInstanceId)
|
|
|
|
|
|
{
|
|
|
|
|
|
const QUuid oUuid(sInstanceId);
|
|
|
|
|
|
return !oUuid.isNull() &&
|
|
|
|
|
|
oUuid.toString().remove('{').remove('}') == sInstanceId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool validateGaugeData(const nmNumericalWellGaugeData& oGaugeData)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!isCanonicalInstanceId(oGaugeData.m_sWellInstanceId) ||
|
|
|
|
|
|
oGaugeData.m_vecPressureRecords.size() >
|
|
|
|
|
|
static_cast<int>(g_nMaximumGaugeRecordCount) ||
|
|
|
|
|
|
oGaugeData.m_vecFlowRecords.size() >
|
|
|
|
|
|
static_cast<int>(g_nMaximumGaugeRecordCount))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
quint64 nTotalPointCount = 0;
|
|
|
|
|
|
for(int nIndex = 0;
|
|
|
|
|
|
nIndex < oGaugeData.m_vecPressureRecords.size(); ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
const nmPressureGaugeRecord& oRecord =
|
|
|
|
|
|
oGaugeData.m_vecPressureRecords[nIndex];
|
|
|
|
|
|
const QByteArray baCode = oRecord.sGaugeCode.toUtf8();
|
|
|
|
|
|
const QByteArray baName = oRecord.sGaugeName.toUtf8();
|
|
|
|
|
|
const QByteArray baTime = oRecord.sGaugeTime.toUtf8();
|
|
|
|
|
|
if(!isGaugeStatusValid(oRecord.eStatus) ||
|
|
|
|
|
|
static_cast<quint64>(baCode.size()) > g_nMaximumStringBytes ||
|
|
|
|
|
|
static_cast<quint64>(baName.size()) > g_nMaximumStringBytes ||
|
|
|
|
|
|
static_cast<quint64>(baTime.size()) > g_nMaximumStringBytes ||
|
|
|
|
|
|
static_cast<quint64>(oRecord.vecPressurePoints.size()) >
|
|
|
|
|
|
g_nMaximumCurvePoints - nTotalPointCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
nTotalPointCount += static_cast<quint64>(
|
|
|
|
|
|
oRecord.vecPressurePoints.size());
|
|
|
|
|
|
for(int nPoint = 0; nPoint < oRecord.vecPressurePoints.size(); ++nPoint)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!isFiniteValue(oRecord.vecPressurePoints[nPoint].x()) ||
|
|
|
|
|
|
!isFiniteValue(oRecord.vecPressurePoints[nPoint].y()))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
for(int nIndex = 0; nIndex < oGaugeData.m_vecFlowRecords.size(); ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
const nmFlowGaugeRecord& oRecord = oGaugeData.m_vecFlowRecords[nIndex];
|
|
|
|
|
|
const QByteArray baCode = oRecord.sGaugeCode.toUtf8();
|
|
|
|
|
|
const QByteArray baName = oRecord.sGaugeName.toUtf8();
|
|
|
|
|
|
const QByteArray baTime = oRecord.sGaugeTime.toUtf8();
|
|
|
|
|
|
const QVector<QPointF> arrPoints[] = {
|
|
|
|
|
|
oRecord.vecOilPoints, oRecord.vecGasPoints, oRecord.vecWaterPoints
|
|
|
|
|
|
};
|
|
|
|
|
|
if(!isGaugeStatusValid(oRecord.eStatus) ||
|
|
|
|
|
|
static_cast<quint64>(baCode.size()) > g_nMaximumStringBytes ||
|
|
|
|
|
|
static_cast<quint64>(baName.size()) > g_nMaximumStringBytes ||
|
|
|
|
|
|
static_cast<quint64>(baTime.size()) > g_nMaximumStringBytes)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
for(int nPhase = 0; nPhase < 3; ++nPhase)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(static_cast<quint64>(arrPoints[nPhase].size()) >
|
|
|
|
|
|
g_nMaximumCurvePoints - nTotalPointCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
nTotalPointCount += static_cast<quint64>(arrPoints[nPhase].size());
|
|
|
|
|
|
for(int nPoint = 0; nPoint < arrPoints[nPhase].size(); ++nPoint)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!isFiniteValue(arrPoints[nPhase][nPoint].x()) ||
|
|
|
|
|
|
!isFiniteValue(arrPoints[nPhase][nPoint].y()))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
const int nSegmentCount =
|
|
|
|
|
|
nmDataWellBase::getRawFlowRecordSegmentCount(oRecord);
|
|
|
|
|
|
if(oRecord.nIndexF < 0 ||
|
|
|
|
|
|
(oRecord.nIndexF > 0 && oRecord.nIndexF > nSegmentCount))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
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()
|
|
|
|
|
|
{
|
|
|
|
|
|
// v5 保存完整 Gauge 副本及输入签名,不再依赖框架当前记录。
|
|
|
|
|
|
return 5;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
if(!isSha1(oHistory.m_sGaugeInputSha1))
|
|
|
|
|
|
{
|
|
|
|
|
|
return setError(pError, "Numerical well history Gauge signature is invalid.");
|
|
|
|
|
|
}
|
|
|
|
|
|
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) &&
|
|
|
|
|
|
writeUInt32(oFile, g_nWellHistoryFormatVersion) &&
|
|
|
|
|
|
writeUInt32(oFile, g_nEndianMarker) &&
|
|
|
|
|
|
writeString(oFile, oHistory.m_sGaugeInputSha1) &&
|
|
|
|
|
|
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) &&
|
|
|
|
|
|
readUInt32(oFile, nFormatVersion) &&
|
|
|
|
|
|
nFormatVersion == g_nWellHistoryFormatVersion &&
|
|
|
|
|
|
readUInt32(oFile, nEndianMarker) &&
|
|
|
|
|
|
nEndianMarker == g_nEndianMarker &&
|
|
|
|
|
|
readString(oFile, oLoaded.m_sGaugeInputSha1) &&
|
|
|
|
|
|
isSha1(oLoaded.m_sGaugeInputSha1) &&
|
|
|
|
|
|
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::writeWellGaugeData(
|
|
|
|
|
|
const QString& sFilePath,
|
|
|
|
|
|
const nmNumericalWellGaugeData& oGaugeData,
|
|
|
|
|
|
QString* pError)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(pError != NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
pError->clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
if(!validateGaugeData(oGaugeData))
|
|
|
|
|
|
{
|
|
|
|
|
|
return setError(pError, "Numerical well Gauge data are invalid.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
QFile oFile(sFilePath);
|
|
|
|
|
|
if(!oFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
|
|
|
|
|
|
{
|
|
|
|
|
|
return setError(pError, "Cannot create numerical well Gauge file.");
|
|
|
|
|
|
}
|
|
|
|
|
|
bool bWritten = writeAll(oFile, g_aWellGaugeMagic, 8) &&
|
|
|
|
|
|
writeUInt32(oFile, g_nWellGaugeFormatVersion) &&
|
|
|
|
|
|
writeUInt32(oFile, g_nEndianMarker) &&
|
|
|
|
|
|
writeString(oFile, oGaugeData.m_sWellInstanceId) &&
|
|
|
|
|
|
writeUInt32(oFile, static_cast<quint32>(
|
|
|
|
|
|
oGaugeData.m_vecPressureRecords.size()));
|
|
|
|
|
|
for(int nIndex = 0;
|
|
|
|
|
|
bWritten && nIndex < oGaugeData.m_vecPressureRecords.size();
|
|
|
|
|
|
++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
const nmPressureGaugeRecord& oRecord =
|
|
|
|
|
|
oGaugeData.m_vecPressureRecords[nIndex];
|
|
|
|
|
|
bWritten = writeString(oFile, oRecord.sGaugeCode) &&
|
|
|
|
|
|
writeString(oFile, oRecord.sGaugeName) &&
|
|
|
|
|
|
writeString(oFile, oRecord.sGaugeTime) &&
|
|
|
|
|
|
writeUInt32(oFile, static_cast<quint32>(oRecord.eStatus)) &&
|
|
|
|
|
|
writePointVector(oFile, oRecord.vecPressurePoints);
|
|
|
|
|
|
}
|
|
|
|
|
|
bWritten = bWritten && writeUInt32(oFile, static_cast<quint32>(
|
|
|
|
|
|
oGaugeData.m_vecFlowRecords.size()));
|
|
|
|
|
|
for(int nIndex = 0;
|
|
|
|
|
|
bWritten && nIndex < oGaugeData.m_vecFlowRecords.size(); ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
const nmFlowGaugeRecord& oRecord =
|
|
|
|
|
|
oGaugeData.m_vecFlowRecords[nIndex];
|
|
|
|
|
|
bWritten = writeString(oFile, oRecord.sGaugeCode) &&
|
|
|
|
|
|
writeString(oFile, oRecord.sGaugeName) &&
|
|
|
|
|
|
writeString(oFile, oRecord.sGaugeTime) &&
|
|
|
|
|
|
writeUInt32(oFile, oRecord.bMultiPhase ? 1u : 0u) &&
|
|
|
|
|
|
writeUInt32(oFile, static_cast<quint32>(oRecord.eStatus)) &&
|
|
|
|
|
|
writePointVector(oFile, oRecord.vecOilPoints) &&
|
|
|
|
|
|
writePointVector(oFile, oRecord.vecGasPoints) &&
|
|
|
|
|
|
writePointVector(oFile, oRecord.vecWaterPoints) &&
|
|
|
|
|
|
writeUInt32(oFile, static_cast<quint32>(
|
|
|
|
|
|
static_cast<qint32>(oRecord.nIndexF)));
|
|
|
|
|
|
}
|
|
|
|
|
|
bWritten = bWritten && oFile.flush() && oFile.size() > 0 &&
|
|
|
|
|
|
static_cast<quint64>(oFile.size()) <=
|
|
|
|
|
|
maximumBinaryPayloadBytes();
|
|
|
|
|
|
oFile.close();
|
|
|
|
|
|
if(!bWritten)
|
|
|
|
|
|
{
|
|
|
|
|
|
QFile::remove(sFilePath);
|
|
|
|
|
|
return setError(pError,
|
|
|
|
|
|
"Cannot write complete numerical well Gauge file.");
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch(const std::bad_alloc&)
|
|
|
|
|
|
{
|
|
|
|
|
|
QFile::remove(sFilePath);
|
|
|
|
|
|
return setError(pError,
|
|
|
|
|
|
"Not enough memory to save numerical well Gauge data.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool nmNumericalResultPersistence::readWellGaugeData(
|
|
|
|
|
|
const QString& sFilePath,
|
|
|
|
|
|
nmNumericalWellGaugeData& oGaugeData,
|
|
|
|
|
|
QString* pError)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(pError != NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
pError->clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
const QFileInfo oInfo(sFilePath);
|
|
|
|
|
|
if(!oInfo.isFile() || oInfo.size() <= 0 ||
|
|
|
|
|
|
static_cast<quint64>(oInfo.size()) > maximumBinaryPayloadBytes())
|
|
|
|
|
|
{
|
|
|
|
|
|
return setError(pError,
|
|
|
|
|
|
"Numerical well Gauge payload is missing or too large.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
QFile oFile(sFilePath);
|
|
|
|
|
|
if(!oFile.open(QIODevice::ReadOnly))
|
|
|
|
|
|
{
|
|
|
|
|
|
return setError(pError, "Cannot open numerical well Gauge file.");
|
|
|
|
|
|
}
|
|
|
|
|
|
char aMagic[8] = { 0 };
|
|
|
|
|
|
quint32 nFormatVersion = 0;
|
|
|
|
|
|
quint32 nEndianMarker = 0;
|
|
|
|
|
|
quint32 nPressureCount = 0;
|
|
|
|
|
|
quint32 nFlowCount = 0;
|
|
|
|
|
|
quint64 nTotalPointCount = 0;
|
|
|
|
|
|
nmNumericalWellGaugeData oLoaded;
|
|
|
|
|
|
bool bRead = readAll(oFile, aMagic, 8) &&
|
|
|
|
|
|
hasExactMagic(aMagic, g_aWellGaugeMagic, 8) &&
|
|
|
|
|
|
readUInt32(oFile, nFormatVersion) &&
|
|
|
|
|
|
nFormatVersion == g_nWellGaugeFormatVersion &&
|
|
|
|
|
|
readUInt32(oFile, nEndianMarker) &&
|
|
|
|
|
|
nEndianMarker == g_nEndianMarker &&
|
|
|
|
|
|
readString(oFile, oLoaded.m_sWellInstanceId) &&
|
|
|
|
|
|
isCanonicalInstanceId(oLoaded.m_sWellInstanceId) &&
|
|
|
|
|
|
readUInt32(oFile, nPressureCount) &&
|
|
|
|
|
|
nPressureCount <= g_nMaximumGaugeRecordCount;
|
|
|
|
|
|
oLoaded.m_vecPressureRecords.reserve(
|
|
|
|
|
|
bRead ? static_cast<int>(nPressureCount) : 0);
|
|
|
|
|
|
for(quint32 nIndex = 0; bRead && nIndex < nPressureCount; ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
nmPressureGaugeRecord oRecord;
|
|
|
|
|
|
quint32 nStatus = 0;
|
|
|
|
|
|
bRead = readString(oFile, oRecord.sGaugeCode) &&
|
|
|
|
|
|
readString(oFile, oRecord.sGaugeName) &&
|
|
|
|
|
|
readString(oFile, oRecord.sGaugeTime) &&
|
|
|
|
|
|
readUInt32(oFile, nStatus) &&
|
|
|
|
|
|
nStatus <= static_cast<quint32>(
|
|
|
|
|
|
NM_GaugeRecord_InvalidIdentity) &&
|
|
|
|
|
|
readPointVector(oFile, oRecord.vecPressurePoints,
|
|
|
|
|
|
nTotalPointCount);
|
|
|
|
|
|
if(bRead)
|
|
|
|
|
|
{
|
|
|
|
|
|
oRecord.eStatus = static_cast<NM_GAUGE_RECORD_STATUS>(nStatus);
|
|
|
|
|
|
oLoaded.m_vecPressureRecords.append(oRecord);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
bRead = bRead && readUInt32(oFile, nFlowCount) &&
|
|
|
|
|
|
nFlowCount <= g_nMaximumGaugeRecordCount;
|
|
|
|
|
|
oLoaded.m_vecFlowRecords.reserve(
|
|
|
|
|
|
bRead ? static_cast<int>(nFlowCount) : 0);
|
|
|
|
|
|
for(quint32 nIndex = 0; bRead && nIndex < nFlowCount; ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
nmFlowGaugeRecord oRecord;
|
|
|
|
|
|
quint32 nMultiPhase = 0;
|
|
|
|
|
|
quint32 nStatus = 0;
|
|
|
|
|
|
quint32 nIndexF = 0;
|
|
|
|
|
|
bRead = readString(oFile, oRecord.sGaugeCode) &&
|
|
|
|
|
|
readString(oFile, oRecord.sGaugeName) &&
|
|
|
|
|
|
readString(oFile, oRecord.sGaugeTime) &&
|
|
|
|
|
|
readUInt32(oFile, nMultiPhase) && nMultiPhase <= 1u &&
|
|
|
|
|
|
readUInt32(oFile, nStatus) &&
|
|
|
|
|
|
nStatus <= static_cast<quint32>(
|
|
|
|
|
|
NM_GaugeRecord_InvalidIdentity) &&
|
|
|
|
|
|
readPointVector(oFile, oRecord.vecOilPoints,
|
|
|
|
|
|
nTotalPointCount) &&
|
|
|
|
|
|
readPointVector(oFile, oRecord.vecGasPoints,
|
|
|
|
|
|
nTotalPointCount) &&
|
|
|
|
|
|
readPointVector(oFile, oRecord.vecWaterPoints,
|
|
|
|
|
|
nTotalPointCount) &&
|
|
|
|
|
|
readUInt32(oFile, nIndexF) && nIndexF <= INT_MAX;
|
|
|
|
|
|
if(bRead)
|
|
|
|
|
|
{
|
|
|
|
|
|
oRecord.bMultiPhase = nMultiPhase != 0;
|
|
|
|
|
|
oRecord.eStatus = static_cast<NM_GAUGE_RECORD_STATUS>(nStatus);
|
|
|
|
|
|
oRecord.nIndexF = static_cast<int>(nIndexF);
|
|
|
|
|
|
oLoaded.m_vecFlowRecords.append(oRecord);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
bRead = bRead && oFile.atEnd() && validateGaugeData(oLoaded);
|
|
|
|
|
|
oFile.close();
|
|
|
|
|
|
if(!bRead)
|
|
|
|
|
|
{
|
|
|
|
|
|
return setError(pError, "Numerical well Gauge file is damaged.");
|
|
|
|
|
|
}
|
|
|
|
|
|
oGaugeData = oLoaded;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch(const std::bad_alloc&)
|
|
|
|
|
|
{
|
|
|
|
|
|
return setError(pError,
|
|
|
|
|
|
"Not enough memory to load numerical well Gauge data.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|