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); } - $('