use std::mem;
use std::sync::{Arc, Mutex};
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use std::process;
use rusoto_s3::*;
use rusoto_core::ByteStream;
use failure::Error;
use failure::ResultExt;
use flate2::Compression;
use flate2::write::GzEncoder;
use futures::prelude::*;
use tokio::prelude::*;
use crate::config::S3_BUCKET_NAME;
use crate::file_upload::FileUpload;
use crate::components::AppComponent;
use crate::config::Environment;
pub struct AppBuilder<'a> {
components: Vec<&'a AppComponent>,
files: Vec<FileUpload>,
}
impl<'a> AppBuilder<'a> {
pub fn new() -> Self {
Self {
files: Vec::new(),
components: Vec::new(),
}
}
pub fn component(&mut self, component: &'a dyn AppComponent) -> &mut Self {
self.components.push(component);
self
}
pub fn size(&self) -> usize {
let mut size = 0;
for FileUpload {bytes, ..} in self.files.iter() {
size += bytes.len();
}
size
}
pub fn build(&mut self, project_path: &PathBuf,
file_prefix: &String, env: &Environment) -> Result<(), Error> {
let release_flag = match env {
Environment::Production => " --release",
_ => ""
};
let mut build_proc = process::Command::new("sh")
.current_dir(project_path)
.arg("-c")
.arg(format!("cargo build --target wasm32-unknown-unknown{}", release_flag))
.stdout(process::Stdio::piped())
.spawn()
.context("Failed to spawn build")?;
let exit_code = build_proc.wait().context("Failed to wait for build")?;
if !exit_code.success() {
return Err(format_err!("Build failed, please check output above."))
}
for cmpnt in self.components.iter() {
let files = cmpnt.files(file_prefix).unwrap();
for f in files.into_iter() {
self.files.push(f);
};
};
Ok(())
}
pub fn upload(&self, client: S3Client) -> Result<(), Error> {
let failures = Arc::new(Mutex::new(0));
let fail_count = Arc::clone(&failures);
let work = stream::iter_ok(self.files.clone()).for_each(move |f| {
let fails_ref = Arc::clone(&failures);
let FileUpload {filename, mimetype, bytes} = f;
let mut gzip = GzEncoder::new(Vec::new(), Compression::default());
gzip.write_all(&bytes).expect("Failed to gzip encode bytes");
let compressed_bytes = gzip.finish().expect("Failed to gzip file");
let req = PutObjectRequest {
bucket: String::from(S3_BUCKET_NAME),
key: filename.to_owned(),
body: Some(ByteStream::from(compressed_bytes)),
content_type: Some(mimetype.to_owned()),
content_encoding: Some(String::from("gzip")),
..Default::default()
};
tokio::spawn(
client.put_object(req)
.map(move |_| () )
.map_err(move |e| {
let mut fails = *fails_ref.lock().unwrap();
let mut new_fails = fails + 1;
mem::swap(&mut fails, &mut new_fails);
println!("File upload error: {}", e);
})
)
});
tokio::run(work);
if *fail_count.lock().unwrap() > 0 {
Err(format_err!("Failed to upload app to S3"))
} else {
Ok(())
}
}
pub fn download(&self) -> Result<(), Error> {
for FileUpload {filename, mimetype: _, bytes} in self.files.iter() {
println!("Downloading file {}", filename);
let mut dir = PathBuf::from(filename);
dir.pop();
fs::create_dir_all(&dir).context("Failed to make directory").ok();
let mut f = fs::OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(PathBuf::from(filename))
.context("Unable to create or overwrite file")?;
f.write_all(bytes).context("Unable to write file")?;
};
Ok(())
}
}