Basic CryptDrive export with pads
This commit is contained in:
parent
f4d5a7567f
commit
92ce311694
@ -46,7 +46,8 @@
|
|||||||
"html2canvas": "^0.4.1",
|
"html2canvas": "^0.4.1",
|
||||||
"croppie": "^2.5.0",
|
"croppie": "^2.5.0",
|
||||||
"sortablejs": "#^1.6.0",
|
"sortablejs": "#^1.6.0",
|
||||||
"saferphore": "^0.0.1"
|
"saferphore": "^0.0.1",
|
||||||
|
"jszip": "Stuk/jszip#^3.1.5"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"bootstrap": "^v4.0.0"
|
"bootstrap": "^v4.0.0"
|
||||||
|
|||||||
@ -744,6 +744,15 @@ define([
|
|||||||
Cryptpad.removeLoginBlock(data, cb);
|
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) {
|
if (cfg.addRpc) {
|
||||||
cfg.addRpc(sframeChan, Cryptpad, Utils);
|
cfg.addRpc(sframeChan, Cryptpad, Utils);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -270,5 +270,9 @@ define({
|
|||||||
'Q_IS_PAD_STORED': true,
|
'Q_IS_PAD_STORED': true,
|
||||||
|
|
||||||
// Import mediatag from a pad
|
// 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,
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -562,6 +562,8 @@ define(function () {
|
|||||||
out.settings_backupCategory = "Backup";
|
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_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_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_restore = "Restore";
|
||||||
|
|
||||||
out.settings_resetNewTitle = "Clean CryptDrive";
|
out.settings_resetNewTitle = "Clean CryptDrive";
|
||||||
|
|||||||
@ -12,6 +12,7 @@ define([
|
|||||||
'/customize/credential.js',
|
'/customize/credential.js',
|
||||||
'/customize/application_config.js',
|
'/customize/application_config.js',
|
||||||
'/api/config',
|
'/api/config',
|
||||||
|
'/settings/make-backup.js',
|
||||||
|
|
||||||
'/bower_components/file-saver/FileSaver.min.js',
|
'/bower_components/file-saver/FileSaver.min.js',
|
||||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||||
@ -30,7 +31,8 @@ define([
|
|||||||
h,
|
h,
|
||||||
Cred,
|
Cred,
|
||||||
AppConfig,
|
AppConfig,
|
||||||
ApiConfig
|
ApiConfig,
|
||||||
|
Backup,
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var saveAs = window.saveAs;
|
var saveAs = window.saveAs;
|
||||||
@ -50,6 +52,7 @@ define([
|
|||||||
'cp-settings-autostore',
|
'cp-settings-autostore',
|
||||||
'cp-settings-userfeedback',
|
'cp-settings-userfeedback',
|
||||||
'cp-settings-change-password',
|
'cp-settings-change-password',
|
||||||
|
'cp-settings-backup',
|
||||||
'cp-settings-delete'
|
'cp-settings-delete'
|
||||||
],
|
],
|
||||||
'creation': [
|
'creation': [
|
||||||
@ -300,6 +303,45 @@ define([
|
|||||||
return $div;
|
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 () {
|
create['delete'] = function () {
|
||||||
if (!common.isLoggedIn()) { return; }
|
if (!common.isLoggedIn()) { return; }
|
||||||
var $div = $('<div>', { 'class': 'cp-settings-delete cp-sidebarlayout-element'});
|
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);
|
$import.attr('class', 'btn btn-success').text(Messages.settings_restore);
|
||||||
$div.append($import);
|
$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;
|
return $div;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
116
www/settings/make-backup.js
Normal file
116
www/settings/make-backup.js
Normal 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
|
||||||
|
};
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user