keepassxc

Форк
0
/
Group.cpp 
1298 строк · 31.4 Кб
1
/*
2
 *  Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
3
 *  Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
4
 *
5
 *  This program is free software: you can redistribute it and/or modify
6
 *  it under the terms of the GNU General Public License as published by
7
 *  the Free Software Foundation, either version 2 or (at your option)
8
 *  version 3 of the License.
9
 *
10
 *  This program is distributed in the hope that it will be useful,
11
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 *  GNU General Public License for more details.
14
 *
15
 *  You should have received a copy of the GNU General Public License
16
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18

19
#include "Group.h"
20
#include "config-keepassx.h"
21

22
#include "core/Config.h"
23

24
#ifdef WITH_XC_KEESHARE
25
#include "keeshare/KeeShare.h"
26
#endif
27

28
#include "core/Global.h"
29
#include "core/Metadata.h"
30
#include "core/Tools.h"
31

32
#include <QtConcurrent>
33
#include <QtConcurrentFilter>
34

35
const int Group::DefaultIconNumber = 48;
36
const int Group::OpenFolderIconNumber = 49;
37
const int Group::RecycleBinIconNumber = 43;
38
const QString Group::RootAutoTypeSequence = "{USERNAME}{TAB}{PASSWORD}{ENTER}";
39

40
Group::Group()
41
    : m_customData(new CustomData(this))
42
    , m_updateTimeinfo(true)
43
{
44
    m_data.iconNumber = DefaultIconNumber;
45
    m_data.isExpanded = true;
46
    m_data.autoTypeEnabled = Inherit;
47
    m_data.searchingEnabled = Inherit;
48
    m_data.mergeMode = Default;
49

50
    connect(m_customData, &CustomData::modified, this, &Group::modified);
51
    connect(this, &Group::modified, this, &Group::updateTimeinfo);
52
    connect(this, &Group::groupNonDataChange, this, &Group::updateTimeinfo);
53
}
54

55
Group::~Group()
56
{
57
    setUpdateTimeinfo(false);
58
    // Destroy entries and children manually so DeletedObjects can be added
59
    // to database.
60
    const QList<Entry*> entries = m_entries;
61
    for (Entry* entry : entries) {
62
        delete entry;
63
    }
64

65
    const QList<Group*> children = m_children;
66
    for (Group* group : children) {
67
        delete group;
68
    }
69

70
    if (m_db && m_parent) {
71
        DeletedObject delGroup;
72
        delGroup.deletionTime = Clock::currentDateTimeUtc();
73
        delGroup.uuid = m_uuid;
74
        m_db->addDeletedObject(delGroup);
75
    }
76

77
    cleanupParent();
78
}
79

80
template <class P, class V> inline bool Group::set(P& property, const V& value)
81
{
82
    if (property != value) {
83
        property = value;
84
        emitModified();
85
        return true;
86
    } else {
87
        return false;
88
    }
89
}
90

91
bool Group::canUpdateTimeinfo() const
92
{
93
    return m_updateTimeinfo;
94
}
95

96
void Group::updateTimeinfo()
97
{
98
    if (m_updateTimeinfo) {
99
        m_data.timeInfo.setLastModificationTime(Clock::currentDateTimeUtc());
100
        m_data.timeInfo.setLastAccessTime(Clock::currentDateTimeUtc());
101
    }
102
}
103

104
void Group::setUpdateTimeinfo(bool value)
105
{
106
    m_updateTimeinfo = value;
107
}
108

109
const QUuid& Group::uuid() const
110
{
111
    return m_uuid;
112
}
113

114
const QString Group::uuidToHex() const
115
{
116
    return Tools::uuidToHex(m_uuid);
117
}
118

119
QString Group::name() const
120
{
121
    return m_data.name;
122
}
123

124
QString Group::notes() const
125
{
126
    return m_data.notes;
127
}
128

129
QString Group::tags() const
130
{
131
    return m_data.tags;
132
}
133

134
QString Group::fullPath() const
135
{
136
    QString fullPath;
137
    auto group = this;
138

139
    do {
140
        fullPath.insert(0, "/" + group->name());
141
        group = group->parentGroup();
142
    } while (group);
143

144
    return fullPath;
145
}
146

147
int Group::iconNumber() const
148
{
149
    return m_data.iconNumber;
150
}
151

152
const QUuid& Group::iconUuid() const
153
{
154
    return m_data.customIcon;
155
}
156

157
const TimeInfo& Group::timeInfo() const
158
{
159
    return m_data.timeInfo;
160
}
161

162
bool Group::isExpanded() const
163
{
164
    return m_data.isExpanded;
165
}
166

167
QString Group::defaultAutoTypeSequence() const
168
{
169
    return m_data.defaultAutoTypeSequence;
170
}
171

172
/**
173
 * Determine the effective sequence that will be injected
174
 * This function return an empty string if the current group or any parent has autotype disabled
175
 */
176
QString Group::effectiveAutoTypeSequence() const
177
{
178
    QString sequence;
179

180
    const Group* group = this;
181
    do {
182
        if (group->autoTypeEnabled() == Group::Disable) {
183
            return {};
184
        }
185

186
        sequence = group->defaultAutoTypeSequence();
187
        group = group->parentGroup();
188
    } while (group && sequence.isEmpty());
189

190
    if (sequence.isEmpty()) {
191
        sequence = RootAutoTypeSequence;
192
    }
193

194
    return sequence;
195
}
196

197
Group::TriState Group::autoTypeEnabled() const
198
{
199
    return m_data.autoTypeEnabled;
200
}
201

202
Group::TriState Group::searchingEnabled() const
203
{
204
    return m_data.searchingEnabled;
205
}
206

207
Group::MergeMode Group::mergeMode() const
208
{
209
    if (m_data.mergeMode == Group::MergeMode::Default) {
210
        if (m_parent) {
211
            return m_parent->mergeMode();
212
        }
213
        return Group::MergeMode::KeepNewer; // fallback
214
    }
215
    return m_data.mergeMode;
216
}
217

218
Entry* Group::lastTopVisibleEntry() const
219
{
220
    return m_lastTopVisibleEntry;
221
}
222

223
bool Group::isRecycled() const
224
{
225
    auto group = this;
226
    auto db = group->database();
227
    if (db) {
228
        auto recycleBin = db->metadata()->recycleBin();
229
        do {
230
            if (group == recycleBin) {
231
                return true;
232
            }
233
            group = group->m_parent;
234
        } while (group);
235
    }
236

237
    return false;
238
}
239

240
bool Group::isExpired() const
241
{
242
    return m_data.timeInfo.expires() && m_data.timeInfo.expiryTime() < Clock::currentDateTimeUtc();
243
}
244

245
bool Group::isEmpty() const
246
{
247
    return !hasChildren() && m_entries.isEmpty();
248
}
249

250
CustomData* Group::customData()
251
{
252
    return m_customData;
253
}
254

255
const CustomData* Group::customData() const
256
{
257
    return m_customData;
258
}
259

260
Group::TriState Group::resolveCustomDataTriState(const QString& key, bool checkParent) const
261
{
262
    // If not defined, check our parent up to the root group
263
    if (!m_customData->contains(key)) {
264
        if (!m_parent || !checkParent) {
265
            return Inherit;
266
        } else {
267
            return m_parent->resolveCustomDataTriState(key);
268
        }
269
    }
270

271
    return m_customData->value(key) == TRUE_STR ? Enable : Disable;
272
}
273

274
void Group::setCustomDataTriState(const QString& key, const Group::TriState& value)
275
{
276
    switch (value) {
277
    case Enable:
278
        m_customData->set(key, TRUE_STR);
279
        break;
280
    case Disable:
281
        m_customData->set(key, FALSE_STR);
282
        break;
283
    case Inherit:
284
        m_customData->remove(key);
285
        break;
286
    }
287
}
288

289
// Note that this returns an empty string both if the key is missing *or* if the key is present but value is empty.
290
QString Group::resolveCustomDataString(const QString& key, bool checkParent) const
291
{
292
    // If not defined, check our parent up to the root group
293
    if (!m_customData->contains(key)) {
294
        if (!m_parent || !checkParent) {
295
            return QString();
296
        } else {
297
            return m_parent->resolveCustomDataString(key);
298
        }
299
    }
300

301
    return m_customData->value(key);
302
}
303

304
bool Group::equals(const Group* other, CompareItemOptions options) const
305
{
306
    if (!other) {
307
        return false;
308
    }
309
    if (m_uuid != other->m_uuid) {
310
        return false;
311
    }
312
    if (!m_data.equals(other->m_data, options)) {
313
        return false;
314
    }
315
    if (m_customData != other->m_customData) {
316
        return false;
317
    }
318
    if (m_children.count() != other->m_children.count()) {
319
        return false;
320
    }
321
    if (m_entries.count() != other->m_entries.count()) {
322
        return false;
323
    }
324
    for (int i = 0; i < m_children.count(); ++i) {
325
        if (m_children[i]->uuid() != other->m_children[i]->uuid()) {
326
            return false;
327
        }
328
    }
329
    for (int i = 0; i < m_entries.count(); ++i) {
330
        if (m_entries[i]->uuid() != other->m_entries[i]->uuid()) {
331
            return false;
332
        }
333
    }
334
    return true;
335
}
336

337
void Group::setUuid(const QUuid& uuid)
338
{
339
    set(m_uuid, uuid);
340
}
341

342
void Group::setName(const QString& name)
343
{
344
    if (set(m_data.name, name)) {
345
        emit groupDataChanged(this);
346
    }
347
}
348

349
void Group::setNotes(const QString& notes)
350
{
351
    set(m_data.notes, notes);
352
}
353

354
void Group::setTags(const QString& tags)
355
{
356
    set(m_data.tags, tags);
357
}
358

359
void Group::setIcon(int iconNumber)
360
{
361
    if (iconNumber >= 0 && (m_data.iconNumber != iconNumber || !m_data.customIcon.isNull())) {
362
        m_data.iconNumber = iconNumber;
363
        m_data.customIcon = QUuid();
364
        emitModified();
365
        emit groupDataChanged(this);
366
    }
367
}
368

369
void Group::setIcon(const QUuid& uuid)
370
{
371
    if (!uuid.isNull() && m_data.customIcon != uuid) {
372
        m_data.customIcon = uuid;
373
        m_data.iconNumber = 0;
374
        emitModified();
375
        emit groupDataChanged(this);
376
    }
377
}
378

379
void Group::setTimeInfo(const TimeInfo& timeInfo)
380
{
381
    m_data.timeInfo = timeInfo;
382
}
383

384
void Group::setExpanded(bool expanded)
385
{
386
    if (m_data.isExpanded != expanded) {
387
        m_data.isExpanded = expanded;
388
        emit groupNonDataChange();
389
    }
390
}
391

392
void Group::setDefaultAutoTypeSequence(const QString& sequence)
393
{
394
    set(m_data.defaultAutoTypeSequence, sequence);
395
}
396

397
void Group::setAutoTypeEnabled(TriState enable)
398
{
399
    set(m_data.autoTypeEnabled, enable);
400
}
401

402
void Group::setSearchingEnabled(TriState enable)
403
{
404
    set(m_data.searchingEnabled, enable);
405
}
406

407
void Group::setLastTopVisibleEntry(Entry* entry)
408
{
409
    set(m_lastTopVisibleEntry, entry);
410
}
411

412
void Group::setExpires(bool value)
413
{
414
    if (m_data.timeInfo.expires() != value) {
415
        m_data.timeInfo.setExpires(value);
416
        emitModified();
417
    }
418
}
419

420
void Group::setExpiryTime(const QDateTime& dateTime)
421
{
422
    if (m_data.timeInfo.expiryTime() != dateTime) {
423
        m_data.timeInfo.setExpiryTime(dateTime);
424
        emitModified();
425
    }
426
}
427

428
void Group::setMergeMode(MergeMode newMode)
429
{
430
    set(m_data.mergeMode, newMode);
431
}
432

433
Group* Group::parentGroup()
434
{
435
    return m_parent;
436
}
437

438
const Group* Group::parentGroup() const
439
{
440
    return m_parent;
441
}
442

443
void Group::setParent(Group* parent, int index, bool trackPrevious)
444
{
445
    Q_ASSERT(parent);
446
    Q_ASSERT(index >= -1 && index <= parent->children().size());
447
    // setting a new parent for root groups is not allowed
448
    Q_ASSERT(!m_db || (m_db->rootGroup() != this));
449

450
    bool moveWithinDatabase = (m_db && m_db == parent->m_db);
451

452
    if (index == -1) {
453
        index = parent->children().size();
454

455
        if (parentGroup() == parent) {
456
            index--;
457
        }
458
    }
459

460
    if (m_parent == parent && parent->children().indexOf(this) == index) {
461
        return;
462
    }
463

464
    if (!moveWithinDatabase) {
465
        cleanupParent();
466
        m_parent = parent;
467
        if (m_db) {
468
            setPreviousParentGroup(nullptr);
469
            recCreateDelObjects();
470

471
            // copy custom icon to the new database
472
            if (!iconUuid().isNull() && parent->m_db && m_db->metadata()->hasCustomIcon(iconUuid())
473
                && !parent->m_db->metadata()->hasCustomIcon(iconUuid())) {
474
                parent->m_db->metadata()->addCustomIcon(iconUuid(), m_db->metadata()->customIcon(iconUuid()));
475
            }
476
        }
477
        if (m_db != parent->m_db) {
478
            connectDatabaseSignalsRecursive(parent->m_db);
479
        }
480
        QObject::setParent(parent);
481
        emit groupAboutToAdd(this, index);
482
        Q_ASSERT(index <= parent->m_children.size());
483
        parent->m_children.insert(index, this);
484
    } else {
485
        emit aboutToMove(this, parent, index);
486
        if (trackPrevious && m_parent != parent) {
487
            setPreviousParentGroup(m_parent);
488
        }
489
        m_parent->m_children.removeAll(this);
490
        m_parent = parent;
491
        QObject::setParent(parent);
492
        Q_ASSERT(index <= parent->m_children.size());
493
        parent->m_children.insert(index, this);
494
    }
495

496
    if (m_updateTimeinfo) {
497
        m_data.timeInfo.setLocationChanged(Clock::currentDateTimeUtc());
498
    }
499

500
    emitModified();
501

502
    if (!moveWithinDatabase) {
503
        emit groupAdded();
504
    } else {
505
        emit groupMoved();
506
    }
507
}
508

509
void Group::setParent(Database* db)
510
{
511
    Q_ASSERT(db);
512
    Q_ASSERT(db->rootGroup() == this);
513

514
    cleanupParent();
515

516
    m_parent = nullptr;
517
    connectDatabaseSignalsRecursive(db);
518

519
    QObject::setParent(db);
520
}
521

522
QStringList Group::hierarchy(int height) const
523
{
524
    QStringList hierarchy;
525
    const Group* group = this;
526
    const Group* parent = m_parent;
527

528
    if (height == 0) {
529
        return hierarchy;
530
    }
531

532
    hierarchy.prepend(group->name());
533

534
    int level = 1;
535
    bool heightReached = level == height;
536

537
    while (parent && !heightReached) {
538
        group = group->parentGroup();
539
        parent = group->parentGroup();
540
        heightReached = ++level == height;
541

542
        hierarchy.prepend(group->name());
543
    }
544

545
    return hierarchy;
546
}
547

548
bool Group::hasChildren() const
549
{
550
    return !children().isEmpty();
551
}
552

553
Database* Group::database()
554
{
555
    return m_db;
556
}
557

558
const Database* Group::database() const
559
{
560
    return m_db;
561
}
562

563
QList<Group*> Group::children()
564
{
565
    return m_children;
566
}
567

568
const QList<Group*>& Group::children() const
569
{
570
    return m_children;
571
}
572

573
QList<Entry*> Group::entries()
574
{
575
    return m_entries;
576
}
577

578
const QList<Entry*>& Group::entries() const
579
{
580
    return m_entries;
581
}
582

583
QList<Entry*> Group::entriesRecursive(bool includeHistoryItems) const
584
{
585
    QList<Entry*> entryList;
586

587
    entryList.append(m_entries);
588

589
    if (includeHistoryItems) {
590
        for (Entry* entry : m_entries) {
591
            entryList.append(entry->historyItems());
592
        }
593
    }
594

595
    for (Group* group : m_children) {
596
        entryList.append(group->entriesRecursive(includeHistoryItems));
597
    }
598

599
    return entryList;
600
}
601

602
QList<Entry*> Group::referencesRecursive(const Entry* entry) const
603
{
604
    auto entries = entriesRecursive();
605
    return QtConcurrent::blockingFiltered(entries,
606
                                          [entry](const Entry* e) { return e->hasReferencesTo(entry->uuid()); });
607
}
608

609
Entry* Group::findEntryByUuid(const QUuid& uuid, bool recursive) const
610
{
611
    if (uuid.isNull()) {
612
        return nullptr;
613
    }
614

615
    auto entries = m_entries;
616
    if (recursive) {
617
        entries = entriesRecursive(false);
618
    }
619

620
    for (auto entry : entries) {
621
        if (entry->uuid() == uuid) {
622
            return entry;
623
        }
624
    }
625

626
    return nullptr;
627
}
628

629
Entry* Group::findEntryByPath(const QString& entryPath) const
630
{
631
    if (entryPath.isEmpty()) {
632
        return nullptr;
633
    }
634

635
    // Add a beginning slash if the search string contains a slash
636
    // We don't add a slash by default to allow searching by entry title
637
    QString normalizedEntryPath = entryPath;
638
    if (!normalizedEntryPath.startsWith("/") && normalizedEntryPath.contains("/")) {
639
        normalizedEntryPath = "/" + normalizedEntryPath;
640
    }
641
    return findEntryByPathRecursive(normalizedEntryPath, "/");
642
}
643

644
Entry* Group::findEntryBySearchTerm(const QString& term, EntryReferenceType referenceType)
645
{
646
    Q_ASSERT_X(referenceType != EntryReferenceType::Unknown,
647
               "Database::findEntryRecursive",
648
               "Can't search entry with \"referenceType\" parameter equal to \"Unknown\"");
649

650
    const QList<Group*> groups = groupsRecursive(true);
651

652
    for (const Group* group : groups) {
653
        bool found = false;
654
        const QList<Entry*>& entryList = group->entries();
655
        for (Entry* entry : entryList) {
656
            switch (referenceType) {
657
            case EntryReferenceType::Unknown:
658
                return nullptr;
659
            case EntryReferenceType::Title:
660
                found = entry->title() == term;
661
                break;
662
            case EntryReferenceType::UserName:
663
                found = entry->username() == term;
664
                break;
665
            case EntryReferenceType::Password:
666
                found = entry->password() == term;
667
                break;
668
            case EntryReferenceType::Url:
669
                found = entry->url() == term;
670
                break;
671
            case EntryReferenceType::Notes:
672
                found = entry->notes() == term;
673
                break;
674
            case EntryReferenceType::QUuid:
675
                found = entry->uuid() == QUuid::fromRfc4122(QByteArray::fromHex(term.toLatin1()));
676
                break;
677
            case EntryReferenceType::CustomAttributes:
678
                found = entry->attributes()->containsValue(term);
679
                break;
680
            }
681

682
            if (found) {
683
                return entry;
684
            }
685
        }
686
    }
687

688
    return nullptr;
689
}
690

691
Entry* Group::findEntryByPathRecursive(const QString& entryPath, const QString& basePath) const
692
{
693
    // Return the first entry that matches the full path OR if there is no leading
694
    // slash, return the first entry title that matches
695
    for (Entry* entry : entries()) {
696
        // clang-format off
697
        if (entryPath == (basePath + entry->title())
698
            || (!entryPath.startsWith("/") && entry->title() == entryPath)) {
699
            return entry;
700
        }
701
        // clang-format on
702
    }
703

704
    for (Group* group : children()) {
705
        Entry* entry = group->findEntryByPathRecursive(entryPath, basePath + group->name() + "/");
706
        if (entry != nullptr) {
707
            return entry;
708
        }
709
    }
710

711
    return nullptr;
712
}
713

714
Group* Group::findGroupByPath(const QString& groupPath)
715
{
716
    // normalize the groupPath by adding missing front and rear slashes. once.
717
    QString normalizedGroupPath;
718

719
    if (groupPath.isEmpty()) {
720
        normalizedGroupPath = QString("/"); // root group
721
    } else {
722
        // clang-format off
723
        normalizedGroupPath = (groupPath.startsWith("/") ? "" : "/")
724
            + groupPath
725
            + (groupPath.endsWith("/") ? "" : "/");
726
        // clang-format on
727
    }
728
    return findGroupByPathRecursive(normalizedGroupPath, "/");
729
}
730

731
Group* Group::findGroupByPathRecursive(const QString& groupPath, const QString& basePath)
732
{
733
    // paths must be normalized
734
    Q_ASSERT(groupPath.startsWith("/") && groupPath.endsWith("/"));
735
    Q_ASSERT(basePath.startsWith("/") && basePath.endsWith("/"));
736

737
    if (groupPath == basePath) {
738
        return this;
739
    }
740

741
    for (Group* innerGroup : children()) {
742
        QString innerBasePath = basePath + innerGroup->name() + "/";
743
        Group* group = innerGroup->findGroupByPathRecursive(groupPath, innerBasePath);
744
        if (group != nullptr) {
745
            return group;
746
        }
747
    }
748

749
    return nullptr;
750
}
751

752
QString Group::print(bool recursive, bool flatten, int depth)
753
{
754
    QString response;
755
    QString prefix;
756

757
    if (flatten) {
758
        const QString separator("/");
759
        prefix = hierarchy(depth).join(separator);
760
        if (!prefix.isEmpty()) {
761
            prefix += separator;
762
        }
763
    } else {
764
        prefix = QString("  ").repeated(depth);
765
    }
766

767
    if (entries().isEmpty() && children().isEmpty()) {
768
        response += prefix + tr("[empty]", "group has no children") + "\n";
769
        return response;
770
    }
771

772
    for (Entry* entry : entries()) {
773
        response += prefix + entry->title() + "\n";
774
    }
775

776
    for (Group* innerGroup : children()) {
777
        response += prefix + innerGroup->name() + "/\n";
778
        if (recursive) {
779
            response += innerGroup->print(recursive, flatten, depth + 1);
780
        }
781
    }
782

783
    return response;
784
}
785

786
QList<const Group*> Group::groupsRecursive(bool includeSelf) const
787
{
788
    QList<const Group*> groupList;
789
    if (includeSelf) {
790
        groupList.append(this);
791
    }
792

793
    for (const Group* group : asConst(m_children)) {
794
        groupList.append(group->groupsRecursive(true));
795
    }
796

797
    return groupList;
798
}
799

800
QList<Group*> Group::groupsRecursive(bool includeSelf)
801
{
802
    QList<Group*> groupList;
803
    if (includeSelf) {
804
        groupList.append(this);
805
    }
806

807
    for (Group* group : asConst(m_children)) {
808
        groupList.append(group->groupsRecursive(true));
809
    }
810

811
    return groupList;
812
}
813

814
QSet<QUuid> Group::customIconsRecursive() const
815
{
816
    QSet<QUuid> result;
817

818
    if (!iconUuid().isNull()) {
819
        result.insert(iconUuid());
820
    }
821

822
    const QList<Entry*> entryList = entriesRecursive(true);
823
    for (Entry* entry : entryList) {
824
        if (!entry->iconUuid().isNull()) {
825
            result.insert(entry->iconUuid());
826
        }
827
    }
828

829
    for (Group* group : m_children) {
830
        result.unite(group->customIconsRecursive());
831
    }
832

833
    return result;
834
}
835

836
QList<QString> Group::usernamesRecursive(int topN) const
837
{
838
    // Collect all usernames and sort for easy counting
839
    QHash<QString, int> countedUsernames;
840
    for (const auto* entry : entriesRecursive()) {
841
        const auto username = entry->username();
842
        if (!username.isEmpty() && !entry->isAttributeReference(EntryAttributes::UserNameKey)) {
843
            countedUsernames.insert(username, ++countedUsernames[username]);
844
        }
845
    }
846

847
    // Sort username/frequency pairs by frequency and name
848
    QList<QPair<QString, int>> sortedUsernames;
849
    for (const auto& key : countedUsernames.keys()) {
850
        sortedUsernames.append({key, countedUsernames[key]});
851
    }
852

853
    auto comparator = [](const QPair<QString, int>& arg1, const QPair<QString, int>& arg2) {
854
        if (arg1.second == arg2.second) {
855
            return arg1.first < arg2.first;
856
        }
857
        return arg1.second > arg2.second;
858
    };
859

860
    std::sort(sortedUsernames.begin(), sortedUsernames.end(), comparator);
861

862
    // Take first topN usernames if set
863
    QList<QString> usernames;
864
    int actualUsernames = topN < 0 ? sortedUsernames.size() : std::min(topN, sortedUsernames.size());
865
    for (int i = 0; i < actualUsernames; i++) {
866
        usernames.append(sortedUsernames[i].first);
867
    }
868

869
    return usernames;
870
}
871

872
Group* Group::findGroupByUuid(const QUuid& uuid)
873
{
874
    if (uuid.isNull()) {
875
        return nullptr;
876
    }
877

878
    for (Group* group : groupsRecursive(true)) {
879
        if (group->uuid() == uuid) {
880
            return group;
881
        }
882
    }
883

884
    return nullptr;
885
}
886

887
const Group* Group::findGroupByUuid(const QUuid& uuid) const
888
{
889
    if (uuid.isNull()) {
890
        return nullptr;
891
    }
892

893
    for (const Group* group : groupsRecursive(true)) {
894
        if (group->uuid() == uuid) {
895
            return group;
896
        }
897
    }
898

899
    return nullptr;
900
}
901

902
Group* Group::findChildByName(const QString& name)
903
{
904
    for (Group* group : asConst(m_children)) {
905
        if (group->name() == name) {
906
            return group;
907
        }
908
    }
909

910
    return nullptr;
911
}
912

913
/**
914
 * Creates a duplicate of this group.
915
 * Note that you need to copy the custom icons manually when inserting the
916
 * new group into another database.
917
 */
918
Group* Group::clone(Entry::CloneFlags entryFlags, Group::CloneFlags groupFlags) const
919
{
920
    auto clonedGroup = new Group();
921

922
    clonedGroup->setUpdateTimeinfo(false);
923

924
    if (groupFlags & Group::CloneNewUuid) {
925
        clonedGroup->setUuid(QUuid::createUuid());
926
    } else {
927
        clonedGroup->setUuid(this->uuid());
928
    }
929

930
    clonedGroup->m_data = m_data;
931
    clonedGroup->m_customData->copyDataFrom(m_customData);
932

933
    if (groupFlags & Group::CloneIncludeEntries) {
934
        const QList<Entry*> entryList = entries();
935
        for (Entry* entry : entryList) {
936
            Entry* clonedEntry = entry->clone(entryFlags);
937
            clonedEntry->setGroup(clonedGroup);
938
        }
939

940
        const QList<Group*> childrenGroups = children();
941
        for (Group* groupChild : childrenGroups) {
942
            Group* clonedGroupChild = groupChild->clone(entryFlags, groupFlags);
943
            clonedGroupChild->setParent(clonedGroup);
944
        }
945
    }
946

947
    clonedGroup->setUpdateTimeinfo(true);
948
    if (groupFlags & Group::CloneResetTimeInfo) {
949

950
        QDateTime now = Clock::currentDateTimeUtc();
951
        clonedGroup->m_data.timeInfo.setCreationTime(now);
952
        clonedGroup->m_data.timeInfo.setLastModificationTime(now);
953
        clonedGroup->m_data.timeInfo.setLastAccessTime(now);
954
        clonedGroup->m_data.timeInfo.setLocationChanged(now);
955
    }
956

957
    if (groupFlags & Group::CloneRenameTitle) {
958
        clonedGroup->setName(tr("%1 - Clone").arg(name()));
959
    }
960

961
    return clonedGroup;
962
}
963

964
void Group::copyDataFrom(const Group* other)
965
{
966
    if (set(m_data, other->m_data)) {
967
        emit groupDataChanged(this);
968
    }
969
    m_customData->copyDataFrom(other->m_customData);
970
    m_lastTopVisibleEntry = other->m_lastTopVisibleEntry;
971
}
972

973
void Group::addEntry(Entry* entry)
974
{
975
    Q_ASSERT(entry);
976
    Q_ASSERT(!m_entries.contains(entry));
977

978
    emit entryAboutToAdd(entry);
979

980
    m_entries << entry;
981
    connect(entry, &Entry::entryDataChanged, this, &Group::entryDataChanged);
982
    if (m_db) {
983
        connect(entry, &Entry::modified, m_db, &Database::markAsModified);
984
    }
985

986
    emitModified();
987
    emit entryAdded(entry);
988
}
989

990
void Group::removeEntry(Entry* entry)
991
{
992
    Q_ASSERT_X(m_entries.contains(entry),
993
               Q_FUNC_INFO,
994
               QString("Group %1 does not contain %2").arg(this->name(), entry->title()).toLatin1());
995

996
    emit entryAboutToRemove(entry);
997

998
    entry->disconnect(this);
999
    if (m_db) {
1000
        entry->disconnect(m_db);
1001
    }
1002
    m_entries.removeAll(entry);
1003
    emitModified();
1004
    emit entryRemoved(entry);
1005
}
1006

1007
void Group::moveEntryUp(Entry* entry)
1008
{
1009
    int row = m_entries.indexOf(entry);
1010
    if (row <= 0) {
1011
        return;
1012
    }
1013

1014
    emit entryAboutToMoveUp(row);
1015
    m_entries.move(row, row - 1);
1016
    emit entryMovedUp();
1017
    emit groupNonDataChange();
1018
}
1019

1020
void Group::moveEntryDown(Entry* entry)
1021
{
1022
    int row = m_entries.indexOf(entry);
1023
    if (row >= m_entries.size() - 1) {
1024
        return;
1025
    }
1026

1027
    emit entryAboutToMoveDown(row);
1028
    m_entries.move(row, row + 1);
1029
    emit entryMovedDown();
1030
    emit groupNonDataChange();
1031
}
1032

1033
void Group::connectDatabaseSignalsRecursive(Database* db)
1034
{
1035
    if (m_db) {
1036
        disconnect(m_db);
1037
    }
1038

1039
    for (Entry* entry : asConst(m_entries)) {
1040
        if (m_db) {
1041
            entry->disconnect(m_db);
1042
        }
1043
        if (db) {
1044
            connect(entry, &Entry::modified, db, &Database::markAsModified);
1045
        }
1046
    }
1047

1048
    if (db) {
1049
        // clang-format off
1050
        connect(this, &Group::groupDataChanged, db, &Database::groupDataChanged);
1051
        connect(this, &Group::groupAboutToRemove, db, &Database::groupAboutToRemove);
1052
        connect(this, &Group::groupRemoved, db, &Database::groupRemoved);
1053
        connect(this, &Group::groupAboutToAdd, db, &Database::groupAboutToAdd);
1054
        connect(this, &Group::groupAdded, db, &Database::groupAdded);
1055
        connect(this, &Group::aboutToMove, db, &Database::groupAboutToMove);
1056
        connect(this, &Group::groupMoved, db, &Database::groupMoved);
1057
        connect(this, &Group::groupNonDataChange, db, &Database::markNonDataChange);
1058
        connect(this, &Group::modified, db, &Database::markAsModified);
1059
        // clang-format on
1060
    }
1061

1062
    m_db = db;
1063

1064
    for (Group* group : asConst(m_children)) {
1065
        group->connectDatabaseSignalsRecursive(db);
1066
    }
1067
}
1068

1069
void Group::cleanupParent()
1070
{
1071
    if (m_parent) {
1072
        emit groupAboutToRemove(this);
1073
        m_parent->m_children.removeAll(this);
1074
        emitModified();
1075
        emit groupRemoved();
1076
    }
1077
}
1078

1079
void Group::recCreateDelObjects()
1080
{
1081
    if (m_db) {
1082
        for (Entry* entry : asConst(m_entries)) {
1083
            m_db->addDeletedObject(entry->uuid());
1084
        }
1085

1086
        for (Group* group : asConst(m_children)) {
1087
            group->recCreateDelObjects();
1088
        }
1089
        m_db->addDeletedObject(m_uuid);
1090
    }
1091
}
1092

1093
bool Group::resolveSearchingEnabled() const
1094
{
1095
    switch (m_data.searchingEnabled) {
1096
    case Inherit:
1097
        if (!m_parent) {
1098
            return true;
1099
        } else {
1100
            return m_parent->resolveSearchingEnabled();
1101
        }
1102
    case Enable:
1103
        return true;
1104
    case Disable:
1105
        return false;
1106
    default:
1107
        Q_ASSERT(false);
1108
        return false;
1109
    }
1110
}
1111

1112
bool Group::resolveAutoTypeEnabled() const
1113
{
1114
    switch (m_data.autoTypeEnabled) {
1115
    case Inherit:
1116
        if (!m_parent) {
1117
            return true;
1118
        } else {
1119
            return m_parent->resolveAutoTypeEnabled();
1120
        }
1121
    case Enable:
1122
        return true;
1123
    case Disable:
1124
        return false;
1125
    default:
1126
        Q_ASSERT(false);
1127
        return false;
1128
    }
1129
}
1130

1131
Entry* Group::addEntryWithPath(const QString& entryPath)
1132
{
1133
    if (entryPath.isEmpty() || findEntryByPath(entryPath)) {
1134
        return nullptr;
1135
    }
1136

1137
    QStringList groups = entryPath.split("/");
1138
    QString entryTitle = groups.takeLast();
1139
    QString groupPath = groups.join("/");
1140

1141
    Group* group = findGroupByPath(groupPath);
1142
    if (!group) {
1143
        return nullptr;
1144
    }
1145

1146
    auto* entry = new Entry();
1147
    entry->setTitle(entryTitle);
1148
    entry->setUuid(QUuid::createUuid());
1149
    entry->setGroup(group);
1150

1151
    return entry;
1152
}
1153

1154
void Group::applyGroupIconOnCreateTo(Entry* entry)
1155
{
1156
    Q_ASSERT(entry);
1157

1158
    if (!config()->get(Config::UseGroupIconOnEntryCreation).toBool()) {
1159
        return;
1160
    }
1161

1162
    if ((iconNumber() == Group::DefaultIconNumber || iconNumber() == Group::OpenFolderIconNumber)
1163
        && iconUuid().isNull()) {
1164
        return;
1165
    }
1166

1167
    applyGroupIconTo(entry);
1168
}
1169

1170
void Group::applyGroupIconTo(Entry* entry)
1171
{
1172
    Q_ASSERT(entry);
1173

1174
    if (iconUuid().isNull()) {
1175
        entry->setIcon(iconNumber());
1176
    } else {
1177
        entry->setIcon(iconUuid());
1178
    }
1179
}
1180

1181
void Group::applyGroupIconTo(Group* other)
1182
{
1183
    Q_ASSERT(other);
1184

1185
    if (iconUuid().isNull()) {
1186
        other->setIcon(iconNumber());
1187
    } else {
1188
        other->setIcon(iconUuid());
1189
    }
1190
}
1191

1192
void Group::applyGroupIconToChildGroups()
1193
{
1194
    for (Group* recursiveChild : groupsRecursive(false)) {
1195
        applyGroupIconTo(recursiveChild);
1196
    }
1197
}
1198

1199
void Group::applyGroupIconToChildEntries()
1200
{
1201
    for (Entry* recursiveEntry : entriesRecursive(false)) {
1202
        applyGroupIconTo(recursiveEntry);
1203
    }
1204
}
1205

1206
void Group::sortChildrenRecursively(bool reverse)
1207
{
1208
    Group* recycleBin = nullptr;
1209
    if (database()) {
1210
        recycleBin = database()->metadata()->recycleBin();
1211
    }
1212
    std::sort(m_children.begin(), m_children.end(), [=](const Group* childGroup1, const Group* childGroup2) -> bool {
1213
        if (childGroup1 == recycleBin) {
1214
            return false;
1215
        }
1216
        QString name1 = childGroup1->name();
1217
        QString name2 = childGroup2->name();
1218
        return reverse ? name1.compare(name2, Qt::CaseInsensitive) > 0 : name1.compare(name2, Qt::CaseInsensitive) < 0;
1219
    });
1220

1221
    for (auto child : m_children) {
1222
        child->sortChildrenRecursively(reverse);
1223
    }
1224

1225
    emitModified();
1226
}
1227

1228
const Group* Group::previousParentGroup() const
1229
{
1230
    if (!database() || !database()->rootGroup()) {
1231
        return nullptr;
1232
    }
1233
    return database()->rootGroup()->findGroupByUuid(m_data.previousParentGroupUuid);
1234
}
1235

1236
QUuid Group::previousParentGroupUuid() const
1237
{
1238
    return m_data.previousParentGroupUuid;
1239
}
1240

1241
void Group::setPreviousParentGroupUuid(const QUuid& uuid)
1242
{
1243
    set(m_data.previousParentGroupUuid, uuid);
1244
}
1245

1246
void Group::setPreviousParentGroup(const Group* group)
1247
{
1248
    setPreviousParentGroupUuid(group ? group->uuid() : QUuid());
1249
}
1250

1251
bool Group::GroupData::operator==(const Group::GroupData& other) const
1252
{
1253
    return equals(other, CompareItemDefault);
1254
}
1255

1256
bool Group::GroupData::operator!=(const Group::GroupData& other) const
1257
{
1258
    return !(*this == other);
1259
}
1260

1261
bool Group::GroupData::equals(const Group::GroupData& other, CompareItemOptions options) const
1262
{
1263
    if (::compare(name, other.name, options) != 0) {
1264
        return false;
1265
    }
1266
    if (::compare(notes, other.notes, options) != 0) {
1267
        return false;
1268
    }
1269
    if (::compare(tags, other.tags, options) != 0) {
1270
        return false;
1271
    }
1272
    if (::compare(iconNumber, other.iconNumber) != 0) {
1273
        return false;
1274
    }
1275
    if (::compare(customIcon, other.customIcon) != 0) {
1276
        return false;
1277
    }
1278
    if (!timeInfo.equals(other.timeInfo, options)) {
1279
        return false;
1280
    }
1281
    // TODO HNH: Some properties are configurable - should they be ignored?
1282
    if (::compare(isExpanded, other.isExpanded, options) != 0) {
1283
        return false;
1284
    }
1285
    if (::compare(defaultAutoTypeSequence, other.defaultAutoTypeSequence, options) != 0) {
1286
        return false;
1287
    }
1288
    if (::compare(autoTypeEnabled, other.autoTypeEnabled, options) != 0) {
1289
        return false;
1290
    }
1291
    if (::compare(searchingEnabled, other.searchingEnabled, options) != 0) {
1292
        return false;
1293
    }
1294
    if (::compare(mergeMode, other.mergeMode, options) != 0) {
1295
        return false;
1296
    }
1297
    return true;
1298
}
1299

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.