mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Detect background changes to database file.
This gives the option to reload the database. TODO: - Settings for reloadBehavior (ask, reloadUnchanged, ignore) - Improve notification, by using a header instead of dialog: nicer, less intrusive, gives more options to user, and works better when multiple databases are open. - Keep tab order on reload.
This commit is contained in:
parent
850c7c7ecf
commit
d5c8787451
@ -227,6 +227,11 @@ bool Database::verifyKey(const CompositeKey& key) const
|
||||
return (m_key.rawKey() == key.rawKey());
|
||||
}
|
||||
|
||||
CompositeKey Database::key() const
|
||||
{
|
||||
return m_key;
|
||||
}
|
||||
|
||||
void Database::createRecycleBin()
|
||||
{
|
||||
Group* recycleBin = Group::createRecycleBin();
|
||||
|
@ -88,6 +88,7 @@ public:
|
||||
void setKey(const CompositeKey& key);
|
||||
bool hasKey() const;
|
||||
bool verifyKey(const CompositeKey& key) const;
|
||||
CompositeKey key() const;
|
||||
void recycleEntry(Entry* entry);
|
||||
void recycleGroup(Group* group);
|
||||
void setEmitModified(bool value);
|
||||
|
@ -91,14 +91,26 @@ void DatabaseOpenWidget::enterKey(const QString& pw, const QString& keyFile)
|
||||
openDatabase();
|
||||
}
|
||||
|
||||
void DatabaseOpenWidget::enterKey(const CompositeKey& masterKey)
|
||||
{
|
||||
if (masterKey.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
openDatabase(masterKey);
|
||||
}
|
||||
|
||||
void DatabaseOpenWidget::openDatabase()
|
||||
{
|
||||
KeePass2Reader reader;
|
||||
CompositeKey masterKey = databaseKey();
|
||||
if (masterKey.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
openDatabase(masterKey);
|
||||
}
|
||||
|
||||
void DatabaseOpenWidget::openDatabase(const CompositeKey& masterKey)
|
||||
{
|
||||
KeePass2Reader reader;
|
||||
QFile file(m_filename);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
// TODO: error message
|
||||
|
@ -39,6 +39,7 @@ public:
|
||||
~DatabaseOpenWidget();
|
||||
void load(const QString& filename);
|
||||
void enterKey(const QString& pw, const QString& keyFile);
|
||||
void enterKey(const CompositeKey& masterKey);
|
||||
Database* database();
|
||||
|
||||
Q_SIGNALS:
|
||||
@ -49,6 +50,7 @@ protected:
|
||||
|
||||
protected Q_SLOTS:
|
||||
virtual void openDatabase();
|
||||
void openDatabase(const CompositeKey& masterKey);
|
||||
void reject();
|
||||
|
||||
private Q_SLOTS:
|
||||
|
@ -20,6 +20,9 @@
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtGui/QTabWidget>
|
||||
#include <QtGui/QMessageBox>
|
||||
#include <QtCore/QFileSystemWatcher>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#include "autotype/AutoType.h"
|
||||
#include "core/Config.h"
|
||||
@ -45,7 +48,9 @@ DatabaseManagerStruct::DatabaseManagerStruct()
|
||||
const int DatabaseTabWidget::LastDatabasesCount = 5;
|
||||
|
||||
DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
|
||||
: QTabWidget(parent)
|
||||
: QTabWidget(parent),
|
||||
m_fileWatcher(new QFileSystemWatcher(this)),
|
||||
m_reloadBehavior(ReloadUnmodified) //TODO: setting
|
||||
{
|
||||
DragTabBar* tabBar = new DragTabBar(this);
|
||||
tabBar->setDrawBase(false);
|
||||
@ -53,6 +58,7 @@ DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
|
||||
|
||||
connect(this, SIGNAL(tabCloseRequested(int)), SLOT(closeDatabase(int)));
|
||||
connect(autoType(), SIGNAL(globalShortcutTriggered()), SLOT(performGlobalAutoType()));
|
||||
connect(m_fileWatcher, SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString)));
|
||||
}
|
||||
|
||||
DatabaseTabWidget::~DatabaseTabWidget()
|
||||
@ -92,7 +98,7 @@ void DatabaseTabWidget::openDatabase()
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
|
||||
const QString& keyFile)
|
||||
const QString& keyFile, const CompositeKey& key)
|
||||
{
|
||||
QFileInfo fileInfo(fileName);
|
||||
QString canonicalFilePath = fileInfo.canonicalFilePath();
|
||||
@ -136,12 +142,17 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
|
||||
dbStruct.filePath = fileInfo.absoluteFilePath();
|
||||
dbStruct.canonicalFilePath = canonicalFilePath;
|
||||
dbStruct.fileName = fileInfo.fileName();
|
||||
dbStruct.lastModified = fileInfo.lastModified();
|
||||
|
||||
insertDatabase(db, dbStruct);
|
||||
m_fileWatcher->addPath(dbStruct.filePath);
|
||||
|
||||
updateRecentDatabases(dbStruct.filePath);
|
||||
|
||||
if (!pw.isNull() || !keyFile.isEmpty()) {
|
||||
if (!key.isEmpty()) {
|
||||
dbStruct.dbWidget->switchToOpenDatabase(dbStruct.filePath, key);
|
||||
}
|
||||
else if (!pw.isNull() || !keyFile.isEmpty()) {
|
||||
dbStruct.dbWidget->switchToOpenDatabase(dbStruct.filePath, pw, keyFile);
|
||||
}
|
||||
else {
|
||||
@ -168,6 +179,111 @@ void DatabaseTabWidget::importKeePass1Database()
|
||||
dbStruct.dbWidget->switchToImportKeepass1(fileName);
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::fileChanged(const QString &fileName)
|
||||
{
|
||||
const bool wasEmpty = m_changedFiles.isEmpty();
|
||||
m_changedFiles.insert(fileName);
|
||||
if (wasEmpty && !m_changedFiles.isEmpty())
|
||||
QTimer::singleShot(200, this, SLOT(checkReloadDatabases()));
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::expectFileChange(const DatabaseManagerStruct& dbStruct)
|
||||
{
|
||||
if (dbStruct.filePath.isEmpty())
|
||||
return;
|
||||
m_expectedFileChanges.insert(dbStruct.filePath);
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::unexpectFileChange(DatabaseManagerStruct& dbStruct)
|
||||
{
|
||||
if (dbStruct.filePath.isEmpty())
|
||||
return;
|
||||
m_expectedFileChanges.remove(dbStruct.filePath);
|
||||
dbStruct.lastModified = QFileInfo(dbStruct.filePath).lastModified();
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::checkReloadDatabases()
|
||||
{
|
||||
QSet<QString> changedFiles;
|
||||
|
||||
changedFiles = m_changedFiles.subtract(m_expectedFileChanges);
|
||||
m_changedFiles.clear();
|
||||
|
||||
if (changedFiles.isEmpty())
|
||||
return;
|
||||
|
||||
Q_FOREACH (DatabaseManagerStruct dbStruct, m_dbList) {
|
||||
QString filePath = dbStruct.filePath;
|
||||
Database * db = dbStruct.dbWidget->database();
|
||||
|
||||
if (!changedFiles.contains(filePath))
|
||||
continue;
|
||||
|
||||
QFileInfo fi(filePath);
|
||||
QDateTime lastModified = fi.lastModified();
|
||||
if (dbStruct.lastModified == lastModified)
|
||||
continue;
|
||||
|
||||
DatabaseWidget::Mode mode = dbStruct.dbWidget->currentMode();
|
||||
if (mode == DatabaseWidget::None || mode == DatabaseWidget::LockedMode || !db->hasKey())
|
||||
continue;
|
||||
|
||||
if ( (m_reloadBehavior == AlwaysAsk)
|
||||
|| (m_reloadBehavior == ReloadUnmodified && mode == DatabaseWidget::EditMode)
|
||||
|| (m_reloadBehavior == ReloadUnmodified && dbStruct.modified)) {
|
||||
//TODO: display banner instead, to let user now file has changed and choose to Reload, Overwrite, and SaveAs
|
||||
// --> less obstrubsive (esp. if multiple DB are open), cleaner UI
|
||||
if (QMessageBox::warning(this, fi.exists() ? tr("Database file changed") : tr("Database file removed"),
|
||||
tr("Do you want to discard your changes and reload?"),
|
||||
QMessageBox::Yes|QMessageBox::No) == QMessageBox::No)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fi.exists()) {
|
||||
//Ignore/cancel all edits
|
||||
dbStruct.dbWidget->switchToView(false);
|
||||
dbStruct.modified = false;
|
||||
|
||||
//Save current group/entry
|
||||
Uuid currentGroup;
|
||||
if (Group* group = dbStruct.dbWidget->groupView()->currentGroup())
|
||||
currentGroup = group->uuid();
|
||||
Uuid currentEntry;
|
||||
if (Entry* entry = dbStruct.dbWidget->entryView()->currentEntry())
|
||||
currentEntry = entry->uuid();
|
||||
QString searchText = dbStruct.dbWidget->searchText();
|
||||
|
||||
//Reload updated db
|
||||
CompositeKey key = db->key();
|
||||
closeDatabase(db);
|
||||
openDatabase(filePath, QString(), QString(), key);
|
||||
|
||||
//Restore current group/entry
|
||||
dbStruct = indexDatabaseManagerStruct(count() - 1);
|
||||
if (dbStruct.dbWidget) {
|
||||
Database * db = dbStruct.dbWidget->database();
|
||||
if (!searchText.isEmpty())
|
||||
dbStruct.dbWidget->showSearch(searchText);
|
||||
if (!currentGroup.isNull())
|
||||
if (Group* group = db->resolveGroup(currentGroup))
|
||||
dbStruct.dbWidget->groupView()->setCurrentGroup(group);
|
||||
if (!currentEntry.isNull())
|
||||
if (Entry* entry = db->resolveEntry(currentEntry))
|
||||
dbStruct.dbWidget->entryView()->setCurrentEntry(entry);
|
||||
}
|
||||
|
||||
//TODO: keep tab order...
|
||||
} else {
|
||||
//Ignore/cancel all edits
|
||||
dbStruct.dbWidget->switchToView(false);
|
||||
dbStruct.modified = false;
|
||||
|
||||
//Close database
|
||||
closeDatabase(dbStruct.dbWidget->database());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DatabaseTabWidget::closeDatabase(Database* db)
|
||||
{
|
||||
Q_ASSERT(db);
|
||||
@ -219,6 +335,7 @@ void DatabaseTabWidget::deleteDatabase(Database* db)
|
||||
const DatabaseManagerStruct dbStruct = m_dbList.value(db);
|
||||
int index = databaseIndex(db);
|
||||
|
||||
m_fileWatcher->removePath(dbStruct.filePath);
|
||||
removeTab(index);
|
||||
toggleTabbar();
|
||||
m_dbList.remove(db);
|
||||
@ -260,12 +377,16 @@ void DatabaseTabWidget::saveDatabase(Database* db)
|
||||
if (dbStruct.saveToFilename) {
|
||||
bool result = false;
|
||||
|
||||
expectFileChange(dbStruct);
|
||||
|
||||
QSaveFile saveFile(dbStruct.filePath);
|
||||
if (saveFile.open(QIODevice::WriteOnly)) {
|
||||
m_writer.writeDatabase(&saveFile, db);
|
||||
result = saveFile.commit();
|
||||
}
|
||||
|
||||
unexpectFileChange(dbStruct);
|
||||
|
||||
if (result) {
|
||||
dbStruct.modified = false;
|
||||
updateTabName(db);
|
||||
@ -283,12 +404,12 @@ void DatabaseTabWidget::saveDatabase(Database* db)
|
||||
void DatabaseTabWidget::saveDatabaseAs(Database* db)
|
||||
{
|
||||
DatabaseManagerStruct& dbStruct = m_dbList[db];
|
||||
QString oldFileName;
|
||||
QString oldFilePath;
|
||||
if (dbStruct.saveToFilename) {
|
||||
oldFileName = dbStruct.filePath;
|
||||
oldFilePath = dbStruct.filePath;
|
||||
}
|
||||
QString fileName = fileDialog()->getSaveFileName(this, tr("Save database as"),
|
||||
oldFileName, tr("KeePass 2 Database").append(" (*.kdbx)"));
|
||||
oldFilePath, tr("KeePass 2 Database").append(" (*.kdbx)"));
|
||||
if (!fileName.isEmpty()) {
|
||||
bool result = false;
|
||||
|
||||
@ -299,15 +420,18 @@ void DatabaseTabWidget::saveDatabaseAs(Database* db)
|
||||
}
|
||||
|
||||
if (result) {
|
||||
m_fileWatcher->removePath(oldFilePath);
|
||||
dbStruct.modified = false;
|
||||
dbStruct.saveToFilename = true;
|
||||
QFileInfo fileInfo(fileName);
|
||||
dbStruct.filePath = fileInfo.absoluteFilePath();
|
||||
dbStruct.canonicalFilePath = fileInfo.canonicalFilePath();
|
||||
dbStruct.fileName = fileInfo.fileName();
|
||||
dbStruct.lastModified = fileInfo.lastModified();
|
||||
dbStruct.dbWidget->updateFilename(dbStruct.filePath);
|
||||
updateTabName(db);
|
||||
updateRecentDatabases(dbStruct.filePath);
|
||||
m_fileWatcher->addPath(dbStruct.filePath);
|
||||
}
|
||||
else {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Writing the database failed.") + "\n\n"
|
||||
|
@ -18,7 +18,9 @@
|
||||
#ifndef KEEPASSX_DATABASETABWIDGET_H
|
||||
#define KEEPASSX_DATABASETABWIDGET_H
|
||||
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QSet>
|
||||
#include <QtGui/QTabWidget>
|
||||
|
||||
#include "format/KeePass2Writer.h"
|
||||
@ -27,6 +29,7 @@
|
||||
class DatabaseWidget;
|
||||
class DatabaseOpenWidget;
|
||||
class QFile;
|
||||
class QFileSystemWatcher;
|
||||
|
||||
struct DatabaseManagerStruct
|
||||
{
|
||||
@ -39,6 +42,7 @@ struct DatabaseManagerStruct
|
||||
bool saveToFilename;
|
||||
bool modified;
|
||||
bool readOnly;
|
||||
QDateTime lastModified;
|
||||
};
|
||||
|
||||
Q_DECLARE_TYPEINFO(DatabaseManagerStruct, Q_MOVABLE_TYPE);
|
||||
@ -51,16 +55,24 @@ public:
|
||||
explicit DatabaseTabWidget(QWidget* parent = Q_NULLPTR);
|
||||
~DatabaseTabWidget();
|
||||
void openDatabase(const QString& fileName, const QString& pw = QString(),
|
||||
const QString& keyFile = QString());
|
||||
const QString& keyFile = QString(), const CompositeKey& key = CompositeKey());
|
||||
DatabaseWidget* currentDatabaseWidget();
|
||||
bool hasLockableDatabases();
|
||||
|
||||
static const int LastDatabasesCount;
|
||||
|
||||
enum ReloadBehavior {
|
||||
AlwaysAsk,
|
||||
ReloadUnmodified,
|
||||
IgnoreAll
|
||||
};
|
||||
|
||||
public Q_SLOTS:
|
||||
void newDatabase();
|
||||
void openDatabase();
|
||||
void importKeePass1Database();
|
||||
void fileChanged(const QString& fileName);
|
||||
void checkReloadDatabases();
|
||||
void saveDatabase(int index = -1);
|
||||
void saveDatabaseAs(int index = -1);
|
||||
bool closeDatabase(int index = -1);
|
||||
@ -96,9 +108,15 @@ private:
|
||||
void insertDatabase(Database* db, const DatabaseManagerStruct& dbStruct);
|
||||
void updateRecentDatabases(const QString& filename);
|
||||
void connectDatabase(Database* newDb, Database* oldDb = Q_NULLPTR);
|
||||
void expectFileChange(const DatabaseManagerStruct& dbStruct);
|
||||
void unexpectFileChange(DatabaseManagerStruct& dbStruct);
|
||||
|
||||
KeePass2Writer m_writer;
|
||||
QHash<Database*, DatabaseManagerStruct> m_dbList;
|
||||
QSet<QString> m_changedFiles;
|
||||
QSet<QString> m_expectedFileChanges;
|
||||
QFileSystemWatcher* m_fileWatcher;
|
||||
ReloadBehavior m_reloadBehavior;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_DATABASETABWIDGET_H
|
||||
|
@ -533,6 +533,13 @@ void DatabaseWidget::switchToOpenDatabase(const QString& fileName, const QString
|
||||
m_databaseOpenWidget->enterKey(password, keyFile);
|
||||
}
|
||||
|
||||
void DatabaseWidget::switchToOpenDatabase(const QString &fileName, const CompositeKey& masterKey)
|
||||
{
|
||||
updateFilename(fileName);
|
||||
switchToOpenDatabase(fileName);
|
||||
m_databaseOpenWidget->enterKey(masterKey);
|
||||
}
|
||||
|
||||
void DatabaseWidget::switchToImportKeepass1(const QString& fileName)
|
||||
{
|
||||
updateFilename(fileName);
|
||||
@ -556,10 +563,10 @@ void DatabaseWidget::closeSearch()
|
||||
m_groupView->setCurrentGroup(m_lastGroup);
|
||||
}
|
||||
|
||||
void DatabaseWidget::showSearch()
|
||||
void DatabaseWidget::showSearch(const QString & searchString)
|
||||
{
|
||||
m_searchUi->searchEdit->blockSignals(true);
|
||||
m_searchUi->searchEdit->clear();
|
||||
m_searchUi->searchEdit->setText(searchString);
|
||||
m_searchUi->searchEdit->blockSignals(false);
|
||||
|
||||
m_searchUi->searchCurrentRadioButton->blockSignals(true);
|
||||
@ -665,6 +672,11 @@ bool DatabaseWidget::isInSearchMode()
|
||||
return m_entryView->inEntryListMode();
|
||||
}
|
||||
|
||||
QString DatabaseWidget::searchText()
|
||||
{
|
||||
return m_entryView->inEntryListMode() ? m_searchUi->searchEdit->text() : QString();
|
||||
}
|
||||
|
||||
void DatabaseWidget::clearLastGroup(Group* group)
|
||||
{
|
||||
if (group) {
|
||||
|
@ -37,6 +37,7 @@ class KeePass1OpenWidget;
|
||||
class QFile;
|
||||
class QMenu;
|
||||
class UnlockDatabaseWidget;
|
||||
class CompositeKey;
|
||||
|
||||
namespace Ui {
|
||||
class SearchWidget;
|
||||
@ -63,6 +64,7 @@ public:
|
||||
bool dbHasKey();
|
||||
bool canDeleteCurrentGoup();
|
||||
bool isInSearchMode();
|
||||
QString searchText();
|
||||
int addWidget(QWidget* w);
|
||||
void setCurrentIndex(int index);
|
||||
void setCurrentWidget(QWidget* widget);
|
||||
@ -97,14 +99,16 @@ public Q_SLOTS:
|
||||
void switchToDatabaseSettings();
|
||||
void switchToOpenDatabase(const QString& fileName);
|
||||
void switchToOpenDatabase(const QString& fileName, const QString& password, const QString& keyFile);
|
||||
void switchToOpenDatabase(const QString &fileName, const CompositeKey &masterKey);
|
||||
void switchToImportKeepass1(const QString& fileName);
|
||||
void switchToView(bool accepted);
|
||||
void toggleSearch();
|
||||
void showSearch(const QString & searchString = QString());
|
||||
void emitGroupContextMenuRequested(const QPoint& pos);
|
||||
void emitEntryContextMenuRequested(const QPoint& pos);
|
||||
|
||||
private Q_SLOTS:
|
||||
void switchBackToEntryEdit();
|
||||
void switchToView(bool accepted);
|
||||
void switchToHistoryView(Entry* entry);
|
||||
void switchToEntryEdit(Entry* entry);
|
||||
void switchToEntryEdit(Entry* entry, bool create);
|
||||
@ -117,7 +121,6 @@ private Q_SLOTS:
|
||||
void search();
|
||||
void startSearch();
|
||||
void startSearchTimer();
|
||||
void showSearch();
|
||||
void closeSearch();
|
||||
|
||||
private:
|
||||
|
@ -284,6 +284,15 @@ void MainWindow::clearLastDatabases()
|
||||
config()->set("LastDatabases", QVariant());
|
||||
}
|
||||
|
||||
void MainWindow::changeEvent(QEvent *e)
|
||||
{
|
||||
QMainWindow::changeEvent(e);
|
||||
if (e->type() == QEvent::ActivationChange) {
|
||||
if (isActiveWindow())
|
||||
m_ui->tabWidget->checkReloadDatabases();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::openDatabase(const QString& fileName, const QString& pw, const QString& keyFile)
|
||||
{
|
||||
m_ui->tabWidget->openDatabase(fileName, pw, keyFile);
|
||||
|
@ -41,7 +41,8 @@ public Q_SLOTS:
|
||||
const QString& keyFile = QString());
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent* event) Q_DECL_OVERRIDE;
|
||||
void changeEvent(QEvent *e);
|
||||
void closeEvent(QCloseEvent* event) Q_DECL_OVERRIDE;
|
||||
|
||||
private Q_SLOTS:
|
||||
void setMenuActionState(DatabaseWidget::Mode mode = DatabaseWidget::None);
|
||||
|
Loading…
Reference in New Issue
Block a user