Merge branch 'sharedfolder' into staging

This commit is contained in:
yflory
2018-07-18 14:18:30 +02:00
18 changed files with 2173 additions and 694 deletions

View File

@@ -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

View File

@@ -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', [
@@ -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; }
@@ -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;

View File

@@ -73,26 +73,14 @@ define([
data.password = val;
}));
}).nThen(function (waitFor) {
var base = common.getMetadataMgr().getPrivateData().origin;
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
if (!val) { return; }
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('roHref', waitFor(function (err, val) {
if (!val) { return; }
data.roHref = base + val;
}));
common.getPadAttribute('channel', waitFor(function (err, val) {
data.channel = val;
@@ -162,7 +150,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;
@@ -191,7 +179,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: newPass
}, function (err, data) {
if (err || data.error) {
@@ -203,11 +191,11 @@ define([
// If we had a password and we removed it, we have to remove the /p/
if (data.warning) {
return void UI.alert(Messages.properties_passwordWarning, function () {
common.gotoURL(hasPassword && newPass ? undefined : data.href);
common.gotoURL(hasPassword && newPass ? undefined : (data.href || data.roHref));
}, {force: true});
}
return void UI.alert(Messages.properties_passwordSuccess, function () {
common.gotoURL(hasPassword && newPass ? undefined : data.href);
common.gotoURL(hasPassword && newPass ? undefined : (data.href || data.roHref));
}, {force: true});
});
});
@@ -242,17 +230,21 @@ define([
}));
}
$('<label>', {'for': 'cp-app-prop-ctime'}).text(Messages.fm_creation)
.appendTo($d);
$d.append(UI.dialog.selectable(new Date(data.ctime).toLocaleString(), {
id: 'cp-app-prop-ctime',
}));
if (data.ctime) {
$('<label>', {'for': 'cp-app-prop-ctime'}).text(Messages.fm_creation)
.appendTo($d);
$d.append(UI.dialog.selectable(new Date(data.ctime).toLocaleString(), {
id: 'cp-app-prop-ctime',
}));
}
$('<label>', {'for': 'cp-app-prop-atime'}).text(Messages.fm_lastAccess)
.appendTo($d);
$d.append(UI.dialog.selectable(new Date(data.atime).toLocaleString(), {
id: 'cp-app-prop-atime',
}));
if (data.atime) {
$('<label>', {'for': 'cp-app-prop-atime'}).text(Messages.fm_lastAccess)
.appendTo($d);
$d.append(UI.dialog.selectable(new Date(data.atime).toLocaleString(), {
id: 'cp-app-prop-atime',
}));
}
if (common.isLoggedIn() && AppConfig.enablePinning) {
// check the size of this file...
@@ -533,6 +525,35 @@ define([
}
return tabs;
};
UIElements.createSFShareModal = function (config) {
var origin = config.origin;
var pathname = config.pathname;
var hashes = config.hashes;
if (!hashes.editHash) { throw new Error("You must provide a valid hash"); }
var url = origin + pathname + '#' + hashes.editHash;
// Share link tab
var link = h('div.cp-share-modal', [
h('label', Messages.sharedFolders_share),
h('br'),
UI.dialog.selectable(url, { id: 'cp-share-link-preview', tabindex: 1 })
]);
var linkButtons = [{
name: Messages.cancel,
onClick: function () {},
keys: [27]
}, {
className: 'primary',
name: Messages.share_linkCopy,
onClick: function () {
var success = Clipboard.copy(url);
if (success) { UI.log(Messages.shareSuccess); }
},
keys: [13]
}];
return UI.dialog.customModal(link, {buttons: linkButtons});
};
UIElements.createButton = function (common, type, rightside, data, callback) {
var AppConfig = common.getAppConfig();
@@ -695,17 +716,27 @@ define([
button
.click(common.prepareFeedback(type))
.click(function() {
var msg = common.isLoggedIn() ? Messages.forgetPrompt : Messages.fm_removePermanentlyDialog;
UI.confirm(msg, function (yes) {
if (!yes) { return; }
sframeChan.query('Q_MOVE_TO_TRASH', null, function (err) {
if (err) { return void callback(err); }
var cMsg = common.isLoggedIn() ? Messages.movedToTrash : Messages.deleted;
var msg = common.fixLinks($('<div>').html(cMsg));
UI.alert(msg);
callback();
sframeChan.query('Q_IS_ONLY_IN_SHARED_FOLDER', null, function (err, res) {
if (err || res.error) { return void console.log(err || res.error); }
var msg = Messages.forgetPrompt;
if (res) {
UI.alert(Messages.sharedFolders_forget);
return;
} else if (!common.isLoggedIn()) {
msg = Messages.fm_removePermanentlyDialog;
}
UI.confirm(msg, function (yes) {
if (!yes) { return; }
sframeChan.query('Q_MOVE_TO_TRASH', null, function (err) {
if (err) { return void callback(err); }
var cMsg = common.isLoggedIn() ? Messages.movedToTrash : Messages.deleted;
var msg = common.fixLinks($('<div>').html(cMsg));
UI.alert(msg);
callback();
return;
});
});
});
});
break;

View File

@@ -84,6 +84,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 = {
@@ -99,6 +104,25 @@ 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.addSharedFolder = function (secret, cb) {
postMessage("ADD_SHARED_FOLDER", {
path: ['root'],
folderData: {
href: '/drive/#' + Hash.getEditHashFromKeys(secret),
roHref: '/drive/#' + Hash.getViewHashFromKeys(secret),
channel: secret.channel,
ctime: +new Date()
}
}, cb);
};
common.drive = {};
common.drive.onLog = Util.mkEvent();
common.drive.onChange = Util.mkEvent();
@@ -294,6 +318,13 @@ define([
});
};
common.isOnlyInSharedFolder = function (data, cb) {
postMessage("IS_ONLY_IN_SHARED_FOLDER", data, function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
cb(null, obj);
});
};
common.setDisplayName = function (value, cb) {
postMessage("SET_DISPLAY_NAME", value, cb);
};
@@ -506,7 +537,8 @@ define([
if (common.initialPath) {
if (!data.path) {
data.path = common.initialPath;
data.path = Array.isArray(common.initialPath) ? common.initialPath
: decodeURIComponent(common.initialPath).split(',');
delete common.initialPath;
}
}
@@ -620,7 +652,7 @@ define([
if (!parsed.hash) { return void cb({ error: 'EINVAL_HREF' }); }
var warning = false;
var newHash;
var newHash, newRoHref;
var oldChannel;
var newSecret;
@@ -693,6 +725,11 @@ define([
common.setPadAttribute('channel', newSecret.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 && newPassword) { return; } // same hash
common.setPadAttribute('href', newHref, waitFor(function (err) {
@@ -702,7 +739,8 @@ define([
cb({
warning: warning,
hash: newHash,
href: newHref
href: newHref,
roHref: newRoHref
});
});
};

View File

@@ -6,84 +6,6 @@ define([
], function (Crypt, FO, Hash, Realtime) {
var exp = {};
var getType = function (el) {
if (el === null) { return "null"; }
return Array.isArray(el) ? "array" : typeof(el);
};
var findAvailableKey = function (obj, key) {
if (typeof (obj[key]) === "undefined") { return key; }
var i = 1;
var nkey = key;
while (typeof (obj[nkey]) !== "undefined") {
nkey = key + '_' + i;
i++;
}
return nkey;
};
var createFromPath = function (proxy, oldFo, path, id) {
var root = proxy.drive;
if (!oldFo.isFile(id)) { return; }
var error = function (msg) {
console.error(msg || "Unable to find that path", path);
};
if (oldFo.isInTrashRoot(path)) {
id = oldFo.find(path.slice(0,3));
path.pop();
}
var next, nextRoot;
path.forEach(function (p, i) {
if (!root) { return; }
if (typeof(p) === "string") {
if (getType(root) !== "object") { root = undefined; error(); return; }
if (i === path.length - 1) {
root[Hash.createChannelId()] = id;
return;
}
next = getType(path[i+1]);
nextRoot = getType(root[p]);
if (nextRoot !== "undefined") {
if (next === "string" && nextRoot === "object" || next === "number" && nextRoot === "array") {
root = root[p];
return;
}
p = findAvailableKey(root, p);
}
if (next === "number") {
root[p] = [];
root = root[p];
return;
}
root[p] = {};
root = root[p];
return;
}
// Path contains a non-string element: it's an array index
if (typeof(p) !== "number") { root = undefined; error(); return; }
if (getType(root) !== "array") { root = undefined; error(); return; }
if (i === path.length - 1) {
if (root.indexOf(id) === -1) { root.push(id); }
return;
}
next = getType(path[i+1]);
if (next === "number") {
error('2 consecutives arrays in the user object');
root = undefined;
//root.push([]);
//root = root[root.length - 1];
return;
}
root.push({});
root = root[root.length - 1];
return;
});
};
exp.anonDriveIntoUser = function (proxyData, fsHash, cb) {
// Make sure we have an FS_hash and we don't use it, otherwise just stop the migration and cb
if (!fsHash || !proxyData.loggedIn) {
@@ -105,43 +27,38 @@ define([
if (parsed) {
var proxy = proxyData.proxy;
var oldFo = FO.init(parsed.drive, {
loggedIn: proxyData.loggedIn,
pinPads: function () {} // without pinPads /outer/userObject.js won't be loaded
loggedIn: true,
outer: true
});
var onMigrated = function () {
oldFo.fixFiles(true);
var newFo = proxyData.userObject;
var oldRecentPads = parsed.drive[newFo.FILES_DATA];
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;
});
var manager = proxyData.manager;
var oldFiles = oldFo.getFiles([oldFo.FILES_DATA]);
oldFiles.forEach(function (id) {
var href = oldRecentPads[id].href;
// 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 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);
var data = oldFo.getFileData(id);
var channel = data.channel;
var datas = manager.findChannel(channel, true);
// Do not migrate a pad if we already have it, it would create a duplicate
// in the drive
if (datas.length !== 0) {
// We want to merge a read-only pad: it can't be stronger than what
// we already have so abort
if (!data.href) { return; }
// We want to merge an edit pad: check if we have the same channel
// but read-only and upgrade it in that case
datas.forEach(function (pad) {
if (!pad.href) { data.href = pad.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);
if (paths.length === 0) { return; }
// Add the file data in our array and use the id to add the file
var data = oldFo.getFileData(id);
// Here it means we have a new href, so we should add it to the drive
if (data) {
newFo.pushData(data, function (err, id) {
if (err) { return void console.error("Cannot import file:", data, err); }
createFromPath(proxy, oldFo, paths[0], id);
manager.addPad(null, data, function (err) {
if (err) {
return void console.error("Cannot import file:", data, err);
}
});
}
});

View File

@@ -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

View File

@@ -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 = {};
@@ -27,17 +28,23 @@ define([
var postMessage = function () {};
var broadcast = function () {};
var sendDriveEvent = function () {};
var registerProxyEvents = function () {};
var storeHash;
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.whenRealtimeSyncs(store.sharedFolders[k].realtime, waitFor());
}
}
}).nThen(function () { cb(); });
};
Store.get = function (clientId, key, cb) {
cb(Util.find(store.proxy, key));
};
@@ -55,6 +62,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' &&
@@ -78,17 +92,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.getChannelsList(edPublic, 'pin');
// Get the avatar
var profile = store.proxy.profile;
@@ -111,19 +118,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) {
@@ -380,7 +376,7 @@ define([
Store.getDeletedPads = function (clientId, data, cb) {
if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); }
var list = getCanonicalChannelList(true);
var list = (data && data.list) || getCanonicalChannelList(true);
if (!Array.isArray(list)) {
return void cb({error: 'INVALID_FILE_LIST'});
}
@@ -435,10 +431,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)),
@@ -446,16 +443,21 @@ 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);
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; }
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) {
if (e) { return void cb({error: "Error while adding a template:"+ e}); }
var path = data.path || ['root'];
store.userObject.add(id, path);
store.manager.addPad(data.path, pad, function (e) {
if (e) { return void cb({error: "Error while adding the pad:"+ e}); }
sendDriveEvent('DRIVE_CHANGE', {
path: ['drive', UserObject.FILES_DATA]
}, clientId);
@@ -464,17 +466,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));
@@ -596,33 +589,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;
@@ -651,7 +617,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);
@@ -659,11 +625,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);
@@ -681,11 +674,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) {
@@ -695,6 +689,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 +700,17 @@ define([
};
// Pads
Store.isOnlyInSharedFolder = function (clientId, channel, cb) {
var res = store.manager.findChannel(channel);
// A pad is only in a shared worker if:
// 1. this pad is in at least one proxy
// 2. no proxy containing this pad is the main drive
return cb (res.length && !res.some(function (obj) {
// Main drive doesn't have an fId (folder ID)
return !obj.fId;
}));
};
Store.moveToTrash = function (clientId, data, cb) {
var href = Hash.getRelativeHref(data.href);
store.userObject.forget(href);
@@ -734,72 +740,39 @@ 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) { continue; }
var p2 = Hash.parsePadUrl(pad.href);
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;
}
pad.expire = expire;
if (h.mode === 'view') { return; }
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;
// 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) {
var roHref;
if (h.mode === "view") {
roHref = href;
href = undefined;
}
Store.addPad(clientId, {
href: href,
roHref: roHref,
channel: channel,
title: title,
owners: owners,
@@ -819,7 +792,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 || {};
@@ -834,19 +806,19 @@ define([
}
return filtered;
};
store.userObject.getFiles(where).forEach(function (id) {
var data = store.userObject.getFileData(id);
var parsed = Hash.parsePadUrl(data.href);
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));
};
@@ -1192,37 +1164,60 @@ define([
}]));
};
// SHARED FOLDERS
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(),
channel: secret.channel,
readOnly: false,
validateKey: secret.keys.validateKey || undefined,
crypto: Crypto.createEncryptor(secret.keys),
userName: 'sharedFolder',
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, info.metadata);
});
if (store.driveEvents) {
registerProxyEvents(rt.proxy, id);
}
return rt;
};
Store.addSharedFolder = function (clientId, data, cb) {
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);
};
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
@@ -1259,32 +1254,41 @@ define([
// Special events
var driveEventInit = false;
sendDriveEvent = function (q, data, sender) {
driveEventClients.forEach(function (cId) {
if (cId === sender) { return; }
postMessage(cId, q, data);
});
};
registerProxyEvents = function (proxy, fId) {
proxy.on('change', [], function (o, n, p) {
sendDriveEvent('DRIVE_CHANGE', {
id: fId,
old: o,
new: n,
path: p
});
});
proxy.on('remove', [], function (o, p) {
sendDriveEvent('DRIVE_REMOVE', {
id: fId,
old: o,
path: p
});
});
};
Store._subscribeToDrive = function (clientId) {
if (driveEventClients.indexOf(clientId) === -1) {
driveEventClients.push(clientId);
}
if (!driveEventInit) {
store.proxy.on('change', [], function (o, n, p) {
sendDriveEvent('DRIVE_CHANGE', {
old: o,
new: n,
path: p
});
if (!store.driveEvents) {
store.driveEvents = true;
registerProxyEvents(store.proxy);
Object.keys(store.manager.folders).forEach(function (fId) {
var proxy = store.manager.folders[fId].proxy;
registerProxyEvents(proxy, fId);
});
store.proxy.on('remove', [], function (o, p) {
sendDriveEvent(clientId, 'DRIVE_REMOVE', {
old: o,
path: p
});
});
driveEventInit = true;
}
};
@@ -1340,12 +1344,60 @@ define([
/////////////////////// Init /////////////////////////////////////
//////////////////////////////////////////////////////////////////
var loadSharedFolders = function (waitFor) {
store.sharedFolders = {};
var shared = Util.find(store.proxy, ['drive', UserObject.SHARED_FOLDERS]) || {};
// Check if any of our shared folder is expired or deleted by its owner.
// If we don't check now, Listmap will create an empty proxy if it no longer exists on
// the server.
nThen(function (waitFor) {
var edPublic = store.proxy.edPublic;
var checkExpired = Object.keys(shared).filter(function (fId) {
var d = shared[fId];
return (Array.isArray(d.owners) && d.owners.length &&
(!edPublic || d.owners.indexOf(edPublic) === -1))
|| (d.expire && d.expire < (+new Date()));
}).map(function (fId) {
return shared[fId].channel;
});
Store.getDeletedPads(null, {list: checkExpired}, waitFor(function (chans) {
if (chans && chans.error) { return void console.error(chans.error); }
if (!Array.isArray(chans) || !chans.length) { return; }
var toDelete = [];
Object.keys(shared).forEach(function (fId) {
if (chans.indexOf(shared[fId].channel) !== -1
&& toDelete.indexOf(fId) === -1) {
toDelete.push(fId);
}
});
toDelete.forEach(function (fId) {
var paths = store.userObject.findFile(Number(fId));
store.userObject.delete(paths, waitFor(), true);
delete shared[fId];
});
}));
}).nThen(function (waitFor) {
Object.keys(shared).forEach(function (id) {
var sf = shared[id];
loadSharedFolder(id, sf, waitFor());
});
}).nThen(waitFor());
};
var onReady = function (clientId, returned, cb) {
var proxy = store.proxy;
var userObject = store.userObject = UserObject.init(proxy.drive, {
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); },
var unpin = function (data, cb) {
if (!store.loggedIn) { return void cb(); }
Store.unpinPads(null, data, cb);
};
var pin = function (data, cb) {
if (!store.loggedIn) { return void cb(); }
Store.pinPads(null, data, cb);
};
var manager = store.manager = ProxyManager.create(proxy.drive, proxy.edPublic,
pin, unpin, loadSharedFolder, {
outer: true,
removeOwnedChannel: function (data, cb) { Store.removeOwnedChannel('', data, cb); },
edPublic: store.proxy.edPublic,
loggedIn: store.loggedIn,
log: function (msg) {
@@ -1353,6 +1405,7 @@ define([
sendDriveEvent("DRIVE_LOG", msg);
}
});
var userObject = store.userObject = manager.user.userObject;
nThen(function (waitFor) {
postMessage(clientId, 'LOADING_DRIVE', {
state: 2
@@ -1361,16 +1414,18 @@ define([
}).nThen(function (waitFor) {
Migrate(proxy, waitFor(), function (version, progress) {
postMessage(clientId, 'LOADING_DRIVE', {
state: 2,
state: (2 + (version / 10)),
progress: progress
});
});
}).nThen(function () {
Store.initAnonRpc(null, null, waitFor());
}).nThen(function (waitFor) {
postMessage(clientId, 'LOADING_DRIVE', {
state: 3
});
userObject.fixFiles();
loadSharedFolders(waitFor);
}).nThen(function () {
var requestLogin = function () {
broadcast([], "REQUEST_LOGIN");
};

View File

@@ -42,6 +42,7 @@ define([
MOVE_TO_TRASH: Store.moveToTrash,
RESET_DRIVE: Store.resetDrive,
GET_METADATA: Store.getMetadata,
IS_ONLY_IN_SHARED_FOLDER: Store.isOnlyInSharedFolder,
SET_DISPLAY_NAME: Store.setDisplayName,
SET_PAD_ATTRIBUTE: Store.setPadAttribute,
GET_PAD_ATTRIBUTE: Store.getPadAttribute,
@@ -53,6 +54,8 @@ define([
GET_PAD_DATA: Store.getPadData,
GET_STRONGER_HASH: Store.getStrongerHash,
INCREMENT_TEMPLATE_USE: Store.incrementTemplateUse,
GET_SHARED_FOLDER: Store.getSharedFolder,
ADD_SHARED_FOLDER: Store.addSharedFolder,
// Messaging
INVITE_FROM_USERLIST: Store.inviteFromUserlist,
ADD_DIRECT_MESSAGE_HANDLERS: Store.addDirectMessageHandlers,

View File

@@ -14,15 +14,11 @@ define([
};
module.init = function (config, exp, files) {
var unpinPads = config.unpinPads || function () {
console.error("unpinPads was not provided");
};
var pinPads = config.pinPads;
var removeOwnedChannel = config.removeOwnedChannel || function () {
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 +27,7 @@ define([
var UNSORTED = exp.UNSORTED;
var TRASH = exp.TRASH;
var TEMPLATE = exp.TEMPLATE;
var SHARED_FOLDERS = exp.SHARED_FOLDERS;
var debug = exp.debug;
@@ -50,35 +47,31 @@ 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 () {}; }
var todo = function () {
var id = Util.createRandomInteger();
files[FILES_DATA][id] = data;
cb(null, id);
};
if (!loggedIn || !AppConfig.enablePinning || config.testMode) {
return void todo();
var id = Util.createRandomInteger();
files[FILES_DATA][id] = data;
cb(null, id);
};
exp.pushSharedFolder = function (data, cb) {
if (typeof cb !== "function") { cb = function () {}; }
// Check if we already have this shared folder in our drive
if (Object.keys(files[SHARED_FOLDERS]).some(function (k) {
return files[SHARED_FOLDERS][k].channel === data.channel;
})) {
return void cb ('EEXISTS');
}
if (!pinPads) { return; }
pinPads([data.channel], function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
todo();
});
// Add the folder
if (!loggedIn || !AppConfig.enablePinning || config.testMode) {
return void cb("EAUTH");
}
var id = Util.createRandomInteger();
files[SHARED_FOLDERS][id] = data;
cb(null, id);
};
// FILES DATA
@@ -89,19 +82,20 @@ 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, cb) {
if (!loggedIn && !config.testMode) { return void cb(); }
var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]);
var toClean = [];
exp.getFiles([FILES_DATA]).forEach(function (id) {
var ownedRemoved = [];
exp.getFiles([FILES_DATA, SHARED_FOLDERS]).forEach(function (id) {
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 &&
if (!isOwnPadRemoved && !sharedFolder &&
fd.owners && fd.owners.indexOf(edPublic) !== -1 && channelId) {
if (channelId) { ownedRemoved.push(channelId); }
removeOwnedChannel(channelId, function (obj) {
if (obj && obj.error) {
// If the error is that the file is already removed, nothing to
@@ -116,14 +110,15 @@ define([
});
}
if (channelId) { toClean.push(channelId); }
spliceFileData(id);
if (exp.isSharedFolder(id)) {
delete files[SHARED_FOLDERS][id];
} else {
spliceFileData(id);
}
}
});
if (!toClean.length) { return; }
unpinPads(toClean, function (response) {
if (response && response.error) { return console.error(response.error); }
// console.error(response);
});
if (!toClean.length) { return void cb(); }
cb(null, toClean, ownedRemoved);
};
var deleteHrefs = function (ids) {
ids.forEach(function (obj) {
@@ -137,7 +132,7 @@ define([
files[TRASH][obj.name].splice(idx, 1);
});
};
exp.deleteMultiplePermanently = function (paths, nocheck, isOwnPadRemoved) {
exp.deleteMultiplePermanently = function (paths, nocheck, isOwnPadRemoved, cb) {
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]); });
@@ -145,14 +140,11 @@ define([
if (!loggedIn && !config.testMode) {
allFilesPaths.forEach(function (path) {
var el = exp.find(path);
if (!el) { return; }
var id = exp.getIdFromHref(el.href);
var id = path[1];
if (!id) { return; }
spliceFileData(id);
removePadAttribute(el.href);
});
return;
return void cb();
}
var ids = [];
@@ -192,11 +184,68 @@ 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, cb); }
else { cb(); }
};
// 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) {
id = Number(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) {
exp.log(Messages.sharedFolders_duplicate);
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) {
exp.log(Messages.sharedFolders_duplicate);
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] = []; }
@@ -259,7 +308,6 @@ define([
if (!id) { return; }
if (!loggedIn && !config.testMode) {
// delete permanently
exp.removePadAttribute(href);
spliceFileData(id);
return;
}
@@ -268,14 +316,7 @@ define([
};
// REPLACE
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, 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);
@@ -300,9 +341,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)) {
@@ -406,7 +447,6 @@ define([
});
delete files[OLD_FILES_DATA];
delete files.migrate;
console.log('done');
todo();
};
if (exp.rt) {
@@ -473,6 +513,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;
@@ -516,6 +557,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];
@@ -547,7 +589,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]);
@@ -563,13 +605,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 +627,23 @@ define([
continue;
}
// If we have an edit link, check the view link
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.getViewHashFromKeys(secret);
} else {
var parsed2 = Hash.parsePadUrl(el.roHref);
if (!parsed2.hash || !parsed2.type) {
secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
el.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret);
}
}
}
// Fix href
if (/^https*:\/\//.test(el.href)) { el.href = Hash.getRelativeHref(el.href); }
// Fix creation time
@@ -592,9 +653,12 @@ 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);
console.log(el);
debug('Adding missing channel in filesData ', el.channel);
} catch (e) {
console.error(e);
}
@@ -611,6 +675,21 @@ 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 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 () {
Object.keys(files).forEach(function (key) {
@@ -618,12 +697,11 @@ define([
});
};
fixSharedFolders();
fixRoot();
fixTrashRoot();
if (!workgroup) {
fixTemplate();
fixFilesData();
}
fixTemplate();
fixFilesData();
fixDrive();
if (JSON.stringify(files) !== before) {

982
www/common/proxy-manager.js Normal file
View File

@@ -0,0 +1,982 @@
define([
'/common/userObject.js',
'/common/common-util.js',
'/common/common-hash.js',
'/customize/messages.js',
'/bower_components/nthen/index.js',
], function (UserObject, Util, Hash, Messages, nThen) {
var getConfig = function (Env) {
var cfg = {};
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(Env);
cfg.sharedFolder = true;
cfg.id = id;
var userObject = UserObject.init(proxy, cfg);
if (userObject.fixFiles) {
// Only in outer
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];
};
/*
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;
};
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 = Number(uo.id);
if (!fId) { return; }
var fPath = Env.user.userObject.findFile(fId)[0];
return fPath;
};
// 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({
fId: fId,
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);
if (id) {
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);
if (!id) { return; }
ret.push({
fId: fId,
data: Env.folders[fId].userObject.getFileData(id),
userObject: Env.folders[fId].userObject
});
});
return ret;
};
// Return paths linked to a file ID
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.forEach(function (p) {
Array.prototype.unshift.apply(p, fPath);
});
}
// Push the results from this proxy
Array.prototype.push.apply(ret, results);
});
return ret;
};
// Returns file IDs corresponding to the provided channels
var _findChannels = function (Env, channels, onlyMain) {
if (onlyMain) {
return Env.user.userObject.findChannels(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 _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;
};
// 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;
// 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<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
files = 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;
};
/*
Drive RPC
*/
// 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(); }
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;
var ownedPads = [];
toCopy.forEach(function (obj) {
newUserObject.copyFromOtherDrive(newResolved.path, obj.el, obj.data);
var _owned = Object.keys(obj.data).filter(function (id) {
var owners = obj.data[id].owners;
return Array.isArray(owners) && owners.indexOf(Env.edPublic) !== -1;
});
Array.prototype.push.apply(ownedPads, _owned);
});
var rootPath = resolved.main[0].slice();
rootPath.pop();
ownedPads = Util.deduplicateString(ownedPads);
ownedPads.forEach(function (id) {
Env.user.userObject.add(Number(id), rootPath);
});
// Remove the elements from the old location (without unpinning)
Env.user.userObject.delete(resolved.main, waitFor());
}
}
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);
toCopy.forEach(function (obj) {
uoTo.copyFromOtherDrive(newResolved.path, obj.el, obj.data);
});
// Remove the elements from the old location (without unpinning)
uoFrom.delete(paths, waitFor());
}
});
}
}).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, function (obj) {
// The result is the relative path of the new folder. We have to make it absolute.
if (obj.newPath && resolved.id) {
var fPath = _getUserObjectPath(Env, resolved.userObject);
if (fPath) {
// This is a shared folder, we have to fix the paths in the search results
Array.prototype.unshift.apply(obj.newPath, fPath);
}
}
cb(obj);
});
};
// Add a folder/subfolder
var _addSharedFolder = function (Env, data, cb) {
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 (!rt.proxy.metadata) { // Creating a new shared folder
rt.proxy.metadata = { title: data.name || Messages.fm_newFolder };
}
// 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 || {};
var resolved = _resolvePaths(Env, data.paths);
if (!resolved.main.length && !Object.keys(resolved.folders).length) {
return void cb({error: 'E_NOTFOUND'});
}
var toUnpin = [];
var ownedRemoved;
nThen(function (waitFor)  {
// Delete paths from the main drive and get the list of pads to unpin
// We also get the list of owned pads that were removed
if (resolved.main.length) {
Env.user.userObject.delete(resolved.main, waitFor(function (err, _toUnpin, _ownedRemoved) {
if (!Env.unpinPads || !_toUnpin) { return; }
Array.prototype.push.apply(toUnpin, _toUnpin);
ownedRemoved = _ownedRemoved;
}), data.nocheck, data.isOwnPadRemoved);
}
}).nThen(function (waitFor) {
// Check if removed owned pads are duplicated is some shared folders
// If that's the case, we have to remove them from the shared folders too
// We can do that by adding their paths to the list of pads to remove from shared folders
if (ownedRemoved) {
var ids = _findChannels(Env, ownedRemoved);
ids.forEach(function (id) {
var paths = findFile(Env, id);
var _resolved = _resolvePaths(Env, paths);
Object.keys(_resolved.folders).forEach(function (fId) {
if (resolved.folders[fId]) {
Array.prototype.push.apply(resolved.folders[fId], _resolved.folders[fId]);
} else {
resolved.folders[fId] = _resolved.folders[fId];
}
});
});
}
// Delete paths from the shared folders
Object.keys(resolved.folders).forEach(function (id) {
Env.folders[id].userObject.delete(resolved.folders[id], waitFor(function (err, _toUnpin) {
if (!Env.unpinPads || !_toUnpin) { return; }
Array.prototype.push.apply(toUnpin, _toUnpin);
}), data.nocheck, data.isOwnPadRemoved);
});
}).nThen(function (waitFor) {
if (!Env.unpinPads) { return; }
// Deleted channels
toUnpin = Util.deduplicateString(toUnpin);
// Deleted channels that are still in another proxy
var toKeep = _findChannels(Env, toUnpin).map(function (id) {
return _getFileData(Env, id).channel;
});
// Compute the unpin list and unpin
var unpinList = [];
toUnpin.forEach(function (chan) {
if (toKeep.indexOf(chan) === -1) {
unpinList.push(chan);
}
});
Env.unpinPads(unpinList, waitFor(function (response) {
if (response && response.error) { return console.error(response.error); }
}));
}).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'}); }
if (!resolved.id) {
var el = Env.user.userObject.find(resolved.path);
if (Env.user.userObject.isSharedFolder(el) && Env.folders[el]) {
Env.folders[el].proxy.metadata.title = data.newName;
return void cb();
}
}
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;
case 'restore':
_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':
_emptyTrash(Env, data, cb); break;
case 'rename':
_rename(Env, data, cb); break;
default:
cb();
}
};
// 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;
});
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());
});
list = 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 list;
};
/*
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 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 && (!edPublic || 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);
}
};
}
if (type === "pin") {
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 (result.indexOf(data.channel) === -1) {
result.push(data.channel);
}
};
}
};
if (type === 'owned' && !edPublic) { return result; }
if (type === 'pin' && !edPublic) { return result; }
// Get the list of user objects
var userObjects = _getUserObjects(Env);
userObjects.forEach(function (uo) {
var files = uo.getFiles([UserObject.FILES_DATA]);
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;
});
Array.prototype.push.apply(result, sfChannels);
}
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;
}
var todo = function () {
uo.pushData(pad, function (e, id) {
if (e) { return void cb(e); }
uo.add(id, p);
cb();
});
};
if (!Env.pinPads) { return void todo(); }
Env.pinPads([pad.channel], function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
todo();
});
};
var create = function (proxy, edPublic, pinPads, unpinPads, loadSf, uoConfig) {
var Env = {
pinPads: pinPads,
unpinPads: unpinPads,
loadSharedFolder: loadSf,
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 {
// 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),
findHref: callWithEnv(findHref),
user: Env.user,
folders: Env.folders
};
};
/* =============================================================================
* =============================================================================
* 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 addSharedFolderInner = function (Env, path, data, cb) {
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",
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 findChannels = _findChannels;
var getFileData = _getFileData;
var getUserObjectPath = _getUserObjectPath;
var find = function (Env, path) {
var resolved = _resolvePath(Env, path);
return resolved.userObject.find(resolved.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 (!results.length) { return; }
if (fPath) {
// This is a shared folder, we have to fix the paths in the search results
results.forEach(function (r) {
r.paths.forEach(function (p) {
Array.prototype.unshift.apply(p, fPath);
});
});
}
// Push the results from this proxy
Array.prototype.push.apply(ret, results);
});
return ret;
};
var getRecentPads = function (Env) {
return Env.user.userObject.getRecentPads();
};
var getOwnedPads = function (Env, edPublic) {
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;
};
var isInSharedFolder = function (Env, path) {
var resolved = _resolvePath(Env, path);
return typeof resolved.id === "number" ? resolved.id : false;
};
/* 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 isSharedFolder = function (Env, el) {
return Env.user.userObject.isSharedFolder(el);
};
var isFolderEmpty = function (Env, el) {
if (Env.folders[el]) {
var uo = Env.folders[el].userObject;
return uo.isFolderEmpty(uo.find[uo.ROOT]);
}
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) {
if (Env.folders[el]) {
var uo = Env.folders[el].userObject;
return uo.hasSubfolder(uo.find[uo.ROOT]);
}
return Env.user.userObject.hasSubfolder(el, trashRoot);
};
var hasFile = function (Env, el, trashRoot) {
if (Env.folders[el]) {
var uo = Env.folders[el].userObject;
return uo.hasFile(uo.find[uo.ROOT]);
}
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),
addSharedFolder: callWithEnv(addSharedFolderInner),
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),
getSharedFolderData: callWithEnv(getSharedFolderData),
isInSharedFolder: callWithEnv(isInSharedFolder),
getUserObjectPath: callWithEnv(getUserObjectPath),
// Generic
isFile: callWithEnv(isFile),
isFolder: callWithEnv(isFolder),
isSharedFolder: callWithEnv(isSharedFolder),
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,
createInner: createInner
};
});

View File

@@ -266,7 +266,7 @@ define([
isDeleted: isNewFile && window.location.hash.length > 0,
forceCreationScreen: forceCreationScreen,
password: password,
channel: secret.channel
channel: secret.channel,
};
for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; }
@@ -484,6 +484,12 @@ define([
cb();
});
sframeChan.on('Q_IS_ONLY_IN_SHARED_FOLDER', function (data, cb) {
Cryptpad.isOnlyInSharedFolder(secret.channel, function (err, t) {
if (err) { return void cb({error: err}); }
cb(t);
});
});
// Present mode URL
sframeChan.on('Q_PRESENT_URL_GET_VALUE', function (data, cb) {

View File

@@ -114,6 +114,10 @@ define({
'Q_GET_PAD_ATTRIBUTE': true,
'Q_SET_PAD_ATTRIBUTE': true,
// Check if a pad is only in a shared folder or (also) in the main drive.
// This allows us to change the behavior of some buttons (trash icon...)
'Q_IS_ONLY_IN_SHARED_FOLDER': true,
// Open/close the File picker (sent from the iframe to the outside)
'EV_FILE_PICKER_OPEN': true,
'EV_FILE_PICKER_CLOSE': true,
@@ -206,6 +210,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

View File

@@ -13,10 +13,10 @@ 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 = {};
var pinPads = config.pinPads;
var sframeChan = config.sframeChan;
var FILES_DATA = module.FILES_DATA = exp.FILES_DATA = Constants.storageKey;
@@ -28,14 +28,21 @@ define([
exp.UNSORTED = UNSORTED;
exp.TRASH = TRASH;
exp.TEMPLATE = TEMPLATE;
exp.SHARED_FOLDERS = SHARED_FOLDERS;
var sharedFolder = exp.sharedFolder = config.sharedFolder;
exp.id = config.id;
// Logging
var logging = function () {
console.log.apply(console, arguments);
};
var log = config.log || logging;
var log = exp.log = config.log || logging;
var logError = config.logError || logging;
var debug = exp.debug = config.debug || logging;
exp.fixFiles = function () {}; // Overriden by OuterFO
var error = exp.error = function() {
if (sframeChan) {
return void sframeChan.query("Q_DRIVE_USEROBJECT", {
@@ -46,12 +53,10 @@ define([
exp.fixFiles();
}
console.error.apply(console, arguments);
exp.fixFiles();
};
// TODO: workgroup
var workgroup = config.workgroup;
if (pinPads) {
if (config.outer) {
// Extend "exp" with methods used only outside of the iframe (requires access to store)
OuterFO.init(config, exp, files);
}
@@ -76,7 +81,12 @@ define([
var compareFiles = function (fileA, fileB) { return fileA === fileB; };
var isSharedFolder = exp.isSharedFolder = function (element) {
if (sharedFolder) { return false; } // No recursive shared folders
return Boolean(files[SHARED_FOLDERS][element]);
};
var isFile = exp.isFile = function (element, allowStr) {
if (isSharedFolder(element)) { return false; }
return typeof(element) === "number" ||
((typeof(files[OLD_FILES_DATA]) !== "undefined" || allowStr)
&& typeof(element) === "string");
@@ -85,15 +95,11 @@ define([
exp.isReadOnlyFile = function (element) {
if (!isFile(element)) { return false; }
var data = exp.getFileData(element);
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';
return Boolean(data.roHref && !data.href);
};
var isFolder = exp.isFolder = function (element) {
return typeof(element) === "object";
return typeof(element) === "object" || isSharedFolder(element);
};
exp.isFolderEmpty = function (element) {
if (!isFolder(element)) { return false; }
@@ -144,9 +150,11 @@ define([
// Data from filesData
var getTitle = exp.getTitle = function (file, type) {
if (workgroup) { debug("No titles in workgroups"); return; }
if (isSharedFolder(file)) {
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;
}
@@ -216,14 +224,16 @@ 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 (isFile(root[e]) || isSharedFolder(root[e])) {
if(arr.indexOf(root[e]) === -1) { arr.push(root[e]); }
} else {
getFilesRecursively(root[e], arr);
}
}
return arr;
};
var _getFiles = {};
_getFiles['array'] = function (cat) {
@@ -235,6 +245,7 @@ define([
});
_getFiles['hrefArray'] = function () {
var ret = [];
if (sharedFolder) { return ret; }
getHrefArray().forEach(function (c) {
ret = ret.concat(_getFiles[c]());
});
@@ -279,10 +290,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") {
@@ -295,11 +311,11 @@ 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;
}
return;
});
return result;
};
@@ -315,7 +331,7 @@ define([
}
};
if (isFile(root)) {
if (isFile(root) || isSharedFolder(root)) {
if (compareFiles(file, root)) {
if (paths.indexOf(path) === -1) {
paths.push(path);
@@ -335,6 +351,7 @@ define([
return _findFileInRoot([ROOT], file);
};
var _findFileInHrefArray = function (rootName, file) {
if (sharedFolder) { return []; }
if (!files[rootName]) { return []; }
var unsorted = files[rootName].slice();
var ret = [];
@@ -345,6 +362,7 @@ define([
return ret;
};
var _findFileInTrash = function (path, file) {
if (sharedFolder) { return []; }
var root = find(path);
var paths = [];
var addPaths = function (p) {
@@ -572,8 +590,9 @@ define([
}
}, cb);
}
exp.deleteMultiplePermanently(paths, nocheck, isOwnPadRemoved);
if (typeof cb === "function") { cb(); }
cb = cb || function () {};
exp.deleteMultiplePermanently(paths, nocheck, isOwnPadRemoved, cb);
//if (typeof cb === "function") { cb(); }
};
exp.emptyTrash = function (cb) {
if (sframeChan) {
@@ -605,7 +624,7 @@ define([
var element = find(path);
// Folders
if (isFolder(element)) {
if (isFolder(element) && !isSharedFolder(element)) {
var parentPath = path.slice();
var oldName = parentPath.pop();
if (!newName || !newName.trim() || oldName === newName) { return; }
@@ -620,8 +639,13 @@ define([
return;
}
// Files
var data = files[FILES_DATA][element];
// Files or Shared folder
var data;
if (isSharedFolder(element)) {
data = files[SHARED_FOLDERS][element];
} else {
data = files[FILES_DATA][element];
}
if (!data) { return; }
if (!newName || newName.trim() === "") {
delete data.filename;