Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,15 @@
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileAttribute;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
Expand Down Expand Up @@ -680,8 +684,40 @@ private Mono<ReliableDownload> downloadHelper(BlobRange range, DownloadRetryOpti
* @return A reactive response containing the blob properties and metadata.
*/
public Mono<BlobProperties> downloadToFile(String filePath) {
return downloadToFile(filePath, false);
}

/**
* Downloads the entire blob into a file specified by the path.
*
* <p>The file will be created and must not exist, if the file already exists a {@link FileAlreadyExistsException}
* will be thrown.</p>
*
* <p>Uploading data must be done from the {@link BlockBlobClient}, {@link PageBlobClient}, or {@link
* AppendBlobClient}.</p>
*
* <p><strong>Code Samples</strong></p>
*
* {@codesnippet com.azure.storage.blob.specialized.BlobAsyncClientBase.downloadToFile#String}
*
* <p>For more information, see the
* <a href="https://docs.microsoft.com/en-us/rest/api/storageservices/get-blob">Azure Docs</a></p>
*
* @param filePath A non-null {@link OutputStream} instance where the downloaded data will be written.
* @param overwrite Whether or not to overwrite the file, should the file exist.
* @return A reactive response containing the blob properties and metadata.
*/
public Mono<BlobProperties> downloadToFile(String filePath, boolean overwrite) {
try {
return downloadToFileWithResponse(filePath, null, null, null, null, false).flatMap(FluxUtil::toMono);
Set<OpenOption> openOptions = null;
if (overwrite) {
openOptions = new HashSet<>();
openOptions.add(StandardOpenOption.CREATE);
openOptions.add(StandardOpenOption.TRUNCATE_EXISTING); // If the file already exists and it is opened
// for WRITE access, then its length is truncated to 0.
}
return downloadToFileWithResponse(filePath, null, null, null, null, false, openOptions)
.flatMap(FluxUtil::toMono);
} catch (RuntimeException ex) {
return monoError(logger, ex);
}
Expand Down Expand Up @@ -720,34 +756,79 @@ public Mono<BlobProperties> downloadToFile(String filePath) {
public Mono<Response<BlobProperties>> downloadToFileWithResponse(String filePath, BlobRange range,
ParallelTransferOptions parallelTransferOptions, DownloadRetryOptions options,
BlobRequestConditions requestConditions, boolean rangeGetContentMd5) {
return downloadToFileWithResponse(filePath, range, parallelTransferOptions, options, requestConditions,
rangeGetContentMd5, null);
}

/**
* Downloads the entire blob into a file specified by the path.
*
* <p>The file will be created and must not exist, if the file already exists a {@link FileAlreadyExistsException}
* will be thrown.</p>
*
* <p>Uploading data must be done from the {@link BlockBlobClient}, {@link PageBlobClient}, or {@link
* AppendBlobClient}.</p>
*
* <p>This method makes an extra HTTP call to get the length of the blob in the beginning. To avoid this extra
* call, provide the {@link BlobRange} parameter.</p>
*
* <p><strong>Code Samples</strong></p>
*
* {@codesnippet com.azure.storage.blob.specialized.BlobAsyncClientBase.downloadToFileWithResponse#String-BlobRange-ParallelTransferOptions-DownloadRetryOptions-BlobRequestConditions-boolean}
*
* <p>For more information, see the
* <a href="https://docs.microsoft.com/en-us/rest/api/storageservices/get-blob">Azure Docs</a></p>
*
* @param filePath A non-null {@link OutputStream} instance where the downloaded data will be written.
* @param range {@link BlobRange}
* @param parallelTransferOptions {@link ParallelTransferOptions} to use to download to file. Number of parallel
* transfers parameter is ignored.
* @param options {@link DownloadRetryOptions}
* @param requestConditions {@link BlobRequestConditions}
* @param rangeGetContentMd5 Whether the contentMD5 for the specified blob range should be returned.
* @param openOptions {@link OpenOption OpenOptions} to use to configure how to open or create the file.
* @return A reactive response containing the blob properties and metadata.
* @throws IllegalArgumentException If {@code blockSize} is less than 0 or greater than 100MB.
* @throws UncheckedIOException If an I/O error occurs.
*/
public Mono<Response<BlobProperties>> downloadToFileWithResponse(String filePath, BlobRange range,
ParallelTransferOptions parallelTransferOptions, DownloadRetryOptions options,
BlobRequestConditions requestConditions, boolean rangeGetContentMd5, Set<OpenOption> openOptions) {
try {
return withContext(context -> downloadToFileWithResponse(filePath, range, parallelTransferOptions, options,
requestConditions, rangeGetContentMd5, context));
requestConditions, rangeGetContentMd5, openOptions, context));
} catch (RuntimeException ex) {
return monoError(logger, ex);
}
}

Mono<Response<BlobProperties>> downloadToFileWithResponse(String filePath, BlobRange range,
ParallelTransferOptions parallelTransferOptions, DownloadRetryOptions downloadRetryOptions,
BlobRequestConditions requestConditions, boolean rangeGetContentMd5, Context context) {
BlobRequestConditions requestConditions, boolean rangeGetContentMd5, Set<OpenOption> openOptions,
Context context) {
BlobRange finalRange = range == null ? new BlobRange(0) : range;
final ParallelTransferOptions finalParallelTransferOptions =
ModelHelper.populateAndApplyDefaults(parallelTransferOptions);
BlobRequestConditions finalConditions = requestConditions == null
? new BlobRequestConditions() : requestConditions;

AsynchronousFileChannel channel = downloadToFileResourceSupplier(filePath);
if (openOptions == null) {
openOptions = new HashSet<>();
openOptions.add(StandardOpenOption.CREATE_NEW);
}
openOptions.add(StandardOpenOption.WRITE); // Open file for write
openOptions.add(StandardOpenOption.READ); // Open file for read

AsynchronousFileChannel channel = downloadToFileResourceSupplier(filePath, openOptions);
return Mono.just(channel)
.flatMap(c -> this.downloadToFileImpl(c, finalRange, finalParallelTransferOptions,
downloadRetryOptions, finalConditions, rangeGetContentMd5, context))
.doFinally(signalType -> this.downloadToFileCleanup(channel, filePath, signalType));
}

private AsynchronousFileChannel downloadToFileResourceSupplier(String filePath) {
private AsynchronousFileChannel downloadToFileResourceSupplier(String filePath, Set<OpenOption> openOptions) {
try {
return AsynchronousFileChannel.open(Paths.get(filePath), StandardOpenOption.READ, StandardOpenOption.WRITE,
StandardOpenOption.CREATE_NEW);
return AsynchronousFileChannel.open(Paths.get(filePath), openOptions, null);
} catch (IOException e) {
throw logger.logExceptionAsError(new UncheckedIOException(e));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,13 @@
import java.io.UncheckedIOException;
import java.net.URL;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.OpenOption;
import java.nio.file.StandardOpenOption;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import static com.azure.storage.common.implementation.StorageImplUtils.blockWithOptionalTimeout;

Expand Down Expand Up @@ -444,7 +448,41 @@ public BlobDownloadResponse downloadWithResponse(OutputStream stream, BlobRange
* @throws UncheckedIOException If an I/O error occurs
*/
public BlobProperties downloadToFile(String filePath) {
return downloadToFileWithResponse(filePath, null, null, null, null, false, null, Context.NONE).getValue();
return downloadToFile(filePath, false);
}

/**
* Downloads the entire blob into a file specified by the path.
*
* <p>The file will be created and must not exist, if the file already exists a {@link FileAlreadyExistsException}
* will be thrown.</p>
*
* <p>Uploading data must be done from the {@link BlockBlobClient}, {@link PageBlobClient}, or {@link
* AppendBlobClient}.</p>
*
* <p><strong>Code Samples</strong></p>
*
* {@codesnippet com.azure.storage.blob.specialized.BlobClientBase.downloadToFile#String}
*
* <p>For more information, see the
* <a href="https://docs.microsoft.com/en-us/rest/api/storageservices/get-blob">Azure Docs</a></p>
*
* @param filePath A non-null {@link OutputStream} instance where the downloaded data will be written.
* @param overwrite Whether or not to overwrite the file, should the file exist.
* @return The blob properties and metadata.
* @throws UncheckedIOException If an I/O error occurs
*/
public BlobProperties downloadToFile(String filePath, boolean overwrite) {
Set<OpenOption> openOptions = new HashSet<>();
if (overwrite) {
openOptions.add(StandardOpenOption.CREATE);
openOptions.add(StandardOpenOption.TRUNCATE_EXISTING); // If the file already exists and it is opened
// for WRITE access, then its length is truncated to 0.
} else {
openOptions.add(StandardOpenOption.CREATE_NEW); // Create new file, fails if the file already exists
}
return downloadToFileWithResponse(filePath, null, null, null, null, false, openOptions, null, Context.NONE)
.getValue();
}

/**
Expand Down Expand Up @@ -481,8 +519,48 @@ public BlobProperties downloadToFile(String filePath) {
public Response<BlobProperties> downloadToFileWithResponse(String filePath, BlobRange range,
ParallelTransferOptions parallelTransferOptions, DownloadRetryOptions downloadRetryOptions,
BlobRequestConditions requestConditions, boolean rangeGetContentMd5, Duration timeout, Context context) {
return downloadToFileWithResponse(filePath, range, parallelTransferOptions, downloadRetryOptions,
requestConditions, rangeGetContentMd5, null, timeout, context);
}

/**
* Downloads the entire blob into a file specified by the path.
*
* <p>The file will be created and must not exist, if the file already exists a {@link FileAlreadyExistsException}
* will be thrown.</p>
*
* <p>Uploading data must be done from the {@link BlockBlobClient}, {@link PageBlobClient}, or {@link
* AppendBlobClient}.</p>
*
* <p>This method makes an extra HTTP call to get the length of the blob in the beginning. To avoid this extra
* call, provide the {@link BlobRange} parameter.</p>
*
* <p><strong>Code Samples</strong></p>
*
* {@codesnippet com.azure.storage.blob.specialized.BlobClientBase.downloadToFileWithResponse#String-BlobRange-ParallelTransferOptions-DownloadRetryOptions-BlobRequestConditions-boolean-Duration-Context}
*
* <p>For more information, see the
* <a href="https://docs.microsoft.com/en-us/rest/api/storageservices/get-blob">Azure Docs</a></p>
*
* @param filePath A non-null {@link OutputStream} instance where the downloaded data will be written.
* @param range {@link BlobRange}
* @param parallelTransferOptions {@link ParallelTransferOptions} to use to download to file. Number of parallel
* transfers parameter is ignored.
* @param downloadRetryOptions {@link DownloadRetryOptions}
* @param requestConditions {@link BlobRequestConditions}
* @param rangeGetContentMd5 Whether the contentMD5 for the specified blob range should be returned.
* @param openOptions {@link OpenOption OpenOptions} to use to configure how to open or create the file.
* @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised.
* @param context Additional context that is passed through the Http pipeline during the service call.
* @return A response containing the blob properties and metadata.
* @throws UncheckedIOException If an I/O error occurs.
*/
public Response<BlobProperties> downloadToFileWithResponse(String filePath, BlobRange range,
ParallelTransferOptions parallelTransferOptions, DownloadRetryOptions downloadRetryOptions,
BlobRequestConditions requestConditions, boolean rangeGetContentMd5, Set<OpenOption> openOptions,
Duration timeout, Context context) {
Mono<Response<BlobProperties>> download = client.downloadToFileWithResponse(filePath, range,
parallelTransferOptions, downloadRetryOptions, requestConditions, rangeGetContentMd5, context);
parallelTransferOptions, downloadRetryOptions, requestConditions, rangeGetContentMd5, openOptions, context);
return blockWithOptionalTimeout(download, timeout);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@ import spock.lang.Requires
import spock.lang.Unroll

import java.nio.ByteBuffer
import java.nio.channels.NonWritableChannelException
import java.nio.charset.StandardCharsets
import java.nio.file.FileAlreadyExistsException
import java.nio.file.Files
import java.nio.file.OpenOption
import java.nio.file.StandardOpenOption
import java.security.MessageDigest
import java.time.Duration
import java.time.OffsetDateTime
Expand Down Expand Up @@ -263,6 +266,7 @@ class BlobAPITest extends APISpec {
}

when:
// Default overwrite is false so this should fail
bc.downloadToFile(testFile.getPath())

then:
Expand All @@ -273,6 +277,23 @@ class BlobAPITest extends APISpec {
testFile.delete()
}

def "Download to file exists succeeds"() {
setup:
def testFile = new File(testName + ".txt")
if (testFile.exists()) {
assert testFile.createNewFile()
}

when:
bc.downloadToFile(testFile.getPath(), true)

then:
new String(Files.readAllBytes(testFile.toPath()), StandardCharsets.UTF_8) == defaultText

cleanup:
testFile.delete()
}

def "Download to file does not exist"() {
setup:
def testFile = new File(testName + ".txt")
Expand All @@ -290,6 +311,45 @@ class BlobAPITest extends APISpec {
testFile.delete()
}

def "Download file does not exist open options"() {
setup:
def testFile = new File(testName + ".txt")
if (testFile.exists()) {
assert testFile.delete()
}

when:
Set<OpenOption> openOptions = new HashSet<>()
openOptions.add(StandardOpenOption.CREATE_NEW)
bc.downloadToFileWithResponse(testFile.getPath(), null, null, null, null, false, openOptions, null, null)

then:
new String(Files.readAllBytes(testFile.toPath()), StandardCharsets.UTF_8) == defaultText

cleanup:
testFile.delete()
}

def "Download file exist open options"() {
setup:
def testFile = new File(testName + ".txt")
if (!testFile.exists()) {
assert testFile.createNewFile()
}

when:
Set<OpenOption> openOptions = new HashSet<>()
openOptions.add(StandardOpenOption.CREATE)
openOptions.add(StandardOpenOption.TRUNCATE_EXISTING)
bc.downloadToFileWithResponse(testFile.getPath(), null, null, null, null, false, openOptions, null, null)

then:
new String(Files.readAllBytes(testFile.toPath()), StandardCharsets.UTF_8) == defaultText

cleanup:
testFile.delete()
}

@Requires({ liveMode() })
@Unroll
def "Download file"() {
Expand Down
Loading