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
diff options
context:
space:
mode:
-rw-r--r--src/core/Database.cpp74
-rw-r--r--src/core/Database.h5
-rw-r--r--src/core/FileWatcher.cpp107
-rw-r--r--src/core/FileWatcher.h23
-rw-r--r--src/gui/DatabaseTabWidget.cpp5
-rw-r--r--src/gui/DatabaseWidget.cpp41
-rw-r--r--src/gui/DatabaseWidget.h6
-rw-r--r--tests/CMakeLists.txt2
-rw-r--r--tests/TestDatabase.cpp57
-rw-r--r--tests/TestDatabase.h2
10 files changed, 204 insertions, 118 deletions
diff --git a/src/core/Database.cpp b/src/core/Database.cpp
index 03d2e96ee..b818a9034 100644
--- a/src/core/Database.cpp
+++ b/src/core/Database.cpp
@@ -19,6 +19,7 @@
#include "Database.h"
#include "core/Clock.h"
+#include "core/FileWatcher.h"
#include "core/Group.h"
#include "core/Merger.h"
#include "core/Metadata.h"
@@ -42,6 +43,7 @@ Database::Database()
, m_data()
, m_rootGroup(nullptr)
, m_timer(new QTimer(this))
+ , m_fileWatcher(new FileWatcher(this))
, m_emitModified(false)
, m_uuid(QUuid::createUuid())
{
@@ -54,7 +56,9 @@ Database::Database()
connect(m_metadata, SIGNAL(metadataModified()), this, SLOT(markAsModified()));
connect(m_timer, SIGNAL(timeout()), SIGNAL(databaseModified()));
+ connect(this, SIGNAL(databaseOpened()), SLOT(updateCommonUsernames()));
connect(this, SIGNAL(databaseSaved()), SLOT(updateCommonUsernames()));
+ connect(m_fileWatcher, SIGNAL(fileChanged()), SIGNAL(databaseFileChanged()));
m_modified = false;
m_emitModified = true;
@@ -116,6 +120,7 @@ bool Database::open(const QString& filePath, QSharedPointer<const CompositeKey>
emit databaseDiscarded();
}
+ m_initialized = false;
setEmitModified(false);
QFile dbFile(filePath);
@@ -138,8 +143,7 @@ bool Database::open(const QString& filePath, QSharedPointer<const CompositeKey>
}
KeePass2Reader reader;
- bool ok = reader.readDatabase(&dbFile, std::move(key), this);
- if (reader.hasError()) {
+ if (!reader.readDatabase(&dbFile, std::move(key), this)) {
if (error) {
*error = tr("Error while reading the database: %1").arg(reader.errorString());
}
@@ -150,22 +154,23 @@ bool Database::open(const QString& filePath, QSharedPointer<const CompositeKey>
setFilePath(filePath);
dbFile.close();
- updateCommonUsernames();
-
- setInitialized(ok);
markAsClean();
+ m_initialized = true;
+ emit databaseOpened();
+ m_fileWatcher->start(canonicalFilePath());
setEmitModified(true);
- return ok;
+
+ return true;
}
/**
* Save the database to the current file path. It is an error to call this function
* if no file path has been defined.
*
+ * @param error error message in case of failure
* @param atomic Use atomic file transactions
* @param backup Backup the existing database file, if exists
- * @param error error message in case of failure
* @return true on success
*/
bool Database::save(QString* error, bool atomic, bool backup)
@@ -194,27 +199,52 @@ bool Database::save(QString* error, bool atomic, bool backup)
* wrong moment.
*
* @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 error error message in case of failure
* @return true on success
*/
bool Database::saveAs(const QString& filePath, QString* error, bool atomic, bool backup)
{
- // Disallow saving to the same file if read-only
- if (m_data.isReadOnly && filePath == m_data.filePath) {
- Q_ASSERT_X(false, "Database::saveAs", "Could not save, database file is read-only.");
- if (error) {
- *error = tr("Could not save, database file is read-only.");
+ if (filePath == m_data.filePath) {
+ // Disallow saving to the same file if read-only
+ if (m_data.isReadOnly) {
+ Q_ASSERT_X(false, "Database::saveAs", "Could not save, database file is read-only.");
+ if (error) {
+ *error = tr("Could not save, database file is read-only.");
+ }
+ return false;
+ }
+
+ // Fail-safe check to make sure we don't overwrite underlying file changes
+ // that have not yet triggered a file reload/merge operation.
+ if (!m_fileWatcher->hasSameFileChecksum()) {
+ if (error) {
+ *error = tr("Database file has unmerged changes.");
+ }
+ return false;
}
- return false;
}
// Clear read-only flag
setReadOnly(false);
+ m_fileWatcher->stop();
auto& canonicalFilePath = QFileInfo::exists(filePath) ? QFileInfo(filePath).canonicalFilePath() : filePath;
+ bool ok = performSave(canonicalFilePath, error, atomic, backup);
+ if (ok) {
+ setFilePath(filePath);
+ m_fileWatcher->start(canonicalFilePath);
+ } else {
+ // Saving failed, don't rewatch file since it does not represent our database
+ markAsModified();
+ }
+
+ return ok;
+}
+bool Database::performSave(const QString& filePath, QString* error, bool atomic, bool backup)
+{
if (atomic) {
QSaveFile saveFile(filePath);
if (saveFile.open(QIODevice::WriteOnly)) {
@@ -224,12 +254,11 @@ bool Database::saveAs(const QString& filePath, QString* error, bool atomic, bool
}
if (backup) {
- backupDatabase(canonicalFilePath);
+ backupDatabase(filePath);
}
if (saveFile.commit()) {
// successfully saved database file
- setFilePath(filePath);
return true;
}
}
@@ -248,28 +277,26 @@ bool Database::saveAs(const QString& filePath, QString* error, bool atomic, bool
tempFile.close(); // flush to disk
if (backup) {
- backupDatabase(canonicalFilePath);
+ backupDatabase(filePath);
}
// Delete the original db and move the temp file in place
- QFile::remove(canonicalFilePath);
+ QFile::remove(filePath);
// Note: call into the QFile rename instead of QTemporaryFile
// due to an undocumented difference in how the function handles
// errors. This prevents errors when saving across file systems.
- if (tempFile.QFile::rename(canonicalFilePath)) {
+ if (tempFile.QFile::rename(filePath)) {
// successfully saved the database
tempFile.setAutoRemove(false);
- setFilePath(filePath);
return true;
- } else if (!backup || !restoreDatabase(canonicalFilePath)) {
+ } else if (!backup || !restoreDatabase(filePath)) {
// Failed to copy new database in place, and
// failed to restore from backup or backups disabled
tempFile.setAutoRemove(false);
if (error) {
*error = tr("%1\nBackup database located at %2").arg(tempFile.errorString(), tempFile.fileName());
}
- markAsModified();
return false;
}
}
@@ -280,7 +307,6 @@ bool Database::saveAs(const QString& filePath, QString* error, bool atomic, bool
}
// Saving failed
- markAsModified();
return false;
}
@@ -490,6 +516,8 @@ void Database::setFilePath(const QString& filePath)
if (filePath != m_data.filePath) {
QString oldPath = m_data.filePath;
m_data.filePath = filePath;
+ // Don't watch for changes until the next open or save operation
+ m_fileWatcher->stop();
emit filePathChanged(oldPath, filePath);
}
}
diff --git a/src/core/Database.h b/src/core/Database.h
index ea3453daa..7f504cc55 100644
--- a/src/core/Database.h
+++ b/src/core/Database.h
@@ -32,6 +32,7 @@
class Entry;
enum class EntryReferenceType;
+class FileWatcher;
class Group;
class Metadata;
class QTimer;
@@ -144,9 +145,11 @@ signals:
void groupRemoved();
void groupAboutToMove(Group* group, Group* toGroup, int index);
void groupMoved();
+ void databaseOpened();
void databaseModified();
void databaseSaved();
void databaseDiscarded();
+ void databaseFileChanged();
private slots:
void startModifiedTimer();
@@ -177,12 +180,14 @@ private:
bool writeDatabase(QIODevice* device, QString* error = nullptr);
bool backupDatabase(const QString& filePath);
bool restoreDatabase(const QString& filePath);
+ bool performSave(const QString& filePath, QString* error, bool atomic, bool backup);
Metadata* const m_metadata;
DatabaseData m_data;
Group* m_rootGroup;
QList<DeletedObject> m_deletedObjects;
QPointer<QTimer> m_timer;
+ QPointer<FileWatcher> m_fileWatcher;
bool m_initialized = false;
bool m_modified = false;
bool m_emitModified;
diff --git a/src/core/FileWatcher.cpp b/src/core/FileWatcher.cpp
index ae7878191..1b39e597d 100644
--- a/src/core/FileWatcher.cpp
+++ b/src/core/FileWatcher.cpp
@@ -19,6 +19,7 @@
#include "FileWatcher.h"
#include "core/Clock.h"
+#include <QCryptographicHash>
#include <QFileInfo>
#ifdef Q_OS_LINUX
@@ -27,36 +28,23 @@
namespace
{
- const int FileChangeDelay = 500;
- const int TimerResolution = 100;
+ const int FileChangeDelay = 200;
} // namespace
-DelayingFileWatcher::DelayingFileWatcher(QObject* parent)
+FileWatcher::FileWatcher(QObject* parent)
: QObject(parent)
, m_ignoreFileChange(false)
{
- connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(onWatchedFileChanged()));
- connect(&m_fileUnblockTimer, SIGNAL(timeout()), this, SLOT(observeFileChanges()));
- connect(&m_fileChangeDelayTimer, SIGNAL(timeout()), this, SIGNAL(fileChanged()));
+ connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), SLOT(onWatchedFileChanged()));
+ connect(&m_fileChangeDelayTimer, SIGNAL(timeout()), SIGNAL(fileChanged()));
+ connect(&m_fileChecksumTimer, SIGNAL(timeout()), SLOT(checkFileChecksum()));
m_fileChangeDelayTimer.setSingleShot(true);
- m_fileUnblockTimer.setSingleShot(true);
+ m_fileIgnoreDelayTimer.setSingleShot(true);
}
-void DelayingFileWatcher::restart()
+void FileWatcher::start(const QString& filePath, int checksumInterval)
{
- m_fileWatcher.addPath(m_filePath);
-}
-
-void DelayingFileWatcher::stop()
-{
- m_fileWatcher.removePath(m_filePath);
-}
-
-void DelayingFileWatcher::start(const QString& filePath)
-{
- if (!m_filePath.isEmpty()) {
- m_fileWatcher.removePath(m_filePath);
- }
+ stop();
#if defined(Q_OS_LINUX)
struct statfs statfsBuf;
@@ -74,45 +62,80 @@ void DelayingFileWatcher::start(const QString& filePath)
#endif
m_fileWatcher.addPath(filePath);
+ m_filePath = filePath;
+ m_fileChecksum = calculateChecksum();
+ m_fileChecksumTimer.start(checksumInterval);
+ m_ignoreFileChange = false;
+}
- if (!filePath.isEmpty()) {
- m_filePath = filePath;
+void FileWatcher::stop()
+{
+ if (!m_filePath.isEmpty()) {
+ m_fileWatcher.removePath(m_filePath);
}
+ m_filePath.clear();
+ m_fileChecksum.clear();
+ m_fileChangeDelayTimer.stop();
}
-void DelayingFileWatcher::ignoreFileChanges()
+void FileWatcher::pause()
{
m_ignoreFileChange = true;
m_fileChangeDelayTimer.stop();
}
-void DelayingFileWatcher::observeFileChanges(bool delayed)
+void FileWatcher::resume()
{
- int timeout = 0;
- if (delayed) {
- timeout = FileChangeDelay;
- } else {
- m_ignoreFileChange = false;
- start(m_filePath);
- }
- if (timeout > 0 && !m_fileUnblockTimer.isActive()) {
- m_fileUnblockTimer.start(timeout);
+ m_ignoreFileChange = false;
+ // Add a short delay to start in the next event loop
+ if (!m_fileIgnoreDelayTimer.isActive()) {
+ m_fileIgnoreDelayTimer.start(0);
}
}
-void DelayingFileWatcher::onWatchedFileChanged()
+void FileWatcher::onWatchedFileChanged()
{
- if (m_ignoreFileChange) {
- // the client forcefully silenced us
+ // Don't notify if we are ignoring events or already started a notification chain
+ if (shouldIgnoreChanges()) {
return;
}
- if (m_fileChangeDelayTimer.isActive()) {
- // we are waiting to fire the delayed fileChanged event, so nothing
- // to do here
+
+ m_fileChecksum = calculateChecksum();
+ m_fileChangeDelayTimer.start(0);
+}
+
+bool FileWatcher::shouldIgnoreChanges()
+{
+ return m_filePath.isEmpty() || m_ignoreFileChange || m_fileIgnoreDelayTimer.isActive()
+ || m_fileChangeDelayTimer.isActive();
+}
+
+bool FileWatcher::hasSameFileChecksum()
+{
+ return calculateChecksum() == m_fileChecksum;
+}
+
+void FileWatcher::checkFileChecksum()
+{
+ if (shouldIgnoreChanges()) {
return;
}
- m_fileChangeDelayTimer.start(FileChangeDelay);
+ if (!hasSameFileChecksum()) {
+ onWatchedFileChanged();
+ }
+}
+
+QByteArray FileWatcher::calculateChecksum()
+{
+ QFile file(m_filePath);
+ if (file.open(QFile::ReadOnly)) {
+ QCryptographicHash hash(QCryptographicHash::Sha256);
+ if (hash.addData(&file)) {
+ return hash.result();
+ }
+ }
+ return {};
}
BulkFileWatcher::BulkFileWatcher(QObject* parent)
@@ -281,7 +304,7 @@ void BulkFileWatcher::observeFileChanges(bool delayed)
{
int timeout = 0;
if (delayed) {
- timeout = TimerResolution;
+ timeout = FileChangeDelay;
} else {
const QDateTime current = Clock::currentDateTimeUtc();
for (const QString& key : m_watchedFilesIgnored.keys()) {
diff --git a/src/core/FileWatcher.h b/src/core/FileWatcher.h
index f6953cf80..3793ae860 100644
--- a/src/core/FileWatcher.h
+++ b/src/core/FileWatcher.h
@@ -23,34 +23,39 @@
#include <QTimer>
#include <QVariant>
-class DelayingFileWatcher : public QObject
+class FileWatcher : public QObject
{
Q_OBJECT
public:
- explicit DelayingFileWatcher(QObject* parent = nullptr);
+ explicit FileWatcher(QObject* parent = nullptr);
- void blockAutoReload(bool block);
- void start(const QString& path);
-
- void restart();
+ void start(const QString& path, int checksumInterval = 1000);
void stop();
- void ignoreFileChanges();
+
+ bool hasSameFileChecksum();
signals:
void fileChanged();
public slots:
- void observeFileChanges(bool delayed = false);
+ void pause();
+ void resume();
private slots:
void onWatchedFileChanged();
+ void checkFileChecksum();
private:
+ QByteArray calculateChecksum();
+ bool shouldIgnoreChanges();
+
QString m_filePath;
QFileSystemWatcher m_fileWatcher;
+ QByteArray m_fileChecksum;
QTimer m_fileChangeDelayTimer;
- QTimer m_fileUnblockTimer;
+ QTimer m_fileIgnoreDelayTimer;
+ QTimer m_fileChecksumTimer;
bool m_ignoreFileChange;
};
diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp
index 8e7096a1c..4c8458e52 100644
--- a/src/gui/DatabaseTabWidget.cpp
+++ b/src/gui/DatabaseTabWidget.cpp
@@ -200,11 +200,14 @@ void DatabaseTabWidget::addDatabaseTab(DatabaseWidget* dbWidget, bool inBackgrou
setCurrentIndex(index);
}
- connect(dbWidget, SIGNAL(databaseFilePathChanged(QString, QString)), SLOT(updateTabName()));
connect(dbWidget,
SIGNAL(requestOpenDatabase(QString, bool, QString, QString)),
SLOT(addDatabaseTab(QString, bool, QString, QString)));
+ connect(dbWidget, SIGNAL(databaseFilePathChanged(QString, QString)), SLOT(updateTabName()));
connect(dbWidget, SIGNAL(closeRequest()), SLOT(closeDatabaseTabFromSender()));
+ connect(dbWidget,
+ SIGNAL(databaseReplaced(const QSharedPointer<Database>&, const QSharedPointer<Database>&)),
+ SLOT(updateTabName()));
connect(dbWidget, SIGNAL(databaseModified()), SLOT(updateTabName()));
connect(dbWidget, SIGNAL(databaseSaved()), SLOT(updateTabName()));
connect(dbWidget, SIGNAL(databaseUnlocked()), SLOT(updateTabName()));
diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp
index bc6d2b507..ef76bd82d 100644
--- a/src/gui/DatabaseWidget.cpp
+++ b/src/gui/DatabaseWidget.cpp
@@ -94,7 +94,6 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
, m_opVaultOpenWidget(new OpVaultOpenWidget(this))
, m_groupView(new GroupView(m_db.data(), m_mainSplitter))
, m_saveAttempts(0)
- , m_fileWatcher(new DelayingFileWatcher(this))
{
m_messageWidget->setHidden(true);
@@ -199,7 +198,6 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
connect(m_keepass1OpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
connect(m_opVaultOpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
connect(m_csvImportWizard, SIGNAL(importFinished(bool)), SLOT(csvImportFinished(bool)));
- connect(m_fileWatcher.data(), SIGNAL(fileChanged()), this, SLOT(reloadDatabaseFile()));
connect(this, SIGNAL(currentChanged(int)), SLOT(emitCurrentModeChanged()));
// clang-format on
@@ -895,6 +893,7 @@ void DatabaseWidget::connectDatabaseSignals()
connect(m_db.data(), SIGNAL(databaseModified()), SIGNAL(databaseModified()));
connect(m_db.data(), SIGNAL(databaseModified()), SLOT(onDatabaseModified()));
connect(m_db.data(), SIGNAL(databaseSaved()), SIGNAL(databaseSaved()));
+ connect(m_db.data(), SIGNAL(databaseFileChanged()), this, SLOT(reloadDatabaseFile()));
}
void DatabaseWidget::loadDatabase(bool accepted)
@@ -908,14 +907,12 @@ void DatabaseWidget::loadDatabase(bool accepted)
if (accepted) {
replaceDatabase(openWidget->database());
switchToMainView();
- m_fileWatcher->restart();
m_saveAttempts = 0;
emit databaseUnlocked();
if (config()->get("MinimizeAfterUnlock").toBool()) {
window()->showMinimized();
}
} else {
- m_fileWatcher->stop();
if (m_databaseOpenWidget->database()) {
m_databaseOpenWidget->database().reset();
}
@@ -1063,7 +1060,6 @@ void DatabaseWidget::switchToOpenDatabase()
void DatabaseWidget::switchToOpenDatabase(const QString& filePath)
{
- updateFilePath(filePath);
m_databaseOpenWidget->load(filePath);
setCurrentWidget(m_databaseOpenWidget);
}
@@ -1091,14 +1087,12 @@ void DatabaseWidget::csvImportFinished(bool accepted)
void DatabaseWidget::switchToImportKeepass1(const QString& filePath)
{
- updateFilePath(filePath);
m_keepass1OpenWidget->load(filePath);
setCurrentWidget(m_keepass1OpenWidget);
}
void DatabaseWidget::switchToImportOpVault(const QString& fileName)
{
- updateFilePath(fileName);
m_opVaultOpenWidget->load(fileName);
setCurrentWidget(m_opVaultOpenWidget);
}
@@ -1384,21 +1378,6 @@ bool DatabaseWidget::lock()
return true;
}
-void DatabaseWidget::updateFilePath(const QString& filePath)
-{
- m_fileWatcher->start(filePath);
- m_db->setFilePath(filePath);
-}
-
-void DatabaseWidget::blockAutoReload(bool block)
-{
- if (block) {
- m_fileWatcher->ignoreFileChanges();
- } else {
- m_fileWatcher->observeFileChanges(true);
- }
-}
-
void DatabaseWidget::reloadDatabaseFile()
{
if (!m_db || isLocked()) {
@@ -1417,22 +1396,20 @@ void DatabaseWidget::reloadDatabaseFile()
if (result == MessageBox::No) {
// Notify everyone the database does not match the file
m_db->markAsModified();
- // Rewatch the database file
- m_fileWatcher->restart();
return;
}
}
QString error;
auto db = QSharedPointer<Database>::create(m_db->filePath());
- if (db->open(database()->key(), &error, true)) {
+ if (db->open(database()->key(), &error)) {
if (m_db->isModified()) {
// Ask if we want to merge changes into new database
auto result = MessageBox::question(
this,
tr("Merge Request"),
tr("The database file has changed and you have unsaved changes.\nDo you want to merge your changes?"),
- MessageBox::Merge | MessageBox::Cancel,
+ MessageBox::Merge | MessageBox::Discard,
MessageBox::Merge);
if (result == MessageBox::Merge) {
@@ -1442,11 +1419,9 @@ void DatabaseWidget::reloadDatabaseFile()
}
}
- QUuid groupBeforeReload;
+ QUuid groupBeforeReload = m_db->rootGroup()->uuid();
if (m_groupView && m_groupView->currentGroup()) {
groupBeforeReload = m_groupView->currentGroup()->uuid();
- } else {
- groupBeforeReload = m_db->rootGroup()->uuid();
}
QUuid entryBeforeReload;
@@ -1454,19 +1429,15 @@ void DatabaseWidget::reloadDatabaseFile()
entryBeforeReload = m_entryView->currentEntry()->uuid();
}
- bool isReadOnly = m_db->isReadOnly();
replaceDatabase(db);
- m_db->setReadOnly(isReadOnly);
restoreGroupEntryFocus(groupBeforeReload, entryBeforeReload);
+ m_blockAutoSave = false;
} else {
showMessage(tr("Could not open the new database file while attempting to autoreload.\nError: %1").arg(error),
MessageWidget::Error);
// Mark db as modified since existing data may differ from file or file was deleted
m_db->markAsModified();
}
-
- // Rewatch the database file
- m_fileWatcher->restart();
}
int DatabaseWidget::numberOfSelectedEntries() const
@@ -1604,7 +1575,6 @@ bool DatabaseWidget::save()
}
// Prevent recursions and infinite save loops
- blockAutoReload(true);
m_blockAutoSave = true;
++m_saveAttempts;
@@ -1612,7 +1582,6 @@ bool DatabaseWidget::save()
bool useAtomicSaves = config()->get("UseAtomicSaves", true).toBool();
QString errorMessage;
bool ok = m_db->save(&errorMessage, useAtomicSaves, config()->get("BackupBeforeSave").toBool());
- blockAutoReload(false);
if (ok) {
m_saveAttempts = 0;
diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h
index 7f5e8099d..5a163a312 100644
--- a/src/gui/DatabaseWidget.h
+++ b/src/gui/DatabaseWidget.h
@@ -35,7 +35,7 @@ class KeePass1OpenWidget;
class OpVaultOpenWidget;
class DatabaseSettingsDialog;
class Database;
-class DelayingFileWatcher;
+class FileWatcher;
class EditEntryWidget;
class EditGroupWidget;
class Entry;
@@ -108,8 +108,6 @@ public:
bool currentEntryHasNotes();
bool currentEntryHasTotp();
- void blockAutoReload(bool block = true);
-
QByteArray entryViewState() const;
bool setEntryViewState(const QByteArray& state) const;
QList<int> mainSplitterSizes() const;
@@ -210,7 +208,6 @@ protected:
void showEvent(QShowEvent* event) override;
private slots:
- void updateFilePath(const QString& filePath);
void entryActivationSignalReceived(Entry* entry, EntryModel::ModelColumn column);
void switchBackToEntryEdit();
void switchToHistoryView(Entry* entry);
@@ -273,7 +270,6 @@ private:
bool m_searchLimitGroup;
// Autoreload
- QPointer<DelayingFileWatcher> m_fileWatcher;
bool m_blockAutoSave;
};
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index c4df1d8e6..288f64470 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -224,7 +224,7 @@ if(WITH_XC_KEESHARE)
endif()
add_unit_test(NAME testdatabase SOURCES TestDatabase.cpp
- LIBS ${TEST_LIBRARIES})
+ LIBS testsupport ${TEST_LIBRARIES})
add_unit_test(NAME testtools SOURCES TestTools.cpp
LIBS ${TEST_LIBRARIES})
diff --git a/tests/TestDatabase.cpp b/tests/TestDatabase.cpp
index 94e3c8ba7..960578872 100644
--- a/tests/TestDatabase.cpp
+++ b/tests/TestDatabase.cpp
@@ -20,13 +20,14 @@
#include "TestGlobal.h"
#include <QSignalSpy>
-#include <QTemporaryFile>
#include "config-keepassx-tests.h"
#include "core/Metadata.h"
+#include "core/Tools.h"
#include "crypto/Crypto.h"
#include "format/KeePass2Writer.h"
#include "keys/PasswordKey.h"
+#include "util/TemporaryFile.h"
QTEST_GUILESS_MAIN(TestDatabase)
@@ -35,6 +36,60 @@ void TestDatabase::initTestCase()
QVERIFY(Crypto::init());
}
+void TestDatabase::testOpen()
+{
+ auto db = QSharedPointer<Database>::create();
+ QVERIFY(!db->isInitialized());
+ QVERIFY(!db->isModified());
+
+ auto key = QSharedPointer<CompositeKey>::create();
+ key->addKey(QSharedPointer<PasswordKey>::create("a"));
+
+ bool ok = db->open(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.kdbx"), key);
+ QVERIFY(ok);
+
+ QVERIFY(db->isInitialized());
+ QVERIFY(!db->isModified());
+
+ db->metadata()->setName("test");
+ QVERIFY(db->isModified());
+}
+
+void TestDatabase::testSave()
+{
+ QByteArray data;
+ QFile sourceDbFile(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.kdbx"));
+ QVERIFY(sourceDbFile.open(QIODevice::ReadOnly));
+ QVERIFY(Tools::readAllFromDevice(&sourceDbFile, data));
+ sourceDbFile.close();
+
+ TemporaryFile tempFile;
+ QVERIFY(tempFile.open());
+ QCOMPARE(tempFile.write(data), static_cast<qint64>((data.size())));
+ tempFile.close();
+
+ auto db = QSharedPointer<Database>::create();
+ auto key = QSharedPointer<CompositeKey>::create();
+ key->addKey(QSharedPointer<PasswordKey>::create("a"));
+
+ QString error;
+ bool ok = db->open(tempFile.fileName(), key, &error);
+ QVERIFY(ok);
+
+ // Test safe saves
+ db->metadata()->setName("test");
+ QVERIFY(db->isModified());
+
+ // Test unsafe saves
+ QVERIFY2(db->save(&error, false, false), error.toLatin1());
+
+ QVERIFY2(db->save(&error), error.toLatin1());
+ QVERIFY(!db->isModified());
+
+ // Test save backups
+ QVERIFY2(db->save(&error, true, true), error.toLatin1());
+}
+
void TestDatabase::testEmptyRecycleBinOnDisabled()
{
QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinDisabled.kdbx");
diff --git a/tests/TestDatabase.h b/tests/TestDatabase.h
index 46deb58aa..b5df8690f 100644
--- a/tests/TestDatabase.h
+++ b/tests/TestDatabase.h
@@ -27,6 +27,8 @@ class TestDatabase : public QObject
private slots:
void initTestCase();
+ void testOpen();
+ void testSave();
void testEmptyRecycleBinOnDisabled();
void testEmptyRecycleBinOnNotCreated();
void testEmptyRecycleBinOnEmpty();