Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge database tool #47

Merged
merged 20 commits into from
Nov 8, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ac5c1f4
Add capability to merge a database into the currently open one, also in
monomon Aug 17, 2016
ac64617
Update check for presence of uuid to use isNull(), was upsetting the CI
monomon Aug 18, 2016
9ac32be
Run your tests, stoopid
monomon Aug 18, 2016
16d7d2e
Add a function to resolve conflicts when merging groups.
monomon Aug 20, 2016
e4fb80d
Detect background changes to database file. Merged from https://githu…
Oct 6, 2016
b627c8f
:bug: replace Q_FOREACH, use proper name in UI
TheZ3ro Oct 19, 2016
758ba9e
Merge remote-tracking branch 'origin/develop' into feature/merge-db-#22
droidmonkey Oct 27, 2016
46dc39c
Rev 1 of search bar always visible #15
droidmonkey Oct 14, 2016
d815642
Completely updated the Gui tests to be atomic.
droidmonkey Oct 20, 2016
5fb8077
Test fix for travis
droidmonkey Oct 21, 2016
360619f
Search box gets disabled instead of hidden; Fixed Travis CI gui tests
droidmonkey Oct 25, 2016
069ebd1
Finalized new search behavior.
droidmonkey Oct 29, 2016
3c3ecdb
Search is disabled on first load and fixed escape to end search.
droidmonkey Oct 29, 2016
aa6994d
Merge remote-tracking branch 'origin/develop' into feature/merge-db-#22
droidmonkey Oct 29, 2016
8945ee7
Merge remote-tracking branch 'origin/feature/search-viz-#15' into fea…
droidmonkey Oct 30, 2016
c0dbc45
Fixed crash in checkReloadDatabases and taint database if you do not …
droidmonkey Oct 30, 2016
72ef9fe
Merge remote-tracking branch 'origin/develop' into feature/merge-db-#22
droidmonkey Nov 3, 2016
2ee92fe
Merge remote-tracking branch 'origin/develop' into feature/merge-db-#22
droidmonkey Nov 3, 2016
4f7cdb5
Merge remote-tracking branch 'origin/develop' into feature/merge-db-#22
droidmonkey Nov 6, 2016
9066cd2
Reverted Detect background changes to database file. (reverted commi…
droidmonkey Nov 8, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/core/Database.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,12 @@ void Database::recycleGroup(Group* group)
}
}

void Database::merge(const Database* other)
{
m_rootGroup->merge(other->rootGroup());
Q_EMIT modified();
}

void Database::setEmitModified(bool value)
{
if (m_emitModified && !value) {
Expand Down
1 change: 1 addition & 0 deletions src/core/Database.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ class Database : public QObject
void recycleGroup(Group* group);
void setEmitModified(bool value);
void copyAttributesFrom(const Database* other);
void merge(const Database* other);

/**
* Returns a unique id that is only valid as long as the Database exists.
Expand Down
113 changes: 113 additions & 0 deletions src/core/Group.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Group::Group()
m_data.isExpanded = true;
m_data.autoTypeEnabled = Inherit;
m_data.searchingEnabled = Inherit;
m_data.mergeMode = ModeInherit;
}

Group::~Group()
Expand Down Expand Up @@ -196,6 +197,19 @@ Group::TriState Group::searchingEnabled() const
return m_data.searchingEnabled;
}

Group::MergeMode Group::mergeMode() const
{
if (m_data.mergeMode == Group::MergeMode::ModeInherit) {
if (m_parent) {
return m_parent->mergeMode();
} else {
return Group::MergeMode::KeepNewer; // fallback
}
} else {
return m_data.mergeMode;
}
}

Entry* Group::lastTopVisibleEntry() const
{
return m_lastTopVisibleEntry;
Expand Down Expand Up @@ -303,6 +317,11 @@ void Group::setExpiryTime(const QDateTime& dateTime)
}
}

void Group::setMergeMode(MergeMode newMode)
{
set(m_data.mergeMode, newMode);
}

Group* Group::parentGroup()
{
return m_parent;
Expand Down Expand Up @@ -440,6 +459,18 @@ QList<Entry*> Group::entriesRecursive(bool includeHistoryItems) const
return entryList;
}

Entry* Group::findEntry(const Uuid& uuid)
{
Q_ASSERT(!uuid.isNull());
for (Entry* entry : asConst(m_entries)) {
if (entry->uuid() == uuid) {
return entry;
}
}

return nullptr;
}

QList<const Group*> Group::groupsRecursive(bool includeSelf) const
{
QList<const Group*> groupList;
Expand Down Expand Up @@ -490,6 +521,44 @@ QSet<Uuid> Group::customIconsRecursive() const
return result;
}

void Group::merge(const Group* other)
{
// merge entries
const QList<Entry*> dbEntries = other->entries();
for (Entry* entry : dbEntries) {
// entries are searched by uuid
if (!findEntry(entry->uuid())) {
entry->clone(Entry::CloneNoFlags)->setGroup(this);
} else {
resolveConflict(this->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);
} else {
group->setParent(this);
}
}

Q_EMIT modified();
}

Group* Group::findChildByName(const QString& name)
{
for (Group* group : asConst(m_children)) {
if (group->name() == name) {
return group;
}
}

return nullptr;
}

Group* Group::clone(Entry::CloneFlags entryFlags) const
{
Group* clonedGroup = new Group();
Expand Down Expand Up @@ -624,6 +693,14 @@ void Group::recCreateDelObjects()
}
}

void Group::markOlderEntry(Entry* entry)
{
entry->attributes()->set(
"merged",
QString("older entry merged from database \"%1\"").arg(entry->group()->database()->metadata()->name())
);
}

bool Group::resolveSearchingEnabled() const
{
switch (m_data.searchingEnabled) {
Expand Down Expand Up @@ -663,3 +740,39 @@ bool Group::resolveAutoTypeEnabled() const
return false;
}
}

void Group::resolveConflict(Entry* existingEntry, Entry* otherEntry)
{
const QDateTime timeExisting = existingEntry->timeInfo().lastModificationTime();
const QDateTime timeOther = otherEntry->timeInfo().lastModificationTime();

Entry* clonedEntry;

switch(this->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);
} else if (timeExisting < timeOther) {
clonedEntry = otherEntry->clone(Entry::CloneNoFlags);
clonedEntry->setGroup(this);
this->markOlderEntry(existingEntry);
}
break;
case KeepNewer:
if (timeExisting < timeOther) {
// only if other entry is newer, replace existing one
this->removeEntry(existingEntry);
this->addEntry(otherEntry);
}

break;
case KeepExisting:
break;
default:
// do nothing
break;
}
}
9 changes: 9 additions & 0 deletions src/core/Group.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class Group : public QObject

public:
enum TriState { Inherit, Enable, Disable };
enum MergeMode { ModeInherit, KeepBoth, KeepNewer, KeepExisting };

struct GroupData
{
Expand All @@ -46,6 +47,7 @@ class Group : public QObject
QString defaultAutoTypeSequence;
Group::TriState autoTypeEnabled;
Group::TriState searchingEnabled;
Group::MergeMode mergeMode;
};

Group();
Expand All @@ -66,6 +68,7 @@ class Group : public QObject
QString defaultAutoTypeSequence() const;
Group::TriState autoTypeEnabled() const;
Group::TriState searchingEnabled() const;
Group::MergeMode mergeMode() const;
bool resolveSearchingEnabled() const;
bool resolveAutoTypeEnabled() const;
Entry* lastTopVisibleEntry() const;
Expand All @@ -74,6 +77,8 @@ class Group : public QObject
static const int DefaultIconNumber;
static const int RecycleBinIconNumber;

Entry* findEntry(const Uuid& uuid);
Group* findChildByName(const QString& name);
void setUuid(const Uuid& uuid);
void setName(const QString& name);
void setNotes(const QString& notes);
Expand All @@ -87,6 +92,7 @@ class Group : public QObject
void setLastTopVisibleEntry(Entry* entry);
void setExpires(bool value);
void setExpiryTime(const QDateTime& dateTime);
void setMergeMode(MergeMode newMode);

void setUpdateTimeinfo(bool value);

Expand All @@ -113,6 +119,7 @@ class Group : public QObject
*/
Group* clone(Entry::CloneFlags entryFlags = Entry::CloneNewUuid | Entry::CloneResetTimeInfo) const;
void copyDataFrom(const Group* other);
void merge(const Group* other);

Q_SIGNALS:
void dataChanged(Group* group);
Expand Down Expand Up @@ -142,6 +149,8 @@ class Group : public QObject
void addEntry(Entry* entry);
void removeEntry(Entry* entry);
void setParent(Database* db);
void markOlderEntry(Entry* entry);
void resolveConflict(Entry* existingEntry, Entry* otherEntry);

void recSetDatabase(Database* db);
void cleanupParent();
Expand Down
16 changes: 16 additions & 0 deletions src/gui/DatabaseTabWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

#include "autotype/AutoType.h"
#include "core/Config.h"
#include "core/Global.h"
#include "core/Database.h"
#include "core/Group.h"
#include "core/Metadata.h"
Expand Down Expand Up @@ -192,6 +193,21 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
}
}

void DatabaseTabWidget::mergeDatabase()
{
QString filter = QString("%1 (*.kdbx);;%2 (*)").arg(tr("KeePass 2 Database"), tr("All files"));
const QString fileName = fileDialog()->getOpenFileName(this, tr("Merge database"), QString(),
filter);
if (!fileName.isEmpty()) {
mergeDatabase(fileName);
}
}

void DatabaseTabWidget::mergeDatabase(const QString& fileName)
{
currentDatabaseWidget()->switchToOpenMergeDatabase(fileName);
}

void DatabaseTabWidget::importKeePass1Database()
{
QString fileName = fileDialog()->getOpenFileName(this, tr("Open KeePass 1 database"), QString(),
Expand Down
2 changes: 2 additions & 0 deletions src/gui/DatabaseTabWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class DatabaseTabWidget : public QTabWidget
~DatabaseTabWidget();
void openDatabase(const QString& fileName, const QString& pw = QString(),
const QString& keyFile = QString());
void mergeDatabase(const QString& fileName);
DatabaseWidget* currentDatabaseWidget();
bool hasLockableDatabases() const;

Expand All @@ -63,6 +64,7 @@ class DatabaseTabWidget : public QTabWidget
public Q_SLOTS:
void newDatabase();
void openDatabase();
void mergeDatabase();
void importKeePass1Database();
bool saveDatabase(int index = -1);
bool saveDatabaseAs(int index = -1);
Expand Down
53 changes: 53 additions & 0 deletions src/gui/DatabaseWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
m_databaseSettingsWidget->setObjectName("databaseSettingsWidget");
m_databaseOpenWidget = new DatabaseOpenWidget();
m_databaseOpenWidget->setObjectName("databaseOpenWidget");
m_databaseOpenMergeWidget = new DatabaseOpenWidget();
m_databaseOpenMergeWidget->setObjectName("databaseOpenMergeWidget");
m_keepass1OpenWidget = new KeePass1OpenWidget();
m_keepass1OpenWidget->setObjectName("keepass1OpenWidget");
m_unlockDatabaseWidget = new UnlockDatabaseWidget();
Expand All @@ -129,6 +131,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
addWidget(m_databaseSettingsWidget);
addWidget(m_historyEditEntryWidget);
addWidget(m_databaseOpenWidget);
addWidget(m_databaseOpenMergeWidget);
addWidget(m_keepass1OpenWidget);
addWidget(m_unlockDatabaseWidget);

Expand All @@ -147,6 +150,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
connect(m_changeMasterKeyWidget, SIGNAL(editFinished(bool)), SLOT(updateMasterKey(bool)));
connect(m_databaseSettingsWidget, SIGNAL(editFinished(bool)), SLOT(switchToView(bool)));
connect(m_databaseOpenWidget, SIGNAL(editFinished(bool)), SLOT(openDatabase(bool)));
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(this, SIGNAL(currentChanged(int)), this, SLOT(emitCurrentModeChanged()));
Expand Down Expand Up @@ -663,6 +667,28 @@ void DatabaseWidget::openDatabase(bool accepted)
}
}

void DatabaseWidget::mergeDatabase(bool accepted)
{
if (accepted) {
if (!m_db) {
MessageBox::critical(this, tr("Error"), tr("No current database."));
return;
}

Database* srcDb = static_cast<DatabaseOpenWidget*>(sender())->database();

if (!srcDb) {
MessageBox::critical(this, tr("Error"), tr("No source database, nothing to do."));
return;
}

m_db->merge(srcDb);
}

setCurrentWidget(m_mainWidget);
Q_EMIT databaseMerged(m_db);
}

void DatabaseWidget::unlockDatabase(bool accepted)
{
if (!accepted) {
Expand Down Expand Up @@ -745,6 +771,19 @@ void DatabaseWidget::switchToOpenDatabase(const QString& fileName, const QString
m_databaseOpenWidget->enterKey(password, keyFile);
}

void DatabaseWidget::switchToOpenMergeDatabase(const QString& fileName)
{
m_databaseOpenMergeWidget->load(fileName);
setCurrentWidget(m_databaseOpenMergeWidget);
}

void DatabaseWidget::switchToOpenMergeDatabase(const QString& fileName, const QString& password,
const QString& keyFile)
{
switchToOpenMergeDatabase(fileName);
m_databaseOpenMergeWidget->enterKey(password, keyFile);
}

void DatabaseWidget::switchToImportKeepass1(const QString& fileName)
{
updateFilename(fileName);
Expand Down Expand Up @@ -856,6 +895,12 @@ bool DatabaseWidget::isInSearchMode() const
return m_entryView->inEntryListMode();
}

Group* DatabaseWidget::currentGroup() const
{
return isInSearchMode() ? m_lastGroup
: m_groupView->currentGroup();
}

void DatabaseWidget::clearLastGroup(Group* group)
{
if (group) {
Expand Down Expand Up @@ -956,3 +1001,11 @@ bool DatabaseWidget::currentEntryHasNotes()
}
return !currentEntry->notes().isEmpty();
}

GroupView* DatabaseWidget::groupView() {
return m_groupView;
}

EntryView* DatabaseWidget::entryView() {
return m_entryView;
}
Loading