diff --git a/config.sample.js b/config.sample.js index dd53e7e..e243a7e 100644 --- a/config.sample.js +++ b/config.sample.js @@ -208,23 +208,32 @@ module.exports = { /* Chunk size for chunked uploads. Needs to be in MB. + If this is enabled, every files uploaded from the homepage uploader will forcibly be chunked by the size specified in "default". Users can configure the chunk size they want from the homepage uploader, but you can force allowed max size of each chunk with "max". Min size will always be 1MB. + Users will still be able to upload bigger files with the API as long as they don't surpass the limit specified in the "maxSize" option above. Once all chunks have been uploads, their total size will be tested against the "maxSize" option again. + + With "timeout", you can specify how long a particular chunked upload attempt + can remain inactive before their temporary data gets cleared out + (partially uploaded files or other internal data). + This option is mainly useful for hosters that use Cloudflare, since Cloudflare limits upload size to 100MB on their Free plan. https://support.cloudflare.com/hc/en-us/articles/200172516#h_51422705-42d0-450d-8eb1-5321dcadb5bc - NOTE: Set to falsy value to disable chunked uploads. + + NOTE: Set "default" or the option itself to falsy value to disable chunked uploads. */ chunkSize: { max: '95MB', - default: '25MB' + default: '25MB', + timeout: 30 * 60 * 1000 // 30 minutes }, /* diff --git a/controllers/uploadController.js b/controllers/uploadController.js index 7aff0db..0cb9e00 100644 --- a/controllers/uploadController.js +++ b/controllers/uploadController.js @@ -29,6 +29,7 @@ const maxFilesPerUpload = 20 const chunkedUploads = config.uploads.chunkSize && typeof config.uploads.chunkSize === 'object' && config.uploads.chunkSize.default +const chunkedUploadsTimeout = config.uploads.chunkSize.timeout || 1800000 const chunksData = {} // Hard-coded min chunk size of 1 MB (e.g. 50 MB = max 50 chunks) const maxChunksCount = maxSize @@ -40,6 +41,35 @@ const urlExtensionsFilter = Array.isArray(config.uploads.urlExtensionsFilter) && const temporaryUploads = Array.isArray(config.uploads.temporaryUploadAges) && config.uploads.temporaryUploadAges.length +class ChunksData { + constructor (uuid, root) { + this.uuid = uuid + this.root = root + this.filename = 'tmp' + this.chunks = 0 + this.stream = null + this.hasher = null + } + + onTimeout () { + if (this.stream) + this.stream.end() + if (this.hasher) + this.hasher.dispose() + self.cleanUpChunks(this.uuid, true) + } + + setTimeout (delay) { + this.clearTimeout() + this._timeout = setTimeout(this.onTimeout.bind(this), delay) + } + + clearTimeout () { + if (this._timeout) + clearTimeout(this._timeout) + } +} + const initChunks = async uuid => { if (chunksData[uuid] === undefined) { const root = path.join(paths.chunks, uuid) @@ -51,14 +81,9 @@ const initChunks = async uuid => { throw err await paths.mkdir(root) } - chunksData[uuid] = { - root, - filename: 'tmp', - chunks: 0, - stream: null, - hasher: null - } + chunksData[uuid] = new ChunksData(uuid, root) } + chunksData[uuid].setTimeout(chunkedUploadsTimeout) return chunksData[uuid] } @@ -539,7 +564,7 @@ self.actuallyFinishChunks = async (req, res, user) => { } } -self.cleanUpChunks = async (uuid) => { +self.cleanUpChunks = async (uuid, onTimeout) => { // Remove tmp file await paths.unlink(path.join(chunksData[uuid].root, chunksData[uuid].filename)) .catch(error => { @@ -549,6 +574,7 @@ self.cleanUpChunks = async (uuid) => { // Remove UUID dir await paths.rmdir(chunksData[uuid].root) // Delete cached chunks data + if (!onTimeout) chunksData[uuid].clearTimeout() delete chunksData[uuid] }