Skip to content

Add zip functionality #525

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .nycrc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"check-coverage": true,
"lines": 98,
"statements": 98,
"functions": 99,
"functions": 98,
"branches": 92,
"report-dir": "./coverage",
"all": true
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## Next Release
- Add zip functionality ([#525](https://github.com/box/box-node-sdk/pull/525))

## 1.33.0 [2020-06-25]

- Add path parameter sanitization ([#505](https://github.com/box/box-node-sdk/pull/505))
Expand Down
96 changes: 96 additions & 0 deletions docs/files.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ file's contents, upload new versions, and perform other common file operations
- [Unlock a File](#unlock-a-file)
- [Get Representation Info](#get-representation-info)
- [Get Representation Content](#get-representation-content)
- [Create a Zip File](#create-a-zip-file)
- [Download a Zip File](#download-a-zip-file)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

Expand Down Expand Up @@ -1246,3 +1248,97 @@ client.files.getRepresentationContent('12345', '[png?dimensions=1024x1024]', { a
```

[get-rep-content]: http://opensource.box.com/box-node-sdk/jsdoc/Files.html#getRepresentationContent

Create a Zip File
_________________

Calling [`files.createZip(name, items, callback)`][create-a-zip-file] will let you create a new zip file with the specified name and
with the specified items and will return a response with the download and status link. This file does not show up in your Box account, but will be temporarily available for download.

```js
var name = 'test',
items = [
{
type: 'file',
id: '466239504569'
},
{
type: 'folder',
id: '466239504580'
}
];
client.files.createZip(name, items)
.then(zip => {
/* zip -> {
"download_url": "https://api.box.com/2.0/zip_downloads/124hfiowk3fa8kmrwh/content",
"status_url": "https://api.box.com/2.0/zip_downloads/124hfiowk3fa8kmrwh/status",
"expires_at": "2018-04-25T11:00:18-07:00",
"name_conflicts": [
[
{
"id": "100",
"type": "file",
"original_name": "salary.pdf",
"download_name": "aqc823.pdf"
},
{
"id": "200",
"type": "file",
"original_name": "salary.pdf",
"download_name": "aci23s.pdf"
}
],
[
{
"id": "1000",
"type": "folder",
"original_name": "employees",
"download_name": "3d366a_employees"
},
{
"id": "2000",
"type": "folder",
"original_name": "employees",
"download_name": "3aa6a7_employees"
}
]
]
}
*/
});
```
[create-a-zip-file]: http://opensource.box.com/box-node-sdk/jsdoc/Files.html#createZip

Download a Zip File
_________________

Calling [`file.download(name, items, stream, callback)`][download-a-zip-file] will let you create a new zip file
with the specified name and with the specified items and download it to the stream that is passed in. The return object is status
object that contains information about the download, including whether it was successful. The created zip file does not show up in your Box account.

```js
var name = 'test',
items = [
{
type: 'file',
id: '466239504569'
},
{
type: 'folder',
id: '466239504580'
}
],
stream = new Readable();
client.files.downloadZip(name, items, stream)
.then(status => {
/* status -> {
"total_file_count": 20,
"downloaded_file_count": 10,
"skipped_file_count": 10,
"skipped_folder_count": 10,
"state": "succeeded"
}
*/
});
```
[download-a-zip-file]: http://opensource.box.com/box-node-sdk/jsdoc/Files.html#downloadZip
64 changes: 63 additions & 1 deletion lib/managers/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ var urlPath = require('../util/url-path'),
var BASE_PATH = '/files',
VERSIONS_SUBRESOURCE = '/versions',
WATERMARK_SUBRESOURCE = '/watermark',
UPLOAD_SESSION_SUBRESOURCE = '/upload_sessions';
UPLOAD_SESSION_SUBRESOURCE = '/upload_sessions',
ZIP_DOWNLOAD_PATH = '/zip_downloads';

// Enum of valid lock types
var lockTypes = {
Expand Down Expand Up @@ -1591,6 +1592,67 @@ Files.prototype.getRepresentationContent = function(fileID, representationType,
.asCallback(callback);
};

/**
* Creates a zip of multiple files and folders.
*
* API Endpoint: '/zip_downloads'
* Method: POST
*
* @param {name} name - The name of the zip file to be created
* @param {Array} items - Array of files or folders to be part of the created zip
* @param {Function} [callback] Passed a stream over the representation contents if successful
* @returns {Promise<string>} A promise resolving to the file's download URL
*/
Files.prototype.createZip = function(name, items, callback) {
var params = {
body: {
download_file_name: name,
items
}
};

return this.client.wrapWithDefaultHandler(this.client.post)(ZIP_DOWNLOAD_PATH, params, callback);
};

/**
* Creates a zip of multiple files and folders and downloads it.
*
* API Endpoint: '/zip_downloads'
* Method: GET
*
* @param {name} name - The name of the zip file to be created
* @param {Array} items - Array of files or folders to be part of the created zip
* @param {Stream} stream - Stream to pipe the readable stream of the zip file
* @param {Function} [callback] - Passed a status response object
* @returns {Promise<Readable>} A promise resolving for the file stream
*/
Files.prototype.downloadZip = function(name, items, stream, callback) {
var downloadStreamOptions = {
streaming: true,
headers: {}
};

var params = {
body: {
download_file_name: name,
items
}
};

return this.client.post(ZIP_DOWNLOAD_PATH, params)
.then(response => this.client.get(response.body.download_url, downloadStreamOptions)
.then(responseStream => {
responseStream.pipe(stream);
// eslint-disable-next-line promise/avoid-new
return new Promise((resolve, reject) => {
responseStream.on('end', () => resolve('Done downloading'));
responseStream.on('error', error => reject(error));
}).then(() => this.client.get(response.body.status_url).then(responseStatus => responseStatus.body));
})
)
.asCallback(callback);
};

/**
* @module box-node-sdk/lib/managers/files
* @see {@Link Files}
Expand Down
117 changes: 116 additions & 1 deletion tests/endpoint-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ var assert = require('chai').assert,
url = require('url'),
crypto = require('crypto'),
path = require('path'),
Promise = require('bluebird');
Promise = require('bluebird'),
Stream = require('stream');

function getFixture(fixture) {
return fs.readFileSync(path.resolve(__dirname, `fixtures/endpoints/${fixture}.json`));
Expand Down Expand Up @@ -2168,6 +2169,120 @@ describe('Endpoint', function() {
});
});

describe('createZip()', function() {
it('should create a zip file', function() {
var name = 'test',
items = [
{
type: 'file',
id: '466239504569'
},
{
type: 'folder',
id: '466239504580'
}
],
expectedBody = {
items,
download_file_name: name
},
fixture = getFixture('files/post_zip_downloads_202');

apiMock.post('/2.0/zip_downloads', expectedBody)
.matchHeader('Authorization', function(authHeader) {
assert.equal(authHeader, `Bearer ${TEST_ACCESS_TOKEN}`);
return true;
})
.matchHeader('User-Agent', function(uaHeader) {
assert.include(uaHeader, 'Box Node.js SDK v');
return true;
})
.reply(202, fixture);

return basicClient.files.createZip(name, items)
.then(info => {
assert.deepEqual(info, JSON.parse(fixture));
});
});
});

describe('downloadZip()', function() {
it('should create a zip file and download it', function() {
var name = 'test',
items = [
{
type: 'file',
id: '466239504569'
},
{
type: 'folder',
id: '466239504580'
}
],
expectedBody = {
items,
download_file_name: name
},
zipFileBytes = '',
readableStream = new Stream.Readable({
read() {
this.push('zip file');
this.push(null);
}
}),
writableStream = new Stream.Writable({
write(chunk, encoding, next) {
zipFileBytes += chunk.toString();
next();
}
}),
downloadUrl = '/2.0/zip_downloads/124hfiowk3fa8kmrwh/content',
statusUrl = '/2.0/zip_downloads/124hfiowk3fa8kmrwh/status',
fixture = getFixture('files/post_zip_downloads_202'),
fixture2 = getFixture('files/get_zip_downloads_status_200'),
fileDownloadRoot = 'https://dl.boxcloud.com',
dlMock = nock(fileDownloadRoot);

apiMock.post('/2.0/zip_downloads', expectedBody)
.matchHeader('Authorization', function(authHeader) {
assert.equal(authHeader, `Bearer ${TEST_ACCESS_TOKEN}`);
return true;
})
.matchHeader('User-Agent', function(uaHeader) {
assert.include(uaHeader, 'Box Node.js SDK v');
return true;
})
.reply(202, fixture);

dlMock.get(downloadUrl)
.matchHeader('Authorization', function(authHeader) {
assert.equal(authHeader, `Bearer ${TEST_ACCESS_TOKEN}`);
return true;
})
.matchHeader('User-Agent', function(uaHeader) {
assert.include(uaHeader, 'Box Node.js SDK v');
return true;
})
.reply(200, readableStream);

apiMock.get(statusUrl)
.matchHeader('Authorization', function(authHeader) {
assert.equal(authHeader, `Bearer ${TEST_ACCESS_TOKEN}`);
return true;
})
.matchHeader('User-Agent', function(uaHeader) {
assert.include(uaHeader, 'Box Node.js SDK v');
return true;
})
.reply(200, fixture2);

return basicClient.files.downloadZip(name, items, writableStream)
.then(status => {
assert.deepEqual(status, JSON.parse(fixture2));
assert.equal(zipFileBytes, 'zip file');
});
});
});
});

describe('Folders', function() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"total_file_count": 20,
"downloaded_file_count": 10,
"skipped_file_count": 10,
"skipped_folder_count": 10,
"state": "succeeded"
}
35 changes: 35 additions & 0 deletions tests/fixtures/endpoints/files/post_zip_downloads_202.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"download_url": "https://dl.boxcloud.com/2.0/zip_downloads/124hfiowk3fa8kmrwh/content",
"status_url": "https://api.box.com/2.0/zip_downloads/124hfiowk3fa8kmrwh/status",
"expires_at": "2018-04-25T11:00:18-07:00",
"name_conflicts": [
[
{
"id": "100",
"type": "file",
"original_name": "salary.pdf",
"download_name": "aqc823.pdf"
},
{
"id": "200",
"type": "file",
"original_name": "salary.pdf",
"download_name": "aci23s.pdf"
}
],
[
{
"id": "1000",
"type": "folder",
"original_name": "employees",
"download_name": "3d366a_employees"
},
{
"id": "2000",
"type": "folder",
"original_name": "employees",
"download_name": "3aa6a7_employees"
}
]
]
}