diff --git a/ApplicationExeCode/CMakeLists.txt b/ApplicationExeCode/CMakeLists.txt index ca5d0ff7a7..6a7f38f147 100644 --- a/ApplicationExeCode/CMakeLists.txt +++ b/ApplicationExeCode/CMakeLists.txt @@ -33,6 +33,7 @@ if(Qt5Core_FOUND) Concurrent PrintSupport Svg + Sql OPTIONAL_COMPONENTS Charts ) set(QT_LIBRARIES @@ -45,6 +46,7 @@ if(Qt5Core_FOUND) Qt5::Concurrent Qt5::PrintSupport Qt5::Svg + Qt5::Sql ) if(Qt5Charts_FOUND) list(APPEND QT_LIBRARIES Qt5::Charts) @@ -484,6 +486,13 @@ if(RESINSIGHT_PRIVATE_INSTALL) OPTIONAL ) + # Required sql driver + install( + FILES ${QT_PLUGIN_PATH}/sqldrivers/libqsqlite.so + DESTINATION ${RESINSIGHT_INSTALL_FOLDER}/sqldrivers/ + OPTIONAL + ) + install(FILES qt.conf DESTINATION ${RESINSIGHT_INSTALL_FOLDER}/) endif(RESINSIGHT_QT5_BUNDLE_LIBRARIES) diff --git a/ApplicationLibCode/Application/RiaPreferences.cpp b/ApplicationLibCode/Application/RiaPreferences.cpp index d0ea2ebdee..1f0269778f 100644 --- a/ApplicationLibCode/Application/RiaPreferences.cpp +++ b/ApplicationLibCode/Application/RiaPreferences.cpp @@ -140,6 +140,8 @@ RiaPreferences::RiaPreferences() CAF_PDM_InitField( &m_loggerFlushInterval, "loggerFlushInterval", 500, "Logging Flush Interval [ms]" ); CAF_PDM_InitField( &m_loggerTrapSignalAndFlush, "loggerTrapSignalAndFlush", false, "Trap SIGNAL and Flush File Logs" ); + 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 ); @@ -477,7 +479,8 @@ void RiaPreferences::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& otherGroup->add( &m_gridCalculationExpressionFolder ); otherGroup->add( &m_summaryCalculationExpressionFolder ); - caf::PdmUiGroup* loggingGroup = uiOrdering.addNewGroup( "Logging" ); + caf::PdmUiGroup* loggingGroup = uiOrdering.addNewGroup( "Logging and Backup" ); + loggingGroup->add( &m_storeBackupOfProjectFile ); loggingGroup->add( &m_loggerFilename ); loggingGroup->add( &m_loggerFlushInterval ); loggingGroup->add( &m_loggerTrapSignalAndFlush ); @@ -983,6 +986,14 @@ bool RiaPreferences::loggerTrapSignalAndFlush() const return m_loggerTrapSignalAndFlush(); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RiaPreferences::storeBackupOfProjectFiles() const +{ + return m_storeBackupOfProjectFile(); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/Application/RiaPreferences.h b/ApplicationLibCode/Application/RiaPreferences.h index 96628784f7..78f6d283c8 100644 --- a/ApplicationLibCode/Application/RiaPreferences.h +++ b/ApplicationLibCode/Application/RiaPreferences.h @@ -120,6 +120,7 @@ public: QString loggerFilename() const; int loggerFlushInterval() const; bool loggerTrapSignalAndFlush() const; + bool storeBackupOfProjectFiles() const; RiaPreferencesGeoMech* geoMechPreferences() const; RiaPreferencesSummary* summaryPreferences() const; @@ -212,6 +213,8 @@ private: caf::PdmField m_loggerFlushInterval; caf::PdmField m_loggerTrapSignalAndFlush; + caf::PdmField m_storeBackupOfProjectFile; + // Surface Import caf::PdmField m_surfaceImportResamplingDistance; diff --git a/ApplicationLibCode/Application/Tools/CMakeLists_files.cmake b/ApplicationLibCode/Application/Tools/CMakeLists_files.cmake index ad437b02db..ceb110c3a9 100644 --- a/ApplicationLibCode/Application/Tools/CMakeLists_files.cmake +++ b/ApplicationLibCode/Application/Tools/CMakeLists_files.cmake @@ -54,6 +54,7 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RiaNumericalTools.h ${CMAKE_CURRENT_LIST_DIR}/RiaRegressionTextTools.h ${CMAKE_CURRENT_LIST_DIR}/RiaFileLogger.h + ${CMAKE_CURRENT_LIST_DIR}/RiaProjectBackupTools.h ) set(SOURCE_GROUP_SOURCE_FILES @@ -105,6 +106,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RiaNumericalTools.cpp ${CMAKE_CURRENT_LIST_DIR}/RiaRegressionTextTools.cpp ${CMAKE_CURRENT_LIST_DIR}/RiaFileLogger.cpp + ${CMAKE_CURRENT_LIST_DIR}/RiaProjectBackupTools.cpp ) list(APPEND CODE_SOURCE_FILES ${SOURCE_GROUP_SOURCE_FILES}) diff --git a/ApplicationLibCode/Application/Tools/RiaProjectBackupTools.cpp b/ApplicationLibCode/Application/Tools/RiaProjectBackupTools.cpp new file mode 100644 index 0000000000..7ca26035f8 --- /dev/null +++ b/ApplicationLibCode/Application/Tools/RiaProjectBackupTools.cpp @@ -0,0 +1,109 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 "RiaProjectBackupTools.h" +#include "RiaLogging.h" + +#include +#include +#include +#include +#include + +namespace RiaProjectBackupTools +{ + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool createTableIfNeeded() +{ + QSqlQuery query; + if ( !query.exec( "CREATE TABLE IF NOT EXISTS file_versions (" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "timestamp DATETIME," + "content TEXT)" ) ) + { + QString txt = "Error creating table:" + query.lastError().text(); + RiaLogging::error( txt ); + return false; + } + + return true; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool insertContent( const QString& content ) +{ + QSqlQuery query; + query.prepare( "INSERT INTO file_versions (timestamp, content) " + "VALUES (:timestamp, :content)" ); + query.bindValue( ":timestamp", QDateTime::currentDateTime() ); + query.bindValue( ":content", content ); + if ( !query.exec() ) + { + QString txt = "Error saving file content to database:" + query.lastError().text(); + RiaLogging::error( txt ); + return false; + } + + return true; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool appendTextToDatabase( const QString& databaseFilePath, const QString& content ) +{ + const QString databaseType = "QSQLITE"; + + if ( !QSqlDatabase::isDriverAvailable( databaseType ) ) + { + RiaLogging::error( "sqlite database is not available." ); + return false; + } + + // Try to open the SQLITE database + QSqlDatabase db = QSqlDatabase::database(); + if ( !db.isValid() || !db.open() ) + { + RiaLogging::info( "Adding database" ); + + // Add the SQLITE database, and it it required to do this once per session. The database will be available during the lifetime of + // the application, and can be accessed using QSqlDatabase::database() + db = QSqlDatabase::addDatabase( databaseType ); + } + if ( !db.open() ) + { + QString txt = "Error opening database:" + db.lastError().text(); + RiaLogging::error( txt ); + return false; + } + + // Set the file name for the database. The database will be created if it does not exist. + db.setDatabaseName( databaseFilePath ); + + if ( !createTableIfNeeded() ) return false; + if ( !insertContent( content ) ) return false; + + return true; +} + +} // namespace RiaProjectBackupTools diff --git a/ApplicationLibCode/Application/Tools/RiaProjectBackupTools.h b/ApplicationLibCode/Application/Tools/RiaProjectBackupTools.h new file mode 100644 index 0000000000..a265702e12 --- /dev/null +++ b/ApplicationLibCode/Application/Tools/RiaProjectBackupTools.h @@ -0,0 +1,29 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 + +//================================================================================================== +// +//================================================================================================== +namespace RiaProjectBackupTools +{ +bool appendTextToDatabase( const QString& databaseFilePath, const QString& content ); +} diff --git a/ApplicationLibCode/CMakeLists.txt b/ApplicationLibCode/CMakeLists.txt index e88e49300d..75cfd28df2 100644 --- a/ApplicationLibCode/CMakeLists.txt +++ b/ApplicationLibCode/CMakeLists.txt @@ -37,6 +37,7 @@ if(Qt5Core_FOUND) Concurrent PrintSupport Svg + Sql OPTIONAL_COMPONENTS Charts ) set(QT_LIBRARIES @@ -49,6 +50,7 @@ if(Qt5Core_FOUND) Qt5::Concurrent Qt5::PrintSupport Qt5::Svg + Qt5::Sql ) if(Qt5Charts_FOUND) list(APPEND QT_LIBRARIES Qt5::Charts) diff --git a/ApplicationLibCode/ProjectDataModel/RimProject.cpp b/ApplicationLibCode/ProjectDataModel/RimProject.cpp index 4494e782aa..4300f3714c 100644 --- a/ApplicationLibCode/ProjectDataModel/RimProject.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimProject.cpp @@ -24,6 +24,8 @@ #include "RiaFieldHandleTools.h" #include "RiaFilePathTools.h" #include "RiaGuiApplication.h" +#include "RiaPreferences.h" +#include "RiaProjectBackupTools.h" #include "RiaProjectFileVersionTools.h" #include "RiaTextStringTools.h" #include "RiaVersionInfo.h" @@ -405,10 +407,22 @@ RimMainPlotCollection* RimProject::mainPlotCollection() const bool RimProject::writeProjectFile() { transferPathsToGlobalPathList(); - bool couldOpenFile = writeFile(); + + QFile xmlFile( fileName ); + if ( !xmlFile.open( QIODevice::WriteOnly | QIODevice::Text ) ) return false; + + QString content = documentAsString(); + xmlFile.write( content.toUtf8() ); + + if ( RiaPreferences::current()->storeBackupOfProjectFiles() ) + { + QString backupFilename = fileName + "db"; + RiaProjectBackupTools::appendTextToDatabase( backupFilename, content ); + } + distributePathsFromGlobalPathList(); - return couldOpenFile; + return true; } //-------------------------------------------------------------------------------------------------- diff --git a/Fwk/AppFwk/cafProjectDataModel/cafPdmDocument.cpp b/Fwk/AppFwk/cafProjectDataModel/cafPdmDocument.cpp index b7bcf264b0..8fe5ecadc8 100644 --- a/Fwk/AppFwk/cafProjectDataModel/cafPdmDocument.cpp +++ b/Fwk/AppFwk/cafProjectDataModel/cafPdmDocument.cpp @@ -83,7 +83,7 @@ void PdmDocument::readFile( QIODevice* xmlFile ) } } - // Ask all objects to initialize and set up internal datastructure and pointers + // Ask all objects to initialize and set up internal data structures and pointers // after everything is read from file resolveReferencesRecursively(); @@ -113,16 +113,22 @@ void PdmDocument::writeFile( QIODevice* xmlFile ) setupBeforeSaveRecursively(); QXmlStreamWriter xmlStream( xmlFile ); - xmlStream.setAutoFormatting( true ); + writeDocumentToXmlStream( xmlStream ); +} - xmlStream.writeStartDocument(); - QString className = classKeyword(); +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString PdmDocument::documentAsString() +{ + // Ask all objects to make them ready to write themselves to file + setupBeforeSaveRecursively(); - xmlStream.writeStartElement( "", className ); - writeFields( xmlStream ); - xmlStream.writeEndElement(); + QString content; + QXmlStreamWriter xmlStream( &content ); + writeDocumentToXmlStream( xmlStream ); - xmlStream.writeEndDocument(); + return content; } //-------------------------------------------------------------------------------------------------- @@ -162,4 +168,21 @@ void PdmDocument::beforeInitAfterRead() { } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void PdmDocument::writeDocumentToXmlStream( QXmlStreamWriter& xmlStream ) +{ + xmlStream.setAutoFormatting( true ); + + xmlStream.writeStartDocument(); + QString className = classKeyword(); + + xmlStream.writeStartElement( "", className ); + writeFields( xmlStream ); + xmlStream.writeEndElement(); + + xmlStream.writeEndDocument(); +} + } // End of namespace caf diff --git a/Fwk/AppFwk/cafProjectDataModel/cafPdmDocument.h b/Fwk/AppFwk/cafProjectDataModel/cafPdmDocument.h index 21164263a2..6451c43c20 100644 --- a/Fwk/AppFwk/cafProjectDataModel/cafPdmDocument.h +++ b/Fwk/AppFwk/cafProjectDataModel/cafPdmDocument.h @@ -39,6 +39,8 @@ #include "cafPdmField.h" #include "cafPdmObject.h" +class QXmlStreamWriter; + namespace caf { //================================================================================================== @@ -57,6 +59,8 @@ public: void readFile(); bool writeFile(); + QString documentAsString(); + void readFile( QIODevice* device ); void writeFile( QIODevice* device ); @@ -64,6 +68,9 @@ public: protected: virtual void beforeInitAfterRead(); + +private: + void writeDocumentToXmlStream( QXmlStreamWriter& xmlStream ); }; } // End of namespace caf