diff --git a/ApplicationLibCode/Application/Tools/RiaProjectBackupTools.cpp b/ApplicationLibCode/Application/Tools/RiaProjectBackupTools.cpp index 7ca26035f8..49844b8b57 100644 --- a/ApplicationLibCode/Application/Tools/RiaProjectBackupTools.cpp +++ b/ApplicationLibCode/Application/Tools/RiaProjectBackupTools.cpp @@ -66,11 +66,40 @@ bool insertContent( const QString& content ) return true; } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool deleteOldRecords( int maximumRecordCount ) +{ + QSqlQuery countQuery( "SELECT COUNT(*) FROM file_versions" ); + if ( !countQuery.exec() || !countQuery.next() ) + { + RiaLogging::error( "Error counting records: " + countQuery.lastError().text() ); + return false; + } + + int count = countQuery.value( 0 ).toInt(); + int recordsToDelete = count - maximumRecordCount; + if ( recordsToDelete <= 0 ) return true; + + QSqlQuery query; + query.prepare( "DELETE FROM file_versions WHERE id IN (SELECT id FROM file_versions ORDER BY timestamp ASC LIMIT :limit)" ); + query.bindValue( ":limit", recordsToDelete ); + + if ( !query.exec() ) + { + QString txt = "Error deleting old records:" + query.lastError().text(); + RiaLogging::error( txt ); + return false; + } + + return true; +} //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -bool appendTextToDatabase( const QString& databaseFilePath, const QString& content ) +bool appendTextToDatabase( const QString& databaseFilePath, int maximumRecordCount, const QString& content ) { const QString databaseType = "QSQLITE"; @@ -101,6 +130,8 @@ bool appendTextToDatabase( const QString& databaseFilePath, const QString& conte db.setDatabaseName( databaseFilePath ); if ( !createTableIfNeeded() ) return false; + if ( !deleteOldRecords( maximumRecordCount ) ) return false; + if ( !insertContent( content ) ) return false; return true; diff --git a/ApplicationLibCode/Application/Tools/RiaProjectBackupTools.h b/ApplicationLibCode/Application/Tools/RiaProjectBackupTools.h index a265702e12..658bb6b06b 100644 --- a/ApplicationLibCode/Application/Tools/RiaProjectBackupTools.h +++ b/ApplicationLibCode/Application/Tools/RiaProjectBackupTools.h @@ -25,5 +25,5 @@ //================================================================================================== namespace RiaProjectBackupTools { -bool appendTextToDatabase( const QString& databaseFilePath, const QString& content ); -} +bool appendTextToDatabase( const QString& databaseFilePath, int maximumRecordCount, const QString& content ); +} // namespace RiaProjectBackupTools diff --git a/ApplicationLibCode/ProjectDataModel/RimProject.cpp b/ApplicationLibCode/ProjectDataModel/RimProject.cpp index 4210ed99f0..99ff1d34f5 100644 --- a/ApplicationLibCode/ProjectDataModel/RimProject.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimProject.cpp @@ -416,8 +416,9 @@ bool RimProject::writeProjectFile() if ( RiaPreferences::current()->storeBackupOfProjectFiles() ) { - QString backupFilename = fileName + "db"; - RiaProjectBackupTools::appendTextToDatabase( backupFilename, content ); + QString backupFilename = fileName + "db"; + const int maximumRecordCount = 50; + RiaProjectBackupTools::appendTextToDatabase( backupFilename, maximumRecordCount, content ); } distributePathsFromGlobalPathList(); diff --git a/CMakeLists.txt b/CMakeLists.txt index b0010b31b2..80323bde92 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -989,6 +989,11 @@ else(OCTAVE_MKOCTFILE) ) endif(OCTAVE_MKOCTFILE) +add_subdirectory(ThirdParty/extract-projectfile-versions) +install(TARGETS extract-projectfile-versions + DESTINATION ${RESINSIGHT_INSTALL_FOLDER} +) + # ############################################################################## # Visual Studio : Create the ruleset file to be used by Static Code Analysis # https://stackoverflow.com/questions/75031903/how-to-enable-static-analysis-with-custom-ruleset-in-msvc-via-cmakelists-txt diff --git a/ThirdParty/extract-projectfile-versions/.clang-format b/ThirdParty/extract-projectfile-versions/.clang-format new file mode 100644 index 0000000000..241093fc3e --- /dev/null +++ b/ThirdParty/extract-projectfile-versions/.clang-format @@ -0,0 +1,78 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: true +AlignEscapedNewlinesLeft: true +AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: true +AllowShortFunctionsOnASingleLine: InlineOnly +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: false +BinPackParameters: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Allman +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: true +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 140 +CommentPragmas: "^ IWYU pragma:" +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ForEachMacros: [foreach, Q_FOREACH, BOOST_FOREACH] +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|isl|json)/)' + Priority: 3 + - Regex: ".*" + Priority: 1 +IncludeIsMainRegex: "$" +IndentCaseLabels: true +IndentWidth: 4 +IndentWrappedFunctionNames: true +JavaScriptQuotes: Leave +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: "" +MacroBlockEnd: "" +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: Inner +PenaltyBreakAssignment: 13 +PenaltyBreakBeforeFirstCallParameter: 10000 +PenaltyBreakComment: 20 +PenaltyBreakFirstLessLess: 12 +PenaltyBreakString: 100 +PenaltyExcessCharacter: 5 +PenaltyReturnTypeOnItsOwnLine: 30 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: true +SpacesInSquareBrackets: false +Standard: c++20 +TabWidth: 4 +UseTab: Never diff --git a/ThirdParty/extract-projectfile-versions/CMakeLists.txt b/ThirdParty/extract-projectfile-versions/CMakeLists.txt new file mode 100644 index 0000000000..9c21b24629 --- /dev/null +++ b/ThirdParty/extract-projectfile-versions/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.5) +project(extract-projectfile-versions) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +find_package(Qt5 COMPONENTS Core Sql REQUIRED) + +add_executable(extract-projectfile-versions main.cpp) + +target_link_libraries(extract-projectfile-versions Qt5::Core Qt5::Sql) + +if(MSVC) + add_custom_command( + TARGET extract-projectfile-versions + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy $ + $ + COMMAND_EXPAND_LISTS + ) +endif(MSVC) diff --git a/ThirdParty/extract-projectfile-versions/main.cpp b/ThirdParty/extract-projectfile-versions/main.cpp new file mode 100644 index 0000000000..88d5b56ed5 --- /dev/null +++ b/ThirdParty/extract-projectfile-versions/main.cpp @@ -0,0 +1,135 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector> getAllContent() +{ + std::vector> content; + + QSqlQuery query; + + query.prepare( "SELECT timestamp, content FROM file_versions" ); + if ( !query.exec() ) + { + qDebug() << "Error retrieving content:" << query.lastError().text(); + return content; + } + + while ( query.next() ) + { + QDateTime timestamp = query.value( 0 ).toDateTime(); + QString projectContent = query.value( 1 ).toString(); + content.push_back( std::make_pair( timestamp, projectContent ) ); + } + + return content; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void extractVersionsToFolder( const QString& destinationFolder ) +{ + auto allContent = getAllContent(); + + for ( const auto& [timestamp, content] : allContent ) + { + const QString dateFormat = "yyyy-MM-dd_hh-mm-ss"; + QString filename = destinationFolder + "/" + timestamp.toString( dateFormat ) + ".rsp"; + QFile file( filename ); + if ( !file.open( QIODevice::WriteOnly | QIODevice::Text ) ) + { + qCritical() << "Error opening file for writing:" << filename; + return; + } + + QTextStream out( &file ); + out << content; + file.close(); + } +} + +int main( int argc, char* argv[] ) +{ + QCoreApplication app( argc, argv ); + QCoreApplication::setApplicationName( "extract-projectfile-versions" ); + QCoreApplication::setApplicationVersion( "1.0" ); + + QCommandLineParser parser; + parser.setApplicationDescription( + "extract-projectfile-versions is used to restore previous revisions of a ResInsight project from a project file database." ); + parser.addHelpOption(); + parser.addVersionOption(); + + parser.addPositionalArgument( "file", "ResInsight project file database (*.rspdb)" ); + parser.addPositionalArgument( "outputfolder", "Output folder all project files" ); + + parser.process( app ); + + const QStringList args = parser.positionalArguments(); + if ( args.size() != 2 ) + { + qCritical() << "Failed to detect two input arguments."; + parser.showHelp( 1 ); + return 1; + } + + QString databaseFilePath = args.front(); + if ( !QFile::exists( databaseFilePath ) ) + { + qCritical() << "Database file does not exist:" << databaseFilePath; + return 1; + } + + QString destinationDir = args[1]; + QDir dir; + if ( !dir.mkpath( destinationDir ) ) + { + qCritical() << "Not able to create destination folder : " << destinationDir; + return 1; + } + + const QString databaseType = "QSQLITE"; + + if ( !QSqlDatabase::isDriverAvailable( databaseType ) ) + { + qInfo() << "sqlite database is not available."; + return 1; + } + + // Try to open the SQLITE database + QSqlDatabase db = QSqlDatabase::database(); + if ( !db.isValid() || !db.open() ) + { + qInfo() << "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 ); + } + + QFileInfo fileInfo( databaseFilePath ); + auto dbPath = fileInfo.absoluteFilePath(); + db.setDatabaseName( dbPath ); + + if ( !db.open() ) + { + QString txt = "Error opening database:" + db.lastError().text(); + qCritical() << txt; + return 1; + } + + extractVersionsToFolder( destinationDir ); + + return 1; +}