Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
luckyrat committed Feb 12, 2024
1 parent 49edcce commit ed8a979
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 6 deletions.
54 changes: 48 additions & 6 deletions lib/src/kdbx_meta.dart
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,12 @@ class KdbxMeta extends KdbxNode implements KdbxNodeContext {
(customIcon) => XmlUtils.createNode(KdbxXml.NODE_ICON, [
XmlUtils.createTextNode(KdbxXml.NODE_UUID, customIcon.uuid.uuid),
XmlUtils.createTextNode(
KdbxXml.NODE_DATA, base64.encode(customIcon.data))
KdbxXml.NODE_DATA, base64.encode(customIcon.data)),
if (ctx.version > KdbxVersion.V4 && customIcon.name != null)
XmlUtils.createTextNode(KdbxXml.NODE_NAME, customIcon.name!),
if (ctx.version > KdbxVersion.V4 && customIcon.lastModified != null)
XmlUtils.createTextNode(KdbxXml.NODE_LAST_MODIFICATION_TIME,
DateTimeUtils.toBase64(customIcon.lastModified!)),
]),
)),
);
Expand Down Expand Up @@ -269,12 +274,14 @@ class KdbxMeta extends KdbxNode implements KdbxNodeContext {
mergeKdbxMetaCustomDataWithDates(
customData, other.customData, ctx, otherIsNewer);

mergeCustomIconsWithDates(
_customIcons, other._customIcons, ctx, otherIsNewer);
// merge custom icons
// Unused icons will be cleaned up later
//TODO: Use modified dates for better merging?
for (final otherCustomIcon in other._customIcons.values) {
_customIcons[otherCustomIcon.uuid] ??= otherCustomIcon;
}
// //TODO: Use modified dates for better merging?
// for (final otherCustomIcon in other._customIcons.values) {
// _customIcons[otherCustomIcon.uuid] ??= otherCustomIcon;
// }

if (other.entryTemplatesGroupChanged.isAfter(entryTemplatesGroupChanged)) {
entryTemplatesGroup.set(other.entryTemplatesGroup.get());
Expand Down Expand Up @@ -325,6 +332,36 @@ class KdbxMeta extends KdbxNode implements KdbxNodeContext {
}
}

void mergeCustomIconsWithDates(
Map<KdbxUuid, KdbxCustomIcon> local,
Map<KdbxUuid, KdbxCustomIcon> other,
MergeContext ctx,
bool assumeRemoteIsNewerWhenDatesMissing) {
for (final entry in other.entries) {
final otherKey = entry.key;
final otherItem = entry.value;
final existingItem = local[otherKey];
if (existingItem != null) {
if ((existingItem.lastModified == null ||
otherItem.lastModified == null) &&
assumeRemoteIsNewerWhenDatesMissing) {
local[otherKey] = KdbxCustomIcon(
uuid: otherItem.uuid,
data: otherItem.data,
lastModified: otherItem.lastModified ?? clock.now().toUtc(),
name: otherItem.name,
);
} else if (existingItem.lastModified != null &&
otherItem.lastModified != null &&
otherItem.lastModified!.isAfter(existingItem.lastModified!)) {
local[otherKey] = otherItem;
}
} else if (!ctx.deletedObjects.containsKey(otherKey)) {
local[otherKey] = otherItem;
}
}
}

// Import changes in [other] into this meta data.
void import(KdbxMeta other) {
// import custom icons
Expand Down Expand Up @@ -546,11 +583,16 @@ class BrowserDbSettings {
}

class KdbxCustomIcon {
KdbxCustomIcon({required this.uuid, required this.data});
KdbxCustomIcon(
{required this.uuid, required this.data, this.name, this.lastModified});

/// uuid of the icon, must be unique within each file.
final KdbxUuid uuid;

/// Encoded png data of the image. will be base64 encoded into the kdbx file.
final Uint8List data;

final String? name;

final DateTime? lastModified;
}
3 changes: 3 additions & 0 deletions lib/src/kdbx_xml.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ class KdbxXml {
/// CustomIcons >> Icon >> Data
static const NODE_DATA = 'Data';

/// CustomIcons >> Icon >> Name
static const NODE_NAME = 'Name';

/// Used for objects UUID and CustomIcons
static const NODE_UUID = 'UUID';

Expand Down
100 changes: 100 additions & 0 deletions test/merge/kdbx_merge_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,33 @@ void main() {
//TODO: https://github.com/authpass/authpass/issues/335

group('Real merges', () {

final icon1 = KdbxCustomIcon(
uuid: KdbxUuid.random(),
data: Uint8List.fromList([1,2,3]),
lastModified: fakeClock.now().toUtc(),
name: 'icon1',
);
final icon2 = KdbxCustomIcon(
uuid: KdbxUuid.random(),
data: Uint8List.fromList([4,5,6]),
lastModified: fakeClock.now().add(const Duration(minutes: 5)).toUtc(),
name: 'icon2',
);
final icon3 = KdbxCustomIcon(
uuid: KdbxUuid.random(),
data: Uint8List.fromList([7,8,9]),
lastModified: fakeClock.now().add(const Duration(minutes: 10)).toUtc(),
name: 'icon3',
);
final icon4 = KdbxCustomIcon(
uuid: KdbxUuid.random(),
data: Uint8List.fromList([10,11,12]),
);
final icon5 = KdbxCustomIcon(
uuid: KdbxUuid.random(),
data: Uint8List.fromList([13,14,15]),
);
test('Local file custom data wins', () async {
await withClock(fakeClock, () async {
final file = await TestUtil.createRealFile(proceedSeconds);
Expand Down Expand Up @@ -234,6 +261,79 @@ void main() {
),
);
});

test('Local file custom icon wins', () async {
await withClock(fakeClock, () async {
final file = await TestUtil.createRealFile(proceedSeconds);

final fileMod = await TestUtil.saveAndRead(file);
final fileReverse = await TestUtil.saveAndRead(file);

fileMod.body.meta.addCustomIcon(icon4);
proceedSeconds(10);
file.body.meta.addCustomIcon(icon5);
.........................
fileMod.body.meta.customData['custom2'] =
(value: 'custom value 3', lastModified: null);

final file2 = await TestUtil.saveAndRead(fileMod);
final file2Reverse = await TestUtil.saveAndRead(fileMod);

final merge = file.merge(file2);
final set = Set<KdbxUuid>.from(merge.merged.keys);
expect(set, hasLength(5));
expect(file.body.meta.customData['custom1'],
(value: 'custom value 1', lastModified: null));
expect(file.body.meta.customData['custom2'],
(value: 'custom value 3', lastModified: null));

final mergeReverse = file2Reverse.merge(fileReverse);
final setReverse = Set<KdbxUuid>.from(mergeReverse.merged.keys);
expect(setReverse, hasLength(5));
expect(file2Reverse.body.meta.customData['custom1'],
(value: 'custom value 2', lastModified: null));
expect(file2Reverse.body.meta.customData['custom2'],
(value: 'custom value 3', lastModified: null));
});
});

test('Newer file custom icon wins', () async {
await withClock(fakeClock, () async {
final file = await TestUtil.createRealFile(proceedSeconds);

final time1 = fakeClock.now().toUtc();
final fileMod = await TestUtil.saveAndRead(file);

fileMod.body.meta.customData['custom1'] =
(value: 'custom value 2', lastModified: time1);
proceedSeconds(10);
final time2 = fakeClock.now().toUtc();
file.body.meta.customData['custom1'] =
(value: 'custom value 1', lastModified: time2);
fileMod.body.meta.customData['custom2'] =
(value: 'custom value 3', lastModified: time2);

final fileReverse = await TestUtil.saveAndRead(file);
final file2 = await TestUtil.saveAndRead(fileMod);
final file2Reverse = await TestUtil.saveAndRead(fileMod);

final merge = file.merge(file2);
final set = Set<KdbxUuid>.from(merge.merged.keys);
expect(set, hasLength(5));
expect(file.body.meta.customData['custom1'],
(value: 'custom value 1', lastModified: time2));
expect(file.body.meta.customData['custom2'],
(value: 'custom value 3', lastModified: time2));

final mergeReverse = file2Reverse.merge(fileReverse);
final setReverse = Set<KdbxUuid>.from(mergeReverse.merged.keys);
expect(setReverse, hasLength(5));
expect(file2Reverse.body.meta.customData['custom1'],
(value: 'custom value 1', lastModified: time2));
expect(file2Reverse.body.meta.customData['custom2'],
(value: 'custom value 3', lastModified: time2));
});
});
});

group('Moving entries', () {
Expand Down

0 comments on commit ed8a979

Please sign in to comment.