Skip to content

Commit

Permalink
Feature : Update entries across groups when merging
Browse files Browse the repository at this point in the history
  • Loading branch information
Louis-Bertrand Varin authored and louib committed Aug 30, 2017
1 parent 5e309fe commit d86a2b3
Show file tree
Hide file tree
Showing 8 changed files with 547 additions and 152 deletions.
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
75 changes: 49 additions & 26 deletions src/core/Group.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -488,16 +488,15 @@ 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;
}
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 @@ -655,25 +654,45 @@ 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 +710,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 +719,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 @@ -884,20 +905,22 @@ void Group::resolveConflict(Entry* existingEntry, Entry* otherEntry)
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 = otherEntry->clone(Entry::CloneNewUuid);
clonedEntry->setGroup(this);
markOlderEntry(clonedEntry);
} else if (timeExisting < timeOther) {
clonedEntry = otherEntry->clone(Entry::CloneNoFlags);
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
removeEntry(existingEntry);
addEntry(otherEntry->clone(Entry::CloneNoFlags));
Group* currentGroup = existingEntry->group();
currentGroup->removeEntry(existingEntry);
otherEntry->clone(Entry::CloneNoFlags)->setGroup(currentGroup);
}

break;
Expand Down
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

0 comments on commit d86a2b3

Please sign in to comment.