diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 7f3aebb00..f746399fa 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1148,6 +1148,7 @@ define([ var loadSharedFolder = function (id, data, cb) { var parsed = Hash.parsePadUrl(data.href); var secret = Hash.getSecrets('drive', parsed.hash, data.password); + var owners = data.owners; var listmapConfig = { data: {}, websocketURL: NetConfig.getWebsocketURL(), @@ -1159,85 +1160,39 @@ define([ logLevel: 1, ChainPad: ChainPad, classic: true, + owners: owners }; var rt = Listmap.create(listmapConfig); store.sharedFolders[id] = rt; rt.proxy.on('ready', function (info) { store.manager.addProxy(id, rt.proxy, info.leave); - cb(rt); + cb(rt, info.metadata); }); return rt; }; Store.addSharedFolder = function (clientId, data, cb) { - var path = data.path; - var folderData = data.folderData || {}; - var id; - nThen(function (waitFor) { - Store.pinPads(clientId, [folderData.channel], waitFor()); - }).nThen(function (waitFor) { - // 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 - loadSharedFolder(id, data.folderData, waitFor(function (rt) { - if (data.metadata) { // Creating a new shared folder - rt.proxy.metadata = data.metadata; - onSync(waitFor()); - } - })); - }).nThen(function () { - sendDriveEvent('DRIVE_CHANGE', { - path: ['drive'].concat(path) - }, clientId); - cb(); - }); - }; - store.createSharedFolder = function () { - // XXX - var hash = Hash.createRandomHash('drive'); - var href = '/drive/#' + hash; - var secret = Hash.getSecrets('drive', hash); - Store.addSharedFolder(null, { - path: ['root'], - folderData: { - href: href, - roHref: '/drive/#' + Hash.getViewHashFromKeys(secret), - channel: secret.channel, - ctime: +new Date() - }, - metadata: { - title: "Test" - } - }, function () { - console.log('done'); - }); + Store.userObjectCommand(clientId, { + cmd: 'addSharedFolder', + data: data + }, cb); }; - // Drive Store.userObjectCommand = function (clientId, cmdData, cb) { if (!cmdData || !cmdData.cmd) { return; } - var data = cmdData.data; + //var data = cmdData.data; var cb2 = function (data2) { - var paths = data.paths || [data.path] || []; - paths = paths.concat(data.newPath || []); - paths.forEach(function (p) { + //var paths = data.paths || [data.path] || []; + //paths = paths.concat(data.newPath ? [data.newPath] : []); + //paths.forEach(function (p) { sendDriveEvent('DRIVE_CHANGE', { - //path: ['drive', UserObject.FILES_DATA] - path: ['drive'].concat(p) + path: ['drive', UserObject.FILES_DATA] + //path: ['drive'].concat(p) }, clientId); + //}); + onSync(function () { + cb(data2); }); - cb(data2); }; store.manager.command(cmdData, cb2); }; @@ -1376,9 +1331,10 @@ define([ if (!store.loggedIn) { return void cb(); } Store.pinPads(null, data, cb); }; - var manager = store.manager = ProxyManager.create(proxy.drive, proxy.edPublic, pin, unpin, { + var manager = store.manager = ProxyManager.create(proxy.drive, proxy.edPublic, + pin, unpin, loadSharedFolder, { outer: true, - removeOwnedChannel: function (data, cb) { Store.removeOwnedChannel(null, data, cb); }, + removeOwnedChannel: function (data, cb) { Store.removeOwnedChannel('', data, cb); }, edPublic: store.proxy.edPublic, loggedIn: store.loggedIn, log: function (msg) { diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index 703426d56..defc4dbd9 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -83,13 +83,14 @@ define([ // 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, cb) { - if (!loggedIn && !config.testMode) { return; } + if (!loggedIn && !config.testMode) { return void cb(); } var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]); var toClean = []; - exp.getFiles([FILES_DATA]).forEach(function (id) { + exp.getFiles([FILES_DATA, SHARED_FOLDERS]).forEach(function (id) { + // XXX if (filesList.indexOf(id) === -1) { - var fd = exp.getFileData(id); + var fd = exp.isSharedFolder(id) ? files[SHARED_FOLDERS][id] : exp.getFileData(id); var channelId = fd.channel; // If trying to remove an owned pad, remove it from server also if (!isOwnPadRemoved && !sharedFolder && @@ -108,10 +109,14 @@ define([ }); } if (channelId) { toClean.push(channelId); } - spliceFileData(id); + if (exp.isSharedFolder(id)) { + delete files[SHARED_FOLDERS][id]; + } else { + spliceFileData(id); + } } }); - if (!toClean.length) { return; } + if (!toClean.length) { return void cb(); } cb(null, toClean); }; var deleteHrefs = function (ids) { @@ -672,6 +677,17 @@ define([ var fixSharedFolders = function () { if (sharedFolder) { return; } if (typeof(files[SHARED_FOLDERS]) !== "object") { debug("SHARED_FOLDER was not an object"); files[SHARED_FOLDERS] = {}; } + var sf = files[SHARED_FOLDERS]; + var rootFiles = exp.getFiles([ROOT]); + var root = exp.find([ROOT]); + for (var id in sf) { + id = Number(id); + if (rootFiles.indexOf(id) === -1) { + console.log('missing' + id); + var newName = Hash.createChannelId(); + root[newName] = id; + } + } }; var fixDrive = function () { diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js index de69bdd59..ca927cfe3 100644 --- a/www/common/proxy-manager.js +++ b/www/common/proxy-manager.js @@ -1,8 +1,9 @@ define([ '/common/userObject.js', '/common/common-util.js', + '/common/common-hash.js', '/bower_components/nthen/index.js', -], function (UserObject, Util, nThen) { +], function (UserObject, Util, Hash, nThen) { var getConfig = function (Env) { @@ -315,6 +316,65 @@ define([ cb(obj); }); }; + // Add a folder/subfolder + var _addSharedFolder = function (Env, data, cb) { + console.log(data); + data = data || {}; + var resolved = _resolvePath(Env, data.path); + if (!resolved || !resolved.userObject) { return void cb({error: 'E_NOTFOUND'}); } + if (resolved.id) { return void cb({error: 'EINVAL'}); } + if (!Env.pinPads) { return void cb({error: 'EAUTH'}); } + + var folderData = data.folderData || {}; + + var id; + nThen(function () { + // Check if it is an imported folder or a folder creation + if (data.folderData) { return; } + + // Folder creation + var hash = Hash.createRandomHash('drive'); + var href = '/drive/#' + hash; + var secret = Hash.getSecrets('drive', hash); + folderData = { + href: href, + roHref: '/drive/#' + Hash.getViewHashFromKeys(secret), + channel: secret.channel, + ctime: +new Date() + }; + if (data.password) { folderData.password = data.password; } + if (data.owned) { folderData.owners = [Env.edPublic]; } + }).nThen(function (waitFor) { + Env.pinPads([folderData.channel], waitFor()); + }).nThen(function (waitFor) { + // 1. add the shared folder to our list of shared folders + Env.user.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 + Env.user.userObject.add(id, resolved.path); + + // 2b. load the proxy + Env.loadSharedFolder(id, folderData, waitFor(function (rt, metadata) { + if (data.name && !rt.proxy.metadata) { // Creating a new shared folder + rt.proxy.metadata = {title: data.name}; + } + // If we're importing a folder, check its serverside metadata + if (data.folderData && metadata) { + var fData = Env.user.proxy[UserObject.SHARED_FOLDERS][id]; + if (metadata.owners) { fData.owners = metadata.owners; } + if (metadata.expire) { fData.expire = +metadata.expire; } + } + })); + }).nThen(function () { + cb(id); + }); + }; // Delete permanently some pads or folders var _delete = function (Env, data, cb) { data = data || {}; @@ -327,13 +387,13 @@ define([ nThen(function (waitFor)  { if (resolved.main.length) { Env.user.userObject.delete(resolved.main, waitFor(function (err, _toUnpin) { - if (!Env.unpinPads) { return; } + if (!Env.unpinPads || !_toUnpin) { return; } Array.prototype.push.apply(toUnpin, _toUnpin); }), data.nocheck, data.isOwnPadRemoved); } Object.keys(resolved.folders).forEach(function (id) { Env.folders[id].userObject.delete(resolved.folders[id], waitFor(function (err, _toUnpin) { - if (!Env.unpinPads) { return; } + if (!Env.unpinPads || !_toUnpin) { return; } Array.prototype.push.apply(toUnpin, _toUnpin); }), data.nocheck, data.isOwnPadRemoved); }); @@ -389,6 +449,8 @@ define([ _restore(Env, data, cb); break; case 'addFolder': _addFolder(Env, data, cb); break; + case 'addSharedFolder': + _addSharedFolder(Env, data, cb); break; case 'delete': _delete(Env, data, cb); break; case 'emptyTrash': @@ -483,7 +545,7 @@ define([ // Get the list of channels filtered by a type (expirable channels, owned channels, pin list) var getChannelsList = function (Env, edPublic, type) { - if (!edPublic) { return; } + //if (!edPublic) { return; } var result = []; var addChannel = function (userObject) { if (type === 'expirable') { @@ -492,7 +554,7 @@ 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) + if ((data.owners && data.owners.length && (!edPublic || data.owners.indexOf(edPublic) === -1)) || (data.expire && data.expire < (+new Date()))) { result.push(data.channel); } @@ -524,6 +586,9 @@ define([ } }; + if (type === 'owned' && !edPublic) { return result; } + if (type === 'pin' && !edPublic) { return result; } + // Get the list of user objects var userObjects = _getUserObjects(Env); @@ -532,6 +597,17 @@ define([ files.forEach(addChannel(uo)); }); + // NOTE: expirable shared folder should be added here if we ever decide to enable them + if (type === "owned") { + var sfOwned = Object.keys(Env.user.proxy[UserObject.SHARED_FOLDERS]).filter(function (fId) { + var owners = Env.user.proxy[UserObject.SHARED_FOLDERS][fId].owners; + if (Array.isArray(owners) && owners.length && + owners.indexOf(edPublic) !== -1) { return true; } + }).map(function (fId) { + return Env.user.proxy[UserObject.SHARED_FOLDERS][fId].channel; + }); + Array.prototype.push.apply(result, sfOwned); + } if (type === "pin") { var sfChannels = Object.keys(Env.folders).map(function (fId) { return Env.user.proxy[UserObject.SHARED_FOLDERS][fId].channel; @@ -566,10 +642,11 @@ define([ }); }; - var create = function (proxy, edPublic, pinPads, unpinPads, uoConfig) { + var create = function (proxy, edPublic, pinPads, unpinPads, loadSf, uoConfig) { var Env = { pinPads: pinPads, unpinPads: unpinPads, + loadSharedFolder: loadSf, cfg: uoConfig, edPublic: edPublic, user: { @@ -642,6 +719,18 @@ define([ } }, cb); }; + var addSharedFolderInner = function (Env, path, data, cb) { + console.log(data); + return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", { + cmd: "addSharedFolder", + data: { + path: path, + name: data.name, + owned: data.owned, + password: data.password + } + }, cb); + }; var deleteInner = function (Env, paths, cb, nocheck, isOwnPadRemoved, noUnpin) { return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", { cmd: "delete", @@ -822,6 +911,7 @@ define([ move: callWithEnv(moveInner), emptyTrash: callWithEnv(emptyTrashInner), addFolder: callWithEnv(addFolderInner), + addSharedFolder: callWithEnv(addSharedFolderInner), delete: callWithEnv(deleteInner), restore: callWithEnv(restoreInner), // Tools diff --git a/www/common/userObject.js b/www/common/userObject.js index ce1cfb665..7a1576f96 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -213,7 +213,7 @@ define([ var getFilesRecursively = exp.getFilesRecursively = function (root, arr) { arr = arr || []; for (var e in root) { - if (isFile(root[e])) { + if (isFile(root[e]) || isSharedFolder(root[e])) { if(arr.indexOf(root[e]) === -1) { arr.push(root[e]); } } else { getFilesRecursively(root[e], arr); @@ -275,10 +275,15 @@ define([ if (!files[FILES_DATA]) { return ret; } return Object.keys(files[FILES_DATA]).map(Number); }; + _getFiles[SHARED_FOLDERS] = function () { + var ret = []; + if (!files[SHARED_FOLDERS]) { return ret; } + return Object.keys(files[SHARED_FOLDERS]).map(Number); + }; var getFiles = exp.getFiles = function (categories) { var ret = []; if (!categories || !categories.length) { - categories = [ROOT, 'hrefArray', TRASH, OLD_FILES_DATA, FILES_DATA]; + categories = [ROOT, 'hrefArray', TRASH, OLD_FILES_DATA, FILES_DATA, SHARED_FOLDERS]; } categories.forEach(function (c) { if (typeof _getFiles[c] === "function") { diff --git a/www/drive/inner.js b/www/drive/inner.js index d648ed092..d74d61d06 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -213,10 +213,11 @@ define([ for (var k in objRef) { delete objRef[k]; } $.extend(true, objRef, objToCopy); }; - var updateSharedFolders = function (sframeChan, drive, folders, cb) { + var updateSharedFolders = function (sframeChan, manager, drive, folders, cb) { if (!drive || !drive.sharedFolders) { return void cb(); } + var oldIds = Object.keys(folders); nThen(function (waitFor) { Object.keys(drive.sharedFolders).forEach(function (fId) { sframeChan.query('Q_DRIVE_GETOBJECT', { @@ -224,6 +225,9 @@ define([ }, waitFor(function (err, newObj) { folders[fId] = folders[fId] || {}; copyObjectValue(folders[fId], newObj); + if (manager && oldIds.indexOf(fId) === -1) { + manager.addProxy(fId, folders[fId]); + } })); }); }).nThen(function () { @@ -260,6 +264,10 @@ define([ 'tabindex': '-1', 'data-icon': faFolder, }, Messages.fc_newfolder)), + h('li', h('a.cp-app-drive-context-newsharedfolder.dropdown-item.cp-app-drive-context-editable', { + 'tabindex': '-1', + 'data-icon': faSharedFolder, + }, 'New SF')), // XXX h('li', h('a.cp-app-drive-context-hashtag.dropdown-item.cp-app-drive-context-editable', { 'tabindex': '-1', 'data-icon': faTags, @@ -786,6 +794,7 @@ define([ if (type === "content") { filter = function ($el, className) { if (className === 'newfolder') { return; } + if (className === 'newsharedfolder') { return; } return AppConfig.availablePadTypes.indexOf($el.attr('data-type')) === -1; }; } else { @@ -869,7 +878,7 @@ define([ switch(type) { case 'content': - show = ['newfolder', 'newdoc']; + show = ['newfolder', 'newsharedfolder', 'newdoc']; break; case 'tree': show = ['open', 'openro', 'rename', 'delete', 'deleteowned', 'removesf', @@ -1648,6 +1657,52 @@ define([ $container.append($button); }; + // Get the upload options + var addSharedFolderModal = function (cb) { + var createHelper = function (href, text) { + var q = h('a.fa.fa-question-circle', { + style: 'text-decoration: none !important;', + title: text, + href: APP.origin + href, + target: "_blank", + 'data-tippy-placement': "right" + }); + return q; + }; + + // Ask for name, password and owner + // XXX + var content = h('div', [ + h('h4', "NEW SF TITLE"), + h('label', {for: 'cp-app-drive-sf-name'}, 'SF NAME'), + h('input#cp-app-drive-sf-name', {type: 'text', placeholder: "NEW FOLDER NAME"}), + //h('label', {for: 'cp-app-drive-sf-password'}, 'SF PASSWORD'), + //UI.passwordInput({id: 'cp-app-drive-sf-password'}), + h('span', { + style: 'display:flex;align-items:center;justify-content:space-between' + }, [ + UI.createCheckbox('cp-app-drive-sf-owned', "OWNED?", true), + createHelper('/faq.html#keywords-owned', Messages.creation_owned1) // TODO + ]), + ]); + + UI.confirm(content, function (yes) { + if (!yes) { return void cb(); } + + // Get the values + var newName = $(content).find('#cp-app-drive-sf-name').val(); + //var password = $(content).find('#cp-app-drive-sf-password').val() || undefined; + var password; + var owned = $(content).find('#cp-app-drive-sf-owned').is(':checked'); + + cb({ + name: newName, + password: password, + owned: owned + }); + }); + }; + var getNewPadTypes = function () { var arr = []; AppConfig.availablePadTypes.forEach(function (type) { @@ -1680,6 +1735,12 @@ define([ .click(function () { manager.addFolder(currentPath, null, onCreated); }); + $block.find('a.cp-app-drive-new-shared-folder, li.cp-app-drive-new-shared-folder') + .click(function () { + addSharedFolderModal(function (obj) { + manager.addSharedFolder(currentPath, obj, refresh); + }); + }); $block.find('a.cp-app-drive-new-upload, li.cp-app-drive-new-upload') .click(function () { var $input = $('', { @@ -1719,6 +1780,11 @@ define([ attributes: {'class': 'cp-app-drive-new-folder'}, content: $('
').append($folderIcon.clone()).html() + Messages.fm_folder }); + options.push({ + tag: 'a', + attributes: {'class': 'cp-app-drive-new-shared-folder'}, + content: $('
').append($folderIcon.clone()).html() + "NEW SF" // XXX + }); options.push({tag: 'hr'}); options.push({ tag: 'a', @@ -1967,6 +2033,13 @@ define([ }).prepend($folderIcon.clone()).appendTo($container); $element1.append($('', { 'class': 'cp-app-drive-new-name' }) .text(Messages.fm_folder)); + // Shared Folder + var $element3 = $('
  • ', { + 'class': 'cp-app-drive-new-shared-folder cp-app-drive-element-row ' + + 'cp-app-drive-element-grid' + }).prepend($folderIcon.clone()).appendTo($container); + $element3.append($('', { 'class': 'cp-app-drive-new-name' }) + .text("SF")); // XXX // File var $element2 = $('
  • ', { 'class': 'cp-app-drive-new-upload cp-app-drive-element-row ' + @@ -2510,7 +2583,7 @@ define([ } updateObject(sframeChan, proxy, function () { copyObjectValue(files, proxy.drive); - updateSharedFolders(sframeChan, files, folders, function () { + updateSharedFolders(sframeChan, manager, files, folders, function () { _displayDirectory(path, force); }); }); @@ -2590,7 +2663,14 @@ define([ // Display root content var $list = $('
      ').appendTo($container); - var keys = Object.keys(root).sort(); + var keys = Object.keys(root).sort(function (a, b) { + var newA = manager.isSharedFolder(root[a]) ? + manager.getSharedFolderData(root[a]).title : a; + var newB = manager.isSharedFolder(root[b]) ? + manager.getSharedFolderData(root[b]).title : b; + return newA < newB ? -1 : + (newA === newB ? 0 : 1); + }); keys.forEach(function (key) { // Do not display files in the menu if (!manager.isFolder(root[key])) { return; } @@ -2604,8 +2684,8 @@ define([ 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); + var newRoot = manager.folders[fId].proxy[manager.user.userObject.ROOT]; + subfolder = manager.hasSubfolder(newRoot); // Fix name key = manager.getSharedFolderData(fId).title; // Fix icon @@ -2893,6 +2973,12 @@ define([ }; manager.addFolder(paths[0].path, null, onFolderCreated); } + else if ($(this).hasClass('cp-app-drive-context-newsharedfolder')) { + if (paths.length !== 1) { return; } + addSharedFolderModal(function (obj) { + manager.addSharedFolder(paths[0].path, obj, refresh); + }); + } else if ($(this).hasClass("cp-app-drive-context-newdoc")) { var ntype = $(this).data('type') || 'pad'; var path2 = manager.isPathIn(currentPath, [TRASH]) ? '' : currentPath; @@ -3165,7 +3251,7 @@ define([ var sframeChan = common.getSframeChannel(); updateObject(sframeChan, proxy, waitFor(function () { - updateSharedFolders(sframeChan, proxy.drive, folders, waitFor()); + updateSharedFolders(sframeChan, null, proxy.drive, folders, waitFor()); })); }).nThen(function () { var sframeChan = common.getSframeChannel();