mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 08:36:12 +01:00
feat: new cronjob and ACP option to delete orphans after configurable number of days, closes #10659
This commit is contained in:
@@ -45,6 +45,7 @@
|
|||||||
"allowMultipleBadges": 0,
|
"allowMultipleBadges": 0,
|
||||||
"maximumFileSize": 2048,
|
"maximumFileSize": 2048,
|
||||||
"stripEXIFData": 1,
|
"stripEXIFData": 1,
|
||||||
|
"orphanExpiryDays": 0,
|
||||||
"resizeImageWidthThreshold": 2000,
|
"resizeImageWidthThreshold": 2000,
|
||||||
"resizeImageWidth": 760,
|
"resizeImageWidth": 760,
|
||||||
"rejectImageWidth": 5000,
|
"rejectImageWidth": 5000,
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
{
|
{
|
||||||
"posts": "Posts",
|
"posts": "Posts",
|
||||||
|
"orphans": "Orphaned Files",
|
||||||
"private": "Make uploaded files private",
|
"private": "Make uploaded files private",
|
||||||
"strip-exif-data": "Strip EXIF Data",
|
"strip-exif-data": "Strip EXIF Data",
|
||||||
"preserve-orphaned-uploads": "Keep uploaded files on disk after a post is purged",
|
"preserve-orphaned-uploads": "Keep uploaded files on disk after a post is purged",
|
||||||
|
"orphanExpiryDays": "Days to keep orphaned files",
|
||||||
"private-extensions": "File extensions to make private",
|
"private-extensions": "File extensions to make private",
|
||||||
"private-uploads-extensions-help": "Enter comma-separated list of file extensions to make private here (e.g. <code>pdf,xls,doc</code>). An empty list means all files are private.",
|
"private-uploads-extensions-help": "Enter comma-separated list of file extensions to make private here (e.g. <code>pdf,xls,doc</code>). An empty list means all files are private.",
|
||||||
"resize-image-width-threshold": "Resize images if they are wider than specified width",
|
"resize-image-width-threshold": "Resize images if they are wider than specified width",
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const nconf = require('nconf');
|
const nconf = require('nconf');
|
||||||
|
const fs = require('fs').promises;
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const winston = require('winston');
|
const winston = require('winston');
|
||||||
const mime = require('mime');
|
const mime = require('mime');
|
||||||
const validator = require('validator');
|
const validator = require('validator');
|
||||||
|
const cronJob = require('cron').CronJob;
|
||||||
|
|
||||||
const db = require('../database');
|
const db = require('../database');
|
||||||
const image = require('../image');
|
const image = require('../image');
|
||||||
@@ -27,6 +29,29 @@ module.exports = function (Posts) {
|
|||||||
return fullPath.startsWith(pathPrefix) && await file.exists(fullPath) ? filePath : false;
|
return fullPath.startsWith(pathPrefix) && await file.exists(fullPath) ? filePath : false;
|
||||||
}))).filter(Boolean);
|
}))).filter(Boolean);
|
||||||
|
|
||||||
|
const runJobs = nconf.get('runJobs');
|
||||||
|
if (runJobs) {
|
||||||
|
new cronJob('0 2 * * 0', (async () => {
|
||||||
|
const now = Date.now();
|
||||||
|
const days = meta.config.orphanExpiryDays;
|
||||||
|
if (!days) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let orphans = await Posts.uploads.getOrphans();
|
||||||
|
|
||||||
|
orphans = await Promise.all(orphans.map(async (relPath) => {
|
||||||
|
const { mtimeMs } = await fs.stat(_getFullPath(relPath));
|
||||||
|
return mtimeMs < now - (1000 * 60 * 60 * 24 * meta.config.orphanExpiryDays) ? relPath : null;
|
||||||
|
}));
|
||||||
|
orphans = orphans.filter(Boolean);
|
||||||
|
|
||||||
|
orphans.forEach((relPath) => {
|
||||||
|
file.delete(_getFullPath(relPath));
|
||||||
|
});
|
||||||
|
}), null, true);
|
||||||
|
}
|
||||||
|
|
||||||
Posts.uploads.sync = async function (pid) {
|
Posts.uploads.sync = async function (pid) {
|
||||||
// Scans a post's content and updates sorted set of uploads
|
// Scans a post's content and updates sorted set of uploads
|
||||||
|
|
||||||
@@ -78,6 +103,16 @@ module.exports = function (Posts) {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Posts.uploads.getOrphans = async () => {
|
||||||
|
let files = await fs.readdir(_getFullPath('/files'));
|
||||||
|
files = files.filter(filename => filename !== '.gitignore');
|
||||||
|
|
||||||
|
files = await Promise.all(files.map(async filename => (await Posts.uploads.isOrphan(`files/${filename}`) ? `files/${filename}` : null)));
|
||||||
|
files = files.filter(Boolean);
|
||||||
|
|
||||||
|
return files;
|
||||||
|
};
|
||||||
|
|
||||||
Posts.uploads.isOrphan = async function (filePath) {
|
Posts.uploads.isOrphan = async function (filePath) {
|
||||||
const length = await db.sortedSetCard(`upload:${md5(filePath)}:pids`);
|
const length = await db.sortedSetCard(`upload:${md5(filePath)}:pids`);
|
||||||
return length === 0;
|
return length === 0;
|
||||||
|
|||||||
@@ -13,13 +13,6 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="checkbox">
|
|
||||||
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
|
|
||||||
<input class="mdl-switch__input" type="checkbox" data-field="preserveOrphanedUploads">
|
|
||||||
<span class="mdl-switch__label"><strong>[[admin/settings/uploads:preserve-orphaned-uploads]]</strong></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
|
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
|
||||||
<input class="mdl-switch__input" type="checkbox" data-field="privateUploads">
|
<input class="mdl-switch__input" type="checkbox" data-field="privateUploads">
|
||||||
@@ -127,7 +120,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-2 col-xs-12 settings-header">
|
||||||
|
[[admin/settings/uploads:orphans]]
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-10 col-xs-12">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
|
||||||
|
<input class="mdl-switch__input" type="checkbox" data-field="preserveOrphanedUploads">
|
||||||
|
<span class="mdl-switch__label"><strong>[[admin/settings/uploads:preserve-orphaned-uploads]]</strong></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-group col-sm-4">
|
||||||
|
<label for="orphanExpiryDays">[[admin/settings/uploads:orphanExpiryDays]]</label>
|
||||||
|
<input id="orphanExpiryDays" type="number" min="0" placeholder="0 to disable" class="form-control" data-field="orphanExpiryDays" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
Reference in New Issue
Block a user