|
14 | 14 | import com.azure.core.http.rest.PagedResponseBase;
|
15 | 15 | import com.azure.core.http.rest.Response;
|
16 | 16 | import com.azure.core.http.rest.SimpleResponse;
|
| 17 | +import com.azure.core.http.rest.StreamResponse; |
17 | 18 | import com.azure.core.util.Context;
|
18 | 19 | import com.azure.core.util.CoreUtils;
|
19 | 20 | import com.azure.core.util.FluxUtil;
|
|
48 | 49 | import com.azure.storage.file.share.implementation.util.ShareSasImplUtil;
|
49 | 50 | import com.azure.storage.file.share.models.CloseHandlesInfo;
|
50 | 51 | import com.azure.storage.file.share.models.CopyStatusType;
|
| 52 | +import com.azure.storage.file.share.models.DownloadRetryOptions; |
51 | 53 | import com.azure.storage.file.share.models.HandleItem;
|
52 | 54 | import com.azure.storage.file.share.models.LeaseDurationType;
|
53 | 55 | import com.azure.storage.file.share.models.LeaseStateType;
|
|
58 | 60 | import com.azure.storage.file.share.models.ShareErrorCode;
|
59 | 61 | import com.azure.storage.file.share.models.ShareFileCopyInfo;
|
60 | 62 | import com.azure.storage.file.share.models.ShareFileDownloadAsyncResponse;
|
| 63 | +import com.azure.storage.file.share.models.ShareFileDownloadHeaders; |
61 | 64 | import com.azure.storage.file.share.models.ShareFileHttpHeaders;
|
62 | 65 | import com.azure.storage.file.share.models.ShareFileInfo;
|
63 | 66 | import com.azure.storage.file.share.models.ShareFileMetadataInfo;
|
|
70 | 73 | import com.azure.storage.file.share.models.ShareFileUploadRangeFromUrlInfo;
|
71 | 74 | import com.azure.storage.file.share.models.ShareRequestConditions;
|
72 | 75 | import com.azure.storage.file.share.models.ShareStorageException;
|
| 76 | +import com.azure.storage.file.share.options.ShareFileDownloadOptions; |
73 | 77 | import com.azure.storage.file.share.options.ShareFileListRangesDiffOptions;
|
74 | 78 | import com.azure.storage.file.share.options.ShareFileUploadRangeFromUrlOptions;
|
75 | 79 | import com.azure.storage.file.share.sas.ShareServiceSasSignatureValues;
|
|
94 | 98 | import java.time.OffsetDateTime;
|
95 | 99 | import java.util.ArrayList;
|
96 | 100 | import java.util.Collections;
|
| 101 | +import java.util.ConcurrentModificationException; |
97 | 102 | import java.util.List;
|
98 | 103 | import java.util.Map;
|
99 | 104 | import java.util.Objects;
|
@@ -135,6 +140,7 @@ public class ShareFileAsyncClient {
|
135 | 140 | static final long FILE_DEFAULT_BLOCK_SIZE = 4 * 1024 * 1024L;
|
136 | 141 | static final long FILE_MAX_PUT_RANGE_SIZE = 4 * Constants.MB;
|
137 | 142 | private static final long DOWNLOAD_UPLOAD_CHUNK_TIMEOUT = 300;
|
| 143 | + private static final Duration TIMEOUT_VALUE = Duration.ofSeconds(60); |
138 | 144 |
|
139 | 145 | private final AzureFileStorageImpl azureFileStorageClient;
|
140 | 146 | private final String shareName;
|
@@ -735,7 +741,8 @@ private Mono<Response<ShareFileProperties>> downloadResponseInChunk(Response<Sha
|
735 | 741 | }
|
736 | 742 | return chunks;
|
737 | 743 | }).flatMapMany(Flux::fromIterable).flatMap(chunk ->
|
738 |
| - downloadWithResponse(chunk, false, requestConditions, context) |
| 744 | + downloadWithResponse(new ShareFileDownloadOptions().setRange(chunk).setRangeContentMd5(false) |
| 745 | + .setRequestConditions(requestConditions), context) |
739 | 746 | .map(ShareFileDownloadAsyncResponse::getValue)
|
740 | 747 | .subscribeOn(Schedulers.elastic())
|
741 | 748 | .flatMap(fbb -> FluxUtil
|
@@ -779,8 +786,7 @@ private void channelCleanUp(AsynchronousFileChannel channel) {
|
779 | 786 | */
|
780 | 787 | public Flux<ByteBuffer> download() {
|
781 | 788 | try {
|
782 |
| - return downloadWithResponse(null, null).flatMapMany( |
783 |
| - ShareFileDownloadAsyncResponse::getValue); |
| 789 | + return downloadWithResponse(null).flatMapMany(ShareFileDownloadAsyncResponse::getValue); |
784 | 790 | } catch (RuntimeException ex) {
|
785 | 791 | return fluxError(logger, ex);
|
786 | 792 | }
|
@@ -827,25 +833,110 @@ public Mono<ShareFileDownloadAsyncResponse> downloadWithResponse(ShareFileRange
|
827 | 833 | */
|
828 | 834 | public Mono<ShareFileDownloadAsyncResponse> downloadWithResponse(ShareFileRange range, Boolean rangeGetContentMD5,
|
829 | 835 | ShareRequestConditions requestConditions) {
|
| 836 | + return downloadWithResponse(new ShareFileDownloadOptions().setRange(range) |
| 837 | + .setRangeContentMd5(rangeGetContentMD5).setRequestConditions(requestConditions)); |
| 838 | + } |
| 839 | + |
| 840 | + /** |
| 841 | + * Downloads a file from the system, including its metadata and properties |
| 842 | + * |
| 843 | + * <p><strong>Code Samples</strong></p> |
| 844 | + * |
| 845 | + * <p>Download the file from 1024 to 2048 bytes with its metadata and properties and without the contentMD5. </p> |
| 846 | + * |
| 847 | + * {@codesnippet com.azure.storage.file.share.ShareFileAsyncClient.downloadWithResponse#ShareFileDownloadOptions} |
| 848 | + * |
| 849 | + * <p>For more information, see the |
| 850 | + * <a href="https://docs.microsoft.com/rest/api/storageservices/get-file">Azure Docs</a>.</p> |
| 851 | + * |
| 852 | + * @param options {@link ShareFileDownloadOptions} |
| 853 | + * true, as long as the range is less than or equal to 4 MB in size. |
| 854 | + * @return A reactive response containing response data and the file data. |
| 855 | + */ |
| 856 | + public Mono<ShareFileDownloadAsyncResponse> downloadWithResponse(ShareFileDownloadOptions options) { |
830 | 857 | try {
|
831 |
| - return withContext(context -> downloadWithResponse(range, rangeGetContentMD5, |
832 |
| - requestConditions, context)); |
| 858 | + return withContext(context -> downloadWithResponse(options, context)); |
833 | 859 | } catch (RuntimeException ex) {
|
834 | 860 | return monoError(logger, ex);
|
835 | 861 | }
|
836 | 862 | }
|
837 | 863 |
|
838 |
| - Mono<ShareFileDownloadAsyncResponse> downloadWithResponse(ShareFileRange range, Boolean rangeGetContentMD5, |
| 864 | + Mono<ShareFileDownloadAsyncResponse> downloadWithResponse(ShareFileDownloadOptions options, Context context) { |
| 865 | + options = options == null ? new ShareFileDownloadOptions() : options; |
| 866 | + ShareFileRange range = options.getRange() == null ? new ShareFileRange(0) : options.getRange(); |
| 867 | + ShareRequestConditions requestConditions = options.getRequestConditions() == null |
| 868 | + ? new ShareRequestConditions() : options.getRequestConditions(); |
| 869 | + DownloadRetryOptions retryOptions = options.getRetryOptions() == null ? new DownloadRetryOptions() |
| 870 | + : options.getRetryOptions(); |
| 871 | + Boolean getRangeContentMd5 = options.getRangeContentMd5(); |
| 872 | + |
| 873 | + return downloadRange(range, getRangeContentMd5, requestConditions, context) |
| 874 | + .map(response -> { |
| 875 | + String eTag = ModelHelper.getETag(response.getHeaders()); |
| 876 | + ShareFileDownloadHeaders headers = ModelHelper.transformFileDownloadHeaders(response.getHeaders()); |
| 877 | + |
| 878 | + long finalEnd; |
| 879 | + if (range.getEnd() == null) { |
| 880 | + finalEnd = headers.getContentRange() == null ? headers.getContentLength() |
| 881 | + : Long.parseLong(headers.getContentRange().split("/")[1]); |
| 882 | + } else { |
| 883 | + finalEnd = range.getEnd(); |
| 884 | + } |
| 885 | + |
| 886 | + Flux<ByteBuffer> bufferFlux = FluxUtil.createRetriableDownloadFlux( |
| 887 | + () -> response.getValue().timeout(TIMEOUT_VALUE), |
| 888 | + (throwable, offset) -> { |
| 889 | + if (!(throwable instanceof IOException || throwable instanceof TimeoutException)) { |
| 890 | + return Flux.error(throwable); |
| 891 | + } |
| 892 | + |
| 893 | + long newCount = finalEnd - (offset - range.getStart()); |
| 894 | + |
| 895 | + /* |
| 896 | + It is possible that the network stream will throw an error after emitting all data but before |
| 897 | + completing. Issuing a retry at this stage would leave the download in a bad state with incorrect count |
| 898 | + and offset values. Because we have read the intended amount of data, we can ignore the error at the end |
| 899 | + of the stream. |
| 900 | + */ |
| 901 | + if (newCount == 0) { |
| 902 | + logger.warning("Exception encountered in ReliableDownload after all data read from the network but " |
| 903 | + + "but before stream signaled completion. Returning success as all data was downloaded. " |
| 904 | + + "Exception message: " + throwable.getMessage()); |
| 905 | + return Flux.empty(); |
| 906 | + } |
| 907 | + |
| 908 | + try { |
| 909 | + return downloadRange( |
| 910 | + new ShareFileRange(offset, range.getEnd()), getRangeContentMd5, |
| 911 | + requestConditions, context).flatMapMany(r -> { |
| 912 | + String receivedETag = ModelHelper.getETag(r.getHeaders()); |
| 913 | + if (eTag != null && eTag.equals(receivedETag)) { |
| 914 | + return r.getValue().timeout(TIMEOUT_VALUE); |
| 915 | + } else { |
| 916 | + return Flux.<ByteBuffer>error( |
| 917 | + new ConcurrentModificationException(String.format("File has been modified " |
| 918 | + + "concurrently. Expected eTag: %s, Received eTag: %s", eTag, |
| 919 | + receivedETag))); |
| 920 | + } |
| 921 | + }); |
| 922 | + } catch (Exception e) { |
| 923 | + return Flux.error(e); |
| 924 | + } |
| 925 | + }, |
| 926 | + retryOptions.getMaxRetryRequests(), |
| 927 | + range.getStart() |
| 928 | + ).switchIfEmpty(Flux.just(ByteBuffer.wrap(new byte[0]))); |
| 929 | + |
| 930 | + return new ShareFileDownloadAsyncResponse(response.getRequest(), response.getStatusCode(), |
| 931 | + response.getHeaders(), bufferFlux, headers); |
| 932 | + }); |
| 933 | + } |
| 934 | + |
| 935 | + private Mono<StreamResponse> downloadRange(ShareFileRange range, Boolean rangeGetContentMD5, |
839 | 936 | ShareRequestConditions requestConditions, Context context) {
|
840 |
| - requestConditions = requestConditions == null ? new ShareRequestConditions() : requestConditions; |
841 | 937 | String rangeString = range == null ? null : range.toString();
|
842 |
| - |
843 |
| - return azureFileStorageClient.getFiles() |
844 |
| - .downloadWithResponseAsync(shareName, filePath, null, rangeString, rangeGetContentMD5, |
845 |
| - requestConditions.getLeaseId(), context) |
846 |
| - .map(response -> new ShareFileDownloadAsyncResponse(response.getRequest(), response.getStatusCode(), |
847 |
| - response.getHeaders(), response.getValue(), |
848 |
| - ModelHelper.transformFileDownloadHeaders(response.getHeaders()))); |
| 938 | + return azureFileStorageClient.getFiles().downloadWithResponseAsync(shareName, filePath, null, |
| 939 | + rangeString, rangeGetContentMD5, requestConditions.getLeaseId(), context); |
849 | 940 | }
|
850 | 941 |
|
851 | 942 | /**
|
|
0 commit comments