From bd6a199dca64494b271f64b2caec15fe60554c29 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 5 Jul 2018 13:56:16 +0200 Subject: [PATCH] Add missing functions to the proxy manager --- www/common/outer/async-store.js | 145 +++++++++------------- www/common/proxy-manager.js | 211 +++++++++++++++++++++++++++----- 2 files changed, 241 insertions(+), 115 deletions(-) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index b3601813a..70e5ee95b 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -33,12 +33,17 @@ define([ var store = window.CryptPad_AsyncStore = {}; - var onSync = function (cb) { - Realtime.whenRealtimeSyncs(store.realtime, cb); + nThen(function (waitFor) { + Realtime.whenRealtimeSyncs(store.realtime, waitFor()); + if (store.sharedFolders) { + for (var k in store.sharedFolders) { + Realtime.whenRealtimeSync(store.sharedFolders[k].realtime, waitFor()); + } + } + }).nThen(function () { cb(); }); }; - Store.get = function (clientId, key, cb) { cb(Util.find(store.proxy, key)); }; @@ -82,7 +87,7 @@ define([ // This list is filtered so that it doesn't include pad owned by other users // It now includes channels from shared folders var edPublic = store.proxy.edPublic; - var list = store.manager.getPinList(edPublic); + var list = store.manager.getChannelsList(edPublic, 'pin'); // Get the avatar var profile = store.proxy.profile; @@ -105,19 +110,8 @@ define([ }; var getExpirableChannelList = function () { - var list = []; - store.userObject.getFiles([store.userObject.FILES_DATA]).forEach(function (id) { - var data = store.userObject.getFileData(id); - var edPublic = store.proxy.edPublic; - - // Push channels owned by someone else or channel that should have expired - // because of the expiration time - if ((data.owners && data.owners.length && data.owners.indexOf(edPublic) === -1) || - (data.expire && data.expire < (+new Date()))) { - list.push(data.channel); - } - }); - return list; + var edPublic = store.proxy.edPublic; + return store.manager.getChannelsList(edPublic, 'expirable'); }; var getCanonicalChannelList = function (expirable) { @@ -445,16 +439,8 @@ define([ if (data.expire) { pad.expire = data.expire; } if (data.password) { pad.password = data.password; } if (data.channel) { pad.channel = data.channel; } - var uo = store.userObject; - var path = ['root']; - if (data.path) { - var resolved = store.manager.resolvePath(path); - uo = resolved.userObject; - path = resolved.path; - } - uo.pushData(pad, function (e, id) { + store.manager.addPad(data.path, pad, function (e) { if (e) { return void cb({error: "Error while adding a template:"+ e}); } - uo.add(id, path); sendDriveEvent('DRIVE_CHANGE', { path: ['drive', UserObject.FILES_DATA] }, clientId); @@ -463,17 +449,8 @@ define([ }; var getOwnedPads = function () { - var list = []; - store.userObject.getFiles([store.userObject.FILES_DATA]).forEach(function (id) { - var data = store.userObject.getFileData(id); - var edPublic = store.proxy.edPublic; - - // Push channels owned by someone else or channel that should have expired - // because of the expiration time - if (data.owners && data.owners.length === 1 && data.owners.indexOf(edPublic) !== -1) { - list.push(data.channel); - } - }); + var edPublic = store.proxy.edPublic; + var list = store.manager.getChannelsList(edPublic, 'owned'); if (store.proxy.todo) { // No password for todo list.push(Hash.hrefToHexChannelId('/todo/#' + store.proxy.todo, null)); @@ -595,33 +572,6 @@ define([ }); }; - var getAttributeObject = function (attr) { - if (typeof attr === "string") { - console.error('DEPRECATED: use setAttribute with an array, not a string'); - return { - path: ['settings'], - obj: store.proxy.settings, - key: attr - }; - } - if (!Array.isArray(attr)) { return void console.error("Attribute must be string or array"); } - if (attr.length === 0) { return void console.error("Attribute can't be empty"); } - var obj = store.proxy.settings; - attr.forEach(function (el, i) { - if (i === attr.length-1) { return; } - if (!obj[el]) { - obj[el] = {}; - } - else if (typeof obj[el] !== "object") { return void console.error("Wrong attribute"); } - obj = obj[el]; - }); - return { - path: ['settings'].concat(attr), - obj: obj, - key: attr[attr.length-1] - }; - }; - // Set the display name (username) in the proxy Store.setDisplayName = function (clientId, value, cb) { store.proxy[Constants.displayNameKey] = value; @@ -650,7 +600,7 @@ define([ * - value (String) */ Store.setPadAttribute = function (clientId, data, cb) { - store.userObject.setPadAttribute(data.href, data.attr, data.value, function () { + store.manager.setPadAttribute(data, function () { sendDriveEvent('DRIVE_CHANGE', { path: ['drive', UserObject.FILES_DATA] }, clientId); @@ -658,11 +608,38 @@ define([ }); }; Store.getPadAttribute = function (clientId, data, cb) { - store.userObject.getPadAttribute(data.href, data.attr, function (err, val) { + store.manager.getPadAttribute(data, function (err, val) { if (err) { return void cb({error: err}); } cb(val); }); }; + + var getAttributeObject = function (attr) { + if (typeof attr === "string") { + console.error('DEPRECATED: use setAttribute with an array, not a string'); + return { + path: ['settings'], + obj: store.proxy.settings, + key: attr + }; + } + if (!Array.isArray(attr)) { return void console.error("Attribute must be string or array"); } + if (attr.length === 0) { return void console.error("Attribute can't be empty"); } + var obj = store.proxy.settings; + attr.forEach(function (el, i) { + if (i === attr.length-1) { return; } + if (!obj[el]) { + obj[el] = {}; + } + else if (typeof obj[el] !== "object") { return void console.error("Wrong attribute"); } + obj = obj[el]; + }); + return { + path: ['settings'].concat(attr), + obj: obj, + key: attr[attr.length-1] + }; + }; Store.setAttribute = function (clientId, data, cb) { try { var object = getAttributeObject(data.attr); @@ -680,11 +657,12 @@ define([ // Tags Store.listAllTags = function (clientId, data, cb) { - cb(store.userObject.getTagsList()); + cb(store.manager.getTagsList()); }; // Templates Store.getTemplates = function (clientId, data, cb) { + // No templates in shared folders: we don't need the manager here var templateFiles = store.userObject.getFiles(['template']); var res = []; templateFiles.forEach(function (f) { @@ -694,6 +672,7 @@ define([ cb(res); }; Store.incrementTemplateUse = function (clientId, href) { + // No templates in shared folders: we don't need the manager here store.userObject.getPadAttribute(href, 'used', function (err, data) { // This is a not critical function, abort in case of error to make sure we won't // create any issue with the user object or the async store @@ -705,6 +684,7 @@ define([ // Pads Store.moveToTrash = function (clientId, data, cb) { + // XXX move a pad from a shared folder to the trash? var href = Hash.getRelativeHref(data.href); store.userObject.forget(href); sendDriveEvent('DRIVE_CHANGE', { @@ -785,7 +765,6 @@ define([ // Filepicker app Store.getSecureFilesList = function (clientId, query, cb) { var list = {}; - var hashes = []; var types = query.types; var where = query.where; var filter = query.filter || {}; @@ -800,19 +779,19 @@ define([ } return filtered; }; - store.userObject.getFiles(where).forEach(function (id) { - var data = store.userObject.getFileData(id); + store.manager.getSecureFilesList(where).forEach(function (obj) { + var data = obj.data; + var id = obj.id; var parsed = Hash.parsePadUrl(data.href || data.roHref); if ((!types || types.length === 0 || types.indexOf(parsed.type) !== -1) && - hashes.indexOf(parsed.hash) === -1 && !isFiltered(parsed.type, data)) { - hashes.push(parsed.hash); list[id] = data; } }); cb(list); }; Store.getPadData = function (clientId, id, cb) { + // FIXME: this is only used for templates at the moment, so we don't need the manager cb(store.userObject.getFileData(id)); }; @@ -1159,7 +1138,7 @@ define([ }; // SHARED FOLDERS - var loadSharedFolder = function (id, data) { + var loadSharedFolder = function (id, data, cb) { var parsed = Hash.parsePadUrl(data.href); var secret = Hash.getSecrets('folder', parsed.hash, data.password); var listmapConfig = { @@ -1176,14 +1155,17 @@ define([ }; var rt = Listmap.create(listmapConfig); store.sharedFolders[id] = rt; - store.manager.addProxy(rt.proxy); + rt.proxy.on('ready', function (info) { + store.manager.addProxy(id, rt.proxy, info.leave); + cb(rt); + }); return rt; }; Store.addSharedFolder = function (clientId, data, cb) { var path = data.path; var id; nThen(function (waitFor) { - // TODO + // TODO XXX get the folder data (href, title, ...) var folderData = {}; // 1. add the shared folder to our list of shared folders store.userObject.pushSharedFolder(folderData, waitFor(function (err, folderId) { @@ -1199,11 +1181,7 @@ define([ onSync(waitFor()); // 2b. load the proxy - var rt = loadSharedFolder(id, data); - rt.on('ready', waitFor(function () { - // TODO - // "fixFiles" - })); + loadSharedFolder(id, data, waitFor()); }).nThen(function () { sendDriveEvent('DRIVE_CHANGE', { path: ['drive'].concat(path) @@ -1347,16 +1325,11 @@ define([ ////////////////////////////////////////////////////////////////// var loadSharedFolders = function (waitFor) { - // TODO store.sharedFolders = {}; var shared = Util.find(store.proxy, ['drive', UserObject.SHARED_FOLDERS]) || {}; Object.keys(shared).forEach(function (id) { var sf = shared[id]; - var rt = loadSharedFolder(id, sf); - rt.on('ready', waitFor(function () { - // TODO - // "fixFiles" - })); + loadSharedFolder(id, sf, waitFor()); }); }; diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js index 9b774b86d..bacbb0d9f 100644 --- a/www/common/proxy-manager.js +++ b/www/common/proxy-manager.js @@ -35,9 +35,57 @@ define([ }; /* - Paths + Tools */ + var _getUserObjects = function (Env) { + var userObjects = [Env.user.userObject]; + var foldersUO = Object.keys(Env.folders).map(function (k) { + return Env.folders[k].userObject; + }); + Array.prototype.push.apply(userObjects, foldersUO); + return userObjects; + }; + + // Return files data objects associated to a channel for setPadTitle + // All occurences are returned, in drive or shared folders + var findChannel = function (Env, channel) { + var ret = []; + Env.user.userObject.findChannels([channel]).forEach(function (id) { + ret.push({ + data: Env.user.userObject.getFileData(id), + userObject: Env.user.userObject + }); + }); + Object.keys(Env.folders).forEach(function (fId) { + Env.folders[fId].userObject.findChannels([channel]).forEach(function (id) { + ret.push({ + data: Env.folders[fId].userObject.getFileData(id), + userObject: Env.folders[fId].userObject + }); + }); + }); + return ret; + }; + // Return files data objects associated to a given href for setPadAttribute... + var findHref = function (Env, href) { + var ret = []; + var id = Env.user.userObject.getIdFromHref(href); + ret.push({ + data: Env.user.userObject.getFileData(id), + userObject: Env.user.userObject + }); + Object.keys(Env.folders).forEach(function (fId) { + var id = Env.folders[fId].userObject.getIdFromHref(href); + ret.push({ + fId: fId, + data: Env.folders[fId].userObject.getFileData(id), + userObject: Env.folders[fId].userObject + }); + }); + return ret; + }; + // Transform an absolute path into a path relative to the correct shared folder var _resolvePath = function (Env, path) { var res = { @@ -123,7 +171,7 @@ define([ }; /* - RPC commands + Drive RPC */ // Move files or folders in the drive @@ -251,55 +299,152 @@ define([ } }; - // Return files data objects associated to a channel for setPadTitle - // All occurences are returned, in drive or shared folders - var findChannel = function (Env, channel) { - var ret = []; - Env.user.userObject.findChannels([channel]).forEach(function (id) { - ret.push({ - data: Env.user.userObject.getFileData(id), - userObject: Env.user.userObject - }); + // Set the value everywhere the given pad is stored (main and shared folders) + var setPadAttribute = function (Env, data, cb) { + cb = cb || function () {}; + var datas = findHref(Env, data.href); + var nt = nThen; + datas.forEach(function (d) { + nt = nt(function (waitFor) { + d.userObject.setPadAttribute(data.href, data.attr, data.value, waitFor()); + }).nThen; }); - Object.keys(Env.folders).forEach(function (fId) { - Env.folders[fId].userObject.findChannels([channel]).forEach(function (id) { - ret.push({ - data: Env.folders[fId].userObject.getFileData(id), - userObject: Env.folders[fId].userObject + nt(function () { cb(); }); + }; + // Get pad attribute must return only one value, even if the pad is stored in multiple places + // (main or shared folders) + // We're going to return the value with the most recent atime. The attributes may have been + // updated in a shared folder by another user, so the most recent one is more likely to be the + // correct one. + var getPadAttribute = function (Env, data, cb) { + cb = cb || function () {}; + var datas = findHref(Env, data.href); + var nt = nThen; + var res = {}; + datas.forEach(function (d) { + nt = nt(function (waitFor) { + var atime, value; + var w = waitFor(); + nThen(function (waitFor2) { + d.userObject.getPadAttribute(data.href, 'atime', waitFor2(function (err, v) { + atime = v; + })); + d.userObject.getPadAttribute(data.href, data.attr, waitFor2(function (err, v) { + value = v; + })); + }).nThen(function () { + if (!res.value || res.atime < atime) { + res.atime = atime; + res.value = value; + } + w(); }); + }).nThen; + }); + nt(function () { cb(null, res.value); }); + }; + + var getTagsList = function (Env) { + var list = []; + var userObjects = _getUserObjects(Env); + userObjects.forEach(function (uo) { + Array.prototype.push.apply(list, uo.getTagsList()); + }); + Util.deduplicateString(list); + return list; + }; + + var getSecureFilesList = function (Env, where) { + var userObjects = _getUserObjects(Env); + var list = []; + var channels = []; + userObjects.forEach(function (uo) { + var toPush = uo.getFiles(where).map(function (id) { + return { + id: id, + data: uo.getFileData(id) + }; + }).filter(function (d) { + if (channels.indexOf(d.data.channel) === -1) { + channels.push(d.data.channel); + return true; + } }); + Array.prototype.push.apply(list, toPush); }); - return ret; + return list; }; - // Get the list of channels that should be pinned - var getPinList = function (Env, edPublic) { + + /* + Store + */ + + // Get the list of channels filtered by a type (expirable channels, owned channels, pin list) + var getChannelsList = function (Env, edPublic, type) { if (!edPublic) { return; } - var toPin = []; + var result = []; var addChannel = function (userObject) { + if (type === 'expirable') { + return function (fileId) { + var data = userObject.getFileData(fileId); + // Don't push duplicates + if (result.indexOf(data.channel) !== -1) { return; } + // Return pads owned by someone else or expired by time + if ((data.owners && data.owners.length && data.owners.indexOf(edPublic) === -1) || + (data.expire && data.expire < (+new Date()))) { + result.push(data.channel); + } + }; + } + if (type === 'owned') { + return function (fileId) { + var data = userObject.getFileData(fileId); + // Don't push duplicates + if (result.indexOf(data.channel) !== -1) { return; } + // Return owned pads + if (Array.isArray(data.owners) && data.owners.length && + data.owners.indexOf(edPublic) !== -1) { + result.push(data.channel); + } + }; + } return function (fileId) { var data = userObject.getFileData(fileId); // Don't pin pads owned by someone else if (Array.isArray(data.owners) && data.owners.length && data.owners.indexOf(edPublic) === -1) { return; } // Don't push duplicates - if (toPin.indexOf(data.channel) === -1) { - toPin.push(data.channel); + if (result.indexOf(data.channel) === -1) { + result.push(data.channel); } }; }; // Get the list of user objects - var userObjects = [Env.user.userObject]; - var foldersUO = Object.keys(Env.folders).map(function (k) { - return Env.folders[k].userObject; - }); - Array.prototype.push.apply(userObjects, foldersUO); + var userObjects = _getUserObjects(Env); userObjects.forEach(function (uo) { var files = uo.getFiles([UserObject.FILES_DATA]); files.forEach(addChannel(uo)); }); + + return result; + }; + + var addPad = function (Env, path, pad, cb) { + var uo = Env.user.userObject; + var p = ['root']; + if (path) { + var resolved = _resolvePath(Env, path); + uo = resolved.userObject; + p = resolved.path; + } + uo.pushData(pad, function (e, id) { + if (e) { return void cb(e); } + uo.add(id, p); + cb(); + }); }; var create = function (proxy, edPublic, uoConfig) { @@ -321,12 +466,20 @@ define([ }; return { + // Manager addProxy: callWithEnv(addProxy), removeProxy: callWithEnv(removeProxy), + // Drive command: callWithEnv(onCommand), + getPadAttribute: callWithEnv(getPadAttribute), + setPadAttribute: callWithEnv(setPadAttribute), + getTagsList: callWithEnv(getTagsList), + getSecureFilesList: callWithEnv(getSecureFilesList), + // Store + getChannelsList: callWithEnv(getChannelsList), + addPad: callWithEnv(addPad), + // Tools findChannel: callWithEnv(findChannel), - getPinList: callWithEnv(getPinList), - resolvePath: callWithEnv(_resolvePath), user: Env.user, folders: Env.folders };