diff --git a/customize.dist/src/less2/include/alertify.less b/customize.dist/src/less2/include/alertify.less
index c73c27c78..683584461 100644
--- a/customize.dist/src/less2/include/alertify.less
+++ b/customize.dist/src/less2/include/alertify.less
@@ -462,6 +462,10 @@
}
}
margin-bottom: 15px;
+ &:empty {
+ margin: 0;
+ display: none;
+ }
}
.cp-share-friend {
width: 70px;
@@ -493,6 +497,11 @@
visibility: hidden;
}
}
+ .cp-ownership {
+ & > label {
+ font-weight: bold;
+ }
+ }
}
}
diff --git a/lib/metadata.js b/lib/metadata.js
index 037024b57..63ab31819 100644
--- a/lib/metadata.js
+++ b/lib/metadata.js
@@ -28,10 +28,14 @@ commands.ADD_OWNERS = function (meta, args) {
throw new Error("METADATA_NONSENSE_OWNERS");
}
+ var changed = false;
args.forEach(function (owner) {
if (meta.owners.indexOf(owner) >= 0) { return; }
meta.owners.push(owner);
+ changed = true;
});
+
+ return changed;
};
// ["RM_OWNERS", ["CrufexqXcY-z+eKJlEbNELVy5Sb7E-EAAEFI8GnEtZ0="], 1561623439989]
@@ -45,13 +49,24 @@ commands.RM_OWNERS = function (meta, args) {
throw new Error("METADATA_NONSENSE_OWNERS");
}
+ var changed = false;
// remove owners one by one
// we assume there are no duplicates
args.forEach(function (owner) {
var index = meta.owners.indexOf(owner);
if (index < 0) { return; }
+ if (meta.mailbox) {
+ if (typeof(meta.mailbox) === "string") {
+ delete meta.mailbox;
+ } else {
+ delete meta.mailbox[owner];
+ }
+ }
meta.owners.splice(index, 1);
+ changed = true;
});
+
+ return changed;
};
// ["ADD_PENDING_OWNERS", ["7eEqelGso3EBr5jHlei6av4r9w2B9XZiGGwA1EgZ-5I="], 1561623438989]
@@ -67,16 +82,20 @@ commands.ADD_PENDING_OWNERS = function (meta, args) {
throw new Error("METADATA_NONSENSE_PENDING_OWNERS");
}
+ var changed = false;
// Add pending_owners array if it doesn't exist
if (!meta.pending_owners) {
meta.pending_owners = deduplicate(args);
- return;
+ return true;
}
// or fill it
args.forEach(function (owner) {
if (meta.pending_owners.indexOf(owner) >= 0) { return; }
meta.pending_owners.push(owner);
+ changed = true;
});
+
+ return changed;
};
// ["RM_PENDING_OWNERS", ["CrufexqXcY-z+eKJlEbNELVy5Sb7E-EAAEFI8GnEtZ0="], 1561623439989]
@@ -90,13 +109,17 @@ commands.RM_PENDING_OWNERS = function (meta, args) {
throw new Error("METADATA_NONSENSE_PENDING_OWNERS");
}
+ var changed = false;
// remove owners one by one
// we assume there are no duplicates
args.forEach(function (owner) {
var index = meta.pending_owners.indexOf(owner);
if (index < 0) { return; }
meta.pending_owners.splice(index, 1);
+ changed = true;
});
+
+ return changed;
};
// ["RESET_OWNERS", ["7eEqelGso3EBr5jHlei6av4r9w2B9XZiGGwA1EgZ-5I="], 1561623439989]
@@ -112,6 +135,41 @@ commands.RESET_OWNERS = function (meta, args) {
// overwrite the existing owners with the new one
meta.owners = deduplicate(args);
+ return true;
+};
+
+// ["ADD_MAILBOX", {"7eEqelGso3EBr5jHlei6av4r9w2B9XZiGGwA1EgZ-5I=": mailbox, ...}, 1561623439989]
+commands.ADD_MAILBOX = function (meta, args) {
+ // expect a new array, even if it's empty
+ if (!args || typeof(args) !== "object") {
+ throw new Error('METADATA_INVALID_MAILBOX');
+ }
+ // assume there are owners to start
+ if (!Array.isArray(meta.owners)) {
+ throw new Error("METADATA_NONSENSE_OWNERS");
+ }
+
+ var changed = false;
+
+ // For each mailbox we try to add, check if the associated edPublic is an owner
+ // If they are, add or replace the mailbox
+ Object.keys(args).forEach(function (edPublic) {
+ if (meta.owners.indexOf(edPublic) === -1) { return; }
+
+ if (typeof(meta.mailbox) === "string") {
+ var str = meta.mailbox;
+ meta.mailbox = {};
+ meta.mailbox[meta.owners[0]] = str;
+ }
+
+ // Make sure mailbox is defined
+ if (!meta.mailbox) { meta.mailbox = {}; }
+
+ meta.mailbox[edPublic] = args[edPublic];
+ changed = true;
+ });
+
+ return changed;
};
commands.UPDATE_EXPIRATION = function () {
@@ -127,7 +185,7 @@ var handleCommand = Meta.handleCommand = function (meta, line) {
throw new Error("METADATA_UNSUPPORTED_COMMAND");
}
- commands[command](meta, args);
+ return commands[command](meta, args);
};
Meta.commands = Object.keys(commands);
diff --git a/rpc.js b/rpc.js
index 437e9222d..fd0c11f35 100644
--- a/rpc.js
+++ b/rpc.js
@@ -340,6 +340,7 @@ var getMetadata = function (Env, channel, cb) {
value: value
}
*/
+// XXX global saferphore may cause issues here, a queue "per channel" is probably better
var metadataSem = Saferphore.create(1);
var setMetadata = function (Env, data, unsafeKey, cb) {
var channel = data.channel;
@@ -382,13 +383,20 @@ var setMetadata = function (Env, data, unsafeKey, cb) {
// Add the new metadata line
var line = [command, data.value, +new Date()];
+ var changed = false;
try {
- Meta.handleCommand(metadata, line);
+ changed = Meta.handleCommand(metadata, line);
} catch (e) {
g();
return void cb(e);
}
+ // if your command is valid but it didn't result in any change to the metadata,
+ // call back now and don't write any "useless" line to the log
+ if (!changed) {
+ g();
+ return void cb(void 0, metadata);
+ }
Env.msgStore.writeMetadata(channel, JSON.stringify(line), function (e) {
g();
if (e) {
diff --git a/www/common/common-messaging.js b/www/common/common-messaging.js
index 3e673b1e9..8e27b3fd9 100644
--- a/www/common/common-messaging.js
+++ b/www/common/common-messaging.js
@@ -83,6 +83,9 @@ define([
store.messenger.updateMyData();
}
var myData = createData(store.proxy);
+ if (store.proxy.friends) {
+ store.proxy.friends.me = myData;
+ }
var todo = function (friend) {
if (!friend || !friend.notifications) { return; }
myData.channel = friend.channel;
diff --git a/www/common/common-messenger.js b/www/common/common-messenger.js
index 80f4ef43f..1210e652f 100644
--- a/www/common/common-messenger.js
+++ b/www/common/common-messenger.js
@@ -230,6 +230,11 @@ define([
});
};
+ messenger.onFriendUpdate = function (curve) {
+ var friend = getFriend(proxy, curve);
+ checkFriendData(curve, friend, friend.channel);
+ };
+
// Id message allows us to map a netfluxId with a public curve key
var onIdMessage = function (msg, sender) {
var channel, parsed0;
@@ -374,12 +379,14 @@ define([
|| mySyncData.profile !== myData.profile
|| mySyncData.avatar !== myData.avatar) {
delete myData.channel;
- Object.keys(channels).forEach(function (chan) {
- var channel = channels[chan];
+ Object.keys(friends).forEach(function (curve) {
+ var friend = friends[curve];
+ var chan = friend.channel;
+ if (friend.notifications) { return; }
+ if (!chan) { return; }
- if (!channel) {
- return void console.error('NO_SUCH_CHANNEL');
- }
+ var channel = channels[chan];
+ if (!channel) { return; }
if (channel.readOnly) { return; }
var msg = [Types.update, myData.curvePublic, +new Date(), myData];
@@ -397,7 +404,6 @@ define([
info: myData,
types: ['displayName', 'profile', 'avatar'],
});
- friends.me = myData;
}
};
diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js
index b37d71355..bdfa56d7d 100644
--- a/www/common/common-ui-elements.js
+++ b/www/common/common-ui-elements.js
@@ -71,56 +71,22 @@ define([
var getPropertiesData = function (common, cb) {
var data = {};
NThen(function (waitFor) {
- common.getPadAttribute('password', waitFor(function (err, val) {
- data.password = val;
- }));
- }).nThen(function (waitFor) {
var base = common.getMetadataMgr().getPrivateData().origin;
- // XXX getFileData?
- // XXX getPadMetadata
- common.getPadAttribute('href', waitFor(function (err, val) {
- if (!val) { return; }
- data.href = base + val;
- }));
- common.getPadAttribute('roHref', waitFor(function (err, val) {
- if (!val) { return; }
- data.roHref = base + val;
- }));
- common.getPadAttribute('channel', waitFor(function (err, val) {
- data.channel = val;
- }));
- common.getPadAttribute('rtChannel', waitFor(function (err, val) {
- data.rtChannel = val;
- }));
- common.getPadAttribute('lastVersion', waitFor(function (err, val) {
- data.lastVersion = val;
- }));
- common.getPadAttribute('atime', waitFor(function (err, val) {
- data.atime = val;
- }));
- common.getPadAttribute('ctime', waitFor(function (err, val) {
- data.ctime = val;
- }));
- common.getPadAttribute('title', waitFor(function (err, val) {
- data.title = val;
- }));
- common.getPadAttribute('tags', waitFor(function (err, val) {
- data.tags = val;
+ common.getPadAttribute('', waitFor(function (err, val) {
+ if (err || !val) {
+ waitFor.abort();
+ return void cb(err || 'EEMPTY');
+ }
+ Util.extend(data, val);
+ if (data.href) { data.href = base + data.href; }
+ if (data.roHref) { data.roHref = base + data.roHref; }
}));
common.getPadMetadata(null, waitFor(function (obj) {
- console.log(obj);
if (obj && obj.error) { return; }
data.owners = obj.owners;
data.expire = obj.expire;
data.pending_owners = obj.pending_owners;
}));
- /*
- common.getPadAttribute('owners', waitFor(function (err, val) {
- data.owners = val;
- }));
- common.getPadAttribute('expire', waitFor(function (err, val) {
- data.expire = val;
- }));*/
}).nThen(function () {
cb(void 0, data);
});
@@ -132,20 +98,20 @@ define([
var user = common.getMetadataMgr().getUserData();
var edPublic = priv.edPublic;
var channel = data.channel;
- var owners = data.owners;
- var pending_owners = data.pending_owners;
+ var owners = data.owners || [];
+ var pending_owners = data.pending_owners || [];
var redrawAll = function () {};
- var div1 = h('div.cp-share-friends.cp-share-column');
- var div2 = h('div.cp-share-friends.cp-share-column');
+ var div1 = h('div.cp-share-friends.cp-share-column.cp-ownership');
+ var div2 = h('div.cp-share-friends.cp-share-column.cp-ownership');
var $div1 = $(div1);
var $div2 = $(div2);
// Remove owner column
var drawRemove = function (pending) {
var _owners = {};
- var o = pending ? pending_owners : owners;
+ var o = (pending ? pending_owners : owners) || [];
o.forEach(function (ed) {
var f;
Object.keys(friends).some(function (c) {
@@ -154,20 +120,25 @@ define([
return true;
}
});
+ if (ed === edPublic) {
+ f = f || user;
+ if (f.name) {
+ f.displayName = f.name;
+ }
+ }
_owners[ed] = f || {
- displayName: 'Unknown user: '+ ed, // XXX
+ displayName: Messages._getKey('owner_unknownUser', [ed]),
notifications: true,
edPublic: ed,
};
});
- var msg = pending ? 'Remove a pending owner:'
- : 'Remove an existing owner:'; // XXX
+ var msg = pending ? Messages.owner_removePendingText
+ : Messages.owner_removeText;
var removeCol = UIElements.getFriendsList(msg, {
common: common,
friends: _owners,
noFilter: true
}, function () {
- console.log(arguments);
});
var $div = $(removeCol.div);
var others1 = removeCol.others;
@@ -184,12 +155,13 @@ define([
});
// When clicking on the remove button, we check the selected users.
// If you try to remove yourself, we'll display an additional warning message
- var btnMsg = pending ? 'Remove pending owners' : 'Remove owners'; // XXX
+ var btnMsg = pending ? Messages.owner_removePendingButton : Messages.owner_removeButton;
var removeButton = h('button.no-margin', btnMsg);
$(removeButton).click(function () {
// Check selection
var $sel = $div.find('.cp-share-friend.cp-selected');
var sel = $sel.toArray();
+ if (!sel.length) { return; }
var me = false;
var toRemove = sel.map(function (el) {
var ed = $(el).attr('data-ed');
@@ -197,25 +169,54 @@ define([
if (ed === edPublic) { me = true; }
return ed;
}).filter(function (x) { return x; });
- // Send the command
- var send = function () {
+ NThen(function (waitFor) {
+ var msg = me ? Messages.owner_removeMeConfirm : Messages.owner_removeConfirm;
+ UI.confirm(msg, waitFor(function (yes) {
+ if (!yes) {
+ waitFor.abort();
+ return;
+ }
+ }));
+ }).nThen(function (waitFor) {
+ // Send the command
sframeChan.query('Q_SET_PAD_METADATA', {
channel: channel,
command: pending ? 'RM_PENDING_OWNERS' : 'RM_OWNERS',
value: toRemove
- }, function (err, res) {
+ }, waitFor(function (err, res) {
err = err || (res && res.error);
- if (err) { return void UI.warn('ERROR' + err); } // XXX
- redrawAll();
- UI.log('DONE'); // XXX
+ if (err) {
+ waitFor.abort();
+ redrawAll();
+ var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
+ : Messages.error;
+ return void UI.warn(text);
+ }
+ UI.log(Messages.saved);
+ }));
+ }).nThen(function (waitFor) {
+ sel.forEach(function (el) {
+ var friend = friends[$(el).attr('data-curve')];
+ if (!friend) { return; }
+ common.mailbox.sendTo("RM_OWNER", {
+ channel: channel,
+ title: data.title,
+ pending: pending,
+ user: {
+ displayName: user.name,
+ avatar: user.avatar,
+ profile: user.profile,
+ notifications: user.notifications,
+ curvePublic: user.curvePublic,
+ edPublic: priv.edPublic
+ }
+ }, {
+ channel: friend.notifications,
+ curvePublic: friend.curvePublic
+ }, waitFor());
});
- };
- var msg = me ?
- "Are you sure? You're going to give up on your rights, this can't be undone!" :
- "Are you sure?"; // XXX
- UI.confirm(msg, function (yes) {
- if (!yes) { return; }
- send();
+ }).nThen(function () {
+ redrawAll();
});
});
$div.append(h('p', removeButton));
@@ -226,16 +227,16 @@ define([
var drawAdd = function () {
var _friends = JSON.parse(JSON.stringify(friends));
Object.keys(_friends).forEach(function (curve) {
- if (owners.indexOf(_friends[curve].edPublic) !== -1) {
+ if (owners.indexOf(_friends[curve].edPublic) !== -1 ||
+ pending_owners.indexOf(_friends[curve].edPublic) !== -1) {
delete _friends[curve];
}
});
- var addCol = UIElements.getFriendsList('Ask a friend to be an owner.', {
+ var addCol = UIElements.getFriendsList(Messages.owner_addText, {
common: common,
friends: _friends
}, function () {
- // XXX onSelect...
- console.log(arguments);
+ //console.log(arguments);
});
$div2 = $(addCol.div);
var others2 = addCol.others;
@@ -249,20 +250,20 @@ define([
order = order ? 'order:'+order : '';
$(this).removeClass('cp-selected').attr('style', order);
}
- // XXX onSelect...
});
// When clicking on the add button, we get the selected users.
- var addButton = h('button.no-margin', 'Add owners'); // XXX
+ var addButton = h('button.no-margin', Messages.owner_addButton);
$(addButton).click(function () {
// Check selection
var $sel = $div2.find('.cp-share-friend.cp-selected');
var sel = $sel.toArray();
+ if (!sel.length) { return; }
var toAdd = sel.map(function (el) {
return friends[$(el).attr('data-curve')].edPublic;
}).filter(function (x) { return x; });
NThen(function (waitFor) {
- var msg = "Are you sure?"; // XXX
+ var msg = Messages.owner_addConfirm;
UI.confirm(msg, waitFor(function (yes) {
if (!yes) {
waitFor.abort();
@@ -270,23 +271,22 @@ define([
}
}));
}).nThen(function (waitFor) {
- console.log('koko');
// Send the command
sframeChan.query('Q_SET_PAD_METADATA', {
channel: channel,
command: 'ADD_PENDING_OWNERS',
value: toAdd
}, waitFor(function (err, res) {
- console.error(arguments);
err = err || (res && res.error);
if (err) {
waitFor.abort();
- return void UI.warn('ERROR' + err);
- } // XXX
+ redrawAll();
+ var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
+ : Messages.error;
+ return void UI.warn(text);
+ }
}));
}).nThen(function (waitFor) {
- console.log('okok');
- // TODO send notifications
sel.forEach(function (el) {
var friend = friends[$(el).attr('data-curve')];
if (!friend) { return; }
@@ -310,28 +310,42 @@ define([
});
}).nThen(function () {
redrawAll();
- UI.log('DONE'); // XXX
+ UI.log(Messages.saved);
});
});
$div2.append(h('p', addButton));
return $div2;
};
- redrawAll = function () {
- $div1.empty();
- $div2.empty();
- common.getPadMetadata(null, function (obj) {
+ redrawAll = function (md) {
+ var todo = function (obj) {
if (obj && obj.error) { return; }
- owners = obj.owners;
- pending_owners = obj.pending_owners;
+ owners = obj.owners || [];
+ pending_owners = obj.pending_owners || [];
+ $div1.empty();
+ $div2.empty();
$div1.append(drawRemove(false)).append(drawRemove(true));
$div2.append(drawAdd());
- });
+ };
+
+ if (md) { return void todo(md); }
+ common.getPadMetadata({
+ channel: data.channel
+ }, todo);
};
$div1.append(drawRemove(false)).append(drawRemove(true));
$div2.append(drawAdd());
+ var handler = sframeChan.on('EV_RT_METADATA', function (md) {
+ if (!$div1.length) {
+ return void handler.stop();
+ }
+ owners = md.owners || [];
+ pending_owners = md.pending_owners || [];
+ redrawAll(md);
+ });
+
// Create modal
var link = h('div.cp-share-columns', [
div1,
@@ -341,146 +355,165 @@ define([
]);
var linkButtons = [{
className: 'cancel',
- name: 'CLOSE', // XXX existing key?
+ name: Messages.filePicker_close,
onClick: function () {},
keys: [27]
}];
return UI.dialog.customModal(link, {buttons: linkButtons});
};
var getRightsProperties = function (common, data, cb) {
- var $d = $('
');
- if (!data) { return void cb(void 0, $d); }
+ var $div = $('
');
+ if (!data) { return void cb(void 0, $div); }
- $('