Skip to content

Commit

Permalink
Created chunked download api.
Browse files Browse the repository at this point in the history
  • Loading branch information
supernomad committed Jun 21, 2015
1 parent c73f574 commit 71cec10
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 6 deletions.
20 changes: 19 additions & 1 deletion libs/models/apiModels.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,27 @@ function upload(request) {
};
}

function download(request, size, chunkSize) {
var self = this;
self.id = "";
self.path = request.path;
self.fileSize = size;
self.chunkSize = chunkSize;
self.count = (size / chunkSize) + (size % chunkSize > 0 ? 1 : 0);
self.chunks = [];

self.configure = function(id) {
self.id = id;
for (var i = 0; i < self.count; i++) {
self.chunks.push(false);
}
};
}

module.exports = {
RouteHandler: routeHandler,
ErrorHandler: errorHandler,
ApiResponse: apiResponse,
Upload: upload
Upload: upload,
Download: download
};
5 changes: 5 additions & 0 deletions libs/models/errorModels.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ function uploadMissing() {
return new GenericError(404, "Missing Upload Error", "The upload specified could not be found, please check the supplied id and try again.");
}

function downloadMissing() {
return new GenericError(404, "Missing Download Error", "The download specified could not be found, please check the supplied id and try again.");
}

function serverError() {
return new GenericError(500, "Internal Server Error", "There has been an internal server error, so things are basically blowing up in our datacenter. We will get back to you in a minute but go ahead and try again.");
}
Expand All @@ -20,6 +24,7 @@ function validationError(msg) {
module.exports = {
GenericError: GenericError,
UploadMissing: uploadMissing,
DownloadMissing: downloadMissing,
ServerError: serverError,
ValidationError: validationError
};
18 changes: 18 additions & 0 deletions libs/validators/chunked-download-validators.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
var typeHelper = require.main.require('libs/helpers/typeHelper');

var valid = "valid";

function validateDownloadRequest(downloadRequest) {
if(!typeHelper.isObject(downloadRequest)) {
return "Download request object was not recieved, or is not an object.";
} else if (!typeHelper.isString(downloadRequest.path)) {
return "Download path missing or it is not a string.";
} else {
return valid;
}
}

module.exports = {
valid: valid,
validateDownloadRequest: validateDownloadRequest
};
84 changes: 79 additions & 5 deletions routes/chunked-download-routes.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,109 @@
/* global Buffer */
var apiModels = require.main.require('libs/models/apiModels'),
errorModels = require.main.require('libs/models/errorModels'),
guidHelper = require.main.require('libs/helpers/guidHelper'),
errorHelper = require.main.require('libs/helpers/errorHelper'),
typeHelper = require.main.require('libs/helpers/typeHelper'),
stringHelper = require.main.require('libs/helpers/stringHelper');
stringHelper = require.main.require('libs/helpers/stringHelper'),
validators = require.main.require('libs/validators/chunked-download-validators');

var debug = false,
routePrefix = "/chunked/download",
defaultTtl = 3600,
chunkSize = 1024,
io = null,
dataCache = null;

var routes = {
"get": new apiModels.RouteHandler(routePrefix + "/:downloadId/:index", function (req, res) {
var index = parseInt(req.params.index);
if (!guidHelper.isGuid(req.params.downloadId)){
throw errorModels.ValidationError("The supplied downloadId is not a valid v4 GUID");
} else if (!typeHelper.isNumber(index)) {
throw errorModels.ValidationError("The supplied index is not a valid number");
}

dataCache.restore(req.params.downloadId, function(error, keyVal) {
errorHelper.genericErrorHandler(error);
if(typeHelper.doesExist(keyVal.value)) {
var download = keyVal.value,
buffer = new Buffer(chunkSize);

function readCallback(error, read, buffer) {

}

download.chunks[index] = true;
dataCache.update(download.id, download, defaultTtl, function (error, success) {
errorHelper.genericErrorHandler(error);
if(success) {
io.ReadFileChunk(download.path, buffer, 0, buffer.length, index * chunkSize, readCallback);
} else {
throw errorModels.ServerError();
}
});
} else {
throw errorModels.DownloadMissing();
}
});
}),
"post": new apiModels.RouteHandler(routePrefix, function (req, res) {

var valid = validators.validateUploadRequest(req.body);
if(valid !== validators.valid) {
throw errorModels.ValidationError(valid);
}
io.getFileStats(req.body.path, function(error, stats) {
var download = new apiModels.Download(req.body, stats.size, chunkSize);
download.configure(guidHelper.newGuid());

dataCache.create(download.id, download, defaultTtl, function (error, success) {
errorHelper.genericErrorHandler(error);
if(success) {
res.json(new apiModels.ApiResponse(routePrefix, {}, download));
} else {
throw errorModels.ServerError();
}
});
});
}),
"delete": new apiModels.RouteHandler(routePrefix + "/:downloadId", function (req, res) {
if (!guidHelper.isGuid(req.params.downloadId)){
throw errorModels.ValidationError("The supplied downloadId is not a valid v4 GUID");
}

dataCache.restore(req.params.downloadId, function(error, download) {
errorHelper.genericErrorHandler(error, debug);

dataCache.delete(download.id, function (error, count) {
errorHelper.genericErrorHandler(error);
res.json(new apiModels.ApiResponse(routePrefix, {}, "Download: " + req.params.downloadId + ", deleted successfuly."));
});
});
}),
"error": new apiModels.ErrorHandler(function (error, req, res, next) {

if(error instanceof errorModels.GenericError) {
res.status(error.Code);
res.json({
Error: error.Error,
Message: error.Message
});
} else {
next();
}
})
};

function configure(cache, storage, options) {
if(typeHelper.isObject(options)) {
if(options.hasOwnProperty('debug') && typeHelper.isBoolean(options.debug)){
if(typeHelper.isBoolean(options.debug)){
debug = options.debug;
}
if(options.hasOwnProperty('routePrefix') && typeHelper.isString(options.routePrefix)){
if(typeHelper.isString(options.routePrefix)){
routePrefix = stringHelper.stripTrailingSlashes(options.routePrefix);
}
if(typeHelper.isNumber(options.chunkSize)) {
chunkSize = options.chunkSize
}
}

io = storage;
Expand Down

0 comments on commit 71cec10

Please sign in to comment.