install-framework-base 1.0.0

[Install Framework] Official generic interface implementation
Documentation
// Copyright 2021 Yuri6037

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

use std::path::Path;
use std::path::PathBuf;
use std::string::String;
use std::collections::HashMap;
use std::io;
use std::io::Write;
use std::fs::File;
use install_framework_core::command::CommandQueue;
use install_framework_core::interface::Installer;
use install_framework_core::interface::Component;
use install_framework_core::interface::PostInstall;
use install_framework_core::interface::PostUninstall;
use install_framework_core::interface::InstallMethod;

use crate::interpreter::InitInterpreter;
use crate::interpreter::StatusInterpreter;
use crate::interpreter::InstallInterpreter;
use crate::interpreter::UninstallInterpreter;
use crate::error::Error;
use crate::cache::Cache;

pub trait SimpleInterpreter<ErrorType>
{
    fn read_user_input_text(&mut self, message: &str) -> Result<String, ErrorType>;
    fn begin_step(&mut self, message: &str) -> Result<(), ErrorType>;
    fn update_step(&mut self, percent: f32) -> Result<(), ErrorType>;
    fn end_step(&mut self) -> Result<(), ErrorType>;
    fn begin_substep(&mut self, message: &str) -> Result<(), ErrorType>;
    fn update_substep(&mut self, percent: f32) -> Result<(), ErrorType>;
    fn end_substep(&mut self) -> Result<(), ErrorType>;
}

fn install_dir_is_empty(install_dir: &Path) -> bool
{
    let files = match std::fs::read_dir(install_dir)
    {
        Ok(v) => v,
        Err(_) => return false
    };
    for f in files
    {
        let ff = match f
        {
            Ok(v) => v,
            Err(_) => return false
        };
        let useless = ff.file_name();
        let name = useless.to_string_lossy();
        let t = match ff.file_type()
        {
            Ok(v) => v,
            Err(_) => return false
        };
        if name != "manifest.txt" && !t.is_dir()
        {
            return false;
        }
        if t.is_dir() && !install_dir_is_empty(&ff.path())
        {
            return false;
        }
    }
    return true
}

#[derive(Clone)]
pub struct InstallationState
{
    pub manifest: String,
    pub installed_components: Vec<Component>,
    pub non_installed_components: Vec<Component>
}

pub struct BaseInterface<TInterpreter: SimpleInterpreter<TError::ErrorType>, TError: Error>
{
    interpreter: TInterpreter,
    cache: Cache<TError>,
    installed: bool,
    props: HashMap<String, String>,
    name: &'static str,
    version: &'static str,
    author: &'static str,
    last_res_id: usize
}

impl <TInterpreter: SimpleInterpreter<TError::ErrorType>, TError: Error> BaseInterface<TInterpreter, TError>
{
    pub fn new(interpreter: TInterpreter) -> BaseInterface<TInterpreter, TError>
    {
        return BaseInterface
        {
            interpreter: interpreter,
            cache: Cache::new(),
            installed: false,
            props: HashMap::new(),
            name: "Unknown",
            version: "Unknown",
            author: "Unknown",
            last_res_id: 0
        }
    }

    pub fn set_prop(&mut self, key: String, value: String)
    {
        self.props.insert(key, value);
    }

    pub fn set_static_info(&mut self, name: &'static str, version: &'static str, author: &'static str)
    {
        self.name = name;
        self.version = version;
        self.author = author;
    }

    pub fn get_components(&mut self, installer: &mut dyn Installer, resources: &HashMap<&'static str, &'static [u8]>) -> Result<Vec<Component>, TError::ErrorType>
    {
        self.interpreter.begin_step("Initializing...")?;
        let mut commands = CommandQueue::new(self.last_res_id);
        installer.init(&mut commands);
        self.last_res_id = commands.get_last_resource_id();
        let mut interpreter = InitInterpreter::new(&mut self.interpreter, &mut self.cache, &self.props, resources);
        commands.run(&mut interpreter)?;
        let path = &self.cache.get_path(Path::new("."))?;
        let v = installer.get_components(path);
        self.interpreter.end_step()?;
        return Ok(v);
    }

    pub fn run_post_uninstall(&mut self, post: &mut dyn PostUninstall, install_dir: &Path) -> Result<(), TError::ErrorType>
    {
        let (bin, content) = self.get_target_dirs(install_dir);
        let cache = self.cache.get_path(Path::new("."))?;
        post.post_uninstall(&cache, &content, &bin);
        return Ok(());
    }

    pub fn run_post_install(&mut self, post: &mut dyn PostInstall, install_dir: &Path) -> Result<(), TError::ErrorType>
    {
        let (bin, content) = self.get_target_dirs(install_dir);
        let cache = self.cache.get_path(Path::new("."))?;
        post.post_install(&cache, &content, &bin);
        return Ok(());
    }

    fn setup_install(&self, content_dir: &Path, bin_dir: &Path) -> io::Result<()>
    {
        if !content_dir.exists()
        {
            std::fs::create_dir(&content_dir)?;
        }
        if !bin_dir.exists()
        {
            std::fs::create_dir(&bin_dir)?;
        }
        let manifest = content_dir.join("manifest.txt");
        let mut f = File::create(manifest)?;
        writeln!(f, "Installer Name: {}", self.name)?;
        writeln!(f, "Installer Version: {}", self.version)?;
        writeln!(f, "Installer Author: {}", self.author)?;
        return Ok(());
    }

    pub fn get_target_dirs(&self, install_dir: &Path) -> (PathBuf, PathBuf)
    {
        #[cfg(windows)]
        let bin = install_dir.join(self.name).join("bin");
        #[cfg(unix)]
        let bin = install_dir.join("bin");
        let content = install_dir.join(self.name);
        return (bin, content);
    }

    pub fn get_installation_state(&mut self, components: Vec<Component>, installer: &mut dyn Installer, install_dir: &Path) -> Result<Option<InstallationState>, TError::ErrorType>
    {
        let (bin, content) = self.get_target_dirs(install_dir);
        let manifest = content.join("manifest.txt");
        let manifest_data;
        match std::fs::read_to_string(manifest)
        {
            Ok(v) => manifest_data = v,
            Err(_) => return Ok(None)
        }
        let mut installed_components = Vec::new();
        let mut non_installed_components = Vec::new();
        for c in components
        {
            let mut interpreter: StatusInterpreter<TError> = StatusInterpreter::new(&content, &bin, &mut self.cache);
            let mut commands = CommandQueue::new(self.last_res_id);
            installer.install_component(&c.name, &mut commands);
            self.last_res_id = commands.get_last_resource_id();
            commands.run(&mut interpreter)?;
            if interpreter.is_installed()
            {
                installed_components.push(c);
            }
            else
            {
                non_installed_components.push(c);
            }
        }
        return Ok(Some(InstallationState
        {
            manifest: manifest_data,
            installed_components: installed_components,
            non_installed_components: non_installed_components
        }));
    }

    pub fn install_component(&mut self, component: &Component, installer: &mut dyn Installer, install_dir: &Path, method: InstallMethod, resources: &HashMap<&'static str, &'static [u8]>) -> Result<(), TError::ErrorType>
    {
        self.interpreter.begin_step(&format!("Installing component {}...", component.name))?;
        let (bin, content) = self.get_target_dirs(install_dir);
        if !self.installed
        {
            if let Err(e) = self.setup_install(&content, &bin)
            {
                return Err(TError::io(e));
            }
            self.installed = true;
        }
        #[cfg(target_os = "linux")]
        let mut interpreter = InstallInterpreter::new(&mut self.interpreter, &mut self.cache, install_dir, method, &content, &bin, &self.props, resources);
        #[cfg(target_os = "windows")]
        let mut interpreter = InstallInterpreter::new(&mut self.interpreter, &mut self.cache, method, &content, &bin, &self.props, resources);
        #[cfg(target_os = "macos")]
        let mut interpreter = InstallInterpreter::new(&mut self.interpreter, &mut self.cache, &content, &bin, &self.props, resources);
        let mut commands = CommandQueue::new(self.last_res_id);
        installer.install_component(&component.name, &mut commands);
        self.last_res_id = commands.get_last_resource_id();
        commands.run(&mut interpreter)?;
        self.interpreter.end_step()?;
        return Ok(());
    }

    pub fn uninstall_component(&mut self, component: &Component, installer: &mut dyn Installer, install_dir: &Path, method: InstallMethod) -> Result<(), TError::ErrorType>
    {
        self.interpreter.begin_step(&format!("Uninstalling component {}...", component.name))?;
        let (bin, content) = self.get_target_dirs(install_dir);
        #[cfg(target_os = "linux")]
        let mut interpreter = UninstallInterpreter::new(&mut self.interpreter, &mut self.cache, install_dir, method, &content, &bin, &self.props);
        #[cfg(target_os = "windows")]
        let mut interpreter = UninstallInterpreter::new(&mut self.interpreter, &mut self.cache, method, &content, &bin, &self.props);
        #[cfg(target_os = "macos")]
        let mut interpreter = UninstallInterpreter::new(&mut self.interpreter, &mut self.cache, &content, &bin, &self.props);
        let mut commands = CommandQueue::new(self.last_res_id);
        installer.install_component(&component.name, &mut commands);
        self.last_res_id = commands.get_last_resource_id();
        commands.run(&mut interpreter)?;
        if install_dir_is_empty(&content)
        {
            if let Err(e) = std::fs::remove_dir_all(content)
            {
                return Err(TError::io(e));
            }
            #[cfg(windows)]
            {
                use crate::platform;
                platform::winpath::remove_path::<TError>(&bin, method)?;
            }
        }
        self.interpreter.end_step()?;
        return Ok(());
    }
}