File indexing completed on 2025-02-02 05:17:48
0001 /* 0002 * SPDX-FileCopyrightText: 2011, 2012 Ivan Cukic <ivan.cukic(at)kde.org> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 // Self 0008 #include "Database.h" 0009 #include <kactivities-features.h> 0010 0011 // Qt 0012 #include <QDir> 0013 #include <QStandardPaths> 0014 0015 // Utils 0016 #include <utils/d_ptr_implementation.h> 0017 #include <utils/qsqlquery_iterator.h> 0018 0019 // System 0020 #include <array> 0021 #include <cmath> 0022 #include <memory> 0023 0024 // Local 0025 #include "DebugResources.h" 0026 #include "Utils.h" 0027 0028 #include <common/database/schema/ResourcesDatabaseSchema.h> 0029 0030 class ResourcesDatabaseInitializer::Private 0031 { 0032 public: 0033 Common::Database::Ptr database; 0034 }; 0035 0036 Common::Database::Ptr resourcesDatabase() 0037 { 0038 static ResourcesDatabaseInitializer instance; 0039 return instance.d->database; 0040 } 0041 0042 void ResourcesDatabaseInitializer::initDatabase(bool retryOnFail) 0043 { 0044 // 0045 // There are three situations we want to handle: 0046 // 1. The database can not be opened at all. 0047 // This means that the current database files have 0048 // been corrupted and that we need to replace them 0049 // with the last working backup. 0050 // 2. The database was opened, but an error appeared 0051 // somewhere at runtime. 0052 // 3. The database was successfully opened and no errors 0053 // appeared during runtime. 0054 // 0055 // To achieve this, we will have three locations for 0056 // database files: 0057 // 0058 // 1. `resources` - the current database files 0059 // 2. `resources-test-backup` - at each KAMD start, 0060 // we copy the current database files here. 0061 // If an error appears during execution, the files 0062 // will be removed and the error will be added to 0063 // the log file `resources/errors.log` 0064 // 3. `resources-working-backup` - on each KAMD start, 0065 // if there are files in `resources-test-backup` 0066 // (meaning no error appeared at runtime), they 0067 // will be copied to `resources-working-backup`. 0068 // 0069 // This means that the `working` backup will be a bit 0070 // older, but it will be the last database that produced 0071 // no errors at runtime. 0072 // 0073 0074 const QString databaseDirectoryPath = 0075 QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kactivitymanagerd/resources/"); 0076 0077 const QString databaseTestBackupDirectoryPath = 0078 QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kactivitymanagerd/resources/test-backup/"); 0079 0080 const QString databaseWorkingBackupDirectoryPath = 0081 QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kactivitymanagerd/resources/working-backup/"); 0082 0083 static const std::array<QString, 3> databaseFiles{"database", "database-wal", "database-shm"}; 0084 0085 { 0086 QDir dir; 0087 dir.mkpath(databaseDirectoryPath); 0088 dir.mkpath(databaseTestBackupDirectoryPath); 0089 dir.mkpath(databaseWorkingBackupDirectoryPath); 0090 0091 if (!dir.exists(databaseDirectoryPath) || !dir.exists(databaseTestBackupDirectoryPath) || !dir.exists(databaseWorkingBackupDirectoryPath)) { 0092 qCWarning(KAMD_LOG_RESOURCES) << "Database directory can not be created!"; 0093 return; 0094 } 0095 } 0096 0097 const QDir databaseDirectory(databaseDirectoryPath); 0098 const QDir databaseTestBackupDirectory(databaseTestBackupDirectoryPath); 0099 const QDir databaseWorkingBackupDirectory(databaseWorkingBackupDirectoryPath); 0100 0101 auto removeDatabaseFiles = [](const QDir &dir) { 0102 return std::all_of(databaseFiles.begin(), databaseFiles.cend(), [&dir](const QString &fileName) { 0103 const auto filePath = dir.filePath(fileName); 0104 return !QFile::exists(filePath) || QFile::remove(filePath); 0105 }); 0106 }; 0107 0108 auto copyDatabaseFiles = [removeDatabaseFiles](const QDir &fromDir, const QDir &toDir) { 0109 return removeDatabaseFiles(toDir) && std::all_of(databaseFiles.begin(), databaseFiles.cend(), [&fromDir, &toDir](const QString &fileName) { 0110 const auto fromFilePath = fromDir.filePath(fileName); 0111 const auto toFilePath = toDir.filePath(fileName); 0112 return QFile::copy(fromFilePath, toFilePath); 0113 }); 0114 }; 0115 0116 auto databaseFilesExistIn = [](const QDir &dir) { 0117 return dir.exists() && std::all_of(databaseFiles.begin(), databaseFiles.cend(), [&dir](const QString &fileName) { 0118 const auto filePath = dir.filePath(fileName); 0119 return QFile::exists(filePath); 0120 }); 0121 }; 0122 0123 // First, let's move the files from `resources-test-backup` to 0124 // `resources-working-backup` (if they exist) 0125 if (databaseFilesExistIn(databaseTestBackupDirectory)) { 0126 qCDebug(KAMD_LOG_RESOURCES) << "Marking the test backup as working..."; 0127 if (copyDatabaseFiles(databaseTestBackupDirectory, databaseWorkingBackupDirectory)) { 0128 removeDatabaseFiles(databaseTestBackupDirectory); 0129 } else { 0130 qCWarning(KAMD_LOG_RESOURCES) << "Marking the test backup as working failed!"; 0131 removeDatabaseFiles(databaseWorkingBackupDirectory); 0132 } 0133 } 0134 0135 // Next, copy the current database files to `resources-test-backup` 0136 if (databaseFilesExistIn(databaseDirectory)) { 0137 qCDebug(KAMD_LOG_RESOURCES) << "Creating the backup of the current database..."; 0138 if (!copyDatabaseFiles(databaseDirectory, databaseTestBackupDirectory)) { 0139 qCWarning(KAMD_LOG_RESOURCES) << "Creating the backup of the current database failed!"; 0140 removeDatabaseFiles(databaseTestBackupDirectory); 0141 } 0142 } 0143 0144 // Now we can try to open the database 0145 d->database = Common::Database::instance(Common::Database::ResourcesDatabase, Common::Database::ReadWrite); 0146 0147 if (d->database) { 0148 qCDebug(KAMD_LOG_RESOURCES) << "Database opened successfully"; 0149 QObject::connect(d->database.get(), &Common::Database::error, d->database.get(), [databaseTestBackupDirectory, removeDatabaseFiles](const QSqlError &error) { 0150 const QString errorLog = 0151 QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kactivitymanagerd/resources/errors.log"); 0152 QFile file(errorLog); 0153 if (file.open(QIODevice::Append)) { 0154 QTextStream out(&file); 0155 out << QDateTime::currentDateTime().toString(Qt::ISODate) << " error: " << error.text() << "\n"; 0156 } else { 0157 qCWarning(KAMD_LOG_RESOURCES) << QDateTime::currentDateTime().toString(Qt::ISODate) << " error: " << error.text(); 0158 } 0159 0160 removeDatabaseFiles(databaseTestBackupDirectory); 0161 }); 0162 Common::ResourcesDatabaseSchema::initSchema(*d->database); 0163 0164 } else { 0165 // The current database can not be opened, delete the 0166 // backup we just created 0167 removeDatabaseFiles(databaseTestBackupDirectory); 0168 0169 if (databaseFilesExistIn(databaseWorkingBackupDirectoryPath)) { 0170 qCWarning(KAMD_LOG_RESOURCES) << "The database seems to be corrupted, trying to load the latest working version"; 0171 0172 const auto success = copyDatabaseFiles(databaseWorkingBackupDirectory, databaseDirectory); 0173 0174 if (success && retryOnFail) { 0175 // Avoid infinite recursion 0176 initDatabase(false); 0177 } 0178 0179 } else { 0180 qCWarning(KAMD_LOG_RESOURCES) << "The database might be corrupted and there is no working backup"; 0181 } 0182 } 0183 } 0184 0185 ResourcesDatabaseInitializer::ResourcesDatabaseInitializer() 0186 { 0187 initDatabase(true); 0188 } 0189 0190 ResourcesDatabaseInitializer::~ResourcesDatabaseInitializer() 0191 { 0192 }