mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-04-18 14:55:49 -04:00
Merge 4efb857652ab09f3e7841f89ea7bce129b1c08bc into 31c0b2389007f85c03ae73870e3d6f2a648fa67b
This commit is contained in:
commit
66ea85fcdd
@ -990,10 +990,15 @@ Entry* Entry::clone(CloneFlags flags) const
|
||||
|
||||
if (flags & CloneResetTimeInfo) {
|
||||
QDateTime now = Clock::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 & CloneResetCreationTime) {
|
||||
entry->m_data.timeInfo.setCreationTime(now);
|
||||
}
|
||||
if (flags & CloneResetLastAccessTime) {
|
||||
entry->m_data.timeInfo.setLastAccessTime(now);
|
||||
}
|
||||
if (flags & CloneResetLocationChangedTime) {
|
||||
entry->m_data.timeInfo.setLocationChanged(now);
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & CloneRenameTitle) {
|
||||
@ -1370,10 +1375,8 @@ void Entry::setGroup(Group* group, bool trackPrevious)
|
||||
m_group->database()->addDeletedObject(m_uuid);
|
||||
|
||||
// copy custom icon to the new database
|
||||
if (!iconUuid().isNull() && group->database() && m_group->database()->metadata()->hasCustomIcon(iconUuid())
|
||||
&& !group->database()->metadata()->hasCustomIcon(iconUuid())) {
|
||||
group->database()->metadata()->addCustomIcon(iconUuid(),
|
||||
m_group->database()->metadata()->customIcon(iconUuid()));
|
||||
if (group->database()) {
|
||||
group->database()->metadata()->copyCustomIcon(iconUuid(), m_group->database()->metadata());
|
||||
}
|
||||
} else if (trackPrevious && m_group->database() && group != m_group) {
|
||||
setPreviousParentGroup(m_group);
|
||||
@ -1596,7 +1599,10 @@ QUuid Entry::previousParentGroupUuid() const
|
||||
|
||||
void Entry::setPreviousParentGroupUuid(const QUuid& uuid)
|
||||
{
|
||||
bool prevUpdateTimeinfo = m_updateTimeinfo;
|
||||
m_updateTimeinfo = false; // prevent update of LastModificationTime
|
||||
set(m_data.previousParentGroupUuid, uuid);
|
||||
m_updateTimeinfo = prevUpdateTimeinfo;
|
||||
}
|
||||
|
||||
void Entry::setPreviousParentGroup(const Group* group)
|
||||
|
@ -182,13 +182,18 @@ public:
|
||||
{
|
||||
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
|
||||
CloneResetCreationTime = 2, // set timeInfo.CreationTime to the current time
|
||||
CloneResetLastAccessTime = 4, // set timeInfo.LastAccessTime to the current time
|
||||
CloneResetLocationChangedTime = 8, // set timeInfo.LocationChangedTime to the current time
|
||||
CloneIncludeHistory = 16, // clone the history items
|
||||
CloneRenameTitle = 32, // add "-Clone" after the original title
|
||||
CloneUserAsRef = 64, // Add the user as a reference to the original entry
|
||||
ClonePassAsRef = 128, // Add the password as a reference to the original entry
|
||||
|
||||
CloneResetTimeInfo = CloneResetCreationTime | CloneResetLastAccessTime | CloneResetLocationChangedTime,
|
||||
CloneExactCopy = CloneIncludeHistory,
|
||||
CloneCopy = CloneExactCopy | CloneNewUuid | CloneResetTimeInfo,
|
||||
CloneDefault = CloneNewUuid | CloneResetTimeInfo,
|
||||
CloneCopy = CloneNewUuid | CloneResetTimeInfo | CloneIncludeHistory,
|
||||
CloneRenameTitle = 8, // add "-Clone" after the original title
|
||||
CloneUserAsRef = 16, // Add the user as a reference to the original entry
|
||||
ClonePassAsRef = 32, // Add the password as a reference to the original entry
|
||||
};
|
||||
Q_DECLARE_FLAGS(CloneFlags, CloneFlag)
|
||||
|
||||
|
@ -77,11 +77,11 @@ Group::~Group()
|
||||
cleanupParent();
|
||||
}
|
||||
|
||||
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, bool preserveTimeinfo)
|
||||
{
|
||||
if (property != value) {
|
||||
property = value;
|
||||
emitModified();
|
||||
emitModifiedEx(preserveTimeinfo);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@ -454,6 +454,15 @@ const Group* Group::parentGroup() const
|
||||
return m_parent;
|
||||
}
|
||||
|
||||
void Group::emitModifiedEx(bool preserveTimeinfo) {
|
||||
bool prevUpdateTimeinfo = m_updateTimeinfo;
|
||||
if (preserveTimeinfo) {
|
||||
m_updateTimeinfo = false; // prevent update of LastModificationTime
|
||||
}
|
||||
emitModified();
|
||||
m_updateTimeinfo = prevUpdateTimeinfo;
|
||||
}
|
||||
|
||||
void Group::setParent(Group* parent, int index, bool trackPrevious)
|
||||
{
|
||||
Q_ASSERT(parent);
|
||||
@ -483,9 +492,8 @@ void Group::setParent(Group* parent, int index, bool trackPrevious)
|
||||
recCreateDelObjects();
|
||||
|
||||
// copy custom icon to the new database
|
||||
if (!iconUuid().isNull() && parent->m_db && m_db->metadata()->hasCustomIcon(iconUuid())
|
||||
&& !parent->m_db->metadata()->hasCustomIcon(iconUuid())) {
|
||||
parent->m_db->metadata()->addCustomIcon(iconUuid(), m_db->metadata()->customIcon(iconUuid()));
|
||||
if (parent->m_db) {
|
||||
parent->m_db->metadata()->copyCustomIcon(iconUuid(), m_db->metadata());
|
||||
}
|
||||
}
|
||||
if (m_db != parent->m_db) {
|
||||
@ -511,7 +519,7 @@ void Group::setParent(Group* parent, int index, bool trackPrevious)
|
||||
m_data.timeInfo.setLocationChanged(Clock::currentDateTimeUtc());
|
||||
}
|
||||
|
||||
emitModified();
|
||||
emitModifiedEx(true);
|
||||
|
||||
if (!moveWithinDatabase) {
|
||||
emit groupAdded();
|
||||
@ -564,6 +572,16 @@ bool Group::hasChildren() const
|
||||
return !children().isEmpty();
|
||||
}
|
||||
|
||||
bool Group::isDescendantOf(const Group* group) const
|
||||
{
|
||||
for(const Group* parent = m_parent; parent; parent = parent->m_parent) {
|
||||
if (parent == group) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Database* Group::database()
|
||||
{
|
||||
return m_db;
|
||||
@ -960,12 +978,16 @@ Group* Group::clone(Entry::CloneFlags entryFlags, Group::CloneFlags groupFlags)
|
||||
|
||||
clonedGroup->setUpdateTimeinfo(true);
|
||||
if (groupFlags & Group::CloneResetTimeInfo) {
|
||||
|
||||
QDateTime now = Clock::currentDateTimeUtc();
|
||||
clonedGroup->m_data.timeInfo.setCreationTime(now);
|
||||
clonedGroup->m_data.timeInfo.setLastModificationTime(now);
|
||||
clonedGroup->m_data.timeInfo.setLastAccessTime(now);
|
||||
clonedGroup->m_data.timeInfo.setLocationChanged(now);
|
||||
if (groupFlags & Group::CloneResetCreationTime) {
|
||||
clonedGroup->m_data.timeInfo.setCreationTime(now);
|
||||
}
|
||||
if (groupFlags & Group::CloneResetLastAccessTime) {
|
||||
clonedGroup->m_data.timeInfo.setLastAccessTime(now);
|
||||
}
|
||||
if (groupFlags & Group::CloneResetLocationChangedTime) {
|
||||
clonedGroup->m_data.timeInfo.setLocationChanged(now);
|
||||
}
|
||||
}
|
||||
|
||||
if (groupFlags & Group::CloneRenameTitle) {
|
||||
@ -997,7 +1019,7 @@ void Group::addEntry(Entry* entry)
|
||||
connect(entry, &Entry::modified, m_db, &Database::markAsModified);
|
||||
}
|
||||
|
||||
emitModified();
|
||||
emitModifiedEx(true);
|
||||
emit entryAdded(entry);
|
||||
}
|
||||
|
||||
@ -1014,7 +1036,7 @@ void Group::removeEntry(Entry* entry)
|
||||
entry->disconnect(m_db);
|
||||
}
|
||||
m_entries.removeAll(entry);
|
||||
emitModified();
|
||||
emitModifiedEx(true);
|
||||
emit entryRemoved(entry);
|
||||
}
|
||||
|
||||
@ -1085,7 +1107,7 @@ void Group::cleanupParent()
|
||||
if (m_parent) {
|
||||
emit groupAboutToRemove(this);
|
||||
m_parent->m_children.removeAll(this);
|
||||
emitModified();
|
||||
emitModifiedEx(true);
|
||||
emit groupRemoved();
|
||||
}
|
||||
}
|
||||
@ -1236,7 +1258,7 @@ void Group::sortChildrenRecursively(bool reverse)
|
||||
child->sortChildrenRecursively(reverse);
|
||||
}
|
||||
|
||||
emitModified();
|
||||
emitModifiedEx(true);
|
||||
}
|
||||
|
||||
const Group* Group::previousParentGroup() const
|
||||
@ -1254,7 +1276,7 @@ QUuid Group::previousParentGroupUuid() const
|
||||
|
||||
void Group::setPreviousParentGroupUuid(const QUuid& uuid)
|
||||
{
|
||||
set(m_data.previousParentGroupUuid, uuid);
|
||||
set(m_data.previousParentGroupUuid, uuid, true);
|
||||
}
|
||||
|
||||
void Group::setPreviousParentGroup(const Group* group)
|
||||
|
131
src/core/Group.h
131
src/core/Group.h
@ -20,11 +20,24 @@
|
||||
#define KEEPASSX_GROUP_H
|
||||
|
||||
#include <QPointer>
|
||||
#include <QList>
|
||||
#include <utility>
|
||||
|
||||
#include "core/CustomData.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/Entry.h"
|
||||
|
||||
|
||||
class Entry;
|
||||
class Group;
|
||||
|
||||
|
||||
template <typename TCallable> concept CGroupVisitor = std::is_invocable_v<TCallable, Group*>;
|
||||
template <typename TCallable> concept CGroupConstVisitor = std::is_invocable_v<TCallable, const Group*>;
|
||||
template <typename TCallable> concept CEntryVisitor = std::is_invocable_v<TCallable, Entry*>;
|
||||
template <typename TCallable> concept CEntryConstVisitor = std::is_invocable_v<TCallable, const Entry*>;
|
||||
|
||||
|
||||
class Group : public ModifiableObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -47,10 +60,16 @@ public:
|
||||
{
|
||||
CloneNoFlags = 0,
|
||||
CloneNewUuid = 1, // generate a random uuid for the clone
|
||||
CloneResetTimeInfo = 2, // set all TimeInfo attributes to the current time
|
||||
CloneIncludeEntries = 4, // clone the group entries
|
||||
CloneDefault = CloneNewUuid | CloneResetTimeInfo | CloneIncludeEntries,
|
||||
CloneRenameTitle = 8, // add "- Clone" after the original title
|
||||
CloneResetCreationTime = 2, // set timeInfo.CreationTime to the current time
|
||||
CloneResetLastAccessTime = 4, // set timeInfo.LastAccessTime to the current time
|
||||
CloneResetLocationChangedTime = 8, // set timeInfo.LocationChangedTime to the current time
|
||||
CloneIncludeEntries = 16, // clone the group entries
|
||||
CloneRenameTitle = 32, // add "- Clone" after the original title
|
||||
|
||||
CloneResetTimeInfo = CloneResetCreationTime | CloneResetLastAccessTime | CloneResetLocationChangedTime,
|
||||
CloneExactCopy = CloneIncludeEntries,
|
||||
CloneCopy = CloneExactCopy | CloneNewUuid | CloneResetTimeInfo,
|
||||
CloneDefault = CloneCopy,
|
||||
};
|
||||
Q_DECLARE_FLAGS(CloneFlags, CloneFlag)
|
||||
|
||||
@ -148,6 +167,7 @@ public:
|
||||
void setParent(Group* parent, int index = -1, bool trackPrevious = true);
|
||||
QStringList hierarchy(int height = -1) const;
|
||||
bool hasChildren() const;
|
||||
bool isDescendantOf(const Group* group) const;
|
||||
|
||||
Database* database();
|
||||
const Database* database() const;
|
||||
@ -159,6 +179,53 @@ public:
|
||||
QList<Entry*> entriesRecursive(bool includeHistoryItems = false) const;
|
||||
QList<const Group*> groupsRecursive(bool includeSelf) const;
|
||||
QList<Group*> groupsRecursive(bool includeSelf);
|
||||
|
||||
/**
|
||||
* Walk methods for traversing the tree (depth-first search)
|
||||
*
|
||||
* @param[in] includeSelf is the current group to be included or excluded
|
||||
* if `false` the current group's entries will not be included either
|
||||
* @param[in] groupVisitor functor that takes a single argument: ([const] Group*)
|
||||
* the functor may return a bool to indicate whether to stop=`true` or continue=`false` traversing
|
||||
* for a non-`bool` return-type the value is ignored and the traversing will continue as if `false` had been returned
|
||||
* @param[in] entryVisitor functor that takes a single argument: ([const] Entry*)
|
||||
* the functor may return a bool to indicate whether to stop=`true` or continue=`false` traversing
|
||||
* for a non-`bool` return-type the value is ignored and the traversing will continue as if `false` had been returned
|
||||
* @return `false` if the traversing completed without stop, or `true` otherwise
|
||||
*/
|
||||
template <CGroupVisitor TGroupCallable, CEntryVisitor TEntryCallable>
|
||||
bool walk(bool includeSelf, TGroupCallable&& groupVisitor, TEntryCallable&& entryVisitor)
|
||||
{
|
||||
return walk<TGroupCallable, TEntryCallable, false, true, true>(
|
||||
includeSelf, std::forward<TGroupCallable>(groupVisitor), std::forward<TEntryCallable>(entryVisitor));
|
||||
}
|
||||
template <CGroupConstVisitor TGroupCallable, CEntryConstVisitor TEntryCallable>
|
||||
bool walk(bool includeSelf, TGroupCallable&& groupVisitor, TEntryCallable&& entryVisitor) const
|
||||
{
|
||||
return walk<TGroupCallable, TEntryCallable, true, true, true>(
|
||||
includeSelf, std::forward<TGroupCallable>(groupVisitor), std::forward<TEntryCallable>(entryVisitor));
|
||||
}
|
||||
template <CGroupConstVisitor TGroupCallable> bool walkGroups(bool includeSelf, TGroupCallable&& groupVisitor) const
|
||||
{
|
||||
return walk<TGroupCallable, void*, true, true, false>(
|
||||
includeSelf, std::forward<TGroupCallable>(groupVisitor), nullptr);
|
||||
}
|
||||
template <CGroupVisitor TGroupCallable> bool walkGroups(bool includeSelf, TGroupCallable&& groupVisitor)
|
||||
{
|
||||
return walk<TGroupCallable, void*, false, true, false>(
|
||||
includeSelf, std::forward<TGroupCallable>(groupVisitor), nullptr);
|
||||
}
|
||||
template <CEntryConstVisitor TEntryCallable> bool walkEntries(TEntryCallable&& entryVisitor) const
|
||||
{
|
||||
return walk<void*, TEntryCallable, true, false, true>(
|
||||
true, nullptr, std::forward<TEntryCallable>(entryVisitor));
|
||||
}
|
||||
template <CEntryVisitor TEntryCallable> bool walkEntries(TEntryCallable&& entryVisitor)
|
||||
{
|
||||
return walk<void*, TEntryCallable, false, false, true>(
|
||||
true, nullptr, std::forward<TEntryCallable>(entryVisitor));
|
||||
}
|
||||
|
||||
QSet<QUuid> customIconsRecursive() const;
|
||||
QList<QString> usernamesRecursive(int topN = -1) const;
|
||||
|
||||
@ -204,8 +271,11 @@ private slots:
|
||||
void updateTimeinfo();
|
||||
|
||||
private:
|
||||
template <class P, class V> bool set(P& property, const V& value);
|
||||
template <typename TGroupCallable, typename TEntryCallable, bool kIsConst, bool kVisitGroups, bool kVisitEntries>
|
||||
bool walk(bool includeSelf, TGroupCallable&& groupVisitor, TEntryCallable&& entryVisitor) const;
|
||||
template <class P, class V> bool set(P& property, const V& value, bool preserveTimeinfo = false);
|
||||
|
||||
void emitModifiedEx(bool preserveTimeinfo);
|
||||
void setParent(Database* db);
|
||||
|
||||
void connectDatabaseSignalsRecursive(Database* db);
|
||||
@ -233,4 +303,55 @@ private:
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(Group::CloneFlags)
|
||||
|
||||
// helpers to support non-bool returning callables
|
||||
template <bool kDefaultRetVal, typename TCallable, typename... Args>
|
||||
bool visitorPredicateImpl(std::true_type, TCallable&& callable, Args&&... args)
|
||||
{
|
||||
return callable(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <bool kDefaultRetVal, typename TCallable, typename... Args>
|
||||
bool visitorPredicateImpl(std::false_type, TCallable&& callable, Args&&... args)
|
||||
{
|
||||
callable(std::forward<Args>(args)...);
|
||||
return kDefaultRetVal;
|
||||
}
|
||||
|
||||
template <bool kDefaultRetVal, typename TCallable, typename... Args>
|
||||
bool visitorPredicate(TCallable&& callable, Args&&... args)
|
||||
{
|
||||
using RetType = decltype(callable(args...));
|
||||
return visitorPredicateImpl<kDefaultRetVal>(
|
||||
std::is_same<RetType, bool>{}, std::forward<TCallable>(callable), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename TGroupCallable, typename TEntryCallable, bool kIsConst, bool kVisitGroups, bool kVisitEntries>
|
||||
bool Group::walk(bool includeSelf, TGroupCallable&& groupVisitor, TEntryCallable&& entryVisitor) const
|
||||
{
|
||||
using GroupType = typename std::conditional<kIsConst,const Group, Group>::type;
|
||||
QList<Group*> groupsToVisit;
|
||||
if (includeSelf) {
|
||||
groupsToVisit.append(const_cast<Group*>(this));
|
||||
} else {
|
||||
groupsToVisit.append(m_children);
|
||||
}
|
||||
while (!groupsToVisit.isEmpty()) {
|
||||
GroupType* group = groupsToVisit.takeLast(); // right-to-left
|
||||
if constexpr (kVisitGroups) {
|
||||
if (visitorPredicate<false>(groupVisitor, group)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if constexpr (kVisitEntries) {
|
||||
for (auto* entry : group->m_entries) {
|
||||
if (visitorPredicate<false>(entryVisitor, entry)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
groupsToVisit.append(group->m_children);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif // KEEPASSX_GROUP_H
|
||||
|
@ -419,14 +419,21 @@ QUuid Metadata::findCustomIcon(const QByteArray& candidate)
|
||||
return m_customIconsHashes.value(hash, QUuid());
|
||||
}
|
||||
|
||||
void Metadata::copyCustomIcon(const QUuid& iconUuid, const Metadata* otherMetadata)
|
||||
{
|
||||
if (iconUuid.isNull()) {
|
||||
return;
|
||||
}
|
||||
Q_ASSERT(otherMetadata->hasCustomIcon(iconUuid));
|
||||
if (!hasCustomIcon(iconUuid) && otherMetadata->hasCustomIcon(iconUuid)) {
|
||||
addCustomIcon(iconUuid, otherMetadata->customIcon(iconUuid));
|
||||
}
|
||||
}
|
||||
|
||||
void Metadata::copyCustomIcons(const QSet<QUuid>& iconList, const Metadata* otherMetadata)
|
||||
{
|
||||
for (const QUuid& uuid : iconList) {
|
||||
Q_ASSERT(otherMetadata->hasCustomIcon(uuid));
|
||||
|
||||
if (!hasCustomIcon(uuid) && otherMetadata->hasCustomIcon(uuid)) {
|
||||
addCustomIcon(uuid, otherMetadata->customIcon(uuid));
|
||||
}
|
||||
copyCustomIcon(uuid, otherMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,6 +138,7 @@ public:
|
||||
const QString& name = {},
|
||||
const QDateTime& lastModified = {});
|
||||
void removeCustomIcon(const QUuid& uuid);
|
||||
void copyCustomIcon(const QUuid& iconUuid, const Metadata* otherMetadata);
|
||||
void copyCustomIcons(const QSet<QUuid>& iconList, const Metadata* otherMetadata);
|
||||
QUuid findCustomIcon(const QByteArray& candidate);
|
||||
void setRecycleBinEnabled(bool value);
|
||||
|
@ -724,6 +724,10 @@ QList<DatabaseWidget*> MainWindow::getOpenDatabases()
|
||||
return dbWidgets;
|
||||
}
|
||||
|
||||
DatabaseWidget* MainWindow::currentDatabaseWidget() {
|
||||
return m_ui->tabWidget->currentDatabaseWidget();
|
||||
}
|
||||
|
||||
void MainWindow::showErrorMessage(const QString& message)
|
||||
{
|
||||
m_ui->globalMessageWidget->showMessage(message, MessageWidget::Error);
|
||||
|
@ -52,6 +52,7 @@ public:
|
||||
~MainWindow() override;
|
||||
|
||||
QList<DatabaseWidget*> getOpenDatabases();
|
||||
DatabaseWidget* currentDatabaseWidget();
|
||||
void restoreConfigState();
|
||||
void setAllowScreenCapture(bool state);
|
||||
|
||||
|
@ -466,7 +466,7 @@ Qt::DropActions EntryModel::supportedDropActions() const
|
||||
|
||||
Qt::DropActions EntryModel::supportedDragActions() const
|
||||
{
|
||||
return (Qt::MoveAction | Qt::CopyAction);
|
||||
return Qt::MoveAction | Qt::CopyAction | Qt::LinkAction;
|
||||
}
|
||||
|
||||
Qt::ItemFlags EntryModel::flags(const QModelIndex& modelIndex) const
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "core/Tools.h"
|
||||
#include "gui/DatabaseIcons.h"
|
||||
#include "gui/Icons.h"
|
||||
#include "gui/MainWindow.h"
|
||||
#include "keeshare/KeeShare.h"
|
||||
|
||||
GroupModel::GroupModel(Database* db, QObject* parent)
|
||||
@ -180,7 +181,7 @@ Group* GroupModel::groupFromIndex(const QModelIndex& index) const
|
||||
|
||||
Qt::DropActions GroupModel::supportedDropActions() const
|
||||
{
|
||||
return Qt::MoveAction | Qt::CopyAction;
|
||||
return Qt::MoveAction | Qt::CopyAction | Qt::LinkAction;
|
||||
}
|
||||
|
||||
Qt::ItemFlags GroupModel::flags(const QModelIndex& modelIndex) const
|
||||
@ -204,9 +205,11 @@ bool GroupModel::dropMimeData(const QMimeData* data,
|
||||
|
||||
if (action == Qt::IgnoreAction) {
|
||||
return true;
|
||||
} else if (action != Qt::MoveAction && action != Qt::CopyAction && action != ::Qt::LinkAction) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!data || (action != Qt::MoveAction && action != Qt::CopyAction) || !parent.isValid()) {
|
||||
if (!data || !parent.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -223,6 +226,12 @@ bool GroupModel::dropMimeData(const QMimeData* data,
|
||||
row = rowCount(parent);
|
||||
}
|
||||
|
||||
auto showErrorMessage = [](const QString& errorMessage){
|
||||
if(auto dbWidget = getMainWindow()->currentDatabaseWidget()) {
|
||||
dbWidget->showErrorMessage(errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
// decode and insert
|
||||
QByteArray encoded = data->data(isGroup ? types.at(0) : types.at(1));
|
||||
QDataStream stream(&encoded, QIODevice::ReadOnly);
|
||||
@ -234,17 +243,17 @@ bool GroupModel::dropMimeData(const QMimeData* data,
|
||||
QUuid groupUuid;
|
||||
stream >> dbUuid >> groupUuid;
|
||||
|
||||
Database* db = Database::databaseByUuid(dbUuid);
|
||||
if (!db) {
|
||||
Database* sourceDb = Database::databaseByUuid(dbUuid);
|
||||
if (!sourceDb) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Group* dragGroup = db->rootGroup()->findGroupByUuid(groupUuid);
|
||||
if (!dragGroup || !db->rootGroup()->findGroupByUuid(dragGroup->uuid()) || dragGroup == db->rootGroup()) {
|
||||
Group* dragGroup = sourceDb->rootGroup()->findGroupByUuid(groupUuid);
|
||||
if (!dragGroup || dragGroup == sourceDb->rootGroup()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dragGroup == parentGroup || dragGroup->findGroupByUuid(parentGroup->uuid())) {
|
||||
if (dragGroup == parentGroup || parentGroup->isDescendantOf(dragGroup)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -252,21 +261,64 @@ bool GroupModel::dropMimeData(const QMimeData* data,
|
||||
row--;
|
||||
}
|
||||
|
||||
Database* sourceDb = dragGroup->database();
|
||||
Database* targetDb = parentGroup->database();
|
||||
|
||||
Group* group = dragGroup;
|
||||
|
||||
if (sourceDb != targetDb) {
|
||||
QSet<QUuid> customIcons = group->customIconsRecursive();
|
||||
targetDb->metadata()->copyCustomIcons(customIcons, sourceDb->metadata());
|
||||
if (action == Qt::MoveAction || action == Qt::LinkAction) { // clang-format off
|
||||
|
||||
// Always clone the group across db's to reset UUIDs
|
||||
group = dragGroup->clone(Entry::CloneDefault | Entry::CloneIncludeHistory);
|
||||
if (action == Qt::MoveAction) {
|
||||
// Remove the original group from the sourceDb
|
||||
Group* binGroup = sourceDb->metadata()->recycleBin();
|
||||
if(binGroup && binGroup->uuid() == dragGroup->uuid()) {
|
||||
showErrorMessage(tr("Move error: \"%1\" group cannot be moved").arg(binGroup->name()));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Collect all UUID(s) or short-circuit when UUID is deleted in targetDb
|
||||
QSet<QUuid> uuidSet;
|
||||
bool complexMove = group->walk(true,
|
||||
[&](const Group* group) {
|
||||
uuidSet.insert(group->uuid());
|
||||
return targetDb->containsDeletedObject(group->uuid());
|
||||
},
|
||||
[&](const Entry* entry) {
|
||||
uuidSet.insert(entry->uuid());
|
||||
return targetDb->containsDeletedObject(entry->uuid());
|
||||
}
|
||||
);
|
||||
|
||||
// Unable to handle complex moves until the Merger interface supports single group/entry merging
|
||||
if (complexMove || targetDb->rootGroup()->walk(true,
|
||||
[&](const Group* group)-> bool {
|
||||
return uuidSet.contains(group->uuid());
|
||||
},
|
||||
[&](const Entry* entry) -> bool {
|
||||
return uuidSet.contains(entry->uuid());
|
||||
}
|
||||
)) {
|
||||
showErrorMessage(tr("Move error: the group or one of it's descendants is already present in this database"));
|
||||
return true;
|
||||
}
|
||||
} // clang-format on
|
||||
|
||||
if (action == Qt::MoveAction) { // -- Tracked move
|
||||
|
||||
// A clone with new UUID but original CreationTime
|
||||
group = dragGroup->clone(Entry::CloneFlags(Entry::CloneCopy & ~Entry::CloneResetCreationTime),
|
||||
Group::CloneFlags(Group::CloneCopy & ~Group::CloneResetCreationTime));
|
||||
// Original UUID is marked as deleted to propagate the move to dbs that merge with this one
|
||||
delete dragGroup;
|
||||
} else if (action == Qt::LinkAction) { // -- Untracked move
|
||||
|
||||
QList<DeletedObject> deletedObjects(sourceDb->deletedObjects());
|
||||
group = dragGroup->clone(Entry::CloneExactCopy, Group::CloneExactCopy);
|
||||
delete dragGroup;
|
||||
// Unmark UUID(s) as deleted by restoring the previous list
|
||||
sourceDb->setDeletedObjects(deletedObjects);
|
||||
} else {
|
||||
group = dragGroup->clone(Entry::CloneCopy);
|
||||
}
|
||||
|
||||
targetDb->metadata()->copyCustomIcons(group->customIconsRecursive(), sourceDb->metadata());
|
||||
} else if (action == Qt::CopyAction) {
|
||||
group = dragGroup->clone(Entry::CloneCopy);
|
||||
}
|
||||
@ -277,43 +329,69 @@ bool GroupModel::dropMimeData(const QMimeData* data,
|
||||
return false;
|
||||
}
|
||||
|
||||
int entries{0}, entriesNotMoved{0};
|
||||
while (!stream.atEnd()) {
|
||||
QUuid dbUuid;
|
||||
QUuid entryUuid;
|
||||
stream >> dbUuid >> entryUuid;
|
||||
++entries;
|
||||
|
||||
Database* db = Database::databaseByUuid(dbUuid);
|
||||
if (!db) {
|
||||
Database* sourceDb = Database::databaseByUuid(dbUuid);
|
||||
if (!sourceDb) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Entry* dragEntry = db->rootGroup()->findEntryByUuid(entryUuid);
|
||||
if (!dragEntry || !db->rootGroup()->findEntryByUuid(dragEntry->uuid())) {
|
||||
Entry* dragEntry = sourceDb->rootGroup()->findEntryByUuid(entryUuid);
|
||||
if (!dragEntry) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Database* sourceDb = dragEntry->group()->database();
|
||||
Database* targetDb = parentGroup->database();
|
||||
|
||||
Entry* entry = dragEntry;
|
||||
|
||||
if (sourceDb != targetDb) {
|
||||
QUuid customIcon = entry->iconUuid();
|
||||
if (!customIcon.isNull() && !targetDb->metadata()->hasCustomIcon(customIcon)) {
|
||||
targetDb->metadata()->addCustomIcon(customIcon, sourceDb->metadata()->customIcon(customIcon).data);
|
||||
if (action == Qt::MoveAction || action == Qt::LinkAction) { // clang-format off
|
||||
|
||||
// Unable to handle complex moves until the Merger interface supports single group/entry merging
|
||||
if (targetDb->containsDeletedObject(dragEntry->uuid()) ||
|
||||
targetDb->rootGroup()->walkEntries([=](const Entry* entry) {
|
||||
return dragEntry->uuid() == entry->uuid();
|
||||
}
|
||||
)) {
|
||||
++entriesNotMoved;
|
||||
continue;
|
||||
}
|
||||
} // clang-format on
|
||||
|
||||
if (action == Qt::MoveAction) { // -- Tracked move
|
||||
|
||||
// A clone with new UUID but original CreationTime
|
||||
entry = dragEntry->clone(Entry::CloneFlags(Entry::CloneCopy & ~Entry::CloneResetCreationTime));
|
||||
// Original UUID is marked as deleted to propagate the move to dbs that merge with this one
|
||||
delete dragEntry;
|
||||
} else if (action == Qt::LinkAction) { // -- Untracked move
|
||||
|
||||
QList<DeletedObject> deletedObjects(sourceDb->deletedObjects());
|
||||
entry = dragEntry->clone(Entry::CloneExactCopy);
|
||||
delete dragEntry;
|
||||
// Unmark UUID as deleted by restoring the previous list
|
||||
sourceDb->setDeletedObjects(deletedObjects);
|
||||
} else {
|
||||
entry = dragEntry->clone(Entry::CloneCopy);
|
||||
}
|
||||
|
||||
// Reset the UUID when moving across db boundary
|
||||
entry = dragEntry->clone(Entry::CloneDefault | Entry::CloneIncludeHistory);
|
||||
if (action == Qt::MoveAction) {
|
||||
delete dragEntry;
|
||||
}
|
||||
targetDb->metadata()->copyCustomIcon(entry->iconUuid(), sourceDb->metadata());
|
||||
} else if (action == Qt::CopyAction) {
|
||||
entry = dragEntry->clone(Entry::CloneCopy);
|
||||
}
|
||||
|
||||
entry->setGroup(parentGroup);
|
||||
}
|
||||
|
||||
if (entriesNotMoved) {
|
||||
showErrorMessage(
|
||||
tr("Move error: %1 of %2 entry(s) are already present in this database").arg(entriesNotMoved).arg(entries));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -24,11 +24,15 @@
|
||||
#include "core/Config.h"
|
||||
#include "core/Group.h"
|
||||
#include "gui/group/GroupModel.h"
|
||||
#include "gui/entry/EntryView.h"
|
||||
#include "gui/DatabaseWidget.h"
|
||||
|
||||
GroupView::GroupView(Database* db, QWidget* parent)
|
||||
: QTreeView(parent)
|
||||
, m_model(new GroupModel(db, this))
|
||||
, m_updatingExpanded(false)
|
||||
, m_isDragEventSrcFromOtherDb(false)
|
||||
, m_lastAcceptedDropAction(Qt::IgnoreAction)
|
||||
{
|
||||
QTreeView::setModel(m_model);
|
||||
setHeaderHidden(true);
|
||||
@ -96,20 +100,83 @@ void GroupView::changeDatabase(const QSharedPointer<Database>& newDb)
|
||||
setColumnWidth(0, sizeHintForColumn(0));
|
||||
}
|
||||
|
||||
void GroupView::dragMoveEvent(QDragMoveEvent* event)
|
||||
void GroupView::dragEnterEvent(QDragEnterEvent *event)
|
||||
{
|
||||
if (event->keyboardModifiers() & Qt::ControlModifier) {
|
||||
event->setDropAction(Qt::CopyAction);
|
||||
} else {
|
||||
event->setDropAction(Qt::MoveAction);
|
||||
event->ignore(); // default to ignore
|
||||
|
||||
auto const eventSource = event->source();
|
||||
// ignore events from other processes
|
||||
if (!eventSource) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore events with unsupported mime-types
|
||||
auto supportedFormats = m_model->mimeTypes().toSet();
|
||||
if (!supportedFormats.intersects(event->mimeData()->formats().toSet())) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto firstAncestorOfTypeDatabaseWidget = [](QObject* object) -> DatabaseWidget* {
|
||||
if (object) {
|
||||
for (auto parent = object->parent(); parent; parent = parent->parent()) {
|
||||
if (auto dbWidget = qobject_cast<DatabaseWidget*>(parent)) {
|
||||
return dbWidget;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
m_isDragEventSrcFromOtherDb = false;
|
||||
if (GroupView* view = qobject_cast<GroupView*>(eventSource)) {
|
||||
m_isDragEventSrcFromOtherDb = view != this;
|
||||
} else if (EntryView* view = qobject_cast<EntryView*>(eventSource)) {
|
||||
auto targetDbWidget = firstAncestorOfTypeDatabaseWidget(this);
|
||||
auto sourceDbWidget = firstAncestorOfTypeDatabaseWidget(view);
|
||||
m_isDragEventSrcFromOtherDb = sourceDbWidget != targetDbWidget;
|
||||
}
|
||||
|
||||
QTreeView::dragEnterEvent(event);
|
||||
}
|
||||
|
||||
void GroupView::dragMoveEvent(QDragMoveEvent* event)
|
||||
{
|
||||
QTreeView::dragMoveEvent(event);
|
||||
|
||||
if (!event->isAccepted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// entries may only be dropped on groups
|
||||
if (event->isAccepted() && event->mimeData()->hasFormat("application/x-keepassx-entry")
|
||||
if (event->mimeData()->hasFormat("application/x-keepassx-entry")
|
||||
&& (dropIndicatorPosition() == AboveItem || dropIndicatorPosition() == BelowItem)) {
|
||||
event->ignore();
|
||||
return;
|
||||
}
|
||||
|
||||
// figure out which dropaction should be used
|
||||
Qt::DropAction dropAction = Qt::MoveAction;
|
||||
if (event->keyboardModifiers() & Qt::ControlModifier) {
|
||||
dropAction = Qt::CopyAction;
|
||||
} else if (event->keyboardModifiers() & Qt::AltModifier) {
|
||||
dropAction = m_isDragEventSrcFromOtherDb ? Qt::LinkAction : Qt::IgnoreAction;
|
||||
}
|
||||
|
||||
if (dropAction != Qt::IgnoreAction && event->possibleActions() & dropAction) {
|
||||
event->setDropAction(dropAction);
|
||||
m_lastAcceptedDropAction = event->dropAction();
|
||||
} else {
|
||||
event->ignore();
|
||||
}
|
||||
}
|
||||
|
||||
void GroupView::dropEvent(QDropEvent* event)
|
||||
{
|
||||
if (m_lastAcceptedDropAction != Qt::IgnoreAction) {
|
||||
event->setDropAction(m_lastAcceptedDropAction);
|
||||
QTreeView::dropEvent(event);
|
||||
} else {
|
||||
event->ignore();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,9 @@ private slots:
|
||||
void selectNextGroup();
|
||||
|
||||
protected:
|
||||
void dragEnterEvent(QDragEnterEvent *event) override;
|
||||
void dragMoveEvent(QDragMoveEvent* event) override;
|
||||
void dropEvent(QDropEvent* event) override;
|
||||
void focusInEvent(QFocusEvent* event) override;
|
||||
|
||||
private:
|
||||
@ -58,6 +60,8 @@ private:
|
||||
|
||||
GroupModel* const m_model;
|
||||
bool m_updatingExpanded;
|
||||
bool m_isDragEventSrcFromOtherDb;
|
||||
Qt::DropAction m_lastAcceptedDropAction;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_GROUPVIEW_H
|
||||
|
@ -168,7 +168,7 @@ if(WITH_XC_SSHAGENT)
|
||||
endif()
|
||||
|
||||
add_unit_test(NAME testentry SOURCES TestEntry.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
LIBS testsupport ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testmerge SOURCES TestMerge.cpp
|
||||
LIBS testsupport ${TEST_LIBRARIES})
|
||||
|
@ -25,13 +25,34 @@
|
||||
#include "core/TimeInfo.h"
|
||||
#include "crypto/Crypto.h"
|
||||
|
||||
#include "mock/MockClock.h"
|
||||
|
||||
QTEST_GUILESS_MAIN(TestEntry)
|
||||
|
||||
namespace
|
||||
{
|
||||
MockClock* m_clock = nullptr;
|
||||
}
|
||||
|
||||
void TestEntry::initTestCase()
|
||||
{
|
||||
QVERIFY(Crypto::init());
|
||||
}
|
||||
|
||||
void TestEntry::init()
|
||||
{
|
||||
Q_ASSERT(m_clock == nullptr);
|
||||
m_clock = new MockClock(2010, 5, 5, 10, 30, 10);
|
||||
MockClock::setup(m_clock);
|
||||
}
|
||||
|
||||
void TestEntry::cleanup()
|
||||
{
|
||||
MockClock::teardown();
|
||||
m_clock = nullptr;
|
||||
}
|
||||
|
||||
|
||||
void TestEntry::testHistoryItemDeletion()
|
||||
{
|
||||
QScopedPointer<Entry> entry(new Entry());
|
||||
@ -110,6 +131,8 @@ void TestEntry::testClone()
|
||||
QCOMPARE(entryCloneNewUuid->timeInfo().creationTime(), entryOrg->timeInfo().creationTime());
|
||||
|
||||
// Reset modification time
|
||||
entryOrgTime.setLastAccessTime(Clock::datetimeUtc(60));
|
||||
entryOrgTime.setLocationChanged(Clock::datetimeUtc(60));
|
||||
entryOrgTime.setLastModificationTime(Clock::datetimeUtc(60));
|
||||
entryOrg->setTimeInfo(entryOrgTime);
|
||||
|
||||
@ -123,7 +146,12 @@ void TestEntry::testClone()
|
||||
QCOMPARE(entryCloneResetTime->uuid(), entryOrg->uuid());
|
||||
QCOMPARE(entryCloneResetTime->title(), QString("New Title"));
|
||||
QCOMPARE(entryCloneResetTime->historyItems().size(), 0);
|
||||
// Cloning with CloneResetTimeInfo should affect the CreationTime, LocationChanged, LastAccessTime
|
||||
QVERIFY(entryCloneResetTime->timeInfo().creationTime() != entryOrg->timeInfo().creationTime());
|
||||
QVERIFY(entryCloneResetTime->timeInfo().locationChanged() != entryOrg->timeInfo().locationChanged());
|
||||
QVERIFY(entryCloneResetTime->timeInfo().lastAccessTime() != entryOrg->timeInfo().lastAccessTime());
|
||||
// Cloning with CloneResetTimeInfo should not affect the LastModificationTime
|
||||
QCOMPARE(entryCloneResetTime->timeInfo().lastModificationTime(), entryOrg->timeInfo().lastModificationTime());
|
||||
|
||||
// Date back history of original entry
|
||||
Entry* firstHistoryItem = entryOrg->historyItems()[0];
|
||||
@ -851,3 +879,33 @@ void TestEntry::testPreviousParentGroup()
|
||||
QVERIFY(entry->previousParentGroupUuid() == group1->uuid());
|
||||
QVERIFY(entry->previousParentGroup() == group1);
|
||||
}
|
||||
|
||||
void TestEntry::testTimeinfoChanges()
|
||||
{
|
||||
Database db;
|
||||
auto* root = db.rootGroup();
|
||||
auto* subgroup = new Group();
|
||||
subgroup->setUuid(QUuid::createUuid());
|
||||
subgroup->setParent(root);
|
||||
QDateTime startTime = Clock::currentDateTimeUtc();
|
||||
TimeInfo startTimeinfo;
|
||||
startTimeinfo.setCreationTime(startTime);
|
||||
startTimeinfo.setLastModificationTime(startTime);
|
||||
startTimeinfo.setLocationChanged(startTime);
|
||||
startTimeinfo.setLastAccessTime(startTime);
|
||||
m_clock->advanceMinute(1);
|
||||
|
||||
QScopedPointer<Entry> entry(new Entry());
|
||||
entry->setUuid(QUuid::createUuid());
|
||||
entry->setGroup(root);
|
||||
entry->setTimeInfo(startTimeinfo);
|
||||
entry->setPreviousParentGroup(subgroup);
|
||||
// setting previous parent group should not affect the LastModificationTime
|
||||
QCOMPARE(entry->timeInfo().lastModificationTime(), startTime);
|
||||
entry->setGroup(subgroup);
|
||||
// changing group should not affect LastModicationTime, CreationTime
|
||||
QCOMPARE(entry->timeInfo().creationTime(), startTime);
|
||||
QCOMPARE(entry->timeInfo().lastModificationTime(), startTime);
|
||||
// changing group should affect the LocationChanged time
|
||||
QCOMPARE(entry->timeInfo().locationChanged(), Clock::currentDateTimeUtc());
|
||||
}
|
||||
|
@ -28,6 +28,8 @@ class TestEntry : public QObject
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void init();
|
||||
void cleanup();
|
||||
void testHistoryItemDeletion();
|
||||
void testCopyDataFrom();
|
||||
void testClone();
|
||||
@ -42,6 +44,7 @@ private slots:
|
||||
void testIsRecycled();
|
||||
void testMoveUpDown();
|
||||
void testPreviousParentGroup();
|
||||
void testTimeinfoChanges();
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_TESTENTRY_H
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "mock/MockClock.h"
|
||||
|
||||
#include <QSet>
|
||||
#include <QVector>
|
||||
#include <QSignalSpy>
|
||||
#include <QtTestGui>
|
||||
|
||||
@ -382,18 +383,21 @@ void TestGroup::testClone()
|
||||
QCOMPARE(clonedGroup->iconNumber(), 42);
|
||||
QCOMPARE(clonedGroup->children().size(), 1);
|
||||
QCOMPARE(clonedGroup->entries().size(), 1);
|
||||
QCOMPARE(clonedGroup->timeInfo(), originalGroup->timeInfo());
|
||||
|
||||
Entry* clonedGroupEntry = clonedGroup->entries().at(0);
|
||||
QVERIFY(clonedGroupEntry->uuid() != originalGroupEntry->uuid());
|
||||
QCOMPARE(clonedGroupEntry->title(), QString("GroupEntry"));
|
||||
QCOMPARE(clonedGroupEntry->iconNumber(), 43);
|
||||
QCOMPARE(clonedGroupEntry->historyItems().size(), 0);
|
||||
QCOMPARE(clonedGroupEntry->timeInfo(), originalGroupEntry->timeInfo());
|
||||
|
||||
Group* clonedSubGroup = clonedGroup->children().at(0);
|
||||
QVERIFY(clonedSubGroup->uuid() != subGroup->uuid());
|
||||
QCOMPARE(clonedSubGroup->name(), QString("SubGroup"));
|
||||
QCOMPARE(clonedSubGroup->children().size(), 0);
|
||||
QCOMPARE(clonedSubGroup->entries().size(), 1);
|
||||
QCOMPARE(clonedSubGroup->timeInfo(), subGroup->timeInfo());
|
||||
|
||||
Entry* clonedSubGroupEntry = clonedSubGroup->entries().at(0);
|
||||
QVERIFY(clonedSubGroupEntry->uuid() != subGroupEntry->uuid());
|
||||
@ -411,15 +415,17 @@ void TestGroup::testClone()
|
||||
QCOMPARE(clonedGroupNewUuid->entries().size(), 0);
|
||||
QVERIFY(clonedGroupNewUuid->uuid() != originalGroup->uuid());
|
||||
|
||||
// Making sure the new modification date is not the same.
|
||||
// Verify Timeinfo modifications for CloneResetTimeInfo
|
||||
m_clock->advanceSecond(1);
|
||||
|
||||
QScopedPointer<Group> clonedGroupResetTimeInfo(
|
||||
originalGroup->clone(Entry::CloneNoFlags, Group::CloneNewUuid | Group::CloneResetTimeInfo));
|
||||
QCOMPARE(clonedGroupResetTimeInfo->entries().size(), 0);
|
||||
QVERIFY(clonedGroupResetTimeInfo->uuid() != originalGroup->uuid());
|
||||
QVERIFY(clonedGroupResetTimeInfo->timeInfo().lastModificationTime()
|
||||
!= originalGroup->timeInfo().lastModificationTime());
|
||||
QVERIFY(clonedGroupResetTimeInfo->timeInfo().creationTime() != originalGroup->timeInfo().creationTime());
|
||||
QVERIFY(clonedGroupResetTimeInfo->timeInfo().lastAccessTime() != originalGroup->timeInfo().lastAccessTime());
|
||||
QVERIFY(clonedGroupResetTimeInfo->timeInfo().locationChanged() != originalGroup->timeInfo().locationChanged());
|
||||
QCOMPARE(clonedGroupResetTimeInfo->timeInfo().lastModificationTime(), originalGroup->timeInfo().lastModificationTime());
|
||||
}
|
||||
|
||||
void TestGroup::testCopyCustomIcons()
|
||||
@ -1319,3 +1325,158 @@ void TestGroup::testAutoTypeState()
|
||||
QVERIFY(!entry1->groupAutoTypeEnabled());
|
||||
QVERIFY(entry2->groupAutoTypeEnabled());
|
||||
}
|
||||
|
||||
void TestGroup::testTimeinfoChanges()
|
||||
{
|
||||
Database db, db2;
|
||||
auto* root = db.rootGroup();
|
||||
auto* subgroup1 = new Group();
|
||||
auto* subgroup2 = new Group();
|
||||
subgroup1->setUuid(QUuid::createUuid());
|
||||
subgroup1->setParent(root);
|
||||
subgroup2->setUuid(QUuid::createUuid());
|
||||
subgroup2->setParent(root);
|
||||
QDateTime startTime = Clock::currentDateTimeUtc();
|
||||
TimeInfo startTimeinfo;
|
||||
startTimeinfo.setCreationTime(startTime);
|
||||
startTimeinfo.setLastModificationTime(startTime);
|
||||
startTimeinfo.setLocationChanged(startTime);
|
||||
startTimeinfo.setLastAccessTime(startTime);
|
||||
m_clock->advanceMinute(1);
|
||||
root->setTimeInfo(startTimeinfo);
|
||||
subgroup1->setTimeInfo(startTimeinfo);
|
||||
subgroup2->setTimeInfo(startTimeinfo);
|
||||
|
||||
subgroup2->setPreviousParentGroup(subgroup1);
|
||||
// setting previous parent group should not affect the LastModificationTime
|
||||
QCOMPARE(subgroup2->timeInfo().lastModificationTime(), startTime);
|
||||
subgroup2->setPreviousParentGroup(nullptr);
|
||||
subgroup2->setParent(subgroup1);
|
||||
QCOMPARE(root->timeInfo(), startTimeinfo);
|
||||
QCOMPARE(subgroup1->timeInfo(), startTimeinfo);
|
||||
// changing group should not affect LastModificationTime, CreationTime
|
||||
QCOMPARE(subgroup2->timeInfo().creationTime(), startTime);
|
||||
QCOMPARE(subgroup2->timeInfo().lastModificationTime(), startTime);
|
||||
// changing group should affect the LocationChanged time
|
||||
QCOMPARE(subgroup2->timeInfo().locationChanged(), Clock::currentDateTimeUtc());
|
||||
|
||||
// cross-db move
|
||||
db2.rootGroup()->setTimeInfo(startTimeinfo);
|
||||
m_clock->advanceMinute(1);
|
||||
subgroup2->setParent(db2.rootGroup());
|
||||
QCOMPARE(subgroup2->timeInfo().creationTime(), startTime);
|
||||
QCOMPARE(subgroup2->timeInfo().lastModificationTime(), startTime);
|
||||
QCOMPARE(subgroup2->timeInfo().locationChanged(), Clock::currentDateTimeUtc());
|
||||
QCOMPARE(db2.rootGroup()->timeInfo(), startTimeinfo);
|
||||
|
||||
QScopedPointer<Entry> entry1(new Entry());
|
||||
entry1->setGroup(subgroup1);
|
||||
// adding/removing an entry should not affect the LastModificationTime
|
||||
QCOMPARE(subgroup1->timeInfo().lastModificationTime(), startTime);
|
||||
entry1.reset(); // delete
|
||||
QCOMPARE(subgroup1->timeInfo().lastModificationTime(), startTime);
|
||||
|
||||
// sorting should not affect the LastModificationTime
|
||||
root->sortChildrenRecursively(true);
|
||||
root->sortChildrenRecursively(false);
|
||||
QCOMPARE(root->timeInfo().lastModificationTime(), startTime);
|
||||
QCOMPARE(subgroup1->timeInfo().lastModificationTime(), startTime);
|
||||
}
|
||||
|
||||
void TestGroup::testWalk()
|
||||
{
|
||||
QScopedPointer<Group> root(new Group());
|
||||
size_t totalGroups{1}, totalEntries{0};
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
Group* subgroup = new Group();
|
||||
subgroup->setParent(root.data());
|
||||
++totalGroups;
|
||||
int rows = i + 1;
|
||||
int columns = i;
|
||||
QVector<Group*> groupsVec;
|
||||
groupsVec.resize(rows * columns);
|
||||
for (int r = 0; r < rows; ++r) {
|
||||
for (int c = 0; c < columns; ++c) {
|
||||
int index = r * columns + c;
|
||||
Group* group = new Group();
|
||||
groupsVec[index] = group;
|
||||
group->setParent(c > 0 ? groupsVec[index - 1] : subgroup);
|
||||
int entryCount = std::max<int>(1, c + 1 >= columns ? 20 - (i * 3) : c + r);
|
||||
for (int e = 0; e < entryCount; ++e) {
|
||||
Entry* entry = new Entry();
|
||||
entry->setGroup(group);
|
||||
}
|
||||
totalEntries += entryCount;
|
||||
}
|
||||
}
|
||||
totalGroups += groupsVec.size();
|
||||
}
|
||||
|
||||
|
||||
size_t groupCount{0}, entryCount{0};
|
||||
auto groupCounter = [&](Group* group){ groupCount += 1; };
|
||||
auto entryCounter = [&](const Entry* entry){ entryCount += 1; };
|
||||
bool shouldHaveStopped = false;
|
||||
bool calledAfterStopped = false;
|
||||
auto groupStopHalfway = [&](Group* group) {
|
||||
groupCounter(group);
|
||||
if (groupCount >= totalGroups / 2) {
|
||||
if (shouldHaveStopped) {
|
||||
calledAfterStopped = true;
|
||||
} else {
|
||||
shouldHaveStopped = true;
|
||||
calledAfterStopped = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
auto entryStopHalfWay = [&](Entry* entry) {
|
||||
entryCounter(entry);
|
||||
if (entryCount >= totalEntries / 2) {
|
||||
if (shouldHaveStopped) {
|
||||
calledAfterStopped = true;
|
||||
} else {
|
||||
shouldHaveStopped = true;
|
||||
calledAfterStopped = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
bool result = root->walk(true, groupCounter, entryCounter);
|
||||
// walk should not stopped
|
||||
QCOMPARE(result, false);
|
||||
// walk should have visited all groups & entries
|
||||
QCOMPARE(groupCount, totalGroups);
|
||||
QCOMPARE(entryCount, totalEntries);
|
||||
|
||||
groupCount = entryCount = 0;
|
||||
result = root->walkGroups(true, groupCounter);
|
||||
QCOMPARE(result, false);
|
||||
QCOMPARE(groupCount, totalGroups);
|
||||
result = const_cast<const Group*>(root.data())->walkEntries(entryCounter);
|
||||
QCOMPARE(result, false);
|
||||
QCOMPARE(entryCount, totalEntries);
|
||||
|
||||
groupCount = entryCount = 0;
|
||||
result = root->walk(false, groupStopHalfway, entryStopHalfWay);
|
||||
// should have stopped
|
||||
QCOMPARE(result, true);
|
||||
// should not have been called after stopped
|
||||
QCOMPARE(calledAfterStopped, false);
|
||||
|
||||
groupCount = entryCount = 0;
|
||||
shouldHaveStopped = false;
|
||||
result = root->walkGroups(false, groupStopHalfway);
|
||||
QCOMPARE(result, true);
|
||||
QCOMPARE(calledAfterStopped, false);
|
||||
|
||||
groupCount = entryCount = 0;
|
||||
shouldHaveStopped = false;
|
||||
result = root->walkEntries(entryStopHalfWay);
|
||||
QCOMPARE(result, true);
|
||||
QCOMPARE(calledAfterStopped, false);
|
||||
}
|
||||
|
@ -50,6 +50,8 @@ private slots:
|
||||
void testMoveUpDown();
|
||||
void testPreviousParentGroup();
|
||||
void testAutoTypeState();
|
||||
void testTimeinfoChanges();
|
||||
void testWalk();
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_TESTGROUP_H
|
||||
|
Loading…
x
Reference in New Issue
Block a user