-
Notifications
You must be signed in to change notification settings - Fork 84
feat: add blob.uploadfrom(inputstream) #162
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
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
0be56f0
feat: add blob.uploadfrom(inputstream)
dmitry-fa 532aa0c
feat: add blob.uploadfrom(inputstream)
dmitry-fa d24efa5
feat: add blob.uploadfrom(inputstream)
dmitry-fa 90f7f5f
feat: add blob.uploadfrom(inputstream)
dmitry-fa 1ac041c
feat: add blob.uploadfrom(inputstream)
dmitry-fa b09e16a
feat: add blob.uploadfrom(inputstream)
dmitry-fa 53463e3
feat: add blob.uploadfrom(inputstream)
dmitry-fa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,9 +38,11 @@ | |
import com.google.common.io.BaseEncoding; | ||
import com.google.common.io.CountingOutputStream; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.io.ObjectInputStream; | ||
import java.io.OutputStream; | ||
import java.net.URL; | ||
import java.nio.ByteBuffer; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.security.Key; | ||
|
@@ -73,7 +75,8 @@ public Blob apply(Tuple<Storage, StorageObject> pb) { | |
} | ||
}; | ||
|
||
private static final int DEFAULT_CHUNK_SIZE = 2 * 1024 * 1024; | ||
private static final int DEFAULT_CHUNK_SIZE = 15 * 1024 * 1024; | ||
private static final int MIN_BUFFER_SIZE = 256 * 1024; | ||
|
||
/** Class for specifying blob source options when {@code Blob} methods are used. */ | ||
public static class BlobSourceOption extends Option { | ||
|
@@ -260,6 +263,88 @@ public void downloadTo(Path path) { | |
downloadTo(path, new BlobSourceOption[0]); | ||
} | ||
|
||
/** | ||
* Uploads the given file path to this blob using specified blob write options. | ||
* | ||
* @param path file to upload | ||
* @param options blob write options | ||
* @return updated blob | ||
* @throws IOException on I/O error | ||
* @throws StorageException on failure | ||
*/ | ||
public Blob uploadFrom(Path path, BlobWriteOption... options) throws IOException { | ||
if (Files.isDirectory(path)) { | ||
throw new StorageException(0, path + " is a directory"); | ||
} | ||
try (InputStream input = Files.newInputStream(path)) { | ||
return uploadFrom(input, options); | ||
} | ||
} | ||
|
||
/** | ||
* Uploads the given content to this blob using specified blob write options. | ||
* | ||
* @param input content to upload | ||
* @param options blob write options | ||
* @return updated blob | ||
* @throws IOException on I/O error | ||
* @throws StorageException on failure | ||
*/ | ||
public Blob uploadFrom(InputStream input, BlobWriteOption... options) throws IOException { | ||
try (WriteChannel writer = storage.writer(this, options)) { | ||
uploadFrom(input, writer); | ||
} | ||
BlobId blobId = getBlobId(); | ||
try { | ||
return storage.get(BlobId.of(blobId.getBucket(), blobId.getName())); | ||
} catch (StorageException e) { | ||
throw new StorageException( | ||
e.getCode(), "Content has been uploaded successfully. Failed to retrieve blob.", e); | ||
} | ||
} | ||
|
||
static void uploadFrom(InputStream input, WriteChannel writer) throws IOException { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can this be private? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this method will be invoked from the StorageImpl class |
||
uploadFrom(input, writer, DEFAULT_CHUNK_SIZE); | ||
} | ||
|
||
/** | ||
* Uploads the given content to the storage using specified write channel and the given buffer | ||
* size. Other uploadFrom() methods invoke this one with a buffer size of 15 MiB. Users can pass | ||
* alternative values. Larger buffer sizes might improve the upload performance but require more | ||
* memory. This can cause an OutOfMemoryError or add significant garbage collection overhead. | ||
* Smaller buffer sizes reduce memory consumption, that is noticeable when uploading many objects | ||
* in parallel. Buffer sizes less than 256 KiB are treated as 256 KiB. | ||
* | ||
* <p>This method does not close either the InputStream or the WriterChannel. | ||
* | ||
* <p>Example of uploading: | ||
* | ||
* <pre>{@code | ||
* BlobId blobId = BlobId.of(bucketName, blobName); | ||
* BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("video/webm").build(); | ||
* Path file = Paths.get("humongous.file"); | ||
* try (InputStream input = Files.newInputStream(file); WriteChannel writer = storage.writer(blobInfo)) { | ||
* Blob.uploadFrom(input, writer, 150 * 1024 * 1024); | ||
* } catch (IOException e) { | ||
* // your handler | ||
* } | ||
* }</pre> | ||
* | ||
* @param input content to upload | ||
* @param writer channel | ||
* @param bufferSize size of the buffer to read from input and send over writer | ||
* @throws IOException on I/O error | ||
*/ | ||
public static void uploadFrom(InputStream input, WriteChannel writer, int bufferSize) | ||
throws IOException { | ||
bufferSize = Math.max(bufferSize, MIN_BUFFER_SIZE); | ||
byte[] buffer = new byte[bufferSize]; | ||
int length; | ||
while ((length = input.read(buffer)) >= 0) { | ||
writer.write(ByteBuffer.wrap(buffer, 0, length)); | ||
} | ||
} | ||
|
||
/** Builder for {@code Blob}. */ | ||
public static class Builder extends BlobInfo.Builder { | ||
|
||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I probably just don't understand how this API works, but this sounds off to me. I'd expect a static method that uploads the path to a new Blob and returns it. What does it mean to "upload to this blob"? I'm missing something here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The storage hierarchy is the following:
Class BlobId: A BlobId object includes the name of the containing bucket, the blob's name and possibly the blob's generation. If getGeneration() is null the identifier refers to the latest blob's generation.
Class BlobInfo: encapsulate BlobId and set of properties.
Class Blob extends BlobInfo and adds a number of methods to modify the blob like update, copyTo, downloadTo, etc. Objects of this class are immutable. Operations that modify the blob return a new object.
New
Blob.uploadFrom()
methods will allow those who already have a blob instance to upload some content to it as simply asblob = blob.uploadFrom(myFile);
A static method would require an extra parameter
storage
, which is not very convenient.As I said previously I'm going to extend the Storage interface with new methods:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is what's throwing me: the object is immutable but has methods to modify it? Why?
This seems to be the existing API, but it's still extremely wonky. After you call uploadFrom (or downloadTo) how does the new Blob bject returned differ from the original Blob object?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can consider a Blob instance like a pointer to {bucket, name, generation} and methods to operate with that pointer. Each modification of the blob causes the generation change, so existing pointer becomes outdated and the Blob instance useless(Immutability does not allow to modify the generation in place). A new object returned by uploadFrom has the new generation value pointing to the latest blob version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice if the docs said that, or the classes were named accurately. but c'est la vie.
#176
My immediate question is what exactly has changed in the object? What field has been modified and how?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new Blob objects are constructed from data received from GCS.
In case of uploadFrom only 'generation' and 'size' will be changed. (These fields are inherited from the BlobInfo class.)
uploadFrom uses WriteChannel, so it has to perform a separate GET request to obtain the fresh data.
upload method performs the RPC request and obtain the data to construct new object from the response.