Skip to content

Commit

Permalink
Add support for all streams for uploading files (#519)
Browse files Browse the repository at this point in the history
  • Loading branch information
sujaygarlanka authored Jun 25, 2020
1 parent 4dafa31 commit 93a27ec
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Next Release

- Add path parameter sanitization ([#505](https://github.com/box/box-node-sdk/pull/505))
- Add support for all streams for uploading files ([#519](https://github.com/box/box-node-sdk/pull/519))

## 1.32.0 [2020-04-01]

Expand Down
17 changes: 9 additions & 8 deletions docs/files.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ Upload a File
-------------

The simplest way to upload a file to a folder is by calling the
[`files.uploadFile(parentFolderID, filename, content, callback)`](http://opensource.box.com/box-node-sdk/jsdoc/Files.html#uploadFile)
[`files.uploadFile(parentFolderID, filename, content, options, callback)`](http://opensource.box.com/box-node-sdk/jsdoc/Files.html#uploadFile)
method with a `stream.Readable` or `Buffer` of the file to upload.

<!-- sample post_files_content -->
Expand Down Expand Up @@ -296,13 +296,14 @@ client.files.uploadFile(folderID, 'My File.pdf', stream)
});
```

To preserve file timestamps, you may pass the created and modified times as optional parameters:
If the stream passed is not an fs stream, you must pass the stream length as an optional parameter like below. To preserve file timestamps, you may pass the created and modified times as optional parameters:
```js
var fs = require('fs');
var stream = fs.createReadStream('/path/to/file');
var options = {
content_created_at: '2015-05-12T17:38:14-0600',
content_modified_at: '2016-02-15T22:42:09-0600'
content_modified_at: '2016-02-15T22:42:09-0600',
content_length: 5
};
client.files.uploadFile('98768', 'New File', stream, options)
.then(file => {
Expand Down Expand Up @@ -836,7 +837,7 @@ Upload a New Version of a File
------------------------------

New versions of a file can be uploaded with the
[`files.uploadNewFileVersion(fileID, content, callback)`](http://opensource.box.com/box-node-sdk/jsdoc/Files.html#uploadNewFileVersion) method.
[`files.uploadNewFileVersion(fileID, content, options, callback)`](http://opensource.box.com/box-node-sdk/jsdoc/Files.html#uploadNewFileVersion) method.

<!-- sample post_files_id_content -->
```js
Expand Down Expand Up @@ -898,15 +899,15 @@ client.files.uploadNewFileVersion('11111', stream)
item_status: 'active' } ] }
*/
```
To rename the file on upload or manually specify a modification timestamp for the file, pass the corresponding optional
parameter:
If the stream passed in is not an fs stream, you must pass the stream length as an optional parameter as shown below.
To rename the file on upload or manually specify a modification timestamp for the file, pass the corresponding optional parameter:
```js
var fs = require('fs');
var stream = fs.createReadStream('/path/to/file.pdf');
var options = {
name: 'New filename.pdf',
content_modified_at: '2016-02-15T22:42:09-0600'
content_modified_at: '2016-02-15T22:42:09-0600',
content_length: 5
};
client.files.uploadNewFileVersion('11111', stream, options)
.then(file => {
Expand Down
25 changes: 21 additions & 4 deletions lib/managers/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,11 @@ function createFileMetadataFormData(parentFolderID, filename, options) {
/**
* Returns the multipart form value for file upload content.
* @param {string|Buffer|Stream} content - the content of the file being uploaded
* @param {Object} options - options for the content
* @returns {Object} - the form value expected by the API for the 'content' key
* @private
*/
function createFileContentFormData(content) {
function createFileContentFormData(content, options) {
// The upload API appears to look for a form field that contains a filename
// property and assume that this form field contains the file content. Thus,
// the value of name does not actually matter (as long as it does not conflict
Expand All @@ -90,7 +91,7 @@ function createFileContentFormData(content) {
// filename specified in the metadata form field instead.
return {
value: content,
options: { filename: 'unused' }
options: Object.assign({ filename: 'unused' }, options)
};
}

Expand Down Expand Up @@ -601,6 +602,7 @@ Files.prototype.promoteVersion = function(fileID, versionID, callback) {
* @param {Object} [options] - Optional parameters
* @param {string} [options.content_created_at] - RFC 3339 timestamp when the file was created
* @param {string} [options.content_modified_at] - RFC 3339 timestamp when the file was last modified
* @param {int} [options.content_length] - Optional length of the content. Required if content is a read stream of any type other than fs stream.
* @param {Function} [callback] - called with data about the upload if successful, or an error if the
* upload failed
* @returns {Promise<Object>} A promise resolving to the uploaded file
Expand All @@ -613,10 +615,17 @@ Files.prototype.uploadFile = function(parentFolderID, filename, content, options
options = {};
}

var formOptions = {};
if (options && options.hasOwnProperty('content_length')) {
formOptions.knownLength = options.content_length;
// Delete content_length from options so it's not added to the attributes of the form
delete options.content_length;
}

var apiPath = urlPath(BASE_PATH, '/content'),
multipartFormData = {
attributes: createFileMetadataFormData(parentFolderID, filename, options),
content: createFileContentFormData(content)
content: createFileContentFormData(content, formOptions)
};

return this.client.wrapWithDefaultHandler(this.client.upload)(apiPath, null, multipartFormData, callback);
Expand All @@ -635,6 +644,7 @@ Files.prototype.uploadFile = function(parentFolderID, filename, content, options
* @param {Object} [options] - Optional parameters
* @param {string} [options.content_modified_at] - RFC 3339 timestamp when the file was last modified
* @param {string} [options.name] - A new name for the file
* @param {int} [options.content_length] - Optional length of the content. Required if content is a read stream of any type other than fs stream.
* @param {Function} [callback] - called with data about the upload if successful, or an error if the
* upload failed
* @returns {Promise<Object>} A promise resolving to the uploaded file
Expand All @@ -650,11 +660,18 @@ Files.prototype.uploadNewFileVersion = function(fileID, content, options, callba
var apiPath = urlPath(BASE_PATH, fileID, '/content'),
multipartFormData = {};


var formOptions = {};
if (options) {
if (options.hasOwnProperty('content_length')) {
formOptions.knownLength = options.content_length;
// Delete content_length from options so it's not added to the attributes of the form
delete options.content_length;
}
multipartFormData.attributes = JSON.stringify(options);
}

multipartFormData.content = createFileContentFormData(content);
multipartFormData.content = createFileContentFormData(content, formOptions);

return this.client.wrapWithDefaultHandler(this.client.upload)(apiPath, null, multipartFormData, callback);
};
Expand Down
50 changes: 50 additions & 0 deletions tests/lib/managers/files-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1300,6 +1300,32 @@ describe('Files', function() {
files.uploadFile(PARENT_FOLDER_ID, FILENAME, CONTENT, options);
});

it('should pass content length parameter when specified', function() {

var options = {
content_modified_at: '2017-11-18T11:18:00-0800',
content_created_at: '1988-11-18T11:18:00-0800',
content_length: 11
};
var expectedFormData = {
attributes: JSON.stringify({
name: FILENAME,
parent: { id: PARENT_FOLDER_ID },
content_modified_at: options.content_modified_at,
content_created_at: options.content_created_at
}),
content: {
value: CONTENT,
options: { filename: 'unused', knownLength: options.content_length}
}
};

sandbox.stub(boxClientFake, 'wrapWithDefaultHandler').returnsArg(0);
sandbox.mock(boxClientFake).expects('upload')
.withArgs('/files/content', null, expectedFormData);
files.uploadFile(PARENT_FOLDER_ID, FILENAME, CONTENT, options);
});

it('should wrap with default handler when called', function() {

sandbox.stub(boxClientFake, 'upload').returns(Promise.resolve());
Expand Down Expand Up @@ -1370,6 +1396,30 @@ describe('Files', function() {
files.uploadNewFileVersion(FILE_ID, CONTENT, options);
});

it('should pass content length parameter when specified', function() {

var options = {
name: 'New filename.txt',
content_modified_at: '2017-12-17T12:34:56-0800',
content_length: 11
};
var expectedFormData = {
attributes: JSON.stringify({
name: options.name,
content_modified_at: options.content_modified_at
}),
content: {
value: CONTENT,
options: { filename: 'unused', knownLength: options.content_length}
}
};

sandbox.stub(boxClientFake, 'wrapWithDefaultHandler').returnsArg(0);
sandbox.mock(boxClientFake).expects('upload')
.withArgs('/files/1234/content', null, expectedFormData);
files.uploadNewFileVersion(FILE_ID, CONTENT, options);
});

it('should wrap with default handler when called', function() {

sandbox.stub(boxClientFake, 'upload').returns(Promise.resolve());
Expand Down

0 comments on commit 93a27ec

Please sign in to comment.