uvm-install2 0.13.0

Install specified unity version.
Documentation
use std::fs;
use crate::*;
use std::fs::DirBuilder;
use std::path::Path;
use std::process::{Command, Stdio};
use thiserror_context::Context;
use crate::install::installer::{Installer, InstallerWithDestination};
use crate::install::{InstallHandler, UnityEditor, UnityModule};
use crate::install::error::InstallerErrorInner::{InstallationFailed, InstallerCreateFailed};
use crate::install::error::InstallerResult;

pub struct Xz;
pub type EditorXzInstaller = Installer<UnityEditor, Xz, InstallerWithDestination>;
pub type ModuleXzInstaller = Installer<UnityModule, Xz, InstallerWithDestination>;

impl<V, I> Installer<V, Xz, I> {
    fn untar<P, D>(&self, source: P, destination: D) -> InstallerResult<()>
    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(InstallationFailed(
                "failed to untar payload:/n{}".to_string(),
                String::from_utf8_lossy(&tar_output.stderr).to_string()
            )
            .into());
        }
        Ok(())
    }
}

impl InstallHandler for EditorXzInstaller {
    fn before_install(&self) -> InstallerResult<()> {
        self.clean_directory(self.destination())
    }

    fn install_handler(&self) -> InstallerResult<()> {
        debug!("install editor from xz archive");
        self.untar(self.installer(), self.destination())
    }

    fn installer(&self) -> &Path {
        self.installer()
    }
}

impl EditorXzInstaller {

}

impl InstallHandler for ModuleXzInstaller {
    fn install_handler(&self) -> InstallerResult<()> {
        debug!(
            "install module from xz archive {} to {}",
            self.installer().display(),
            self.destination().display()
        );

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

        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)?;
        self.untar(installer, destination)
    }

    fn after_install(&self) -> InstallerResult<()> {
        if let Some((from, to)) = &self.rename() {
            uvm_move_dir::move_dir(from, to)?;
        }
        Ok(())
    }

    fn installer(&self) -> &Path {
        self.installer()
    }

    fn error_handler(&self) {
        self.cleanup_directory_failable(&self.destination());
    }

    fn before_install(&self) -> InstallerResult<()> {
        if self.destination().exists() {
            if self.destination().is_dir() {
                info!("Destination directory {} already exists, removing it", self.destination().display());
                fs::remove_dir_all(self.destination()).context("failed to remove the existing destination directory")?;
            } else {
                info!("Destination file {} already exists, removing it", self.destination().display());
                fs::remove_file(self.destination()).context("failed to remove the existing destination file")?;
            }
        }
        Ok(())
    }
}