Implement Group sync for KeeShare (#11593)

---------

Co-authored-by: ever <ever@brokenmouse.studio>
Co-authored-by: Ben Kluwe <ben.kl@go4more.de>
This commit is contained in:
Jonathan White
2025-11-09 16:10:45 -05:00
committed by GitHub
parent e542070902
commit d87554e6d2
7 changed files with 91 additions and 19 deletions

View File

@@ -3735,6 +3735,14 @@ Supported extensions are: %1.</source>
<source>Select import/export file</source> <source>Select import/export file</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Maintain group structure with shared database</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Keep Group Structure</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>EditGroupWidgetMain</name> <name>EditGroupWidgetMain</name>

View File

@@ -213,7 +213,7 @@ namespace KeeShareSettings
} }
} }
} else { } else {
qWarning("Unknown KeeShareSettings element %s", qPrintable(reader.name().toString())); qDebug("Unknown KeeShareSettings element %s", qPrintable(reader.name().toString()));
reader.skipCurrentElement(); reader.skipCurrentElement();
} }
} }
@@ -253,7 +253,7 @@ namespace KeeShareSettings
} else if (reader.name() == "PublicKey") { } else if (reader.name() == "PublicKey") {
own.certificate = Certificate::deserialize(reader); own.certificate = Certificate::deserialize(reader);
} else { } else {
qWarning("Unknown KeeShareSettings element %s", qPrintable(reader.name().toString())); qDebug("Unknown KeeShareSettings element %s", qPrintable(reader.name().toString()));
reader.skipCurrentElement(); reader.skipCurrentElement();
} }
} }
@@ -262,8 +262,7 @@ namespace KeeShareSettings
} }
Reference::Reference() Reference::Reference()
: type(Inactive) : uuid(QUuid::createUuid())
, uuid(QUuid::createUuid())
{ {
} }
@@ -320,12 +319,21 @@ namespace KeeShareSettings
writer.writeStartElement("Password"); writer.writeStartElement("Password");
writer.writeCharacters(reference.password.toUtf8().toBase64()); writer.writeCharacters(reference.password.toUtf8().toBase64());
writer.writeEndElement(); writer.writeEndElement();
writer.writeStartElement("KeepGroups");
writer.writeCharacters(reference.keepGroups ? "True" : "False");
writer.writeEndElement();
}); });
} }
Reference Reference::deserialize(const QString& raw) Reference Reference::deserialize(const QString& raw)
{ {
if (raw.isEmpty()) {
return {};
}
Reference reference; Reference reference;
// If KeepGroups is not present, default to false for backward compatibility
reference.keepGroups = false;
xmlDeserialize(raw, [&](QXmlStreamReader& reader) { xmlDeserialize(raw, [&](QXmlStreamReader& reader) {
while (!reader.error() && reader.readNextStartElement()) { while (!reader.error() && reader.readNextStartElement()) {
if (reader.name() == "Type") { if (reader.name() == "Type") {
@@ -346,8 +354,10 @@ namespace KeeShareSettings
reference.path = QString::fromUtf8(QByteArray::fromBase64(reader.readElementText().toLatin1())); reference.path = QString::fromUtf8(QByteArray::fromBase64(reader.readElementText().toLatin1()));
} else if (reader.name() == "Password") { } else if (reader.name() == "Password") {
reference.password = QString::fromUtf8(QByteArray::fromBase64(reader.readElementText().toLatin1())); reference.password = QString::fromUtf8(QByteArray::fromBase64(reader.readElementText().toLatin1()));
} else if (reader.name() == "KeepGroups") {
reference.keepGroups = reader.readElementText().compare("True") == 0;
} else { } else {
qWarning("Unknown Reference element %s", qPrintable(reader.name().toString())); qDebug("Unknown Reference element %s", qPrintable(reader.name().toString()));
reader.skipCurrentElement(); reader.skipCurrentElement();
} }
} }

View File

@@ -122,10 +122,11 @@ namespace KeeShareSettings
struct Reference struct Reference
{ {
Type type; Type type = Inactive;
QUuid uuid; QUuid uuid;
QString path; QString path;
QString password; QString password;
bool keepGroups = true;
Reference(); Reference();
bool isNull() const; bool isNull() const;

View File

@@ -62,6 +62,39 @@ namespace
} }
} }
void cloneIcon(Metadata* targetMetadata, const Database* sourceDb, const QUuid& iconUuid)
{
if (!iconUuid.isNull() && !targetMetadata->hasCustomIcon(iconUuid)) {
targetMetadata->addCustomIcon(iconUuid, sourceDb->metadata()->customIcon(iconUuid));
}
}
void cloneEntries(Metadata* targetMetadata, const Group* sourceGroup, Group* targetGroup)
{
for (const Entry* sourceEntry : sourceGroup->entries()) {
auto* targetEntry = sourceEntry->clone(Entry::CloneIncludeHistory);
const bool updateTimeinfoEntry = targetEntry->canUpdateTimeinfo();
targetEntry->setUpdateTimeinfo(false);
targetEntry->setGroup(targetGroup);
targetEntry->setUpdateTimeinfo(updateTimeinfoEntry);
cloneIcon(targetMetadata, sourceEntry->database(), targetEntry->iconUuid());
}
}
void cloneChildren(Metadata* targetMetadata, const Group* sourceRoot, Group* targetRoot)
{
for (const Group* sourceGroup : sourceRoot->children()) {
auto* targetGroup = sourceGroup->clone(Entry::CloneNoFlags, Group::CloneNoFlags);
const bool updateTimeinfo = targetGroup->canUpdateTimeinfo();
targetGroup->setUpdateTimeinfo(false);
targetGroup->setParent(targetRoot);
targetGroup->setUpdateTimeinfo(updateTimeinfo);
cloneIcon(targetMetadata, sourceRoot->database(), targetGroup->iconUuid());
cloneEntries(targetMetadata, sourceGroup, targetGroup);
cloneChildren(targetMetadata, sourceGroup, targetGroup);
}
}
Database* extractIntoDatabase(const KeeShareSettings::Reference& reference, const Group* sourceRoot) Database* extractIntoDatabase(const KeeShareSettings::Reference& reference, const Group* sourceRoot)
{ {
const auto* sourceDb = sourceRoot->database(); const auto* sourceDb = sourceRoot->database();
@@ -75,17 +108,10 @@ namespace
targetRoot->setUpdateTimeinfo(false); targetRoot->setUpdateTimeinfo(false);
KeeShare::setReferenceTo(targetRoot, KeeShareSettings::Reference()); KeeShare::setReferenceTo(targetRoot, KeeShareSettings::Reference());
targetRoot->setUpdateTimeinfo(updateTimeinfo); targetRoot->setUpdateTimeinfo(updateTimeinfo);
const auto sourceEntries = sourceRoot->entriesRecursive(false); cloneIcon(targetMetadata, sourceRoot->database(), targetRoot->iconUuid());
for (const Entry* sourceEntry : sourceEntries) { cloneEntries(targetMetadata, sourceRoot, targetRoot);
auto* targetEntry = sourceEntry->clone(Entry::CloneIncludeHistory); if (reference.keepGroups) {
const bool updateTimeinfoEntry = targetEntry->canUpdateTimeinfo(); cloneChildren(targetMetadata, sourceRoot, targetRoot);
targetEntry->setUpdateTimeinfo(false);
targetEntry->setGroup(targetRoot);
targetEntry->setUpdateTimeinfo(updateTimeinfoEntry);
const auto iconUuid = targetEntry->iconUuid();
if (!iconUuid.isNull() && !targetMetadata->hasCustomIcon(iconUuid)) {
targetMetadata->addCustomIcon(iconUuid, sourceEntry->database()->metadata()->customIcon(iconUuid));
}
} }
auto key = QSharedPointer<CompositeKey>::create(); auto key = QSharedPointer<CompositeKey>::create();

View File

@@ -43,6 +43,7 @@ EditGroupWidgetKeeShare::EditGroupWidgetKeeShare(QWidget* parent)
connect(m_ui->pathEdit, SIGNAL(editingFinished()), SLOT(selectPath())); connect(m_ui->pathEdit, SIGNAL(editingFinished()), SLOT(selectPath()));
connect(m_ui->pathSelectionButton, SIGNAL(pressed()), SLOT(launchPathSelectionDialog())); connect(m_ui->pathSelectionButton, SIGNAL(pressed()), SLOT(launchPathSelectionDialog()));
connect(m_ui->typeComboBox, SIGNAL(currentIndexChanged(int)), SLOT(selectType())); connect(m_ui->typeComboBox, SIGNAL(currentIndexChanged(int)), SLOT(selectType()));
connect(m_ui->keepGroupsCheckbox, SIGNAL(toggled(bool)), SLOT(keepGroupsToggled(bool)));
connect(m_ui->clearButton, SIGNAL(clicked(bool)), SLOT(clearInputs())); connect(m_ui->clearButton, SIGNAL(clicked(bool)), SLOT(clearInputs()));
connect(KeeShare::instance(), SIGNAL(activeChanged()), SLOT(updateSharingState())); connect(KeeShare::instance(), SIGNAL(activeChanged()), SLOT(updateSharingState()));
@@ -97,6 +98,7 @@ void EditGroupWidgetKeeShare::updateSharingState()
m_ui->pathEdit->setEnabled(isEnabled); m_ui->pathEdit->setEnabled(isEnabled);
m_ui->pathSelectionButton->setEnabled(isEnabled); m_ui->pathSelectionButton->setEnabled(isEnabled);
m_ui->passwordEdit->setEnabled(isEnabled); m_ui->passwordEdit->setEnabled(isEnabled);
m_ui->keepGroupsCheckbox->setEnabled(isEnabled);
if (!m_temporaryGroup || !isEnabled) { if (!m_temporaryGroup || !isEnabled) {
m_ui->messageWidget->hideMessage(); m_ui->messageWidget->hideMessage();
@@ -188,6 +190,7 @@ void EditGroupWidgetKeeShare::update()
m_ui->typeComboBox->setCurrentIndex(reference.type); m_ui->typeComboBox->setCurrentIndex(reference.type);
m_ui->passwordEdit->setText(reference.password); m_ui->passwordEdit->setText(reference.password);
m_ui->pathEdit->setText(reference.path); m_ui->pathEdit->setText(reference.path);
m_ui->keepGroupsCheckbox->setChecked(reference.keepGroups);
} }
updateSharingState(); updateSharingState();
@@ -291,3 +294,13 @@ void EditGroupWidgetKeeShare::selectType()
updateSharingState(); updateSharingState();
} }
void EditGroupWidgetKeeShare::keepGroupsToggled(bool toggled)
{
if (!m_temporaryGroup) {
return;
}
auto reference = KeeShare::referenceOf(m_temporaryGroup);
reference.keepGroups = toggled;
KeeShare::setReferenceTo(m_temporaryGroup, reference);
}

View File

@@ -48,6 +48,7 @@ private slots:
void selectPassword(); void selectPassword();
void launchPathSelectionDialog(); void launchPathSelectionDialog();
void selectPath(); void selectPath();
void keepGroupsToggled(bool);
private: private:
QScopedPointer<Ui::EditGroupWidgetKeeShare> m_ui; QScopedPointer<Ui::EditGroupWidgetKeeShare> m_ui;

View File

@@ -138,7 +138,7 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="3" column="1"> <item row="4" column="1">
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<spacer name="horizontalSpacer"> <spacer name="horizontalSpacer">
@@ -171,7 +171,7 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="4" column="0"> <item row="5" column="0">
<spacer name="verticalSpacer_2"> <spacer name="verticalSpacer_2">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
@@ -184,6 +184,19 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="3" column="1">
<widget class="QCheckBox" name="keepGroupsCheckbox">
<property name="toolTip">
<string>Maintain group structure with shared database</string>
</property>
<property name="text">
<string>Keep Group Structure</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
</layout> </layout>