Browse Source

very trivial merge test.

pull/3/head
Herbert Poul 4 years ago
parent
commit
76ba4aedbf
  1. 22
      lib/src/kdbx_entry.dart
  2. 4
      lib/src/kdbx_file.dart
  3. 12
      lib/src/kdbx_format.dart
  4. 2
      lib/src/kdbx_group.dart
  5. 70
      test/merge/kdbx_merge_test.dart

22
lib/src/kdbx_entry.dart

@ -77,6 +77,11 @@ extension KdbxEntryInternal on KdbxEntry {
other._overwriteNodes,
);
// overwrite all strings
final stringsDiff = _diffMap(_strings, other._strings);
if (stringsDiff.isNotEmpty) {
overwriteContext.trackChange(this,
node: 'strings', debug: 'changed: ${stringsDiff.join(',')}');
}
_strings.clear();
_strings.addAll(other._strings);
// overwrite all binaries
@ -94,6 +99,17 @@ extension KdbxEntryInternal on KdbxEntry {
}
}
}
List<String> _diffMap(Map<Object, Object> a, Map<Object, Object> b) {
final keys = {...a.keys, ...b.keys};
final ret = <String>[];
for (final key in keys) {
if (a[key] != b[key]) {
ret.add(key.toString());
}
}
return ret;
}
}
class KdbxEntry extends KdbxObject {
@ -337,9 +353,11 @@ class KdbxEntry extends KdbxObject {
void merge(MergeContext mergeContext, KdbxEntry other) {
assertSameUuid(other, 'merge');
if (other.wasModifiedAfter(this)) {
_logger.finest('$this has incoming changes.');
// other object is newer, create new history entry and copy fields.
modify(() => _overwriteFrom(mergeContext, other));
} else if (wasModifiedAfter(other)) {
_logger.finest('$this has outgoing changes.');
// we are newer. check if the old revision lives on in our history.
final ourLastModificationTime = times.lastModificationTime.get();
final historyEntry = _findHistoryEntry(history, ourLastModificationTime);
@ -348,6 +366,8 @@ class KdbxEntry extends KdbxObject {
// it to history.
history.add(other.cloneInto(parent, toHistoryEntry: true));
}
} else {
_logger.finest('$this has no changes.');
}
// copy missing history entries.
for (final otherHistoryEntry in other.history) {
@ -367,6 +387,6 @@ class KdbxEntry extends KdbxObject {
@override
String toString() {
return 'KdbxGroup{uuid=$uuid,name=$label}';
return 'KdbxEntry{uuid=$uuid,name=$label}';
}
}

4
lib/src/kdbx_file.dart

@ -123,12 +123,12 @@ class KdbxFile {
/// Merges the given file into this file.
/// Both files must have the same origin (ie. same root group UUID).
/// FIXME: THiS iS NOT YET FINISHED, DO NOT USE.
void merge(KdbxFile other) {
MergeContext merge(KdbxFile other) {
if (other.body.rootGroup.uuid != body.rootGroup.uuid) {
throw KdbxUnsupportedException(
'Root groups of source and dest file do not match.');
}
body.merge(other.body);
return body.merge(other.body);
}
}

12
lib/src/kdbx_format.dart

@ -302,7 +302,7 @@ class KdbxBody extends KdbxNode {
concat([rootGroup.getAllGroups(), rootGroup.getAllEntries()])
.map((e) => MapEntry(e.uuid, e)));
void merge(KdbxBody other) {
MergeContext merge(KdbxBody other) {
// sync deleted objects.
final deleted =
Map.fromEntries(ctx._deletedObjects.map((e) => MapEntry(e.uuid, e)));
@ -341,7 +341,7 @@ class KdbxBody extends KdbxNode {
// FIXME do some cleanup.
_logger.info('Finished merging. ${mergeContext.debugChanges()}');
_logger.info('Finished merging:\n${mergeContext.debugChanges()}');
final incomingObjects = other._createObjectIndex();
_logger.info('Merged: ${mergeContext.merged} vs. '
'(local objects: ${mergeContext.objectIndex.length}, '
@ -351,6 +351,7 @@ class KdbxBody extends KdbxNode {
if (mergeContext.merged.keys.length != mergeContext.objectIndex.length) {
// TODO figure out what went wrong.
}
return mergeContext;
}
xml.XmlDocument generateXml(ProtectedSaltGenerator saltGenerator) {
@ -419,6 +420,10 @@ class MergeChange {
/// the name of the subnode of [object].
final String node;
final String debug;
String debugString() {
return [node, debug].where((e) => e != null).join(' ');
}
}
class MergeContext implements OverwriteContext {
@ -451,8 +456,7 @@ class MergeContext implements OverwriteContext {
return group.entries
.map((e) => [
e.key.toString(),
': ',
...e.value.map((e) => e.toString()),
...e.value.map((e) => e.debugString()),
].join('\n '))
.join('\n');
}

2
lib/src/kdbx_group.dart

@ -124,6 +124,7 @@ class KdbxGroup extends KdbxObject {
other._entries,
importToHere: (other) => other.cloneInto(this),
);
mergeContext.markAsMerged(this);
}
void _mergeSubObjects<T extends KdbxObject>(
@ -161,7 +162,6 @@ class KdbxGroup extends KdbxObject {
meObj.merge(mergeContext, otherObj);
}
}
mergeContext.markAsMerged(this);
}
List<KdbxSubNode> get _overwriteNodes => [

70
test/merge/kdbx_merge_test.dart

@ -0,0 +1,70 @@
import 'package:clock/clock.dart';
import 'package:kdbx/kdbx.dart';
import 'package:test/test.dart';
import '../internal/test_utils.dart';
import 'package:logging/logging.dart';
final _logger = Logger('kdbx_merge_test');
void main() {
TestUtil.setupLogging();
DateTime now = DateTime.fromMillisecondsSinceEpoch(0);
final fakeClock = Clock(() => now);
final kdbxFormat = TestUtil.kdbxFormat();
void proceedSeconds(int seconds) {
now = now.add(Duration(seconds: seconds));
}
setUp(() {
DateTime.fromMillisecondsSinceEpoch(0);
});
group('Simple merges', () {
test('Noop merge', () async {
final file = kdbxFormat.create(
Credentials.composite(ProtectedValue.fromString('asdf'), null),
'example');
_createEntry(file, file.body.rootGroup, 'test1', 'test1');
final file2 = await TestUtil.saveAndRead(file);
final merge = file.merge(file2);
final set = Set<KdbxUuid>.from(merge.merged.keys);
expect(set, hasLength(2));
expect(merge.changes, isEmpty);
});
test('Username change', () async {
await withClock(fakeClock, () async {
final file = kdbxFormat.create(
Credentials.composite(ProtectedValue.fromString('asdf'), null),
'example');
_createEntry(file, file.body.rootGroup, 'test1', 'test1');
final fileMod = await TestUtil.saveAndRead(file);
proceedSeconds(10);
fileMod.body.rootGroup.entries.first
.setString(KdbxKey('UserName'), PlainValue('changed.'));
_logger.info('mod date: ' +
fileMod.body.rootGroup.entries.first.times.lastModificationTime
.get()
.toString());
final file2 = await TestUtil.saveAndRead(fileMod);
_logger.info('\n\n\nstarting merge.\n');
final merge = file.merge(file2);
final set = Set<KdbxUuid>.from(merge.merged.keys);
expect(set, hasLength(2));
expect(merge.changes, hasLength(1));
});
});
});
}
KdbxEntry _createEntry(
KdbxFile file, KdbxGroup group, String username, String password) {
final entry = KdbxEntry.create(file, group);
group.addEntry(entry);
entry.setString(KdbxKey('UserName'), PlainValue(username));
entry.setString(KdbxKey('Password'), ProtectedValue.fromString(password));
return entry;
}
Loading…
Cancel
Save