diff --git a/ApplicationExeCode/CMakeLists.txt b/ApplicationExeCode/CMakeLists.txt
index 8b9f4a5955..28ba766559 100644
--- a/ApplicationExeCode/CMakeLists.txt
+++ b/ApplicationExeCode/CMakeLists.txt
@@ -302,12 +302,7 @@ set(UNITY_EXCLUDE_FILES
# forever is used as variable name, and this symbol is defined by Qt and
# used in precompiled headers
${ResInsight_SOURCE_DIR}/ThirdParty/gtest/gtest-all.cc
- qrc_cafAnimControl.cpp
- qrc_ResInsight.cpp
- qrc_cafCommandFeatures.cpp
- # Exclude files including opm-common
- ProjectDataModel/RimVfpTableExtractor.cpp
- ProjectDataModel/RimVfpPlot.cpp
+ qrc_cafAnimControl.cpp qrc_ResInsight.cpp qrc_cafCommandFeatures.cpp
)
if(RESINSIGHT_ENABLE_UNITY_BUILD)
diff --git a/ApplicationExeCode/Resources/ResInsight.qrc b/ApplicationExeCode/Resources/ResInsight.qrc
index 7c2e9a61bd..cb62facd2e 100644
--- a/ApplicationExeCode/Resources/ResInsight.qrc
+++ b/ApplicationExeCode/Resources/ResInsight.qrc
@@ -287,6 +287,7 @@
regression-curve.svg
padlock.svg
warning.svg
+ cloud-and-server.svg
fs_CellFace.glsl
diff --git a/ApplicationExeCode/Resources/cloud-and-server.svg b/ApplicationExeCode/Resources/cloud-and-server.svg
new file mode 100644
index 0000000000..67c6a4f24a
--- /dev/null
+++ b/ApplicationExeCode/Resources/cloud-and-server.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/ApplicationLibCode/Application/CMakeLists_files.cmake b/ApplicationLibCode/Application/CMakeLists_files.cmake
index 55bb5fa2e1..8dc2c7f0aa 100644
--- a/ApplicationLibCode/Application/CMakeLists_files.cmake
+++ b/ApplicationLibCode/Application/CMakeLists_files.cmake
@@ -11,6 +11,7 @@ set(SOURCE_GROUP_HEADER_FILES
${CMAKE_CURRENT_LIST_DIR}/RiaPreferencesGrid.h
${CMAKE_CURRENT_LIST_DIR}/RiaPreferencesSystem.h
${CMAKE_CURRENT_LIST_DIR}/RiaPreferencesOsdu.h
+ ${CMAKE_CURRENT_LIST_DIR}/RiaPreferencesSumo.h
${CMAKE_CURRENT_LIST_DIR}/RiaPorosityModel.h
${CMAKE_CURRENT_LIST_DIR}/RiaSummaryCurveDefinition.h
${CMAKE_CURRENT_LIST_DIR}/RiaCurveSetDefinition.h
@@ -54,6 +55,7 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RiaPreferencesGrid.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaPreferencesSystem.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaPreferencesOsdu.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/RiaPreferencesSumo.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaPorosityModel.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaSummaryCurveDefinition.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaCurveSetDefinition.cpp
diff --git a/ApplicationLibCode/Application/RiaApplication.cpp b/ApplicationLibCode/Application/RiaApplication.cpp
index aeae3a2659..70a26aaf0a 100644
--- a/ApplicationLibCode/Application/RiaApplication.cpp
+++ b/ApplicationLibCode/Application/RiaApplication.cpp
@@ -17,7 +17,10 @@
/////////////////////////////////////////////////////////////////////////////////
#include "RiaApplication.h"
+#include "Cloud/RiaSumoConnector.h"
+#include "Cloud/RiaSumoDefines.h"
#include "OsduImportCommands/RiaOsduConnector.h"
+
#include "RiaArgumentParser.h"
#include "RiaBaseDefs.h"
#include "RiaFilePathTools.h"
@@ -26,6 +29,7 @@
#include "RiaImportEclipseCaseTools.h"
#include "RiaLogging.h"
#include "RiaPreferences.h"
+#include "RiaPreferencesSumo.h"
#include "RiaPreferencesSystem.h"
#include "RiaProjectModifier.h"
#include "RiaSocketServer.h"
@@ -1710,3 +1714,25 @@ RiaOsduConnector* RiaApplication::makeOsduConnector()
m_osduConnector = new RiaOsduConnector( RiuMainWindow::instance(), server, dataPartitionId, authority, scopes, clientId );
return m_osduConnector;
}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+RiaSumoConnector* RiaApplication::makeSumoConnector()
+{
+ if ( !m_sumoConnector )
+ {
+ auto sumoPrefs = preferences()->sumoPreferences();
+ const QString server = sumoPrefs->server();
+ const QString authority = sumoPrefs->authority();
+ const QString scopes = sumoPrefs->scopes();
+ const QString clientId = sumoPrefs->clientId();
+
+ m_sumoConnector = new RiaSumoConnector( RiuMainWindow::instance(), server, authority, scopes, clientId );
+
+ m_sumoConnector->setTokenDataFilePath( RiaSumoDefines::tokenPath() );
+ m_sumoConnector->importTokenFromFile();
+ }
+
+ return m_sumoConnector;
+}
diff --git a/ApplicationLibCode/Application/RiaApplication.h b/ApplicationLibCode/Application/RiaApplication.h
index b98be5cd3e..c9e45cb537 100644
--- a/ApplicationLibCode/Application/RiaApplication.h
+++ b/ApplicationLibCode/Application/RiaApplication.h
@@ -69,6 +69,7 @@ class RiuPlotMainWindow;
class RiuRecentFileActionProvider;
class RiaArgumentParser;
class RiaOsduConnector;
+class RiaSumoConnector;
namespace caf
{
@@ -202,6 +203,7 @@ public:
virtual void showFormattedTextInMessageBoxOrConsole( const QString& errMsg ) = 0;
RiaOsduConnector* makeOsduConnector();
+ RiaSumoConnector* makeSumoConnector();
protected:
// Protected implementation specific overrides
@@ -259,4 +261,5 @@ protected:
private:
static RiaApplication* s_riaApplication;
QPointer m_osduConnector;
+ QPointer m_sumoConnector;
};
diff --git a/ApplicationLibCode/Application/RiaPreferences.cpp b/ApplicationLibCode/Application/RiaPreferences.cpp
index ba163931a0..131551e6f1 100644
--- a/ApplicationLibCode/Application/RiaPreferences.cpp
+++ b/ApplicationLibCode/Application/RiaPreferences.cpp
@@ -27,6 +27,7 @@
#include "RiaPreferencesGeoMech.h"
#include "RiaPreferencesGrid.h"
#include "RiaPreferencesSummary.h"
+#include "RiaPreferencesSumo.h"
#include "RiaPreferencesSystem.h"
#include "RiaQDateTimeTools.h"
#include "RiaValidRegExpValidator.h"
@@ -146,9 +147,6 @@ RiaPreferences::RiaPreferences()
CAF_PDM_InitField( &m_storeBackupOfProjectFile, "storeBackupOfProjectFile", true, "Store Backup of Project Files" );
- CAF_PDM_InitField( &ssihubAddress, "ssihubAddress", QString( "http://" ), "SSIHUB Address" );
- ssihubAddress.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::TOP );
-
CAF_PDM_InitFieldNoDefault( &m_defaultMeshModeType, "defaultMeshModeType", "Show Grid Lines" );
CAF_PDM_InitField( &defaultGridLineColors, "defaultGridLineColors", RiaColorTables::defaultGridLineColor(), "Mesh Color" );
CAF_PDM_InitField( &defaultFaultGridLineColors,
@@ -273,6 +271,9 @@ RiaPreferences::RiaPreferences()
CAF_PDM_InitFieldNoDefault( &m_osduPreferences, "osduPreferences", "osduPreferences" );
m_osduPreferences = new RiaPreferencesOsdu;
+
+ CAF_PDM_InitFieldNoDefault( &m_sumoPreferences, "sumoPreferences", "sumoPreferences" );
+ m_sumoPreferences = new RiaPreferencesSumo;
}
//--------------------------------------------------------------------------------------------------
@@ -370,8 +371,15 @@ void RiaPreferences::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering&
viewsGroup->add( &m_showInfoBox );
viewsGroup->add( &m_showGridBox, { .newRow = false, .totalColumnSpan = 1 } );
+ caf::PdmUiGroup* loggingGroup = uiOrdering.addNewGroup( "Logging and Backup" );
+ loggingGroup->add( &m_storeBackupOfProjectFile );
+ loggingGroup->add( &m_loggerFilename );
+ loggingGroup->add( &m_loggerFlushInterval );
+ loggingGroup->add( &m_loggerTrapSignalAndFlush );
+ m_loggerTrapSignalAndFlush.uiCapability()->setUiReadOnly( !m_loggerFilename().first );
+ m_loggerFlushInterval.uiCapability()->setUiReadOnly( !m_loggerFilename().first );
+
caf::PdmUiGroup* otherGroup = uiOrdering.addNewGroup( "Other" );
- otherGroup->add( &ssihubAddress );
otherGroup->add( &holoLensDisableCertificateVerification );
otherGroup->add( &m_useUndoRedo );
}
@@ -466,17 +474,11 @@ void RiaPreferences::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering&
otherGroup->add( &m_gridCalculationExpressionFolder );
otherGroup->add( &m_summaryCalculationExpressionFolder );
- caf::PdmUiGroup* loggingGroup = uiOrdering.addNewGroup( "Logging and Backup" );
- loggingGroup->add( &m_storeBackupOfProjectFile );
- loggingGroup->add( &m_loggerFilename );
- loggingGroup->add( &m_loggerFlushInterval );
- loggingGroup->add( &m_loggerTrapSignalAndFlush );
- m_loggerTrapSignalAndFlush.uiCapability()->setUiReadOnly( !m_loggerFilename().first );
- m_loggerFlushInterval.uiCapability()->setUiReadOnly( !m_loggerFilename().first );
- }
- else if ( uiConfigName == RiaPreferences::tabNameOsdu() )
- {
- m_osduPreferences()->uiOrdering( uiConfigName, uiOrdering );
+ caf::PdmUiGroup* osduGroup = uiOrdering.addNewGroup( "OSDU" );
+ m_osduPreferences()->uiOrdering( uiConfigName, *osduGroup );
+
+ caf::PdmUiGroup* sumoGroup = uiOrdering.addNewGroup( "SUMO" );
+ m_sumoPreferences()->uiOrdering( uiConfigName, *sumoGroup );
}
else if ( RiaApplication::enableDevelopmentFeatures() && uiConfigName == RiaPreferences::tabNameSystem() )
{
@@ -605,14 +607,6 @@ QString RiaPreferences::tabNameSystem()
return "System";
}
-//--------------------------------------------------------------------------------------------------
-///
-//--------------------------------------------------------------------------------------------------
-QString RiaPreferences::tabNameOsdu()
-{
- return "Osdu";
-}
-
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -653,7 +647,6 @@ QStringList RiaPreferences::tabNames()
names << tabNameGeomech();
#endif
names << tabNameImportExport();
- names << tabNameOsdu();
if ( RiaApplication::enableDevelopmentFeatures() )
{
@@ -1028,6 +1021,14 @@ RiaPreferencesOsdu* RiaPreferences::osduPreferences() const
return m_osduPreferences();
}
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+RiaPreferencesSumo* RiaPreferences::sumoPreferences() const
+{
+ return m_sumoPreferences();
+}
+
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
diff --git a/ApplicationLibCode/Application/RiaPreferences.h b/ApplicationLibCode/Application/RiaPreferences.h
index 89d8964805..c56cccf38c 100644
--- a/ApplicationLibCode/Application/RiaPreferences.h
+++ b/ApplicationLibCode/Application/RiaPreferences.h
@@ -46,6 +46,7 @@ class RiaPreferencesGeoMech;
class RiaPreferencesSystem;
class RiaPreferencesOsdu;
class RiaPreferencesGrid;
+class RiaPreferencesSumo;
//--------------------------------------------------------------------------------------------------
///
@@ -129,6 +130,7 @@ public:
RiaPreferencesSummary* summaryPreferences() const;
RiaPreferencesSystem* systemPreferences() const;
RiaPreferencesOsdu* osduPreferences() const;
+ RiaPreferencesSumo* sumoPreferences() const;
RiaPreferencesGrid* gridPreferences() const;
public:
@@ -139,8 +141,6 @@ public:
caf::PdmField scriptEditorExecutable;
caf::PdmField showPythonDebugInfo;
- caf::PdmField ssihubAddress;
-
caf::PdmField defaultGridLineColors;
caf::PdmField defaultFaultGridLineColors;
caf::PdmField defaultViewerBackgroundColor;
@@ -171,7 +171,6 @@ private:
static QString tabNamePlotting();
static QString tabNameScripting();
static QString tabNameSystem();
- static QString tabNameOsdu();
static QString tabNameImportExport();
static double defaultMarginSize( QPageSize::PageSizeId pageSizeId );
@@ -238,6 +237,7 @@ private:
// Osdu settings
caf::PdmChildField m_osduPreferences;
+ caf::PdmChildField m_sumoPreferences;
// 3d view
caf::PdmField> m_defaultMeshModeType;
diff --git a/ApplicationLibCode/Application/RiaPreferencesSumo.cpp b/ApplicationLibCode/Application/RiaPreferencesSumo.cpp
new file mode 100644
index 0000000000..0ad7f66e76
--- /dev/null
+++ b/ApplicationLibCode/Application/RiaPreferencesSumo.cpp
@@ -0,0 +1,75 @@
+/////////////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2024 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 "RiaApplication.h"
+
+#include "RiaPreferences.h"
+#include "RiaPreferencesSumo.h"
+
+CAF_PDM_SOURCE_INIT( RiaPreferencesSumo, "RiaPreferencesSumo" );
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+RiaPreferencesSumo::RiaPreferencesSumo()
+{
+ CAF_PDM_InitFieldNoDefault( &m_server, "server", "Server" );
+ CAF_PDM_InitFieldNoDefault( &m_authority, "authority", "Authority" );
+ CAF_PDM_InitFieldNoDefault( &m_scopes, "scopes", "Scopes" );
+ CAF_PDM_InitFieldNoDefault( &m_clientId, "clientId", "Client Id" );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+RiaPreferencesSumo* RiaPreferencesSumo::current()
+{
+ return RiaApplication::instance()->preferences()->sumoPreferences();
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+QString RiaPreferencesSumo::server() const
+{
+ return m_server;
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+QString RiaPreferencesSumo::authority() const
+{
+ return m_authority;
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+QString RiaPreferencesSumo::scopes() const
+{
+ return m_scopes;
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+QString RiaPreferencesSumo::clientId() const
+{
+ return m_clientId;
+}
diff --git a/ApplicationLibCode/Application/RiaPreferencesSumo.h b/ApplicationLibCode/Application/RiaPreferencesSumo.h
new file mode 100644
index 0000000000..a66c2bef67
--- /dev/null
+++ b/ApplicationLibCode/Application/RiaPreferencesSumo.h
@@ -0,0 +1,46 @@
+/////////////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2024 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.
+//
+/////////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+
+#include "cafPdmField.h"
+#include "cafPdmObject.h"
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+class RiaPreferencesSumo : public caf::PdmObject
+{
+ CAF_PDM_HEADER_INIT;
+
+public:
+ RiaPreferencesSumo();
+
+ static RiaPreferencesSumo* current();
+
+ QString server() const;
+ QString authority() const;
+ QString scopes() const;
+ QString clientId() const;
+
+private:
+ caf::PdmField m_server;
+ caf::PdmField m_authority;
+ caf::PdmField m_scopes;
+ caf::PdmField m_clientId;
+};
diff --git a/ApplicationLibCode/Application/Tools/Cloud/CMakeLists_files.cmake b/ApplicationLibCode/Application/Tools/Cloud/CMakeLists_files.cmake
new file mode 100644
index 0000000000..6fe5273a78
--- /dev/null
+++ b/ApplicationLibCode/Application/Tools/Cloud/CMakeLists_files.cmake
@@ -0,0 +1,17 @@
+set(SOURCE_GROUP_HEADER_FILES
+ ${CMAKE_CURRENT_LIST_DIR}/RiaSumoConnector.h
+ ${CMAKE_CURRENT_LIST_DIR}/RiaSumoDefines.h
+ ${CMAKE_CURRENT_LIST_DIR}/RiaConnectorTools.h
+)
+
+set(SOURCE_GROUP_SOURCE_FILES
+ ${CMAKE_CURRENT_LIST_DIR}/RiaSumoConnector.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/RiaSumoDefines.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/RiaConnectorTools.cpp
+)
+
+list(APPEND QT_MOC_HEADERS ${CMAKE_CURRENT_LIST_DIR}/RiaSumoConnector.h)
+
+list(APPEND CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES})
+
+list(APPEND CODE_SOURCE_FILES ${SOURCE_GROUP_SOURCE_FILES})
diff --git a/ApplicationLibCode/Application/Tools/Cloud/RiaConnectorTools.cpp b/ApplicationLibCode/Application/Tools/Cloud/RiaConnectorTools.cpp
new file mode 100644
index 0000000000..c7872bc1b5
--- /dev/null
+++ b/ApplicationLibCode/Application/Tools/Cloud/RiaConnectorTools.cpp
@@ -0,0 +1,80 @@
+/////////////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2024- 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 "RiaConnectorTools.h"
+
+#include
+#include
+#include
+#include
+#include
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+QString RiaConnectorTools::tokenDataAsJson( QOAuth2AuthorizationCodeFlow* authCodeFlow )
+{
+ QJsonObject obj;
+ obj.insert( "token", authCodeFlow->token() );
+ obj.insert( "refreshToken", authCodeFlow->refreshToken() );
+
+ QJsonDocument doc( obj );
+ return doc.toJson( QJsonDocument::Indented );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaConnectorTools::initializeTokenDataFromJson( QOAuth2AuthorizationCodeFlow* authCodeFlow, const QString& tokenDataJson )
+{
+ QJsonDocument doc = QJsonDocument::fromJson( tokenDataJson.toUtf8() );
+ QJsonObject obj = doc.object();
+
+ authCodeFlow->setToken( obj["token"].toString() );
+ authCodeFlow->setRefreshToken( obj["refreshToken"].toString() );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaConnectorTools::writeTokenData( const QString& filePath, const QString& tokenDataJson )
+{
+ QFile file( filePath );
+ if ( file.open( QIODevice::WriteOnly ) )
+ {
+ QTextStream stream( &file );
+ stream << tokenDataJson;
+ file.close();
+ }
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+QString RiaConnectorTools::readTokenData( const QString& filePath )
+{
+ QFile file( filePath );
+ if ( file.open( QIODevice::ReadOnly ) )
+ {
+ QTextStream stream( &file );
+ QString result = stream.readAll();
+ file.close();
+ return result;
+ }
+ return {};
+}
diff --git a/ApplicationLibCode/Application/Tools/Cloud/RiaConnectorTools.h b/ApplicationLibCode/Application/Tools/Cloud/RiaConnectorTools.h
new file mode 100644
index 0000000000..5acb2f4f8e
--- /dev/null
+++ b/ApplicationLibCode/Application/Tools/Cloud/RiaConnectorTools.h
@@ -0,0 +1,31 @@
+/////////////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2024- 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.
+//
+/////////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+
+#include
+
+class QOAuth2AuthorizationCodeFlow;
+
+namespace RiaConnectorTools
+{
+QString tokenDataAsJson( QOAuth2AuthorizationCodeFlow* authCodeFlow );
+void initializeTokenDataFromJson( QOAuth2AuthorizationCodeFlow* authCodeFlow, const QString& tokenDataJson );
+void writeTokenData( const QString& filePath, const QString& tokenDataJson );
+QString readTokenData( const QString& filePath );
+} // namespace RiaConnectorTools
diff --git a/ApplicationLibCode/Application/Tools/Cloud/RiaSumoConnector.cpp b/ApplicationLibCode/Application/Tools/Cloud/RiaSumoConnector.cpp
new file mode 100644
index 0000000000..9e7feda679
--- /dev/null
+++ b/ApplicationLibCode/Application/Tools/Cloud/RiaSumoConnector.cpp
@@ -0,0 +1,1110 @@
+/////////////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2024 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 "RiaSumoConnector.h"
+
+#include "RiaConnectorTools.h"
+#include "RiaFileDownloader.h"
+#include "RiaLogging.h"
+#include "RiaOsduDefines.h"
+
+#include "OsduImportCommands/RiaOsduOAuthHttpServerReplyHandler.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+RiaSumoConnector::RiaSumoConnector( QObject* parent, const QString& server, const QString& authority, const QString& scopes, const QString& clientId )
+ : QObject( parent )
+ , m_server( server )
+ , m_authority( authority )
+ , m_scopes( scopes )
+ , m_clientId( clientId )
+{
+ m_authCodeFlow = new QOAuth2AuthorizationCodeFlow( this );
+ m_networkAccessManager = new QNetworkAccessManager( this );
+ m_authCodeFlow->setNetworkAccessManager( m_networkAccessManager );
+
+ RiaLogging::debug( "SSL BUILD VERSION: " + QSslSocket::sslLibraryBuildVersionString() );
+ RiaLogging::debug( "SSL VERSION STRING: " + QSslSocket::sslLibraryVersionString() );
+
+ // NB: Make sure the port is not in use by another application
+ const unsigned int port = 53527;
+
+ connect( m_authCodeFlow,
+ &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser,
+ []( QUrl url )
+ {
+ RiaLogging::info( "Authorize with url: " + url.toString() );
+ QUrlQuery query( url );
+ url.setQuery( query );
+ QDesktopServices::openUrl( url );
+ } );
+
+ QString authUrl = constructAuthUrl( m_authority );
+ m_authCodeFlow->setAuthorizationUrl( QUrl( authUrl ) );
+
+ QString tokenUrl = constructTokenUrl( m_authority );
+ m_authCodeFlow->setAccessTokenUrl( QUrl( tokenUrl ) );
+
+ // App key
+ m_authCodeFlow->setClientIdentifier( m_clientId );
+ m_authCodeFlow->setScope( m_scopes );
+
+ auto replyHandler = new RiaOsduOAuthHttpServerReplyHandler( port, this );
+ m_authCodeFlow->setReplyHandler( replyHandler );
+
+ connect( m_authCodeFlow, SIGNAL( granted() ), this, SLOT( accessGranted() ) );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::accessGranted()
+{
+ m_token = m_authCodeFlow->token();
+
+ QString tokenDataJson = RiaConnectorTools::tokenDataAsJson( m_authCodeFlow );
+ RiaConnectorTools::writeTokenData( m_tokenDataFilePath, tokenDataJson );
+
+ emit tokenReady( m_token );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::requestFailed( const QAbstractOAuth::Error error )
+{
+ RiaLogging::error( "Request failed: " );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::parquetDownloadComplete( const QString& blobId, const QByteArray& contents, const QString& url )
+{
+ SumoRedirect obj;
+ obj.objectId = blobId;
+ obj.contents = contents;
+ obj.url = url;
+
+ m_redirectInfo.push_back( obj );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::requestToken()
+{
+ RiaLogging::debug( "Requesting token." );
+ m_authCodeFlow->grant();
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+RiaSumoConnector::~RiaSumoConnector()
+{
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+QString RiaSumoConnector::token() const
+{
+ return m_token;
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::importTokenFromFile()
+{
+ auto tokenDataJson = RiaConnectorTools::readTokenData( m_tokenDataFilePath );
+ if ( !tokenDataJson.isEmpty() )
+ {
+ RiaConnectorTools::initializeTokenDataFromJson( m_authCodeFlow, tokenDataJson );
+ m_token = m_authCodeFlow->token();
+ }
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::setTokenDataFilePath( const QString& filePath )
+{
+ m_tokenDataFilePath = filePath;
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::requestCasesForField( const QString& fieldName )
+{
+ m_cases.clear();
+
+ requestTokenBlocking();
+
+ QNetworkRequest m_networkRequest;
+ m_networkRequest.setUrl( QUrl( constructSearchUrl( m_server ) ) );
+
+ addStandardHeader( m_networkRequest, m_token, RiaDefines::contentTypeJson() );
+
+ QString payloadTemplate = R"(
+{
+ "query": {
+ "bool": {
+ "filter": [
+ {"term":{"class.keyword":"case"}},
+ {"term":{"access.asset.name.keyword":"%1"}}
+ ]
+ }
+ },
+ "sort": [
+ {"tracklog.datetime":{"order":"desc"}}
+ ],
+ "track_total_hits":true,
+ "size":100,
+ "from":0
+}
+)";
+
+ QString payload = payloadTemplate.arg( fieldName );
+ auto reply = m_networkAccessManager->post( m_networkRequest, payload.toUtf8() );
+
+ connect( reply,
+ &QNetworkReply::finished,
+ [this, reply]()
+ {
+ if ( reply->error() == QNetworkReply::NoError )
+ {
+ parseCases( reply );
+ }
+ } );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::requestCasesForFieldBlocking( const QString& fieldName )
+{
+ QEventLoop loop;
+ connect( this, SIGNAL( casesFinished() ), &loop, SLOT( quit() ) );
+ QTimer timer;
+
+ requestCasesForField( fieldName );
+
+ timer.setSingleShot( true );
+ timer.start( RiaSumoDefines::requestTimeoutMillis() );
+ loop.exec();
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::requestAssets()
+{
+ requestTokenBlocking();
+
+ QNetworkRequest m_networkRequest;
+ m_networkRequest.setUrl( QUrl( m_server + "/api/v1/userpermissions" ) );
+
+ addStandardHeader( m_networkRequest, m_token, RiaDefines::contentTypeJson() );
+
+ auto reply = m_networkAccessManager->get( m_networkRequest );
+
+ connect( reply,
+ &QNetworkReply::finished,
+ [this, reply]()
+ {
+ if ( reply->error() == QNetworkReply::NoError )
+ {
+ parseAssets( reply );
+ }
+ } );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::requestAssetsBlocking()
+{
+ QEventLoop loop;
+ connect( this, SIGNAL( assetsFinished() ), &loop, SLOT( quit() ) );
+ QTimer timer;
+
+ requestAssets();
+
+ timer.setSingleShot( true );
+ timer.start( RiaSumoDefines::requestTimeoutMillis() );
+ loop.exec();
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::requestEnsembleByCasesId( const SumoCaseId& caseId )
+{
+ QString payloadTemplate = R"(
+
+{
+ "query": {
+ "bool": {
+ "filter": [
+ {"term":{"_sumo.parent_object.keyword":"%1"}}
+ ]
+ }
+ },
+ "aggs": {
+ "aggs_columns": {
+ "terms": { "field": "fmu.iteration.name.keyword", "size": 5 }
+ }
+ },
+ "track_total_hits":true,
+ "size":20,
+ "from":0,
+ "_source": false
+}
+
+)";
+
+ QNetworkRequest m_networkRequest;
+ m_networkRequest.setUrl( QUrl( m_server + "/api/v1/search" ) );
+
+ addStandardHeader( m_networkRequest, m_token, RiaDefines::contentTypeJson() );
+
+ auto payload = payloadTemplate.arg( caseId.get() );
+ auto reply = m_networkAccessManager->post( m_networkRequest, payload.toUtf8() );
+
+ connect( reply,
+ &QNetworkReply::finished,
+ [this, reply, caseId]()
+ {
+ if ( reply->error() == QNetworkReply::NoError )
+ {
+ parseEnsembleNames( reply, caseId );
+ }
+ } );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::requestEnsembleByCasesIdBlocking( const SumoCaseId& caseId )
+{
+ QEventLoop loop;
+ connect( this, SIGNAL( ensembleNamesFinished() ), &loop, SLOT( quit() ) );
+ QTimer timer;
+
+ requestEnsembleByCasesId( caseId );
+
+ timer.setSingleShot( true );
+ timer.start( RiaSumoDefines::requestTimeoutMillis() );
+ loop.exec();
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::requestVectorNamesForEnsemble( const SumoCaseId& caseId, const QString& ensembleName )
+{
+ QString payloadTemplate = R"(
+{
+ "track_total_hits": true,
+ "query": { "bool": {
+ "must": [
+ {"term": {"class": "table"}},
+ {"term": {"_sumo.parent_object.keyword": "%1"}},
+ {"term": {"fmu.iteration.name.keyword": "%2"}},
+ {"term": {"fmu.context.stage.keyword": "iteration"}},
+ {"term": {"fmu.aggregation.operation.keyword": "collection"}},
+ {"term": {"data.tagname.keyword": "summary"}},
+ {"term": {"data.content.keyword": "timeseries"}}
+ ]}
+ },
+ "aggs": {
+ "smry_tables": {
+ "terms": {
+ "field": "data.name.keyword"
+ },
+ "aggs": {
+ "smry_columns": {
+ "terms": {
+ "field": "data.spec.columns.keyword",
+ "size": 65535
+ }
+ }
+ }
+ }
+ },
+ "_source": false,
+ "size": 0
+})";
+
+ QNetworkRequest m_networkRequest;
+ m_networkRequest.setUrl( QUrl( m_server + "/api/v1/search" ) );
+
+ addStandardHeader( m_networkRequest, m_token, RiaDefines::contentTypeJson() );
+
+ auto payload = payloadTemplate.arg( caseId.get() ).arg( ensembleName );
+ auto reply = m_networkAccessManager->post( m_networkRequest, payload.toUtf8() );
+
+ connect( reply,
+ &QNetworkReply::finished,
+ [this, reply, ensembleName, caseId]()
+ {
+ if ( reply->error() == QNetworkReply::NoError )
+ {
+ parseVectorNames( reply, caseId, ensembleName );
+ }
+ } );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::requestVectorNamesForEnsembleBlocking( const SumoCaseId& caseId, const QString& ensembleName )
+{
+ QEventLoop loop;
+ connect( this, SIGNAL( vectorNamesFinished() ), &loop, SLOT( quit() ) );
+ QTimer timer;
+
+ requestVectorNamesForEnsemble( caseId, ensembleName );
+
+ timer.setSingleShot( true );
+ timer.start( RiaSumoDefines::requestTimeoutMillis() );
+ loop.exec();
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::requestRealizationIdsForEnsemble( const SumoCaseId& caseId, const QString& ensembleName )
+{
+ QString payloadTemplate = R"(
+{
+ "track_total_hits": true,
+ "query": {
+ "bool": {
+ "must": [
+ {"term": {"class": "table"}},
+ {"term": {"_sumo.parent_object.keyword": "%1"}},
+ {"term": {"fmu.iteration.name.keyword": "%2"}},
+ {"term": {"fmu.context.stage.keyword": "iteration"}},
+ {"term": {"fmu.aggregation.operation.keyword": "collection"}},
+ {"term": {"data.tagname.keyword": "summary"}},
+ {"term": {"data.content.keyword": "timeseries"}}
+ ]}
+ },
+ "aggs": {
+ "realization-ids": {
+ "terms": {
+ "field": "fmu.aggregation.realization_ids",
+ "size":1000
+ }
+ }
+ },
+ "_source": false,
+ "size":0
+}
+)";
+ m_realizationIds.clear();
+
+ QNetworkRequest m_networkRequest;
+ m_networkRequest.setUrl( QUrl( m_server + "/api/v1/search" ) );
+
+ addStandardHeader( m_networkRequest, m_token, RiaDefines::contentTypeJson() );
+
+ auto payload = payloadTemplate.arg( caseId.get() ).arg( ensembleName );
+ auto reply = m_networkAccessManager->post( m_networkRequest, payload.toUtf8() );
+
+ connect( reply,
+ &QNetworkReply::finished,
+ [this, reply, ensembleName, caseId]()
+ {
+ if ( reply->error() == QNetworkReply::NoError )
+ {
+ parseRealizationNumbers( reply, caseId, ensembleName );
+ }
+ } );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::requestRealizationIdsForEnsembleBlocking( const SumoCaseId& caseId, const QString& ensembleName )
+{
+ QEventLoop loop;
+ connect( this, SIGNAL( realizationIdsFinished() ), &loop, SLOT( quit() ) );
+ QTimer timer;
+
+ requestRealizationIdsForEnsemble( caseId, ensembleName );
+
+ timer.setSingleShot( true );
+ timer.start( RiaSumoDefines::requestTimeoutMillis() );
+ loop.exec();
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::requestBlobIdForEnsemble( const SumoCaseId& caseId, const QString& ensembleName, const QString& vectorName )
+{
+ QString payloadTemplate = R"(
+{
+ "track_total_hits": true,
+ "query": { "bool": {
+ "must": [
+ {"term": {"class": "table"}},
+ {"term": {"_sumo.parent_object.keyword": "%1"}},
+ {"term": {"fmu.iteration.name.keyword": "%2"}},
+ {"term": {"fmu.context.stage.keyword": "iteration"}},
+ {"term": {"fmu.aggregation.operation.keyword": "collection"}},
+ {"term": {"data.tagname.keyword": "summary"}},
+ {"term": {"data.spec.columns.keyword": "%3"}}
+ ]}
+ },
+ "fields": [
+ "data.name",
+ "_sumo.blob_name"
+ ],
+ "_source": true,
+ "size": 1
+}
+)";
+
+ QNetworkRequest m_networkRequest;
+ m_networkRequest.setUrl( QUrl( m_server + "/api/v1/search" ) );
+
+ addStandardHeader( m_networkRequest, m_token, RiaDefines::contentTypeJson() );
+
+ auto payload = payloadTemplate.arg( caseId.get() ).arg( ensembleName ).arg( vectorName );
+ auto reply = m_networkAccessManager->post( m_networkRequest, payload.toUtf8() );
+
+ connect( reply,
+ &QNetworkReply::finished,
+ [this, reply, ensembleName, caseId, vectorName]()
+ {
+ if ( reply->error() == QNetworkReply::NoError )
+ {
+ parseBlobIds( reply, caseId, ensembleName, vectorName );
+ }
+ } );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::requestBlobIdForEnsembleBlocking( const SumoCaseId& caseId, const QString& ensembleName, const QString& vectorName )
+{
+ QEventLoop loop;
+ connect( this, SIGNAL( blobIdFinished() ), &loop, SLOT( quit() ) );
+ QTimer timer;
+
+ requestBlobIdForEnsemble( caseId, ensembleName, vectorName );
+
+ timer.setSingleShot( true );
+ timer.start( RiaSumoDefines::requestTimeoutMillis() );
+ loop.exec();
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::requestBlobDownload( const QString& blobId )
+{
+ QString url = constructDownloadUrl( m_server, blobId );
+
+ QNetworkRequest networkRequest;
+ networkRequest.setUrl( url );
+
+ // Other redirection policies are NoLessSafeRedirectPolicy, SameOriginRedirectPolicy, UserVerifiedRedirectPolicy. They were tested, but
+ // did not work. Use ManualRedirectPolicy instead, and inspect the reply for the redirection target.
+ networkRequest.setAttribute( QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy );
+
+ addStandardHeader( networkRequest, m_token, RiaDefines::contentTypeJson() );
+
+ auto reply = m_networkAccessManager->get( networkRequest );
+
+ connect( reply,
+ &QNetworkReply::finished,
+ [this, reply, blobId, url]()
+ {
+ if ( reply->error() == QNetworkReply::NoError )
+ {
+ auto contents = reply->readAll();
+
+ QVariant redirectUrl = reply->attribute( QNetworkRequest::RedirectionTargetAttribute );
+ if ( redirectUrl.isValid() )
+ {
+ requestBlobByRedirectUri( blobId, redirectUrl.toString() );
+ }
+ else
+ {
+ QString errorMessage = "Not able to parse and interpret valid redirect Url";
+ RiaLogging::error( errorMessage );
+ }
+ }
+ else
+ {
+ QString errorMessage = "Download failed: " + url + " failed." + reply->errorString();
+ RiaLogging::error( errorMessage );
+ }
+ } );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::requestBlobByRedirectUri( const QString& blobId, const QString& redirectUri )
+{
+ QNetworkRequest networkRequest;
+ networkRequest.setUrl( redirectUri );
+
+ auto reply = m_networkAccessManager->get( networkRequest );
+
+ connect( reply,
+ &QNetworkReply::finished,
+ [this, reply, blobId, redirectUri]()
+ {
+ if ( reply->error() == QNetworkReply::NoError )
+ {
+ auto contents = reply->readAll();
+
+ QString msg = "Received data from : " + redirectUri;
+ RiaLogging::info( msg );
+
+ parquetDownloadComplete( blobId, contents, redirectUri );
+
+ emit parquetDownloadFinished( contents, redirectUri );
+ }
+ else
+ {
+ QString errorMessage = "Download failed: " + redirectUri + " failed." + reply->errorString();
+ RiaLogging::error( errorMessage );
+
+ emit parquetDownloadFinished( {}, redirectUri );
+ }
+ } );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+QByteArray RiaSumoConnector::requestParquetDataBlocking( const SumoCaseId& caseId, const QString& ensembleName, const QString& vectorName )
+{
+ requestBlobIdForEnsembleBlocking( caseId, ensembleName, vectorName );
+
+ if ( m_blobUrl.empty() ) return {};
+
+ auto blobId = m_blobUrl.back();
+
+ QEventLoop loop;
+ connect( this, SIGNAL( parquetDownloadFinished( const QByteArray&, const QString& ) ), &loop, SLOT( quit() ) );
+ QTimer timer;
+
+ requestBlobDownload( blobId );
+
+ timer.setSingleShot( true );
+ timer.start( RiaSumoDefines::requestTimeoutMillis() );
+ loop.exec();
+
+ for ( const auto& blobData : m_redirectInfo )
+ {
+ if ( blobData.objectId == blobId )
+ {
+ return blobData.contents;
+ }
+ }
+
+ return {};
+}
+
+//--------------------------------------------------------------------------------------------------
+//
+//--------------------------------------------------------------------------------------------------
+QString RiaSumoConnector::constructSearchUrl( const QString& server )
+{
+ return server + "/api/v1/search";
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+QString RiaSumoConnector::constructDownloadUrl( const QString& server, const QString& blobId )
+{
+ return server + "/api/v1/objects('" + blobId + "')/blob";
+ // https: // main-sumo-prod.radix.equinor.com/api/v1/objects('76d6d11f-2278-3fe2-f12f-77142ad163c6')/blob
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+QString RiaSumoConnector::constructAuthUrl( const QString& authority )
+{
+ return authority + "/oauth2/v2.0/authorize";
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+QString RiaSumoConnector::constructTokenUrl( const QString& authority )
+{
+ return authority + "/oauth2/v2.0/token";
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+QNetworkReply* RiaSumoConnector::makeRequest( const std::map& parameters, const QString& server, const QString& token )
+{
+ QNetworkRequest m_networkRequest;
+ m_networkRequest.setUrl( QUrl( constructSearchUrl( server ) ) );
+
+ addStandardHeader( m_networkRequest, token, RiaDefines::contentTypeJson() );
+
+ QJsonObject obj;
+ for ( auto [key, value] : parameters )
+ {
+ obj.insert( key, value );
+ }
+
+ QJsonDocument doc( obj );
+ QString strJson( doc.toJson( QJsonDocument::Compact ) );
+
+ auto reply = m_networkAccessManager->post( m_networkRequest, strJson.toUtf8() );
+ return reply;
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::parseAssets( QNetworkReply* reply )
+{
+ QByteArray result = reply->readAll();
+ reply->deleteLater();
+
+ if ( reply->error() == QNetworkReply::NoError )
+ {
+ QJsonDocument doc = QJsonDocument::fromJson( result );
+ QJsonObject jsonObj = doc.object();
+
+ m_assets.clear();
+
+ for ( auto key : jsonObj.keys() )
+ {
+ QString id;
+ QString kind;
+ QString fieldName = key;
+ m_assets.push_back( SumoAsset{ SumoAssetId( id ), kind, fieldName } );
+ }
+
+ for ( auto a : m_assets )
+ {
+ RiaLogging::info( QString( "Asset: %1" ).arg( a.name ) );
+ }
+ }
+ emit assetsFinished();
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::parseEnsembleNames( QNetworkReply* reply, const SumoCaseId& caseId )
+{
+ QByteArray result = reply->readAll();
+ reply->deleteLater();
+
+ if ( reply->error() == QNetworkReply::NoError )
+ {
+ m_ensembleNames.clear();
+
+ QJsonDocument doc = QJsonDocument::fromJson( result );
+ QJsonObject jsonObj = doc.object();
+ auto keys_1 = jsonObj.keys();
+
+ auto aggregationsObject = jsonObj["aggregations"].toObject();
+
+ QJsonObject aggregationColumnsObject = aggregationsObject["aggs_columns"].toObject();
+ auto keys_2 = aggregationColumnsObject.keys();
+
+ QJsonArray bucketsArray = aggregationColumnsObject["buckets"].toArray();
+ foreach ( const QJsonValue& bucket, bucketsArray )
+ {
+ QJsonObject bucketObj = bucket.toObject();
+ auto keys_3 = bucketObj.keys();
+
+ auto ensembleName = bucketObj["key"].toString();
+ m_ensembleNames.push_back( { caseId, ensembleName } );
+ }
+ }
+
+ emit ensembleNamesFinished();
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::parseCases( QNetworkReply* reply )
+{
+ QByteArray result = reply->readAll();
+ reply->deleteLater();
+
+ if ( reply->error() == QNetworkReply::NoError )
+ {
+ QJsonDocument doc = QJsonDocument::fromJson( result );
+ QJsonObject jsonObj = doc.object();
+ QJsonObject rootHits = jsonObj["hits"].toObject();
+
+ QJsonArray hitsObjects = rootHits["hits"].toArray();
+
+ m_cases.clear();
+
+ foreach ( const QJsonValue& value, hitsObjects )
+ {
+ QJsonObject resultObj = value.toObject();
+ auto keys_1 = resultObj.keys();
+
+ QJsonObject sourceObj = resultObj["_source"].toObject();
+ auto sourceKeys = sourceObj.keys();
+
+ QJsonObject fmuObj = sourceObj["fmu"].toObject();
+ auto fmuObjKeys = fmuObj.keys();
+
+ QJsonObject fmuCase = fmuObj["case"].toObject();
+ auto fmuCaseKeys = fmuCase.keys();
+
+ QString id = resultObj["_id"].toString();
+ QString kind = "";
+ QString fieldName = fmuCase["name"].toString();
+ m_cases.push_back( SumoCase{ SumoCaseId( id ), kind, fieldName } );
+ }
+
+ emit casesFinished();
+ }
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::parseVectorNames( QNetworkReply* reply, const SumoCaseId& caseId, const QString& ensembleName )
+{
+ QByteArray result = reply->readAll();
+ reply->deleteLater();
+
+ m_vectorNames.clear();
+
+ if ( reply->error() == QNetworkReply::NoError )
+ {
+ QJsonDocument doc = QJsonDocument::fromJson( result );
+ QJsonObject jsonObj = doc.object();
+
+ QJsonArray tableHits = jsonObj["aggregations"].toObject()["smry_tables"].toObject()["buckets"].toArray();
+ for ( const auto& tableHit : tableHits )
+ {
+ QJsonArray columnHits = tableHit.toObject()["smry_columns"].toObject()["buckets"].toArray();
+ for ( const auto& columnHit : columnHits )
+ {
+ m_vectorNames.push_back( columnHit.toObject()["key"].toString() );
+ }
+ }
+ }
+
+ emit vectorNamesFinished();
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::parseRealizationNumbers( QNetworkReply* reply, const SumoCaseId& caseId, const QString& ensembleName )
+{
+ QByteArray result = reply->readAll();
+ reply->deleteLater();
+
+ if ( reply->error() == QNetworkReply::NoError )
+ {
+ QJsonDocument doc = QJsonDocument::fromJson( result );
+ QJsonObject jsonObj = doc.object();
+
+ QJsonArray hits = jsonObj["aggregations"].toObject()["realization-ids"].toObject()["buckets"].toArray();
+ for ( const auto& hit : hits )
+ {
+ QJsonObject resultObj = hit.toObject();
+ auto keys_1 = resultObj.keys();
+
+ auto val = resultObj.value( "key" );
+ auto intValue = val.toInt();
+
+ auto realizationId = QString::number( intValue );
+ m_realizationIds.push_back( realizationId );
+ }
+ }
+
+ emit realizationIdsFinished();
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::parseBlobIds( QNetworkReply* reply, const SumoCaseId& caseId, const QString& ensembleName, const QString& vectorName )
+{
+ QByteArray result = reply->readAll();
+ reply->deleteLater();
+
+ m_blobUrl.clear();
+
+ if ( reply->error() == QNetworkReply::NoError )
+ {
+ QJsonDocument doc = QJsonDocument::fromJson( result );
+ QJsonObject jsonObj = doc.object();
+
+ QJsonObject rootHits = jsonObj["hits"].toObject();
+ QJsonArray hitsObjects = rootHits["hits"].toArray();
+
+ foreach ( const QJsonValue& value, hitsObjects )
+ {
+ QJsonObject resultObj = value.toObject();
+ auto keys_1 = resultObj.keys();
+
+ QJsonObject sourceObj = resultObj["_source"].toObject();
+ auto sourceKeys = sourceObj.keys();
+
+ QJsonObject fmuObj = sourceObj["_sumo"].toObject();
+ auto fmuObjKeys = fmuObj.keys();
+
+ auto blobName = fmuObj["blob_name"].toString();
+ m_blobUrl.push_back( blobName );
+ }
+ }
+
+ emit blobIdFinished();
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::saveFile( QNetworkReply* reply, const QString& fileId )
+{
+ QByteArray result = reply->readAll();
+ reply->deleteLater();
+
+ if ( reply->error() == QNetworkReply::NoError )
+ {
+ QEventLoop loop;
+
+ QJsonDocument doc = QJsonDocument::fromJson( result );
+ QJsonObject jsonObj = doc.object();
+
+ QString signedUrl = jsonObj["SignedUrl"].toString();
+
+ RiaFileDownloader* downloader = new RiaFileDownloader;
+ QUrl url( signedUrl );
+ QString filePath = "/tmp/" + generateRandomString( 30 ) + ".txt";
+
+ QString formattedJsonString = doc.toJson( QJsonDocument::Indented );
+
+ RiaLogging::info( QString( "File download: %1 => %2" ).arg( signedUrl ).arg( filePath ) );
+ connect( this, SIGNAL( fileDownloadFinished( const QString&, const QString& ) ), &loop, SLOT( quit() ) );
+ connect( downloader,
+ &RiaFileDownloader::done,
+ [this, fileId, filePath]()
+ {
+ RiaLogging::info( QString( "Download complete %1 => %2" ).arg( fileId ).arg( filePath ) );
+ emit( fileDownloadFinished( fileId, filePath ) );
+ } );
+ RiaLogging::info( "Starting download" );
+ downloader->downloadFile( url, filePath );
+
+ downloader->deleteLater();
+ loop.exec();
+ }
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::addStandardHeader( QNetworkRequest& networkRequest, const QString& token, const QString& contentType )
+{
+ networkRequest.setHeader( QNetworkRequest::ContentTypeHeader, contentType );
+ networkRequest.setRawHeader( "Authorization", "Bearer " + token.toUtf8() );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+QString RiaSumoConnector::requestTokenBlocking()
+{
+ if ( !m_token.isEmpty() ) return m_token;
+
+ QTimer timer;
+ timer.setSingleShot( true );
+ QEventLoop loop;
+ connect( this, SIGNAL( tokenReady( const QString& ) ), &loop, SLOT( quit() ) );
+ connect( &timer, SIGNAL( timeout() ), &loop, SLOT( quit() ) );
+ requestToken();
+ timer.start( RiaSumoDefines::requestTimeoutMillis() );
+ loop.exec();
+ return m_token;
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+QNetworkReply* RiaSumoConnector::makeDownloadRequest( const QString& url, const QString& token, const QString& contentType )
+{
+ QNetworkRequest m_networkRequest;
+ m_networkRequest.setUrl( QUrl( url ) );
+
+ addStandardHeader( m_networkRequest, token, contentType );
+
+ auto reply = m_networkAccessManager->get( m_networkRequest );
+ return reply;
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RiaSumoConnector::requestParquetData( const QString& url, const QString& token )
+{
+ RiaLogging::info( "Requesting download of parquet from: " + url );
+
+ auto reply = makeDownloadRequest( url, token, RiaDefines::contentTypeJson() );
+ connect( reply,
+ &QNetworkReply::finished,
+ [this, reply, url]()
+ {
+ if ( reply->error() == QNetworkReply::NoError )
+ {
+ QByteArray contents = reply->readAll();
+ RiaLogging::info( QString( "Download succeeded: %1 bytes." ).arg( contents.length() ) );
+ emit parquetDownloadFinished( contents, "" );
+ }
+ else
+ {
+ QString errorMessage = "Download failed: " + url + " failed." + reply->errorString();
+ RiaLogging::error( errorMessage );
+ emit parquetDownloadFinished( QByteArray(), errorMessage );
+ }
+ } );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+QString RiaSumoConnector::generateRandomString( int randomStringLength )
+{
+ const QString possibleCharacters( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" );
+ QString randomString;
+ for ( int i = 0; i < randomStringLength; ++i )
+ {
+ quint32 value = QRandomGenerator::global()->generate();
+ int index = value % possibleCharacters.length();
+ QChar nextChar = possibleCharacters.at( index );
+ randomString.append( nextChar );
+ }
+ return randomString;
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+QString RiaSumoConnector::server() const
+{
+ return m_server;
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+std::vector RiaSumoConnector::assets() const
+{
+ return m_assets;
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+std::vector RiaSumoConnector::cases() const
+{
+ return m_cases;
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+std::vector RiaSumoConnector::ensembleNamesForCase( const SumoCaseId& caseId ) const
+{
+ std::vector ensembleNames;
+ for ( const auto& ensemble : m_ensembleNames )
+ {
+ if ( ensemble.caseId == caseId )
+ {
+ ensembleNames.push_back( ensemble.name );
+ }
+ }
+ return ensembleNames;
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+std::vector RiaSumoConnector::vectorNames() const
+{
+ return m_vectorNames;
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+std::vector RiaSumoConnector::realizationIds() const
+{
+ return m_realizationIds;
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+std::vector RiaSumoConnector::blobUrls() const
+{
+ return m_blobUrl;
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+std::vector RiaSumoConnector::blobContents() const
+{
+ return m_redirectInfo;
+}
diff --git a/ApplicationLibCode/Application/Tools/Cloud/RiaSumoConnector.h b/ApplicationLibCode/Application/Tools/Cloud/RiaSumoConnector.h
new file mode 100644
index 0000000000..3e8fb62a83
--- /dev/null
+++ b/ApplicationLibCode/Application/Tools/Cloud/RiaSumoConnector.h
@@ -0,0 +1,182 @@
+/////////////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2024 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.
+//
+/////////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+
+#include "RiaSumoDefines.h"
+
+#include
+#include
+#include
+
+#include