Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new put and get APIs for objects #78

Merged
merged 1 commit into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ md5 = "0.7.0"
multimap = "0.10.0"
os_info = "3.7.0"
percent-encoding = "2.3.0"
rand = "0.8.5"
regex = "1.9.4"
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.105"
Expand All @@ -50,6 +49,7 @@ features = ["native-tls", "blocking", "rustls-tls", "stream"]

[dev-dependencies]
async-std = { version = "1.12.0", features = ["attributes", "tokio1"] }
rand = { version = "0.8.5", features = ["small_rng"] }

[[example]]
name = "file-uploader"
38 changes: 11 additions & 27 deletions examples/file-uploader.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use log::{error, info};
use minio::s3::args::{BucketExistsArgs, MakeBucketArgs, UploadObjectArgs};
use minio::s3::client::Client;
use log::info;
use minio::s3::args::{BucketExistsArgs, MakeBucketArgs};
use minio::s3::client::ClientBuilder;
use minio::s3::creds::StaticProvider;
use minio::s3::http::BaseUrl;
use std::path::Path;
Expand All @@ -9,8 +9,7 @@ use std::path::Path;
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
env_logger::init(); // Note: set environment variable RUST_LOG="INFO" to log info and higher

//let base_url = "https://play.min.io".parse::<BaseUrl>()?;
let base_url: BaseUrl = "http://192.168.178.227:9000".parse::<BaseUrl>()?;
let base_url = "https://play.min.io".parse::<BaseUrl>()?;

info!("Trying to connect to MinIO at: `{:?}`", base_url);

Expand All @@ -20,13 +19,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
None,
);

let client = Client::new(
base_url.clone(),
Some(Box::new(static_provider)),
None,
None,
)
.unwrap();
let client = ClientBuilder::new(base_url.clone())
.provider(Some(Box::new(static_provider)))
.build()?;

let bucket_name: &str = "asiatrip";

Expand All @@ -45,28 +40,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
}

// File we are going to upload to the bucket
let filename: &Path = Path::new("/home/user/Photos/asiaphotos.zip");
let filename: &Path = Path::new("/tmp/asiaphotos.zip");

// Name of the object that will be stored in the bucket
let object_name: &str = "asiaphotos-2015.zip";

info!("filename {}", &filename.to_str().unwrap());

// Check if the file exists
let file_exists: bool = filename.exists();
if !file_exists {
error!("File `{}` does not exist!", filename.display());
()
}

// Upload 'filename' as 'object_name' to bucket 'bucket_name'.
client
.upload_object(
&mut UploadObjectArgs::new(&bucket_name, &object_name, &filename.to_str().unwrap())
.unwrap(),
)
.await
.unwrap();
.put_object_from_file(bucket_name, object_name, filename)
.send()
.await?;

info!(
"file `{}` is successfully uploaded as object `{object_name}` to bucket `{bucket_name}`.",
Expand Down
10 changes: 5 additions & 5 deletions src/s3/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,17 +105,17 @@ fn calc_part_info(
) -> Result<(usize, i16), Error> {
if let Some(v) = part_size {
if v < MIN_PART_SIZE {
return Err(Error::InvalidMinPartSize(v));
return Err(Error::InvalidMinPartSize(v as u64));
}

if v > MAX_PART_SIZE {
return Err(Error::InvalidMaxPartSize(v));
return Err(Error::InvalidMaxPartSize(v as u64));
}
}

if let Some(v) = object_size {
if v > MAX_OBJECT_SIZE {
return Err(Error::InvalidObjectSize(v));
return Err(Error::InvalidObjectSize(v as u64));
}
} else {
if part_size.is_none() {
Expand All @@ -142,8 +142,8 @@ fn calc_part_info(

if part_count as u16 > MAX_MULTIPART_COUNT {
return Err(Error::InvalidPartCount(
object_size.unwrap(),
psize,
object_size.unwrap() as u64,
psize as u64,
MAX_MULTIPART_COUNT,
));
}
Expand Down
6 changes: 6 additions & 0 deletions src/s3/builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,15 @@
//! Argument builders for [minio::s3::client::Client](crate::s3::client::Client) APIs

mod buckets;
mod get_object;
mod list_objects;
mod listen_bucket_notification;
mod object_content;
mod put_object;

pub use buckets::*;
pub use get_object::*;
pub use list_objects::*;
pub use listen_bucket_notification::*;
pub use object_content::*;
pub use put_object::*;
220 changes: 220 additions & 0 deletions src/s3/builders/get_object.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
// MinIO Rust Library for Amazon S3 Compatible Cloud Storage
// Copyright 2023 MinIO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use http::Method;

use crate::s3::{
client::Client,
error::Error,
response::GetObjectResponse2,
sse::{Sse, SseCustomerKey},
types::{S3Api, S3Request, ToS3Request},
utils::{check_bucket_name, merge, to_http_header_value, Multimap, UtcTime},
};

#[derive(Debug, Clone, Default)]
pub struct GetObject {
client: Option<Client>,

extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>,
bucket: String,
object: String,
version_id: Option<String>,
offset: Option<u64>,
length: Option<u64>,
region: Option<String>,
ssec: Option<SseCustomerKey>,

// Conditionals
match_etag: Option<String>,
not_match_etag: Option<String>,
modified_since: Option<UtcTime>,
unmodified_since: Option<UtcTime>,
}

// builder interface
impl GetObject {
pub fn new(bucket: &str, object: &str) -> Self {
Self {
bucket: bucket.to_string(),
object: object.to_string(),
..Default::default()
}
}

pub fn client(mut self, client: &Client) -> Self {
self.client = Some(client.clone());
self
}

pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers;
self
}

pub fn extra_query_params(mut self, extra_query_params: Option<Multimap>) -> Self {
self.extra_query_params = extra_query_params;
self
}

pub fn version_id(mut self, version_id: Option<String>) -> Self {
self.version_id = version_id;
self
}

pub fn offset(mut self, offset: Option<u64>) -> Self {
self.offset = offset;
self
}

pub fn length(mut self, length: Option<u64>) -> Self {
self.length = length;
self
}

pub fn region(mut self, region: Option<String>) -> Self {
self.region = region;
self
}

pub fn ssec(mut self, ssec: Option<SseCustomerKey>) -> Self {
self.ssec = ssec;
self
}

pub fn match_etag(mut self, etag: Option<String>) -> Self {
self.match_etag = etag;
self
}

pub fn not_match_etag(mut self, etag: Option<String>) -> Self {
self.not_match_etag = etag;
self
}

pub fn modified_since(mut self, time: Option<UtcTime>) -> Self {
self.modified_since = time;
self
}

pub fn unmodified_since(mut self, time: Option<UtcTime>) -> Self {
self.unmodified_since = time;
self
}
}

// internal helpers
impl GetObject {
fn get_range_header_value(&self) -> Option<String> {
let (offset, length) = match self.length {
Some(_) => (Some(self.offset.unwrap_or(0_u64)), self.length),
None => (self.offset, None),
};

if let Some(o) = offset {
let mut range = String::new();
range.push_str("bytes=");
range.push_str(&o.to_string());
range.push('-');
if let Some(l) = length {
range.push_str(&(o + l - 1).to_string());
}
Some(range)
} else {
None
}
}

fn get_headers(&self) -> Multimap {
let mut headers = Multimap::new();

if let Some(val) = self.get_range_header_value() {
headers.insert(String::from("Range"), val);
}

if let Some(v) = &self.match_etag {
headers.insert(String::from("if-match"), v.to_string());
}

if let Some(v) = &self.not_match_etag {
headers.insert(String::from("if-none-match"), v.to_string());
}

if let Some(v) = self.modified_since {
headers.insert(String::from("if-modified-since"), to_http_header_value(v));
}

if let Some(v) = self.unmodified_since {
headers.insert(String::from("if-unmodified-since"), to_http_header_value(v));
}

if let Some(v) = &self.ssec {
merge(&mut headers, &v.headers());
}

headers
}
}

impl ToS3Request for GetObject {
fn to_s3request(&self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?;

if self.object.is_empty() {
return Err(Error::InvalidObjectName(String::from(
"object name cannot be empty",
)));
}

let client = self.client.clone().ok_or(Error::NoClientProvided)?;

if let Some(_) = &self.ssec {
if !client.is_secure() {
return Err(Error::SseTlsRequired(None));
}
}

let mut headers = Multimap::new();
if let Some(v) = &self.extra_headers {
merge(&mut headers, v);
}
merge(&mut headers, &self.get_headers());

let mut query_params = Multimap::new();
if let Some(v) = &self.extra_query_params {
merge(&mut query_params, v);
}
if let Some(v) = &self.version_id {
query_params.insert(String::from("versionId"), v.to_string());
}

let req = S3Request::new(
self.client.as_ref().ok_or(Error::NoClientProvided)?,
Method::GET,
)
.region(self.region.as_deref())
.bucket(Some(&self.bucket))
.object(Some(&self.object))
.query_params(query_params)
.headers(headers);

Ok(req)
}
}

impl S3Api for GetObject {
type S3Response = GetObjectResponse2;
}
Loading