Skip to content

Commit

Permalink
group merge bugfixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Antelle committed Nov 29, 2015
1 parent 428fa67 commit dacc087
Show file tree
Hide file tree
Showing 6 changed files with 862 additions and 59 deletions.
16 changes: 8 additions & 8 deletions lib/format/kdbx-entry.js
Expand Up @@ -28,8 +28,8 @@ var KdbxEntry = function() {
enabled: undefined, obfuscation: undefined, defaultSequence: undefined, items: []
};
this.history = [];
this.localHistoryTombstones = undefined;
this.parentGroup = undefined;
this._editState = undefined;
Object.preventExtensions(this);
};

Expand Down Expand Up @@ -199,10 +199,10 @@ KdbxEntry.prototype._setField = function(name, str, secure) {
};

KdbxEntry.prototype._addHistoryTombstone = function(isAdded, dt) {
if (!this.localHistoryTombstones) {
this.localHistoryTombstones = { added: [], deleted: [] };
if (!this._editState) {
this._editState = { added: [], deleted: [] };
}
this.localHistoryTombstones[isAdded ? 'added' : 'deleted'].push(dt.getTime());
this._editState[isAdded ? 'added' : 'deleted'].push(dt.getTime());
};

/**
Expand Down Expand Up @@ -320,7 +320,7 @@ KdbxEntry.prototype.merge = function(objectMap) {
* Tombstones are stored locally and must be immediately discarded by replica after successful upstream push.
* It's client responsibility, to save and load tombstones for local replica, and to clear them after successful upstream push.
*
* Implements remove-win OR-set CRDT with local tombstones stored in localHistoryTombstones.
* Implements remove-win OR-set CRDT with local tombstones stored in _editState.
*
* Format doesn't allow saving tombstones for history entries, so they are stored locally.
* Any unmodified state from past or modifications of current state synced with central upstream will be successfully merged.
Expand Down Expand Up @@ -366,7 +366,7 @@ KdbxEntry.prototype._mergeHistory = function(remoteHistory, remoteLastModTime) {
}
if (!historyEntry || entryTime > remoteEntryTime) {
// local is absent
if (this.localHistoryTombstones && this.localHistoryTombstones.deleted.indexOf(remoteEntryTime) < 0) {
if (this._editState && this._editState.deleted.indexOf(remoteEntryTime) < 0) {
// added remotely
var remoteHistoryEntryClone = new KdbxEntry();
remoteHistoryEntryClone.copyFrom(remoteHistoryEntry);
Expand All @@ -377,14 +377,14 @@ KdbxEntry.prototype._mergeHistory = function(remoteHistory, remoteLastModTime) {
}
// (!remoteHistoryEntry || entryTime < remoteEntryTime) && historyEntry
// remote is absent
if (this.localHistoryTombstones && this.localHistoryTombstones.added.indexOf(entryTime) > 0) {
if (this._editState && this._editState.added.indexOf(entryTime) > 0) {
// added locally
newHistory.push(historyEntry);
} else if (entryTime > remoteLastModTime) {
// outdated replica history has ended
newHistory.push(historyEntry);
} // else: deleted remotely
historyEntry++;
historyIx++;
}
return newHistory;
};
Expand Down
9 changes: 5 additions & 4 deletions lib/format/kdbx-group.js
Expand Up @@ -167,12 +167,13 @@ KdbxGroup.prototype._mergeCollection = function(collection, remoteCollection, ob
return; // item already processed as local item or deleted
}
item = objectMap.objects[remoteItem.uuid];
if (remoteItem.times.locationChanged > item.times.locationChanged) {
newItems.push(item); // item moved to this group remotely later than local
} else {
if (item && remoteItem.times.locationChanged > item.times.locationChanged) {
item.parentGroup = this; // item moved to this group remotely later than local
newItems.push(item);
} else if (!item) {
// item created remotely
var newItem = new remoteItem.constructor();
item.copyFrom(remoteItem);
newItem.copyFrom(remoteItem);
newItem.parentGroup = this;
newItems.push(newItem);
}
Expand Down
126 changes: 89 additions & 37 deletions lib/format/kdbx-meta.js
Expand Up @@ -16,35 +16,83 @@ var Constants = {
var KdbxMeta = function() {
this.generator = undefined;
this.headerHash = undefined;
this.name = undefined;
this._name = undefined;
this.nameChanged = undefined;
this.desc = undefined;
this._desc = undefined;
this.descChanged = undefined;
this.defaultUser = undefined;
this._defaultUser = undefined;
this.defaultUserChanged = undefined;
this.mntncHistoryDays = undefined;
this.color = undefined;
this._mntncHistoryDays = undefined;
this._color = undefined;
this.keyChanged = undefined;
this.keyChangeRec = undefined;
this.keyChangeForce = undefined;
this.recycleBinEnabled = undefined;
this.recycleBinUuid = undefined;
this._keyChangeRec = undefined;
this._keyChangeForce = undefined;
this._recycleBinEnabled = undefined;
this._recycleBinUuid = undefined;
this.recycleBinChanged = undefined;
this.entryTemplatesGroup = undefined;
this._entryTemplatesGroup = undefined;
this.entryTemplatesGroupChanged = undefined;
this.historyMaxItems = undefined;
this.historyMaxSize = undefined;
this.lastSelectedGroup = undefined;
this.lastTopVisibleGroup = undefined;
this.memoryProtection = {
this._historyMaxItems = undefined;
this._historyMaxSize = undefined;
this._lastSelectedGroup = undefined;
this._lastTopVisibleGroup = undefined;
this._memoryProtection = {
title: undefined, userName: undefined, password: undefined, url: undefined, notes: undefined
};
this.binaries = {};
this.customData = {};
this.customIcons = {};
this._editState = undefined;
Object.preventExtensions(this);
};

var props = {
name: 'nameChanged',
desc: 'descChanged',
defaultUser: 'defaultUserChanged',
mntncHistoryDays: null,
color: null,
keyChangeRec: null,
keyChangeForce: null,
recycleBinEnabled: 'recycleBinChanged',
recycleBinUuid: 'recycleBinChanged',
entryTemplatesGroup: 'entryTemplatesGroupChanged',
historyMaxItems: null,
historyMaxSize: null,
lastSelectedGroup: null,
lastTopVisibleGroup: null,
memoryProtection: null
};

Object.keys(props).forEach(function(prop) {
createProperty(prop, props[prop]);
});

function createProperty(prop, propChanged) {
var field = '_' + prop;
Object.defineProperty(KdbxMeta.prototype, prop, {
enumerable: true,
get: function() { return this[field]; },
set: function(value) {
if (value !== this[field]) {
this[field] = value;
if (propChanged) {
this[propChanged] = new Date();
} else {
this._setPropModDate(prop);
}
}
}
});
}

KdbxMeta.prototype._setPropModDate = function(prop) {
if (!this._editState) {
this._editState = { };
}
this._editState[prop] = new Date().getTime();
};

KdbxMeta.prototype._readNode = function(node) {
switch (node.tagName) {
case XmlNames.Elem.Generator:
Expand All @@ -54,64 +102,64 @@ KdbxMeta.prototype._readNode = function(node) {
this.headerHash = XmlUtils.getBytes(node);
break;
case XmlNames.Elem.DbName:
this.name = XmlUtils.getText(node);
this._name = XmlUtils.getText(node);
break;
case XmlNames.Elem.DbNameChanged:
this.nameChanged = XmlUtils.getDate(node);
break;
case XmlNames.Elem.DbDesc:
this.desc = XmlUtils.getText(node);
this._desc = XmlUtils.getText(node);
break;
case XmlNames.Elem.DbDescChanged:
this.descChanged = XmlUtils.getDate(node);
break;
case XmlNames.Elem.DbDefaultUser:
this.defaultUser = XmlUtils.getText(node);
this._defaultUser = XmlUtils.getText(node);
break;
case XmlNames.Elem.DbDefaultUserChanged:
this.defaultUserChanged = XmlUtils.getDate(node);
break;
case XmlNames.Elem.DbMntncHistoryDays:
this.mntncHistoryDays = XmlUtils.getNumber(node);
this._mntncHistoryDays = XmlUtils.getNumber(node);
break;
case XmlNames.Elem.DbColor:
this.color = XmlUtils.getText(node);
this._color = XmlUtils.getText(node);
break;
case XmlNames.Elem.DbKeyChanged:
this.keyChanged = XmlUtils.getDate(node);
break;
case XmlNames.Elem.DbKeyChangeRec:
this.keyChangeRec = XmlUtils.getNumber(node);
this._keyChangeRec = XmlUtils.getNumber(node);
break;
case XmlNames.Elem.DbKeyChangeForce:
this.keyChangeForce = XmlUtils.getNumber(node);
this._keyChangeForce = XmlUtils.getNumber(node);
break;
case XmlNames.Elem.RecycleBinEnabled:
this.recycleBinEnabled = XmlUtils.getBoolean(node);
this._recycleBinEnabled = XmlUtils.getBoolean(node);
break;
case XmlNames.Elem.RecycleBinUuid:
this.recycleBinUuid = XmlUtils.getUuid(node);
this._recycleBinUuid = XmlUtils.getUuid(node);
break;
case XmlNames.Elem.RecycleBinChanged:
this.recycleBinChanged = XmlUtils.getDate(node);
break;
case XmlNames.Elem.EntryTemplatesGroup:
this.entryTemplatesGroup = XmlUtils.getUuid(node);
this._entryTemplatesGroup = XmlUtils.getUuid(node);
break;
case XmlNames.Elem.EntryTemplatesGroupChanged:
this.entryTemplatesGroupChanged = XmlUtils.getDate(node);
break;
case XmlNames.Elem.HistoryMaxItems:
this.historyMaxItems = XmlUtils.getNumber(node);
this._historyMaxItems = XmlUtils.getNumber(node);
break;
case XmlNames.Elem.HistoryMaxSize:
this.historyMaxSize = XmlUtils.getNumber(node);
this._historyMaxSize = XmlUtils.getNumber(node);
break;
case XmlNames.Elem.LastSelectedGroup:
this.lastSelectedGroup = XmlUtils.getUuid(node);
this._lastSelectedGroup = XmlUtils.getUuid(node);
break;
case XmlNames.Elem.LastTopVisibleGroup:
this.lastTopVisibleGroup = XmlUtils.getUuid(node);
this._lastTopVisibleGroup = XmlUtils.getUuid(node);
break;
case XmlNames.Elem.MemoryProt:
this._readMemoryProtection(node);
Expand Down Expand Up @@ -312,29 +360,27 @@ KdbxMeta.prototype.write = function(parentNode) {
*/
KdbxMeta.prototype.merge = function(remote, objectMap) {
if (remote.nameChanged > this.nameChanged) {
this.name = remote.name;
this._name = remote.name;
this.nameChanged = remote.nameChanged;
}
if (remote.descChanged > this.descChanged) {
this.desc = remote.desc;
this._desc = remote.desc;
this.descChanged = remote.descChanged;
}
if (remote.defaultUserChanged > this.defaultUserChanged) {
this.defaultUser = remote.defaultUser;
this._defaultUser = remote.defaultUser;
this.defaultUserChanged = remote.defaultUserChanged;
}
if (remote.keyChanged > this.keyChanged) {
this.keyChanged = remote.keyChanged;
this.keyChangeRec = remote.keyChangeRec;
this.keyChangeForce = remote.keyChangeForce;
}
if (remote.recycleBinChanged > this.recycleBinChanged) {
this.recycleBinEnabled = remote.recycleBinEnabled;
this.recycleBinUuid = remote.recycleBinUuid;
this._recycleBinEnabled = remote.recycleBinEnabled;
this._recycleBinUuid = remote.recycleBinUuid;
this.recycleBinChanged = remote.recycleBinChanged;
}
if (remote.entryTemplatesGroupChanged > this.entryTemplatesGroupChanged) {
this.entryTemplatesGroup = remote.entryTemplatesGroup;
this._entryTemplatesGroup = remote.entryTemplatesGroup;
this.entryTemplatesGroupChanged = remote.entryTemplatesGroupChanged;
}
Object.keys(remote.customData).forEach(function(key) {
Expand All @@ -352,6 +398,12 @@ KdbxMeta.prototype.merge = function(remote, objectMap) {
this.binaries[key] = remote.binaries[key];
}
}, this);
if (!this._editState || !this._editState.historyMaxItems) { this.historyMaxItems = remote.historyMaxItems; }
if (!this._editState || !this._editState.historyMaxSize) { this.historyMaxSize = remote.historyMaxSize; }
if (!this._editState || !this._editState.keyChangeRec) { this.keyChangeRec = remote.keyChangeRec; }
if (!this._editState || !this._editState.keyChangeForce) { this.keyChangeForce = remote.keyChangeForce; }
if (!this._editState || !this._editState.mntncHistoryDays) { this.mntncHistoryDays = remote.mntncHistoryDays; }
if (!this._editState || !this._editState.color) { this.color = remote.color; }
};

/**
Expand Down

0 comments on commit dacc087

Please sign in to comment.