pub use crate::generated::clients::{BlobClient, BlobClientOptions};
use crate::{
generated::clients::BlobClient as GeneratedBlobClient,
generated::models::BlobClientDownloadInternalOptions,
logging::apply_storage_logging_defaults,
models::{
http_ranges::IntoRangeHeader, BlobClientDownloadOptions, BlobClientDownloadResult,
BlobClientUploadOptions, BlobClientUploadResult, StorageErrorCode,
},
partitioned_transfer::{self, PartitionedDownloadBehavior},
pipeline::StorageHeadersPolicy,
AppendBlobClient, BlockBlobClient, PageBlobClient,
};
use async_trait::async_trait;
use azure_core::{
credentials::TokenCredential,
error::ErrorKind,
http::{
policies::{auth::BearerTokenAuthorizationPolicy, Policy},
AsyncRawResponse, ClientMethodOptions, NoFormat, Pipeline, RequestContent, StatusCode, Url,
UrlExt,
},
tracing, Bytes, Result,
};
use std::{num::NonZero, ops::Range, sync::Arc};
impl BlobClient {
pub fn new(
endpoint: &str,
container_name: &str,
blob_name: &str,
credential: Option<Arc<dyn TokenCredential>>,
options: Option<BlobClientOptions>,
) -> Result<Self> {
let mut url = Url::parse(endpoint)?;
{
let mut path_segments = url.path_segments_mut().map_err(|_| {
azure_core::Error::with_message(
azure_core::error::ErrorKind::Other,
"Invalid endpoint URL: Failed to parse out path segments from provided endpoint URL.",
)
})?;
path_segments.extend([container_name, blob_name]);
}
Self::from_url(url, credential, options)
}
#[tracing::new("Storage.Blob.Blob")]
pub fn from_url(
blob_url: Url,
credential: Option<Arc<dyn TokenCredential>>,
options: Option<BlobClientOptions>,
) -> Result<Self> {
let mut options = options.unwrap_or_default();
apply_storage_logging_defaults(&mut options.client_options);
let storage_headers_policy = Arc::new(StorageHeadersPolicy);
options
.client_options
.per_call_policies
.push(storage_headers_policy);
if let Some(token_credential) = credential {
if !blob_url.scheme().starts_with("https") {
return Err(azure_core::Error::with_message(
azure_core::error::ErrorKind::Other,
format!("{blob_url} must use https"),
));
}
let auth_policy: Arc<dyn Policy> = Arc::new(BearerTokenAuthorizationPolicy::new(
token_credential,
vec!["https://storage.azure.com/.default"],
));
options.client_options.per_try_policies.push(auth_policy);
}
let pipeline = Pipeline::new(
option_env!("CARGO_PKG_NAME"),
option_env!("CARGO_PKG_VERSION"),
options.client_options.clone(),
Vec::default(),
Vec::default(),
None,
);
Ok(Self {
endpoint: blob_url,
version: options.version,
pipeline,
})
}
pub async fn download_into(
&self,
buffer: &mut [u8],
options: Option<BlobClientDownloadOptions<'_>>,
) -> Result<usize> {
let options = options.unwrap_or_default();
let parallel = options.parallel.unwrap_or(DEFAULT_DOWNLOAD_PARALLEL);
let partition_size = options
.partition_size
.unwrap_or(DEFAULT_DOWNLOAD_PARTITION_SIZE);
let get_range_options = BlobClientDownloadInternalOptions {
encryption_algorithm: options.encryption_algorithm,
encryption_key: options.encryption_key,
encryption_key_sha256: options.encryption_key_sha256,
if_match: options.if_match,
if_modified_since: options.if_modified_since,
if_none_match: options.if_none_match,
if_tags: options.if_tags,
if_unmodified_since: options.if_unmodified_since,
lease_id: options.lease_id,
method_options: ClientMethodOptions {
context: options.method_options.context.into_owned(),
},
range: None,
range_get_content_crc64: options.range_get_content_crc64,
range_get_content_md5: options.range_get_content_md5,
snapshot: options.snapshot,
structured_body_type: options.structured_body_type,
timeout: options.timeout,
version_id: options.version_id,
};
let client = GeneratedBlobClient {
endpoint: self.endpoint.clone(),
pipeline: self.pipeline.clone(),
version: self.version.clone(),
tracer: self.tracer.clone(),
};
let client = BlobClientDownloadBehavior::new(client, get_range_options);
partitioned_transfer::download_into(
buffer,
options.range,
parallel,
partition_size,
Arc::new(client),
)
.await
}
pub fn append_blob_client(&self) -> AppendBlobClient {
AppendBlobClient {
endpoint: self.endpoint.clone(),
pipeline: self.pipeline.clone(),
version: self.version.clone(),
tracer: self.tracer.clone(),
}
}
pub fn block_blob_client(&self) -> BlockBlobClient {
BlockBlobClient {
endpoint: self.endpoint.clone(),
pipeline: self.pipeline.clone(),
version: self.version.clone(),
tracer: self.tracer.clone(),
}
}
pub fn page_blob_client(&self) -> PageBlobClient {
PageBlobClient {
endpoint: self.endpoint.clone(),
pipeline: self.pipeline.clone(),
version: self.version.clone(),
tracer: self.tracer.clone(),
}
}
pub fn url(&self) -> &Url {
&self.endpoint
}
pub fn with_version(&self, version_id: &str) -> Result<Self> {
let mut versioned_endpoint = self.endpoint.clone();
{
let mut query_builder = versioned_endpoint.query_builder();
query_builder.set_pair("versionid", version_id);
query_builder.build();
}
Ok(Self {
endpoint: versioned_endpoint,
pipeline: self.pipeline.clone(),
version: self.version.clone(),
tracer: self.tracer.clone(),
})
}
pub fn with_snapshot(&self, snapshot: &str) -> Result<Self> {
let mut snapshot_endpoint = self.endpoint.clone();
{
let mut query_builder = snapshot_endpoint.query_builder();
query_builder.set_pair("snapshot", snapshot);
query_builder.build();
}
Ok(Self {
endpoint: snapshot_endpoint,
pipeline: self.pipeline.clone(),
version: self.version.clone(),
tracer: self.tracer.clone(),
})
}
#[tracing::function("Storage.Blob.Blob.download")]
pub async fn download(
&self,
options: Option<BlobClientDownloadOptions<'_>>,
) -> Result<BlobClientDownloadResult> {
let options = options.unwrap_or_default();
let parallel = options.parallel.unwrap_or(DEFAULT_DOWNLOAD_PARALLEL);
let partition_size = options
.partition_size
.unwrap_or(DEFAULT_DOWNLOAD_PARTITION_SIZE);
let get_range_options = BlobClientDownloadInternalOptions {
encryption_algorithm: options.encryption_algorithm,
encryption_key: options.encryption_key,
encryption_key_sha256: options.encryption_key_sha256,
if_match: options.if_match,
if_modified_since: options.if_modified_since,
if_none_match: options.if_none_match,
if_tags: options.if_tags,
if_unmodified_since: options.if_unmodified_since,
lease_id: options.lease_id,
method_options: ClientMethodOptions {
context: options.method_options.context.into_owned(),
},
range: None,
range_get_content_crc64: options.range_get_content_crc64,
range_get_content_md5: options.range_get_content_md5,
snapshot: options.snapshot,
structured_body_type: options.structured_body_type,
timeout: options.timeout,
version_id: options.version_id,
};
let inner_client = GeneratedBlobClient {
endpoint: self.endpoint.clone(),
pipeline: self.pipeline.clone(),
version: self.version.clone(),
tracer: self.tracer.clone(),
};
let behavior = BlobClientDownloadBehavior::new(inner_client, get_range_options);
let response = partitioned_transfer::download(
options.range,
parallel,
partition_size,
Arc::new(behavior),
)
.await?;
BlobClientDownloadResult::from_headers(response)
}
pub async fn upload(
&self,
content: RequestContent<Bytes, NoFormat>,
options: Option<BlobClientUploadOptions<'_>>,
) -> Result<BlobClientUploadResult> {
self.block_blob_client().upload(content, options).await
}
pub async fn exists(&self) -> Result<bool> {
match self.get_properties(None).await {
Ok(_) => Ok(true),
Err(e) if e.http_status() == Some(StatusCode::NotFound) => match e.kind() {
ErrorKind::HttpResponse {
error_code: Some(error_code),
..
} if error_code == StorageErrorCode::BlobNotFound.as_ref()
|| error_code == StorageErrorCode::ContainerNotFound.as_ref() =>
{
Ok(false)
}
_ => Err(e),
},
Err(e) => Err(e),
}
}
}
const DEFAULT_DOWNLOAD_PARALLEL: NonZero<usize> = NonZero::new(4).unwrap();
const DEFAULT_DOWNLOAD_PARTITION_SIZE: NonZero<usize> = NonZero::new(4 * 1024 * 1024).unwrap();
struct BlobClientDownloadBehavior<'a> {
client: GeneratedBlobClient,
options: BlobClientDownloadInternalOptions<'a>,
}
impl<'a> BlobClientDownloadBehavior<'a> {
fn new(client: GeneratedBlobClient, options: BlobClientDownloadInternalOptions<'a>) -> Self {
Self { client, options }
}
}
#[async_trait]
impl PartitionedDownloadBehavior for BlobClientDownloadBehavior<'_> {
async fn transfer_range(&self, range: Option<Range<usize>>) -> Result<AsyncRawResponse> {
let mut opt = self.options.clone();
opt.range = range.map(|r| r.as_range_header());
self.client
.download_internal(Some(opt))
.await
.map(AsyncRawResponse::from)
}
}