Last active
January 31, 2022 16:21
-
-
Save loretoparisi/43653aab4ec3f1aeac9130bfd61a316a to your computer and use it in GitHub Desktop.
Resumable.JS - NodeJS Express example - fixed deprecations, updated to latest express
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var fs = require("fs"), | |
path = require("path"), | |
util = require("util"), | |
Stream = require("stream").Stream; | |
module.exports = resumable = function (temporaryFolder) { | |
var $ = this; | |
$.temporaryFolder = temporaryFolder; | |
$.maxFileSize = null; | |
$.fileParameterName = "file"; | |
try { | |
fs.mkdirSync($.temporaryFolder); | |
} catch (e) {} | |
var cleanIdentifier = function (identifier) { | |
return identifier.replace(/^0-9A-Za-z_-/gim, ""); | |
}; | |
var getChunkFilename = function (chunkNumber, identifier) { | |
// Clean up the identifier | |
identifier = cleanIdentifier(identifier); | |
// What would the file name be? | |
return path.join( | |
$.temporaryFolder, | |
"./resumable-" + identifier + "." + chunkNumber | |
); | |
}; | |
var validateRequest = function ( | |
chunkNumber, | |
chunkSize, | |
totalSize, | |
identifier, | |
filename, | |
fileSize | |
) { | |
// Clean up the identifier | |
identifier = cleanIdentifier(identifier); | |
// Check if the request is sane | |
if ( | |
chunkNumber == 0 || | |
chunkSize == 0 || | |
totalSize == 0 || | |
identifier.length == 0 || | |
filename.length == 0 | |
) { | |
return "non_resumable_request"; | |
} | |
var numberOfChunks = Math.max(Math.floor(totalSize / (chunkSize * 1.0)), 1); | |
if (chunkNumber > numberOfChunks) { | |
return "invalid_resumable_request1"; | |
} | |
// Is the file too big? | |
if ($.maxFileSize && totalSize > $.maxFileSize) { | |
return "invalid_resumable_request2"; | |
} | |
if (typeof fileSize != "undefined") { | |
if (chunkNumber < numberOfChunks && fileSize != chunkSize) { | |
// The chunk in the POST request isn't the correct size | |
return "invalid_resumable_request3"; | |
} | |
if ( | |
numberOfChunks > 1 && | |
chunkNumber == numberOfChunks && | |
fileSize != (totalSize % chunkSize) + chunkSize | |
) { | |
// The chunks in the POST is the last one, and the fil is not the correct size | |
return "invalid_resumable_request4"; | |
} | |
if (numberOfChunks == 1 && fileSize != totalSize) { | |
// The file is only a single chunk, and the data size does not fit | |
return "invalid_resumable_request5"; | |
} | |
} | |
return "valid"; | |
}; | |
//'found', filename, original_filename, identifier | |
//'not_found', null, null, null | |
$.get = function (req, callback) { | |
var chunkNumber = req.query["resumableChunkNumber"] || 0; | |
var chunkSize = req.query["resumableChunkSize"] || 0; | |
var totalSize = req.query["resumableTotalSize"] || 0; | |
var identifier = req.query["resumableIdentifier"] || ""; | |
var filename = req.query["resumableFilename"] || ""; | |
if ( | |
validateRequest( | |
chunkNumber, | |
chunkSize, | |
totalSize, | |
identifier, | |
filename | |
) == "valid" | |
) { | |
var chunkFilename = getChunkFilename(chunkNumber, identifier); | |
fs.stat(chunkFilename, function (err, stats) { | |
if (!err) { | |
callback("found", chunkFilename, filename, identifier); | |
} else { | |
callback("not_found", null, null, null); | |
} | |
}); | |
} else { | |
callback("not_found", null, null, null); | |
} | |
}; | |
//'partly_done', filename, original_filename, identifier | |
//'done', filename, original_filename, identifier | |
//'invalid_resumable_request', null, null, null | |
//'non_resumable_request', null, null, null | |
//CHANGED HERE REMOVED REQ AND PROVIDED FIELDS AND FILES PARAMETER | |
$.post = function (req, callback) { | |
var fields = req.body; | |
var files = req.files; | |
var chunkNumber = fields["resumableChunkNumber"]; | |
var chunkSize = fields["resumableChunkSize"]; | |
var totalSize = fields["resumableTotalSize"]; | |
var identifier = cleanIdentifier(fields["resumableIdentifier"].toString()); | |
var filename = fields["resumableFilename"]; | |
var original_filename = fields["resumableIdentifier"]; | |
//Changed !files[$.fileParameterName] to files['file'][0].fieldName & !files[$.fileParameterName].size to files["file"][0].size | |
console.log("files parameter name: " + files["file"]["fieldName"]); | |
if (!files["file"].fieldName || !files["file"].size) { | |
callback("invalid_resumable_request", null, null, null); | |
return; | |
} | |
var validation = validateRequest( | |
chunkNumber, | |
chunkSize, | |
totalSize, | |
identifier, | |
files["file"].size | |
); | |
if (validation == "valid") { | |
var chunkFilename = getChunkFilename(chunkNumber, identifier); | |
// Save the chunk (TODO: OVERWRITE) | |
fs.rename(files["file"].path, chunkFilename, function () { | |
// Do we have all the chunks? | |
var currentTestChunk = 1; | |
var numberOfChunks = Math.max( | |
Math.floor(totalSize / (chunkSize * 1.0)), | |
1 | |
); | |
var testChunkExists = function () { | |
fs.stat(getChunkFilename(currentTestChunk, identifier), function (err, stats) { | |
if (!err) { | |
currentTestChunk++; | |
if (currentTestChunk > numberOfChunks) { | |
callback("done", filename, original_filename, identifier); | |
} else { | |
// Recursion | |
testChunkExists(); | |
} | |
} else { | |
callback("partly_done", filename, original_filename, identifier); | |
} | |
}); | |
}; | |
testChunkExists(); | |
}); | |
} else { | |
callback(validation, filename, original_filename, identifier); | |
} | |
}; | |
// Pipe chunks directly in to an existsing WritableStream | |
// r.write(identifier, response); | |
// r.write(identifier, response, {end:false}); | |
// | |
// var stream = fs.createWriteStream(filename); | |
// r.write(identifier, stream); | |
// stream.on('data', function(data){...}); | |
// stream.on('end', function(){...}); | |
$.write = function (identifier, writableStream, options) { | |
options = options || {}; | |
options.end = typeof options["end"] == "undefined" ? true : options["end"]; | |
// Iterate over each chunk | |
var pipeChunk = function (number) { | |
var chunkFilename = getChunkFilename(number, identifier); | |
fs.stat(chunkFilename, function (err, stats) { | |
if (!err) { | |
// If the chunk with the current number exists, | |
// then create a ReadStream from the file | |
// and pipe it to the specified writableStream. | |
var sourceStream = fs.createReadStream(chunkFilename); | |
sourceStream.pipe(writableStream, { | |
end: false, | |
}); | |
sourceStream.on("end", function () { | |
// When the chunk is fully streamed, | |
// jump to the next one | |
pipeChunk(number + 1); | |
}); | |
} else { | |
// When all the chunks have been piped, end the stream | |
if (options.end) writableStream.end(); | |
if (options.onDone) options.onDone(); | |
} | |
}); | |
}; | |
pipeChunk(1); | |
}; | |
$.clean = function (identifier, options) { | |
options = options || {}; | |
// Iterate over each chunk | |
var pipeChunkRm = function (number) { | |
var chunkFilename = getChunkFilename(number, identifier); | |
//console.log('removing pipeChunkRm ', number, 'chunkFilename', chunkFilename); | |
fs.exists(chunkFilename, function (exists) { | |
if (exists) { | |
console.log("exist removing ", chunkFilename); | |
fs.unlink(chunkFilename, function (err) { | |
if (err && options.onError) options.onError(err); | |
}); | |
pipeChunkRm(number + 1); | |
} else { | |
if (options.onDone) options.onDone(); | |
} | |
}); | |
}; | |
pipeChunkRm(1); | |
}; | |
return $; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment