From 554b4a978d65027ca48b1c0fba3f863666b70e25 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 28 Jun 2018 14:15:30 +0200 Subject: [PATCH 01/27] Load shared folders in the store --- www/common/outer/async-store.js | 69 +++++++++++++++++++++++++++++++++ www/common/userObject.js | 1 + 2 files changed, 70 insertions(+) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 52c0efb9c..01d2bd62b 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1183,6 +1183,61 @@ define([ }])); }; + // SHARED FOLDERS + var loadSharedFolder = function (id, data) { + var parsed = Hash.parsePadUrl(data.href); + var secret = Hash.getSecrets('folder', parsed.hash, data.password); + var listmapConfig = { + data: {}, + websocketURL: NetConfig.getWebsocketURL(), + channel: secret.channel, + readOnly: false, + validateKey: secret.keys.validateKey || undefined, + crypto: Crypto.createEncryptor(secret.keys), + userName: 'sharedFolder', + logLevel: 1, + ChainPad: ChainPad, + classic: true, + }; + var rt = Listmap.create(listmapConfig); + store.sharedFolders[id] = rt; + return rt; + }; + Store.addSharedFolder = function (clientId, data, cb) { + var path = data.path; + var href = data.href; + var id; + nThen(function (waitFor) { + // TODO + var folderData = {}; + // 1. add the shared folder to our list of shared folders + store.userObject.pushSharedFolder(folderData, waitFor(function (err, folderId) { + if (err) { + waitFor.abort(); + return void cb(err); + } + id = folderId; + })); + nThen(function (waitFor) { + // 2a. add the shared folder to the path in our drive + store.userObject.add(id, path); + onSync(waitFor()); + + // 2b. load the proxy + var rt = loadSharedFolder(folderId, data); + rt.on('ready', waitFor(function () { + // TODO + // "fixFiles" + })); + }).nThen(function () { + sendDriveEvent('DRIVE_CHANGE', { + path: ['drive'].concat(path) + }, clientId); + cb(); + }); + }; + + // Drive Store.userObjectCommand = function (clientId, cmdData, cb) { if (!cmdData || !cmdData.cmd) { return; } @@ -1331,6 +1386,20 @@ define([ /////////////////////// Init ///////////////////////////////////// ////////////////////////////////////////////////////////////////// + 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" + })); + }); + }; + var onReady = function (clientId, returned, cb) { var proxy = store.proxy; var userObject = store.userObject = UserObject.init(proxy.drive, { diff --git a/www/common/userObject.js b/www/common/userObject.js index fb39eb484..0042d5d57 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -13,6 +13,7 @@ define([ var UNSORTED = module.UNSORTED = "unsorted"; var TRASH = module.TRASH = "trash"; var TEMPLATE = module.TEMPLATE = "template"; + var SHARED_FOLDERS = module.SHARED_FOLDERS = "sharedFolders"; module.init = function (files, config) { var exp = {}; From 470f404a24380058c51721be819840d899a6659c Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 28 Jun 2018 15:31:30 +0200 Subject: [PATCH 02/27] temp --- www/common/common-interface.js | 2 +- www/common/common-ui-elements.js | 16 ++++++++++++---- www/common/cryptpad-common.js | 10 ++++++++-- www/drive/inner.js | 29 ++++++++++++++++++----------- 4 files changed, 39 insertions(+), 18 deletions(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 7b05d22fe..974490796 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -761,7 +761,7 @@ define([ UI.getFileIcon = function (data) { var $icon = UI.getIcon(); if (!data) { return $icon; } - var href = data.href; + var href = data.href || data.roHref; var type = data.type; if (!href && !type) { return $icon; } diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index d54540efa..b35bebdfd 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -73,6 +73,8 @@ define([ data.password = val; })); }).nThen(function (waitFor) { + var base = common.getMetadataMgr().getPrivateData().origin; + /* XXX common.getPadAttribute('href', waitFor(function (err, val) { var base = common.getMetadataMgr().getPrivateData().origin; @@ -93,6 +95,12 @@ define([ if (!hrefsecret.keys) { return; } var viewHash = Hash.getViewHashFromKeys(hrefsecret); data.roHref = hBase + viewHash; + }));*/ + common.getPadAttribute('href', waitFor(function (err, val) { + data.href = base + val; + })); + common.getPadAttribute('roHref', waitFor(function (err, val) { + data.roHref = base + val; })); common.getPadAttribute('channel', waitFor(function (err, val) { data.channel = val; @@ -162,7 +170,7 @@ define([ $d.append(password); } - var parsed = Hash.parsePadUrl(data.href); + var parsed = Hash.parsePadUrl(data.href || data.roHref); if (owned && parsed.hashData.type === 'pad') { var sframeChan = common.getSframeChannel(); var changePwTitle = Messages.properties_changePassword; @@ -186,7 +194,7 @@ define([ UI.confirm(changePwConfirm, function (yes) { if (!yes) { return; } sframeChan.query("Q_PAD_PASSWORD_CHANGE", { - href: data.href, + href: data.href || data.roHref, password: $(newPassword).find('input').val() }, function (err, data) { if (err || data.error) { @@ -195,11 +203,11 @@ define([ UI.findOKButton().click(); if (data.warning) { return void UI.alert(Messages.properties_passwordWarning, function () { - common.gotoURL(hasPassword ? undefined : data.href); + common.gotoURL(hasPassword ? undefined : (data.href || data.roHref)); }, {force: true}); } return void UI.alert(Messages.properties_passwordSuccess, function () { - common.gotoURL(hasPassword ? undefined : data.href); + common.gotoURL(hasPassword ? undefined : (data.href || data.roHref)); }, {force: true}); }); }); diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 589ec0292..afd177577 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -613,7 +613,7 @@ define([ if (!parsed.hash) { return void cb({ error: 'EINVAL_HREF' }); } var warning = false; - var newHash; + var newHash, newRoHref; var oldChannel; if (parsed.hashData.password) { newHash = parsed.hash; @@ -678,6 +678,11 @@ define([ common.setPadAttribute('channel', secret.channel, waitFor(function (err) { if (err) { warning = true; } }), href); + var viewHash = Hash.getViewHashFromKeys(secret); + newRoHref = '/' + parsed.type + '/#' + viewHash; + common.setPadAttribute('roHref', newRoHref, waitFor(function (err) { + if (err) { warning = true; } + }), href); if (parsed.hashData.password) { return; } // same hash common.setPadAttribute('href', newHref, waitFor(function (err) { @@ -687,7 +692,8 @@ define([ cb({ warning: warning, hash: newHash, - href: newHref + href: newHref, + roHref: newRoHref }); }); }; diff --git a/www/drive/inner.js b/www/drive/inner.js index d072992d6..9e538b1f4 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -666,10 +666,10 @@ define([ var openFile = function (el, href) { if (!href) { var data = filesOp.getFileData(el); - if (!data || !data.href) { + if (!data || (!data.href && !data.roHref)) { return void logError("Missing data for the file", el, data); } - href = data.href; + href = data.href || data.roHref; } window.open(APP.origin + href); }; @@ -1271,9 +1271,10 @@ define([ if (!filesOp.isFile(element)) { return; } var data = filesOp.getFileData(element); + var href = data.href || data.roHref; if (!data) { return void logError("No data for the file", element); } - var hrefData = Hash.parsePadUrl(data.href); + var hrefData = Hash.parsePadUrl(href); if (hrefData.type) { $span.addClass('cp-border-color-'+hrefData.type); } @@ -1305,7 +1306,7 @@ define([ $span.attr('title', name); var type = Messages.type[hrefData.type] || hrefData.type; - common.displayThumbnail(data.href, data.channel, data.password, $span, function ($thumb) { + common.displayThumbnail(href || data.roHref, data.channel, data.password, $span, function ($thumb) { // Called only if the thumbnail exists // Remove the .hide() added by displayThumnail() because it hides the icon in // list mode too @@ -1847,7 +1848,7 @@ define([ var data = filesOp.getFileData(id); if (!data) { return ''; } if (prop === 'type') { - var hrefData = Hash.parsePadUrl(data.href); + var hrefData = Hash.parsePadUrl(data.href || data.roHref); return hrefData.type; } if (prop === 'atime' || prop === 'ctime') { @@ -1882,7 +1883,7 @@ define([ }; } if (prop === 'type') { - var hrefData = Hash.parsePadUrl(e.href); + var hrefData = Hash.parsePadUrl(e.href || e.roHref); return hrefData.type; } if (prop === 'atime' || prop === 'ctime') { @@ -2690,10 +2691,12 @@ define([ return $div.html(); }; + /* XXX var getReadOnlyUrl = APP.getRO = function (id) { if (!filesOp.isFile(id)) { return; } var data = filesOp.getFileData(id); if (!data) { return; } + if (data.roHref) { return data.roHref; } var parsed = Hash.parsePadUrl(data.href); if (parsed.hashData.type !== "pad") { return; } var i = data.href.indexOf('#') + 1; @@ -2702,7 +2705,7 @@ define([ if (!hrefsecret.keys) { return; } var viewHash = Hash.getViewHashFromKeys(hrefsecret); return base + viewHash; - }; + };*/ // Disable middle click in the context menu to avoid opening /drive/inner.html# in new tabs $(window).click(function (e) { @@ -2717,12 +2720,14 @@ define([ if (!filesOp.isFile(el)) { return void cb('NOT_FILE'); } - var ro = filesOp.isReadOnlyFile(el); + //var ro = filesOp.isReadOnlyFile(el); var base = APP.origin; var data = JSON.parse(JSON.stringify(filesOp.getFileData(el))); if (!data || !data.href) { return void cb('INVALID_FILE'); } data.href = base + data.href; + data.roHref = base + data.roHref; + /* XXX var roUrl; if (ro) { data.roHref = data.href; @@ -2731,6 +2736,7 @@ define([ roUrl = getReadOnlyUrl(el); if (roUrl) { data.roHref = base + roUrl; } } + */ UIElements.getProperties(common, data, cb); }; @@ -2806,8 +2812,9 @@ define([ var el = filesOp.find(p.path); if (filesOp.isPathIn(p.path, [FILES_DATA])) { el = el.href; } if (!el || filesOp.isFolder(el)) { return; } - var roUrl = getReadOnlyUrl(el); - openFile(null, roUrl); + // var roUrl = getReadOnlyUrl(el); + openFile(el); + //, roUrl); XXX }); } else if ($(this).hasClass('cp-app-drive-context-newfolder')) { @@ -2847,7 +2854,7 @@ define([ el = filesOp.find(paths[0].path); var data = filesOp.getFileData(el); if (!data) { return void console.error("Expected to find a file"); } - var href = data.href; + var href = data.href || data.roHref; common.updateTags(href); } else if ($(this).hasClass("cp-app-drive-context-empty")) { From 0f9a71686e338dc0d70dda0bd9f5b27dbe102abd Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 28 Jun 2018 18:16:34 +0200 Subject: [PATCH 03/27] Add support for read-only href stored in filesData --- www/common/common-hash.js | 12 ++++++++- www/common/mergeDrive.js | 23 ++++++++-------- www/common/outer/async-store.js | 24 ++++++++++++----- www/common/outer/userObject.js | 48 ++++++++++++++++++++------------- www/common/userObject.js | 8 ++++-- 5 files changed, 76 insertions(+), 39 deletions(-) diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 4b0c2c607..d5066b757 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -398,10 +398,16 @@ Version 1 Hash.findWeaker = function (href, channel, recents) { var parsed = parsePadUrl(href); if (!parsed.hash) { return false; } + // We can't have a weaker hash if we're already in view mode + if (parsed.hashData && parsed.hashData.mode === 'view') { return; } var weaker; Object.keys(recents).some(function (id) { var pad = recents[id]; - var p = parsePadUrl(pad.href); + if (pad.href || !pad.roHref) { + // This pad has an edit link, so it can't be weaker + return; + } + var p = parsePadUrl(pad.roHref); if (p.type !== parsed.type) { return; } // Not the same type if (p.hash === parsed.hash) { return; } // Same hash, not stronger if (channel !== pad.channel) { return; } // Not the same channel @@ -430,6 +436,10 @@ Version 1 var stronger; Object.keys(recents).some(function (id) { var pad = recents[id]; + if (!pad.href) { + // This pad doesn't have an edit link, so it can't be stronger + return; + } var p = parsePadUrl(pad.href); if (p.type !== parsed.type) { return; } // Not the same type if (p.hash === parsed.hash) { return; } // Same hash, not stronger diff --git a/www/common/mergeDrive.js b/www/common/mergeDrive.js index ea4c6edc7..bcc30666e 100644 --- a/www/common/mergeDrive.js +++ b/www/common/mergeDrive.js @@ -115,23 +115,24 @@ define([ var newRecentPads = proxy.drive[newFo.FILES_DATA]; var oldFiles = oldFo.getFiles([newFo.FILES_DATA]); var newHrefs = Object.keys(newRecentPads).map(function (id) { - return newRecentPads[id].href; + return newRecentPads[id].href || newRecentPads[id].roHref; }); oldFiles.forEach(function (id) { - var href = oldRecentPads[id].href; + var href = oldRecentPads[id].href || oldRecentPads[id].roHref; // Do not migrate a pad if we already have it, it would create a duplicate in the drive if (newHrefs.indexOf(href) !== -1) { return; } // If we have a stronger version, do not add the current href - if (Hash.findStronger(href, oldRecentPads[id].channel, newRecentPads)) { return; } + // If the current href is read-only, don't check, we won't have a stronger + if (isRo && Hash.findStronger(href, oldRecentPads[id].channel, newRecentPads)) { return; } // If we have a weaker version, replace the href by the new one - // NOTE: if that weaker version is in the trash, the strong one will be put in unsorted - var weaker = Hash.findWeaker(href, oldRecentPads[id].channel, newRecentPads); - if (weaker) { - // Update RECENTPADS - weaker.href = href; - // Update the file in the drive - newFo.replace(weaker.href, href); - return; + // If the current href is an edit link, don't check, we won't have a weaker + if (!isRo) { + var weaker = Hash.findWeaker(href, oldRecentPads[id].channel, newRecentPads); + if (weaker) { + // Update RECENTPADS + weaker.href = href; + return; + } } // Here it means we have a new href, so we should add it to the drive at its old location var paths = oldFo.findFile(id); diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 52c0efb9c..3837b338a 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -426,10 +426,11 @@ define([ cb(JSON.parse(JSON.stringify(metadata))); }; - var makePad = function (href, title) { + var makePad = function (href, roHref, title) { var now = +new Date(); return { href: href, + roHref: roHref, atime: now, ctime: now, title: title || Hash.getDefaultName(Hash.parsePadUrl(href)), @@ -437,8 +438,13 @@ define([ }; Store.addPad = function (clientId, data, cb) { - if (!data.href) { return void cb({error:'NO_HREF'}); } - var pad = makePad(data.href, data.title); + if (!data.href && !data.roHref) { return void cb({error:'NO_HREF'}); } + if (!data.roHref) { + var parsed = Hash.parsePadUrl(data.href); + var secret = Hash.getSecrets(parsed.type, parsed.hash, data.password); + data.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret); + } + var pad = makePad(data.href, data.roHref, data.title); if (data.owners) { pad.owners = data.owners; } if (data.expire) { pad.expire = data.expire; } if (data.password) { pad.password = data.password; } @@ -736,9 +742,9 @@ define([ // Edit > Edit (present) > View > View (present) for (var id in allPads) { var pad = allPads[id]; - if (!pad.href) { continue; } + if (!pad.href || !pad.roHref) { continue; } - var p2 = Hash.parsePadUrl(pad.href); + var p2 = Hash.parsePadUrl(pad.href || pad.roHref); var h2 = p2.hashData; // Different types, proceed to the next one @@ -789,8 +795,14 @@ define([ // Add the pad if it does not exist in our drive if (!contains) { + var roHref; + if (h.mode === "view") { + roHref = href; + href = undefined; + } Store.addPad(clientId, { href: href, + roHref: roHref, channel: channel, title: title, owners: owners, @@ -827,7 +839,7 @@ define([ }; store.userObject.getFiles(where).forEach(function (id) { var data = store.userObject.getFileData(id); - var parsed = Hash.parsePadUrl(data.href); + 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)) { diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index 31e70a3bb..630f8aae1 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -50,19 +50,6 @@ define([ var data = exp.getFileData(id); cb(null, clone(data[attr])); }; - var removePadAttribute = exp.removePadAttribute = function (f) { - if (typeof(f) !== 'string') { - console.error("Can't find pad attribute for an undefined pad"); - return; - } - Object.keys(files).forEach(function (key) { - var hash = f.indexOf('#') !== -1 ? f.slice(f.indexOf('#') + 1) : null; - if (hash && key.indexOf(hash) === 0) { - exp.debug("Deleting pad attribute in the realtime object"); - delete files[key]; - } - }); - }; exp.pushData = function (data, cb) { if (typeof cb !== "function") { cb = function () {}; } @@ -145,12 +132,14 @@ define([ if (!loggedIn && !config.testMode) { allFilesPaths.forEach(function (path) { + var id = path[1]; + /* XXX var el = exp.find(path); if (!el) { return; } var id = exp.getIdFromHref(el.href); + */ if (!id) { return; } spliceFileData(id); - removePadAttribute(el.href); }); return; } @@ -259,7 +248,6 @@ define([ if (!id) { return; } if (!loggedIn && !config.testMode) { // delete permanently - exp.removePadAttribute(href); spliceFileData(id); return; } @@ -268,6 +256,7 @@ define([ }; // REPLACE + /* XXX exp.replace = function (o, n) { var idO = exp.getIdFromHref(o); if (!idO || !exp.isFile(idO)) { return; } @@ -275,7 +264,8 @@ define([ if (!data) { return; } data.href = n; }; - // If all the occurences of an href are in the trash, remvoe them and add the file in root. + */ + // If all the occurences of an href are in the trash, remove them and add the file in root. // This is use with setPadTitle when we open a stronger version of a deleted pad exp.restoreHref = function (href) { var idO = exp.getIdFromHref(href); @@ -563,13 +553,15 @@ define([ continue; } // Clean missing href - if (!el.href) { + if (!el.href && !el.roHref) { debug("Removing an element in filesData with a missing href.", el); toClean.push(id); continue; } - var parsed = Hash.parsePadUrl(el.href); + var parsed = Hash.parsePadUrl(el.href || el.roHref); + var secret; + // Clean invalid hash if (!parsed.hash) { debug("Removing an element in filesData with a invalid href.", el); @@ -583,6 +575,22 @@ define([ continue; } + // If we have an edit link, check the view link + if (el.href) { + var fixRo = function () { + secret = Hash.getSecrets(parsed.type, parsed.hash, el.password); + el.roHref = '/' + parsed.type + '/#' + Hash.getViewHasFromKeys(secret); + }; + if (!el.roHref) { + fixRo(); + } else { + var parsed2 = Hash.parsePadUrl(el.roHref); + if (!parsed2.hash || !parsed2.type) { + fixRo(); + } + } + } + // Fix href if (/^https*:\/\//.test(el.href)) { el.href = Hash.getRelativeHref(el.href); } // Fix creation time @@ -592,7 +600,9 @@ define([ // Fix channel if (!el.channel) { try { - var secret = Hash.getSecrets(parsed.type, parsed.hash, el.password); + if (!secret) { + secret = Hash.getSecrets(parsed.type, parsed.hash, el.password); + } el.channel = secret.channel; console.log('Adding missing channel in filesData ', el.channel); } catch (e) { diff --git a/www/common/userObject.js b/www/common/userObject.js index fb39eb484..4d8a6a0a2 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -78,11 +78,14 @@ define([ exp.isReadOnlyFile = function (element) { if (!isFile(element)) { return false; } var data = exp.getFileData(element); + return Boolean(data.roHref && !data.href); + /* XXX var parsed = Hash.parsePadUrl(data.href); if (!parsed) { return false; } var pHash = parsed.hashData; if (!pHash || pHash.type !== "pad") { return; } return pHash && pHash.mode === 'view'; + */ }; var isFolder = exp.isFolder = function (element) { @@ -139,7 +142,7 @@ define([ var getTitle = exp.getTitle = function (file, type) { if (workgroup) { debug("No titles in workgroups"); return; } var data = getFileData(file); - if (!file || !data || !data.href) { + if (!file || !data || !(data.href || data.roHref)) { error("getTitle called with a non-existing file id: ", file, data); return; } @@ -288,7 +291,8 @@ define([ var getIdFromHref = exp.getIdFromHref = function (href) { var result; getFiles([FILES_DATA]).some(function (id) { - if (files[FILES_DATA][id].href === href) { + if (files[FILES_DATA][id].href === href || + files[FILES_DATA][id].roHref === href) { result = id; return true; } From 425ac8ea57bb30a9c502b69494c50ef105f97f22 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 29 Jun 2018 18:16:04 +0200 Subject: [PATCH 04/27] Migration for read-only links + fix issues with read-only pads --- www/common/common-interface.js | 2 +- www/common/common-ui-elements.js | 24 ++------------ www/common/mergeDrive.js | 1 + www/common/migrate-user-object.js | 52 +++++++++++++++++++++++++++++-- www/common/outer/async-store.js | 10 +++--- www/common/outer/userObject.js | 29 +++++------------ www/common/userObject.js | 7 ----- www/drive/inner.js | 50 ++++++++++------------------- www/drive/tests.js | 8 ++--- 9 files changed, 85 insertions(+), 98 deletions(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 974490796..5e5c75176 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -665,7 +665,7 @@ define([ // Update the current state loading.driveState = data.state; data.progress = data.progress || 100; - data.msg = Messages['loading_drive_'+data.state] || ''; + data.msg = Messages['loading_drive_'+ Math.floor(data.state)] || ''; $progress.html(data.msg); if (data.progress) { $progress.append(h('div.cp-loading-progress-bar', [ diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index b35bebdfd..3d057ed04 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -74,32 +74,12 @@ define([ })); }).nThen(function (waitFor) { var base = common.getMetadataMgr().getPrivateData().origin; - /* XXX - common.getPadAttribute('href', waitFor(function (err, val) { - var base = common.getMetadataMgr().getPrivateData().origin; - - var parsed = Hash.parsePadUrl(val); - if (parsed.hashData.mode === "view") { - data.roHref = base + val; - return; - } - - // We're not in a read-only pad - data.href = base + val; - - // Get Read-only href - if (parsed.hashData.type !== "pad") { return; } - var i = data.href.indexOf('#') + 1; - var hBase = data.href.slice(0, i); - var hrefsecret = Hash.getSecrets(parsed.type, parsed.hash, data.password); - if (!hrefsecret.keys) { return; } - var viewHash = Hash.getViewHashFromKeys(hrefsecret); - data.roHref = hBase + viewHash; - }));*/ 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) { diff --git a/www/common/mergeDrive.js b/www/common/mergeDrive.js index bcc30666e..020861481 100644 --- a/www/common/mergeDrive.js +++ b/www/common/mergeDrive.js @@ -119,6 +119,7 @@ define([ }); oldFiles.forEach(function (id) { var href = oldRecentPads[id].href || oldRecentPads[id].roHref; + var isRo = href === oldRecentPads[id].roHref; // Do not migrate a pad if we already have it, it would create a duplicate in the drive if (newHrefs.indexOf(href) !== -1) { return; } // If we have a stronger version, do not add the current href diff --git a/www/common/migrate-user-object.js b/www/common/migrate-user-object.js index fb69fb20b..2f1ea5e88 100644 --- a/www/common/migrate-user-object.js +++ b/www/common/migrate-user-object.js @@ -123,12 +123,58 @@ define([ })); }); }); - n.nThen(waitFor()); + n.nThen(waitFor(function () { + Feedback.send('Migrate-6', true); + userObject.version = version = 6; + })); }; if (version < 6) { addChannelId(); - Feedback.send('Migrate-6', true); - userObject.version = version = 6; + } + }).nThen(function (waitFor) { + var addRoHref = function () { + var data = userObject.drive.filesData; + var el, parsed; + var n = nThen(function () {}); + var padsLength = Object.keys(data).length; + Object.keys(data).forEach(function (k, i) { + n = n.nThen(function (w) { + setTimeout(w(function () { + el = data[k]; + if (!el.href || (el.roHref && false)) { + // Already migrated + return void progress(7, Math.round(100*i/padsLength)); + } + parsed = Hash.parsePadUrl(el.href); + if (parsed.hashData.type !== "pad") { + // No read-only mode for files + return void progress(7, Math.round(100*i/padsLength)); + } + if (parsed.hashData.mode === "view") { + // This is a read-only pad in our drive + el.roHref = el.href; + delete el.href; + console.log('Move href to roHref in filesData ', el.roHref); + } else { + var secret = Hash.getSecrets(parsed.type, parsed.hash, el.password); + var hash = Hash.getViewHashFromKeys(secret); + if (hash) { + // Version 0 won't have a view hash available + el.roHref = '/' + parsed.type + '/#' + hash; + console.log('Adding missing roHref in filesData ', el.href); + } + } + progress(6, Math.round(100*i/padsLength)); + })); + }); + }); + n.nThen(waitFor(function () { + Feedback.send('Migrate-7', true); + userObject.version = version = 7; + })); + }; + if (version < 7) { + addRoHref(); } /*}).nThen(function (waitFor) { // Test progress bar in the loading screen diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 3837b338a..7981cec53 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -441,8 +441,10 @@ define([ if (!data.href && !data.roHref) { return void cb({error:'NO_HREF'}); } if (!data.roHref) { var parsed = Hash.parsePadUrl(data.href); - var secret = Hash.getSecrets(parsed.type, parsed.hash, data.password); - data.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret); + if (parsed.hashData.type === "pad") { + var secret = Hash.getSecrets(parsed.type, parsed.hash, data.password); + data.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret); + } } var pad = makePad(data.href, data.roHref, data.title); if (data.owners) { pad.owners = data.owners; } @@ -742,7 +744,7 @@ define([ // Edit > Edit (present) > View > View (present) for (var id in allPads) { var pad = allPads[id]; - if (!pad.href || !pad.roHref) { continue; } + if (!pad.href && !pad.roHref) { continue; } var p2 = Hash.parsePadUrl(pad.href || pad.roHref); var h2 = p2.hashData; @@ -1364,7 +1366,7 @@ define([ }).nThen(function (waitFor) { Migrate(proxy, waitFor(), function (version, progress) { postMessage(clientId, 'LOADING_DRIVE', { - state: 2, + state: (2 + (version / 10)), progress: progress }); }); diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index 630f8aae1..4bc0019c3 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -133,11 +133,6 @@ define([ if (!loggedIn && !config.testMode) { allFilesPaths.forEach(function (path) { var id = path[1]; - /* XXX - var el = exp.find(path); - if (!el) { return; } - var id = exp.getIdFromHref(el.href); - */ if (!id) { return; } spliceFileData(id); }); @@ -256,15 +251,6 @@ define([ }; // REPLACE - /* XXX - exp.replace = function (o, n) { - var idO = exp.getIdFromHref(o); - if (!idO || !exp.isFile(idO)) { return; } - var data = exp.getFileData(idO); - if (!data) { return; } - data.href = n; - }; - */ // If all the occurences of an href are in the trash, remove them and add the file in root. // This is use with setPadTitle when we open a stronger version of a deleted pad exp.restoreHref = function (href) { @@ -576,17 +562,18 @@ define([ } // If we have an edit link, check the view link - if (el.href) { - var fixRo = function () { + if (el.href && parsed.hashData.type === "pad") { + if (parsed.hashData.mode === "view") { + el.roHref = el.href; + delete el.href; + } else if (!el.roHref) { secret = Hash.getSecrets(parsed.type, parsed.hash, el.password); - el.roHref = '/' + parsed.type + '/#' + Hash.getViewHasFromKeys(secret); - }; - if (!el.roHref) { - fixRo(); + el.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret); } else { var parsed2 = Hash.parsePadUrl(el.roHref); if (!parsed2.hash || !parsed2.type) { - fixRo(); + secret = Hash.getSecrets(parsed.type, parsed.hash, el.password); + el.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret); } } } diff --git a/www/common/userObject.js b/www/common/userObject.js index 4d8a6a0a2..e19614363 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -79,13 +79,6 @@ define([ if (!isFile(element)) { return false; } var data = exp.getFileData(element); return Boolean(data.roHref && !data.href); - /* XXX - var parsed = Hash.parsePadUrl(data.href); - if (!parsed) { return false; } - var pHash = parsed.hashData; - if (!pHash || pHash.type !== "pad") { return; } - return pHash && pHash.mode === 'view'; - */ }; var isFolder = exp.isFolder = function (element) { diff --git a/www/drive/inner.js b/www/drive/inner.js index 9e538b1f4..fb6f56bf4 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -2691,22 +2691,6 @@ define([ return $div.html(); }; - /* XXX - var getReadOnlyUrl = APP.getRO = function (id) { - if (!filesOp.isFile(id)) { return; } - var data = filesOp.getFileData(id); - if (!data) { return; } - if (data.roHref) { return data.roHref; } - var parsed = Hash.parsePadUrl(data.href); - if (parsed.hashData.type !== "pad") { return; } - var i = data.href.indexOf('#') + 1; - var base = data.href.slice(0, i); - var hrefsecret = Hash.getSecrets(parsed.type, parsed.hash, data.password); - if (!hrefsecret.keys) { return; } - var viewHash = Hash.getViewHashFromKeys(hrefsecret); - return base + viewHash; - };*/ - // Disable middle click in the context menu to avoid opening /drive/inner.html# in new tabs $(window).click(function (e) { if (!e.target || !$(e.target).parents('.cp-dropdown-content').length) { return; } @@ -2723,20 +2707,14 @@ define([ //var ro = filesOp.isReadOnlyFile(el); var base = APP.origin; var data = JSON.parse(JSON.stringify(filesOp.getFileData(el))); - if (!data || !data.href) { return void cb('INVALID_FILE'); } - data.href = base + data.href; - data.roHref = base + data.roHref; - - /* XXX - var roUrl; - if (ro) { - data.roHref = data.href; - delete data.href; - } else { - roUrl = getReadOnlyUrl(el); - if (roUrl) { data.roHref = base + roUrl; } + if (!data || !(data.href || data.roHref)) { return void cb('INVALID_FILE'); } + + if (data.href) { + data.href = base + data.href; + } + if (data.roHref) { + data.roHref = base + data.roHref; } - */ UIElements.getProperties(common, data, cb); }; @@ -2810,11 +2788,15 @@ define([ else if ($(this).hasClass('cp-app-drive-context-openro')) { paths.forEach(function (p) { var el = filesOp.find(p.path); - if (filesOp.isPathIn(p.path, [FILES_DATA])) { el = el.href; } - if (!el || filesOp.isFolder(el)) { return; } - // var roUrl = getReadOnlyUrl(el); - openFile(el); - //, roUrl); XXX + var href; + if (filesOp.isPathIn(p.path, [FILES_DATA])) { + href = el.roHref; + } else { + if (!el || filesOp.isFolder(el)) { return; } + var data = filesOp.getFileData(el); + href = data.roHref; + } + openFile(null, href); }); } else if ($(this).hasClass('cp-app-drive-context-newfolder')) { diff --git a/www/drive/tests.js b/www/drive/tests.js index a5fec1145..67d986d86 100644 --- a/www/drive/tests.js +++ b/www/drive/tests.js @@ -237,7 +237,8 @@ define([ && typeof files.template[0] === "number" && typeof files.filesData[files.template[0]] === "object" && !files.filesData[files.template[0]].filename - && files.filesData[files.template[0]].href === href3 + && !files.filesData[files.template[0]].href + && files.filesData[files.template[0]].roHref === href3 && typeof fileId2 === "number" && typeof files.filesData[fileId2] === "object" && files.filesData[fileId2].filename === "Trash" @@ -392,11 +393,6 @@ define([ console.log("DRIVE operations: rename"); return cb(); } - fo.replace(href1, href2); - if (fo.getFileData(id1).href !== href2) { - console.log("DRIVE operations: replace"); - return cb(); - } cb(true); }, "DRIVE operations"); From a25a72b5dbcbd45023c97d69f3de71eccffcf75b Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 5 Jul 2018 10:37:06 +0200 Subject: [PATCH 05/27] Add a proxy manager to handle operations between shared folders --- www/common/outer/async-store.js | 138 ++++--------- www/common/outer/userObject.js | 83 ++++++-- www/common/proxy-manager.js | 338 ++++++++++++++++++++++++++++++++ www/common/userObject.js | 17 +- www/drive/inner.js | 43 +--- 5 files changed, 472 insertions(+), 147 deletions(-) create mode 100644 www/common/proxy-manager.js diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 35d3dba67..b3601813a 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1,6 +1,7 @@ define([ 'json.sortify', '/common/userObject.js', + '/common/proxy-manager.js', '/common/migrate-user-object.js', '/common/common-hash.js', '/common/common-util.js', @@ -18,7 +19,7 @@ define([ '/bower_components/chainpad-listmap/chainpad-listmap.js', '/bower_components/nthen/index.js', '/bower_components/saferphore/index.js', -], function (Sortify, UserObject, Migrate, Hash, Util, Constants, Feedback, Realtime, Messaging, Messenger, +], function (Sortify, UserObject, ProxyManager, Migrate, Hash, Util, Constants, Feedback, Realtime, Messaging, Messenger, CpNfWorker, NetConfig, AppConfig, Crypto, ChainPad, Listmap, nThen, Saferphore) { var Store = {}; @@ -78,17 +79,10 @@ define([ if (!userChannel) { return null; } // Get the list of pads' channel ID in your drive - // This list is filtered so that it doesn't include pad owned by other users (you should - // not pin these pads) - var files = store.userObject.getFiles([store.userObject.FILES_DATA]); + // 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 = files.map(function (id) { - var d = store.userObject.getFileData(id); - if (d.owners && d.owners.length && edPublic && - d.owners.indexOf(edPublic) === -1) { return; } - return d.channel; - }) - .filter(function (x) { return x; }); + var list = store.manager.getPinList(edPublic); // Get the avatar var profile = store.proxy.profile; @@ -451,10 +445,16 @@ define([ if (data.expire) { pad.expire = data.expire; } if (data.password) { pad.password = data.password; } if (data.channel) { pad.channel = data.channel; } - store.userObject.pushData(pad, function (e, id) { + 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) { if (e) { return void cb({error: "Error while adding a template:"+ e}); } - var path = data.path || ['root']; - store.userObject.add(id, path); + uo.add(id, path); sendDriveEvent('DRIVE_CHANGE', { path: ['drive', UserObject.FILES_DATA] }, clientId); @@ -733,67 +733,28 @@ define([ expire = +channelData.data.expire || undefined; } - var allPads = Util.find(store.proxy, ['drive', 'filesData']) || {}; - var isStronger; - - // If we don't find the new channel in our existing pads, we'll have to add the pads - // to filesData - var contains; - - // Update all pads that use the same channel but with a weaker hash - // Edit > Edit (present) > View > View (present) - for (var id in allPads) { - var pad = allPads[id]; - if (!pad.href && !pad.roHref) { continue; } - - var p2 = Hash.parsePadUrl(pad.href || pad.roHref); - var h2 = p2.hashData; - - // Different types, proceed to the next one - // No hash data: corrupted pad? - if (p.type !== p2.type || !h2) { continue; } - // Different channel: continue - if (pad.channel !== channel) { continue; } - - var shouldUpdate = p.hash.replace(/\/$/, '') === p2.hash.replace(/\/$/, ''); - - // If the hash is different but represents the same channel, check if weaker or stronger - if (!shouldUpdate && h.version !== 0) { - // We had view & now we have edit, update - if (h2.mode === 'view' && h.mode === 'edit') { shouldUpdate = true; } - // Same mode and we had present URL, update - else if (h.mode === h2.mode && h2.present) { shouldUpdate = true; } - // If we're here it means we have a weaker URL: - // update the date but keep the existing hash - else { - pad.atime = +new Date(); - contains = true; - continue; - } + var datas = store.manager.findChannel(channel); + var contains = datas.length !== 0; + datas.forEach(function (obj) { + var pad = obj.data; + pad.atime = +new Date(); + pad.title = title; + if (owners || h.type !== "file") { + // OWNED_FILES + // Never remove owner for files + pad.owners = owners; } - - if (shouldUpdate) { - contains = true; - pad.atime = +new Date(); - pad.title = title; - if (owners || h.type !== "file") { - // OWNED_FILES - // Never remove owner for files - pad.owners = owners; - } - pad.expire = expire; - - // If the href is different, it means we have a stronger one - if (href !== pad.href) { isStronger = true; } - pad.href = href; + pad.expire = expire; + if (h.mode === 'view') { return; } + + // If we only have rohref, it means we have a stronger href + if (!pad.href) { + // If we have a stronger url, remove the possible weaker from the trash. + // If all of the weaker ones were in the trash, add the stronger to ROOT + obj.userObject.restoreHref(href); } - } - - if (isStronger) { - // If we have a stronger url, remove the possible weaker from the trash. - // If all of the weaker ones were in the trash, add the stronger to ROOT - store.userObject.restoreHref(href); - } + pad.href = href; + }); // Add the pad if it does not exist in our drive if (!contains) { @@ -1215,11 +1176,11 @@ define([ }; var rt = Listmap.create(listmapConfig); store.sharedFolders[id] = rt; + store.manager.addProxy(rt.proxy); return rt; }; Store.addSharedFolder = function (clientId, data, cb) { var path = data.path; - var href = data.href; var id; nThen(function (waitFor) { // TODO @@ -1232,13 +1193,13 @@ define([ } id = folderId; })); - nThen(function (waitFor) { + }).nThen(function (waitFor) { // 2a. add the shared folder to the path in our drive store.userObject.add(id, path); onSync(waitFor()); // 2b. load the proxy - var rt = loadSharedFolder(folderId, data); + var rt = loadSharedFolder(id, data); rt.on('ready', waitFor(function () { // TODO // "fixFiles" @@ -1267,22 +1228,7 @@ define([ }); cb(data2); }; - switch (cmdData.cmd) { - case 'move': - store.userObject.move(data.paths, data.newPath, cb2); break; - case 'restore': - store.userObject.restore(data.path, cb2); break; - case 'addFolder': - store.userObject.addFolder(data.path, data.name, cb2); break; - case 'delete': - store.userObject.delete(data.paths, cb2, data.nocheck, data.isOwnPadRemoved); break; - case 'emptyTrash': - store.userObject.emptyTrash(cb2); break; - case 'rename': - store.userObject.rename(data.path, data.newName, cb2); break; - default: - cb(); - } + store.manager.command(cmdData, cb2); }; // Clients management @@ -1416,7 +1362,7 @@ define([ var onReady = function (clientId, returned, cb) { var proxy = store.proxy; - var userObject = store.userObject = UserObject.init(proxy.drive, { + var manager = store.manager = ProxyManager.create(proxy.drive, proxy.edPublic, { pinPads: function (data, cb) { Store.pinPads(null, data, cb); }, unpinPads: function (data, cb) { Store.unpinPads(null, data, cb); }, removeOwnedChannel: function (data, cb) { Store.removeOwnedChannel(null, data, cb); }, @@ -1427,6 +1373,7 @@ define([ sendDriveEvent("DRIVE_LOG", msg); } }); + var userObject = store.userObject = manager.user.userObject; nThen(function (waitFor) { postMessage(clientId, 'LOADING_DRIVE', { state: 2 @@ -1439,12 +1386,13 @@ define([ progress: progress }); }); - }).nThen(function () { + }).nThen(function (waitFor) { postMessage(clientId, 'LOADING_DRIVE', { state: 3 }); userObject.fixFiles(); - + loadSharedFolders(waitFor); + }).nThen(function () { var requestLogin = function () { broadcast([], "REQUEST_LOGIN"); }; diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index 4bc0019c3..94363a2d2 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -22,7 +22,7 @@ define([ console.error("removeOwnedChannel was not provided"); }; var loggedIn = config.loggedIn; - var workgroup = config.workgroup; + var sharedFolder = config.sharedFolder; var edPublic = config.edPublic; var ROOT = exp.ROOT; @@ -31,6 +31,7 @@ define([ var UNSORTED = exp.UNSORTED; var TRASH = exp.TRASH; var TEMPLATE = exp.TEMPLATE; + var SHARED_FOLDERS = exp.SHARED_FOLDERS; var debug = exp.debug; @@ -76,9 +77,8 @@ define([ // Find files in FILES_DATA that are not anymore in the drive, and remove them from // FILES_DATA. If there are owned pads, remove them from server too, unless the flag tells // us they're already removed - exp.checkDeletedFiles = function (isOwnPadRemoved) { - // Nothing in FILES_DATA for workgroups - if (workgroup || (!loggedIn && !config.testMode)) { return; } + exp.checkDeletedFiles = function (isOwnPadRemoved, noUnpin) { + if (!loggedIn && !config.testMode) { return; } var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]); var toClean = []; @@ -107,6 +107,7 @@ define([ } }); if (!toClean.length) { return; } + if (noUnpin) { return; } unpinPads(toClean, function (response) { if (response && response.error) { return console.error(response.error); } // console.error(response); @@ -124,7 +125,7 @@ define([ files[TRASH][obj.name].splice(idx, 1); }); }; - exp.deleteMultiplePermanently = function (paths, nocheck, isOwnPadRemoved) { + exp.deleteMultiplePermanently = function (paths, nocheck, isOwnPadRemoved, noUnpin) { var hrefPaths = paths.filter(function(x) { return exp.isPathIn(x, ['hrefArray']); }); var rootPaths = paths.filter(function(x) { return exp.isPathIn(x, [ROOT]); }); var trashPaths = paths.filter(function(x) { return exp.isPathIn(x, [TRASH]); }); @@ -176,11 +177,66 @@ define([ deleteMultipleTrashRoot(trashRoot); // In some cases, we want to remove pads from a location without removing them from - // OLD_FILES_DATA (replaceHref) - if (!nocheck) { exp.checkDeletedFiles(isOwnPadRemoved); } + // FILES_DATA (replaceHref) + if (!nocheck) { exp.checkDeletedFiles(isOwnPadRemoved, noUnpin); } }; // Move + + // From another drive + exp.copyFromOtherDrive = function (path, element, data) { + // Copy files data + // We have to remove pads that are already in the current proxy to make sure + // we won't create duplicates + + var toRemove = []; + Object.keys(data).forEach(function (id) { + // Find and maybe update existing pads with the same channel id + var d = data[id]; + var found = false; + for (var i in files[FILES_DATA]) { + if (files[FILES_DATA][i].channel === d.channel) { + // Update href? + if (!files[FILES_DATA][i].href) { files[FILES_DATA][i].href = d.href; } + found = true; + break; + } + } + if (found) { + toRemove.push(id); + return; + } + files[FILES_DATA][id] = data[id]; + }); + + // Remove existing pads from the "element" variable + if (exp.isFile(element) && toRemove.indexOf(element) !== -1) { + // XXX display error in the UI + return; + } else if (exp.isFolder(element)) { + var _removeExisting = function (root) { + for (var k in root) { + if (exp.isFile(root[k])) { + if (toRemove.indexOf(root[k]) !== -1) { + // XXX display message in UI + delete root[k]; + } + } else if (exp.isFolder(root[k])) { + _removeExisting(root[k]); + } + } + }; + _removeExisting(element); + } + + + // Copy file or folder + var newParent = exp.find(path); + var newName = exp.getAvailableName(newParent, Hash.createChannelId()); + newParent[newName] = element; + }; + + // From the same drive var pushToTrash = function (name, element, path) { var trash = files[TRASH]; if (typeof(trash[name]) === "undefined") { trash[name] = []; } @@ -449,6 +505,7 @@ define([ } }; var fixTrashRoot = function () { + if (sharedFolder) { return; } if (typeof(files[TRASH]) !== "object") { debug("TRASH was not an object"); files[TRASH] = {}; } var tr = files[TRASH]; var toClean; @@ -492,6 +549,7 @@ define([ } }; var fixTemplate = function () { + if (sharedFolder) { return; } if (!Array.isArray(files[TEMPLATE])) { debug("TEMPLATE was not an array"); files[TEMPLATE] = []; } files[TEMPLATE] = Util.deduplicateString(files[TEMPLATE].slice()); var us = files[TEMPLATE]; @@ -608,6 +666,10 @@ define([ spliceFileData(id); }); }; + var fixSharedFolders = function () { + if (sharedFolder) { return; } + if (typeof(files[SHARED_FOLDERS]) !== "object") { debug("SHARED_FOLDER was not an object"); files[SHARED_FOLDERS] = {}; } + }; var fixDrive = function () { Object.keys(files).forEach(function (key) { @@ -617,10 +679,9 @@ define([ fixRoot(); fixTrashRoot(); - if (!workgroup) { - fixTemplate(); - fixFilesData(); - } + fixTemplate(); + fixFilesData(); + fixSharedFolders(); fixDrive(); if (JSON.stringify(files) !== before) { diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js new file mode 100644 index 000000000..9b774b86d --- /dev/null +++ b/www/common/proxy-manager.js @@ -0,0 +1,338 @@ +define([ + '/common/userObject.js', + '/common/common-util.js', + '/bower_components/nthen/index.js', +], function (UserObject, Util, nThen) { + + + var getConfig = function (Env) { + var cfg = {}; + for (var k in Env.cfg) { cfg[k] = Env[k]; } + return cfg; + }; + + // Add a shared folder to the list + var addProxy = function (Env, id, proxy, leave) { + var cfg = getConfig(); + cfg.sharedFolder = true; + cfg.id = id; + var userObject = UserObject.init(proxy, Env.cfg); + userObject.fixFiles(); + Env.folders[id] = { + proxy: proxy, + userObject: userObject, + leave: leave + }; + return userObject; + }; + + // TODO: Remove a shared folder from the list + var removeProxy = function (Env, id) { + var f = Env.folders[id]; + if (!f) { return; } + f.leave(); + delete Env.folders[id]; + }; + + /* + Paths + */ + + // Transform an absolute path into a path relative to the correct shared folder + var _resolvePath = function (Env, path) { + var res = { + id: null, + userObject: Env.user.userObject, + path: path + }; + if (!Array.isArray(path) || path.length <= 1) { + return res; + } + var current; + var uo = Env.user.userObject; + for (var i=2; i<=path.length; i++) { + current = uo.find(path.slice(0,i)); + if (uo.isSharedFolder(current)) { + res = { + id: current, + userObject: Env.folders[current].userObject, + path: path.slice(i) + }; + break; + } + } + return res; + }; + var _resolvePaths = function (Env, paths) { + var main = []; + var folders = {}; + paths.forEach(function (path) { + var r = _resolvePath(Env, path); + if (r.id) { + if (!folders[r.id]) { + folders[r.id] = [r.path]; + } else { + folders[r.id].push(r.path); + } + } else { + main.push(r.path); + } + }); + return { + main: main, + folders: folders + }; + }; + + // Get a copy of the elements located in the given paths, with their files data + var _getCopyFromPaths = function (paths, userObject) { + var data = []; + paths.forEach(function (path) { + var el = userObject.find(path); + var files = []; + + // Get the files ID from the current path (file or folder) + if (userObject.isFile(el)) { + files.push(el); + } else { + userObject.getFilesRecursively(el, files); + } + + // Remove the shared folder from this list of files ID + files.filter(function (f) { return !userObject.isSharedFolder(f); }); + // Deduplicate + Util.deduplicateString(files); + + // Get the files data associated to these files + var filesData = {}; + files.forEach(function (f) { + filesData[f] = userObject.getFileData(f); + }); + + // TODO RO + // Encrypt or decrypt edit link here + // filesData.forEach(function (d) { d.href = encrypt(d.href); }); + + + data.push({ + el: el, + data: filesData + }); + }); + return data; + }; + + /* + RPC commands + */ + + // Move files or folders in the drive + var _move = function (Env, data, cb) { + var resolved = _resolvePaths(Env, data.paths); + var newResolved = _resolvePath(Env, data.newPath); + + if (!newResolved.userObject.isFolder(newResolved.path)) { return void cb(); } // XXX + + nThen(function (waitFor) { + if (resolved.main.length) { + // Move from the main drive + if (!newResolved.id) { + // Move from the main drive to the main drive + Env.user.userObject.move(resolved.main, newResolved.path, waitFor()); + } else { + // Move from the main drive to a shared folder + + // Copy the elements to the new location + var toCopy = _getCopyFromPaths(resolved.main, Env.user.userObject); + var newUserObject = newResolved.userObject; + newUserObject.copyFromOtherDrive(newResolved.path, toCopy.el, toCopy.data); + + // Filter owned pads so that we won't remove them from our drive + var toRemove = resolved.main.slice(); + toRemove.filter(function (id) { + var owners = Env.user.userObject.getFileData(id).owners; + return !Array.isArray(owners) || owners.indexOf(Env.edPublic) === -1; + }); + + // Remove the elements from the old location (without unpinning) + Env.user.userObject.delete(resolved.main, waitFor(), false, false, true); + } + } + var folderIds = Object.keys(resolved.folders); + if (folderIds.length) { + // Move from a shared folder + folderIds.forEach(function (fIdStr) { + var fId = Number(fIdStr); + var paths = resolved.folders[fId]; + if (newResolved.id === fId) { + // Move to the same shared folder + newResolved.userObject.move(paths, newResolved.path, waitFor()); + } else { + // Move to a different shared folder or to main drive + var uoFrom = Env.folders[fId].userObject; + var uoTo = newResolved.userObject; + + // Copy the elements to the new location + var toCopy = _getCopyFromPaths(paths, uoFrom); + uoTo.copyFromOtherDrive(newResolved.path, toCopy.el, toCopy.data); + + // Remove the elements from the old location (without unpinning) + uoFrom.delete(paths, waitFor(), false, false, true); + } + }); + } + }).nThen(function () { + cb(); + }); + }; + // Restore from the trash (main drive only) + var _restore = function (Env, data, cb) { + var userObject = Env.user.userObject; + data = data || {}; + userObject.restore(data.path, cb); + }; + // Add a folder/subfolder + var _addFolder = function (Env, data, cb) { + data = data || {}; + var resolved = _resolvePath(Env, data.path); + if (!resolved || !resolved.userObject) { return void cb({error: 'E_NOTFOUND'}); } + resolved.userObject.addFolder(resolved.path, data.name, cb); + }; + // Delete permanently some pads or folders + var _delete = function (Env, data, cb) { + data = data || {}; + var resolved = _resolvePaths(Env, data.paths); + if (!resolved.main.length && !Object.keys(resolved.folders).length) { + return void cb({error: 'E_NOTFOUND'}); + } + nThen(function (waitFor)  { + if (resolved.main.length) { + Env.user.userObject.delete(resolved.main, waitFor(), data.nocheck, + data.isOwnPadRemoved); + } + Object.keys(resolved.folders).forEach(function (id) { + Env.folders[id].userObject.delete(resolved.folders[id], waitFor(), data.nocheck, + data.isOwnPadRemoved); + }); + }).nThen(function () { + cb(); + }); + }; + // Empty the trash (main drive only) + var _emptyTrash = function (Env, data, cb) { + Env.user.userObject.emptyTrash(cb); + }; + // Rename files or folders + var _rename = function (Env, data, cb) { + data = data || {}; + var resolved = _resolvePath(Env, data.path); + if (!resolved || !resolved.userObject) { return void cb({error: 'E_NOTFOUND'}); } + resolved.userObject.rename(resolved.path, data.newName, cb); + }; + var onCommand = function (Env, cmdData, cb) { + var cmd = cmdData.cmd; + var data = cmdData.data || {}; + switch (cmd) { + case 'move': + _move(Env, data, cb); break; + //store.userObject.move(data.paths, data.newPath, cb2); break; + case 'restore': + _restore(Env, data, cb); break; + case 'addFolder': + _addFolder(Env, data, cb); break; + case 'delete': + _delete(Env, data, cb); break; + case 'emptyTrash': + _emptyTrash(Env, data, cb); break; + case 'rename': + _rename(Env, data, cb); break; + default: + cb(); + } + }; + + // 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; + }; + + // Get the list of channels that should be pinned + var getPinList = function (Env, edPublic) { + if (!edPublic) { return; } + var toPin = []; + var addChannel = function (userObject) { + 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); + } + }; + }; + + // 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); + + userObjects.forEach(function (uo) { + var files = uo.getFiles([UserObject.FILES_DATA]); + files.forEach(addChannel(uo)); + }); + }; + + var create = function (proxy, edPublic, uoConfig) { + var Env = { + cfg: uoConfig, + edPublic: edPublic, + user: { + proxy: proxy, + userObject: UserObject.init(proxy, uoConfig) + }, + folders: {} + }; + + var callWithEnv = function (f) { + return function () { + [].unshift.call(arguments, Env); + return f.apply(null, arguments); + }; + }; + + return { + addProxy: callWithEnv(addProxy), + removeProxy: callWithEnv(removeProxy), + command: callWithEnv(onCommand), + findChannel: callWithEnv(findChannel), + getPinList: callWithEnv(getPinList), + resolvePath: callWithEnv(_resolvePath), + user: Env.user, + folders: Env.folders + }; + }; + + return { + create: create + }; +}); diff --git a/www/common/userObject.js b/www/common/userObject.js index f44310296..87a1653fa 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -29,6 +29,7 @@ define([ exp.UNSORTED = UNSORTED; exp.TRASH = TRASH; exp.TEMPLATE = TEMPLATE; + exp.SHARED_FOLDERS = SHARED_FOLDERS; // Logging var logging = function () { @@ -42,9 +43,6 @@ define([ console.error.apply(console, arguments); }; - // TODO: workgroup - var workgroup = config.workgroup; - if (pinPads) { // Extend "exp" with methods used only outside of the iframe (requires access to store) OuterFO.init(config, exp, files); @@ -89,6 +87,9 @@ define([ if (!isFolder(element)) { return false; } return Object.keys(element).length === 0; }; + exp.isSharedFolder = function (element) { + return Boolean(files[SHARED_FOLDERS][element]); + }; exp.hasSubfolder = function (element, trashRoot) { if (!isFolder(element)) { return false; } @@ -134,7 +135,6 @@ define([ // Data from filesData var getTitle = exp.getTitle = function (file, type) { - if (workgroup) { debug("No titles in workgroups"); return; } var data = getFileData(file); if (!file || !data || !(data.href || data.roHref)) { error("getTitle called with a non-existing file id: ", file, data); @@ -206,7 +206,8 @@ define([ // GET FILES - var getFilesRecursively = function (root, arr) { + var getFilesRecursively = exp.getFilesRecursively = function (root, arr) { + arr = arr || []; for (var e in root) { if (isFile(root[e])) { if(arr.indexOf(root[e]) === -1) { arr.push(root[e]); } @@ -214,6 +215,7 @@ define([ getFilesRecursively(root[e], arr); } } + return arr; }; var _getFiles = {}; _getFiles['array'] = function (cat) { @@ -552,18 +554,19 @@ define([ // DELETE // Permanently delete multiple files at once using a list of paths // NOTE: We have to be careful when removing elements from arrays (trash root, unsorted or template) - exp.delete = function (paths, cb, nocheck, isOwnPadRemoved) { + exp.delete = function (paths, cb, nocheck, isOwnPadRemoved, noUnpin) { if (sframeChan) { return void sframeChan.query("Q_DRIVE_USEROBJECT", { cmd: "delete", data: { paths: paths, nocheck: nocheck, + noUnpin: noUnpin, isOwnPadRemoved: isOwnPadRemoved } }, cb); } - exp.deleteMultiplePermanently(paths, nocheck, isOwnPadRemoved); + exp.deleteMultiplePermanently(paths, nocheck, isOwnPadRemoved, noUnpin); if (typeof cb === "function") { cb(); } }; exp.emptyTrash = function (cb) { diff --git a/www/drive/inner.js b/www/drive/inner.js index fb6f56bf4..a6fd5c9ec 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -244,27 +244,27 @@ define([ 'tabindex': '-1', 'data-icon': faTags, }, Messages.fc_hashtag)), - h('li', h('a.cp-app-drive-context-newdoc.dropdown-item.cp-app-drive-context-editable.cp-app-drive-context-own', { + h('li', h('a.cp-app-drive-context-newdoc.dropdown-item.cp-app-drive-context-editable', { 'tabindex': '-1', 'data-icon': AppConfig.applicationsIcon.pad, 'data-type': 'pad' }, Messages.button_newpad)), - h('li', h('a.cp-app-drive-context-newdoc.dropdown-item.cp-app-drive-context-editable.cp-app-drive-context-own', { + h('li', h('a.cp-app-drive-context-newdoc.dropdown-item.cp-app-drive-context-editable', { 'tabindex': '-1', 'data-icon': AppConfig.applicationsIcon.code, 'data-type': 'code' }, Messages.button_newcode)), - h('li', h('a.cp-app-drive-context-newdoc.dropdown-item.cp-app-drive-context-editable.cp-app-drive-context-own', { + h('li', h('a.cp-app-drive-context-newdoc.dropdown-item.cp-app-drive-context-editable', { 'tabindex': '-1', 'data-icon': AppConfig.applicationsIcon.slide, 'data-type': 'slide' }, Messages.button_newslide)), - h('li', h('a.cp-app-drive-context-newdoc.dropdown-item.cp-app-drive-context-editable.cp-app-drive-context-own', { + h('li', h('a.cp-app-drive-context-newdoc.dropdown-item.cp-app-drive-context-editable', { 'tabindex': '-1', 'data-icon': AppConfig.applicationsIcon.poll, 'data-type': 'poll' }, Messages.button_newpoll)), - h('li', h('a.cp-app-drive-context-newdoc.dropdown-item.cp-app-drive-context-editable.cp-app-drive-context-own', { + h('li', h('a.cp-app-drive-context-newdoc.dropdown-item.cp-app-drive-context-editable', { 'tabindex': '-1', 'data-icon': AppConfig.applicationsIcon.whiteboard, 'data-type': 'whiteboard' @@ -311,13 +311,6 @@ define([ var edPublic = priv.edPublic; APP.origin = priv.origin; - var isOwnDrive = function () { - return true; // TODO - }; - var isWorkgroup = function () { - return files.workgroup === 1; - }; - config.workgroup = isWorkgroup(); config.loggedIn = APP.loggedIn; config.sframeChan = sframeChan; @@ -357,11 +350,9 @@ define([ } // FILE MANAGER - // _WORKGROUP_ and other people drive : display Documents as main page - var currentPath = APP.currentPath = isOwnDrive() ? getLastOpenedFolder() : [ROOT]; + var currentPath = APP.currentPath = getLastOpenedFolder(); // Categories dislayed in the menu - // _WORKGROUP_ : do not display unsorted var displayedCategories = [ROOT, TRASH, SEARCH, RECENT]; // PCS enabled: display owned pads @@ -371,7 +362,6 @@ define([ // Tags used: display Tags category if (Object.keys(filesOp.getTagsList()).length) { displayedCategories.push(TAGS); } - if (isWorkgroup()) { displayedCategories = [ROOT, TRASH, SEARCH]; } var virtualCategories = [SEARCH, RECENT, OWNED, TAGS]; if (!APP.loggedIn) { @@ -856,7 +846,6 @@ define([ show.forEach(function (className) { var $el = $contextMenu.find('.cp-app-drive-context-' + className); if (!APP.editable && $el.is('.cp-app-drive-context-editable')) { return; } - if (!isOwnDrive && $el.is('.cp-app-drive-context-own')) { return; } if (filter($el, className)) { return; } $el.parent('li').show(); filtered.push('.cp-app-drive-context-' + className); @@ -1212,10 +1201,6 @@ define([ if (movedPaths && movedPaths.length) { moveElements(movedPaths, newPath, null, refresh); } - if (importedElements && importedElements.length) { - // TODO workgroup - //filesOp.importElements(importedElements, newPath, refresh); - } }; var addDragAndDropHandlers = function ($element, path, isFolder, droppable) { @@ -1266,7 +1251,6 @@ define([ addDragAndDropHandlers($content, null, true, true); // In list mode, display metadata from the filesData object - // _WORKGROUP_ : Do not display title, atime and ctime columns since we don't have files data var addFileData = function (element, $span) { if (!filesOp.isFile(element)) { return; } @@ -1323,10 +1307,7 @@ define([ var $cdate = $('', { 'class': 'cp-app-drive-element-ctime cp-app-drive-element-list' }).text(getDate(data.ctime)); - $span.append($type); - if (!isWorkgroup()) { - $span.append($adate).append($cdate); - } + $span.append($type).append($adate).append($cdate); }; var addFolderData = function (element, key, $span) { @@ -1804,7 +1785,6 @@ define([ $list.find('.' + classSorted).addClass('cp-app-drive-sort-active').prepend($icon); } }; - // _WORKGROUP_ : do not display title, atime and ctime in workgroups since we don't have files data var getFileListHeader = function () { var $fihElement = $('
  • ', { 'class': 'cp-app-drive-element-header cp-app-drive-element-list' @@ -1827,9 +1807,7 @@ define([ }).text(Messages.fm_creation).click(onSortByClick); // If displayTitle is false, it means the "name" is the title, so do not display the "name" header $fihElement.append($fhIcon).append($fhName).append($fhState).append($fhType); - if (!isWorkgroup()) { - $fihElement.append($fhAdate).append($fhCdate); - } + $fihElement.append($fhAdate).append($fhCdate); addFileSortIcon($fihElement); return $fihElement; }; @@ -2277,7 +2255,6 @@ define([ // Display the selected directory into the content part (rightside) // NOTE: Elements in the trash are not using the same storage structure as the others - // _WORKGROUP_ : do not change the lastOpenedFolder value in localStorage var _displayDirectory = function (path, force) { APP.hideMenu(); if (!APP.editable) { debug("Read-only mode"); } @@ -2331,9 +2308,7 @@ define([ $tree.find('#cp-app-drive-tree-search-input')[0].selectionEnd = getSearchCursor(); } - if (!isWorkgroup()) { - setLastOpenedFolder(path); - } + setLastOpenedFolder(path); var $toolbar = createToolbar(path); var $info = createInfoBox(path); From bd6a199dca64494b271f64b2caec15fe60554c29 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 5 Jul 2018 13:56:16 +0200 Subject: [PATCH 06/27] 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 }; From 0c9dfc1fb55ed1839831639ecbe32ef17d5ee421 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 5 Jul 2018 16:33:37 +0200 Subject: [PATCH 07/27] Add proxy manager inner --- www/common/proxy-manager.js | 265 +++++++++++++++++++++++++++++++++++- www/common/userObject.js | 3 + www/drive/inner.js | 263 ++++++++++++++++++----------------- 3 files changed, 399 insertions(+), 132 deletions(-) diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js index bacbb0d9f..ac2feb883 100644 --- a/www/common/proxy-manager.js +++ b/www/common/proxy-manager.js @@ -149,7 +149,7 @@ define([ // Remove the shared folder from this list of files ID files.filter(function (f) { return !userObject.isSharedFolder(f); }); // Deduplicate - Util.deduplicateString(files); + files = Util.deduplicateString(files); // Get the files data associated to these files var filesData = {}; @@ -350,7 +350,7 @@ define([ userObjects.forEach(function (uo) { Array.prototype.push.apply(list, uo.getTagsList()); }); - Util.deduplicateString(list); + list = Util.deduplicateString(list); return list; }; @@ -391,8 +391,8 @@ define([ // 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()))) { + if ((data.owners && data.owners.length && data.owners.indexOf(edPublic) === -1) + || (data.expire && data.expire < (+new Date()))) { result.push(data.channel); } }; @@ -485,7 +485,262 @@ define([ }; }; + /* + Inner only + */ + + var renameInner = function (Env, path, newName, cb) { + return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", { + cmd: "rename", + data: { + path: path, + newName: newName + } + }, cb); + }; + var moveInner = function (Env, paths, newPath, cb) { + return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", { + cmd: "move", + data: { + paths: paths, + newPath: newPath + } + }, cb); + }; + var emptyTrashInner = function (Env, cb) { + return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", { + cmd: "emptyTrash" + }, cb); + }; + var addFolderInner = function (Env, path, name, cb) { + return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", { + cmd: "addFolder", + data: { + path: path, + name: name + } + }, cb); + }; + var deleteInner = function (Env, paths, cb, nocheck, isOwnPadRemoved, noUnpin) { + return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", { + cmd: "delete", + data: { + paths: paths, + nocheck: nocheck, + noUnpin: noUnpin, + isOwnPadRemoved: isOwnPadRemoved + } + }, cb); + }; + var restoreInner = function (Env, path, cb) { + return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", { + cmd: "restore", + data: { + path: path + } + }, cb); + }; + + /* Tools */ + + var _getUserObjectFromId = function (Env, id) { + var userObjects = _getUserObjects(Env); + var userObject = Env.user.userObject; + userObjects.some(function (uo) { + if (Object.keys(uo.getFileData(id)).length) { + userObject = uo; + return true; + } + }); + return userObject; + }; + + var _getUserObjectPath = function (Env, uo) { + var fId = uo.id; + if (!fId) { return; } + var fPath = Env.user.userObject.findFile(fId)[0]; + return fPath; + }; + + var getFileData = function (Env, id) { + var userObjects = _getUserObjects(Env); + var data = {}; + userObjects.some(function (uo) { + data = uo.getFileData(id); + if (Object.keys(data).length) { return true; } + }); + return data; + }; + + var find = function (Env, path) { + var resolved = _resolvePath(Env, path); + return resolved.userObject.find(path); + }; + + var getTitle = function (Env, id, type) { + var uo = _getUserObjectFromId(Env, id); + return uo.getTitle(id, type); + }; + + var isReadOnlyFile = function (Env, id) { + var uo = _getUserObjectFromId(Env, id); + return uo.isReadOnlyFile(id); + }; + + var getFiles = function (Env, categories) { + var files = []; + var userObjects = _getUserObjects(Env); + userObjects.forEach(function (uo) { + Array.prototype.push.apply(files, uo.getFiles(categories)); + }); + files = Util.deduplicateString(files); + return files; + }; + + var search = function (Env, value) { + var ret = []; + var userObjects = _getUserObjects(Env); + userObjects.forEach(function (uo) { + var fPath = _getUserObjectPath(Env, uo); + var results = uo.search(value); + if (fPath) { + // This is a shared folder, we have to fix the paths in the search results + results = results.map(function (r) { + r.paths.map(function (p) { + Array.prototype.unshift.apply(p, fPath); + }); + }); + } + // Push the results from this proxy + Array.prototype.push.apply(ret, results); + }); + return ret; + }; + + var findFile = function (Env, id) { + var ret = []; + var userObjects = _getUserObjects(Env); + userObjects.forEach(function (uo) { + var fPath = _getUserObjectPath(Env, uo); + var results = uo.findFile(id); + if (fPath) { + // This is a shared folder, we have to fix the paths in the results + results = results.map(function (p) { + Array.prototype.unshift.apply(p, fPath); + }); + } + // Push the results from this proxy + Array.prototype.push.apply(ret, results); + }); + return ret; + }; + + var findChannels = function (Env, channels) { + var ret = []; + var userObjects = _getUserObjects(Env); + userObjects.forEach(function (uo) { + var results = uo.findChannels(channels); + Array.prototype.push.apply(ret, results); + }); + ret = Util.deduplicateString(ret); + return ret; + }; + + var getRecentPads = function (Env) { + return Env.user.userObject.getRecentPads(); + }; + var getOwnedPads = function (Env, edPublic) { + return Env.user.userObject.getOwnedPads(edPublic); + }; + + /* Generic: doesn't need access to a proxy */ + var isFile = function (Env, el, allowStr) { + return Env.user.userObject.isFile(el, allowStr); + }; + var isFolder = function (Env, el) { + return Env.user.userObject.isFolder(el); + }; + var isFolderEmpty = function (Env, el) { + return Env.user.userObject.isFolderEmpty(el); + }; + var isPathIn = function (Env, path, categories) { + return Env.user.userObject.isPathIn(path, categories); + }; + var isSubpath = function (Env, path, parentPath) { + return Env.user.userObject.isSubpath(path, parentPath); + }; + var isInTrashRoot = function (Env, path) { + return Env.user.userObject.isInTrashRoot(path); + }; + var comparePath = function (Env, a, b) { + return Env.user.userObject.comparePath(a, b); + }; + var hasSubfolder = function (Env, el, trashRoot) { + return Env.user.userObject.hasSubfolder(el, trashRoot); + }; + var hasFile = function (Env, el, trashRoot) { + return Env.user.userObject.hasFile(el, trashRoot); + }; + + var createInner = function (proxy, sframeChan, uoConfig) { + var Env = { + cfg: uoConfig, + sframeChan: sframeChan, + user: { + proxy: proxy, + userObject: UserObject.init(proxy, uoConfig) + }, + folders: {} + }; + + var callWithEnv = function (f) { + return function () { + [].unshift.call(arguments, Env); + return f.apply(null, arguments); + }; + }; + + return { + // Manager + addProxy: callWithEnv(addProxy), + removeProxy: callWithEnv(removeProxy), + // Drive RPC commands + rename: callWithEnv(renameInner), + move: callWithEnv(moveInner), + emptyTrash: callWithEnv(emptyTrashInner), + addFolder: callWithEnv(addFolderInner), + delete: callWithEnv(deleteInner), + restore: callWithEnv(restoreInner), + // Tools + getFileData: callWithEnv(getFileData), + find: callWithEnv(find), + getTitle: callWithEnv(getTitle), + isReadOnlyFile: callWithEnv(isReadOnlyFile), + getFiles: callWithEnv(getFiles), + search: callWithEnv(search), + getRecentPads: callWithEnv(getRecentPads), + getOwnedPads: callWithEnv(getOwnedPads), + getTagsList: callWithEnv(getTagsList), + findFile: callWithEnv(findFile), + findChannels: callWithEnv(findChannels), + // Generic + isFile: callWithEnv(isFile), + isFolder: callWithEnv(isFolder), + isFolderEmpty: callWithEnv(isFolderEmpty), + isPathIn: callWithEnv(isPathIn), + isSubpath: callWithEnv(isSubpath), + isinTrashRoot: callWithEnv(isInTrashRoot), + comparePath: callWithEnv(comparePath), + hasSubfolder: callWithEnv(hasSubfolder), + hasFile: callWithEnv(hasFile), + // Data + user: Env.user, + folders: Env.folders + }; + }; + return { - create: create + create: create, + createInner: createInner }; }); diff --git a/www/common/userObject.js b/www/common/userObject.js index 87a1653fa..9dbfe4869 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -31,6 +31,9 @@ define([ exp.TEMPLATE = TEMPLATE; exp.SHARED_FOLDERS = SHARED_FOLDERS; + exp.sharedFolder = config.sharedFolder; + exp.id = config.id; + // Logging var logging = function () { console.log.apply(console, arguments); diff --git a/www/drive/inner.js b/www/drive/inner.js index a6fd5c9ec..fe981d4e3 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -12,7 +12,7 @@ define([ '/common/sframe-common.js', '/common/common-realtime.js', '/common/hyperscript.js', - '/common/userObject.js', + '/common/proxy-manager.js', '/customize/application_config.js', '/bower_components/chainpad-listmap/chainpad-listmap.js', '/customize/messages.js', @@ -34,7 +34,7 @@ define([ SFCommon, CommonRealtime, h, - FO, + ProxyManager, AppConfig, Listmap, Messages) @@ -302,7 +302,7 @@ define([ return $(menu); }; - var andThen = function (common, proxy) { + var andThen = function (common, proxy, folders) { var files = proxy.drive; var metadataMgr = common.getMetadataMgr(); var sframeChan = common.getSframeChannel(); @@ -315,8 +315,15 @@ define([ config.sframeChan = sframeChan; - var filesOp = FO.init(files, config); - var error = filesOp.error; + var manager = ProxyManager.createInner(files, sframeChan, config); + + Object.keys(folders).forEach(function (id) { + var f = folders[id]; + // f.data => metadata (href, title, password...) + // f.proxy => listmap + // f.id => id? + manager.addProxy(id, f.proxy); + }); var $tree = APP.$tree = $("#cp-app-drive-tree"); var $content = APP.$content = $("#cp-app-drive-content"); @@ -360,7 +367,7 @@ define([ // Templates enabled: display template category if (AppConfig.enableTemplates) { displayedCategories.push(TEMPLATE); } // Tags used: display Tags category - if (Object.keys(filesOp.getTagsList()).length) { displayedCategories.push(TAGS); } + if (Object.keys(manager.getTagsList()).length) { displayedCategories.push(TAGS); } var virtualCategories = [SEARCH, RECENT, OWNED, TAGS]; @@ -622,7 +629,7 @@ define([ var removeInput = function (cancel) { if (!cancel && $('.cp-app-drive-element-row > input').length === 1) { var $input = $('.cp-app-drive-element-row > input'); - filesOp.rename($input.data('path'), $input.val(), APP.refresh); + manager.rename($input.data('path'), $input.val(), APP.refresh); } $('.cp-app-drive-element-row > input').remove(); $('.cp-app-drive-element-row > span:hidden').removeAttr('style'); @@ -648,14 +655,14 @@ define([ ret = date.toLocaleDateString(); } } catch (e) { - error("Unable to format that string to a date with .toLocaleString", sDate, e); + console.error("Unable to format that string to a date with .toLocaleString", sDate, e); } return ret; }; var openFile = function (el, href) { if (!href) { - var data = filesOp.getFileData(el); + var data = manager.getFileData(el); if (!data || (!data.href && !data.roHref)) { return void logError("Missing data for the file", el, data); } @@ -690,8 +697,8 @@ define([ $name = $element.find('> .cp-app-drive-element'); } $name.hide(); - var el = filesOp.find(path); - var name = filesOp.isFile(el) ? filesOp.getTitle(el) : path[path.length - 1]; + var el = manager.find(path); + var name = manager.isFile(el) ? manager.getTitle(el) : path[path.length - 1]; var $input = $('', { placeholder: name, value: name @@ -705,7 +712,7 @@ define([ e.stopPropagation(); if (e.which === 13) { removeInput(true); - filesOp.rename(path, $input.val(), refresh); + manager.rename(path, $input.val(), refresh); return; } if (e.which === 27) { @@ -880,7 +887,7 @@ define([ }; var updateContextButton = function () { - if (filesOp.isPathIn(currentPath, [TRASH])) { + if (manager.isPathIn(currentPath, [TRASH])) { $driveToolbar.find('cp-app-drive-toolbar-emptytrash').show(); } else { $driveToolbar.find('cp-app-drive-toolbar-emptytrash').hide(); @@ -1098,19 +1105,19 @@ define([ }; var getElementName = function (path) { - var file = filesOp.find(path); - if (!file || !filesOp.isFile(file)) { return '???'; } - return filesOp.getTitle(file); + var file = manager.find(path); + if (!file || !manager.isFile(file)) { return '???'; } + return manager.getTitle(file); }; - // filesOp.moveElements is able to move several paths to a new location, including + // manager.moveElements is able to move several paths to a new location, including // the Trash or the "Unsorted files" folder var moveElements = function (paths, newPath, force, cb) { if (!APP.editable) { return; } var andThenMove = function () { - filesOp.move(paths, newPath, cb); + manager.move(paths, newPath, cb); }; // Cancel drag&drop from TRASH to TRASH - if (filesOp.isPathIn(newPath, [TRASH]) && paths.length && paths[0][0] === TRASH) { + if (manager.isPathIn(newPath, [TRASH]) && paths.length && paths[0][0] === TRASH) { return; } andThenMove(); @@ -1125,7 +1132,7 @@ define([ $selected.each(function (idx, elmt) { var ePath = $(elmt).data('path'); if (ePath) { - var val = filesOp.find(ePath); + var val = manager.find(ePath); if (!val) { return; } // Error? A ".selected" element in not in the object paths.push({ path: ePath, @@ -1139,7 +1146,7 @@ define([ } else { removeSelected(); $element.addClass('cp-app-drive-element-selected'); - var val = filesOp.find(path); + var val = manager.find(path); if (!val) { return; } // The element in not in the object paths = [{ path: path, @@ -1159,7 +1166,7 @@ define([ var $target = $(target); var $el = findDataHolder($target); var newPath = $el.data('path'); - if ((!newPath || filesOp.isFile(filesOp.find(newPath))) + if ((!newPath || manager.isFile(manager.find(newPath))) && $target.parents('#cp-app-drive-content')) { newPath = currentPath; } @@ -1188,7 +1195,7 @@ define([ var movedPaths = []; var importedElements = []; oldPaths.forEach(function (p) { - var el = filesOp.find(p.path); + var el = manager.find(p.path); if (el && (stringify(el) === stringify(p.value.el) || !p.value || !p.value.el)) { movedPaths.push(p.path); } else { @@ -1252,9 +1259,9 @@ define([ // In list mode, display metadata from the filesData object var addFileData = function (element, $span) { - if (!filesOp.isFile(element)) { return; } + if (!manager.isFile(element)) { return; } - var data = filesOp.getFileData(element); + var data = manager.getFileData(element); var href = data.href || data.roHref; if (!data) { return void logError("No data for the file", element); } @@ -1281,7 +1288,7 @@ define([ $owner.attr('title', Messages.fm_padIsOwnedOther); } - var name = filesOp.getTitle(element); + var name = manager.getTitle(element); // The element with the class '.name' is underlined when the 'li' is hovered var $name = $('', {'class': 'cp-app-drive-element-name'}).text(name); @@ -1311,10 +1318,10 @@ define([ }; var addFolderData = function (element, key, $span) { - if (!element || !filesOp.isFolder(element)) { return; } + if (!element || !manager.isFolder(element)) { return; } // The element with the class '.name' is underlined when the 'li' is hovered - var sf = filesOp.hasSubfolder(element); - var files = filesOp.hasFile(element); + var sf = manager.hasSubfolder(element); + var files = manager.hasFile(element); var $name = $('', {'class': 'cp-app-drive-element-name'}).text(key); var $state = $('', {'class': 'cp-app-drive-element-state'}); var $subfolders = $('', { @@ -1329,7 +1336,7 @@ define([ // This is duplicated in cryptpad-common, it should be unified var getFileIcon = function (id) { - var data = filesOp.getFileData(id); + var data = manager.getFileData(id); return UI.getFileIcon(data); }; var getIcon = UI.getIcon; @@ -1348,16 +1355,16 @@ define([ newPath.push(key); } - var element = filesOp.find(newPath); + var element = manager.find(newPath); var $icon = !isFolder ? getFileIcon(element) : undefined; - var ro = filesOp.isReadOnlyFile(element); + var ro = manager.isReadOnlyFile(element); // ro undefined means it's an old hash which doesn't support read-only var roClass = typeof(ro) === 'undefined' ?' cp-app-drive-element-noreadonly' : ro ? ' cp-app-drive-element-readonly' : ''; var liClass = 'cp-app-drive-element-file cp-app-drive-element' + roClass; if (isFolder) { liClass = 'cp-app-drive-element-folder cp-app-drive-element'; - $icon = filesOp.isFolderEmpty(root[key]) ? $folderEmptyIcon.clone() : $folderIcon.clone(); + $icon = manager.isFolderEmpty(root[key]) ? $folderEmptyIcon.clone() : $folderIcon.clone(); } var $element = $('
  • ', { draggable: true, @@ -1397,7 +1404,7 @@ define([ $element.contextmenu(openContextMenu('trash')); $element.data('context', 'trash'); } - var isNewFolder = APP.newFolder && filesOp.comparePath(newPath, APP.newFolder); + var isNewFolder = APP.newFolder && manager.comparePath(newPath, APP.newFolder); if (isNewFolder) { appStatus.onReady(function () { window.setTimeout(function () { displayRenameInput($element, newPath); }, 0); @@ -1444,12 +1451,12 @@ define([ // Create the title block with the "parent folder" button var createTitle = function ($container, path, noStyle) { if (!path || path.length === 0) { return; } - var isTrash = filesOp.isPathIn(path, [TRASH]); + var isTrash = manager.isPathIn(path, [TRASH]); if (APP.mobile() && !noStyle) { // noStyle means title in search result return $container; } var isVirtual = virtualCategories.indexOf(path[0]) !== -1; - var el = isVirtual ? undefined : filesOp.find(path); + var el = isVirtual ? undefined : manager.find(path); path = path[0] === SEARCH ? path.slice(0,1) : path; path.forEach(function (p, idx) { if (isTrash && [2,3].indexOf(idx) !== -1) { return; } @@ -1466,7 +1473,7 @@ define([ APP.displayDirectory(path.slice(0, sliceEnd)); }); } - } else if (idx > 0 && filesOp.isFile(el)) { + } else if (idx > 0 && manager.isFile(el)) { name = getElementName(path); } @@ -1569,7 +1576,7 @@ define([ $button.click(function () { UI.confirm(Messages.fm_emptyTrashDialog, function(res) { if (!res) { return; } - filesOp.emptyTrash(refresh); + manager.emptyTrash(refresh); }); }); $container.append($button); @@ -1605,7 +1612,7 @@ define([ }; $block.find('a.cp-app-drive-new-folder, li.cp-app-drive-new-folder') .click(function () { - filesOp.addFolder(currentPath, null, onCreated); + manager.addFolder(currentPath, null, onCreated); }); $block.find('a.cp-app-drive-new-upload, li.cp-app-drive-new-upload') .click(function () { @@ -1626,7 +1633,7 @@ define([ $block.find('a.cp-app-drive-new-doc, li.cp-app-drive-new-doc') .click(function () { var type = $(this).attr('data-type') || 'pad'; - var path = filesOp.isPathIn(currentPath, [TRASH]) ? '' : currentPath; + var path = manager.isPathIn(currentPath, [TRASH]) ? '' : currentPath; common.sessionStorage.put(Constants.newPadPathKey, path, function () { common.openURL('/' + type + '/'); }); @@ -1636,7 +1643,7 @@ define([ if (!APP.editable) { return; } if (!APP.loggedIn) { return; } // Anonymous users can use the + menu in the toolbar - if (!filesOp.isPathIn(currentPath, [ROOT, 'hrefArray'])) { return; } + if (!manager.isPathIn(currentPath, [ROOT, 'hrefArray'])) { return; } // Create dropdown var options = []; @@ -1813,8 +1820,8 @@ define([ }; var sortElements = function (folder, path, oldkeys, prop, asc, useId) { - var root = path && filesOp.find(path); - var test = folder ? filesOp.isFolder : filesOp.isFile; + var root = path && manager.find(path); + var test = folder ? manager.isFolder : manager.isFile; var keys = oldkeys.filter(function (e) { return useId ? test(e) : (path && test(root[e])); }); @@ -1823,7 +1830,7 @@ define([ var getProp = function (el, prop) { if (folder) { return el.toLowerCase(); } var id = useId ? el : root[el]; - var data = filesOp.getFileData(id); + var data = manager.getFileData(id); if (!data) { return ''; } if (prop === 'type') { var hrefData = Hash.parsePadUrl(data.href || data.roHref); @@ -1832,7 +1839,7 @@ define([ if (prop === 'atime' || prop === 'ctime') { return new Date(data[prop]); } - return (filesOp.getTitle(id) || "").toLowerCase(); + return (manager.getTitle(id) || "").toLowerCase(); }; keys.sort(function(a, b) { if (getProp(a, prop) < getProp(b, prop)) { return mult * -1; } @@ -1842,7 +1849,7 @@ define([ return keys; }; var sortTrashElements = function (folder, oldkeys, prop, asc) { - var test = folder ? filesOp.isFolder : filesOp.isFile; + var test = folder ? manager.isFolder : manager.isFile; var keys = oldkeys.filter(function (e) { return test(e.element); }); @@ -1851,7 +1858,7 @@ define([ var getProp = function (el, prop) { if (prop && !folder) { var element = el.element; - var e = filesOp.getFileData(element); + var e = manager.getFileData(element); if (!e) { e = { href : el, @@ -1966,7 +1973,7 @@ define([ sortBy = sortBy === "" ? sortBy = 'name' : sortBy; var sortedFiles = sortElements(false, [rootName], keys, sortBy, !getSortFileDesc(), true); sortedFiles.forEach(function (id) { - var file = filesOp.getFileData(id); + var file = manager.getFileData(id); if (!file) { //debug("Unsorted or template returns an element not present in filesData: ", href); file = { title: Messages.fm_noname }; @@ -1974,7 +1981,7 @@ define([ } var idx = files[rootName].indexOf(id); var $icon = getFileIcon(id); - var ro = filesOp.isReadOnlyFile(id); + var ro = manager.isReadOnlyFile(id); // ro undefined mens it's an old hash which doesn't support read-only var roClass = typeof(ro) === 'undefined' ? ' cp-app-drive-element-noreadonly' : ro ? ' cp-app-drive-element-readonly' : ''; @@ -2018,11 +2025,11 @@ define([ if (allfiles.length === 0) { return; } var $fileHeader = getFileListHeader(false); $container.append($fileHeader); - var keys = filesOp.getFiles([FILES_DATA]); + var keys = manager.getFiles([FILES_DATA]); var sortedFiles = sortElements(false, [FILES_DATA], keys, APP.store[SORT_FILE_BY], !getSortFileDesc(), true); sortedFiles.forEach(function (id) { var $icon = getFileIcon(id); - var ro = filesOp.isReadOnlyFile(id); + var ro = manager.isReadOnlyFile(id); // ro undefined maens it's an old hash which doesn't support read-only var roClass = typeof(ro) === 'undefined' ? ' cp-app-drive-element-noreadonly' : ro ? ' cp-app-drive-element-readonly' : ''; @@ -2056,7 +2063,7 @@ define([ return; } root[key].forEach(function (el, idx) { - if (!filesOp.isFile(el.element) && !filesOp.isFolder(el.element)) { return; } + if (!manager.isFile(el.element) && !manager.isFolder(el.element)) { return; } var spath = [key, idx, 'element']; filesList.push({ element: el.element, @@ -2067,12 +2074,12 @@ define([ }); var sortedFolders = sortTrashElements(true, filesList, null, !getSortFolderDesc()); var sortedFiles = sortTrashElements(false, filesList, APP.store[SORT_FILE_BY], !getSortFileDesc()); - if (filesOp.hasSubfolder(root, true)) { $list.append($folderHeader); } + if (manager.hasSubfolder(root, true)) { $list.append($folderHeader); } sortedFolders.forEach(function (f) { var $element = createElement([TRASH], f.spath, root, true); $list.append($element); }); - if (filesOp.hasFile(root, true)) { $list.append($fileHeader); } + if (manager.hasFile(root, true)) { $list.append($fileHeader); } sortedFiles.forEach(function (f) { var $element = createElement([TRASH], f.spath, root, false); $list.append($element); @@ -2080,7 +2087,7 @@ define([ }; var displaySearch = function ($list, value) { - var filesList = filesOp.search(value); + var filesList = manager.search(value); filesList.forEach(function (r) { r.paths.forEach(function (path) { var href = r.data.href; @@ -2106,7 +2113,7 @@ define([ .text(Messages.fm_creation); var $ctime = $('', {'class': 'cp-app-drive-search-col2'}) .text(new Date(r.data.ctime).toLocaleString()); - if (filesOp.isPathIn(path, ['hrefArray'])) { + if (manager.isPathIn(path, ['hrefArray'])) { path.pop(); path.push(r.data.title); } @@ -2119,7 +2126,7 @@ define([ if (parentPath) { $a = $('').text(Messages.fm_openParent).click(function (e) { e.preventDefault(); - if (filesOp.isInTrashRoot(parentPath)) { parentPath = [TRASH]; } + if (manager.isInTrashRoot(parentPath)) { parentPath = [TRASH]; } else { parentPath.pop(); } APP.selectedFiles = [r.id]; APP.displayDirectory(parentPath); @@ -2144,25 +2151,25 @@ define([ }; var displayRecent = function ($list) { - var filesList = filesOp.getRecentPads(); + var filesList = manager.getRecentPads(); var limit = 20; var i = 0; filesList.forEach(function (id) { if (i >= limit) { return; } // Check path (pad exists and not in trash) - var paths = filesOp.findFile(id); + var paths = manager.findFile(id); if (!paths.length) { return; } var path = paths[0]; - if (filesOp.isPathIn(path, [TRASH])) { return; } + if (manager.isPathIn(path, [TRASH])) { return; } // Display the pad - var file = filesOp.getFileData(id); + var file = manager.getFileData(id); if (!file) { //debug("Unsorted or template returns an element not present in filesData: ", href); file = { title: Messages.fm_noname }; //return; } var $icon = getFileIcon(id); - var ro = filesOp.isReadOnlyFile(id); + var ro = manager.isReadOnlyFile(id); // ro undefined mens it's an old hash which doesn't support read-only var roClass = typeof(ro) === 'undefined' ? ' cp-app-drive-element-noreadonly' : ro ? ' cp-app-drive-element-readonly' : ''; @@ -2190,17 +2197,17 @@ define([ // Owned pads category var displayOwned = function ($container) { - var list = filesOp.getOwnedPads(edPublic); + var list = manager.getOwnedPads(edPublic); if (list.length === 0) { return; } var $fileHeader = getFileListHeader(false); $container.append($fileHeader); var sortedFiles = sortElements(false, false, list, APP.store[SORT_FILE_BY], !getSortFileDesc(), true); sortedFiles.forEach(function (id) { - var paths = filesOp.findFile(id); + var paths = manager.findFile(id); if (!paths.length) { return; } var path = paths[0]; var $icon = getFileIcon(id); - var ro = filesOp.isReadOnlyFile(id); + var ro = manager.isReadOnlyFile(id); // ro undefined maens it's an old hash which doesn't support read-only var roClass = typeof(ro) === 'undefined' ? ' cp-app-drive-element-noreadonly' : ro ? ' cp-app-drive-element-readonly' : ''; @@ -2226,7 +2233,7 @@ define([ // Tags category var displayTags = function ($container) { - var list = filesOp.getTagsList(); + var list = manager.getTagsList(); if (Object.keys(list).length === 0) { return; } var sortedTags = Object.keys(list); sortedTags.sort(function (a, b) { @@ -2276,16 +2283,16 @@ define([ if (!path || path.length === 0) { path = [ROOT]; } - var isInRoot = filesOp.isPathIn(path, [ROOT]); - var inTrash = filesOp.isPathIn(path, [TRASH]); - var isTrashRoot = filesOp.comparePath(path, [TRASH]); - var isTemplate = filesOp.comparePath(path, [TEMPLATE]); - var isAllFiles = filesOp.comparePath(path, [FILES_DATA]); + var isInRoot = manager.isPathIn(path, [ROOT]); + var inTrash = manager.isPathIn(path, [TRASH]); + var isTrashRoot = manager.comparePath(path, [TRASH]); + var isTemplate = manager.comparePath(path, [TEMPLATE]); + var isAllFiles = manager.comparePath(path, [FILES_DATA]); var isVirtual = virtualCategories.indexOf(path[0]) !== -1; var isSearch = path[0] === SEARCH; var isTags = path[0] === TAGS; - var root = isVirtual ? undefined : filesOp.find(path); + var root = isVirtual ? undefined : manager.find(path); if (!isVirtual && typeof(root) === "undefined") { log(Messages.fm_unknownFolderError); debug("Unable to locate the selected directory: ", path); @@ -2385,20 +2392,20 @@ define([ displayTags($list); } else { $dirContent.contextmenu(openContextMenu('content')); - if (filesOp.hasSubfolder(root)) { $list.append($folderHeader); } + if (manager.hasSubfolder(root)) { $list.append($folderHeader); } // display sub directories var keys = Object.keys(root); var sortedFolders = sortElements(true, path, keys, null, !getSortFolderDesc()); var sortedFiles = sortElements(false, path, keys, APP.store[SORT_FILE_BY], !getSortFileDesc()); sortedFolders.forEach(function (key) { - if (filesOp.isFile(root[key])) { return; } + if (manager.isFile(root[key])) { return; } var $element = createElement(path, key, root, true); $element.appendTo($list); }); - if (filesOp.hasFile(root)) { $list.append($fileHeader); } + if (manager.hasFile(root)) { $list.append($fileHeader); } // display files sortedFiles.forEach(function (key) { - if (filesOp.isFolder(root[key])) { return; } + if (manager.isFolder(root[key])) { return; } var $element = createElement(path, key, root, false); $element.appendTo($list); }); @@ -2460,13 +2467,13 @@ define([ $collapse.removeClass('fa-minus-square-o'); $collapse.addClass('fa-plus-square-o'); // Change the current opened folder if it was collapsed - if (filesOp.isSubpath(currentPath, path)) { + if (manager.isSubpath(currentPath, path)) { displayDirectory(path); } } }); if (wasFolderOpened(path) || - (filesOp.isSubpath(currentPath, path) && path.length < currentPath.length)) { + (manager.isSubpath(currentPath, path) && path.length < currentPath.length)) { $collapse.click(); } } @@ -2479,20 +2486,20 @@ define([ }; var createTree = function ($container, path) { - var root = filesOp.find(path); + var root = manager.find(path); // don't try to display what doesn't exist if (!root) { return; } // Display the root element in the tree - var displayingRoot = filesOp.comparePath([ROOT], path); + var displayingRoot = manager.comparePath([ROOT], path); if (displayingRoot) { - var isRootOpened = filesOp.comparePath([ROOT], currentPath); - var $rootIcon = filesOp.isFolderEmpty(files[ROOT]) ? + var isRootOpened = manager.comparePath([ROOT], currentPath); + var $rootIcon = manager.isFolderEmpty(files[ROOT]) ? (isRootOpened ? $folderOpenedEmptyIcon : $folderEmptyIcon) : (isRootOpened ? $folderOpenedIcon : $folderIcon); var $rootElement = createTreeElement(ROOT_NAME, $rootIcon.clone(), [ROOT], false, true, true, isRootOpened); - if (!filesOp.hasSubfolder(root)) { + if (!manager.hasSubfolder(root)) { $rootElement.find('.cp-app-drive-icon-expcol').css('visibility', 'hidden'); } $rootElement.addClass('cp-app-drive-tree-root'); @@ -2501,19 +2508,19 @@ define([ $('
      ', {'class': 'cp-app-drive-tree-docs'}) .append($rootElement).appendTo($container); $container = $rootElement; - } else if (filesOp.isFolderEmpty(root)) { return; } + } else if (manager.isFolderEmpty(root)) { return; } // Display root content var $list = $('
        ').appendTo($container); var keys = Object.keys(root).sort(); keys.forEach(function (key) { // Do not display files in the menu - if (!filesOp.isFolder(root[key])) { return; } + if (!manager.isFolder(root[key])) { return; } var newPath = path.slice(); newPath.push(key); - var isCurrentFolder = filesOp.comparePath(newPath, currentPath); - var isEmpty = filesOp.isFolderEmpty(root[key]); - var subfolder = filesOp.hasSubfolder(root[key]); + var isCurrentFolder = manager.comparePath(newPath, currentPath); + var isEmpty = manager.isFolderEmpty(root[key]); + var subfolder = manager.hasSubfolder(root[key]); var $icon = isEmpty ? (isCurrentFolder ? $folderOpenedEmptyIcon : $folderEmptyIcon) : (isCurrentFolder ? $folderOpenedIcon : $folderIcon); @@ -2525,8 +2532,8 @@ define([ }; var createTrash = function ($container, path) { - var $icon = filesOp.isFolderEmpty(files[TRASH]) ? $trashEmptyIcon.clone() : $trashIcon.clone(); - var isOpened = filesOp.comparePath(path, currentPath); + var $icon = manager.isFolderEmpty(files[TRASH]) ? $trashEmptyIcon.clone() : $trashIcon.clone(); + var isOpened = manager.comparePath(path, currentPath); var $trashElement = createTreeElement(TRASH_NAME, $icon, [TRASH], false, true, false, isOpened); $trashElement.addClass('root'); $trashElement.find('>.cp-app-drive-element-row') @@ -2567,7 +2574,7 @@ define([ if (!isInSearchTmp) { search.oldLocation = currentPath.slice(); } var newLocation = [SEARCH, $input.val()]; setSearchCursor(); - if (!filesOp.comparePath(newLocation, currentPath.slice())) { displayDirectory(newLocation); } + if (!manager.comparePath(newLocation, currentPath.slice())) { displayDirectory(newLocation); } return; } if (e.which === 27) { @@ -2582,7 +2589,7 @@ define([ if (!isInSearchTmp) { search.oldLocation = currentPath.slice(); } var newLocation = [SEARCH, $input.val()]; setSearchCursor(); - if (!filesOp.comparePath(newLocation, currentPath.slice())) { displayDirectory(newLocation); } + if (!manager.comparePath(newLocation, currentPath.slice())) { displayDirectory(newLocation); } }, 500); }).appendTo($div); $searchIcon.clone().appendTo($div); @@ -2615,7 +2622,7 @@ define([ var createCategory = function ($container, cat) { var options = categories[cat]; var $icon = options.$icon.clone(); - var isOpened = filesOp.comparePath([cat], currentPath); + var isOpened = manager.comparePath([cat], currentPath); var $element = createTreeElement(options.name, $icon, [cat], options.draggable, options.droppable, false, isOpened); $element.addClass('cp-app-drive-tree-root'); var $list = $('
          ', { 'class': 'cp-app-drive-tree-category' }).append($element); @@ -2676,12 +2683,12 @@ define([ }); var getProperties = APP.getProperties = function (el, cb) { - if (!filesOp.isFile(el)) { + if (!manager.isFile(el)) { return void cb('NOT_FILE'); } - //var ro = filesOp.isReadOnlyFile(el); + //var ro = manager.isReadOnlyFile(el); var base = APP.origin; - var data = JSON.parse(JSON.stringify(filesOp.getFileData(el))); + var data = JSON.parse(JSON.stringify(manager.getFileData(el))); if (!data || !(data.href || data.roHref)) { return void cb('INVALID_FILE'); } if (data.href) { @@ -2710,7 +2717,7 @@ define([ UI.confirm(msg, function(res) { $(window).focus(); if (!res) { return; } - filesOp.delete(pathsList, refresh); + manager.delete(pathsList, refresh); }); }; var deleteOwnedPaths = function (paths, pathsList) { @@ -2723,7 +2730,7 @@ define([ UI.confirm(msgD, function(res) { $(window).focus(); if (!res) { return; } - filesOp.delete(pathsList, refresh); + manager.delete(pathsList, refresh); }); }; $contextMenu.on("click", "a", function(e) { @@ -2762,13 +2769,13 @@ define([ } else if ($(this).hasClass('cp-app-drive-context-openro')) { paths.forEach(function (p) { - var el = filesOp.find(p.path); + var el = manager.find(p.path); var href; - if (filesOp.isPathIn(p.path, [FILES_DATA])) { + if (manager.isPathIn(p.path, [FILES_DATA])) { href = el.roHref; } else { - if (!el || filesOp.isFolder(el)) { return; } - var data = filesOp.getFileData(el); + if (!el || manager.isFolder(el)) { return; } + var data = manager.getFileData(el); href = data.roHref; } openFile(null, href); @@ -2781,11 +2788,11 @@ define([ APP.newFolder = info.newPath; APP.displayDirectory(paths[0].path); }; - filesOp.addFolder(paths[0].path, null, onFolderCreated); + manager.addFolder(paths[0].path, null, onFolderCreated); } else if ($(this).hasClass("cp-app-drive-context-newdoc")) { var ntype = $(this).data('type') || 'pad'; - var path2 = filesOp.isPathIn(currentPath, [TRASH]) ? '' : currentPath; + var path2 = manager.isPathIn(currentPath, [TRASH]) ? '' : currentPath; common.sessionStorage.put(Constants.newPadPathKey, path2, function () { common.openURL('/' + ntype + '/'); }); @@ -2794,13 +2801,13 @@ define([ if (type === 'trash') { var pPath = paths[0].path; if (paths.length !== 1 || pPath.length !== 4) { return; } - var element = filesOp.find(pPath.slice(0,3)); // element containing the oldpath + var element = manager.find(pPath.slice(0,3)); // element containing the oldpath var sPath = stringifyPath(element.path); UI.alert('' + Messages.fm_originalPath + ":
          " + sPath, undefined, true); return; } if (paths.length !== 1) { return; } - el = filesOp.find(paths[0].path); + el = manager.find(paths[0].path); getProperties(el, function (e, $prop) { if (e) { return void logError(e); } UI.alert($prop[0], undefined, true); @@ -2808,21 +2815,21 @@ define([ } else if ($(this).hasClass("cp-app-drive-context-hashtag")) { if (paths.length !== 1) { return; } - el = filesOp.find(paths[0].path); - var data = filesOp.getFileData(el); + el = manager.find(paths[0].path); + var data = manager.getFileData(el); if (!data) { return void console.error("Expected to find a file"); } var href = data.href || data.roHref; common.updateTags(href); } else if ($(this).hasClass("cp-app-drive-context-empty")) { if (paths.length !== 1 || !paths[0].element - || !filesOp.comparePath(paths[0].path, [TRASH])) { + || !manager.comparePath(paths[0].path, [TRASH])) { log(Messages.fm_forbidden); return; } UI.confirm(Messages.fm_emptyTrashDialog, function(res) { if (!res) { return; } - filesOp.emptyTrash(refresh); + manager.emptyTrash(refresh); }); } else if ($(this).hasClass("cp-app-drive-context-remove")) { @@ -2833,24 +2840,24 @@ define([ var restorePath = paths[0].path; var restoreName = paths[0].path[paths[0].path.length - 1]; if (restorePath.length === 4) { - var rEl = filesOp.find(restorePath); - if (filesOp.isFile(rEl)) { - restoreName = filesOp.getTitle(rEl); + var rEl = manager.find(restorePath); + if (manager.isFile(rEl)) { + restoreName = manager.getTitle(rEl); } else { restoreName = restorePath[1]; } } UI.confirm(Messages._getKey("fm_restoreDialog", [restoreName]), function(res) { if (!res) { return; } - filesOp.restore(restorePath, refresh); + manager.restore(restorePath, refresh); }); } else if ($(this).hasClass("cp-app-drive-context-openparent")) { if (paths.length !== 1) { return; } var parentPath = paths[0].path.slice(); - if (filesOp.isInTrashRoot(parentPath)) { parentPath = [TRASH]; } + if (manager.isInTrashRoot(parentPath)) { parentPath = [TRASH]; } else { parentPath.pop(); } - el = filesOp.find(paths[0].path); + el = manager.find(paths[0].path); APP.selectedFiles = [el]; APP.displayDirectory(parentPath); } @@ -2883,13 +2890,13 @@ define([ $appContainer.on('keydown', function (e) { // "Del" if (e.which === 46) { - if (filesOp.isPathIn(currentPath, [FILES_DATA]) && APP.loggedIn) { + if (manager.isPathIn(currentPath, [FILES_DATA]) && APP.loggedIn) { return; // We can't remove elements directly from filesData } var $selected = $('.cp-app-drive-element-selected'); if (!$selected.length) { return; } var paths = []; - var isTrash = filesOp.isPathIn(currentPath, [TRASH]); + var isTrash = manager.isPathIn(currentPath, [TRASH]); $selected.each(function (idx, elmt) { if (!$(elmt).data('path')) { return; } paths.push($(elmt).data('path')); @@ -2939,8 +2946,8 @@ define([ if (path[0] !== 'drive') { return false; } path = path.slice(1); var cPath = currentPath.slice(); - if ((filesOp.isPathIn(cPath, ['hrefArray', TRASH]) && cPath[0] === path[0]) || - (path.length >= cPath.length && filesOp.isSubpath(path, cPath))) { + if ((manager.isPathIn(cPath, ['hrefArray', TRASH]) && cPath[0] === path[0]) || + (path.length >= cPath.length && manager.isSubpath(path, cPath))) { // Reload after a few ms to make sure all the change events have been received onRefresh.refresh(); } else if (path.length && path[0] === FILES_DATA) { @@ -2955,8 +2962,8 @@ define([ if (path[0] !== 'drive') { return false; } path = path.slice(1); var cPath = currentPath.slice(); - if ((filesOp.isPathIn(cPath, ['hrefArray', TRASH]) && cPath[0] === path[0]) || - (path.length >= cPath.length && filesOp.isSubpath(path, cPath))) { + if ((manager.isPathIn(cPath, ['hrefArray', TRASH]) && cPath[0] === path[0]) || + (path.length >= cPath.length && manager.isSubpath(path, cPath))) { // Reload after a few to make sure all the change events have been received onRefresh.refresh(); } @@ -2987,13 +2994,13 @@ define([ UI.removeLoadingScreen(); sframeChan.query('Q_DRIVE_GETDELETED', null, function (err, data) { - var ids = filesOp.findChannels(data); + var ids = manager.findChannels(data); var titles = []; ids.forEach(function (id) { - var title = filesOp.getTitle(id); + var title = manager.getTitle(id); titles.push(title); - var paths = filesOp.findFile(id); - filesOp.delete(paths, refresh); + var paths = manager.findFile(id); + manager.delete(paths, refresh); }); if (!titles.length) { return; } UI.log(Messages._getKey('fm_deletedPads', [titles.join(', ')])); @@ -3011,6 +3018,7 @@ define([ var main = function () { var common; var proxy = {}; + var folders = {}; var readOnly; nThen(function (waitFor) { @@ -3051,6 +3059,7 @@ define([ var sframeChan = common.getSframeChannel(); updateObject(sframeChan, proxy, waitFor()); + // XXX Load shared folders }).nThen(function () { var sframeChan = common.getSframeChannel(); var metadataMgr = common.getMetadataMgr(); @@ -3113,7 +3122,7 @@ define([ if (!proxy.drive || typeof(proxy.drive) !== 'object') { throw new Error("Corrupted drive"); } - andThen(common, proxy); + andThen(common, proxy, folders); var onDisconnect = APP.onDisconnect = function (noAlert) { setEditable(false); From e0cc1a6eb6f1c51f9bfbcc6db177d10983316c14 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 9 Jul 2018 14:36:55 +0200 Subject: [PATCH 08/27] Display a shared folder in the drive --- www/common/cryptpad-common.js | 5 ++ www/common/outer/async-store.js | 29 ++++++++++- www/common/outer/store-rpc.js | 1 + www/common/outer/userObject.js | 28 +++++++++-- www/common/proxy-manager.js | 86 ++++++++++++++++++++++++--------- www/common/userObject.js | 27 +++++++---- www/drive/inner.js | 42 ++++++++++++---- www/drive/main.js | 6 +++ 8 files changed, 176 insertions(+), 48 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index afd177577..b9d089c6a 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -83,6 +83,11 @@ define([ cb(obj); }); }; + common.getSharedFolder = function (id, cb) { + postMessage("GET_SHARED_FOLDER", id, function (obj) { + cb(obj); + }); + }; // Settings and ready common.mergeAnonDrive = function (cb) { var data = { diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 70e5ee95b..7c3ec7376 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -38,7 +38,7 @@ define([ Realtime.whenRealtimeSyncs(store.realtime, waitFor()); if (store.sharedFolders) { for (var k in store.sharedFolders) { - Realtime.whenRealtimeSync(store.sharedFolders[k].realtime, waitFor()); + Realtime.whenRealtimeSyncs(store.sharedFolders[k].realtime, waitFor()); } } }).nThen(function () { cb(); }); @@ -61,6 +61,13 @@ define([ onSync(cb); }; + Store.getSharedFolder = function (clientId, id, cb) { + if (store.manager.folders[id]) { + return void cb(store.manager.folders[id].proxy); + } + cb({}); + }; + Store.hasSigningKeys = function () { if (!store.proxy) { return; } return typeof(store.proxy.edPrivate) === 'string' && @@ -1166,7 +1173,7 @@ define([ var id; nThen(function (waitFor) { // TODO XXX get the folder data (href, title, ...) - var folderData = {}; + var folderData = data.folderData || {}; // 1. add the shared folder to our list of shared folders store.userObject.pushSharedFolder(folderData, waitFor(function (err, folderId) { if (err) { @@ -1177,6 +1184,7 @@ define([ })); }).nThen(function (waitFor) { // 2a. add the shared folder to the path in our drive + console.log('adding'); store.userObject.add(id, path); onSync(waitFor()); @@ -1189,6 +1197,23 @@ define([ cb(); }); }; + store.createSharedFolder = function () { + // XXX + var hash = Hash.createRandomHash('folder'); + var href = '/folder/#' + hash; + var secret = Hash.getSecrets('folder', hash); + Store.addSharedFolder(null, { + path: ['root'], + folderData: { + href: href, + roHref: '/folder/#' + Hash.getViewHashFromKeys(secret), + channel: secret.channel, + title: "Test", + } + }, function () { + console.log('done'); + }); + }; // Drive diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js index 972776abe..e041533da 100644 --- a/www/common/outer/store-rpc.js +++ b/www/common/outer/store-rpc.js @@ -52,6 +52,7 @@ define([ GET_PAD_DATA: Store.getPadData, GET_STRONGER_HASH: Store.getStrongerHash, INCREMENT_TEMPLATE_USE: Store.incrementTemplateUse, + GET_SHARED_FOLDER: Store.getSharedFolder, // Messaging INVITE_FROM_USERLIST: Store.inviteFromUserlist, ADD_DIRECT_MESSAGE_HANDLERS: Store.addDirectMessageHandlers, diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index 94363a2d2..5a5469935 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -69,6 +69,23 @@ define([ }); }; + exp.pushSharedFolder = function (data, cb) { + if (typeof cb !== "function") { cb = function () {}; } + var todo = function () { + var id = Util.createRandomInteger(); + files[SHARED_FOLDERS][id] = data; + cb(null, id); + }; + if (!loggedIn || !AppConfig.enablePinning || config.testMode) { + return void cb("EAUTH"); + } + if (!pinPads) { return void cb('EAUTH'); } + pinPads([data.channel], function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + todo(); + }); + }; + // FILES DATA var spliceFileData = function (id) { delete files[FILES_DATA][id]; @@ -191,6 +208,7 @@ define([ var toRemove = []; Object.keys(data).forEach(function (id) { + id = Number(id); // Find and maybe update existing pads with the same channel id var d = data[id]; var found = false; @@ -332,9 +350,9 @@ define([ }; exp.add = function (id, path) { - // TODO WW if (!loggedIn && !config.testMode) { return; } - var data = files[FILES_DATA][id]; + id = Number(id); + var data = files[FILES_DATA][id] || files[SHARED_FOLDERS][id]; if (!data || typeof(data) !== "object") { return; } var newPath = path, parentEl; if (path && !Array.isArray(path)) { @@ -438,7 +456,6 @@ define([ }); delete files[OLD_FILES_DATA]; delete files.migrate; - console.log('done'); todo(); }; if (exp.rt) { @@ -581,7 +598,7 @@ define([ }); }; var fixFilesData = function () { - if (typeof files[FILES_DATA] !== "object") { debug("OLD_FILES_DATA was not an object"); files[FILES_DATA] = {}; } + if (typeof files[FILES_DATA] !== "object") { debug("FILES_DATA was not an object"); files[FILES_DATA] = {}; } var fd = files[FILES_DATA]; var rootFiles = exp.getFiles([ROOT, TRASH, 'hrefArray']); var root = exp.find([ROOT]); @@ -649,7 +666,8 @@ define([ secret = Hash.getSecrets(parsed.type, parsed.hash, el.password); } el.channel = secret.channel; - console.log('Adding missing channel in filesData ', el.channel); + console.log(el); + debug('Adding missing channel in filesData ', el.channel); } catch (e) { console.error(e); } diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js index ac2feb883..2047a017a 100644 --- a/www/common/proxy-manager.js +++ b/www/common/proxy-manager.js @@ -7,17 +7,20 @@ define([ var getConfig = function (Env) { var cfg = {}; - for (var k in Env.cfg) { cfg[k] = Env[k]; } + for (var k in Env.cfg) { cfg[k] = Env.cfg[k]; } return cfg; }; // Add a shared folder to the list var addProxy = function (Env, id, proxy, leave) { - var cfg = getConfig(); + var cfg = getConfig(Env); cfg.sharedFolder = true; cfg.id = id; - var userObject = UserObject.init(proxy, Env.cfg); - userObject.fixFiles(); + var userObject = UserObject.init(proxy, cfg); + if (userObject.fixFiles) { + // Only in outer + userObject.fixFiles(); + } Env.folders[id] = { proxy: proxy, userObject: userObject, @@ -98,7 +101,9 @@ define([ } var current; var uo = Env.user.userObject; - for (var i=2; i<=path.length; i++) { + // We don't need to check the last element of the path because we only need to split it + // when the path contains an element inside the shared folder + for (var i=2; i metadata (href, title, password...) - // f.proxy => listmap - // f.id => id? - manager.addProxy(id, f.proxy); + manager.addProxy(id, f); }); var $tree = APP.$tree = $("#cp-app-drive-tree"); @@ -1166,7 +1179,10 @@ define([ var $target = $(target); var $el = findDataHolder($target); var newPath = $el.data('path'); - if ((!newPath || manager.isFile(manager.find(newPath))) + var dropEl = newPath && manager.find(newPath); + if (newPath && manager.isSharedFolder(dropEl)) { + newPath.push(manager.user.userObject.ROOT); + } else if ((!newPath || manager.isFile(dropEl)) && $target.parents('#cp-app-drive-content')) { newPath = currentPath; } @@ -2293,6 +2309,11 @@ define([ var isTags = path[0] === TAGS; var root = isVirtual ? undefined : manager.find(path); + if (manager.isSharedFolder(root)) { + path.push(manager.user.userObject.ROOT); + root = manager.find(path); + if (!root) { return; } + } if (!isVirtual && typeof(root) === "undefined") { log(Messages.fm_unknownFolderError); debug("Unable to locate the selected directory: ", path); @@ -2434,7 +2455,9 @@ define([ } updateObject(sframeChan, proxy, function () { copyObjectValue(files, proxy.drive); - _displayDirectory(path, force); + updateSharedFolders(sframeChan, files, folders, function () { + _displayDirectory(path, force); + }); }); }; @@ -3058,8 +3081,9 @@ define([ };*/ var sframeChan = common.getSframeChannel(); - updateObject(sframeChan, proxy, waitFor()); - // XXX Load shared folders + updateObject(sframeChan, proxy, waitFor(function () { + updateSharedFolders(sframeChan, proxy.drive, folders, waitFor()); + })); }).nThen(function () { var sframeChan = common.getSframeChannel(); var metadataMgr = common.getMetadataMgr(); diff --git a/www/drive/main.js b/www/drive/main.js index 79347695c..3e96f2e6f 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -53,6 +53,12 @@ define([ Cryptpad.userObjectCommand(data, cb); }); sframeChan.on('Q_DRIVE_GETOBJECT', function (data, cb) { + if (data && data.sharedFolder) { + Cryptpad.getSharedFolder(data.sharedFolder, function (obj) { + cb(obj); + }); + return; + } Cryptpad.getUserObject(function (obj) { cb(obj); }); From 6312dadb600d9f880acb6217cd35bdbe9d586f4a Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 9 Jul 2018 18:11:04 +0200 Subject: [PATCH 09/27] Fix shared folders names in the drive --- www/common/cryptpad-common.js | 8 +++++ www/common/outer/async-store.js | 2 ++ www/common/proxy-manager.js | 10 ++++++ www/common/sframe-protocol.js | 1 + www/drive/inner.js | 61 ++++++++++++++++++++++++++++----- www/drive/main.js | 3 ++ 6 files changed, 77 insertions(+), 8 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index b9d089c6a..0fc70b681 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -103,6 +103,14 @@ define([ common.userObjectCommand = function (data, cb) { postMessage("DRIVE_USEROBJECT", data, cb); }; + common.restoreDrive = function (data, cb) { + postMessage("SET", { + key:['drive'], + value: data + }, function (obj) { + cb(obj); + }); + }; common.drive = {}; common.drive.onLog = Util.mkEvent(); common.drive.onChange = Util.mkEvent(); diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 7c3ec7376..c76f73814 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1173,6 +1173,8 @@ define([ var id; nThen(function (waitFor) { // TODO XXX get the folder data (href, title, ...) + // XXX href should be stored in your drive's .sharedFolders + // and title should be stored in the sharef folder's metadata var folderData = data.folderData || {}; // 1. add the shared folder to our list of shared folders store.userObject.pushSharedFolder(folderData, waitFor(function (err, folderId) { diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js index 2047a017a..6712da200 100644 --- a/www/common/proxy-manager.js +++ b/www/common/proxy-manager.js @@ -677,6 +677,15 @@ define([ return Env.user.userObject.getOwnedPads(edPublic); }; + var getSharedFolderData = function (Env, id) { + if (!Env.folders[id]) { return; } + var obj = Env.folders[id].proxy.metadata || {}; + for (var k in Env.user.proxy[UserObject.SHARED_FOLDERS][id] || {}) { + obj[k] = Env.user.proxy[UserObject.SHARED_FOLDERS][id][k]; + } + return obj; + }; + /* Generic: doesn't need access to a proxy */ var isFile = function (Env, el, allowStr) { return Env.user.userObject.isFile(el, allowStr); @@ -762,6 +771,7 @@ define([ getTagsList: callWithEnv(getTagsList), findFile: callWithEnv(findFile), findChannels: callWithEnv(findChannels), + getSharedFolderData: callWithEnv(getSharedFolderData), // Generic isFile: callWithEnv(isFile), isFolder: callWithEnv(isFolder), diff --git a/www/common/sframe-protocol.js b/www/common/sframe-protocol.js index a23124a65..cc41dcbf3 100644 --- a/www/common/sframe-protocol.js +++ b/www/common/sframe-protocol.js @@ -203,6 +203,7 @@ define({ // Inner drive needs to send command and receive updates from the async store 'Q_DRIVE_USEROBJECT': true, 'Q_DRIVE_GETOBJECT': true, + 'Q_DRIVE_RESTORE': true, // Get the pads deleted from the server by other users to remove them from the drive 'Q_DRIVE_GETDELETED': true, // Store's userObject need to send log messages to inner to display them in the UI diff --git a/www/drive/inner.js b/www/drive/inner.js index 844557f0f..2c78b1904 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -71,6 +71,7 @@ define([ // Icons var faFolder = 'fa-folder'; var faFolderOpen = 'fa-folder-open'; + var faSharedFolder = 'fa-users'; var faReadOnly = 'fa-eye'; var faRename = 'fa-pencil'; var faTrash = 'fa-trash'; @@ -88,6 +89,8 @@ define([ var $folderOpenedIcon = $('', {"class": faFolderOpen + " fa cp-app-drive-icon-folder"}); //var $folderOpenedIcon = $('', {src: "/customize/images/icons/folderOpen.svg", "class": "folder icon"}); var $folderOpenedEmptyIcon = $folderOpenedIcon.clone(); + var $sharedFolderIcon = $('', {"class": faSharedFolder + " fa cp-app-drive-icon-folder"}); + var $sharedFolderOpenedIcon = $sharedFolderIcon.clone(); //var $upIcon = $('', {"class": "fa fa-arrow-circle-up"}); var $unsortedIcon = $('', {"class": "fa fa-files-o"}); var $templateIcon = $('', {"class": "fa fa-cubes"}); @@ -1336,6 +1339,12 @@ define([ var addFolderData = function (element, key, $span) { if (!element || !manager.isFolder(element)) { return; } // The element with the class '.name' is underlined when the 'li' is hovered + if (manager.isSharedFolder(element)) { + var data = manager.getSharedFolderData(element); + key = data && data.title ? data.title : key; + element = manager.folders[element].proxy[manager.user.userObject.ROOT]; + } + var sf = manager.hasSubfolder(element); var files = manager.hasFile(element); var $name = $('', {'class': 'cp-app-drive-element-name'}).text(key); @@ -1372,13 +1381,18 @@ define([ } var element = manager.find(newPath); + var isSharedFolder = manager.isSharedFolder(element); + var $icon = !isFolder ? getFileIcon(element) : undefined; var ro = manager.isReadOnlyFile(element); // ro undefined means it's an old hash which doesn't support read-only var roClass = typeof(ro) === 'undefined' ?' cp-app-drive-element-noreadonly' : ro ? ' cp-app-drive-element-readonly' : ''; var liClass = 'cp-app-drive-element-file cp-app-drive-element' + roClass; - if (isFolder) { + if (isSharedFolder) { + liClass = 'cp-app-drive-element-folder cp-app-drive-element'; + $icon = $sharedFolderIcon.clone(); + } else if (isFolder) { liClass = 'cp-app-drive-element-folder cp-app-drive-element'; $icon = manager.isFolderEmpty(root[key]) ? $folderEmptyIcon.clone() : $folderIcon.clone(); } @@ -1474,11 +1488,20 @@ define([ var isVirtual = virtualCategories.indexOf(path[0]) !== -1; var el = isVirtual ? undefined : manager.find(path); path = path[0] === SEARCH ? path.slice(0,1) : path; + + var skipNext = false; // When encountering a shared folder, skip a key in the path path.forEach(function (p, idx) { + if (skipNext) { return skipNext = false; } if (isTrash && [2,3].indexOf(idx) !== -1) { return; } var name = p; + var el = manager.find(path.slice(0, idx+1)); + if (manager.isSharedFolder(el)) { + name = manager.getSharedFolderData(el).title; + skipNext = true; + } + var $span = $('', {'class': 'cp-app-drive-path-element'}); if (idx < path.length - 1) { if (!noStyle) { @@ -1844,7 +1867,12 @@ define([ if (keys.length < 2) { return keys; } var mult = asc ? 1 : -1; var getProp = function (el, prop) { - if (folder) { return el.toLowerCase(); } + if (folder && root[el] && manager.isSharedFolder(root[el])) { + var title = manager.getSharedFolderData(root[el]).title || el; + return title.toLowerCase(); + } else if (folder) { + return el.toLowerCase(); + } var id = useId ? el : root[el]; var data = manager.getFileData(id); if (!data) { return ''; } @@ -2541,12 +2569,28 @@ define([ if (!manager.isFolder(root[key])) { return; } var newPath = path.slice(); newPath.push(key); - var isCurrentFolder = manager.comparePath(newPath, currentPath); - var isEmpty = manager.isFolderEmpty(root[key]); - var subfolder = manager.hasSubfolder(root[key]); - var $icon = isEmpty ? - (isCurrentFolder ? $folderOpenedEmptyIcon : $folderEmptyIcon) : - (isCurrentFolder ? $folderOpenedIcon : $folderIcon); + var isSharedFolder = manager.isSharedFolder(root[key]); + var $icon, isCurrentFolder, subfolder; + if (isSharedFolder) { + var fId = root[key]; + // Fix path + newPath.push(manager.user.userObject.ROOT); + isCurrentFolder = manager.comparePath(newPath, currentPath); + // Subfolders? + root = manager.folders[fId].proxy[manager.user.userObject.ROOT]; + subfolder = manager.hasSubfolder(root); + // Fix name + key = manager.getSharedFolderData(fId).title; + // Fix icon + $icon = $sharedFolderIcon; + } else { + var isEmpty = manager.isFolderEmpty(root[key]); + subfolder = manager.hasSubfolder(root[key]); + isCurrentFolder = manager.comparePath(newPath, currentPath); + $icon = isEmpty ? + (isCurrentFolder ? $folderOpenedEmptyIcon : $folderEmptyIcon) : + (isCurrentFolder ? $folderOpenedIcon : $folderIcon); + } var $element = createTreeElement(key, $icon.clone(), newPath, true, true, subfolder, isCurrentFolder); $element.appendTo($list); $element.find('>.cp-app-drive-element-row').contextmenu(openContextMenu('tree')); @@ -3113,6 +3157,7 @@ define([ APP.histConfig = { onLocal: function () { proxy.drive = history.currentObj.drive; + sframeChan.query("Q_DRIVE_RESTORE", history.currentObj.drive, function () {}); }, onRemote: function () {}, setHistory: setHistory, diff --git a/www/drive/main.js b/www/drive/main.js index 3e96f2e6f..3a600d18c 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -52,6 +52,9 @@ define([ sframeChan.on('Q_DRIVE_USEROBJECT', function (data, cb) { Cryptpad.userObjectCommand(data, cb); }); + sframeChan.on('Q_DRIVE_RESTORE', function (data, cb) { + Cryptpad.restoreDrive(data, cb); + }); sframeChan.on('Q_DRIVE_GETOBJECT', function (data, cb) { if (data && data.sharedFolder) { Cryptpad.getSharedFolder(data.sharedFolder, function (obj) { From bc7524c134a0a017a32969827f5c4254040cae14 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 10 Jul 2018 10:39:21 +0200 Subject: [PATCH 10/27] Fix tippy --- www/common/common-interface.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 5e5c75176..b1308bc7b 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -826,13 +826,13 @@ define([ var out = false; var xId = $(x).attr('aria-describedby'); if (xId) { - if (xId.indexOf('tippy-tooltip-') === 0) { + if (xId.indexOf('tippy-') === 0) { return true; } } $(x).find('[aria-describedby]').each(function (i, el) { var id = el.getAttribute('aria-describedby'); - if (id.indexOf('tippy-tooltip-') !== 0) { return; } + if (id.indexOf('tippy-') !== 0) { return; } out = true; }); return out; From 4b86ed2dec62afbee70d3c47177cc6389f07a759 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 10 Jul 2018 14:41:37 +0200 Subject: [PATCH 11/27] Shared folders UI --- customize.dist/translations/messages.fr.js | 1 + customize.dist/translations/messages.js | 1 + www/common/common-ui-elements.js | 24 ++++---- www/common/cryptpad-common.js | 11 ++++ www/common/outer/async-store.js | 22 +++++--- www/common/outer/store-rpc.js | 1 + www/common/outer/userObject.js | 10 +++- www/common/proxy-manager.js | 28 +++++++++- www/drive/inner.js | 65 +++++++++++++++++----- www/drive/main.js | 16 +++++- 10 files changed, 142 insertions(+), 37 deletions(-) diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index dc1a76040..520a6ceaf 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -454,6 +454,7 @@ define(function () { out.fc_delete_owned = "Supprimer du serveur"; out.fc_restore = "Restaurer"; out.fc_remove = "Supprimer de votre CryptDrive"; + out.fc_remove_sharedfolder = "Supprimer"; out.fc_empty = "Vider la corbeille"; out.fc_prop = "Propriétés"; out.fc_hashtag = "Mots-clés"; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 07fe95c7a..a9299e71a 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -454,6 +454,7 @@ define(function () { out.fc_delete_owned = "Delete from the server"; out.fc_restore = "Restore"; out.fc_remove = "Remove from your CryptDrive"; + out.fc_remove_sharedfolder = "Remove"; out.fc_empty = "Empty the trash"; out.fc_prop = "Properties"; out.fc_hashtag = "Tags"; diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 3d057ed04..ab7d304f3 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -222,17 +222,21 @@ define([ })); } - $('