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

Feature : Update entries across groups when merging #807

Merged
merged 2 commits into from
Sep 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ release*/
*.kdev4

\.vscode/
*.swp
125 changes: 72 additions & 53 deletions src/core/Group.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -488,18 +488,17 @@ Entry* Group::findEntry(QString entryId)
{
Q_ASSERT(!entryId.isNull());

Entry* entry;
if (Uuid::isUuid(entryId)) {
Uuid entryUuid = Uuid::fromHex(entryId);
for (Entry* entry : entriesRecursive(false)) {
if (entry->uuid() == entryUuid) {
return entry;
}
entry = findEntryByUuid(Uuid::fromHex(entryId));
if (entry) {
return entry;
}
}

Entry* entry = findEntryByPath(entryId);
entry = findEntryByPath(entryId);
if (entry) {
return entry;
return entry;
}

for (Entry* entry : entriesRecursive(false)) {
Expand All @@ -514,7 +513,7 @@ Entry* Group::findEntry(QString entryId)
Entry* Group::findEntryByUuid(const Uuid& uuid)
{
Q_ASSERT(!uuid.isNull());
for (Entry* entry : asConst(m_entries)) {
for (Entry* entry : entriesRecursive(false)) {
if (entry->uuid() == uuid) {
return entry;
}
Expand Down Expand Up @@ -575,7 +574,6 @@ Group* Group::findGroupByPath(QString groupPath, QString basePath)
}

return nullptr;

}

QString Group::print(bool recursive, int depth)
Expand Down Expand Up @@ -655,25 +653,44 @@ QSet<Uuid> Group::customIconsRecursive() const

void Group::merge(const Group* other)
{

Group* rootGroup = this;
while (rootGroup->parentGroup()) {
rootGroup = rootGroup->parentGroup();
}

// merge entries
const QList<Entry*> dbEntries = other->entries();
for (Entry* entry : dbEntries) {
// entries are searched by uuid
if (!findEntryByUuid(entry->uuid())) {

Entry* existingEntry = rootGroup->findEntryByUuid(entry->uuid());

// This entry does not exist at all. Create it.
if (!existingEntry) {
qDebug("New entry %s detected. Creating it.", qPrintable(entry->title()));
entry->clone(Entry::CloneNoFlags)->setGroup(this);
// Entry is already present in the database. Update it.
} else {
resolveConflict(findEntryByUuid(entry->uuid()), entry);
bool locationChanged = existingEntry->timeInfo().locationChanged() < entry->timeInfo().locationChanged();
if (locationChanged && existingEntry->group() != this) {
existingEntry->setGroup(this);
qDebug("Location changed for entry %s. Updating it", qPrintable(existingEntry->title()));
}
resolveConflict(existingEntry, entry);
}
}

// merge groups (recursively)
// merge groups recursively
const QList<Group*> dbChildren = other->children();
for (Group* group : dbChildren) {
// groups are searched by name instead of uuid
if (findChildByName(group->name())) {
findChildByName(group->name())->merge(group);
} else {
group->setParent(this);
qDebug("New group %s detected. Creating it.", qPrintable(group->name()));
Group* newGroup = group->clone(Entry::CloneNoFlags, true);
newGroup->setParent(this);
newGroup->merge(group);
}
}

Expand All @@ -691,7 +708,7 @@ Group* Group::findChildByName(const QString& name)
return nullptr;
}

Group* Group::clone(Entry::CloneFlags entryFlags) const
Group* Group::clone(Entry::CloneFlags entryFlags, bool shallow) const
{
Group* clonedGroup = new Group();

Expand All @@ -700,16 +717,18 @@ Group* Group::clone(Entry::CloneFlags entryFlags) const
clonedGroup->setUuid(Uuid::random());
clonedGroup->m_data = m_data;

const QList<Entry*> entryList = entries();
for (Entry* entry : entryList) {
Entry* clonedEntry = entry->clone(entryFlags);
clonedEntry->setGroup(clonedGroup);
}
if (!shallow) {
const QList<Entry*> entryList = entries();
for (Entry* entry : entryList) {
Entry* clonedEntry = entry->clone(entryFlags);
clonedEntry->setGroup(clonedGroup);
}

const QList<Group*> childrenGroups = children();
for (Group* groupChild : childrenGroups) {
Group* clonedGroupChild = groupChild->clone(entryFlags);
clonedGroupChild->setParent(clonedGroup);
const QList<Group*> childrenGroups = children();
for (Group* groupChild : childrenGroups) {
Group* clonedGroupChild = groupChild->clone(entryFlags);
clonedGroupChild->setParent(clonedGroup);
}
}

clonedGroup->setUpdateTimeinfo(true);
Expand Down Expand Up @@ -829,8 +848,7 @@ void Group::markOlderEntry(Entry* entry)
{
entry->attributes()->set(
"merged",
QString("older entry merged from database \"%1\"").arg(entry->group()->database()->metadata()->name())
);
QString("older entry merged from database \"%1\"").arg(entry->group()->database()->metadata()->name()));
}

bool Group::resolveSearchingEnabled() const
Expand Down Expand Up @@ -880,32 +898,34 @@ void Group::resolveConflict(Entry* existingEntry, Entry* otherEntry)

Entry* clonedEntry;

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

break;
case KeepExisting:
break;
default:
// do nothing
break;
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::CloneNewUuid);
clonedEntry->setGroup(this);
markOlderEntry(clonedEntry);
} else if (timeExisting < timeOther) {
clonedEntry = otherEntry->clone(Entry::CloneNewUuid);
clonedEntry->setGroup(this);
markOlderEntry(existingEntry);
}
break;
case KeepNewer:
if (timeExisting < timeOther) {
qDebug("Updating entry %s.", qPrintable(existingEntry->title()));
// only if other entry is newer, replace existing one
Group* currentGroup = existingEntry->group();
currentGroup->removeEntry(existingEntry);
otherEntry->clone(Entry::CloneNoFlags)->setGroup(currentGroup);
}

break;
case KeepExisting:
break;
default:
// do nothing
break;
}
}

Expand All @@ -928,5 +948,4 @@ QStringList Group::locate(QString locateTerm, QString currentPath)
}

return response;

}
5 changes: 3 additions & 2 deletions src/core/Group.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,14 @@ class Group : public QObject
QList<Group*> groupsRecursive(bool includeSelf);
QSet<Uuid> customIconsRecursive() const;
/**
* Creates a duplicate of this group including all child entries and groups.
* Creates a duplicate of this group including all child entries and groups (if not shallow).
* The exceptions are that the returned group doesn't have a parent group
* and all TimeInfo attributes are set to the current time.
* Note that you need to copy the custom icons manually when inserting the
* new group into another database.
*/
Group* clone(Entry::CloneFlags entryFlags = Entry::CloneNewUuid | Entry::CloneResetTimeInfo) const;
Group* clone(Entry::CloneFlags entryFlags = Entry::CloneNewUuid | Entry::CloneResetTimeInfo,
bool shallow = false) const;
void copyDataFrom(const Group* other);
void merge(const Group* other);
QString print(bool recursive = false, int depth = 0);
Expand Down
3 changes: 3 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ endif()
add_unit_test(NAME testentry SOURCES TestEntry.cpp
LIBS ${TEST_LIBRARIES})

add_unit_test(NAME testmerge SOURCES TestMerge.cpp
LIBS ${TEST_LIBRARIES})

add_unit_test(NAME testtotp SOURCES TestTotp.cpp
LIBS ${TEST_LIBRARIES})

Expand Down
117 changes: 0 additions & 117 deletions tests/TestGroup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -452,123 +452,6 @@ void TestGroup::testCopyCustomIcons()
delete dbSource;
}

void TestGroup::testMerge()
{
Group* group1 = new Group();
group1->setName("group 1");
Group* group2 = new Group();
group2->setName("group 2");

Entry* entry1 = new Entry();
Entry* entry2 = new Entry();

entry1->setGroup(group1);
entry1->setUuid(Uuid::random());
entry2->setGroup(group1);
entry2->setUuid(Uuid::random());

group2->merge(group1);

QCOMPARE(group1->entries().size(), 2);
QCOMPARE(group2->entries().size(), 2);
}

void TestGroup::testMergeDatabase()
{
Database* dbSource = createMergeTestDatabase();
Database* dbDest = new Database();

dbDest->merge(dbSource);

QCOMPARE(dbDest->rootGroup()->children().size(), 2);
QCOMPARE(dbDest->rootGroup()->children().at(0)->entries().size(), 2);

delete dbDest;
delete dbSource;
}

void TestGroup::testMergeConflict()
{
Database* dbSource = createMergeTestDatabase();

// test merging updated entries
// falls back to KeepBoth mode
Database* dbCopy = new Database();
dbCopy->setRootGroup(dbSource->rootGroup()->clone(Entry::CloneNoFlags));

// sanity check
QCOMPARE(dbCopy->rootGroup()->children().at(0)->entries().size(), 2);

// make this entry newer than in original db
Entry* updatedEntry = dbCopy->rootGroup()->children().at(0)->entries().at(0);
TimeInfo updatedTimeInfo = updatedEntry->timeInfo();
updatedTimeInfo.setLastModificationTime(updatedTimeInfo.lastModificationTime().addYears(1));
updatedEntry->setTimeInfo(updatedTimeInfo);

dbCopy->merge(dbSource);

// one entry is duplicated because of mode
QCOMPARE(dbCopy->rootGroup()->children().at(0)->entries().size(), 2);

delete dbSource;
delete dbCopy;
}

void TestGroup::testMergeConflictKeepBoth()
{
Database* dbSource = createMergeTestDatabase();

// test merging updated entries
// falls back to KeepBoth mode
Database* dbCopy = new Database();
dbCopy->setRootGroup(dbSource->rootGroup()->clone(Entry::CloneNoFlags));

// sanity check
QCOMPARE(dbCopy->rootGroup()->children().at(0)->entries().size(), 2);

// make this entry newer than in original db
Entry* updatedEntry = dbCopy->rootGroup()->children().at(0)->entries().at(0);
TimeInfo updatedTimeInfo = updatedEntry->timeInfo();
updatedTimeInfo.setLastModificationTime(updatedTimeInfo.lastModificationTime().addYears(1));
updatedEntry->setTimeInfo(updatedTimeInfo);

dbCopy->rootGroup()->setMergeMode(Group::MergeMode::KeepBoth);

dbCopy->merge(dbSource);

// one entry is duplicated because of mode
QCOMPARE(dbCopy->rootGroup()->children().at(0)->entries().size(), 3);
// the older entry was merged from the other db as last in the group
Entry* olderEntry = dbCopy->rootGroup()->children().at(0)->entries().at(2);
QVERIFY2(olderEntry->attributes()->hasKey("merged"), "older entry is marked with an attribute \"merged\"");

delete dbSource;
delete dbCopy;
}

Database* TestGroup::createMergeTestDatabase()
{
Database* db = new Database();

Group* group1 = new Group();
group1->setName("group 1");
Group* group2 = new Group();
group2->setName("group 2");

Entry* entry1 = new Entry();
Entry* entry2 = new Entry();

entry1->setGroup(group1);
entry1->setUuid(Uuid::random());
entry2->setGroup(group1);
entry2->setUuid(Uuid::random());

group1->setParent(db->rootGroup());
group2->setParent(db->rootGroup());

return db;
}

void TestGroup::testFindEntry()
{
Database* db = new Database();
Expand Down
7 changes: 0 additions & 7 deletions tests/TestGroup.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,10 @@ private slots:
void testCopyCustomIcon();
void testClone();
void testCopyCustomIcons();
void testMerge();
void testMergeConflict();
void testMergeDatabase();
void testMergeConflictKeepBoth();
void testFindEntry();
void testFindGroupByPath();
void testPrint();
void testLocate();

private:
Database* createMergeTestDatabase();
};

#endif // KEEPASSX_TESTGROUP_H
Loading