diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 376bc4b42..1eac615b1 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -25,10 +25,11 @@ Entry::Entry() { m_updateTimeinfo = true; + m_tmpHistoryItem = 0; - m_iconNumber = 0; - m_autoTypeEnabled = true; - m_autoTypeObfuscation = 0; + m_data.iconNumber = 0; + m_data.autoTypeEnabled = true; + m_data.autoTypeObfuscation = 0; m_attributes = new EntryAttributes(this); connect(m_attributes, SIGNAL(modified()), this, SIGNAL(modified())); @@ -37,6 +38,8 @@ Entry::Entry() m_attachments = new EntryAttachments(this); connect(m_attachments, SIGNAL(modified()), this, SIGNAL(modified())); connect(this, SIGNAL(modified()), this, SLOT(updateTimeinfo())); + + connect(this, SIGNAL(modified()), SLOT(updateModifiedSinceBegin())); } Entry::~Entry() @@ -67,12 +70,11 @@ template bool Entry::set(T& property, const T& value) void Entry::updateTimeinfo() { if (m_updateTimeinfo) { - m_timeInfo.setLastModificationTime(QDateTime::currentDateTimeUtc()); - m_timeInfo.setLastAccessTime(QDateTime::currentDateTimeUtc()); + m_data.timeInfo.setLastModificationTime(QDateTime::currentDateTimeUtc()); + m_data.timeInfo.setLastAccessTime(QDateTime::currentDateTimeUtc()); } } - void Entry::setUpdateTimeinfo(bool value) { m_updateTimeinfo = value; @@ -85,25 +87,25 @@ Uuid Entry::uuid() const QImage Entry::icon() const { - if (m_customIcon.isNull()) { - return databaseIcons()->icon(m_iconNumber); + if (m_data.customIcon.isNull()) { + return databaseIcons()->icon(m_data.iconNumber); } else { // TODO check if database() is 0 - return database()->metadata()->customIcon(m_customIcon); + return database()->metadata()->customIcon(m_data.customIcon); } } QPixmap Entry::iconPixmap() const { - if (m_customIcon.isNull()) { - return databaseIcons()->iconPixmap(m_iconNumber); + if (m_data.customIcon.isNull()) { + return databaseIcons()->iconPixmap(m_data.iconNumber); } else { QPixmap pixmap; if (!QPixmapCache::find(m_pixmapCacheKey, &pixmap)) { // TODO check if database() is 0 - pixmap = QPixmap::fromImage(database()->metadata()->customIcon(m_customIcon)); + pixmap = QPixmap::fromImage(database()->metadata()->customIcon(m_data.customIcon)); *const_cast(&m_pixmapCacheKey) = QPixmapCache::insert(pixmap); } @@ -113,57 +115,57 @@ QPixmap Entry::iconPixmap() const int Entry::iconNumber() const { - return m_iconNumber; + return m_data.iconNumber; } Uuid Entry::iconUuid() const { - return m_customIcon; + return m_data.customIcon; } QColor Entry::foregroundColor() const { - return m_foregroundColor; + return m_data.foregroundColor; } QColor Entry::backgroundColor() const { - return m_backgroundColor; + return m_data.backgroundColor; } QString Entry::overrideUrl() const { - return m_overrideUrl; + return m_data.overrideUrl; } QString Entry::tags() const { - return m_tags; + return m_data.tags; } TimeInfo Entry::timeInfo() const { - return m_timeInfo; + return m_data.timeInfo; } bool Entry::autoTypeEnabled() const { - return m_autoTypeEnabled; + return m_data.autoTypeEnabled; } int Entry::autoTypeObfuscation() const { - return m_autoTypeObfuscation; + return m_data.autoTypeObfuscation; } QString Entry::defaultAutoTypeSequence() const { - return m_defaultAutoTypeSequence; + return m_data.defaultAutoTypeSequence; } const QList& Entry::autoTypeAssociations() const { - return m_autoTypeAssociations; + return m_data.autoTypeAssociations; } QString Entry::title() const @@ -221,9 +223,9 @@ void Entry::setIcon(int iconNumber) { Q_ASSERT(iconNumber >= 0); - if (m_iconNumber != iconNumber || !m_customIcon.isNull()) { - m_iconNumber = iconNumber; - m_customIcon = Uuid(); + if (m_data.iconNumber != iconNumber || !m_data.customIcon.isNull()) { + m_data.iconNumber = iconNumber; + m_data.customIcon = Uuid(); m_pixmapCacheKey = QPixmapCache::Key(); @@ -235,9 +237,9 @@ void Entry::setIcon(const Uuid& uuid) { Q_ASSERT(!uuid.isNull()); - if (m_customIcon != uuid) { - m_customIcon = uuid; - m_iconNumber = 0; + if (m_data.customIcon != uuid) { + m_data.customIcon = uuid; + m_data.iconNumber = 0; m_pixmapCacheKey = QPixmapCache::Key(); @@ -247,47 +249,47 @@ void Entry::setIcon(const Uuid& uuid) void Entry::setForegroundColor(const QColor& color) { - set(m_foregroundColor, color); + set(m_data.foregroundColor, color); } void Entry::setBackgroundColor(const QColor& color) { - set(m_backgroundColor, color); + set(m_data.backgroundColor, color); } void Entry::setOverrideUrl(const QString& url) { - set(m_overrideUrl, url); + set(m_data.overrideUrl, url); } void Entry::setTags(const QString& tags) { - set(m_tags, tags); + set(m_data.tags, tags); } void Entry::setTimeInfo(const TimeInfo& timeInfo) { - m_timeInfo = timeInfo; + m_data.timeInfo = timeInfo; } void Entry::setAutoTypeEnabled(bool enable) { - set(m_autoTypeEnabled, enable); + set(m_data.autoTypeEnabled, enable); } void Entry::setAutoTypeObfuscation(int obfuscation) { - set(m_autoTypeObfuscation, obfuscation); + set(m_data.autoTypeObfuscation, obfuscation); } void Entry::setDefaultAutoTypeSequence(const QString& sequence) { - set(m_defaultAutoTypeSequence, sequence); + set(m_data.defaultAutoTypeSequence, sequence); } void Entry::addAutoTypeAssociation(const AutoTypeAssociation& assoc) { - m_autoTypeAssociations << assoc; + m_data.autoTypeAssociations << assoc; Q_EMIT modified(); } @@ -318,16 +320,16 @@ void Entry::setNotes(const QString& notes) void Entry::setExpires(const bool& value) { - if (m_timeInfo.expires() != value) { - m_timeInfo.setExpires(value); + if (m_data.timeInfo.expires() != value) { + m_data.timeInfo.setExpires(value); Q_EMIT modified(); } } void Entry::setExpiryTime(const QDateTime& dateTime) { - if (m_timeInfo.expiryTime() != dateTime) { - m_timeInfo.setExpiryTime(dateTime); + if (m_data.timeInfo.expiryTime() != dateTime) { + m_data.timeInfo.setExpiryTime(dateTime); Q_EMIT modified(); } } @@ -345,11 +347,46 @@ const QList& Entry::historyItems() const void Entry::addHistoryItem(Entry* entry) { Q_ASSERT(!entry->parent()); + Q_ASSERT(entry->uuid() == uuid()); m_history.append(entry); Q_EMIT modified(); } +void Entry::beginUpdate() +{ + Q_ASSERT(!m_tmpHistoryItem); + + m_tmpHistoryItem = new Entry(); + m_tmpHistoryItem->setUpdateTimeinfo(false); + m_tmpHistoryItem->m_uuid = m_uuid; + m_tmpHistoryItem->m_data = m_data; + *m_tmpHistoryItem->m_attributes = *m_attributes; + *m_tmpHistoryItem->m_attachments = *m_attachments; + + m_modifiedSinceBegin = false; +} + +void Entry::endUpdate() +{ + Q_ASSERT(m_tmpHistoryItem); + + if (m_modifiedSinceBegin) { + m_tmpHistoryItem->setUpdateTimeinfo(true); + addHistoryItem(m_tmpHistoryItem); + } + else { + delete m_tmpHistoryItem; + } + + m_tmpHistoryItem = 0; +} + +void Entry::updateModifiedSinceBegin() +{ + m_modifiedSinceBegin = true; +} + Group* Entry::group() { return m_group; @@ -373,7 +410,7 @@ void Entry::setGroup(Group* group) QObject::setParent(group); if (m_updateTimeinfo) { - m_timeInfo.setLocationChanged(QDateTime::currentDateTimeUtc()); + m_data.timeInfo.setLocationChanged(QDateTime::currentDateTimeUtc()); } } diff --git a/src/core/Entry.h b/src/core/Entry.h index e7917dd3a..0536109da 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -43,6 +43,21 @@ struct AutoTypeAssociation Q_DECLARE_TYPEINFO(AutoTypeAssociation, Q_MOVABLE_TYPE); +struct EntryData +{ + int iconNumber; + Uuid customIcon; + QColor foregroundColor; + QColor backgroundColor; + QString overrideUrl; + QString tags; + bool autoTypeEnabled; + int autoTypeObfuscation; + QString defaultAutoTypeSequence; + QList autoTypeAssociations; + TimeInfo timeInfo; +}; + class Entry : public QObject { Q_OBJECT @@ -97,6 +112,12 @@ public: QList historyItems(); const QList& historyItems() const; void addHistoryItem(Entry* entry); + /** + * Call before and after set*() methods to create a history item + * if the entry has been changed. + */ + void beginUpdate(); + void endUpdate(); Group* group(); void setGroup(Group* group); @@ -114,27 +135,20 @@ Q_SIGNALS: private Q_SLOTS: void emitDataChanged(); void updateTimeinfo(); + void updateModifiedSinceBegin(); private: const Database* database() const; template inline bool set(T& property, const T& value); Uuid m_uuid; - int m_iconNumber; - Uuid m_customIcon; - QColor m_foregroundColor; - QColor m_backgroundColor; - QString m_overrideUrl; - QString m_tags; - TimeInfo m_timeInfo; - bool m_autoTypeEnabled; - int m_autoTypeObfuscation; - QString m_defaultAutoTypeSequence; - QList m_autoTypeAssociations; + EntryData m_data; EntryAttributes* m_attributes; EntryAttachments* m_attachments; QList m_history; + Entry* m_tmpHistoryItem; + bool m_modifiedSinceBegin; QPointer m_group; QPixmapCache::Key m_pixmapCacheKey; bool m_updateTimeinfo; diff --git a/src/core/EntryAttachments.cpp b/src/core/EntryAttachments.cpp index 820d24a67..384dee3e1 100644 --- a/src/core/EntryAttachments.cpp +++ b/src/core/EntryAttachments.cpp @@ -73,22 +73,6 @@ void EntryAttachments::remove(const QString& key) Q_EMIT modified(); } -void EntryAttachments::copyFrom(const EntryAttachments* other) -{ - if (*this != *other) { - Q_EMIT aboutToBeReset(); - - m_attachments.clear(); - - Q_FOREACH (const QString& key, other->keys()) { - m_attachments.insert(key, other->value(key)); - } - - Q_EMIT reset(); - Q_EMIT modified(); - } -} - void EntryAttachments::clear() { if (m_attachments.isEmpty()) { @@ -103,7 +87,26 @@ void EntryAttachments::clear() Q_EMIT modified(); } +bool EntryAttachments::operator==(const EntryAttachments& other) const +{ + return m_attachments == other.m_attachments; +} + bool EntryAttachments::operator!=(const EntryAttachments& other) const { return m_attachments != other.m_attachments; } + +EntryAttachments& EntryAttachments::operator=(EntryAttachments& other) +{ + if (*this != other) { + Q_EMIT aboutToBeReset(); + + m_attachments = other.m_attachments; + + Q_EMIT reset(); + Q_EMIT modified(); + } + + return *this; +} diff --git a/src/core/EntryAttachments.h b/src/core/EntryAttachments.h index be1876f0c..675f12c76 100644 --- a/src/core/EntryAttachments.h +++ b/src/core/EntryAttachments.h @@ -31,9 +31,10 @@ public: QByteArray value(const QString& key) const; void set(const QString& key, const QByteArray& value); void remove(const QString& key); - void copyFrom(const EntryAttachments* other); void clear(); + bool operator==(const EntryAttachments& other) const; bool operator!=(const EntryAttachments& other) const; + EntryAttachments& operator=(EntryAttachments& other); Q_SIGNALS: void modified(); diff --git a/src/core/EntryAttributes.cpp b/src/core/EntryAttributes.cpp index eec7c364b..e9505e65b 100644 --- a/src/core/EntryAttributes.cpp +++ b/src/core/EntryAttributes.cpp @@ -149,6 +149,33 @@ bool EntryAttributes::areCustomKeysDifferent(const EntryAttributes* other) return false; } +EntryAttributes& EntryAttributes::operator=(const EntryAttributes& other) +{ + if (*this != other) { + Q_EMIT aboutToBeReset(); + + m_attributes = other.m_attributes; + m_protectedAttributes = other.m_protectedAttributes; + + Q_EMIT reset(); + Q_EMIT modified(); + } + + return *this; +} + +bool EntryAttributes::operator==(const EntryAttributes& other) const +{ + return (m_attributes == other.m_attributes + && m_protectedAttributes == other.m_protectedAttributes); +} + +bool EntryAttributes::operator!=(const EntryAttributes& other) const +{ + return (m_attributes != other.m_attributes + || m_protectedAttributes != other.m_protectedAttributes); +} + void EntryAttributes::clear() { Q_EMIT aboutToBeReset(); diff --git a/src/core/EntryAttributes.h b/src/core/EntryAttributes.h index 490b5567c..43c0fd7f3 100644 --- a/src/core/EntryAttributes.h +++ b/src/core/EntryAttributes.h @@ -35,8 +35,11 @@ public: void set(const QString& key, const QString& value, bool protect = false); void remove(const QString& key); void copyCustomKeysFrom(const EntryAttributes* other); - void clear(); bool areCustomKeysDifferent(const EntryAttributes* other); + void clear(); + EntryAttributes& operator=(const EntryAttributes& other); + bool operator==(const EntryAttributes& other) const; + bool operator!=(const EntryAttributes& other) const; static const QStringList DEFAULT_ATTRIBUTES; static bool isDefaultAttribute(const QString& key); diff --git a/src/gui/EditEntryWidget.cpp b/src/gui/EditEntryWidget.cpp index 91a6f96e0..d90158268 100644 --- a/src/gui/EditEntryWidget.cpp +++ b/src/gui/EditEntryWidget.cpp @@ -115,7 +115,7 @@ void EditEntryWidget::loadEntry(Entry* entry, bool create, const QString& groupN m_entryAttributes->copyCustomKeysFrom(entry->attributes()); m_attributesModel->setEntryAttributes(m_entryAttributes); - m_entryAttachments->copyFrom(entry->attachments()); + *m_entryAttachments = *entry->attachments(); m_attachmentsModel->setEntryAttachments(m_entryAttachments); m_ui->categoryList->setCurrentRow(0); @@ -127,6 +127,9 @@ void EditEntryWidget::saveEntry() QMessageBox::warning(this, tr("Error"), tr("Different passwords supplied.")); return; } + + m_entry->beginUpdate(); + m_entry->setTitle(m_mainUi->titleEdit->text()); m_entry->setUsername(m_mainUi->usernameEdit->text()); m_entry->setUrl(m_mainUi->urlEdit->text()); @@ -138,9 +141,11 @@ void EditEntryWidget::saveEntry() m_entry->attributes()->copyCustomKeysFrom(m_entryAttributes); m_entryAttributes->clear(); - m_entry->attachments()->copyFrom(m_entryAttachments); + *m_entry->attachments() = *m_entryAttachments; m_entryAttachments->clear(); + m_entry->endUpdate(); + m_entry = 0; Q_EMIT editFinished(true); } diff --git a/tests/TestModified.cpp b/tests/TestModified.cpp index 65bb832a1..6f3615f60 100644 --- a/tests/TestModified.cpp +++ b/tests/TestModified.cpp @@ -285,4 +285,63 @@ void TestModified::testEntrySets() delete db; } +void TestModified::testHistoryItem() +{ + Entry* entry = new Entry(); + QDateTime created = entry->timeInfo().creationTime(); + entry->setUuid(Uuid::random()); + entry->setTitle("a"); + entry->setTags("a"); + + EntryAttributes* attributes = new EntryAttributes(); + attributes->copyCustomKeysFrom(entry->attributes()); + + Entry* historyEntry; + + int historyItemsSize = 0; + + entry->beginUpdate(); + entry->setTitle("a"); + entry->setTags("a"); + entry->setOverrideUrl(""); + entry->endUpdate(); + QCOMPARE(entry->historyItems().size(), historyItemsSize); + + QDateTime modified = entry->timeInfo().lastModificationTime(); + QTest::qSleep(10); + entry->beginUpdate(); + entry->setTitle("b"); + entry->endUpdate(); + QCOMPARE(entry->historyItems().size(), ++historyItemsSize); + historyEntry = entry->historyItems().at(historyItemsSize - 1); + QCOMPARE(historyEntry->title(), QString("a")); + QCOMPARE(historyEntry->uuid(), entry->uuid()); + QCOMPARE(historyEntry->tags(), entry->tags()); + QCOMPARE(historyEntry->overrideUrl(), entry->overrideUrl()); + QCOMPARE(historyEntry->timeInfo().creationTime(), created); + QCOMPARE(historyEntry->timeInfo().lastModificationTime(), modified); + + entry->beginUpdate(); + entry->setTags("b"); + entry->endUpdate(); + QCOMPARE(entry->historyItems().size(), ++historyItemsSize); + QCOMPARE(entry->historyItems().at(historyItemsSize - 1)->tags(), QString("a")); + + entry->beginUpdate(); + entry->attachments()->set("test", QByteArray("value")); + entry->endUpdate(); + QCOMPARE(entry->historyItems().size(), ++historyItemsSize); + QCOMPARE(entry->historyItems().at(historyItemsSize - 1)->attachments()->keys().size(), 0); + + attributes->set("k", "myvalue"); + entry->beginUpdate(); + entry->attributes()->copyCustomKeysFrom(attributes); + entry->endUpdate(); + QCOMPARE(entry->historyItems().size(), ++historyItemsSize); + QVERIFY(!entry->historyItems().at(historyItemsSize - 1)->attributes()->keys().contains("k")); + + delete attributes; + delete entry; +} + KEEPASSX_QTEST_CORE_MAIN(TestModified) diff --git a/tests/TestModified.h b/tests/TestModified.h index 3f4de946c..fc9290d1e 100644 --- a/tests/TestModified.h +++ b/tests/TestModified.h @@ -29,6 +29,7 @@ private Q_SLOTS: void testSignals(); void testGroupSets(); void testEntrySets(); + void testHistoryItem(); }; #endif // KEEPASSX_TESTMODIFIED_H