Skip to content

Add nonresumable uploadBytes to modular Storage #4084

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 17 commits into from
Dec 7, 2020

Conversation

hsubox76
Copy link
Contributor

Adding uploadBytes to storage-exp. This is a new method that does a non-resumable upload which requires less code than the resumable upload. We expect people to use this by default and only use the resumable version when needed, so it gets the default-sounding name and the resumable upload is named uploadBytesResumable.

uploadBytes has to generate an UploadTaskSnapshot with less properties (no task, state, bytesTransferred...do we still need totalBytes? not sure) so I created a separate class, and again gave it the default sounding name, while the old one is named UploadTaskResumableSnapshot.

@changeset-bot
Copy link

changeset-bot bot commented Nov 17, 2020

⚠️ No Changeset found

Latest commit: 91c5e61

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@google-oss-bot
Copy link
Contributor

google-oss-bot commented Nov 17, 2020

Size Analysis Report

Affected Products

No changes between base commit (8518b6b) and head commit (0cc7407).

Test Logs

): RequestInfo<Metadata> {
const urlPart = location.bucketOnlyServerUrl();
const headers: { [prop: string]: string } = {
'Content-Type': 'application/octet-stream'
Copy link
Member

Choose a reason for hiding this comment

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

Should it always be octet-stream? the original logic indicates it's a fallback when metadata.contentType and blob.type() are undefined. Perhaps it doesn't matter since we can treat everything as arbitrary binary data?

    (metadata && metadata['contentType']) ||
    (blob && blob.type()) ||
    'application/octet-stream'

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Normally (in the old resumable setup) the content type in the header doesn't actually match the metadata.contentType field because the metadata has a specific content type as seen in the logic above, and the request header Content-Type is always multipart. Since we're doing away with multipart I guess it would be best to make the header match the metadata.

@hsubox76
Copy link
Contributor Author

Whoops, I misunderstood what multipartUpload does (it sends metadata alongside the data and is not related to resumableness), rewriting.

@hsubox76 hsubox76 force-pushed the ch-storage-nonresumable branch from d8f09c1 to c4c6e32 Compare November 18, 2020 02:23
Copy link
Contributor

@schmidt-sebastian schmidt-sebastian left a comment

Choose a reason for hiding this comment

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

Looks pretty good (but somewhat hard to review due to the import/formatting changes). I wonder if you have been able to compute the size difference?

* Uploads data to this object's location.
* The upload is not resumable.
* @public
* @param ref - Storage Reference where data should be uploaded.
Copy link
Contributor

Choose a reason for hiding this comment

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

"Storage reference" or "StorageReference" ?

Copy link
Contributor Author

@hsubox76 hsubox76 Nov 18, 2020

Choose a reason for hiding this comment

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

Hmm, exported type name is just Reference. I guess I should rename it since it's publicly exposed.

const data = dataFromString(format, value);
const metadataClone = { ...metadata } as Metadata;
if (metadataClone['contentType'] == null && data.contentType != null) {
metadataClone['contentType'] = data.contentType!;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the bang needed here? Doesn't the != check catch both null and undefined?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed, seems like it was left over from back when it was using type.isDef instead of != null.

Comment on lines +22 to +27
"test:browser:compat:unit": "karma start --single-run --compat --unit",
"test:browser:exp:unit": "karma start --single-run --exp --unit",
"test:browser:compat:integration": "karma start --single-run --compat --integration",
"test:browser:exp:integration": "karma start --single-run --exp --integration",
"test:browser:compat": "karma start --single-run --compat",
"test:browser:exp": "karma start --single-run --exp",
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we add these new test targets to the CI rule?

Copy link
Contributor Author

@hsubox76 hsubox76 Nov 19, 2020

Choose a reason for hiding this comment

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

These were for development testing convenience, CI tests everything. If it's too messy I guess I can remove them, should be able to pass these args to yarn test:browser using -- I think.

Copy link
Contributor

Choose a reason for hiding this comment

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

Please leave them. It makes everyone's life easier.

* @param data - The data to upload.
* @param metadata - Metadata for the newly uploaded string.
* @param metadata - Metadata for the newly uploaded data.
* @returns An UploadTaskNonResumableSnapshot
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* @returns An UploadTaskNonResumableSnapshot
* @returns An UploadTaskSnapshot

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

* @param value - The string to upload.
* @param format - The format of the string to upload.
* @param metadata - Metadata for the newly uploaded object.
* @param metadata - Metadata for the newly uploaded string.
* @returns An UploadTask that lets you control and
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* @returns An UploadTask that lets you control and
* @returns UploadTaskSnapshot

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

}

/**
* Base code for nonresumable upload, used by uploadString and uploadBytes.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* Base code for nonresumable upload, used by uploadString and uploadBytes.
* Shared code for nonresumable upload, used by uploadString and uploadBytes.

Optional.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.


/**
* Uploads data to this object's location.
* The upload is resumable and exposes progress updates.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* The upload is resumable and exposes progress updates.
* The upload can be paused, resumed and exposes progress updates.

Suggesting this because the Web SDK doesn't actually allow you to resume an upload with an Upload URL (as far as I can tell). Instead, the user can only resume uploads with an UploadTask that they paused.

Android let's users actually resume an upload with an Upload URL: https://firebase.google.com/docs/reference/android/com/google/firebase/storage/StorageReference#putFile(android.net.Uri,%20com.google.firebase.storage.StorageMetadata,%20android.net.Uri)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed.

import {
createResumableUpload,
getResumableUploadStatus,
resumableUploadChunkSize,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
resumableUploadChunkSize,
RESUMABLE_UPLOAD_CHUNK_SIZE,

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Comment on lines 334 to 339
it('uploadString throws', async () => {
expect(() => uploadString(root, 'raw', StringFormat.RAW)).to.throw(
'storage/invalid-root-operation'
);
});
it('uploadBytes throws', async () => {
expect(() => uploadBytes(root, new Blob(['a']))).to.throw(
'storage/invalid-root-operation'
);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
it('uploadString throws', async () => {
expect(() => uploadString(root, 'raw', StringFormat.RAW)).to.throw(
'storage/invalid-root-operation'
);
});
it('uploadBytes throws', async () => {
expect(() => uploadBytes(root, new Blob(['a']))).to.throw(
'storage/invalid-root-operation'
);
});
it('uploadString throws', () => {
expect(() => uploadString(root, 'raw', StringFormat.RAW)).to.throw(
'storage/invalid-root-operation'
);
});
it('uploadBytes throws', () => {
expect(() => uploadBytes(root, new Blob(['a']))).to.throw(
'storage/invalid-root-operation'
);
});

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@@ -114,8 +115,17 @@ export class ReferenceCompat implements types.Reference {
metadata?: Metadata
): types.UploadTask {
this._throwIfRoot('putString');
const data = dataFromString(format, value);
Copy link
Contributor

Choose a reason for hiding this comment

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

Just curious - could this method just call put with new FbsBlob(data.data, true)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You mean call uploadBytes instead of having a shared _nonResumableUpload function? I guess the only drawback would be an extra _throwIfRoot call that won't do anything.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes

Comment on lines 32 to 33
// Warning: (ae-forgotten-export) The symbol "ListOptions" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "ListResult" needs to be exported by the entry point index.d.ts
Copy link
Member

Choose a reason for hiding this comment

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

We should address these warnings.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Partially addressed. There are a lot of files to fix so I will do a full pass on all files in another PR.

@@ -279,10 +277,13 @@ export function metadataForUpload_(
return metadataClone;
}

/**
* Prepare RequestInfo for resumable uploads as Content-Type: multipart.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* Prepare RequestInfo for resumable uploads as Content-Type: multipart.
* Prepare RequestInfo for uploads as Content-Type: multipart.

It is used for both resumable and non-resumable uploads.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, fixed.

Comment on lines 22 to 41
export interface UploadTaskSnapshot {
readonly metadata: Metadata;
readonly ref: StorageReference;
}

export interface UploadTaskResumableSnapshot {
readonly bytesTransferred: number;
readonly totalBytes: number;
readonly state: TaskState;
readonly metadata: Metadata;
readonly task: UploadTask;
readonly ref: StorageReference;
Copy link
Member

Choose a reason for hiding this comment

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

+1

res Outdated
@@ -0,0 +1,998 @@
{
Copy link
Member

Choose a reason for hiding this comment

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

delete

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops

async function buildForTests(
config: TestConfig,
buildAppExp = false,
buildAppCompat = false
Copy link
Member

Choose a reason for hiding this comment

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

Was the script broken because of the missing argument until now? but the workflow seems to work fine?

Copy link
Contributor Author

@hsubox76 hsubox76 Nov 19, 2020

Choose a reason for hiding this comment

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

It used to be accessible anyway because it was a global variable (not window global, like top level in the file) so it worked but maybe in an unexpected way.

Copy link
Contributor

@schmidt-sebastian schmidt-sebastian left a comment

Choose a reason for hiding this comment

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

LGTM from my end (please wait for Fei's LGTM as well). Consider calling uploadBytes from uploadString.

Comment on lines +22 to +27
"test:browser:compat:unit": "karma start --single-run --compat --unit",
"test:browser:exp:unit": "karma start --single-run --exp --unit",
"test:browser:compat:integration": "karma start --single-run --compat --integration",
"test:browser:exp:integration": "karma start --single-run --exp --integration",
"test:browser:compat": "karma start --single-run --compat",
"test:browser:exp": "karma start --single-run --exp",
Copy link
Contributor

Choose a reason for hiding this comment

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

Please leave them. It makes everyone's life easier.

* @param ref - StorageReference where data should be uploaded.
* @param data - The data to upload.
* @param metadata - Metadata for the newly uploaded data.
* @returns An UploadTaskSnapshot
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* @returns An UploadTaskSnapshot
* @returns An UploadTask

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

ref: Reference,
metadata: Metadata
ref: StorageReference,
metadata: Record<string, unknown>
Copy link
Contributor

Choose a reason for hiding this comment

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

What about Partial<Metadata>? I think that makes the intent very clear.

@hsubox76 hsubox76 force-pushed the ch-storage-nonresumable branch from 2615765 to b0612cc Compare December 1, 2020 22:04
@hsubox76 hsubox76 force-pushed the ch-storage-nonresumable branch from b0612cc to 91c5e61 Compare December 7, 2020 21:58
@hsubox76 hsubox76 merged commit 9634efd into master Dec 7, 2020
@firebase firebase locked and limited conversation to collaborators Jan 7, 2021
@hsubox76 hsubox76 deleted the ch-storage-nonresumable branch April 12, 2021 19:46
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants