diff --git a/lib/cmds/files_cmds/download.js b/lib/cmds/files_cmds/download.js index e95e10e..a0a6589 100644 --- a/lib/cmds/files_cmds/download.js +++ b/lib/cmds/files_cmds/download.js @@ -1,22 +1,52 @@ 'use strict'; +const querystring = require('querystring'); const path = require('path'); -const { get, download } = require('../../api'); +const { get, download, list } = require('../../api'); -exports.command = 'download [destDir]'; -exports.desc = 'Download file content by '; +exports.command = 'download [destDir]'; +exports.desc = 'Download file content by '; exports.builder = yargs => { - yargs.positional('fileId', { - describe: 'The ID of the file to download.', + yargs.positional('source', { + describe: 'Either the ID of the file to download or a project ID and prefix of the form / to download a set of files from a project.', type: 'string' }).positional('destDir', { describe: 'The destination directory for the download file. Defaults to current working directory', type: 'string' + }).option('recursive', { + describe: + 'If is a project Id with an optional prefix, download all files under that prefix to the destination.', + alias: 'r', + type: 'boolean', + default: false + }).option('limit', { + describe: 'The maximum number of files to download when used with the --recursive option', + alias: 'l', + type: 'number', + default: 1000 }); }; exports.handler = async argv => { - const response = await get(argv, `/v1/files/${argv.fileId}`); + if (argv.recursive) { + const [datasetId, prefix] = argv.source.split('/'); - await download(argv, `/v1/files/${argv.fileId}?include=downloadUrl`, path.join(argv.destDir || process.cwd(), response.data.name)); + const opts = { + datasetId, + pageSize: argv.limit + }; + + if (prefix) { + opts.name = prefix; + } + + const response = await list(argv, `/v1/files?${querystring.stringify(opts)}`); + for (const item of response.data.items) { + await download(argv, `/v1/files/${item.id}?include=downloadUrl`, path.join(argv.destDir || process.cwd(), item.name)); + } + } else { + const response = await get(argv, `/v1/files/${argv.source}`); + + await download(argv, `/v1/files/${argv.source}?include=downloadUrl`, path.join(argv.destDir || process.cwd(), response.data.name)); + } }; diff --git a/test/unit/commands/file.test.js b/test/unit/commands/file.test.js index 19fdb3e..66093f7 100644 --- a/test/unit/commands/file.test.js +++ b/test/unit/commands/file.test.js @@ -207,6 +207,35 @@ test.serial.cb('The "files-download" command should download a file', t => { .parse('download fileid /dir'); }); +test.serial.cb('The "files-download" command should download a set of files from a project', t => { + listStub.onFirstCall().returns({ + data: { + items: [ + { + id: '1', + name: 'foo.txt' + } + ] + } + }); + + callback = () => { + t.is(listStub.callCount, 1); + t.is(listStub.getCall(0).args[1], '/v1/files?datasetId=projectId&pageSize=1000&name=prefix'); + + t.is(downloadSpy.callCount, 1); + t.is(downloadSpy.getCall(0).args[1], '/v1/files/1?include=downloadUrl'); + t.is(downloadSpy.getCall(0).args[2], '/dir/foo.txt'); + t.end(); + }; + + t.context.deleteFileStub = t.context.sandbox.stub(fs, 'unlinkSync').callsFake(callback); + t.context.copyFileStub = t.context.sandbox.stub(fs, 'copyFileSync').callsFake(callback); + + yargs.command(download) + .parse('download projectId/prefix /dir -r'); +}); + test.serial.cb('The "files-upload" command should upload a file', t => { const res = { data: { uploadUrl: 'https://host/upload' } }; postStub.onFirstCall().returns(res);