diff --git a/bin/kdbx.dart b/bin/kdbx.dart index e5037f8..7f7be5e 100644 --- a/bin/kdbx.dart +++ b/bin/kdbx.dart @@ -8,6 +8,7 @@ import 'package:kdbx/kdbx.dart'; import 'package:kdbx/src/crypto/protected_value.dart'; import 'package:kdbx/src/kdbx_format.dart'; import 'package:kdbx/src/kdbx_group.dart'; +import 'package:kdbx/src/utils/print_utils.dart'; import 'package:logging/logging.dart'; import 'package:logging_appenders/logging_appenders.dart'; import 'package:prompts/prompts.dart' as prompts; @@ -120,32 +121,10 @@ class CatCommand extends KdbxFileCommand { @override Future runWithFile(KdbxFile file) async { - catGroup(file.body.rootGroup); - } - - void catGroup(KdbxGroup group, {int depth = 0}) { - final indent = ' ' * depth; - print('$indent + ${group.name} (${group.uuid})'); - for (final group in group.groups) { - catGroup(group, depth: depth + 1); - } - final valueToSting = (StringValue value) => - forceDecrypt ? value?.getText() : value?.toString(); - - for (final entry in group.entries) { - final value = entry.getString(KdbxKey('Password')); - print('$indent `- ${entry.getString(KdbxKey('Title'))?.getText()}: ' - '${valueToSting(value)}'); - if (allFields) { - print(entry.stringEntries - .map((field) => - '$indent ${field.key} = ${valueToSting(field.value)}') - .join('\n')); - } - print(entry.binaryEntries - .map((b) => '$indent `- file: ${b.key} - ${b.value.value.length}') - .join('\n')); - } + final buf = StringBuffer(); + KdbxPrintUtils(forceDecrypt: forceDecrypt, allFields: allFields) + .catGroup(buf, file.body.rootGroup); + print(buf.toString()); } } diff --git a/lib/src/kdbx_entry.dart b/lib/src/kdbx_entry.dart index bacd892..35d347a 100644 --- a/lib/src/kdbx_entry.dart +++ b/lib/src/kdbx_entry.dart @@ -386,9 +386,11 @@ class KdbxEntry extends KdbxObject { mergeContext.markAsMerged(this); } + String debugLabel() => label ?? _plainValue(KdbxKey('UserName')); + @override String toString() { return 'KdbxEntry{uuid=$uuid,' - 'name=${label ?? _plainValue(KdbxKey('UserName'))}}'; + 'name=${debugLabel()}}'; } } diff --git a/lib/src/kdbx_group.dart b/lib/src/kdbx_group.dart index 4c1d8a7..3531cbb 100644 --- a/lib/src/kdbx_group.dart +++ b/lib/src/kdbx_group.dart @@ -1,4 +1,5 @@ import 'package:kdbx/kdbx.dart'; +import 'package:kdbx/src/internal/extension_utils.dart'; import 'package:kdbx/src/kdbx_consts.dart'; import 'package:kdbx/src/kdbx_entry.dart'; import 'package:kdbx/src/kdbx_format.dart'; @@ -116,6 +117,7 @@ class KdbxGroup extends KdbxObject { importToHere: (other) => KdbxGroup.create(ctx: ctx, parent: this, name: other.name.get()) ..forceSetUuid(other.uuid) + ..let((x) => addGroup(x)) .._overwriteFrom(mergeContext, other), ); _mergeSubObjects( @@ -147,12 +149,15 @@ class KdbxGroup extends KdbxObject { final movedObj = mergeContext.objectIndex[otherObj.uuid]; if (movedObj == null) { // item was created in the other file. we have to import it - importToHere(otherObj); + final newMeObject = importToHere(otherObj); + mergeContext.trackChange(newMeObject, debug: '(was created)'); + newMeObject.merge(mergeContext, otherObj); } else { // item was moved. if (otherObj.wasMovedAfter(movedObj)) { // item was moved in the other file, so we have to move it here. file.move(movedObj, this); + mergeContext.trackChange(movedObj, debug: 'moved to another group'); } else { // item was moved in this file, so nothing to do. } diff --git a/lib/src/utils/print_utils.dart b/lib/src/utils/print_utils.dart new file mode 100644 index 0000000..b93c976 --- /dev/null +++ b/lib/src/utils/print_utils.dart @@ -0,0 +1,41 @@ +import 'package:kdbx/src/crypto/protected_value.dart'; +import 'package:kdbx/src/internal/extension_utils.dart'; +import 'package:kdbx/src/kdbx_entry.dart'; +import 'package:kdbx/src/kdbx_group.dart'; + +class KdbxPrintUtils { + KdbxPrintUtils({ + this.forceDecrypt = false, + this.allFields = false, + }); + final bool forceDecrypt; + final bool allFields; + + String catGroupToString(KdbxGroup group) => + (StringBuffer()..let((that) => catGroup(that, group))).toString(); + + void catGroup(StringBuffer buf, KdbxGroup group, {int depth = 0}) { + final indent = ' ' * depth; + buf.writeln('$indent + ${group.name.get()} (${group.uuid})'); + for (final group in group.groups) { + catGroup(buf, group, depth: depth + 1); + } + final valueToSting = (StringValue value) => + forceDecrypt ? value?.getText() : value?.toString(); + + for (final entry in group.entries) { + final value = entry.getString(KdbxKey('Password')); + buf.writeln('$indent `- ${entry.debugLabel()}: ' + '${valueToSting(value)}'); + if (allFields) { + buf.writeln(entry.stringEntries + .map((field) => + '$indent ${field.key} = ${valueToSting(field.value)}') + .join('\n')); + } + buf.writeln(entry.binaryEntries + .map((b) => '$indent `- file: ${b.key} - ${b.value.value.length}') + .join('\n')); + } + } +} diff --git a/test/merge/kdbx_merge_test.dart b/test/merge/kdbx_merge_test.dart index 7548cb0..395522a 100644 --- a/test/merge/kdbx_merge_test.dart +++ b/test/merge/kdbx_merge_test.dart @@ -1,5 +1,6 @@ import 'package:clock/clock.dart'; import 'package:kdbx/kdbx.dart'; +import 'package:kdbx/src/utils/print_utils.dart'; import 'package:logging/logging.dart'; import 'package:test/test.dart'; @@ -75,6 +76,26 @@ void main() { expect(merge.changes, hasLength(1)); }), ); + test( + 'Move Entry to recycle bin', + () async => await withClock(fakeClock, () async { + final file = await createSimpleFile(); + + final fileMod = await TestUtil.saveAndRead(file); + + expect(fileMod.recycleBin, isNull); + fileMod.deleteEntry(fileMod.body.rootGroup.entries.first); + expect(fileMod.recycleBin, isNotNull); + final file2 = await TestUtil.saveAndRead(fileMod); + final merge = file.merge(file2); + _logger.info('Merged file:\n' + '${KdbxPrintUtils().catGroupToString(file.body.rootGroup)}'); + final set = Set.from(merge.merged.keys); + expect(set, hasLength(5)); + expect(Set.from(merge.changes.map((e) => e.object)), + hasLength(2)); + }), + ); }); }