|
21 | 21 |
|
22 | 22 | import com.google.common.collect.ImmutableList; |
23 | 23 | import com.google.common.collect.Iterables; |
| 24 | +import com.google.common.collect.Lists; |
24 | 25 | import com.google.gcloud.AuthCredentials.ServiceAccountAuthCredentials; |
25 | 26 | import com.google.gcloud.Service; |
26 | 27 | import com.google.gcloud.spi.StorageRpc; |
| 28 | +import com.google.gcloud.spi.StorageRpc.Tuple; |
27 | 29 |
|
28 | 30 | import java.io.InputStream; |
29 | 31 | import java.io.Serializable; |
|
33 | 35 | import java.util.LinkedHashSet; |
34 | 36 | import java.util.LinkedList; |
35 | 37 | import java.util.List; |
| 38 | +import java.util.Objects; |
36 | 39 | import java.util.Set; |
37 | 40 | import java.util.concurrent.TimeUnit; |
38 | 41 |
|
@@ -145,6 +148,105 @@ public static BlobTargetOption metagenerationMatch() { |
145 | 148 | public static BlobTargetOption metagenerationNotMatch() { |
146 | 149 | return new BlobTargetOption(StorageRpc.Option.IF_METAGENERATION_NOT_MATCH); |
147 | 150 | } |
| 151 | + |
| 152 | + static Tuple<BlobInfo, BlobTargetOption[]> convert(BlobInfo info, BlobWriteOption... options) { |
| 153 | + BlobInfo.Builder infoBuilder = info.toBuilder().crc32c(null).md5(null); |
| 154 | + List<BlobTargetOption> targetOptions = Lists.newArrayListWithCapacity(options.length); |
| 155 | + for (BlobWriteOption option : options) { |
| 156 | + switch (option.option) { |
| 157 | + case IF_CRC32C_MATCH: |
| 158 | + infoBuilder.crc32c(info.crc32c()); |
| 159 | + break; |
| 160 | + case IF_MD5_MATCH: |
| 161 | + infoBuilder.md5(info.md5()); |
| 162 | + break; |
| 163 | + default: |
| 164 | + targetOptions.add(option.toTargetOption()); |
| 165 | + break; |
| 166 | + } |
| 167 | + } |
| 168 | + return Tuple.of(infoBuilder.build(), |
| 169 | + targetOptions.toArray(new BlobTargetOption[targetOptions.size()])); |
| 170 | + } |
| 171 | + } |
| 172 | + |
| 173 | + class BlobWriteOption implements Serializable { |
| 174 | + |
| 175 | + private static final long serialVersionUID = -3880421670966224580L; |
| 176 | + |
| 177 | + private final Option option; |
| 178 | + private final Object value; |
| 179 | + |
| 180 | + enum Option { |
| 181 | + PREDEFINED_ACL, IF_GENERATION_MATCH, IF_GENERATION_NOT_MATCH, IF_METAGENERATION_MATCH, |
| 182 | + IF_METAGENERATION_NOT_MATCH, IF_MD5_MATCH, IF_CRC32C_MATCH; |
| 183 | + |
| 184 | + StorageRpc.Option toRpcOption() { |
| 185 | + return StorageRpc.Option.valueOf(this.name()); |
| 186 | + } |
| 187 | + } |
| 188 | + |
| 189 | + BlobTargetOption toTargetOption() { |
| 190 | + return new BlobTargetOption(this.option.toRpcOption(), this.value); |
| 191 | + } |
| 192 | + |
| 193 | + private BlobWriteOption(Option option, Object value) { |
| 194 | + this.option = option; |
| 195 | + this.value = value; |
| 196 | + } |
| 197 | + |
| 198 | + private BlobWriteOption(Option option) { |
| 199 | + this(option, null); |
| 200 | + } |
| 201 | + |
| 202 | + @Override |
| 203 | + public int hashCode() { |
| 204 | + return Objects.hash(option, value); |
| 205 | + } |
| 206 | + |
| 207 | + @Override |
| 208 | + public boolean equals(Object obj) { |
| 209 | + if (obj == null) { |
| 210 | + return false; |
| 211 | + } |
| 212 | + if (!(obj instanceof BlobWriteOption)) { |
| 213 | + return false; |
| 214 | + } |
| 215 | + final BlobWriteOption other = (BlobWriteOption) obj; |
| 216 | + return this.option == other.option && Objects.equals(this.value, other.value); |
| 217 | + } |
| 218 | + |
| 219 | + public static BlobWriteOption predefinedAcl(PredefinedAcl acl) { |
| 220 | + return new BlobWriteOption(Option.PREDEFINED_ACL, acl.entry()); |
| 221 | + } |
| 222 | + |
| 223 | + public static BlobWriteOption doesNotExist() { |
| 224 | + return new BlobWriteOption(Option.IF_GENERATION_MATCH, 0L); |
| 225 | + } |
| 226 | + |
| 227 | + public static BlobWriteOption generationMatch() { |
| 228 | + return new BlobWriteOption(Option.IF_GENERATION_MATCH); |
| 229 | + } |
| 230 | + |
| 231 | + public static BlobWriteOption generationNotMatch() { |
| 232 | + return new BlobWriteOption(Option.IF_GENERATION_NOT_MATCH); |
| 233 | + } |
| 234 | + |
| 235 | + public static BlobWriteOption metagenerationMatch() { |
| 236 | + return new BlobWriteOption(Option.IF_METAGENERATION_MATCH); |
| 237 | + } |
| 238 | + |
| 239 | + public static BlobWriteOption metagenerationNotMatch() { |
| 240 | + return new BlobWriteOption(Option.IF_METAGENERATION_NOT_MATCH); |
| 241 | + } |
| 242 | + |
| 243 | + public static BlobWriteOption md5Match() { |
| 244 | + return new BlobWriteOption(Option.IF_MD5_MATCH, true); |
| 245 | + } |
| 246 | + |
| 247 | + public static BlobWriteOption crc32cMatch() { |
| 248 | + return new BlobWriteOption(Option.IF_CRC32C_MATCH, true); |
| 249 | + } |
148 | 250 | } |
149 | 251 |
|
150 | 252 | class BlobSourceOption extends Option { |
@@ -510,21 +612,25 @@ public static Builder builder() { |
510 | 612 |
|
511 | 613 | /** |
512 | 614 | * Create a new blob. Direct upload is used to upload {@code content}. For large content, |
513 | | - * {@link #writer} is recommended as it uses resumable upload. |
| 615 | + * {@link #writer} is recommended as it uses resumable upload. MD5 and CRC32C hashes of |
| 616 | + * {@code content} are computed and used for validating transferred data. |
514 | 617 | * |
515 | 618 | * @return a complete blob information. |
516 | 619 | * @throws StorageException upon failure |
| 620 | + * @see <a href="https://cloud.google.com/storage/docs/hashes-etags">Hashes and ETags</a> |
517 | 621 | */ |
518 | 622 | BlobInfo create(BlobInfo blobInfo, byte[] content, BlobTargetOption... options); |
519 | 623 |
|
520 | 624 | /** |
521 | 625 | * Create a new blob. Direct upload is used to upload {@code content}. For large content, |
522 | | - * {@link #writer} is recommended as it uses resumable upload. |
| 626 | + * {@link #writer} is recommended as it uses resumable upload. By default any md5 and crc32c |
| 627 | + * values in the given {@code blobInfo} are ignored unless requested via the |
| 628 | + * {@code BlobWriteOption.md5Match} and {@code BlobWriteOption.crc32cMatch} options. |
523 | 629 | * |
524 | 630 | * @return a complete blob information. |
525 | 631 | * @throws StorageException upon failure |
526 | 632 | */ |
527 | | - BlobInfo create(BlobInfo blobInfo, InputStream content, BlobTargetOption... options); |
| 633 | + BlobInfo create(BlobInfo blobInfo, InputStream content, BlobWriteOption... options); |
528 | 634 |
|
529 | 635 | /** |
530 | 636 | * Return the requested bucket or {@code null} if not found. |
@@ -679,11 +785,13 @@ public static Builder builder() { |
679 | 785 | BlobReadChannel reader(BlobId blob, BlobSourceOption... options); |
680 | 786 |
|
681 | 787 | /** |
682 | | - * Create a blob and return a channel for writing its content. |
| 788 | + * Create a blob and return a channel for writing its content. By default any md5 and crc32c |
| 789 | + * values in the given {@code blobInfo} are ignored unless requested via the |
| 790 | + * {@code BlobWriteOption.md5Match} and {@code BlobWriteOption.crc32cMatch} options. |
683 | 791 | * |
684 | 792 | * @throws StorageException upon failure |
685 | 793 | */ |
686 | | - BlobWriteChannel writer(BlobInfo blobInfo, BlobTargetOption... options); |
| 794 | + BlobWriteChannel writer(BlobInfo blobInfo, BlobWriteOption... options); |
687 | 795 |
|
688 | 796 | /** |
689 | 797 | * Generates a signed URL for a blob. |
|
0 commit comments