diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 39c9ad285..41616cb82 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -439,22 +439,40 @@ void Entry::truncateHistory() } } -Entry* Entry::clone() const +Entry* Entry::clone(CloneFlags flags) const { Entry* entry = new Entry(); entry->setUpdateTimeinfo(false); - entry->m_uuid = Uuid::random(); + if (flags & CloneNewUuid) { + entry->m_uuid = Uuid::random(); + } + else { + entry->m_uuid = m_uuid; + } entry->m_data = m_data; entry->m_attributes->copyDataFrom(m_attributes); entry->m_attachments->copyDataFrom(m_attachments); entry->m_autoTypeAssociations->copyDataFrom(this->m_autoTypeAssociations); + if (flags & CloneIncludeHistory) { + Q_FOREACH (Entry* historyItem, m_history) { + Entry* historyItemClone = historyItem->clone(flags & ~CloneIncludeHistory & ~CloneNewUuid); + historyItemClone->setUpdateTimeinfo(false); + historyItemClone->setUuid(entry->uuid()); + historyItemClone->setUpdateTimeinfo(true); + entry->addHistoryItem(historyItemClone); + } + } entry->setUpdateTimeinfo(true); - QDateTime now = Tools::currentDateTimeUtc(); - entry->m_data.timeInfo.setCreationTime(now); - entry->m_data.timeInfo.setLastModificationTime(now); - entry->m_data.timeInfo.setLastAccessTime(now); - entry->m_data.timeInfo.setLocationChanged(now); + if (flags & CloneResetTimeInfo) { + QDateTime now = Tools::currentDateTimeUtc(); + entry->m_data.timeInfo.setCreationTime(now); + entry->m_data.timeInfo.setLastModificationTime(now); + entry->m_data.timeInfo.setLastAccessTime(now); + entry->m_data.timeInfo.setLocationChanged(now); + } + + return entry; } diff --git a/src/core/Entry.h b/src/core/Entry.h index 4343c1406..c2c2938c1 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -110,13 +110,22 @@ public: void addHistoryItem(Entry* entry); void removeHistoryItems(const QList& historyEntries); void truncateHistory(); + + enum CloneFlag { + CloneNoFlags = 0, + CloneNewUuid = 1, // generate a random uuid for the clone + CloneResetTimeInfo = 2, // set all TimeInfo attributes to the current time + CloneIncludeHistory = 4 // clone the history items + }; + Q_DECLARE_FLAGS(CloneFlags, CloneFlag) + /** - * Creates a duplicate of this entry except that returned entry isn't part - * of any group and all TimeInfo attributes are set to the current time. + * Creates a duplicate of this entry except that the returned entry isn't + * part of any group. * Note that you need to copy the custom icons manually when inserting the * new entry into another database. */ - Entry* clone() const; + Entry* clone(CloneFlags flags) const; void copyDataFrom(const Entry* other); QString resolvePlaceholders(const QString& str) const; @@ -166,4 +175,6 @@ private: bool m_updateTimeinfo; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(Entry::CloneFlags) + #endif // KEEPASSX_ENTRY_H diff --git a/src/core/Group.cpp b/src/core/Group.cpp index 4fffe7182..9c192a831 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -474,7 +474,7 @@ Group* Group::clone() const clonedGroup->m_data = m_data; Q_FOREACH (Entry* entry, entries()) { - Entry* clonedEntry = entry->clone(); + Entry* clonedEntry = entry->clone(Entry::CloneNewUuid | Entry::CloneResetTimeInfo); clonedEntry->setGroup(clonedGroup); } diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index d6484478d..8c635a70f 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -224,7 +224,7 @@ void DatabaseWidget::cloneEntry() return; } - Entry* entry = currentEntry->clone(); + Entry* entry = currentEntry->clone(Entry::CloneNewUuid | Entry::CloneResetTimeInfo); entry->setGroup(currentEntry->group()); m_entryView->setFocus(); m_entryView->setCurrentEntry(entry); diff --git a/src/gui/group/GroupModel.cpp b/src/gui/group/GroupModel.cpp index 73b9a59d9..b33a5ff54 100644 --- a/src/gui/group/GroupModel.cpp +++ b/src/gui/group/GroupModel.cpp @@ -298,7 +298,7 @@ bool GroupModel::dropMimeData(const QMimeData* data, Qt::DropAction action, entry = dragEntry; } else { - entry = dragEntry->clone(); + entry = dragEntry->clone(Entry::CloneNewUuid | Entry::CloneResetTimeInfo); } Database* sourceDb = dragEntry->group()->database(); diff --git a/tests/TestEntry.cpp b/tests/TestEntry.cpp index e3c46b100..9c027b6a2 100644 --- a/tests/TestEntry.cpp +++ b/tests/TestEntry.cpp @@ -21,6 +21,12 @@ #include "tests.h" #include "core/Entry.h" +#include "crypto/Crypto.h" + +void TestEntry::initTestCase() +{ + Crypto::init(); +} void TestEntry::testHistoryItemDeletion() { @@ -74,4 +80,46 @@ void TestEntry::testCopyDataFrom() QCOMPARE(entry2->autoTypeAssociations()->get(1).window, QString("3")); } +void TestEntry::testClone() +{ + Entry* entryOrg = new Entry(); + entryOrg->setUuid(Uuid::random()); + entryOrg->setTitle("Original Title"); + entryOrg->beginUpdate(); + entryOrg->setTitle("New Title"); + entryOrg->endUpdate(); + TimeInfo entryOrgTime = entryOrg->timeInfo(); + QDateTime dateTime; + dateTime.setTimeSpec(Qt::UTC); + dateTime.setMSecsSinceEpoch(60); + entryOrgTime.setCreationTime(dateTime); + entryOrg->setTimeInfo(entryOrgTime); + + Entry* entryCloneNone = entryOrg->clone(Entry::CloneNoFlags); + QCOMPARE(entryCloneNone->uuid(), entryOrg->uuid()); + QCOMPARE(entryCloneNone->title(), QString("New Title")); + QCOMPARE(entryCloneNone->historyItems().size(), 0); + QCOMPARE(entryCloneNone->timeInfo().creationTime(), entryOrg->timeInfo().creationTime()); + + Entry* entryCloneNewUuid = entryOrg->clone(Entry::CloneNewUuid); + QVERIFY(entryCloneNewUuid->uuid() != entryOrg->uuid()); + QVERIFY(!entryCloneNewUuid->uuid().isNull()); + QCOMPARE(entryCloneNewUuid->title(), QString("New Title")); + QCOMPARE(entryCloneNewUuid->historyItems().size(), 0); + QCOMPARE(entryCloneNewUuid->timeInfo().creationTime(), entryOrg->timeInfo().creationTime()); + + Entry* entryCloneResetTime = entryOrg->clone(Entry::CloneResetTimeInfo); + QCOMPARE(entryCloneNone->uuid(), entryOrg->uuid()); + QCOMPARE(entryCloneResetTime->title(), QString("New Title")); + QCOMPARE(entryCloneResetTime->historyItems().size(), 0); + QVERIFY(entryCloneResetTime->timeInfo().creationTime() != entryOrg->timeInfo().creationTime()); + + Entry* entryCloneHistory = entryOrg->clone(Entry::CloneIncludeHistory); + QCOMPARE(entryCloneNone->uuid(), entryOrg->uuid()); + QCOMPARE(entryCloneHistory->title(), QString("New Title")); + QCOMPARE(entryCloneHistory->historyItems().size(), 1); + QCOMPARE(entryCloneHistory->historyItems().first()->title(), QString("Original Title")); + QCOMPARE(entryCloneHistory->timeInfo().creationTime(), entryOrg->timeInfo().creationTime()); +} + QTEST_GUILESS_MAIN(TestEntry) diff --git a/tests/TestEntry.h b/tests/TestEntry.h index fe303a609..ed772d505 100644 --- a/tests/TestEntry.h +++ b/tests/TestEntry.h @@ -27,8 +27,10 @@ class TestEntry : public QObject Q_OBJECT private Q_SLOTS: + void initTestCase(); void testHistoryItemDeletion(); void testCopyDataFrom(); + void testClone(); }; #endif // KEEPASSX_TESTENTRY_H diff --git a/tests/TestGroup.cpp b/tests/TestGroup.cpp index 22845af11..65d4eeda3 100644 --- a/tests/TestGroup.cpp +++ b/tests/TestGroup.cpp @@ -441,8 +441,11 @@ void TestGroup::testClone() Entry* originalGroupEntry = new Entry(); originalGroupEntry->setGroup(originalGroup); - originalGroupEntry->setTitle("GroupEntry"); + originalGroupEntry->setTitle("GroupEntryOld"); originalGroupEntry->setIcon(43); + originalGroupEntry->beginUpdate(); + originalGroupEntry->setTitle("GroupEntry"); + originalGroupEntry->endUpdate(); Group* subGroup = new Group(); subGroup->setParent(originalGroup); @@ -465,6 +468,7 @@ void TestGroup::testClone() QVERIFY(clonedGroupEntry->uuid() != originalGroupEntry->uuid()); QCOMPARE(clonedGroupEntry->title(), QString("GroupEntry")); QCOMPARE(clonedGroupEntry->iconNumber(), 43); + QCOMPARE(clonedGroupEntry->historyItems().size(), 0); Group* clonedSubGroup = clonedGroup->children().at(0); QVERIFY(clonedSubGroup->uuid() != subGroup->uuid());