Skip to content

Commit

Permalink
Merge pull request #4 from Supernomad/chunked-v2
Browse files Browse the repository at this point in the history
Chunked v2
  • Loading branch information
supernomad committed Jun 28, 2015
2 parents 78ae544 + c1c9015 commit 84b9122
Show file tree
Hide file tree
Showing 29 changed files with 2,253 additions and 127 deletions.
14 changes: 11 additions & 3 deletions Gruntfile.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-mocha-istanbul');
grunt.loadNpmTasks('grunt-coveralls');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-mkdir');
grunt.loadNpmTasks('grunt-mkdir');
grunt.loadNpmTasks('grunt-coveralls');
grunt.loadNpmTasks('grunt-codacy');

grunt.initConfig({
mkdir: {
Expand All @@ -28,9 +29,16 @@ module.exports = function(grunt) {
coverage: {
src: 'coverage/*.info'
}
},
codacy: {
options: {
},
coverage: {
src: 'coverage/lcov.info'
}
}
});

grunt.registerTask('build', ['mkdir:testDir', 'mocha_istanbul:coverage', 'coveralls:coverage']);
grunt.registerTask('build', ['mkdir:testDir', 'mocha_istanbul:coverage', 'coveralls:coverage', 'codacy:coverage']);
grunt.registerTask('default', ['mkdir:testDir', 'mocha_istanbul:coverage']);
};
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[![GitHub license](https://img.shields.io/badge/license-GPL--3.0-green.svg)]()
[![GitHub version](https://badge.fury.io/gh/supernomad%2Fchunky.svg)](http://badge.fury.io/gh/supernomad%2Fchunky)
[![Codacy Badge](https://www.codacy.com/project/badge/92a9c19ff3474abeaaf0e869317da1a3)](https://www.codacy.com/app/csaide/chunky)
[![Coverage Status](https://coveralls.io/repos/Supernomad/chunky/badge.svg?branch=master)](https://coveralls.io/r/Supernomad/chunky?branch=master)
[![Build Status](https://travis-ci.org/Supernomad/chunky.svg?branch=master)](https://travis-ci.org/Supernomad/chunky)
# chunky
Expand Down
54 changes: 54 additions & 0 deletions apis/chunked-api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/* global __dirname */
var express = require('express'),
router = express.Router();

var multer = require('multer'),
bodyParser = require('body-parser'),
typeHelper = require.main.require('libs/helpers/typeHelper'),
io = require.main.require('libs/io'),
apiCache = null,
uploadRoutes = null,
downloadRoutes = null,
tempChunkPath = __dirname + '/tmp/chunks';

function configure(cache, options) {
if(!typeHelper.isObject(cache)) {
apiCache = require.main.require('libs/caching/localCache');
}
else {
apiCache = cache;
}

if(!typeHelper.isObject(options)) {
options = {};
}

uploadRoutes = require.main.require('routes/chunked-upload-routes')(apiCache, io, options);
downloadRoutes = require.main.require('routes/chunked-download-routes')(apiCache, io, options);

// Request parsing
router.use(bodyParser.json());
router.use(bodyParser.urlencoded({extended: false}));
router.use(multer({
dest: tempChunkPath,
rename: function (fieldname, filename) {
return filename.replace(/\W+/g, '-').toLowerCase() + '-' + fieldname.replace(/\W+/g, '-').toLowerCase();
}
}));

// Upload routes
router.get(uploadRoutes.get.uri, uploadRoutes.get.handler);
router.post(uploadRoutes.post.uri, uploadRoutes.post.handler);
router.put(uploadRoutes.put.uri, uploadRoutes.put.handler);
router.delete(uploadRoutes.delete.uri, uploadRoutes.delete.handler);
router.use(uploadRoutes.error.handler);

// Download routes
router.get(downloadRoutes.get.uri, downloadRoutes.get.handler);
router.post(downloadRoutes.post.uri, downloadRoutes.post.handler);
router.delete(downloadRoutes.delete.uri, downloadRoutes.delete.handler);
router.use(downloadRoutes.error.handler);
return router;
}

module.export = configure;
45 changes: 45 additions & 0 deletions libs/caching/localCache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
var NodeCache = require('node-cache'),
cache = new NodeCache({ stdTTL: 0, checkperiod: 300, useClones: false }),
typeHelper = require.main.require('libs/helpers/typeHelper');

function create(key, val, ttl, callback) {
if(typeHelper.isFunction(callback)) {
cache.set(key, val, ttl, function (error, success) {
callback(error, success);
});
} else {
return cache.set(key, val, ttl);
}
}

function restore(key, callback) {
if(typeHelper.isFunction(callback)) {
cache.get(key, function (error, value) {
callback(error, {key: key, value: value});
});
} else {
var value = cache.get(key);
return {key: key, value: value};
}
}

function update(key, val, ttl, callback) {
return create(key, val, ttl, callback);
}

function del(key, callback) {
if(typeHelper.isFunction(callback)) {
cache.del(key, function (error, count) {
callback(error, count);
});
} else {
return cache.del(key);
}
}

module.exports = {
'create': create,
'restore': restore,
'update': update,
'delete': del
};
13 changes: 13 additions & 0 deletions libs/helpers/errorHelper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
function genericErrorHandler(error, debug) {
if(error) {
if(debug) {
console.error(error);
}
throw new Error(JSON.stringify(error));
}
}


module.exports = {
genericErrorHandler: genericErrorHandler
};
33 changes: 33 additions & 0 deletions libs/helpers/guidHelper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* global process */
var typeHelper = require.main.require('libs/helpers/typeHelper');

var nanoToSeconds = function(nano) {
return parseFloat(nano) / 1000000000;
};

var getFloatingTime = function() {
var time = process.hrtime();
var seconds = time[0];
var nanoseconds = time[1];
return parseFloat(seconds) + nanoToSeconds(nanoseconds);
};

function newGuid() {
var d = getFloatingTime();
var guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (d + Math.random()*16)%16 | 0;
d = Math.floor(d/16);
return (c==='x' ? r : (r&0x3|0x8)).toString(16);
});
return guid;
}

function isGuid(guid) {
return typeHelper.isString(guid)
&& /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[8,9,a,b][0-9a-f]{3}-[0-9a-f]{12}$/ig.test(guid);
}

module.exports = {
newGuid: newGuid,
isGuid: isGuid
};
14 changes: 9 additions & 5 deletions libs/helpers/stringHelper.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
var typeHelper = require('./typeHelper');
var typeHelper = require.main.require('libs/helpers/typeHelper');

function stripTrailingSlashes(str) {
if(!typeHelper.isString(str)) return str;
if(!typeHelper.isString(str)) {
return str;
}
// Match any trailing slash, both '\' and '/', in any quantity as long as they are the last characters in the string.
return str.replace(/[\\\/]+$/g, "");
return str.replace(/[\\\/]+$/g, '');
}

function stripLeadingSlashes(str) {
if(!typeHelper.isString(str)) return str;
if(!typeHelper.isString(str)) {
return str;
}
// Match any leading slash, both '\' and '/', in any quantity as long as they are the first characters in the string.
return str.replace(/^[\\\/]+/g, "");
return str.replace(/^[\\\/]+/g, '');
}

module.exports = {
Expand Down
10 changes: 5 additions & 5 deletions libs/helpers/typeHelper.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
function doesExist(obj) {
return obj != null && obj != undefined;
return obj !== null && obj !== undefined;
}

function isObject(obj) {
return doesExist(obj) && typeof obj === "object";
return doesExist(obj) && typeof obj === 'object';
}

function isFunction(obj) {
return doesExist(obj) && Object.prototype.toString.call(obj) === '[object Function]';
}

function isString(obj) {
return doesExist(obj) && typeof obj === "string";
return doesExist(obj) && typeof obj === 'string';
}

function isBoolean(obj) {
return doesExist(obj) && typeof obj === "boolean";
return doesExist(obj) && typeof obj === 'boolean';
}

function isNumber(obj) {
return doesExist(obj) && typeof obj === "number";
return doesExist(obj) && !isNaN(obj) && typeof obj === 'number';
}

function isType(obj, typ) {
Expand Down
16 changes: 8 additions & 8 deletions libs/io.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ function getFileStats(path, callback) {
});
}

function getWriteStream(path, position, type) {
return fs.createWriteStream(path, {
flags: type,
mode: 0666,
start: position
});
}

function createFile(path, buffer, offset, length, callback) {
var stream = getWriteStream(path, 0, 'w');
return stream.write(buffer.slice(offset, offset + length), function(error) {
Expand Down Expand Up @@ -48,14 +56,6 @@ function renameFile(path, newPath, callback) {
});
}

function getWriteStream(path, position, type) {
return fs.createWriteStream(path, {
flags: type,
mode: 0666,
start: position
});
}

module.exports = {
GetFileStats: getFileStats,
CreateFile: createFile,
Expand Down
51 changes: 50 additions & 1 deletion libs/models/apiModels.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,56 @@ function errorHandler(handler) {
self.handler = handler;
}

function apiResponse(api, meta, data) {
var self = this;
self.api = api;
self.meta = meta;
self.data = data;
}

function upload(request) {
var self = this;
self.id = '';
self.fileName = request.fileName;
self.fileSize = request.fileSize;
self.destination = request.destination;
self.tempPath = '';
self.finalPath = '';
self.chunkSize = request.chunkSize;
self.count = request.count;
self.chunks = [];

self.configure = function(id) {
self.id = id;
for (var i = 0; i < self.count; i++) {
self.chunks.push(false);
};
self.tempPath = self.destination + '/' + self.id;
self.finalPath = self.destination + '/' + self.fileName;
};
}

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
ErrorHandler: errorHandler,
ApiResponse: apiResponse,
Upload: upload,
Download: download
};
30 changes: 30 additions & 0 deletions libs/models/errorModels.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
function GenericError(code, error, message) {
var self = this;
self.Code = code;
self.Error = error;
self.Message = message;
}

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.");
}

function validationError(msg) {
return new GenericError(400, "Validation Error", 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
};
Loading

0 comments on commit 84b9122

Please sign in to comment.