uvm-install2 0.13.0

Install specified unity version.
Documentation
use crate::sys::shared::installer::*;
use std::ffi::OsStr;
use std::fs;
use std::fs::DirBuilder;
use std::fs::File;
use std::io;
use std::io::Read;
use std::io::Write;
use std::path::{Path,PathBuf};
use std::process::{Command, Stdio};
use log::*;

pub fn install_editor<P, D>(installer: P, destination: Option<D>) -> io::Result<()>
where
    P: AsRef<Path>,
    D: AsRef<Path>,
{
    let installer = installer.as_ref();
    let destination = destination.ok_or(io::Error::new(
        io::ErrorKind::InvalidInput,
        "Missing destination path",
    ))?;

    let destination = destination.as_ref();

    _install_editor(installer, destination).map_err(|err| {
        if destination.exists() {
            debug!(
                "Delete destination directory after failure {}",
                destination.display()
            );
            fs::remove_dir_all(destination).unwrap_or_else(|err| {
                error!("Failed to cleanup destination {}", destination.display());
                error!("{}", err);
            })
        }
        err
    })
}

fn _install_editor<P, D>(installer: P, destination: D) -> io::Result<()>
where
    P: AsRef<Path>,
    D: AsRef<Path>,
{
    let installer = installer.as_ref();
    let destination = destination.as_ref();
    debug!(
        "install editor to destination: {} with installer: {}",
        destination.display(),
        installer.display()
    );

    if installer.extension() == Some(OsStr::new("zip")) {
        debug!("install editor from zip archive");
        clean_directory(destination)?;
        deploy_zip(installer, destination)?;
        return Ok(());
    } else if installer.extension() == Some(OsStr::new("xz")) {
        debug!("install editor from xz archive");
        clean_directory(destination)?;
        untar(installer, destination)?;
        return Ok(());
    }

    Err(io::Error::new(
        io::ErrorKind::Other,
        format!(
            "Wrong installer. Expect .zip or .xz {:?} - {}",
            &installer.extension(),
            &installer.display()
        ),
    ))
}

pub fn install_module<P, D>(installer: P, destination: Option<D>) -> io::Result<()>
where
    P: AsRef<Path>,
    D: AsRef<Path>,
{
    _install_module(installer, destination)
}

fn _install_module<P, D>(installer: P, destination: Option<D>) -> io::Result<()>
where
    P: AsRef<Path>,
    D: AsRef<Path>,
{
    let installer = installer.as_ref();
    let destination = match destination {
        Some(ref d) => Some(d.as_ref()),
        _ => None,
    };

    debug!("install component {}", installer.display(),);
    if let Some(destination) = destination {
        debug!("to {}", destination.display());
    }

    match installer.extension() {
        Some(ext) if ext == "zip" => {
            let destination = destination.ok_or_else(|| {
                io::Error::new(
                    io::ErrorKind::InvalidInput,
                    "Missing destination path for zip intaller",
                )
            })?;

            install_module_from_zip(installer, destination).map_err(|err| {
                cleanup_directory_failable(destination);
                err
            })
        }

        Some(ext) if ext == "xz" => {
            let destination = destination.ok_or_else(|| {
                io::Error::new(
                    io::ErrorKind::InvalidInput,
                    "Missing destination path for xz intaller",
                )
            })?;

            install_module_from_xz(installer, destination).map_err(|err| {
                cleanup_directory_failable(destination);
                err
            })
        },

        Some(ext) if ext == "po" => {
            let destination = destination.ok_or_else(|| {
                io::Error::new(
                    io::ErrorKind::InvalidInput,
                    "Missing destination path for po module",
                )
            })?;
            install_po_file(installer, destination)
        }

        Some(ext) if ext == "pkg" => {
            let destination = destination.ok_or_else(|| {
                io::Error::new(
                    io::ErrorKind::InvalidInput,
                    "Missing destination path for pkg intaller",
                )
            })?;

            install_module_from_pkg(installer, destination).map_err(|err| {
                cleanup_directory_failable(destination);
                err
            })
        }

        _ => Err(io::Error::new(
            io::ErrorKind::Other,
            format!(
                "Wrong installer. Expect .pkg, .zip, .xz or .po {}",
                &installer.display()
            ),
        )),
    }
}

fn install_module_from_pkg<P, D>(installer: P, destination: D) -> io::Result<()>
where
    P: AsRef<Path>,
    D: AsRef<Path>,
{
    let installer = installer.as_ref();
    let destination = destination.as_ref();

    debug!(
        "install module from pkg {} to {}",
        installer.display(),
        destination.display()
    );

    let tmp_destination = destination.join("tmp");
    DirBuilder::new().recursive(true).create(&tmp_destination)?;

    xar_pkg(installer, &tmp_destination)?;
    untar_pkg(&tmp_destination, destination)?;
    cleanup_pkg(&tmp_destination)?;
    Ok(())
}

fn install_module_from_xz<P, D>(installer: P, destination: D) -> io::Result<()>
where
    P: AsRef<Path>,
    D: AsRef<Path>,
{
    let installer = installer.as_ref();
    let destination = destination.as_ref();

    debug!(
        "install module from xz archive {} to {}",
        installer.display(),
        destination.display()
    );

    let destination = if destination.ends_with("Editor/Data/PlaybackEngines") {
        destination
            .parent()
            .and_then(|f| f.parent())
            .and_then(|f| f.parent())
            .ok_or_else(|| {
                io::Error::new(
                    io::ErrorKind::NotFound,
                    format!(
                        "Can't determine destination for {} and destination {}",
                        &installer.display(),
                        destination.display()
                    ),
                )
            })?
    } else {
        destination
    };

    DirBuilder::new().recursive(true).create(destination)?;
    untar(installer, destination)?;
    return Ok(());
}

fn xar_pkg<P, D>(installer: P, destination: D) -> io::Result<()>
where
    P: AsRef<Path>,
    D: AsRef<Path>,
{
    let installer = installer.as_ref();
    let destination = destination.as_ref();

    debug!(
        "unpack installer {} to temp destination {}",
        installer.display(),
        destination.display()
    );

    let child = Command::new("7z")
        .arg("x")
        .arg("-y")
        .arg(format!("-o{}", destination.display()))
        .arg(installer)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()?;

    let output = child.wait_with_output()?;
    if !output.status.success() {
        return Err(io::Error::new(
            io::ErrorKind::Other,
            format!(
                "failed to extract installer:/n{}",
                String::from_utf8_lossy(&output.stderr)
            ),
        ));
    }
    Ok(())
}

fn untar_pkg<P, D>(base_payload_path: P, destination: D) -> io::Result<()>
where
    P: AsRef<Path>,
    D: AsRef<Path>,
{
    let base_payload_path = base_payload_path.as_ref();
    let destination = destination.as_ref();

    let payload = find_payload(base_payload_path)?;
    debug!("extract payload at {}", payload.display());

    let tar_child = if payload.file_name() == Some(OsStr::new("Payload~")) {
        let mut cpio = Command::new("cpio")
            .arg("-iu")
            .current_dir(destination)
            .stdin(Stdio::piped())
            .spawn()?;
        {
            let stdin = cpio.stdin.as_mut().expect("stdin");
            let mut file = File::open(payload)?;
            let mut buffer = Vec::new();
            file.read_to_end(&mut buffer)?;
            stdin.write(&buffer)?;
        }
        cpio
    } else {
        let mut gzip = Command::new("gzip")
            .arg("-dc")
            .arg(payload)
            .stdout(Stdio::piped())
            .spawn()?;

        let mut cpio = Command::new("cpio")
            .arg("-iu")
            .current_dir(destination)
            .stdin(Stdio::piped())
            .spawn()?;
        {
            let stdin = cpio.stdin.as_mut().expect("stdin");
            let gzip_std_out = gzip.stdout.as_mut().expect("stdout");
            let mut buffer = Vec::new();
            gzip_std_out.read_to_end(&mut buffer)?;
            stdin.write(&buffer)?;
        }
        cpio
    };

    let tar_output = tar_child.wait_with_output()?;
    if !tar_output.status.success() {
        return Err(io::Error::new(
            io::ErrorKind::Other,
            format!(
                "failed to untar payload:/n{}",
                String::from_utf8_lossy(&tar_output.stderr)
            ),
        ));
    }

    Ok(())
}

fn untar<P, D>(source: P, destination: D) -> io::Result<()>
where
    P: AsRef<Path>,
    D: AsRef<Path>,
{
    let source = source.as_ref();
    let destination = destination.as_ref();

    debug!(
        "untar archive {} to {}",
        source.display(),
        destination.display()
    );
    let tar_child = Command::new("tar")
        .arg("-C")
        .arg(destination)
        .arg("-amxf")
        .arg(source)
        .stdin(Stdio::null())
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .spawn()?;

    let tar_output = tar_child.wait_with_output()?;
    if !tar_output.status.success() {
        return Err(io::Error::new(
            io::ErrorKind::Other,
            format!(
                "failed to untar payload:/n{}",
                String::from_utf8_lossy(&tar_output.stderr)
            ),
        ));
    }

    Ok(())
}