ResInsight/ApplicationCode/Commands/HoloLensCommands/RicHoloLensSession.cpp
2019-09-06 10:57:22 +02:00

415 lines
16 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2018- Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RicHoloLensSession.h"
#include "RicHoloLensSessionObserver.h"
#include "RiaLogging.h"
#include "RiaPreferences.h"
#include "VdeArrayDataPacket.h"
#include "VdeFileExporter.h"
#include "VdePacketDirectory.h"
#include "VdeVizDataExtractor.h"
#include "cvfAssert.h"
#include "cvfTimer.h"
#include <QDir>
#include <algorithm>
//==================================================================================================
//
//
//
//==================================================================================================
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RicHoloLensSession::RicHoloLensSession()
: m_isSessionValid( false )
, m_lastExtractionMetaDataSequenceNumber( -1 )
, m_sessionObserver( nullptr )
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RicHoloLensSession::~RicHoloLensSession()
{
destroySession();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RicHoloLensSession* RicHoloLensSession::createSession( const QString& serverUrl,
const QString& sessionName,
const QByteArray& sessionPinCode,
RicHoloLensSessionObserver* sessionObserver )
{
RicHoloLensSession* newSession = new RicHoloLensSession;
newSession->m_restClient = new RicHoloLensRestClient( serverUrl, sessionName, newSession );
if ( RiaApplication::instance()->preferences()->holoLensDisableCertificateVerification() )
{
RiaLogging::warning( "HoloLens: Disabling certificate verification for HTTPS connections" );
newSession->m_restClient->dbgDisableCertificateVerification();
}
newSession->m_restClient->createSession( sessionPinCode );
newSession->m_sessionObserver = sessionObserver;
const QString dbgExportFolder = RiaApplication::instance()->preferences()->holoLensExportFolder();
if ( !dbgExportFolder.isEmpty() )
{
newSession->m_dbgFileExportDestinationFolder = dbgExportFolder;
RiaLogging::info( QString( "HoloLens: Debug file export will be written to folder: %1" ).arg( dbgExportFolder ) );
}
return newSession;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RicHoloLensSession* RicHoloLensSession::createDummyFileBackedSession()
{
RicHoloLensSession* newSession = new RicHoloLensSession;
newSession->m_isSessionValid = true;
newSession->m_dbgFileExportDestinationFolder = RiaApplication::instance()->preferences()->holoLensExportFolder();
return newSession;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicHoloLensSession::destroySession()
{
if ( m_restClient )
{
if ( m_isSessionValid )
{
m_restClient->deleteSession();
}
m_restClient->clearResponseHandler();
m_restClient->deleteLater();
m_restClient = nullptr;
}
m_isSessionValid = false;
m_lastExtractionMetaDataSequenceNumber = -1;
m_lastExtractionAllReferencedPacketIdsArr.clear();
m_packetDirectory.clear();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RicHoloLensSession::isSessionValid() const
{
return m_isSessionValid;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicHoloLensSession::updateSessionDataFromView( const RimGridView& activeView )
{
RiaLogging::info( "HoloLens: Updating visualization data" );
// Grab the current max ID as an easy way to detect if new IDs have been added for debugging purposes
const int dbgMaxAssignedIdBeforeExtraction = m_cachingIdFactory.lastAssignedId();
// Note that we pass the caching ID factory on the constructor which will try and detect data payloads that
// are equal, and will then "recycle" the array IDs for these
VdeVizDataExtractor extractor( activeView, &m_cachingIdFactory );
QString modelMetaJsonStr;
std::vector<int> allReferencedPacketIds;
extractor.extractViewContents( &modelMetaJsonStr, &allReferencedPacketIds, &m_packetDirectory );
// Note!
// The packet directory should now contain all the packets that are being actively referenced.
// We now prune out any packets that are no longer being referenced. This means we do no caching of actual packet
// data over time and that we assume that the server will ask for data packets/arrays right after having received updated meta data
m_packetDirectory.pruneUnreferencedPackets( allReferencedPacketIds );
m_lastExtractionMetaDataSequenceNumber++;
m_lastExtractionAllReferencedPacketIdsArr = allReferencedPacketIds;
if ( m_restClient )
{
RiaLogging::info( QString( "HoloLens: Sending updated meta data to sharing server (sequenceNumber=%1)" )
.arg( m_lastExtractionMetaDataSequenceNumber ) );
m_restClient->sendMetaData( m_lastExtractionMetaDataSequenceNumber, modelMetaJsonStr );
}
// Debug export to file
if ( !m_dbgFileExportDestinationFolder.isEmpty() )
{
const QDir outputDir( m_dbgFileExportDestinationFolder );
const QString absOutputFolder = outputDir.absolutePath();
if ( !outputDir.mkpath( "." ) )
{
RiaLogging::error(
QString( "HoloLens: Could not create debug file export folder: %1" ).arg( absOutputFolder ) );
return;
}
// For debugging, write only the new packets to file
// Determine which packets are new by comparing the IDs to the max known ID before extraction
std::vector<int> packetIdsToWrite;
for ( int packetId : allReferencedPacketIds )
{
if ( packetId > dbgMaxAssignedIdBeforeExtraction )
{
packetIdsToWrite.push_back( packetId );
}
}
// This will write all packets seen in this extraction to file
// packetIdsToWrite = allReferencedPacketIds;
RiaLogging::info( QString( "HoloLens: Doing debug export of data (%1 packets) to folder: %2" )
.arg( packetIdsToWrite.size() )
.arg( absOutputFolder ) );
VdeFileExporter fileExporter( absOutputFolder );
if ( !fileExporter.exportToFile( modelMetaJsonStr, m_packetDirectory, packetIdsToWrite ) )
{
RiaLogging::error( "HoloLens: Error exporting debug data to folder" );
}
RiaLogging::info( "HoloLens: Done exporting debug data" );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicHoloLensSession::handleSuccessfulCreateSession()
{
RiaLogging::info( "HoloLens: Session successfully created" );
m_isSessionValid = true;
notifyObserver( RicHoloLensSessionObserver::CreateSessionSucceeded );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicHoloLensSession::handleFailedCreateSession()
{
RiaLogging::error( "HoloLens: Failed to create session" );
m_isSessionValid = false;
notifyObserver( RicHoloLensSessionObserver::CreateSessionFailed );
}
//--------------------------------------------------------------------------------------------------
/// Handle the server response we receive after sending new meta data
//--------------------------------------------------------------------------------------------------
void RicHoloLensSession::handleSuccessfulSendMetaData( int metaDataSequenceNumber,
const QByteArray& jsonServerResponseString )
{
cvf::Timer tim;
RiaLogging::info(
QString( "HoloLens: Processing server response (meta data sequenceNumber=%1)" ).arg( metaDataSequenceNumber ) );
if ( m_lastExtractionMetaDataSequenceNumber != metaDataSequenceNumber )
{
RiaLogging::warning(
QString( "HoloLens: Ignoring server response, the meta data sequenceNumber(%1) has been superseded" )
.arg( metaDataSequenceNumber ) );
return;
}
std::vector<int> arrayIdsToSend;
// cvf::Trace::show("Raw JSON response from server: '%s'", jsonServerResponseString.data());
QByteArray trimmedServerResponseString = jsonServerResponseString.trimmed();
if ( trimmedServerResponseString.size() > 0 )
{
if ( !parseJsonIntegerArray( trimmedServerResponseString, &arrayIdsToSend ) )
{
RiaLogging::error(
"HoloLens: Error parsing array server response with array Ids, no data will be sent to server" );
return;
}
}
else
{
// An empty server response means we should send all array referenced by the last sent meta data
if ( m_lastExtractionAllReferencedPacketIdsArr.size() > 0 )
{
arrayIdsToSend = m_lastExtractionAllReferencedPacketIdsArr;
RiaLogging::info( "HoloLens: Empty server response, sending all arrays referenced by last meta data" );
}
}
if ( arrayIdsToSend.size() == 0 )
{
RiaLogging::info( "HoloLens: Nothing to do, no data requested by server" );
return;
}
RiaLogging::info( QString( "HoloLens: Start sending data to server, %1 data arrays have been requested" )
.arg( arrayIdsToSend.size() ) );
size_t totalBytesSent = 0;
size_t totalNumArraysSent = 0;
const bool sendAsIndividualPackets = false;
// Sending data packets one by one
if ( sendAsIndividualPackets )
{
for ( size_t i = 0; i < arrayIdsToSend.size(); i++ )
{
const int arrayId = arrayIdsToSend[i];
const VdeArrayDataPacket* packet = m_packetDirectory.lookupPacket( arrayId );
if ( !packet )
{
RiaLogging::warning(
QString( "HoloLens: Could not get the requested data from cache, array id: %1 " ).arg( arrayId ) );
continue;
}
QByteArray packetByteArr( packet->fullPacketRawPtr(), static_cast<int>( packet->fullPacketSize() ) );
RiaLogging::info( QString( "HoloLens: sending array id: %1, %2KB (%3 bytes)" )
.arg( arrayId )
.arg( packetByteArr.size() / 1024.0, 0, 'f', 2 )
.arg( packetByteArr.size() ) );
m_restClient->sendBinaryData( packetByteArr, "arrId" + QByteArray::number( arrayId ) );
totalNumArraysSent++;
totalBytesSent += packetByteArr.size();
}
}
// Sending all requested arrays/packets in one combined packet
else
{
QByteArray combinedPacketArr;
if ( !m_packetDirectory.getPacketsAsCombinedBuffer( arrayIdsToSend, &combinedPacketArr ) )
{
RiaLogging::warning( "HoloLens: Error gathering the requested arrays, no data will be sent" );
return;
}
totalNumArraysSent = arrayIdsToSend.size();
totalBytesSent = combinedPacketArr.size();
RiaLogging::info( QString( "HoloLens: Sending data to server (%1 arrays combined), %2KB (%3 bytes)" )
.arg( totalNumArraysSent )
.arg( totalBytesSent / 1024.0, 0, 'f', 2 )
.arg( totalBytesSent ) );
m_restClient->sendBinaryData( combinedPacketArr, "metaSeqNum" + QByteArray::number( metaDataSequenceNumber ) );
}
const double totalMb = totalBytesSent / ( 1024.0 * 1024.0 );
RiaLogging::info( QString( "HoloLens: Finished sending data to server, %1 arrays, total %2MB (%3 bytes) in %4ms" )
.arg( totalNumArraysSent )
.arg( totalMb, 0, 'f', 2 )
.arg( totalBytesSent )
.arg( static_cast<int>( tim.time() * 1000 ) ) );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RicHoloLensSession::parseJsonIntegerArray( const QByteArray& jsonString, std::vector<int>* integerArr )
{
// cvf::Trace::show("jsonString: '%s'", jsonString.data());
const int openBraceIdx = jsonString.indexOf( '[' );
const int closeBraceIdx = jsonString.lastIndexOf( ']' );
if ( openBraceIdx < 0 || closeBraceIdx < 0 )
{
RiaLogging::debug( "Error parsing JSON array, could not find opening or closing braces" );
return false;
}
if ( closeBraceIdx <= openBraceIdx )
{
RiaLogging::debug( "Error parsing JSON array, wrong placement of braces" );
return false;
}
QByteArray arrayContents = jsonString.mid( openBraceIdx + 1, closeBraceIdx - openBraceIdx - 1 ).trimmed();
// cvf::Trace::show("arrayContents: '%s'", arrayContents.data());
QList<QByteArray> stringList = arrayContents.split( ',' );
for ( const QByteArray& entry : stringList )
{
bool convertOk = false;
const int intVal = entry.toInt( &convertOk );
if ( convertOk )
{
integerArr->push_back( intVal );
}
}
return true;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicHoloLensSession::handleError( const QString& errMsg, const QString& url, const QString& serverData )
{
QString fullMsg = "HoloLens communication error: " + errMsg;
if ( !serverData.isEmpty() )
{
fullMsg += "\n serverMsg: " + serverData;
}
fullMsg += "\n url: " + url;
RiaLogging::error( fullMsg );
// It is probably not correct to always consider an error a state change, but for now
notifyObserver( RicHoloLensSessionObserver::GeneralError );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicHoloLensSession::notifyObserver( RicHoloLensSessionObserver::Notification notification )
{
if ( m_sessionObserver )
{
m_sessionObserver->handleSessionNotification( this, notification );
}
}