Delete pads after 3 months of inactivity

pull/1/head
yflory 7 years ago
parent 329fd61bb1
commit 5c53868c3b

@ -249,6 +249,14 @@ module.exports = {
*/ */
pinPath: './pins', pinPath: './pins',
/* Pads that are not 'pinned' by any registered user can be set to expire
* after a configurable number of days of inactivity (default 90 days).
* The value can be changed or set to false to remove expiration.
* Expired pads can then be removed using a cron job calling the
* `delete-inactive.js` script with node
*/
inactiveTime: 90, // days
/* CryptPad allows logged in users to upload encrypted files. Files/blobs /* CryptPad allows logged in users to upload encrypted files. Files/blobs
* are stored in a 'blob-store'. Set its location here. * are stored in a 'blob-store'. Set its location here.
*/ */

@ -44,11 +44,11 @@
@colortheme_pad-bg: #1c4fa0; @colortheme_pad-bg: #1c4fa0;
@colortheme_pad-color: #fff; @colortheme_pad-color: #fff;
@colortheme_pad-toolbar-bg: #c1e7ff; @colortheme_pad-toolbar-bg: #c1e7ff;
@colortheme_pad-warn: #F83A3A; @colortheme_pad-warn: #ffae00;
@colortheme_slide-bg: #e57614; @colortheme_slide-bg: #e57614;
@colortheme_slide-color: #fff; @colortheme_slide-color: #fff;
@colortheme_slide-warn: #58D697; @colortheme_slide-warn: #005868;
@colortheme_code-bg: #ffae00; @colortheme_code-bg: #ffae00;
@colortheme_code-color: #000; @colortheme_code-color: #000;
@ -59,7 +59,7 @@
@colortheme_poll-help-bg: #bbffbb; @colortheme_poll-help-bg: #bbffbb;
@colortheme_poll-th-bg: #005bef; @colortheme_poll-th-bg: #005bef;
@colortheme_poll-th-fg: #fff; @colortheme_poll-th-fg: #fff;
@colortheme_poll-warn: #ffae00; @colortheme_poll-warn: #ffade3;
@colortheme_whiteboard-bg: #800080; @colortheme_whiteboard-bg: #800080;
@colortheme_whiteboard-color: #fff; @colortheme_whiteboard-color: #fff;

@ -505,6 +505,7 @@
font-size: @colortheme_app-font-size; font-size: @colortheme_app-font-size;
a { a {
font-size: @colortheme_app-font-size; font-size: @colortheme_app-font-size;
font-family: @colortheme_font;
font-weight: bold; font-weight: bold;
color: @warn-color; color: @warn-color;
&:hover { &:hover {
@ -792,6 +793,7 @@
} }
.cp-toolbar-share-button { .cp-toolbar-share-button {
width: 50px; width: 50px;
text-align: center;
} }
} }
.cp-toolbar-rightside { .cp-toolbar-rightside {

@ -29,11 +29,12 @@ define(function () {
out.typeError = "Ce pad n'est pas compatible avec l'application sélectionnée"; out.typeError = "Ce pad n'est pas compatible avec l'application sélectionnée";
out.onLogout = 'Vous êtes déconnecté de votre compte utilisateur, <a href="/" target="_blank">cliquez ici</a> pour vous authentifier<br>ou appuyez sur <em>Échap</em> pour accéder au pad en mode lecture seule.'; out.onLogout = 'Vous êtes déconnecté de votre compte utilisateur, <a href="/" target="_blank">cliquez ici</a> pour vous authentifier<br>ou appuyez sur <em>Échap</em> pour accéder au pad en mode lecture seule.';
out.wrongApp = "Impossible d'afficher le contenu de ce document temps-réel dans votre navigateur. Vous pouvez essayer de recharger la page."; out.wrongApp = "Impossible d'afficher le contenu de ce document temps-réel dans votre navigateur. Vous pouvez essayer de recharger la page.";
out.padNotPinned = 'Ce pad va expirer dans 3 mois, {0}connectez-vous{1} ou {2}enregistrez-vous{3} pour le préserver.'; out.padNotPinned = 'Ce pad va expirer après 3 mois d\'inactivité, {0}connectez-vous{1} ou {2}enregistrez-vous{3} pour le préserver.';
out.anonymousStoreDisabled = "L'administrateur de cette instance de CryptPad a désactivé le drive pour les utilisateurs non enregistrés. Vous devez vous connecter pour pouvoir utiliser CryptDrive."; out.anonymousStoreDisabled = "L'administrateur de cette instance de CryptPad a désactivé le drive pour les utilisateurs non enregistrés. Vous devez vous connecter pour pouvoir utiliser CryptDrive.";
out.expiredError = "Ce pad a atteint sa date d'expiration est n'est donc plus disponible."; out.expiredError = "Ce pad a atteint sa date d'expiration est n'est donc plus disponible.";
out.expiredErrorCopy = ' Vous pouvez toujours copier son contenu ailleurs en appuyant sur <em>Échap</em>.<br> Dés que vous aurez quitté la page, il sera impossible de le récupérer.'; out.expiredErrorCopy = ' Vous pouvez toujours copier son contenu ailleurs en appuyant sur <em>Échap</em>.<br> Dés que vous aurez quitté la page, il sera impossible de le récupérer.';
out.deletedError = 'Ce pad a été supprimé par son propriétaire et n\'est donc plus disponible.'; out.deletedError = 'Ce pad a été supprimé par son propriétaire et n\'est donc plus disponible.';
out.inactiveError = 'Ce pad a été supprimé en raison de son inactivité. Appuyez sur Échap pour créer un nouveau pad.';
out.loading = "Chargement..."; out.loading = "Chargement...";
out.error = "Erreur"; out.error = "Erreur";

@ -30,11 +30,12 @@ define(function () {
out.typeError = "This pad is not compatible with the selected application"; out.typeError = "This pad is not compatible with the selected application";
out.onLogout = 'You are logged out, <a href="/" target="_blank">click here</a> to log in<br>or press <em>Escape</em> to access your pad in read-only mode.'; out.onLogout = 'You are logged out, <a href="/" target="_blank">click here</a> to log in<br>or press <em>Escape</em> to access your pad in read-only mode.';
out.wrongApp = "Unable to display the content of that realtime session in your browser. Please try to reload that page."; out.wrongApp = "Unable to display the content of that realtime session in your browser. Please try to reload that page.";
out.padNotPinned = 'This pad will expire in 3 months, {0}login{1} or {2}register{3} to preserve it.'; out.padNotPinned = 'This pad will expire after 3 months of inactivity, {0}login{1} or {2}register{3} to preserve it.';
out.anonymousStoreDisabled = "The webmaster of this CryptPad instance has disabled the store for anonymous users. You have to log in to be able to use CryptDrive."; out.anonymousStoreDisabled = "The webmaster of this CryptPad instance has disabled the store for anonymous users. You have to log in to be able to use CryptDrive.";
out.expiredError = 'This pad has reached its expiration time and is no longer available.'; out.expiredError = 'This pad has reached its expiration time and is no longer available.';
out.expiredErrorCopy = ' You can still copy the content to another location by pressing <em>Esc</em>.<br>Once you leave this page, it will disappear forever!'; out.expiredErrorCopy = ' You can still copy the content to another location by pressing <em>Esc</em>.<br>Once you leave this page, it will disappear forever!';
out.deletedError = 'This pad has been deleted by its owner and is no longer available.'; out.deletedError = 'This pad has been deleted by its owner and is no longer available.';
out.inactiveError = 'This pad has been deleted due to inactivity. Press Esc to create a new pad.';
out.loading = "Loading..."; out.loading = "Loading...";
out.error = "Error"; out.error = "Error";

@ -0,0 +1,40 @@
/* jshint esversion: 6, node: true */
const Fs = require("fs");
const nThen = require("nthen");
const Saferphore = require("saferphore");
const PinnedData = require('./pinneddata');
let config;
try {
config = require('./config');
} catch (e) {
config = require('./config.example');
}
if (!config.inactiveTime || typeof(config.inactiveTime) !== "number") { return; }
let inactiveTime = +new Date() - (config.inactiveTime * 24 * 3600 * 1000);
let inactiveConfig = {
unpinned: true,
olderthan: inactiveTime,
blobsolderthan: inactiveTime
};
let toDelete;
nThen(function (waitFor) {
PinnedData.load(inactiveConfig, waitFor(function (err, data) {
if (err) {
waitFor.abort();
throw new Error(err);
}
toDelete = data;
}));
}).nThen(function () {
var sem = Saferphore.create(10);
toDelete.forEach(function (f) {
sem.take(function (give) {
Fs.unlink(f.filename, give(function (err) {
if (err) { return void console.error(err + " " + f.filename); }
console.log(f.filename + " " + f.size + " " + (+f.atime) + " " + (+new Date()));
}));
});
});
});

@ -7,7 +7,6 @@ var config;
try { try {
config = require('./config'); config = require('./config');
} catch (e) { } catch (e) {
console.log("You can customize the configuration by copying config.example.js to config.js");
config = require('./config.example'); config = require('./config.example');
} }

@ -47,12 +47,13 @@ const dsFileStats = {};
const out = []; const out = [];
const pinned = {}; const pinned = {};
nThen((waitFor) => { module.exports.load = function (config, cb) {
nThen((waitFor) => {
Fs.readdir('./datastore', waitFor((err, list) => { Fs.readdir('./datastore', waitFor((err, list) => {
if (err) { throw err; } if (err) { throw err; }
dirList = list; dirList = list;
})); }));
}).nThen((waitFor) => { }).nThen((waitFor) => {
dirList.forEach((f) => { dirList.forEach((f) => {
sema.take((returnAfter) => { sema.take((returnAfter) => {
Fs.readdir('./datastore/' + f, waitFor(returnAfter((err, list2) => { Fs.readdir('./datastore/' + f, waitFor(returnAfter((err, list2) => {
@ -61,13 +62,13 @@ nThen((waitFor) => {
}))); })));
}); });
}); });
}).nThen((waitFor) => { }).nThen((waitFor) => {
Fs.readdir('./blob', waitFor((err, list) => { Fs.readdir('./blob', waitFor((err, list) => {
if (err) { throw err; } if (err) { throw err; }
dirList = list; dirList = list;
})); }));
}).nThen((waitFor) => { }).nThen((waitFor) => {
dirList.forEach((f) => { dirList.forEach((f) => {
sema.take((returnAfter) => { sema.take((returnAfter) => {
Fs.readdir('./blob/' + f, waitFor(returnAfter((err, list2) => { Fs.readdir('./blob/' + f, waitFor(returnAfter((err, list2) => {
@ -76,7 +77,7 @@ nThen((waitFor) => {
}))); })));
}); });
}); });
}).nThen((waitFor) => { }).nThen((waitFor) => {
fileList.forEach((f) => { fileList.forEach((f) => {
sema.take((returnAfter) => { sema.take((returnAfter) => {
Fs.stat(f, waitFor(returnAfter((err, st) => { Fs.stat(f, waitFor(returnAfter((err, st) => {
@ -86,12 +87,12 @@ nThen((waitFor) => {
}))); })));
}); });
}); });
}).nThen((waitFor) => { }).nThen((waitFor) => {
Fs.readdir('./pins', waitFor((err, list) => { Fs.readdir('./pins', waitFor((err, list) => {
if (err) { throw err; } if (err) { throw err; }
dirList = list; dirList = list;
})); }));
}).nThen((waitFor) => { }).nThen((waitFor) => {
fileList.splice(0, fileList.length); fileList.splice(0, fileList.length);
dirList.forEach((f) => { dirList.forEach((f) => {
sema.take((returnAfter) => { sema.take((returnAfter) => {
@ -101,14 +102,14 @@ nThen((waitFor) => {
}))); })));
}); });
}); });
}).nThen((waitFor) => { }).nThen((waitFor) => {
fileList.forEach((f) => { fileList.forEach((f) => {
sema.take((returnAfter) => { sema.take((returnAfter) => {
Fs.readFile(f, waitFor(returnAfter((err, content) => { Fs.readFile(f, waitFor(returnAfter((err, content) => {
if (err) { throw err; } if (err) { throw err; }
const hashes = hashesFromPinFile(content.toString('utf8'), f); const hashes = hashesFromPinFile(content.toString('utf8'), f);
const size = sizeForHashes(hashes, dsFileStats); const size = sizeForHashes(hashes, dsFileStats);
if (process.argv.indexOf('--unpinned') > -1) { if (config.unpinned) {
hashes.forEach((x) => { pinned[x] = 1; }); hashes.forEach((x) => { pinned[x] = 1; });
} else { } else {
out.push([f, Math.floor(size / (1024 * 1024))]); out.push([f, Math.floor(size / (1024 * 1024))]);
@ -116,34 +117,56 @@ nThen((waitFor) => {
}))); })));
}); });
}); });
}).nThen(() => { }).nThen(() => {
if (process.argv.indexOf('--unpinned') > -1) { if (config.unpinned) {
const ot = process.argv.indexOf('--olderthan');
let before = Infinity; let before = Infinity;
if (ot > -1) { if (config.olderthan) {
before = new Date(process.argv[ot+1]); before = config.olderthan;
if (isNaN(before)) { if (isNaN(before)) {
throw new Error('--olderthan error [' + process.argv[ot+1] + '] not a valid date'); return void cb('--olderthan error [' + config.olderthan + '] not a valid date');
} }
} }
const bot = process.argv.indexOf('--blobsolderthan');
let blobsbefore = before; let blobsbefore = before;
if (bot > -1) { if (config.blobsolderthan) {
blobsbefore = new Date(process.argv[bot+1]); blobsbefore = config.blobsolderthan;
if (isNaN(blobsbefore)) { if (isNaN(blobsbefore)) {
throw new Error('--blobsolderthan error [' + process.argv[bot+1] + '] not a valid date'); return void cb('--blobsolderthan error [' + config.blobsolderthan + '] not a valid date');
} }
} }
let files = [];
Object.keys(dsFileStats).forEach((f) => { Object.keys(dsFileStats).forEach((f) => {
if (!(f in pinned)) { if (!(f in pinned)) {
const isBlob = dsFileStats[f].filename.indexOf('.ndjson') === -1; const isBlob = dsFileStats[f].filename.indexOf('.ndjson') === -1;
if ((+dsFileStats[f].mtime) >= ((isBlob) ? blobsbefore : before)) { return; } if ((+dsFileStats[f].atime) >= ((isBlob) ? blobsbefore : before)) { return; }
console.log(dsFileStats[f].filename + " " + dsFileStats[f].size + " " + files.push({
(+dsFileStats[f].mtime)); filename: dsFileStats[f].filename,
size: dsFileStats[f].size,
atime: dsFileStats[f].atime
});
} }
}); });
cb(null, files);
} else { } else {
out.sort((a,b) => (a[1] - b[1])); out.sort((a,b) => (a[1] - b[1]));
out.forEach((x) => { console.log(x[0] + ' ' + x[1] + ' MB'); }); cb(null, out.slice());
} }
}); });
};
if (!module.parent) {
let config = {};
if (process.argv.indexOf('--unpinned') > -1) { config.unpinned = true; }
const ot = process.argv.indexOf('--olderthan');
config.olderthan = ot > -1 && new Date(process.argv[ot+1]);
const bot = process.argv.indexOf('--blobsolderthan');
config.blobsolderthan = bot > -1 && new Date(process.argv[bot+1]);
module.exports.load(config, function (err, data) {
if (err) { throw new Error(err); }
if (!Array.isArray(data)) { return; }
if (config.unpinned) {
data.forEach((f) => { console.log(f.filename + " " + f.size + " " + (+f.atime)); });
} else {
data.forEach((x) => { console.log(x[0] + ' ' + x[1] + ' MB'); });
}
});
}

@ -555,8 +555,11 @@ define([
$loading = $('#' + LOADING); //.show(); $loading = $('#' + LOADING); //.show();
$loading.css('display', ''); $loading.css('display', '');
$loading.removeClass('cp-loading-hidden'); $loading.removeClass('cp-loading-hidden');
$('.cp-loading-spinner-container').show();
if (loadingText) { if (loadingText) {
$('#' + LOADING).find('p').text(loadingText); $('#' + LOADING).find('p').text(loadingText);
} else {
$('#' + LOADING).find('p').text('');
} }
$container = $loading.find('.cp-loading-container'); $container = $loading.find('.cp-loading-container');
} else { } else {
@ -612,7 +615,10 @@ define([
if (exitable) { if (exitable) {
$(window).focus(); $(window).focus();
$(window).keydown(function (e) { $(window).keydown(function (e) {
if (e.which === 27) { $('#' + LOADING).hide(); } if (e.which === 27) {
$('#' + LOADING).hide();
if (typeof(exitable) === "function") { exitable(); }
}
}); });
} }
}; };

@ -426,15 +426,7 @@ define([
common.getSframeChannel().onReady(waitFor()); common.getSframeChannel().onReady(waitFor());
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
Test.registerInner(common.getSframeChannel()); Test.registerInner(common.getSframeChannel());
if (!AppConfig.displayCreationScreen) { return; } common.handleNewFile(waitFor);
var priv = common.getMetadataMgr().getPrivateData();
if (priv.isNewFile) {
var c = (priv.settings.general && priv.settings.general.creation) || {};
if (c.skip && !priv.forceCreationScreen) {
return void common.createPad(c, waitFor());
}
common.getPadCreationScreen(c, waitFor());
}
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
cpNfInner = common.startRealtime({ cpNfInner = common.startRealtime({
// really basic operational transform // really basic operational transform

@ -187,7 +187,7 @@ define([
upgradeURL: Cryptpad.upgradeURL upgradeURL: Cryptpad.upgradeURL
}, },
isNewFile: isNewFile, isNewFile: isNewFile,
isDeleted: window.location.hash.length > 0, isDeleted: isNewFile && window.location.hash.length > 0,
forceCreationScreen: forceCreationScreen forceCreationScreen: forceCreationScreen
}; };
for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; } for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; }
@ -666,8 +666,9 @@ define([
Utils.Feedback.reportAppUsage(); Utils.Feedback.reportAppUsage();
if (!realtime) { return; } if (!realtime) { return; }
if (isNewFile && Utils.LocalStore.isLoggedIn() if (isNewFile && cfg.useCreationScreen) { return; }
&& AppConfig.displayCreationScreen && cfg.useCreationScreen) { return; } //if (isNewFile && Utils.LocalStore.isLoggedIn()
// && AppConfig.displayCreationScreen && cfg.useCreationScreen) { return; }
startRealtime(); startRealtime();
}); });

@ -167,6 +167,30 @@ define([
}; };
// Store // Store
funcs.handleNewFile = function (waitFor) {
var priv = ctx.metadataMgr.getPrivateData();
if (priv.isNewFile) {
var c = (priv.settings.general && priv.settings.general.creation) || {};
var skip = !AppConfig.displayCreationScreen || (c.skip && !priv.forceCreationScreen);
// If this is a new file but we have a hash in the URL and pad creation screen is
// not displayed, then display an error...
if (priv.isDeleted && (!funcs.isLoggedIn() || skip)) {
UI.errorLoadingScreen(Messages.inactiveError, false, function () {
UI.addLoadingScreen();
return void funcs.createPad({}, waitFor());
});
return;
}
// Otherwise, if we don't display the screen, it means it is not a deleted pad
// so we can continue and start realtime...
if (!funcs.isLoggedIn() || skip) {
return void funcs.createPad(c, waitFor());
}
// If we display the pad creation screen, it will handle deleted pads directly
console.log('here');
funcs.getPadCreationScreen(c, waitFor());
}
};
funcs.createPad = function (cfg, cb) { funcs.createPad = function (cfg, cb) {
ctx.sframeChan.query("Q_CREATE_PAD", { ctx.sframeChan.query("Q_CREATE_PAD", {
owned: cfg.owned, owned: cfg.owned,

@ -593,7 +593,7 @@ define([
}; };
var createUnpinnedWarning0 = function (toolbar, config) { var createUnpinnedWarning0 = function (toolbar, config) {
if (true) { return; } // stub this call since it won't make it into the next release //if (true) { return; } // stub this call since it won't make it into the next release
if (Common.isLoggedIn()) { return; } if (Common.isLoggedIn()) { return; }
var pd = config.metadataMgr.getPrivateData(); var pd = config.metadataMgr.getPrivateData();
var o = pd.origin; var o = pd.origin;

@ -1274,16 +1274,9 @@ define([
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
common.getSframeChannel().onReady(waitFor()); common.getSframeChannel().onReady(waitFor());
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
if (!AppConfig.displayCreationScreen) { return; } common.handleNewFile(waitFor);
var priv = common.getMetadataMgr().getPrivateData();
if (priv.isNewFile) {
var c = (priv.settings.general && priv.settings.general.creation) || {};
if (c.skip && !priv.forceCreationScreen) {
return void common.createPad(c, waitFor());
}
common.getPadCreationScreen(c, waitFor());
}
}).nThen(function (/* waitFor */) { }).nThen(function (/* waitFor */) {
console.log('here');
Test.registerInner(common.getSframeChannel()); Test.registerInner(common.getSframeChannel());
var metadataMgr = common.getMetadataMgr(); var metadataMgr = common.getMetadataMgr();
APP.locked = APP.readOnly = metadataMgr.getPrivateData().readOnly; APP.locked = APP.readOnly = metadataMgr.getPrivateData().readOnly;

@ -662,15 +662,7 @@ define([
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
common.getSframeChannel().onReady(waitFor()); common.getSframeChannel().onReady(waitFor());
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
if (!AppConfig.displayCreationScreen) { return; } common.handleNewFile(waitFor);
var priv = common.getMetadataMgr().getPrivateData();
if (priv.isNewFile) {
var c = (priv.settings.general && priv.settings.general.creation) || {};
if (c.skip && !priv.forceCreationScreen) {
return void common.createPad(c, waitFor());
}
common.getPadCreationScreen(c, waitFor());
}
}).nThen(function (/*waitFor*/) { }).nThen(function (/*waitFor*/) {
andThen(common); andThen(common);
}); });

Loading…
Cancel
Save