diff --git a/sdk/storage_blobs/examples/set_blob_properties_00.rs b/sdk/storage_blobs/examples/set_blob_properties_00.rs new file mode 100644 index 0000000000..e70eaac683 --- /dev/null +++ b/sdk/storage_blobs/examples/set_blob_properties_00.rs @@ -0,0 +1,51 @@ +#[macro_use] +extern crate log; +use azure_storage::core::prelude::*; +use azure_storage_blobs::prelude::*; + +#[tokio::main] +async fn main() -> azure_core::Result<()> { + // First we retrieve the account name and master key from environment variables. + let account = + std::env::var("STORAGE_ACCOUNT").expect("Set env variable STORAGE_ACCOUNT first!"); + let master_key = + std::env::var("STORAGE_MASTER_KEY").expect("Set env variable STORAGE_MASTER_KEY first!"); + + let container = std::env::args() + .nth(1) + .expect("please specify container name as command line parameter"); + let blob = std::env::args() + .nth(2) + .expect("please specify blob name as command line parameter"); + + let http_client = azure_core::new_http_client(); + let storage_account_client = + StorageAccountClient::new_access_key(http_client.clone(), &account, &master_key); + + // this is how you would use the SAS token: + // let storage_account_client = StorageAccountClient::new_sas_token(http_client.clone(), &account, + // "sv=2018-11-09&ss=b&srt=o&se=2021-01-15T12%3A09%3A01Z&sp=r&st=2021-01-15T11%3A09%3A01Z&spr=http,https&sig=some_signature")?; + + let storage_client = storage_account_client.storage_client(); + let blob_client = storage_client + .container_client(&container) + .blob_client(&blob); + + trace!("Requesting blob properties"); + + let properties = blob_client + .get_properties() + .into_future() + .await? + .blob + .properties; + + blob_client + .set_properties() + .set_from_blob_properties(properties) + .content_md5(md5::compute("howdy")) + .into_future() + .await?; + + Ok(()) +} diff --git a/sdk/storage_blobs/src/blob/operations/mod.rs b/sdk/storage_blobs/src/blob/operations/mod.rs index 8ede628434..267358f2c1 100644 --- a/sdk/storage_blobs/src/blob/operations/mod.rs +++ b/sdk/storage_blobs/src/blob/operations/mod.rs @@ -22,6 +22,7 @@ mod release_lease; mod renew_lease; mod set_blob_tier; mod set_metadata; +mod set_properties; mod update_page; pub use acquire_lease::*; pub use append_block::*; @@ -47,4 +48,5 @@ pub use release_lease::*; pub use renew_lease::*; pub use set_blob_tier::*; pub use set_metadata::*; +pub use set_properties::*; pub use update_page::*; diff --git a/sdk/storage_blobs/src/blob/operations/set_properties.rs b/sdk/storage_blobs/src/blob/operations/set_properties.rs new file mode 100644 index 0000000000..0c672be2c5 --- /dev/null +++ b/sdk/storage_blobs/src/blob/operations/set_properties.rs @@ -0,0 +1,132 @@ +use crate::{blob::BlobProperties, prelude::*}; +use azure_core::prelude::*; +use azure_core::{ + headers::{ + date_from_headers, etag_from_headers, request_id_from_headers, server_from_headers, Headers, + }, + Method, RequestId, +}; +use chrono::{DateTime, Utc}; +use std::convert::{TryFrom, TryInto}; + +#[derive(Debug, Clone)] +pub struct SetPropertiesBuilder { + blob_client: BlobClient, + lease_id: Option, + timeout: Option, + cache_control: Option, + content_type: Option, + content_encoding: Option, + content_language: Option, + content_disposition: Option, + content_md5: Option, + context: Context, +} + +impl SetPropertiesBuilder { + pub(crate) fn new(blob_client: BlobClient) -> Self { + Self { + blob_client, + lease_id: None, + timeout: None, + cache_control: None, + content_type: None, + content_encoding: None, + content_language: None, + content_disposition: None, + content_md5: None, + context: Context::new(), + } + } + + pub fn set_from_blob_properties(self, blob_properties: BlobProperties) -> Self { + let mut s = self; + + if let Some(cc) = blob_properties.cache_control { + s = s.cache_control(cc); + } + if !blob_properties.content_type.is_empty() { + s = s.content_type(blob_properties.content_type); + } + if let Some(ce) = blob_properties.content_encoding { + s = s.content_encoding(ce); + } + if let Some(cl) = blob_properties.content_language { + s = s.content_language(cl); + } + if let Some(cd) = blob_properties.content_disposition { + s = s.content_disposition(cd); + } + if let Some(cmd5) = blob_properties.content_md5 { + s = s.content_md5(cmd5); + } + s + } + + setters! { + lease_id: LeaseId => Some(lease_id), + timeout: Timeout => Some(timeout), + cache_control: BlobCacheControl => Some(cache_control), + content_type: BlobContentType => Some(content_type), + content_encoding: BlobContentEncoding => Some(content_encoding), + content_language: BlobContentLanguage => Some(content_language), + content_disposition: BlobContentDisposition => Some(content_disposition), + content_md5: BlobContentMD5 => Some(content_md5), + context: Context => context, + } + + pub fn into_future(mut self) -> Response { + Box::pin(async move { + let mut url = self.blob_client.url_with_segments(None)?; + + url.query_pairs_mut().append_pair("comp", "properties"); + self.timeout.append_to_url_query(&mut url); + + let mut request = self.blob_client.prepare_request(url, Method::Put, None)?; + request.add_optional_header(&self.lease_id); + request.add_optional_header(&self.cache_control); + request.add_optional_header(&self.content_type); + request.add_optional_header(&self.content_encoding); + request.add_optional_header(&self.content_language); + request.add_optional_header(&self.content_disposition); + request.add_optional_header(&self.content_md5); + + let response = self + .blob_client + .send(&mut self.context, &mut request) + .await?; + response.headers().try_into() + }) + } +} + +#[derive(Debug, Clone)] +pub struct SetPropertiesResponse { + pub request_id: RequestId, + pub etag: String, + pub server: String, + pub date: DateTime, +} + +impl TryFrom<&Headers> for SetPropertiesResponse { + type Error = crate::Error; + + fn try_from(headers: &Headers) -> Result { + Ok(SetPropertiesResponse { + request_id: request_id_from_headers(headers)?, + etag: etag_from_headers(headers)?, + server: server_from_headers(headers)?.to_owned(), + date: date_from_headers(headers)?, + }) + } +} +pub type Response = futures::future::BoxFuture<'static, azure_core::Result>; + +#[cfg(feature = "into_future")] +impl std::future::IntoFuture for SetPropertiesBuilder { + type IntoFuture = Response; + type Output = ::Output; + fn into_future(self) -> Self::IntoFuture { + Self::into_future(self) + } +} diff --git a/sdk/storage_blobs/src/blob_cache_control.rs b/sdk/storage_blobs/src/blob_cache_control.rs new file mode 100644 index 0000000000..7edc2ab43b --- /dev/null +++ b/sdk/storage_blobs/src/blob_cache_control.rs @@ -0,0 +1,42 @@ +use azure_core::headers::{self, Header}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BlobCacheControl(std::borrow::Cow<'static, str>); + +impl BlobCacheControl { + pub const fn from_static(s: &'static str) -> Self { + Self(std::borrow::Cow::Borrowed(s)) + } + + pub fn as_str(&self) -> &str { + self.0.as_ref() + } +} + +impl From<&'static str> for BlobCacheControl { + fn from(s: &'static str) -> Self { + Self::from_static(s) + } +} + +impl From for BlobCacheControl { + fn from(s: String) -> Self { + Self(std::borrow::Cow::Owned(s)) + } +} + +impl From<&String> for BlobCacheControl { + fn from(s: &String) -> Self { + Self(std::borrow::Cow::Owned(s.clone())) + } +} + +impl Header for BlobCacheControl { + fn name(&self) -> headers::HeaderName { + azure_core::headers::BLOB_CACHE_CONTROL + } + + fn value(&self) -> headers::HeaderValue { + self.0.to_string().into() + } +} diff --git a/sdk/storage_blobs/src/blob_content_disposition.rs b/sdk/storage_blobs/src/blob_content_disposition.rs new file mode 100644 index 0000000000..7a28dbf442 --- /dev/null +++ b/sdk/storage_blobs/src/blob_content_disposition.rs @@ -0,0 +1,42 @@ +use azure_core::headers::{self, Header}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BlobContentDisposition(std::borrow::Cow<'static, str>); + +impl BlobContentDisposition { + pub const fn from_static(s: &'static str) -> Self { + Self(std::borrow::Cow::Borrowed(s)) + } + + pub fn as_str(&self) -> &str { + self.0.as_ref() + } +} + +impl From<&'static str> for BlobContentDisposition { + fn from(s: &'static str) -> Self { + Self::from_static(s) + } +} + +impl From for BlobContentDisposition { + fn from(s: String) -> Self { + Self(std::borrow::Cow::Owned(s)) + } +} + +impl From<&String> for BlobContentDisposition { + fn from(s: &String) -> Self { + Self(std::borrow::Cow::Owned(s.clone())) + } +} + +impl Header for BlobContentDisposition { + fn name(&self) -> headers::HeaderName { + "x-ms-blob-content-disposition".into() + } + + fn value(&self) -> headers::HeaderValue { + self.0.to_string().into() + } +} diff --git a/sdk/storage_blobs/src/blob_content_encoding.rs b/sdk/storage_blobs/src/blob_content_encoding.rs new file mode 100644 index 0000000000..d2476592ee --- /dev/null +++ b/sdk/storage_blobs/src/blob_content_encoding.rs @@ -0,0 +1,42 @@ +use azure_core::headers::{self, Header}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BlobContentEncoding(std::borrow::Cow<'static, str>); + +impl BlobContentEncoding { + pub const fn from_static(s: &'static str) -> Self { + Self(std::borrow::Cow::Borrowed(s)) + } + + pub fn as_str(&self) -> &str { + self.0.as_ref() + } +} + +impl From<&'static str> for BlobContentEncoding { + fn from(s: &'static str) -> Self { + Self::from_static(s) + } +} + +impl From for BlobContentEncoding { + fn from(s: String) -> Self { + Self(std::borrow::Cow::Owned(s)) + } +} + +impl From<&String> for BlobContentEncoding { + fn from(s: &String) -> Self { + Self(std::borrow::Cow::Owned(s.clone())) + } +} + +impl Header for BlobContentEncoding { + fn name(&self) -> headers::HeaderName { + "x-ms-blob-content-encoding".into() + } + + fn value(&self) -> headers::HeaderValue { + self.0.to_string().into() + } +} diff --git a/sdk/storage_blobs/src/blob_content_language.rs b/sdk/storage_blobs/src/blob_content_language.rs new file mode 100644 index 0000000000..78ddbeee1d --- /dev/null +++ b/sdk/storage_blobs/src/blob_content_language.rs @@ -0,0 +1,42 @@ +use azure_core::headers::{self, Header}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BlobContentLanguage(std::borrow::Cow<'static, str>); + +impl BlobContentLanguage { + pub const fn from_static(s: &'static str) -> Self { + Self(std::borrow::Cow::Borrowed(s)) + } + + pub fn as_str(&self) -> &str { + self.0.as_ref() + } +} + +impl From<&'static str> for BlobContentLanguage { + fn from(s: &'static str) -> Self { + Self::from_static(s) + } +} + +impl From for BlobContentLanguage { + fn from(s: String) -> Self { + Self(std::borrow::Cow::Owned(s)) + } +} + +impl From<&String> for BlobContentLanguage { + fn from(s: &String) -> Self { + Self(std::borrow::Cow::Owned(s.clone())) + } +} + +impl Header for BlobContentLanguage { + fn name(&self) -> headers::HeaderName { + "x-ms-blob-content-language".into() + } + + fn value(&self) -> headers::HeaderValue { + self.0.to_string().into() + } +} diff --git a/sdk/storage_blobs/src/blob_content_md5.rs b/sdk/storage_blobs/src/blob_content_md5.rs index 5f01bed9df..ea23ce283b 100644 --- a/sdk/storage_blobs/src/blob_content_md5.rs +++ b/sdk/storage_blobs/src/blob_content_md5.rs @@ -1,4 +1,5 @@ use azure_core::headers::{self, Header}; +use azure_storage::ConsistencyMD5; #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)] pub struct BlobContentMD5([u8; 16]); @@ -9,6 +10,12 @@ impl From for BlobContentMD5 { } } +impl From for BlobContentMD5 { + fn from(md5: ConsistencyMD5) -> Self { + BlobContentMD5(*md5.as_slice()) + } +} + impl Header for BlobContentMD5 { fn name(&self) -> headers::HeaderName { "x-ms-blob-content-md5".into() diff --git a/sdk/storage_blobs/src/blob_content_type.rs b/sdk/storage_blobs/src/blob_content_type.rs new file mode 100644 index 0000000000..da07d52da4 --- /dev/null +++ b/sdk/storage_blobs/src/blob_content_type.rs @@ -0,0 +1,42 @@ +use azure_core::headers::{self, Header}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BlobContentType(std::borrow::Cow<'static, str>); + +impl BlobContentType { + pub const fn from_static(s: &'static str) -> Self { + Self(std::borrow::Cow::Borrowed(s)) + } + + pub fn as_str(&self) -> &str { + self.0.as_ref() + } +} + +impl From<&'static str> for BlobContentType { + fn from(s: &'static str) -> Self { + Self::from_static(s) + } +} + +impl From for BlobContentType { + fn from(s: String) -> Self { + Self(std::borrow::Cow::Owned(s)) + } +} + +impl From<&String> for BlobContentType { + fn from(s: &String) -> Self { + Self(std::borrow::Cow::Owned(s.clone())) + } +} + +impl Header for BlobContentType { + fn name(&self) -> headers::HeaderName { + "x-ms-blob-content-type".into() + } + + fn value(&self) -> headers::HeaderValue { + self.0.to_string().into() + } +} diff --git a/sdk/storage_blobs/src/clients/blob_client.rs b/sdk/storage_blobs/src/clients/blob_client.rs index 430a733046..7c836d8b6f 100644 --- a/sdk/storage_blobs/src/clients/blob_client.rs +++ b/sdk/storage_blobs/src/clients/blob_client.rs @@ -93,6 +93,14 @@ impl BlobClient { GetPropertiesBuilder::new(self.clone()) } + /// Creates a builder for setting blob properties. + /// + /// Several properties are cleared from the blob if not passed. + /// Consider calling `set_from_blob_properties` with existing blob properties. + pub fn set_properties(&self) -> SetPropertiesBuilder { + SetPropertiesBuilder::new(self.clone()) + } + pub fn get_metadata(&self) -> GetMetadataBuilder { GetMetadataBuilder::new(self.clone()) } diff --git a/sdk/storage_blobs/src/lib.rs b/sdk/storage_blobs/src/lib.rs index 6f25141b1b..328350b5b0 100644 --- a/sdk/storage_blobs/src/lib.rs +++ b/sdk/storage_blobs/src/lib.rs @@ -11,7 +11,12 @@ mod access_tier; mod ba512_range; #[allow(clippy::module_inception)] pub mod blob; +mod blob_cache_control; +mod blob_content_disposition; +mod blob_content_encoding; +mod blob_content_language; mod blob_content_md5; +mod blob_content_type; mod block_id; mod clients; mod condition_append_position; @@ -27,7 +32,12 @@ mod version_id; pub use access_tier::AccessTier; use azure_core::{AppendToUrlQuery, Header}; pub use ba512_range::BA512Range; +pub use blob_cache_control::BlobCacheControl; +pub use blob_content_disposition::BlobContentDisposition; +pub use blob_content_encoding::BlobContentEncoding; +pub use blob_content_language::BlobContentLanguage; pub use blob_content_md5::BlobContentMD5; +pub use blob_content_type::BlobContentType; pub use block_id::BlockId; pub use condition_append_position::ConditionAppendPosition; pub use condition_max_size::ConditionMaxSize; diff --git a/sdk/storage_blobs/src/prelude.rs b/sdk/storage_blobs/src/prelude.rs index e5095b0f8b..aea9c8c1d9 100644 --- a/sdk/storage_blobs/src/prelude.rs +++ b/sdk/storage_blobs/src/prelude.rs @@ -6,7 +6,8 @@ pub use crate::{ AsContainerLeaseClient, BlobClient, BlobLeaseClient, BlobServiceClient, ContainerClient, ContainerLeaseClient, }, - AccessTier, BlobContentMD5, BlobVersioning, BlockId, ConditionAppendPosition, ConditionMaxSize, - DeleteSnapshotsMethod, Hash, RehydratePriority, Snapshot, VersionId, + AccessTier, BlobCacheControl, BlobContentDisposition, BlobContentEncoding, BlobContentLanguage, + BlobContentMD5, BlobContentType, BlobVersioning, BlockId, ConditionAppendPosition, + ConditionMaxSize, DeleteSnapshotsMethod, Hash, RehydratePriority, Snapshot, VersionId, }; pub use azure_storage::core::{StoredAccessPolicy, StoredAccessPolicyList};