mirror of
https://github.com/minio/minio-rs.git
synced 2026-01-22 15:42:10 +08:00
Add list_objects API (#2)
This commit is contained in:
parent
36a3b3c155
commit
124b254009
@ -62,9 +62,15 @@ fn main() {
|
|||||||
})
|
})
|
||||||
.map_err(|err| println!("{:?}", err));
|
.map_err(|err| println!("{:?}", err));
|
||||||
|
|
||||||
|
let list_objects_req = c
|
||||||
|
.list_objects(bucket, None, None, None, None)
|
||||||
|
.map(|l_obj_resp| println!("{:?} {:?}", l_obj_resp, l_obj_resp.object_infos.len()))
|
||||||
|
.map_err(|err| println!("{:?}", err));
|
||||||
|
|
||||||
del_req
|
del_req
|
||||||
.join5(make_bucket_req, region_req, buc_exists_req, download_req)
|
.join5(make_bucket_req, region_req, buc_exists_req, download_req)
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.then(|_| list_buckets_req)
|
.and_then(|_| list_buckets_req)
|
||||||
|
.then(|_| list_objects_req)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
46
src/minio.rs
46
src/minio.rs
@ -18,7 +18,7 @@ use std::string::String;
|
|||||||
use time;
|
use time;
|
||||||
use time::Tm;
|
use time::Tm;
|
||||||
|
|
||||||
use types::{Err, GetObjectResp, Region};
|
use types::{Err, GetObjectResp, ListObjectsResp, Region};
|
||||||
|
|
||||||
pub use types::BucketInfo;
|
pub use types::BucketInfo;
|
||||||
|
|
||||||
@ -312,6 +312,50 @@ impl Client {
|
|||||||
.and_then(|s| xml::parse_bucket_list(s))
|
.and_then(|s| xml::parse_bucket_list(s))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn list_objects(
|
||||||
|
&self,
|
||||||
|
b: &str,
|
||||||
|
prefix: Option<&str>,
|
||||||
|
marker: Option<&str>,
|
||||||
|
delimiter: Option<&str>,
|
||||||
|
max_keys: Option<i32>,
|
||||||
|
) -> impl Future<Item = ListObjectsResp, Error = Err> {
|
||||||
|
let mut qparams = HashMap::new();
|
||||||
|
qparams.insert("list-type".to_string(), Some("2".to_string()));
|
||||||
|
if let Some(d) = delimiter {
|
||||||
|
qparams.insert("delimiter".to_string(), Some(d.to_string()));
|
||||||
|
}
|
||||||
|
if let Some(m) = marker {
|
||||||
|
qparams.insert("marker".to_string(), Some(m.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(p) = prefix {
|
||||||
|
qparams.insert("prefix".to_string(), Some(p.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(mkeys) = max_keys {
|
||||||
|
qparams.insert("max-keys".to_string(), Some(mkeys.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let s3_req = S3Req {
|
||||||
|
method: Method::GET,
|
||||||
|
bucket: Some(b.to_string()),
|
||||||
|
object: None,
|
||||||
|
query: qparams,
|
||||||
|
headers: HeaderMap::new(),
|
||||||
|
body: Body::empty(),
|
||||||
|
ts: time::now_utc(),
|
||||||
|
};
|
||||||
|
self.signed_req_future(s3_req, Ok(Body::empty()))
|
||||||
|
.and_then(|resp| {
|
||||||
|
resp.into_body()
|
||||||
|
.concat2()
|
||||||
|
.map_err(|err| Err::HyperErr(err))
|
||||||
|
.and_then(move |chunk| b2s(chunk.into_bytes()))
|
||||||
|
.and_then(|s| xml::parse_list_objects(s))
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_req_future(
|
fn run_req_future(
|
||||||
|
|||||||
@ -6,6 +6,7 @@ use hyper::header::{
|
|||||||
};
|
};
|
||||||
use hyper::{body::Body, Response};
|
use hyper::{body::Body, Response};
|
||||||
use roxmltree;
|
use roxmltree;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::string;
|
use std::string;
|
||||||
use time::{strptime, Tm};
|
use time::{strptime, Tm};
|
||||||
|
|
||||||
@ -35,7 +36,9 @@ pub enum Err {
|
|||||||
HyperErr(hyper::Error),
|
HyperErr(hyper::Error),
|
||||||
FailStatusCodeErr(hyper::StatusCode, Bytes),
|
FailStatusCodeErr(hyper::StatusCode, Bytes),
|
||||||
Utf8DecodingErr(string::FromUtf8Error),
|
Utf8DecodingErr(string::FromUtf8Error),
|
||||||
XmlParseErr(roxmltree::Error),
|
XmlDocParseErr(roxmltree::Error),
|
||||||
|
XmlElemMissing(String),
|
||||||
|
XmlElemParseErr(String),
|
||||||
InvalidXmlResponseErr(String),
|
InvalidXmlResponseErr(String),
|
||||||
MissingRequiredParams,
|
MissingRequiredParams,
|
||||||
RawSvcErr(hyper::StatusCode, Response<Body>),
|
RawSvcErr(hyper::StatusCode, Response<Body>),
|
||||||
@ -101,6 +104,11 @@ fn extract_user_meta(h: &HeaderMap) -> Vec<(String, String)> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_aws_time(time_str: &str) -> Result<Tm, Err> {
|
||||||
|
strptime(time_str, "%Y-%m-%dT%H:%M:%S.%Z")
|
||||||
|
.map_err(|err| Err::InvalidTmFmt(format!("{:?}", err)))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BucketInfo {
|
pub struct BucketInfo {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@ -108,14 +116,56 @@ pub struct BucketInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BucketInfo {
|
impl BucketInfo {
|
||||||
pub fn new(name: &str, time_str: &str) -> Result<BucketInfo, String> {
|
pub fn new(name: &str, time_str: &str) -> Result<BucketInfo, Err> {
|
||||||
strptime(time_str, "%Y-%m-%dT%H:%M:%S.%Z")
|
parse_aws_time(time_str).and_then(|ctime| {
|
||||||
.and_then(|ctime| {
|
Ok(BucketInfo {
|
||||||
Ok(BucketInfo {
|
name: name.to_string(),
|
||||||
name: name.to_string(),
|
created_time: ctime,
|
||||||
created_time: ctime,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.or_else(|err| Err(format!("{:?}", err)))
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ObjectInfo {
|
||||||
|
pub name: String,
|
||||||
|
pub modified_time: Tm,
|
||||||
|
pub etag: String,
|
||||||
|
pub size: i64,
|
||||||
|
pub storage_class: String,
|
||||||
|
pub metadata: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectInfo {
|
||||||
|
pub fn new(
|
||||||
|
name: &str,
|
||||||
|
mtime_str: &str,
|
||||||
|
etag: &str,
|
||||||
|
size: i64,
|
||||||
|
storage_class: &str,
|
||||||
|
metadata: HashMap<String, String>,
|
||||||
|
) -> Result<ObjectInfo, Err> {
|
||||||
|
parse_aws_time(mtime_str).and_then(|mtime| {
|
||||||
|
Ok(ObjectInfo {
|
||||||
|
name: name.to_string(),
|
||||||
|
modified_time: mtime,
|
||||||
|
etag: etag.to_string(),
|
||||||
|
size: size,
|
||||||
|
storage_class: storage_class.to_string(),
|
||||||
|
metadata: metadata,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ListObjectsResp {
|
||||||
|
pub bucket_name: String,
|
||||||
|
pub prefix: String,
|
||||||
|
pub max_keys: i32,
|
||||||
|
pub key_count: i32,
|
||||||
|
pub is_truncated: bool,
|
||||||
|
pub object_infos: Vec<ObjectInfo>,
|
||||||
|
pub common_prefixes: Vec<String>,
|
||||||
|
pub next_continuation_token: String,
|
||||||
|
}
|
||||||
|
|||||||
101
src/minio/xml.rs
101
src/minio/xml.rs
@ -1,7 +1,9 @@
|
|||||||
use crate::minio::types::{BucketInfo, Err, Region};
|
use crate::minio::types::{BucketInfo, Err, ListObjectsResp, ObjectInfo, Region};
|
||||||
use crate::minio::woxml;
|
use crate::minio::woxml;
|
||||||
use hyper::body::Body;
|
use hyper::body::Body;
|
||||||
use roxmltree;
|
use roxmltree;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
pub fn parse_bucket_location(s: String) -> Result<Region, Err> {
|
pub fn parse_bucket_location(s: String) -> Result<Region, Err> {
|
||||||
let res = roxmltree::Document::parse(&s);
|
let res = roxmltree::Document::parse(&s);
|
||||||
@ -14,7 +16,7 @@ pub fn parse_bucket_location(s: String) -> Result<Region, Err> {
|
|||||||
Ok(Region::empty())
|
Ok(Region::empty())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => Err(Err::XmlParseErr(e)),
|
Err(e) => Err(Err::XmlDocParseErr(e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +49,15 @@ pub fn parse_bucket_list(s: String) -> Result<Vec<BucketInfo>, Err> {
|
|||||||
}
|
}
|
||||||
Ok(bucket_infos)
|
Ok(bucket_infos)
|
||||||
}
|
}
|
||||||
Err(err) => Err(Err::XmlParseErr(err)),
|
Err(err) => Err(Err::XmlDocParseErr(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_list_objects(s: String) -> Result<ListObjectsResp, Err> {
|
||||||
|
let doc_res = roxmltree::Document::parse(&s);
|
||||||
|
match doc_res {
|
||||||
|
Ok(doc) => parse_list_objects_result(doc),
|
||||||
|
Err(err) => panic!(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,3 +73,88 @@ pub fn get_mk_bucket_body() -> Result<Body, Err> {
|
|||||||
.or_else(|err| Err(Err::XmlWriteErr(err.to_string())))?;
|
.or_else(|err| Err(Err::XmlWriteErr(err.to_string())))?;
|
||||||
Ok(Body::from(xml_bytes))
|
Ok(Body::from(xml_bytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_child_node(node: roxmltree::Node, tag_name: &str) -> Option<String> {
|
||||||
|
node.children()
|
||||||
|
.find(|node| node.has_tag_name(tag_name))
|
||||||
|
.and_then(|node| node.text())
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
// gets text value inside given tag or return default
|
||||||
|
fn get_child_node_or(node: roxmltree::Node, tag_name: &str, default: &str) -> String {
|
||||||
|
get_child_node(node, tag_name).unwrap_or(default.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_child_content<T>(node: roxmltree::Node, tag: &str) -> Result<T, Err>
|
||||||
|
where
|
||||||
|
T: FromStr,
|
||||||
|
{
|
||||||
|
let content = get_child_node(node, tag).ok_or(Err::XmlElemMissing(format!("{:?}", tag)))?;
|
||||||
|
str::parse::<T>(content.as_str()).map_err(|_| Err::XmlElemParseErr(format!("{}", tag)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_tag_content<T>(node: roxmltree::Node) -> Result<T, Err>
|
||||||
|
where
|
||||||
|
T: FromStr,
|
||||||
|
{
|
||||||
|
let content = node
|
||||||
|
.text()
|
||||||
|
.ok_or(Err::XmlElemMissing(format!("{:?}", node.tag_name())))?;
|
||||||
|
str::parse::<T>(content).map_err(|_| Err::XmlElemParseErr(format!("{:?}", node.tag_name())))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_object_infos(node: roxmltree::Node) -> Result<Vec<ObjectInfo>, Err> {
|
||||||
|
let mut object_infos: Vec<ObjectInfo> = Vec::new();
|
||||||
|
let contents_nodes = node
|
||||||
|
.descendants()
|
||||||
|
.filter(|node| node.has_tag_name("Contents"));
|
||||||
|
for node in contents_nodes {
|
||||||
|
let keys = node.children().filter(|node| node.has_tag_name("Key"));
|
||||||
|
let mtimes = node
|
||||||
|
.children()
|
||||||
|
.filter(|node| node.has_tag_name("LastModified"));
|
||||||
|
let etags = node.children().filter(|node| node.has_tag_name("ETag"));
|
||||||
|
let sizes = node.children().filter(|node| node.has_tag_name("Size"));
|
||||||
|
let storage_classes = node
|
||||||
|
.children()
|
||||||
|
.filter(|node| node.has_tag_name("StorageClass"));
|
||||||
|
for (key, (mtime, (etag, (size, storage_class)))) in
|
||||||
|
keys.zip(mtimes.zip(etags.zip(sizes.zip(storage_classes))))
|
||||||
|
{
|
||||||
|
let sz: i64 = parse_tag_content(size)?;
|
||||||
|
let object_info = ObjectInfo::new(
|
||||||
|
key.text().unwrap(),
|
||||||
|
mtime.text().unwrap(),
|
||||||
|
etag.text().unwrap(),
|
||||||
|
sz,
|
||||||
|
storage_class.text().unwrap(),
|
||||||
|
HashMap::new(),
|
||||||
|
)?;
|
||||||
|
object_infos.push(object_info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(object_infos)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_list_objects_result(doc: roxmltree::Document) -> Result<ListObjectsResp, Err> {
|
||||||
|
let root = doc.root_element();
|
||||||
|
let bucket_name =
|
||||||
|
get_child_node(root, "Name").ok_or(Err::XmlElemMissing("Name".to_string()))?;
|
||||||
|
let prefix = get_child_node_or(root, "Prefix", "");
|
||||||
|
let key_count: i32 = parse_child_content(root, "KeyCount")?;
|
||||||
|
let max_keys: i32 = parse_child_content(root, "MaxKeys")?;
|
||||||
|
let is_truncated: bool = parse_child_content(root, "IsTruncated")?;
|
||||||
|
let object_infos = parse_object_infos(root)?;
|
||||||
|
|
||||||
|
Ok(ListObjectsResp {
|
||||||
|
bucket_name: bucket_name,
|
||||||
|
prefix: prefix,
|
||||||
|
max_keys: max_keys,
|
||||||
|
key_count: key_count,
|
||||||
|
is_truncated: is_truncated,
|
||||||
|
next_continuation_token: "".to_string(),
|
||||||
|
common_prefixes: Vec::new(),
|
||||||
|
object_infos: object_infos,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user