Basic CryptDrive export with pads

This commit is contained in:
yflory 2018-10-17 14:32:33 +02:00
parent f4d5a7567f
commit 92ce311694
6 changed files with 211 additions and 3 deletions

View File

@ -46,7 +46,8 @@
"html2canvas": "^0.4.1",
"croppie": "^2.5.0",
"sortablejs": "#^1.6.0",
"saferphore": "^0.0.1"
"saferphore": "^0.0.1",
"jszip": "Stuk/jszip#^3.1.5"
},
"resolutions": {
"bootstrap": "^v4.0.0"

View File

@ -744,6 +744,15 @@ define([
Cryptpad.removeLoginBlock(data, cb);
});
sframeChan.on('Q_CRYPTGET', function (data, cb) {
Cryptget.get(data.hash, function (err, val) {
cb({
error: err,
data: val
});
}, data.opts);
});
if (cfg.addRpc) {
cfg.addRpc(sframeChan, Cryptpad, Utils);
}

View File

@ -270,5 +270,9 @@ define({
'Q_IS_PAD_STORED': true,
// Import mediatag from a pad
'Q_IMPORT_MEDIATAG': true
'Q_IMPORT_MEDIATAG': true,
// Ability to get a pad's content from its hash
'Q_CRYPTGET': true,
});

View File

@ -562,6 +562,8 @@ define(function () {
out.settings_backupCategory = "Backup";
out.settings_backupHint = "Backup or restore all your CryptDrive's content. It won't contain the content of your pads, just the link to access them.";
out.settings_backup = "Backup";
out.settings_backupHint2 = "Download all your pads. Pads will be downloaded in an readable format when available.";
out.settings_backup2 = "Download my CryptDrive";
out.settings_restore = "Restore";
out.settings_resetNewTitle = "Clean CryptDrive";

View File

@ -12,6 +12,7 @@ define([
'/customize/credential.js',
'/customize/application_config.js',
'/api/config',
'/settings/make-backup.js',
'/bower_components/file-saver/FileSaver.min.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
@ -30,7 +31,8 @@ define([
h,
Cred,
AppConfig,
ApiConfig
ApiConfig,
Backup,
)
{
var saveAs = window.saveAs;
@ -50,6 +52,7 @@ define([
'cp-settings-autostore',
'cp-settings-userfeedback',
'cp-settings-change-password',
'cp-settings-backup',
'cp-settings-delete'
],
'creation': [
@ -300,6 +303,45 @@ define([
return $div;
};
create['backup'] = function () {
if (!common.isLoggedIn()) { return; }
var $div = $('<div>', { 'class': 'cp-settings-backup cp-sidebarlayout-element'});
$('<span>', {'class': 'label'}).text(Messages.settings_backupTitle || 'TODO BACKUP').appendTo($div); // XXX
$('<span>', {'class': 'cp-sidebarlayout-description'})
.append(Messages.settings_backupHint || 'TODO').appendTo($div); // XXX
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved});
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'});
var $button = $('<button>', {'id': 'cp-settings-delete', 'class': 'btn btn-danger'})
.text(Messages.settings_backupButton || 'BACKUP').appendTo($div); // XXX
$button.click(function () {
$spinner.show();
UI.confirm(Messages.settings_backupConfirm || 'TODO Are you sure?', function (yes) { // XXX
if (!yes) { return; }
});
// TODO
/*
UI.confirm("Are you sure?", function (yes) {
// Logout everywhere
// Disconnect other tabs
// Remove owned pads
// Remove owned drive
// Remove pinstore
// Alert: "Account deleted", press OK to be redirected to the home page
$spinner.hide();
});*/
});
$spinner.hide().appendTo($div);
$ok.hide().appendTo($div);
return $div;
};
create['delete'] = function () {
if (!common.isLoggedIn()) { return; }
var $div = $('<div>', { 'class': 'cp-settings-delete cp-sidebarlayout-element'});
@ -861,6 +903,40 @@ define([
$import.attr('class', 'btn btn-success').text(Messages.settings_restore);
$div.append($import);
// Backup all the pads
var exportDrive = function () {
var todo = function (data, filename) {
var getPad = function (data, cb) {
sframeChan.query("Q_CRYPTGET", data, function (err, obj) {
if (err) { return void cb(err); }
if (obj.error) { return void cb(obj.error); }
cb(null, obj.data);
});
};
Backup.create(data, getPad, function (blob) {
saveAs(blob, filename);
});
};
sframeChan.query("Q_SETTINGS_DRIVE_GET", null, function (err, data) {
if (err) { return void console.error(err); }
var sjson = JSON.stringify(data);
var name = displayName || accountName || Messages.anonymous;
var suggestion = name + '-' + new Date().toDateString();
UI.prompt('TODO are you sure? if ye,s pick a name...', // XXX
Util.fixFileName(suggestion) + '.json', function (filename) {
if (!(typeof(filename) === 'string' && filename)) { return; }
todo(data, filename);
});
});
};
$('<span>', {'class': 'cp-sidebarlayout-description'})
.text(Messages.settings_backupHint2).appendTo($div);
var $export = common.createButton('export', true, {}, exportDrive);
$export.attr('class', 'btn btn-success').text(Messages.settings_backup2);
$div.append($export);
return $div;
};

116
www/settings/make-backup.js Normal file
View File

@ -0,0 +1,116 @@
define([
'/common/cryptget.js',
'/common/common-hash.js',
'/bower_components/nthen/index.js',
'/bower_components/saferphore/index.js',
'/bower_components/jszip/dist/jszip.min.js',
], function (Crypt, Hash, nThen, Saferphore, JsZip) {
var sanitize = function (str) {
return str.replace(/[^a-z0-9]/gi, '_').toLowerCase();
};
var getUnique = function (name, ext, existing) {
var n = name;
var i = 1;
while (existing.indexOf(n) !== -1) {
n = name + ' ('+ i++ + ')';
}
return n;
};
var addFile = function (ctx, zip, fData, existingNames) {
if (!fData.href && !fData.roHref) {
return void ctx.errors.push({
error: 'EINVAL',
data: fData
});
}
var parsed = Hash.parsePadUrl(fData.href || fData.roHref);
// TODO deal with files here
if (parsed.hashData.type !== 'pad') { return; }
var w = ctx.waitFor();
ctx.sem.take(function (give) {
var opts = {
password: fData.password
};
var rawName = fData.fileName || fData.title || 'File';
console.log(rawName);
ctx.get({
hash: parsed.hash,
opts: opts
}, give(function (err, val) {
w();
if (err) {
return void ctx.errors.push({
error: err,
data: fData
});
}
// TODO transform file here
// var blob = transform(val, type);
var opts = {};
var fileName = getUnique(sanitize(rawName), '.txt', existingNames);
existingNames.push(fileName);
zip.file(fileName, val, opts);
console.log('DONE ---- ' + rawName);
}));
});
// cb(err, blob);
// wiht blob.name not undefined
};
var makeFolder = function (ctx, root, zip) {
if (typeof (root) !== "object") { return; }
var existingNames = [];
Object.keys(root).forEach(function (k) {
var el = root[k];
if (typeof el === "object") {
var fName = getUnique(sanitize(k), '', existingNames);
existingNames.push(fName);
return void makeFolder(ctx, el, zip.folder(fName));
}
if (ctx.data.sharedFolders[el]) {
// TODO later...
return;
}
var fData = ctx.data.filesData[el];
if (fData) {
addFile(ctx, zip, fData, existingNames);
return;
}
// What is this element?
console.error(el);
});
};
var create = function (data, getPad, cb) {
if (!data || !data.drive) { return void cb('EEMPTY'); }
var sem = Saferphore.create(10);
var ctx = {
get: getPad,
data: data.drive,
zip: new JsZip(),
errors: [],
sem: sem,
};
nThen(function (waitFor) {
ctx.waitFor = waitFor;
var zipRoot = ctx.zip.folder('Root');
makeFolder(ctx, data.drive.root, zipRoot);
}).nThen(function () {
// TODO call cb with ctx.zip here
console.log(ctx.zip);
console.log(ctx.errors);
ctx.zip.generateAsync({type: 'blob'}).then(function (content) {
cb(content);
});
});
};
return {
create: create
};
});