///////////////////////////////////////////////////////////////////////////////// // // 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 // for more details. // ///////////////////////////////////////////////////////////////////////////////// #include "RicHoloLensSession.h" #include "RicHoloLensSessionObserver.h" #include "RiaLogging.h" #include "RiaPreferences.h" #include "VdeVizDataExtractor.h" #include "VdeFileExporter.h" #include "VdePacketDirectory.h" #include "VdeArrayDataPacket.h" #include "cvfAssert.h" #include "cvfTimer.h" #include #include //================================================================================================== // // // //================================================================================================== //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- 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 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 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 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(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(tim.time()*1000))); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RicHoloLensSession::parseJsonIntegerArray(const QByteArray& jsonString, std::vector* 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 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); } }