Skip to content

Commit

Permalink
Implement autoreload of database file (#93)
Browse files Browse the repository at this point in the history
  • Loading branch information
droidmonkey authored Nov 25, 2016
2 parents 3cccfd9 + 3d24936 commit 0dfd200
Show file tree
Hide file tree
Showing 12 changed files with 276 additions and 58 deletions.
1 change: 1 addition & 0 deletions src/core/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ void Config::init(const QString& fileName)
m_defaults.insert("RememberLastKeyFiles", true);
m_defaults.insert("OpenPreviousDatabasesOnStartup", true);
m_defaults.insert("AutoSaveAfterEveryChange", false);
m_defaults.insert("AutoReloadOnChange", true);
m_defaults.insert("AutoSaveOnExit", false);
m_defaults.insert("ShowToolbar", true);
m_defaults.insert("MinimizeOnCopy", false);
Expand Down
6 changes: 6 additions & 0 deletions src/core/Database.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -324,3 +324,9 @@ void Database::startModifiedTimer()
}
m_timer->start(150);
}

const CompositeKey & Database::key() const
{
return m_data.key;
}

1 change: 1 addition & 0 deletions src/core/Database.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class Database : public QObject
QByteArray transformSeed() const;
quint64 transformRounds() const;
QByteArray transformedMasterKey() const;
const CompositeKey & key() const;

void setCipher(const Uuid& cipher);
void setCompressionAlgo(Database::CompressionAlgorithm algo);
Expand Down
16 changes: 8 additions & 8 deletions src/core/Group.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -547,16 +547,16 @@ void Group::merge(const Group* other)
if (!findEntry(entry->uuid())) {
entry->clone(Entry::CloneNoFlags)->setGroup(this);
} else {
resolveConflict(this->findEntry(entry->uuid()), entry);
resolveConflict(findEntry(entry->uuid()), entry);
}
}

// merge groups (recursively)
const QList<Group*> dbChildren = other->children();
for (Group* group : dbChildren) {
// groups are searched by name instead of uuid
if (this->findChildByName(group->name())) {
this->findChildByName(group->name())->merge(group);
if (findChildByName(group->name())) {
findChildByName(group->name())->merge(group);
} else {
group->setParent(this);
}
Expand Down Expand Up @@ -765,24 +765,24 @@ void Group::resolveConflict(Entry* existingEntry, Entry* otherEntry)

Entry* clonedEntry;

switch(this->mergeMode()) {
switch(mergeMode()) {
case KeepBoth:
// if one entry is newer, create a clone and add it to the group
if (timeExisting > timeOther) {
clonedEntry = otherEntry->clone(Entry::CloneNoFlags);
clonedEntry->setGroup(this);
this->markOlderEntry(clonedEntry);
markOlderEntry(clonedEntry);
} else if (timeExisting < timeOther) {
clonedEntry = otherEntry->clone(Entry::CloneNoFlags);
clonedEntry->setGroup(this);
this->markOlderEntry(existingEntry);
markOlderEntry(existingEntry);
}
break;
case KeepNewer:
if (timeExisting < timeOther) {
// only if other entry is newer, replace existing one
this->removeEntry(existingEntry);
this->addEntry(otherEntry);
removeEntry(existingEntry);
addEntry(otherEntry->clone(Entry::CloneNoFlags));
}

break;
Expand Down
46 changes: 24 additions & 22 deletions src/gui/DatabaseTabWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ void DatabaseTabWidget::importKeePass1Database()
Database* db = new Database();
DatabaseManagerStruct dbStruct;
dbStruct.dbWidget = new DatabaseWidget(db, this);
dbStruct.dbWidget->databaseModified();
dbStruct.modified = true;

insertDatabase(db, dbStruct);
Expand Down Expand Up @@ -312,17 +313,28 @@ bool DatabaseTabWidget::closeAllDatabases()
bool DatabaseTabWidget::saveDatabase(Database* db)
{
DatabaseManagerStruct& dbStruct = m_dbList[db];
// temporarily disable autoreload
dbStruct.dbWidget->ignoreNextAutoreload();

if (dbStruct.saveToFilename) {
QSaveFile saveFile(dbStruct.canonicalFilePath);
if (saveFile.open(QIODevice::WriteOnly)) {
// write the database to the file
m_writer.writeDatabase(&saveFile, db);
if (m_writer.hasError()) {
MessageBox::critical(this, tr("Error"), tr("Writing the database failed.") + "\n\n"
+ m_writer.errorString());
return false;
}
if (!saveFile.commit()) {

if (saveFile.commit()) {
// successfully saved database file
dbStruct.modified = false;
dbStruct.dbWidget->databaseSaved();
updateTabName(db);
return true;
}
else {
MessageBox::critical(this, tr("Error"), tr("Writing the database failed.") + "\n\n"
+ saveFile.errorString());
return false;
Expand All @@ -333,10 +345,6 @@ bool DatabaseTabWidget::saveDatabase(Database* db)
+ saveFile.errorString());
return false;
}

dbStruct.modified = false;
updateTabName(db);
return true;
}
else {
return saveDatabaseAs(db);
Expand Down Expand Up @@ -390,22 +398,14 @@ bool DatabaseTabWidget::saveDatabaseAs(Database* db)
}
}

QSaveFile saveFile(fileName);
if (!saveFile.open(QIODevice::WriteOnly)) {
MessageBox::critical(this, tr("Error"), tr("Writing the database failed.") + "\n\n"
+ saveFile.errorString());
return false;
}
// setup variables so saveDatabase succeeds
dbStruct.saveToFilename = true;
dbStruct.canonicalFilePath = fileName;

m_writer.writeDatabase(&saveFile, db);
if (m_writer.hasError()) {
MessageBox::critical(this, tr("Error"), tr("Writing the database failed.") + "\n\n"
+ m_writer.errorString());
return false;
}
if (!saveFile.commit()) {
MessageBox::critical(this, tr("Error"), tr("Writing the database failed.") + "\n\n"
+ saveFile.errorString());
if (!saveDatabase(db)) {
// failed to save, revert back
dbStruct.saveToFilename = false;
dbStruct.canonicalFilePath = oldFileName;
return false;
}

Expand Down Expand Up @@ -626,7 +626,7 @@ void DatabaseTabWidget::insertDatabase(Database* db, const DatabaseManagerStruct
setCurrentIndex(index);
connectDatabase(db);
connect(dbStruct.dbWidget, SIGNAL(closeRequest()), SLOT(closeDatabaseFromSender()));
connect(dbStruct.dbWidget, SIGNAL(databaseChanged(Database*)), SLOT(changeDatabase(Database*)));
connect(dbStruct.dbWidget, SIGNAL(databaseChanged(Database*, bool)), SLOT(changeDatabase(Database*, bool)));
connect(dbStruct.dbWidget, SIGNAL(unlockedDatabase()), SLOT(updateTabNameFromDbWidgetSender()));
connect(dbStruct.dbWidget, SIGNAL(unlockedDatabase()), SLOT(emitDatabaseUnlockedFromDbWidgetSender()));
}
Expand Down Expand Up @@ -744,6 +744,7 @@ void DatabaseTabWidget::modified()

if (!dbStruct.modified) {
dbStruct.modified = true;
dbStruct.dbWidget->databaseModified();
updateTabName(db);
}
}
Expand All @@ -765,14 +766,15 @@ void DatabaseTabWidget::updateLastDatabases(const QString& filename)
}
}

void DatabaseTabWidget::changeDatabase(Database* newDb)
void DatabaseTabWidget::changeDatabase(Database* newDb, bool unsavedChanges)
{
Q_ASSERT(sender());
Q_ASSERT(!m_dbList.contains(newDb));

DatabaseWidget* dbWidget = static_cast<DatabaseWidget*>(sender());
Database* oldDb = databaseFromDatabaseWidget(dbWidget);
DatabaseManagerStruct dbStruct = m_dbList[oldDb];
dbStruct.modified = unsavedChanges;
m_dbList.remove(oldDb);
m_dbList.insert(newDb, dbStruct);

Expand Down
2 changes: 1 addition & 1 deletion src/gui/DatabaseTabWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ private Q_SLOTS:
void updateTabNameFromDbWidgetSender();
void modified();
void toggleTabbar();
void changeDatabase(Database* newDb);
void changeDatabase(Database* newDb, bool unsavedChanges);
void emitActivateDatabaseChanged();
void emitDatabaseUnlockedFromDbWidgetSender();

Expand Down
117 changes: 115 additions & 2 deletions src/gui/DatabaseWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <QDesktopServices>
#include <QHBoxLayout>
#include <QLabel>
#include <QFile>
#include <QLineEdit>
#include <QKeyEvent>
#include <QSplitter>
Expand All @@ -36,6 +37,7 @@
#include "core/Group.h"
#include "core/Metadata.h"
#include "core/Tools.h"
#include "format/KeePass2Reader.h"
#include "gui/ChangeMasterKeyWidget.h"
#include "gui/Clipboard.h"
#include "gui/DatabaseOpenWidget.h"
Expand Down Expand Up @@ -156,9 +158,18 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
connect(m_databaseOpenMergeWidget, SIGNAL(editFinished(bool)), SLOT(mergeDatabase(bool)));
connect(m_keepass1OpenWidget, SIGNAL(editFinished(bool)), SLOT(openDatabase(bool)));
connect(m_unlockDatabaseWidget, SIGNAL(editFinished(bool)), SLOT(unlockDatabase(bool)));
connect(m_unlockDatabaseDialog, SIGNAL(unlockDone(bool)), SLOT(unlockDatabase(bool)));
connect(m_unlockDatabaseDialog, SIGNAL(unlockDone(bool)), SLOT(unlockDatabase(bool)));
connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(onWatchedFileChanged()));
connect(&m_fileWatchTimer, SIGNAL(timeout()), this, SLOT(reloadDatabaseFile()));
connect(&m_ignoreWatchTimer, SIGNAL(timeout()), this, SLOT(onWatchedFileChanged()));
connect(this, SIGNAL(currentChanged(int)), this, SLOT(emitCurrentModeChanged()));

m_databaseModified = false;

m_fileWatchTimer.setSingleShot(true);
m_ignoreWatchTimer.setSingleShot(true);
m_ignoreNextAutoreload = false;

m_searchCaseSensitive = false;
m_searchCurrentGroup = false;

Expand Down Expand Up @@ -291,7 +302,7 @@ void DatabaseWidget::replaceDatabase(Database* db)
Database* oldDb = m_db;
m_db = db;
m_groupView->changeDatabase(m_db);
Q_EMIT databaseChanged(m_db);
Q_EMIT databaseChanged(m_db, m_databaseModified);
delete oldDb;
}

Expand Down Expand Up @@ -663,8 +674,10 @@ void DatabaseWidget::openDatabase(bool accepted)
m_databaseOpenWidget = nullptr;
delete m_keepass1OpenWidget;
m_keepass1OpenWidget = nullptr;
m_fileWatcher.addPath(m_filename);
}
else {
m_fileWatcher.removePath(m_filename);
if (m_databaseOpenWidget->database()) {
delete m_databaseOpenWidget->database();
}
Expand Down Expand Up @@ -809,6 +822,16 @@ void DatabaseWidget::switchToImportKeepass1(const QString& fileName)
setCurrentWidget(m_keepass1OpenWidget);
}

void DatabaseWidget::databaseModified()
{
m_databaseModified = true;
}

void DatabaseWidget::databaseSaved()
{
m_databaseModified = false;
}

void DatabaseWidget::search(const QString& searchtext)
{
if (searchtext.isEmpty())
Expand Down Expand Up @@ -947,9 +970,99 @@ void DatabaseWidget::lock()

void DatabaseWidget::updateFilename(const QString& fileName)
{
if (! m_filename.isEmpty()) {
m_fileWatcher.removePath(m_filename);
}

m_fileWatcher.addPath(fileName);
m_filename = fileName;
}

void DatabaseWidget::ignoreNextAutoreload()
{
m_ignoreNextAutoreload = true;
m_ignoreWatchTimer.start(100);
}

void DatabaseWidget::onWatchedFileChanged()
{
if (m_ignoreNextAutoreload) {
// Reset the watch
m_ignoreNextAutoreload = false;
m_ignoreWatchTimer.stop();
m_fileWatcher.addPath(m_filename);
}
else {
if (m_fileWatchTimer.isActive())
return;

m_fileWatchTimer.start(500);
}
}

void DatabaseWidget::reloadDatabaseFile()
{
if (m_db == nullptr)
return;

if (! config()->get("AutoReloadOnChange").toBool()) {
// Ask if we want to reload the db
QMessageBox::StandardButton mb = MessageBox::question(this, tr("Autoreload Request"),
tr("The database file has changed. Do you want to load the changes?"),
QMessageBox::Yes | QMessageBox::No);

if (mb == QMessageBox::No) {
// Notify everyone the database does not match the file
emit m_db->modified();
m_databaseModified = true;
// Rewatch the database file
m_fileWatcher.addPath(m_filename);
return;
}
}

KeePass2Reader reader;
QFile file(m_filename);
if (file.open(QIODevice::ReadOnly)) {
Database* db = reader.readDatabase(&file, database()->key());
if (db != nullptr) {
if (m_databaseModified) {
// Ask if we want to merge changes into new database
QMessageBox::StandardButton mb = MessageBox::question(this, tr("Merge Request"),
tr("The database file has changed and you have unsaved changes."
"Do you want to merge your changes?"),
QMessageBox::Yes | QMessageBox::No);

if (mb == QMessageBox::Yes) {
// Merge the old database into the new one
m_db->setEmitModified(false);
db->merge(m_db);
}
else {
// Since we are accepting the new file as-is, internally mark as unmodified
// TODO: when saving is moved out of DatabaseTabWidget, this should be replaced
m_databaseModified = false;
}
}

replaceDatabase(db);
}
else {
MessageBox::critical(this, tr("Autoreload Failed"),
tr("Could not parse or unlock the new database file while attempting"
"to autoreload this database."));
}
}
else {
MessageBox::critical(this, tr("Autoreload Failed"),
tr("Could not open the new database file while attempting to autoreload"
"this database."));
}

// Rewatch the database file
m_fileWatcher.addPath(m_filename);
}

int DatabaseWidget::numberOfSelectedEntries() const
{
return m_entryView->numberOfSelectedEntries();
Expand Down
Loading

0 comments on commit 0dfd200

Please sign in to comment.