pub use crate::generated::clients::{BlobClient, BlobClientOptions};
use crate::{
generated::clients::BlobClient as GeneratedBlobClient,
generated::models::BlobClientDownloadInternalOptions,
models::{
BlobClientDownloadOptions, BlobClientDownloadResult, BlobClientUploadOptions,
BlobClientUploadResult, HttpRange, StorageErrorCode,
},
partitioned_transfer::{self, PartitionedDownloadBehavior},
AppendBlobClient, BlockBlobClient, PageBlobClient,
};
use async_trait::async_trait;
use azure_core::{
credentials::TokenCredential,
error::ErrorKind,
http::{
policies::{auth::BearerTokenAuthorizationPolicy, Policy},
AsyncRawResponse, ClientMethodOptions, Etag, NoFormat, Pipeline, RequestContent,
StatusCode, Url, UrlExt,
},
tracing, Bytes, Result,
};
use std::{ops::Range, sync::Arc};
impl BlobClient {
#[tracing::new("Storage.Blob.Blob")]
pub fn new(
blob_url: Url,
credential: Option<Arc<dyn TokenCredential>>,
options: Option<BlobClientOptions>,
) -> Result<Self> {
if blob_url.cannot_be_a_base() {
return Err(azure_core::Error::with_message(
azure_core::error::ErrorKind::Other,
format!("{blob_url} is not a valid base URL"),
));
}
let mut options = options.unwrap_or_default();
super::apply_client_defaults(&mut options.client_options);
let mut per_retry_policies: Vec<Arc<dyn Policy>> = Vec::default();
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"),
));
}
per_retry_policies.push(Arc::new(BearerTokenAuthorizationPolicy::new(
token_credential,
vec!["https://storage.azure.com/.default"],
)));
}
let pipeline = Pipeline::new(
option_env!("CARGO_PKG_NAME"),
option_env!("CARGO_PKG_VERSION"),
options.client_options.clone(),
Vec::default(),
per_retry_policies,
None,
);
Ok(Self {
endpoint: blob_url,
version: options.version,
pipeline,
})
}
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_else(crate::partitioned_transfer::defaults::default_concurrency);
let partition_size = options
.partition_size
.unwrap_or(crate::partitioned_transfer::defaults::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: None,
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),
}
}
}
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>>,
etag_lock: Option<Etag>,
) -> Result<AsyncRawResponse> {
let mut opt = self.options.clone();
opt.range = range.map(HttpRange::from);
if let Some(etag) = etag_lock {
opt.if_match = Some(etag);
opt.if_none_match = None;
opt.if_modified_since = None;
opt.if_unmodified_since = None;
opt.if_tags = None;
}
self.client
.download_internal(Some(opt))
.await
.map(AsyncRawResponse::from)
}
}