extern crate toml;
#[macro_use]
extern crate serde_derive;
extern crate interactor;
extern crate reqwest;
extern crate hyper;
extern crate chrono;
extern crate hmac;
extern crate sha2;
extern crate base64;
extern crate crypto;
extern crate rustc_serialize;
extern crate url;
#[macro_use]
extern crate log;
extern crate md5;
extern crate hmacsha1;
extern crate serde_json;
extern crate regex;
extern crate quick_xml;
extern crate colored;
mod handler;
use std::io;
use std::io::{Read, Write, BufReader, BufRead};
use std::fs::{File, OpenOptions};
use std::str;
use std::str::FromStr;
use std::io::stdout;
use log::{Record, Level, Metadata, LevelFilter};
use colored::*;
static MY_LOGGER: MyLogger = MyLogger;
struct MyLogger;
impl log::Log for MyLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= Level::Trace
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
match record.level() {
log::Level::Error => println!("{} - {}", "ERROR".red().bold(), record.args()),
log::Level::Warn => println!("{} - {}", "WARN".red(), record.args()),
log::Level::Info => println!("{} - {}", "INFO".cyan(), record.args()),
log::Level::Debug => println!("{} - {}", "DEBUG".blue().bold(), record.args()),
log::Level::Trace => println!("{} - {}", "TRACE".blue(), record.args())
}
}
}
fn flush(&self) {}
}
#[derive(Debug, Clone, Deserialize)]
pub struct CredentialConfig {
host: String,
user: Option<String>,
access_key: String,
secrete_key: String,
region: Option<String>,
s3_type: Option<String>
}
#[derive(Debug, Deserialize)]
struct Config {
credential: Option<Vec<CredentialConfig>>
}
impl <'a> Config {
fn gen_selecitons(&'a self) -> Vec<String>{
let mut display_list = Vec::new();
let credential = &self.credential.clone().unwrap();
for cre in credential.into_iter(){
let c = cre.clone();
let mut option = String::from(format!("[{}] {} ({}) {} ({})",
c.s3_type.unwrap_or(String::from("aws")),
c.host,
c.region.unwrap_or(String::from("us-east-1")),
c.user.unwrap_or(String::from("user")),
c.access_key));
display_list.push(option);
}
display_list
}
}
fn read_parse<T>(tty: &mut File, prompt: &str, min: T, max: T)
-> io::Result<T> where T: FromStr + Ord {
try!(tty.write_all(prompt.as_bytes()));
let mut reader = io::BufReader::new(tty);
let mut result = String::new();
try!(reader.read_line(&mut result));
match result.replace("\n", "").parse::<T>() {
Ok(x) => if x >= min && x <= max { Ok(x) }
else { read_parse(reader.into_inner(), prompt, min, max) },
_ => read_parse(reader.into_inner(), prompt, min, max)
}
}
fn my_pick_from_list_internal<T: AsRef<str>>(items: &[T], prompt: &str) -> io::Result<usize> {
let mut tty = try!(OpenOptions::new().read(true).write(true).open("/dev/tty"));
let pad_len = ((items.len() as f32).log10().floor() + 1.0) as usize;
for (i, item) in items.iter().enumerate() {
try!(tty.write_all(format!("{1:0$}. {2}\n", pad_len, i + 1, item.as_ref().replace("\n", "")).as_bytes()))
}
let idx = try!(read_parse::<usize>(&mut tty, prompt, 1, items.len())) - 1;
Ok(idx)
}
fn main() {
log::set_logger(&MY_LOGGER).unwrap();
log::set_max_level(LevelFilter::Error);
let mut s3rscfg = std::env::home_dir().unwrap();
s3rscfg.push(".s3rs.toml");
let mut f;
if s3rscfg.exists() {
f = File::open(s3rscfg).expect("s3rs config file not found");
} else {
f = File::create(s3rscfg).expect("Can not write s3rs config file");
let _ = f.write_all(
b"[[credential]]\ns3_type = \"aws\"\nhost = \"s3.us-east-1.amazonaws.com\"\nuser = \"admin\"\naccess_key = \"L2D11MY86GEVA6I4DX2S\"\nsecrete_key = \"MBCqT90XMUaBcWd1mcUjPPLdFuNZndiBk0amnVVg\"\nregion = \"us-east-1\""
);
print!("Config file {} is created in your home folder, please edit it and add your credentials", ".s3rs.toml".bold());
return
}
let mut config_contents = String::new();
f.read_to_string(&mut config_contents).expect("s3rs config is not readable");
let config:Config = toml::from_str(config_contents.as_str()).unwrap();
let config_option: Vec<String> = config.gen_selecitons();
let mut chosen_int = my_pick_from_list_internal(&config_option, "Selection: ").unwrap();
let config_list = config.credential.unwrap();
let mut handler = handler::Handler::init_from_config(&config_list[chosen_int]);
let mut login_user = config_list[chosen_int].user.clone().unwrap_or("unknown".to_string());
println!("enter command, help for usage or exit for quit");
let mut command = String::new();
fn change_log_type(command: &str) {
if command.ends_with("trace") {
log::set_max_level(LevelFilter::Trace);
println!("set up log level trace");
} else if command.ends_with("debug") {
log::set_max_level(LevelFilter::Debug);
println!("set up log level debug");
} else if command.ends_with("info") {
log::set_max_level(LevelFilter::Info);
println!("set up log level info");
} else if command.ends_with("error") {
log::set_max_level(LevelFilter::Error);
println!("set up log level error");
}else{
println!("usage: log [trace/debug/info/error]");
}
}
fn print_if_error(result: Result<(),&str>) {
match result{
Err(e) => println!("{}", e),
Ok(_) => {}
};
}
while command != "exit" && command != "quit" {
command = match OpenOptions::new().read(true).write(true).open("/dev/tty") {
Ok(mut tty) => {
tty.flush().expect("Could not open tty");
tty.write_all(
format!("{} {} {} ", "s3rs".green(), login_user.cyan(), ">".green()).as_bytes());
let reader = BufReader::new(&tty);
let mut command_iter = reader.lines().map(|l| l.unwrap());
command_iter.next().unwrap_or("logout".to_string())
},
Err(e) => {println!("{:?}", e); "quit".to_string()}
};
debug!("===== do command: {} =====", command);
if command.starts_with("la"){
print_if_error(handler.la());
} else if command.starts_with("ls"){
print_if_error(handler.ls(command.split_whitespace().nth(1)));
} else if command.starts_with("put"){
match handler.put(command.split_whitespace().nth(1).unwrap_or(""), command.split_whitespace().nth(2).unwrap_or("")) {
Err(e) => println!("{}", e),
Ok(_) => println!("upload completed")
};
} else if command.starts_with("get"){
match handler.get(command.split_whitespace().nth(1).unwrap_or(""), command.split_whitespace().nth(2)){
Err(e) => println!("{}", e),
Ok(_) => println!("download completed")
};
} else if command.starts_with("cat"){ print_if_error(handler.cat(command.split_whitespace().nth(1).unwrap_or("")));
} else if command.starts_with("del"){
match handler.del(command.split_whitespace().nth(1).unwrap_or("")){
Err(e) => println!("{}", e),
Ok(_) => println!("deletion completed")
}
} else if command.starts_with("tag"){
let mut iter = command.split_whitespace();
let action = iter.nth(1).unwrap_or("");
let target = iter.nth(0).unwrap_or("");
let mut tags = Vec::new();
loop {
match iter.next() {
Some(kv_pair) => {
match kv_pair.find('=') {
Some(_) => {
tags.push(
(kv_pair.split('=').nth(0).unwrap(),
kv_pair.split('=').nth(1).unwrap()))},
None => {tags.push((&kv_pair, ""))}
}
}
None =>{break;}
};
}
match action {
"add"|"put" => match handler.add_tag(target, &tags){
Err(e) => println!("{}", e),
Ok(_) => println!("tag completed")
},
"del"|"rm" => match handler.del_tag(target){
Err(e) => println!("{}", e),
Ok(_) => println!("tag removed")
},
_ => println!("only support these tag actions: add, put, del, rm")
}
} else if command.starts_with("mb"){
print_if_error(handler.mb(command.split_whitespace().nth(1).unwrap_or("")));
} else if command.starts_with("rb"){
print_if_error(handler.rb(command.split_whitespace().nth(1).unwrap_or("")));
} else if command.starts_with("/"){
match handler.url_command(&command){
Err(e) => println!("{}", e),
Ok(_) => {}
};
} else if command.starts_with("s3_type"){
handler.change_s3_type(&command);
} else if command.starts_with("auth_type"){
handler.change_auth_type(&command);
} else if command.starts_with("format"){
handler.change_format_type(&command);
} else if command.starts_with("url_style"){
handler.change_url_style(&command);
} else if command.starts_with("logout"){
println!("");
chosen_int = my_pick_from_list_internal(&config_option, "Selection: ").unwrap();
handler = handler::Handler::init_from_config(&config_list[chosen_int]);
login_user = config_list[chosen_int].user.clone().unwrap_or(" ".to_string());
} else if command.starts_with("log"){
change_log_type(&command);
} else if command.starts_with("exit") || command.starts_with("quit") {
println!("Thanks for using, cya~");
} else if command.starts_with("help"){
println!(r#"
USAGE:
{0}
list all objects
{1}
list all buckets
{1} {2}
list all objects of the bucket
{3} {2}
create bucket
{4} {2}
delete bucket
{5} {6} s3://{2}/{7}
upload the file with specify object name
{5} {6} s3://{2}
upload the file as the same file name
{5} test s3://{2}/{7}
upload a small test text file with specify object name
{8} s3://{2}/{7} {6}
download the object
{8} s3://{2}/{7}
download the object to current folder
{9} s3://{2}/{7}
display the object content
{10} s3://{2}/{7}
delete the object
{29} {33}/{5} s3://{2}/{7} {30}={31} ...
add tags to the object
{29} {10}/{4} s3://{2}/{7}
remove tags from the object
/{11}?{12}
get uri command
{13}
show this usage
{14} {32}/{15}/{16}/{17}/{18}
change the log level
{32} for every thing
{15} for request auth detail
{16} for request header, status code, raw body
{17} for request http response
{18} is default
{19} {20}/{21}
change the auth type and format for different S3 service
{22} {23}/{24}
change the auth type
{25} {26}/{27}
change the request format
{28}
quit the programe
{34} / {35}
logout and reselect account
"#,
"la".bold(), "ls".bold(), "<bucket>".cyan(), "mb".bold(), "rm".bold(),
"put".bold(), "<file>".cyan(), "<object>".cyan(), "get".bold(), "cat".bold(),
"del".bold(), "<uri>".cyan(), "<query string>".cyan(), "help".bold(), "log".bold(),
"trace".blue(), "debug".blue(), "info".blue(), "error".blue(), "s3_type".bold(),
"aws".blue(), "ceph".blue(), "auth_type".bold(), "aws2".blue(), "aws4".blue(),
"format".bold(), "xml".blue(), "json".blue(), "exit".bold(), "tag".bold(),
"<key>".cyan(), "<value>".cyan(), "trace".blue(), "add".bold(), "logout".bold(),
"Ctrl + D".bold() );
} else {
println!("command {} not found, help for usage or exit for quit", command);
}
println!("");
stdout().flush().expect("Could not flush stdout");
}
}