Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
authorPatrick Klein <42714034+libklein@users.noreply.github.com>2021-11-08 01:41:17 +0300
committerGitHub <noreply@github.com>2021-11-08 01:41:17 +0300
commit84ff6a13f9cd11214396deb31d54955abbcd7b0b (patch)
tree0fa0d84475f9f79c4a8ce12d8582715b0f084940 /src/core
parent8d7e4918109b1e40660e2988e79e1cf15e899580 (diff)
Allow specifing database backup paths. (#7035)
- Default backupFilePath is '{DB_FILENAME}.old.kdbx' to conform to existing standards - Implement backupPathPattern tests. - Show tooltip on how to format database backup location text field.
Diffstat (limited to 'src/core')
-rw-r--r--src/core/Config.cpp6
-rw-r--r--src/core/Config.h2
-rw-r--r--src/core/Database.cpp56
-rw-r--r--src/core/Database.h13
-rw-r--r--src/core/Tools.cpp36
-rw-r--r--src/core/Tools.h2
6 files changed, 82 insertions, 33 deletions
diff --git a/src/core/Config.cpp b/src/core/Config.cpp
index 54fc59c81..a6d179504 100644
--- a/src/core/Config.cpp
+++ b/src/core/Config.cpp
@@ -61,6 +61,7 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
{Config::AutoSaveOnExit,{QS("AutoSaveOnExit"), Roaming, true}},
{Config::AutoSaveNonDataChanges,{QS("AutoSaveNonDataChanges"), Roaming, true}},
{Config::BackupBeforeSave,{QS("BackupBeforeSave"), Roaming, false}},
+ {Config::BackupFilePathPattern,{QS("BackupFilePathPattern"), Roaming, QString("{DB_FILENAME}.old.kdbx")}},
{Config::UseAtomicSaves,{QS("UseAtomicSaves"), Roaming, true}},
{Config::UseDirectWriteSaves,{QS("UseDirectWriteSaves"), Local, false}},
{Config::SearchLimitGroup,{QS("SearchLimitGroup"), Roaming, false}},
@@ -229,6 +230,11 @@ QVariant Config::get(ConfigKey key)
return m_settings->value(cfg.name, defaultValue);
}
+QVariant Config::getDefault(Config::ConfigKey key)
+{
+ return configStrings[key].defaultValue;
+}
+
bool Config::hasAccessError()
{
return m_settings->status() & QSettings::AccessError;
diff --git a/src/core/Config.h b/src/core/Config.h
index 8e5c14e35..1be8699ca 100644
--- a/src/core/Config.h
+++ b/src/core/Config.h
@@ -43,6 +43,7 @@ public:
AutoSaveOnExit,
AutoSaveNonDataChanges,
BackupBeforeSave,
+ BackupFilePathPattern,
UseAtomicSaves,
UseDirectWriteSaves,
SearchLimitGroup,
@@ -195,6 +196,7 @@ public:
~Config() override;
QVariant get(ConfigKey key);
+ QVariant getDefault(ConfigKey key);
QString getFileName();
void set(ConfigKey key, const QVariant& value);
void remove(ConfigKey key);
diff --git a/src/core/Database.cpp b/src/core/Database.cpp
index b30c3623a..5738a3b0a 100644
--- a/src/core/Database.cpp
+++ b/src/core/Database.cpp
@@ -178,10 +178,10 @@ bool Database::isSaving()
*
* @param error error message in case of failure
* @param atomic Use atomic file transactions
- * @param backup Backup the existing database file, if exists
+ * @param backupFilePath Absolute file path to write the backup file to. Pass an empty QString to disable backup.
* @return true on success
*/
-bool Database::save(SaveAction action, bool backup, QString* error)
+bool Database::save(SaveAction action, const QString& backupFilePath, QString* error)
{
Q_ASSERT(!m_data.filePath.isEmpty());
if (m_data.filePath.isEmpty()) {
@@ -191,7 +191,7 @@ bool Database::save(SaveAction action, bool backup, QString* error)
return false;
}
- return saveAs(m_data.filePath, action, backup, error);
+ return saveAs(m_data.filePath, action, backupFilePath, error);
}
/**
@@ -209,10 +209,11 @@ bool Database::save(SaveAction action, bool backup, QString* error)
* @param filePath Absolute path of the file to save
* @param error error message in case of failure
* @param atomic Use atomic file transactions
- * @param backup Backup the existing database file, if exists
+ * @param backupFilePath Absolute path to the location where the backup should be stored. Passing an empty string
+ * disables backup.
* @return true on success
*/
-bool Database::saveAs(const QString& filePath, SaveAction action, bool backup, QString* error)
+bool Database::saveAs(const QString& filePath, SaveAction action, const QString& backupFilePath, QString* error)
{
// Disallow overlapping save operations
if (isSaving()) {
@@ -260,7 +261,7 @@ bool Database::saveAs(const QString& filePath, SaveAction action, bool backup, Q
QFileInfo fileInfo(filePath);
auto realFilePath = fileInfo.exists() ? fileInfo.canonicalFilePath() : fileInfo.absoluteFilePath();
bool isNewFile = !QFile::exists(realFilePath);
- bool ok = AsyncTask::runAndWaitForFuture([&] { return performSave(realFilePath, action, backup, error); });
+ bool ok = AsyncTask::runAndWaitForFuture([&] { return performSave(realFilePath, action, backupFilePath, error); });
if (ok) {
markAsClean();
setFilePath(filePath);
@@ -276,10 +277,10 @@ bool Database::saveAs(const QString& filePath, SaveAction action, bool backup, Q
return ok;
}
-bool Database::performSave(const QString& filePath, SaveAction action, bool backup, QString* error)
+bool Database::performSave(const QString& filePath, SaveAction action, const QString& backupFilePath, QString* error)
{
- if (backup) {
- backupDatabase(filePath);
+ if (!backupFilePath.isNull()) {
+ backupDatabase(filePath, backupFilePath);
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
@@ -337,7 +338,7 @@ bool Database::performSave(const QString& filePath, SaveAction action, bool back
tempFile.setFileTime(createTime, QFile::FileBirthTime);
#endif
return true;
- } else if (!backup || !restoreDatabase(filePath)) {
+ } else if (backupFilePath.isEmpty() || !restoreDatabase(filePath, backupFilePath)) {
// Failed to copy new database in place, and
// failed to restore from backup or backups disabled
tempFile.setAutoRemove(false);
@@ -485,23 +486,26 @@ void Database::releaseData()
}
/**
- * Remove the old backup and replace it with a new one
- * backups are named <filename>.old.<extension>
+ * Remove the old backup and replace it with a new one. Backup name is taken from destinationFilePath.
+ * Non-existing parent directories will be created automatically.
*
* @param filePath Path to the file to backup
+ * @param destinationFilePath Path to the backup destination file
* @return true on success
*/
-bool Database::backupDatabase(const QString& filePath)
+bool Database::backupDatabase(const QString& filePath, const QString& destinationFilePath)
{
- static auto re = QRegularExpression("(\\.[^.]+)$");
-
- auto match = re.match(filePath);
- auto backupFilePath = filePath;
+ // Ensure that the path to write to actually exists
+ auto parentDirectory = QFileInfo(destinationFilePath).absoluteDir();
+ if (!parentDirectory.exists()) {
+ if (!QDir().mkpath(parentDirectory.absolutePath())) {
+ return false;
+ }
+ }
auto perms = QFile::permissions(filePath);
- backupFilePath = backupFilePath.replace(re, "") + ".old" + match.captured(1);
- QFile::remove(backupFilePath);
- bool res = QFile::copy(filePath, backupFilePath);
- QFile::setPermissions(backupFilePath, perms);
+ QFile::remove(destinationFilePath);
+ bool res = QFile::copy(filePath, destinationFilePath);
+ QFile::setPermissions(destinationFilePath, perms);
return res;
}
@@ -513,17 +517,13 @@ bool Database::backupDatabase(const QString& filePath)
* @param filePath Path to the file to restore
* @return true on success
*/
-bool Database::restoreDatabase(const QString& filePath)
+bool Database::restoreDatabase(const QString& filePath, const QString& fromBackupFilePath)
{
- static auto re = QRegularExpression("^(.*?)(\\.[^.]+)?$");
-
- auto match = re.match(filePath);
auto perms = QFile::permissions(filePath);
- auto backupFilePath = match.captured(1) + ".old" + match.captured(2);
// Only try to restore if the backup file actually exists
- if (QFile::exists(backupFilePath)) {
+ if (QFile::exists(fromBackupFilePath)) {
QFile::remove(filePath);
- if (QFile::copy(backupFilePath, filePath)) {
+ if (QFile::copy(fromBackupFilePath, filePath)) {
return QFile::setPermissions(filePath, perms);
}
}
diff --git a/src/core/Database.h b/src/core/Database.h
index 24bb79db2..c42025f85 100644
--- a/src/core/Database.h
+++ b/src/core/Database.h
@@ -79,8 +79,11 @@ public:
QSharedPointer<const CompositeKey> key,
QString* error = nullptr,
bool readOnly = false);
- bool save(SaveAction action = Atomic, bool backup = false, QString* error = nullptr);
- bool saveAs(const QString& filePath, SaveAction action = Atomic, bool backup = false, QString* error = nullptr);
+ bool save(SaveAction action = Atomic, const QString& backupFilePath = QString(), QString* error = nullptr);
+ bool saveAs(const QString& filePath,
+ SaveAction action = Atomic,
+ const QString& backupFilePath = QString(),
+ QString* error = nullptr);
bool extract(QByteArray&, QString* error = nullptr);
bool import(const QString& xmlExportPath, QString* error = nullptr);
@@ -203,9 +206,9 @@ private:
void createRecycleBin();
bool writeDatabase(QIODevice* device, QString* error = nullptr);
- bool backupDatabase(const QString& filePath);
- bool restoreDatabase(const QString& filePath);
- bool performSave(const QString& filePath, SaveAction flags, bool backup, QString* error);
+ bool backupDatabase(const QString& filePath, const QString& destinationFilePath);
+ bool restoreDatabase(const QString& filePath, const QString& fromBackupFilePath);
+ bool performSave(const QString& filePath, SaveAction flags, const QString& backupFilePath, QString* error);
void startModifiedTimer();
void stopModifiedTimer();
diff --git a/src/core/Tools.cpp b/src/core/Tools.cpp
index 58a38f433..7c6d52a59 100644
--- a/src/core/Tools.cpp
+++ b/src/core/Tools.cpp
@@ -22,7 +22,10 @@
#include "config-keepassx.h"
#include "git-info.h"
+#include "core/Clock.h"
+
#include <QElapsedTimer>
+#include <QFileInfo>
#include <QImageReader>
#include <QLocale>
#include <QMetaProperty>
@@ -376,4 +379,37 @@ namespace Tools
}
return result;
}
+
+ QString substituteBackupFilePath(QString pattern, const QString& databasePath)
+ {
+ // Fail if substitution fails
+ if (databasePath.isEmpty()) {
+ return {};
+ }
+
+ // Replace backup pattern
+ QFileInfo dbFileInfo(databasePath);
+ QString baseName = dbFileInfo.completeBaseName();
+
+ pattern.replace(QString("{DB_FILENAME}"), baseName);
+
+ auto re = QRegularExpression(R"(\{TIME(?::([^\\]*))?\})");
+ auto match = re.match(pattern);
+ while (match.hasMatch()) {
+ // Extract time format specifier
+ auto formatSpecifier = QString("dd_MM_yyyy_hh-mm-ss");
+ if (!match.captured(1).isEmpty()) {
+ formatSpecifier = match.captured(1);
+ }
+ auto replacement = Clock::currentDateTime().toString(formatSpecifier);
+ pattern.replace(match.capturedStart(), match.capturedLength(), replacement);
+ match = re.match(pattern);
+ }
+
+ // Replace escaped braces
+ pattern.replace("\\{", "{");
+ pattern.replace("\\}", "}");
+
+ return pattern;
+ }
} // namespace Tools
diff --git a/src/core/Tools.h b/src/core/Tools.h
index 8ebcef1c5..cf3b3593d 100644
--- a/src/core/Tools.h
+++ b/src/core/Tools.h
@@ -75,6 +75,8 @@ namespace Tools
}
QVariantMap qo2qvm(const QObject* object, const QStringList& ignoredProperties = {"objectName"});
+
+ QString substituteBackupFilePath(QString pattern, const QString& databasePath);
} // namespace Tools
#endif // KEEPASSX_TOOLS_H