use std::collections::HashMap;
use std::string::String;
use std::path::Path;
use std::io::Read;
use std::io::Write;
use std::fs::File;
use std::io;
use zip::result::ZipResult;
use zip::result::ZipError;
use zip::ZipArchive;
use reqwest::blocking::Client;
use reqwest::header::HeaderMap;
use reqwest::header::HeaderValue;
use reqwest::header::HeaderName;
use crate::interface::SimpleInterpreter;
use crate::cache::Cache;
use crate::error::Error;
fn save_file<F: FnMut (f32) -> bool>(mut update: F, stream: &mut dyn Read, outpath: &Path, max: Option<u64>) -> io::Result<()>
{
let mut f = File::create(outpath)?;
let mut buf: [u8; 8192] = [0; 8192];
let mut cur: u64 = 0;
loop
{
let len = stream.read(&mut buf)?;
if len <= 0
{
break;
}
f.write(&buf[0..len])?;
if let Some(v) = max
{
cur += len as u64;
if !update((cur as f64 / v as f64) as f32)
{
return Ok(());
}
}
}
return Ok(());
}
fn zip_extract<F: FnMut (f32) -> bool>(mut update: F, infile: &Path, outpath: &Path) -> ZipResult<()>
{
let f = match File::open(&infile)
{
Ok(v) => v,
Err(e) => return Err(ZipError::Io(e))
};
let mut f = ZipArchive::new(f)?;
for i in 0..f.len()
{
if !update((i as f64 / f.len() as f64) as f32)
{
return Ok(());
}
let mut file = f.by_index(i)?;
let out = match file.enclosed_name() {
Some(path) => path.to_owned(),
None => continue,
};
let path = outpath.join(out);
if (&*file.name()).ends_with('/')
{
if let Err(e) = std::fs::create_dir_all(&path)
{
return Err(ZipError::Io(e));
}
continue;
}
if let Some(p) = path.parent()
{
if !p.exists()
{
if let Err(e) = std::fs::create_dir_all(&p)
{
return Err(ZipError::Io(e));
}
}
}
let mut outfile = match File::create(&path)
{
Ok(v) => v,
Err(e) => return Err(ZipError::Io(e))
};
if let Err(e) = io::copy(&mut file, &mut outfile)
{
return Err(ZipError::Io(e));
}
}
return Ok(());
}
pub fn buf_to_file(path: &Path, bytes: &[u8]) -> io::Result<()>
{
let mut f = File::create(path)?;
f.write(bytes)?;
return Ok(());
}
pub fn download_file<TError: Error>(interpreter: &mut dyn SimpleInterpreter<TError::ErrorType>, client: &mut Client, cache: &mut Cache<TError>, resid: usize, filename: String, url: String, headers: HashMap<String, String>) -> Result<(), TError::ErrorType>
{
interpreter.begin_substep(&format!("Downloading {}...", filename))?;
let mut au = HeaderMap::new();
for (k, v) in headers
{
let computed = cache.parse_string(&v)?;
match HeaderValue::from_str(&computed.to_string_lossy()) {
Ok(vv) =>
{
match HeaderName::from_bytes(k.as_ref())
{
Ok(vvv) =>
{
au.insert(vvv, vv);
}
Err(e) => return Err(TError::network_invalid_header_name(e))
};
},
Err(e) => return Err(TError::network_invalid_header_value(e))
};
}
match client.get(url).headers(au).send()
{
Ok(mut v) =>
{
let useless = v.content_length();
let outfile = cache.get_path(Path::new(&filename))?;
let mut err = None;
let updater = |progress|
{
if let Err(e) = interpreter.update_substep(progress)
{
err = Some(e);
return false;
}
return true;
};
if let Err(e) = save_file(updater, &mut v, &outfile, useless)
{
return Err(TError::io(e));
}
if let Some(e) = err
{
return Err(e);
}
interpreter.end_substep()?;
cache.insert(resid, outfile);
},
Err(e) => return Err(TError::network(e))
};
return Ok(());
}
pub fn unpack_cached<TError: Error>(interpreter: &mut dyn SimpleInterpreter<TError::ErrorType>, cache: &mut Cache<TError>, resid: usize, path: String) -> Result<(), TError::ErrorType>
{
let computed = cache.parse_string(&path)?;
interpreter.begin_substep(&format!("Unpacking {:?}...", computed))?;
if !computed.to_string_lossy().ends_with(".zip")
{
return Err(TError::generic(String::from("Only ZIP archives are supported")));
}
let mut computed1 = computed.clone();
computed1.push(".d");
let inpath = Path::new(&computed);
let outpath = Path::new(&computed1);
if let Err(e) = std::fs::create_dir(&outpath)
{
return Err(TError::io(e));
}
let mut err = None;
let updater = |progress|
{
if let Err(e) = interpreter.update_substep(progress)
{
err = Some(e);
return false;
}
return true;
};
if let Err(e) = zip_extract(updater, &inpath, &outpath)
{
return Err(TError::zip(e));
}
if let Some(e) = err
{
return Err(e);
}
interpreter.end_substep()?;
cache.insert(resid, outpath);
return Ok(());
}
pub fn extract_resource<TError: Error>(cache: &mut Cache<TError>, resources: &HashMap<&'static str, &'static [u8]>, resid: usize, path: &'static str) -> Result<(), TError::ErrorType>
{
let v = match resources.get(path)
{
Some(v) => v,
None => return Err(TError::unknown_resource_path(path))
};
let name =
{
if let Some(id) = path.rfind('/')
{
&path[id + 1..]
}
else
{
path
}
};
let outpath = cache.get_path(Path::new(name))?;
if let Err(e) = buf_to_file(&outpath, v)
{
return Err(TError::io(e));
}
cache.insert(resid, outpath);
return Ok(());
}
pub fn user_input<TError: Error>(interpreter: &mut dyn SimpleInterpreter<TError::ErrorType>, cache: &mut Cache<TError>, props: &HashMap<String, String>, resid: usize, prop: String, message: String) -> Result<(), TError::ErrorType>
{
if let Some(val) = props.get(&prop)
{
cache.insert(resid, val);
}
else
{
let line = interpreter.read_user_input_text(&message)?;
cache.insert(resid, line);
}
return Ok(());
}