pkgcloud
Version:
An infrastructure-as-a-service agnostic cloud library for node.js
251 lines (206 loc) • 6.49 kB
JavaScript
/*
* files.js: Instance methods for working with files (blobs) from Azure
*
* (C) Microsoft Open Technologies, Inc.
*
*/
var fs = require('fs'),
filed = require('filed'),
mime = require('mime'),
request = require('request'),
utile = require('utile'),
qs = require('querystring'),
base = require('../../../core/storage'),
AzureConstants = require('../../utils/constants'),
pkgcloud = require('../../../../../lib/pkgcloud'),
storage = pkgcloud.providers.azure.storage;
//
// ### function removeFile (container, file, callback)
// #### @container {string} Name of the container to destroy the file in
// #### @file {string} Name of the file to destroy.
// #### @callback {function} Continuation to respond to when complete.
// Destroys the `file` in the specified `container`.
//
exports.removeFile = function (container, file, callback) {
if (container instanceof storage.Container) {
container = container.name;
}
if (file instanceof storage.File) {
file = file.name;
}
this.request('DELETE', [container + '/' + file], callback, function (body, res) {
callback(null, res.statusCode == 202);
});
};
exports.upload = function (options, callback) {
if (typeof options === 'function' && !callback) {
callback = options;
options = {};
}
var container = options.container,
path,
rstream,
lstream;
if (container instanceof storage.Container) {
container = container.name;
}
options.headers = options.headers || {};
if (options.local) {
lstream = filed(options.local);
options.headers['content-length'] = fs.statSync(options.local).size;
}
else if (options.stream) {
lstream = options.stream;
}
if (options.headers && !options.headers['content-type'] && options.remote) {
options.headers['content-type'] = mime.lookup(options.remote);
}
options.headers['x-ms-blob-type'] = AzureConstants.BlobConstants.BlobTypes.BLOCK;
path = [container + '/' + options.remote];
if(options.azureBlockId) {
path[1] = '?comp=block&blockid=' + encodeURIComponent(options.azureBlockId);
}
if (options.headers['content-length'] !== undefined) {
// Regular upload
rstream = this.request({
method: 'PUT',
upload: true,
path: path,
headers: options.headers || {}
}, callback, function (body, res) {
callback(null, res.statusCode === 200 || res.statusCode === 201, res);
});
} else {
// Multi-part, 5mb chunk upload
rstream = this.multipartUpload(options, callback);
}
if (lstream) lstream.pipe(rstream);
return rstream;
};
var getBlockId = function(a, b) {
return "block" + ((1e15 + a + "").slice(-b));
};
exports.multipartUpload = function (options, callback) {
var self = this,
container = options.container,
chunk = AzureConstants.BlobConstants.DEFAULT_WRITE_BLOCK_SIZE_IN_BYTES,
numBlocks = 0,
chunksFinished = [],
stream = new storage.ChunkedStream(chunk),
ended = false;
if (container instanceof storage.Container) {
container = container.name;
}
stream.on('data', function(data) {
stream.pause();
options.azureBlockId = getBlockId(numBlocks++, 15);
options.headers['content-length'] = data.length;
//console.log(data.length);
var next = function(body, res) {
if(!ended) {
stream.resume();
} else {
self.sendBlockList(options, numBlocks, callback);
}
};
var rstream = self.upload(options, next);
rstream.write(data);
rstream.end();
});
stream.on('end', function(data) {
//console.log('stream end');
ended = true;
});
return stream;
};
exports.sendBlockList = function(options, numBlocks, callback) {
var body,
container = options.container;
if (container instanceof storage.Container) {
container = container.name;
}
options.headers = options.headers || {};
// remove x-ms-blob-type header or request will fail
if(options.headers['x-ms-blob-type']) {
delete options.headers['x-ms-blob-type'];
}
if(options.headers['content-type']) {
options.headers['x-ms-blob-content-type'] = options.headers['content-type'];
} else if (options.remote) {
options.headers['x-ms-blob-content-type'] = mime.lookup(options.remote);
}
path = [container + '/' + options.remote];
path[1] = '?comp=blocklist';
body = '<?xml version="1.0" encoding="utf-8"?>';
body += '<BlockList>';
for(var i = 0; i < numBlocks; i++) {
body += '<Latest>' + encodeURIComponent(getBlockId(i,15)) + '</Latest>';
}
body += '</BlockList>';
//console.log(body);
options.headers['content-length'] = body.length;
this.request({
method: 'PUT',
path: path,
body: body,
headers: options.headers
}, callback, function (body, res) {
callback(null, res.statusCode === 201, res);
});
};
exports.download = function (options, callback) {
var self = this,
container = options.container,
lstream,
rstream;
if (container instanceof storage.Container) {
container = container.name;
}
if (options.local) {
lstream = filed(options.local);
}
else if (options.stream) {
lstream = options.stream;
}
rstream = this.request({
path: [container + '/' + options.remote],
download: true
}, callback, function (body, res) {
callback(null, new (storage.File)(self, utile.mixin(res.headers, {
container: container,
name: options.remote
})));
});
if (lstream) {
rstream.pipe(lstream);
}
return rstream;
};
exports.getFile = function (container, file, callback) {
var containerName = container instanceof base.Container ? container.name : container,
self = this;
this.request({
method: 'GET',
path: [containerName + '/' + file ]
}, callback, function (body, res) {
callback(null, new storage.File(self, utile.mixin(res.headers, {
container: container,
name: file
})));
});
}
exports.getFiles = function (container, download, callback) {
var containerName = container instanceof base.Container ? container.name : container,
self = this;
this.xmlRequest('GET', [containerName,'?restype=container&comp=list'], callback, function (body, res) {
//console.log(body);
if(body.Blobs && body.Blobs.Blob) {
callback(null, self._toArray(body.Blobs.Blob).map(function (file) {
file.container = container;
return new storage.File(self, file);
}));
} else {
callback(null, []);
}
});
};