Introduced missing CustomData on Group and Entry

Introduce missing CustomData-attributes of KDBX4 format to allow
storing of plugin data for groups and entries - adopt Metadata to use
the same storage mechanism
Add simple view for CustomData as part of EditWidgetProperties
Tracking of CustomData-Modification using SIGNAL-SLOT update-mechanism
This commit is contained in:
Christian Kieschnick 2018-02-06 16:37:06 +01:00 committed by Janek Bevendorff
parent 698b44f71c
commit 0b54710734
18 changed files with 500 additions and 118 deletions

View File

@ -42,6 +42,7 @@ set(keepassx_SOURCES
core/AutoTypeMatch.cpp core/AutoTypeMatch.cpp
core/Config.cpp core/Config.cpp
core/CsvParser.cpp core/CsvParser.cpp
core/CustomData.cpp
core/Database.cpp core/Database.cpp
core/DatabaseIcons.cpp core/DatabaseIcons.cpp
core/Entry.cpp core/Entry.cpp

156
src/core/CustomData.cpp Normal file
View File

@ -0,0 +1,156 @@
/*
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "CustomData.h"
CustomData::CustomData(QObject* parent)
: QObject(parent)
{
}
QList<QString> CustomData::keys() const
{
return m_data.keys();
}
bool CustomData::hasKey(const QString& key) const
{
return m_data.contains(key);
}
QString CustomData::value(const QString& key) const
{
return m_data.value(key);
}
bool CustomData::contains(const QString& key) const
{
return m_data.contains(key);
}
bool CustomData::containsValue(const QString& value) const
{
return m_data.values().contains(value);
}
void CustomData::set(const QString& key, const QString& value)
{
bool emitModified = false;
bool addAttribute = !m_data.contains(key);
bool changeValue = !addAttribute && (m_data.value(key) != value);
if (addAttribute ) {
emit aboutToBeAdded(key);
}
if (addAttribute || changeValue) {
m_data.insert(key, value);
emitModified = true;
}
if (emitModified) {
emit modified();
}
if (addAttribute) {
emit added(key);
}
}
void CustomData::remove(const QString& key)
{
emit aboutToBeRemoved(key);
m_data.remove(key);
emit removed(key);
emit modified();
}
void CustomData::rename(const QString& oldKey, const QString& newKey)
{
if (!m_data.contains(oldKey)) {
Q_ASSERT(false);
return;
}
if (m_data.contains(newKey)) {
Q_ASSERT(false);
return;
}
QString data = value(oldKey);
emit aboutToRename(oldKey, newKey);
m_data.remove(oldKey);
m_data.insert(newKey, data);
emit modified();
emit renamed(oldKey, newKey);
}
void CustomData::copyDataFrom(const CustomData* other)
{
if (*this != *other) {
emit aboutToBeReset();
m_data = other->m_data;
emit reset();
emit modified();
}
}
bool CustomData::operator==(const CustomData& other) const
{
return (m_data == other.m_data);
}
bool CustomData::operator!=(const CustomData& other) const
{
return (m_data != other.m_data);
}
void CustomData::clear()
{
emit aboutToBeReset();
m_data.clear();
emit reset();
emit modified();
}
bool CustomData::isEmpty() const
{
return m_data.isEmpty();
}
int CustomData::dataSize()
{
int size = 0;
QHashIterator<QString, QString> i(m_data);
while (i.hasNext()) {
i.next();
size += i.key().toUtf8().size() + i.value().toUtf8().size();
}
return size;
}

64
src/core/CustomData.h Normal file
View File

@ -0,0 +1,64 @@
/*
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSX_CUSTOMDATA_H
#define KEEPASSX_CUSTOMDATA_H
#include <QMap>
#include <QObject>
#include <QSet>
#include <QStringList>
class CustomData : public QObject
{
Q_OBJECT
public:
explicit CustomData(QObject* parent = nullptr);
QList<QString> keys() const;
bool hasKey(const QString& key) const;
QString value(const QString& key) const;
bool contains(const QString& key) const;
bool containsValue(const QString& value) const;
void set(const QString& key, const QString& value);
void remove(const QString& key);
void rename(const QString& oldKey, const QString& newKey);
void clear();
bool isEmpty() const;
int dataSize();
void copyDataFrom(const CustomData* other);
bool operator==(const CustomData& other) const;
bool operator!=(const CustomData& other) const;
signals:
void modified();
void aboutToBeAdded(const QString& key);
void added(const QString& key);
void aboutToBeRemoved(const QString& key);
void removed(const QString& key);
void aboutToRename(const QString& oldKey, const QString& newKey);
void renamed(const QString& oldKey, const QString& newKey);
void aboutToBeReset();
void reset();
private:
QHash<QString, QString> m_data;
};
#endif // KEEPASSX_CUSTOMDATA_H

View File

@ -37,6 +37,7 @@ Entry::Entry()
: m_attributes(new EntryAttributes(this)) : m_attributes(new EntryAttributes(this))
, m_attachments(new EntryAttachments(this)) , m_attachments(new EntryAttachments(this))
, m_autoTypeAssociations(new AutoTypeAssociations(this)) , m_autoTypeAssociations(new AutoTypeAssociations(this))
, m_customData(new CustomData(this))
, m_tmpHistoryItem(nullptr) , m_tmpHistoryItem(nullptr)
, m_modifiedSinceBegin(false) , m_modifiedSinceBegin(false)
, m_updateTimeinfo(true) , m_updateTimeinfo(true)
@ -52,6 +53,7 @@ Entry::Entry()
connect(m_attributes, SIGNAL(defaultKeyModified()), SLOT(emitDataChanged())); connect(m_attributes, SIGNAL(defaultKeyModified()), SLOT(emitDataChanged()));
connect(m_attachments, SIGNAL(modified()), this, SIGNAL(modified())); connect(m_attachments, SIGNAL(modified()), this, SIGNAL(modified()));
connect(m_autoTypeAssociations, SIGNAL(modified()), SIGNAL(modified())); connect(m_autoTypeAssociations, SIGNAL(modified()), SIGNAL(modified()));
connect(m_customData, SIGNAL(modified()), this, SIGNAL(modified()));
connect(this, SIGNAL(modified()), SLOT(updateTimeinfo())); connect(this, SIGNAL(modified()), SLOT(updateTimeinfo()));
connect(this, SIGNAL(modified()), SLOT(updateModifiedSinceBegin())); connect(this, SIGNAL(modified()), SLOT(updateModifiedSinceBegin()));
@ -340,6 +342,16 @@ const EntryAttachments* Entry::attachments() const
return m_attachments; return m_attachments;
} }
CustomData *Entry::customData()
{
return m_customData;
}
const CustomData *Entry::customData() const
{
return m_customData;
}
bool Entry::hasTotp() const bool Entry::hasTotp() const
{ {
return m_attributes->hasKey("TOTP Seed") || m_attributes->hasKey("otp"); return m_attributes->hasKey("TOTP Seed") || m_attributes->hasKey("otp");
@ -606,6 +618,7 @@ void Entry::truncateHistory()
size += historyItem->attributes()->attributesSize(); size += historyItem->attributes()->attributesSize();
size += historyItem->autoTypeAssociations()->associationsSize(); size += historyItem->autoTypeAssociations()->associationsSize();
size += historyItem->attachments()->attachmentsSize(); size += historyItem->attachments()->attachmentsSize();
size += customData()->dataSize();
const QStringList tags = historyItem->tags().split(delimiter, QString::SkipEmptyParts); const QStringList tags = historyItem->tags().split(delimiter, QString::SkipEmptyParts);
for (const QString& tag : tags) { for (const QString& tag : tags) {
size += tag.toUtf8().size(); size += tag.toUtf8().size();
@ -632,6 +645,7 @@ Entry* Entry::clone(CloneFlags flags) const
entry->m_uuid = m_uuid; entry->m_uuid = m_uuid;
} }
entry->m_data = m_data; entry->m_data = m_data;
entry->m_customData->copyDataFrom(m_customData);
entry->m_attributes->copyDataFrom(m_attributes); entry->m_attributes->copyDataFrom(m_attributes);
entry->m_attachments->copyDataFrom(m_attachments); entry->m_attachments->copyDataFrom(m_attachments);
@ -676,6 +690,7 @@ void Entry::copyDataFrom(const Entry* other)
{ {
setUpdateTimeinfo(false); setUpdateTimeinfo(false);
m_data = other->m_data; m_data = other->m_data;
m_customData->copyDataFrom(other->m_customData);
m_attributes->copyDataFrom(other->m_attributes); m_attributes->copyDataFrom(other->m_attributes);
m_attachments->copyDataFrom(other->m_attachments); m_attachments->copyDataFrom(other->m_attachments);
m_autoTypeAssociations->copyDataFrom(other->m_autoTypeAssociations); m_autoTypeAssociations->copyDataFrom(other->m_autoTypeAssociations);

View File

@ -28,6 +28,7 @@
#include <QUrl> #include <QUrl>
#include "core/AutoTypeAssociations.h" #include "core/AutoTypeAssociations.h"
#include "core/CustomData.h"
#include "core/EntryAttachments.h" #include "core/EntryAttachments.h"
#include "core/EntryAttributes.h" #include "core/EntryAttributes.h"
#include "core/TimeInfo.h" #include "core/TimeInfo.h"
@ -107,6 +108,8 @@ public:
const EntryAttributes* attributes() const; const EntryAttributes* attributes() const;
EntryAttachments* attachments(); EntryAttachments* attachments();
const EntryAttachments* attachments() const; const EntryAttachments* attachments() const;
CustomData *customData();
const CustomData *customData() const;
static const int DefaultIconNumber; static const int DefaultIconNumber;
static const int ResolveMaximumDepth; static const int ResolveMaximumDepth;
@ -232,6 +235,8 @@ private:
EntryAttachments* const m_attachments; EntryAttachments* const m_attachments;
AutoTypeAssociations* const m_autoTypeAssociations; AutoTypeAssociations* const m_autoTypeAssociations;
CustomData* const m_customData;
QList<Entry*> m_history; QList<Entry*> m_history;
Entry* m_tmpHistoryItem; Entry* m_tmpHistoryItem;
bool m_modifiedSinceBegin; bool m_modifiedSinceBegin;

View File

@ -33,13 +33,17 @@ Entry::CloneFlags Group::DefaultEntryCloneFlags = static_cast<Entry::CloneFlags>
Entry::CloneNewUuid | Entry::CloneResetTimeInfo); Entry::CloneNewUuid | Entry::CloneResetTimeInfo);
Group::Group() Group::Group()
: m_updateTimeinfo(true) : m_customData(new CustomData(this))
, m_updateTimeinfo(true)
{ {
m_data.iconNumber = DefaultIconNumber; m_data.iconNumber = DefaultIconNumber;
m_data.isExpanded = true; m_data.isExpanded = true;
m_data.autoTypeEnabled = Inherit; m_data.autoTypeEnabled = Inherit;
m_data.searchingEnabled = Inherit; m_data.searchingEnabled = Inherit;
m_data.mergeMode = ModeInherit; m_data.mergeMode = ModeInherit;
connect(m_customData, SIGNAL(modified()), this, SIGNAL(modified()));
connect(this, SIGNAL(modified()), SLOT(updateTimeinfo()));
} }
Group::~Group() Group::~Group()
@ -80,7 +84,6 @@ Group* Group::createRecycleBin()
template <class P, class V> inline bool Group::set(P& property, const V& value) { template <class P, class V> inline bool Group::set(P& property, const V& value) {
if (property != value) { if (property != value) {
property = value; property = value;
updateTimeinfo();
emit modified(); emit modified();
return true; return true;
} else { } else {
@ -251,6 +254,16 @@ bool Group::isExpired() const
return m_data.timeInfo.expires() && m_data.timeInfo.expiryTime() < QDateTime::currentDateTimeUtc(); return m_data.timeInfo.expires() && m_data.timeInfo.expiryTime() < QDateTime::currentDateTimeUtc();
} }
CustomData *Group::customData()
{
return m_customData;
}
const CustomData *Group::customData() const
{
return m_customData;
}
void Group::setUuid(const Uuid& uuid) void Group::setUuid(const Uuid& uuid)
{ {
set(m_uuid, uuid); set(m_uuid, uuid);
@ -275,8 +288,6 @@ void Group::setIcon(int iconNumber)
if (m_data.iconNumber != iconNumber || !m_data.customIcon.isNull()) { if (m_data.iconNumber != iconNumber || !m_data.customIcon.isNull()) {
m_data.iconNumber = iconNumber; m_data.iconNumber = iconNumber;
m_data.customIcon = Uuid(); m_data.customIcon = Uuid();
updateTimeinfo();
emit modified(); emit modified();
emit dataChanged(this); emit dataChanged(this);
} }
@ -289,8 +300,6 @@ void Group::setIcon(const Uuid& uuid)
if (m_data.customIcon != uuid) { if (m_data.customIcon != uuid) {
m_data.customIcon = uuid; m_data.customIcon = uuid;
m_data.iconNumber = 0; m_data.iconNumber = 0;
updateTimeinfo();
emit modified(); emit modified();
emit dataChanged(this); emit dataChanged(this);
} }
@ -305,8 +314,8 @@ void Group::setExpanded(bool expanded)
{ {
if (m_data.isExpanded != expanded) { if (m_data.isExpanded != expanded) {
m_data.isExpanded = expanded; m_data.isExpanded = expanded;
updateTimeinfo();
if (config()->get("IgnoreGroupExpansion").toBool()) { if (config()->get("IgnoreGroupExpansion").toBool()) {
updateTimeinfo();
return; return;
} }
emit modified(); emit modified();
@ -337,7 +346,6 @@ void Group::setExpires(bool value)
{ {
if (m_data.timeInfo.expires() != value) { if (m_data.timeInfo.expires() != value) {
m_data.timeInfo.setExpires(value); m_data.timeInfo.setExpires(value);
updateTimeinfo();
emit modified(); emit modified();
} }
} }
@ -346,7 +354,6 @@ void Group::setExpiryTime(const QDateTime& dateTime)
{ {
if (m_data.timeInfo.expiryTime() != dateTime) { if (m_data.timeInfo.expiryTime() != dateTime) {
m_data.timeInfo.setExpiryTime(dateTime); m_data.timeInfo.setExpiryTime(dateTime);
updateTimeinfo();
emit modified(); emit modified();
} }
} }
@ -768,6 +775,7 @@ Group* Group::clone(Entry::CloneFlags entryFlags, Group::CloneFlags groupFlags)
} }
clonedGroup->m_data = m_data; clonedGroup->m_data = m_data;
clonedGroup->m_customData->copyDataFrom(m_customData);
if (groupFlags & Group::CloneIncludeEntries) { if (groupFlags & Group::CloneIncludeEntries) {
const QList<Entry*> entryList = entries(); const QList<Entry*> entryList = entries();
@ -799,6 +807,7 @@ Group* Group::clone(Entry::CloneFlags entryFlags, Group::CloneFlags groupFlags)
void Group::copyDataFrom(const Group* other) void Group::copyDataFrom(const Group* other)
{ {
m_data = other->m_data; m_data = other->m_data;
m_customData->copyDataFrom(other->m_customData);
m_lastTopVisibleEntry = other->m_lastTopVisibleEntry; m_lastTopVisibleEntry = other->m_lastTopVisibleEntry;
} }

View File

@ -26,6 +26,7 @@
#include "core/Database.h" #include "core/Database.h"
#include "core/Entry.h" #include "core/Entry.h"
#include "core/CustomData.h"
#include "core/TimeInfo.h" #include "core/TimeInfo.h"
#include "core/Uuid.h" #include "core/Uuid.h"
@ -83,6 +84,8 @@ public:
bool resolveAutoTypeEnabled() const; bool resolveAutoTypeEnabled() const;
Entry* lastTopVisibleEntry() const; Entry* lastTopVisibleEntry() const;
bool isExpired() const; bool isExpired() const;
CustomData *customData();
const CustomData *customData() const;
static const int DefaultIconNumber; static const int DefaultIconNumber;
static const int RecycleBinIconNumber; static const int RecycleBinIconNumber;
@ -164,6 +167,9 @@ signals:
void modified(); void modified();
private slots:
void updateTimeinfo();
private: private:
template <class P, class V> bool set(P& property, const V& value); template <class P, class V> bool set(P& property, const V& value);
@ -177,7 +183,6 @@ private:
void recSetDatabase(Database* db); void recSetDatabase(Database* db);
void cleanupParent(); void cleanupParent();
void recCreateDelObjects(); void recCreateDelObjects();
void updateTimeinfo();
QPointer<Database> m_db; QPointer<Database> m_db;
Uuid m_uuid; Uuid m_uuid;
@ -186,6 +191,8 @@ private:
QList<Group*> m_children; QList<Group*> m_children;
QList<Entry*> m_entries; QList<Entry*> m_entries;
CustomData *const m_customData;
QPointer<Group> m_parent; QPointer<Group> m_parent;
bool m_updateTimeinfo; bool m_updateTimeinfo;

View File

@ -27,6 +27,7 @@ const int Metadata::DefaultHistoryMaxSize = 6 * 1024 * 1024;
Metadata::Metadata(QObject* parent) Metadata::Metadata(QObject* parent)
: QObject(parent) : QObject(parent)
, m_customData(new CustomData(this))
, m_updateDatetime(true) , m_updateDatetime(true)
{ {
m_data.generator = "KeePassXC"; m_data.generator = "KeePassXC";
@ -50,6 +51,8 @@ Metadata::Metadata(QObject* parent)
m_entryTemplatesGroupChanged = now; m_entryTemplatesGroupChanged = now;
m_masterKeyChanged = now; m_masterKeyChanged = now;
m_settingsChanged = now; m_settingsChanged = now;
connect(m_customData, SIGNAL(modified()), this, SIGNAL(modified()));
} }
template <class P, class V> bool Metadata::set(P& property, const V& value) template <class P, class V> bool Metadata::set(P& property, const V& value)
@ -291,9 +294,14 @@ int Metadata::historyMaxSize() const
return m_data.historyMaxSize; return m_data.historyMaxSize;
} }
QHash<QString, QString> Metadata::customFields() const CustomData *Metadata::customData()
{ {
return m_customFields; return m_customData;
}
const CustomData *Metadata::customData() const
{
return m_customData;
} }
void Metadata::setGenerator(const QString& value) void Metadata::setGenerator(const QString& value)
@ -511,27 +519,13 @@ void Metadata::setHistoryMaxSize(int value)
set(m_data.historyMaxSize, value); set(m_data.historyMaxSize, value);
} }
void Metadata::addCustomField(const QString& key, const QString& value) QDateTime Metadata::settingsChanged() const
{ {
Q_ASSERT(!m_customFields.contains(key));
m_customFields.insert(key, value);
emit modified();
}
void Metadata::removeCustomField(const QString& key)
{
Q_ASSERT(m_customFields.contains(key));
m_customFields.remove(key);
emit modified();
}
QDateTime Metadata::settingsChanged() const {
return m_settingsChanged; return m_settingsChanged;
} }
void Metadata::setSettingsChanged(const QDateTime& value) { void Metadata::setSettingsChanged(const QDateTime& value)
{
Q_ASSERT(value.timeSpec() == Qt::UTC); Q_ASSERT(value.timeSpec() == Qt::UTC);
m_settingsChanged = value; m_settingsChanged = value;
} }

View File

@ -27,6 +27,7 @@
#include <QPointer> #include <QPointer>
#include "core/Uuid.h" #include "core/Uuid.h"
#include "core/CustomData.h"
class Database; class Database;
class Group; class Group;
@ -97,7 +98,8 @@ public:
int masterKeyChangeForce() const; int masterKeyChangeForce() const;
int historyMaxItems() const; int historyMaxItems() const;
int historyMaxSize() const; int historyMaxSize() const;
QHash<QString, QString> customFields() const; CustomData *customData();
const CustomData *customData() const;
static const int DefaultHistoryMaxItems; static const int DefaultHistoryMaxItems;
static const int DefaultHistoryMaxSize; static const int DefaultHistoryMaxSize;
@ -134,8 +136,6 @@ public:
void setMasterKeyChangeForce(int value); void setMasterKeyChangeForce(int value);
void setHistoryMaxItems(int value); void setHistoryMaxItems(int value);
void setHistoryMaxSize(int value); void setHistoryMaxSize(int value);
void addCustomField(const QString& key, const QString& value);
void removeCustomField(const QString& key);
void setUpdateDatetime(bool value); void setUpdateDatetime(bool value);
/* /*
* Copy all attributes from other except: * Copy all attributes from other except:
@ -175,7 +175,7 @@ private:
QDateTime m_masterKeyChanged; QDateTime m_masterKeyChanged;
QDateTime m_settingsChanged; QDateTime m_settingsChanged;
QHash<QString, QString> m_customFields; CustomData *const m_customData;
bool m_updateDatetime; bool m_updateDatetime;
}; };

View File

@ -295,7 +295,7 @@ void KdbxXmlReader::parseMeta()
} else if (m_xml.name() == "Binaries") { } else if (m_xml.name() == "Binaries") {
parseBinaries(); parseBinaries();
} else if (m_xml.name() == "CustomData") { } else if (m_xml.name() == "CustomData") {
parseCustomData(); parseCustomData(m_meta->customData());
} else if (m_xml.name() == "SettingsChanged") { } else if (m_xml.name() == "SettingsChanged") {
m_meta->setSettingsChanged(readDateTime()); m_meta->setSettingsChanged(readDateTime());
} else { } else {
@ -397,20 +397,20 @@ void KdbxXmlReader::parseBinaries()
} }
} }
void KdbxXmlReader::parseCustomData() void KdbxXmlReader::parseCustomData(CustomData *customData)
{ {
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomData"); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomData");
while (!m_xml.hasError() && m_xml.readNextStartElement()) { while (!m_xml.hasError() && m_xml.readNextStartElement()) {
if (m_xml.name() == "Item") { if (m_xml.name() == "Item") {
parseCustomDataItem(); parseCustomDataItem(customData);
continue; continue;
} }
skipCurrentElement(); skipCurrentElement();
} }
} }
void KdbxXmlReader::parseCustomDataItem() void KdbxXmlReader::parseCustomDataItem(CustomData *customData)
{ {
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Item"); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Item");
@ -432,7 +432,7 @@ void KdbxXmlReader::parseCustomDataItem()
} }
if (keySet && valueSet) { if (keySet && valueSet) {
m_meta->addCustomField(key, value); customData->set(key, value);
return; return;
} }
@ -583,6 +583,10 @@ Group* KdbxXmlReader::parseGroup()
} }
continue; continue;
} }
if (m_xml.name() == "CustomData") {
parseCustomData(group->customData());
continue;
}
skipCurrentElement(); skipCurrentElement();
} }
@ -744,7 +748,10 @@ Entry* KdbxXmlReader::parseEntry(bool history)
} }
continue; continue;
} }
if (m_xml.name() == "CustomData" ){
parseCustomData(entry->customData());
continue;
}
skipCurrentElement(); skipCurrentElement();
} }

View File

@ -66,8 +66,8 @@ protected:
virtual void parseCustomIcons(); virtual void parseCustomIcons();
virtual void parseIcon(); virtual void parseIcon();
virtual void parseBinaries(); virtual void parseBinaries();
virtual void parseCustomData(); virtual void parseCustomData(CustomData *customData);
virtual void parseCustomDataItem(); virtual void parseCustomDataItem(CustomData *customData);
virtual bool parseRoot(); virtual bool parseRoot();
virtual Group* parseGroup(); virtual Group* parseGroup();
virtual void parseDeletedObjects(); virtual void parseDeletedObjects();

View File

@ -129,7 +129,7 @@ void KdbxXmlWriter::writeMetadata()
if (m_kdbxVersion < KeePass2::FILE_VERSION_4) { if (m_kdbxVersion < KeePass2::FILE_VERSION_4) {
writeBinaries(); writeBinaries();
} }
writeCustomData(); writeCustomData(m_meta->customData());
m_xml.writeEndElement(); m_xml.writeEndElement();
} }
@ -218,14 +218,13 @@ void KdbxXmlWriter::writeBinaries()
m_xml.writeEndElement(); m_xml.writeEndElement();
} }
void KdbxXmlWriter::writeCustomData() void KdbxXmlWriter::writeCustomData(const CustomData *customData)
{ {
m_xml.writeStartElement("CustomData"); m_xml.writeStartElement("CustomData");
QHash<QString, QString> customFields = m_meta->customFields(); const QList<QString> keyList = customData->keys();
const QList<QString> keyList = customFields.keys();
for (const QString& key : keyList) { for (const QString& key : keyList) {
writeCustomDataItem(key, customFields.value(key)); writeCustomDataItem(key, customData->value(key));
} }
m_xml.writeEndElement(); m_xml.writeEndElement();
@ -277,6 +276,10 @@ void KdbxXmlWriter::writeGroup(const Group* group)
writeUuid("LastTopVisibleEntry", group->lastTopVisibleEntry()); writeUuid("LastTopVisibleEntry", group->lastTopVisibleEntry());
if (!group->customData()->isEmpty()){
writeCustomData(group->customData());
}
const QList<Entry*>& entryList = group->entries(); const QList<Entry*>& entryList = group->entries();
for (const Entry* entry : entryList) { for (const Entry* entry : entryList) {
writeEntry(entry); writeEntry(entry);
@ -401,6 +404,11 @@ void KdbxXmlWriter::writeEntry(const Entry* entry)
} }
writeAutoType(entry); writeAutoType(entry);
if (!entry->customData()->isEmpty()){
writeCustomData(entry->customData());
}
// write history only for entries that are not history items // write history only for entries that are not history items
if (entry->parent()) { if (entry->parent()) {
writeEntryHistory(entry); writeEntryHistory(entry);

View File

@ -51,7 +51,7 @@ private:
void writeCustomIcons(); void writeCustomIcons();
void writeIcon(const Uuid& uuid, const QImage& icon); void writeIcon(const Uuid& uuid, const QImage& icon);
void writeBinaries(); void writeBinaries();
void writeCustomData(); void writeCustomData(const CustomData *customData);
void writeCustomDataItem(const QString& key, const QString& value); void writeCustomDataItem(const QString& key, const QString& value);
void writeRoot(); void writeRoot();
void writeGroup(const Group* group); void writeGroup(const Group* group);

View File

@ -18,20 +18,27 @@
#include "EditWidgetProperties.h" #include "EditWidgetProperties.h"
#include "ui_EditWidgetProperties.h" #include "ui_EditWidgetProperties.h"
#include <QStandardItemModel>
EditWidgetProperties::EditWidgetProperties(QWidget* parent) EditWidgetProperties::EditWidgetProperties(QWidget* parent)
: QWidget(parent) : QWidget(parent)
, m_ui(new Ui::EditWidgetProperties()) , m_ui(new Ui::EditWidgetProperties())
, m_customData(new CustomData(this))
, m_customDataModel(new QStandardItemModel(this))
{ {
m_ui->setupUi(this); m_ui->setupUi(this);
m_ui->customDataTable->setModel(m_customDataModel);
this->connect( m_ui->removeCustomDataButton, SIGNAL(clicked()), SLOT(removeSelectedPluginData()));
} }
EditWidgetProperties::~EditWidgetProperties() EditWidgetProperties::~EditWidgetProperties()
{ {
} }
void EditWidgetProperties::setFields(TimeInfo timeInfo, Uuid uuid) void EditWidgetProperties::setFields(const TimeInfo &timeInfo, const Uuid &uuid)
{ {
QString timeFormat("d MMM yyyy HH:mm:ss"); static const QString timeFormat("d MMM yyyy HH:mm:ss");
m_ui->modifiedEdit->setText( m_ui->modifiedEdit->setText(
timeInfo.lastModificationTime().toLocalTime().toString(timeFormat)); timeInfo.lastModificationTime().toLocalTime().toString(timeFormat));
m_ui->createdEdit->setText( m_ui->createdEdit->setText(
@ -40,3 +47,36 @@ void EditWidgetProperties::setFields(TimeInfo timeInfo, Uuid uuid)
timeInfo.lastAccessTime().toLocalTime().toString(timeFormat)); timeInfo.lastAccessTime().toLocalTime().toString(timeFormat));
m_ui->uuidEdit->setText(uuid.toHex()); m_ui->uuidEdit->setText(uuid.toHex());
} }
void EditWidgetProperties::setCustomData(const CustomData *customData)
{
Q_ASSERT(customData != nullptr);
m_customData->copyDataFrom(customData);
this->updateModel();
}
const CustomData *EditWidgetProperties::customData() const
{
return m_customData;
}
void EditWidgetProperties::removeSelectedPluginData()
{
const QItemSelectionModel *pSelectionModel = m_ui->customDataTable->selectionModel();
if (pSelectionModel){
for( const QModelIndex& index : pSelectionModel->selectedRows(0) ){
const QString key = index.data().toString();
m_customData->remove(key);
}
this->updateModel();
}
}
void EditWidgetProperties::updateModel()
{
m_customDataModel->clear();
for( const QString& key : m_customData->keys() ){
m_customDataModel->appendRow(QList<QStandardItem*>() << new QStandardItem( key ) << new QStandardItem( m_customData->value( key ) ));
}
}

View File

@ -18,8 +18,11 @@
#ifndef KEEPASSX_EDITWIDGETPROPERTIES_H #ifndef KEEPASSX_EDITWIDGETPROPERTIES_H
#define KEEPASSX_EDITWIDGETPROPERTIES_H #define KEEPASSX_EDITWIDGETPROPERTIES_H
#include <QStandardItemModel>
#include <QPointer>
#include <QWidget> #include <QWidget>
#include "core/CustomData.h"
#include "core/TimeInfo.h" #include "core/TimeInfo.h"
#include "core/Uuid.h" #include "core/Uuid.h"
@ -35,11 +38,21 @@ public:
explicit EditWidgetProperties(QWidget* parent = nullptr); explicit EditWidgetProperties(QWidget* parent = nullptr);
~EditWidgetProperties(); ~EditWidgetProperties();
void setFields(TimeInfo timeInfo, Uuid uuid); void setFields(const TimeInfo &timeInfo, const Uuid &uuid);
void setCustomData(const CustomData *customData);
const CustomData *customData() const;
private slots:
void removeSelectedPluginData();
private: private:
void updateModel();
const QScopedPointer<Ui::EditWidgetProperties> m_ui; const QScopedPointer<Ui::EditWidgetProperties> m_ui;
QPointer<CustomData> m_customData;
QPointer<QStandardItemModel> m_customDataModel;
Q_DISABLE_COPY(EditWidgetProperties) Q_DISABLE_COPY(EditWidgetProperties)
}; };

View File

@ -10,9 +10,12 @@
<height>328</height> <height>328</height>
</rect> </rect>
</property> </property>
<layout class="QFormLayout" name="formLayout"> <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0">
<property name="fieldGrowthPolicy"> <property name="spacing">
<enum>QFormLayout::ExpandingFieldsGrow</enum> <number>5</number>
</property>
<property name="leftMargin">
<number>0</number>
</property> </property>
<property name="topMargin"> <property name="topMargin">
<number>0</number> <number>0</number>
@ -23,72 +26,128 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item row="1" column="1"> <item>
<widget class="QLineEdit" name="createdEdit"> <layout class="QFormLayout" name="formLayout_2">
<property name="sizePolicy"> <item row="1" column="0">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed"> <widget class="QLabel" name="labelCreated">
<horstretch>0</horstretch> <property name="text">
<verstretch>0</verstretch> <string>Created:</string>
</sizepolicy> </property>
</property> </widget>
<property name="readOnly"> </item>
<bool>true</bool> <item row="1" column="1">
</property> <widget class="QLineEdit" name="createdEdit">
</widget> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelModfied">
<property name="text">
<string>Modified:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="modifiedEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="labelAccessed">
<property name="text">
<string>Accessed:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="accessedEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="labelUuid">
<property name="text">
<string>Uuid:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="uuidEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item> </item>
<item row="2" column="1"> <item>
<widget class="QLineEdit" name="modifiedEdit"> <widget class="QGroupBox" name="groupBox">
<property name="sizePolicy"> <property name="title">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed"> <string>Plugin Data</string>
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Created:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Modified:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Accessed:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="accessedEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Uuid:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="uuidEdit">
<property name="readOnly">
<bool>true</bool>
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QTableView" name="customDataTable">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPushButton" name="removeCustomDataButton">
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget> </widget>
</item> </item>
</layout> </layout>

View File

@ -666,6 +666,7 @@ void EditEntryWidget::setForms(const Entry* entry, bool restore)
#endif #endif
m_editWidgetProperties->setFields(entry->timeInfo(), entry->uuid()); m_editWidgetProperties->setFields(entry->timeInfo(), entry->uuid());
m_editWidgetProperties->setCustomData(entry->customData());
if (!m_history && !restore) { if (!m_history && !restore) {
m_historyModel->setEntries(entry->historyItems()); m_historyModel->setEntries(entry->historyItems());
@ -771,7 +772,7 @@ void EditEntryWidget::updateEntryData(Entry* entry) const
{ {
entry->attributes()->copyCustomKeysFrom(m_entryAttributes); entry->attributes()->copyCustomKeysFrom(m_entryAttributes);
entry->attachments()->copyDataFrom(m_advancedUi->attachmentsWidget->entryAttachments()); entry->attachments()->copyDataFrom(m_advancedUi->attachmentsWidget->entryAttachments());
entry->customData()->copyDataFrom(m_editWidgetProperties->customData());
entry->setTitle(m_mainUi->titleEdit->text()); entry->setTitle(m_mainUi->titleEdit->text());
entry->setUsername(m_mainUi->usernameEdit->text()); entry->setUsername(m_mainUi->usernameEdit->text());
entry->setUrl(m_mainUi->urlEdit->text()); entry->setUrl(m_mainUi->urlEdit->text());

View File

@ -95,6 +95,7 @@ void EditGroupWidget::loadGroup(Group* group, bool create, Database* database)
m_editGroupWidgetIcons->load(group->uuid(), database, iconStruct); m_editGroupWidgetIcons->load(group->uuid(), database, iconStruct);
m_editWidgetProperties->setFields(group->timeInfo(), group->uuid()); m_editWidgetProperties->setFields(group->timeInfo(), group->uuid());
m_editWidgetProperties->setCustomData(group->customData());
setCurrentPage(0); setCurrentPage(0);
@ -118,6 +119,8 @@ void EditGroupWidget::apply()
m_group->setSearchingEnabled(triStateFromIndex(m_mainUi->searchComboBox->currentIndex())); m_group->setSearchingEnabled(triStateFromIndex(m_mainUi->searchComboBox->currentIndex()));
m_group->setAutoTypeEnabled(triStateFromIndex(m_mainUi->autotypeComboBox->currentIndex())); m_group->setAutoTypeEnabled(triStateFromIndex(m_mainUi->autotypeComboBox->currentIndex()));
m_group->customData()->copyDataFrom(m_editWidgetProperties->customData());
if (m_mainUi->autoTypeSequenceInherit->isChecked()) { if (m_mainUi->autoTypeSequenceInherit->isChecked()) {
m_group->setDefaultAutoTypeSequence(QString()); m_group->setDefaultAutoTypeSequence(QString());
} }