Skip to content
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

feat: add ability to upload entire directory, add samples for upload … #2118

Merged
merged 5 commits into from
Jan 4, 2023
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: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/nodejs-storage/tre
| Download File | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/downloadFile.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/downloadFile.js,samples/README.md) |
| Download a File in Chunks Utilzing Transfer Manager | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/downloadFileInChunksWithTransferManager.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/downloadFileInChunksWithTransferManager.js,samples/README.md) |
| Download File Using Requester Pays | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/downloadFileUsingRequesterPays.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/downloadFileUsingRequesterPays.js,samples/README.md) |
| Download Folder With Transfer Manager | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/downloadFolderWithTransferManager.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/downloadFolderWithTransferManager.js,samples/README.md) |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sort of a nit, but both directory and folder are used interchangeably throughout the PR. Given the updated param name uses directory, should we stick with that verbiage?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I used folder when downloading as GCS does not call them directories because technically they aren't. I used directory for uploading to represent a local disk directory. Do you think it is better to just stick with directory throughout? My concern was this may cause future confusion with upcoming features.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That distinction makes sense. The only thing that needs to be updated is the readmes: Upload Folder With Transfer Manager

| Download Into Memory | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/downloadIntoMemory.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/downloadIntoMemory.js,samples/README.md) |
| Download Many Files With Transfer Manager | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/downloadManyFilesWithTransferManager.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/downloadManyFilesWithTransferManager.js,samples/README.md) |
| Storage Download Public File. | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/downloadPublicFile.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/downloadPublicFile.js,samples/README.md) |
Expand Down Expand Up @@ -205,6 +206,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/nodejs-storage/tre
| Stream File Download | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/streamFileDownload.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/streamFileDownload.js,samples/README.md) |
| Stream File Upload | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/streamFileUpload.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/streamFileUpload.js,samples/README.md) |
| Upload a directory to a bucket. | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/uploadDirectory.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/uploadDirectory.js,samples/README.md) |
| Upload Directory With Transfer Manager | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/uploadDirectoryWithTransferManager.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/uploadDirectoryWithTransferManager.js,samples/README.md) |
| Upload Encrypted File | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/uploadEncryptedFile.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/uploadEncryptedFile.js,samples/README.md) |
| Upload File | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/uploadFile.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/uploadFile.js,samples/README.md) |
| Upload File With Kms Key | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/uploadFileWithKmsKey.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/uploadFileWithKmsKey.js,samples/README.md) |
Expand Down
40 changes: 40 additions & 0 deletions samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ objects to users via direct download.
* [Download File](#download-file)
* [Download a File in Chunks Utilzing Transfer Manager](#download-a-file-in-chunks-utilzing-transfer-manager)
* [Download File Using Requester Pays](#download-file-using-requester-pays)
* [Download Folder With Transfer Manager](#download-folder-with-transfer-manager)
* [Download Into Memory](#download-into-memory)
* [Download Many Files With Transfer Manager](#download-many-files-with-transfer-manager)
* [Storage Download Public File.](#storage-download-public-file.)
Expand Down Expand Up @@ -124,6 +125,7 @@ objects to users via direct download.
* [Stream File Download](#stream-file-download)
* [Stream File Upload](#stream-file-upload)
* [Upload a directory to a bucket.](#upload-a-directory-to-a-bucket.)
* [Upload Directory With Transfer Manager](#upload-directory-with-transfer-manager)
* [Upload Encrypted File](#upload-encrypted-file)
* [Upload File](#upload-file)
* [Upload File With Kms Key](#upload-file-with-kms-key)
Expand Down Expand Up @@ -752,6 +754,25 @@ __Usage:__



### Download Folder With Transfer Manager

Downloads a folder in parallel utilizing transfer manager.

View the [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/downloadFolderWithTransferManager.js).

[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/downloadFolderWithTransferManager.js,samples/README.md)

__Usage:__


`node downloadFolderWithTransferManager.js <BUCKET_NAME> <FOLDER_NAME>`


-----




### Download Into Memory

View the [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/downloadIntoMemory.js).
Expand Down Expand Up @@ -2012,6 +2033,25 @@ __Usage:__



### Upload Directory With Transfer Manager

Uploads a directory in parallel utilizing transfer manager.

View the [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/uploadDirectoryWithTransferManager.js).

[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/uploadDirectoryWithTransferManager.js,samples/README.md)

__Usage:__


`node uploadFolderWithTransferManager.js <BUCKET_NAME> <DIRECTORY_NAME>`


-----




### Upload Encrypted File

View the [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/uploadEncryptedFile.js).
Expand Down
2 changes: 1 addition & 1 deletion samples/downloadFileInChunksWithTransferManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ function main(
// Creates a client
const storage = new Storage();

// Creates a transfer manager instance
// Creates a transfer manager client
const transferManager = new TransferManager(storage.bucket(bucketName));

async function downloadFileInChunksWithTransferManager() {
Expand Down
60 changes: 60 additions & 0 deletions samples/downloadFolderWithTransferManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* @experimental
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this where this tag belongs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at https://sapui5.hana.ondemand.com/sdk/docs/topics/eeaa5de14e5f4fc1ac796bc0c1ada5fb.html it only notes Classifies an entity that is not ready for production use yet, but available for testing purposes. I stuck it in this same location for all other transfer manager samples.

*/

// sample-metadata:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this comment belong here? it's outside the region tags and im not entirely sure the purpose. do we have a similar comment in other samples? (i only checked 2 and I didnt see it)

Copy link
Contributor Author

@ddelgrosso1 ddelgrosso1 Dec 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed this in some of the newer samples that had been written, for example createBucketWithDualRegion.

// title: Download Folder With Transfer Manager
// description: Downloads a folder in parallel utilizing transfer manager.
// usage: node downloadFolderWithTransferManager.js <BUCKET_NAME> <FOLDER_NAME>

function main(bucketName = 'my-bucket', folderName = 'my-folder') {
// [START storage_download_folder_transfer_manager]
/**
* TODO(developer): Uncomment the following lines before running the sample.
*/
// The ID of your GCS bucket
// const bucketName = 'your-unique-bucket-name';

// The ID of the GCS folder to download. The folder will be downloaded to the local path of the executing code.
// const folderName = 'your-folder-name';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this support both relative and absolute?
If it does can you add it to the comment


// Imports the Google Cloud client library
const {Storage, TransferManager} = require('@google-cloud/storage');

// Creates a client
const storage = new Storage();

// Creates a transfer manager client
const transferManager = new TransferManager(storage.bucket(bucketName));

async function downloadFolderWithTransferManager() {
// Downloads the folder
await transferManager.downloadManyFiles(folderName);

console.log(
`gs://${bucketName}/${folderName} downloaded to ${folderName}.`
);
}

downloadFolderWithTransferManager().catch(console.error);
// [END storage_download_folder_transfer_manager]
}

process.on('unhandledRejection', err => {
console.error(err.message);
process.exitCode = 1;
});
main(...process.argv.slice(2));
2 changes: 1 addition & 1 deletion samples/downloadManyFilesWithTransferManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function main(
// Creates a client
const storage = new Storage();

// Creates a transfer manager instance
// Creates a transfer manager client
const transferManager = new TransferManager(storage.bucket(bucketName));

async function downloadManyFilesWithTransferManager() {
Expand Down
27 changes: 25 additions & 2 deletions samples/system-test/transfer-manager.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ const bucketName = generateName();
const bucket = storage.bucket(bucketName);
const firstFileName = 'test.txt';
const secondFileName = 'test2.txt';
const firstFilePath = path.join(cwd, 'resources', firstFileName);
const secondFilePath = path.join(cwd, 'resources', secondFileName);
const resourcesPath = path.join(cwd, 'resources');
const firstFilePath = path.join(resourcesPath, firstFileName);
const secondFilePath = path.join(resourcesPath, secondFileName);
const downloadFilePath = path.join(cwd, 'downloaded.txt');
const chunkSize = 1024;

Expand Down Expand Up @@ -78,6 +79,28 @@ describe('transfer manager', () => {
)
);
});

it('should upload a directory', async () => {
const output = execSync(
`node uploadDirectoryWithTransferManager.js ${bucketName} ${resourcesPath}`
);
assert.match(
output,
new RegExp(`${resourcesPath} uploaded to ${bucketName}.`)
);
});

it('should download a directory', async () => {
const output = execSync(
`node downloadFolderWithTransferManager.js ${bucketName} ${resourcesPath}`
);
assert.match(
output,
new RegExp(
`gs://${bucketName}/${resourcesPath} downloaded to ${resourcesPath}.`
)
);
});
});

function generateName() {
Expand Down
58 changes: 58 additions & 0 deletions samples/uploadDirectoryWithTransferManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* @experimental
*/

// sample-metadata:
Comment on lines +15 to +18
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same questions here

// title: Upload Directory With Transfer Manager
// description: Uploads a directory in parallel utilizing transfer manager.
// usage: node uploadFolderWithTransferManager.js <BUCKET_NAME> <DIRECTORY_NAME>

function main(bucketName = 'my-bucket', directoryName = 'my-directory') {
// [START storage_upload_directory_transfer_manager]
/**
* TODO(developer): Uncomment the following lines before running the sample.
*/
// The ID of your GCS bucket
// const bucketName = 'your-unique-bucket-name';

// The local directory to upload
// const directoryName = 'your-directory';

// Imports the Google Cloud client library
const {Storage, TransferManager} = require('@google-cloud/storage');

// Creates a client
const storage = new Storage();

// Creates a transfer manager client
const transferManager = new TransferManager(storage.bucket(bucketName));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are your thoughts on accepting a string in TransferManager's constructor and creating a bucket from it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this idea. Let me tackle it in a separate PR as to not muddy the waters on this one.


async function uploadDirectoryWithTransferManager() {
// Uploads the directory
await transferManager.uploadManyFiles(directoryName);

console.log(`${directoryName} uploaded to ${bucketName}.`);
}

uploadDirectoryWithTransferManager().catch(console.error);
// [END storage_upload_directory_transfer_manager]
}

process.on('unhandledRejection', err => {
console.error(err.message);
process.exitCode = 1;
});
main(...process.argv.slice(2));
2 changes: 1 addition & 1 deletion samples/uploadManyFilesWithTransferManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function main(
// Creates a client
const storage = new Storage();

// Creates a transfer manager instance
// Creates a transfer manager client
const transferManager = new TransferManager(storage.bucket(bucketName));

async function uploadManyFilesWithTransferManager() {
Expand Down
33 changes: 30 additions & 3 deletions src/transfer-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ export class TransferManager {
* Upload multiple files in parallel to the bucket. This is a convenience method
* that utilizes {@link Bucket#upload} to perform the upload.
*
* @param {array} [filePaths] An array of fully qualified paths to the files.
* @param {array | string} [filePathsOrDirectory] An array of fully qualified paths to the files or a directory name.
* If a directory name is provided, the directory will be recursively walked and all files will be added to the upload list.
* to be uploaded to the bucket
* @param {UploadManyFilesOptions} [options] Configuration options.
* @returns {Promise<UploadResponse[]>}
Expand All @@ -118,11 +119,13 @@ export class TransferManager {
* // Your bucket now contains:
* // - "local/path/file1.txt" (with the contents of '/local/path/file1.txt')
* // - "local/path/file2.txt" (with the contents of '/local/path/file2.txt')
* const response = await transferManager.uploadManyFiles('/local/directory');
* // Your bucket will now contain all files contained in '/local/directory' maintaining the subdirectory structure.
* ```
* @experimental
*/
async uploadManyFiles(
filePaths: string[],
filePathsOrDirectory: string[] | string,
options: UploadManyFilesOptions = {}
): Promise<UploadResponse[]> {
if (options.skipIfExists && options.passthroughOptions?.preconditionOpts) {
Expand All @@ -142,8 +145,18 @@ export class TransferManager {
options.concurrencyLimit || DEFAULT_PARALLEL_UPLOAD_LIMIT
);
const promises = [];
let allPaths: string[] = [];
if (!Array.isArray(filePathsOrDirectory)) {
for await (const curPath of this.getPathsFromDirectory(
filePathsOrDirectory
)) {
allPaths.push(curPath);
}
} else {
allPaths = filePathsOrDirectory;
}

for (const filePath of filePaths) {
for (const filePath of allPaths) {
const stat = await fsp.lstat(filePath);
if (stat.isDirectory()) {
continue;
Expand Down Expand Up @@ -355,4 +368,18 @@ export class TransferManager {
});
});
}

private async *getPathsFromDirectory(
directory: string
): AsyncGenerator<string> {
const filesAndSubdirectories = await fsp.readdir(directory, {
withFileTypes: true,
});
for (const curFileOrDirectory of filesAndSubdirectories) {
const fullPath = path.join(directory, curFileOrDirectory.name);
curFileOrDirectory.isDirectory()
? yield* this.getPathsFromDirectory(fullPath)
: yield fullPath;
}
}
}