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::vec::Vec;
use std::path::Path;
use std::path::PathBuf;
use std::collections::HashMap;
use std::string::String;
use std::io;
use install_framework_core::command::Interpreter;
use install_framework_core::command::InstallCommand;
use install_framework_core::interface::InstallMethod;

use super::base;
use crate::cache::Cache;
use crate::error::Error;
use crate::interface::SimpleInterpreter;
use crate::platform;

fn auto_remove(path: &Path) -> io::Result<()>
{
    if path.exists()
    {
        if path.is_dir()
        {
            return std::fs::remove_dir_all(path);
        }
        else
        {
            return std::fs::remove_file(path);
        }
    }
    return Ok(());
}

fn is_empty(path: &Path) -> bool
{
    if let Ok(mut v) = path.read_dir()
    {
        return v.next().is_none();
    }
    return true;
}

pub struct UninstallInterpreter<'a, TError: Error>
{
    interpreter: &'a mut dyn SimpleInterpreter<TError::ErrorType>,
    cache: &'a mut Cache<TError>,
    props: &'a HashMap<String, String>,
    content_dir: &'a Path,
    bin_dir: &'a Path,
    delete_dirs: Vec<PathBuf>,
    #[cfg(target_os = "linux")]
    install_dir: &'a Path,
    #[cfg(not(target_os = "macos"))]
    method: InstallMethod
}

impl <'a, TError: Error> UninstallInterpreter<'a, TError>
{
    #[cfg(target_os = "linux")]
    pub fn new(interpreter: &'a mut dyn SimpleInterpreter<TError::ErrorType>, cache: &'a mut Cache<TError>, install_dir: &'a Path, method: InstallMethod, content_dir: &'a Path, bin_dir: &'a Path, props: &'a HashMap<String, String>) -> UninstallInterpreter<'a, TError>
    {
        return UninstallInterpreter
        {
            interpreter: interpreter,
            cache: cache,
            props: props,
            content_dir: content_dir,
            bin_dir: bin_dir,
            delete_dirs: Vec::new(),
            install_dir: install_dir,
            method: method
        }
    }

    #[cfg(target_os = "windows")]
    pub fn new(interpreter: &'a mut dyn SimpleInterpreter<TError::ErrorType>, cache: &'a mut Cache<TError>, method: InstallMethod, content_dir: &'a Path, bin_dir: &'a Path, props: &'a HashMap<String, String>) -> UninstallInterpreter<'a, TError>
    {
        return UninstallInterpreter
        {
            interpreter: interpreter,
            cache: cache,
            props: props,
            content_dir: content_dir,
            bin_dir: bin_dir,
            delete_dirs: Vec::new(),
            method: method
        }
    }

    #[cfg(target_os = "macos")]
    pub fn new(interpreter: &'a mut dyn SimpleInterpreter<TError::ErrorType>, cache: &'a mut Cache<TError>, content_dir: &'a Path, bin_dir: &'a Path, props: &'a HashMap<String, String>) -> UninstallInterpreter<'a, TError>
    {
        return UninstallInterpreter
        {
            interpreter: interpreter,
            cache: cache,
            props: props,
            content_dir: content_dir,
            bin_dir: bin_dir,
            delete_dirs: Vec::new()
        }
    }

    fn download_file(&mut self, resid: usize, filename: String) -> Result<(), TError::ErrorType>
    {
        let outfile = self.cache.get_path(Path::new(&filename))?;
        self.cache.insert(resid, outfile);
        return Ok(());
    }

    fn unpack_cached(&mut self, resid: usize, path: String) -> Result<(), TError::ErrorType>
    {
        if !path.ends_with(".zip")
        {
            return Err(TError::generic(String::from("Only ZIP archives are supported")));
        }
        let computed1 = self.cache.parse_string(&path[..path.len() - 4])?;
        let outpath = self.cache.get_path(Path::new(&computed1))?;
        self.cache.insert(resid, outpath);
        return Ok(());
    }

    fn extract_resource(&mut self, resid: usize, path: &'static str) -> Result<(), TError::ErrorType>
    {
        let name =
        {
            if let Some(id) = path.rfind('/')
            {
                &path[id + 1..]
            }
            else
            {
                path
            }
        };
        let outpath = self.cache.get_path(Path::new(name))?;
        self.cache.insert(resid, outpath);
        return Ok(());
    }

    fn user_input(&mut self, resid: usize, prop: String, message: String) -> Result<(), TError::ErrorType>
    {
        return base::user_input(self.interpreter, &mut self.cache, &self.props, resid, prop, message);
    }

    fn create_folder(&mut self, resid: usize, name: String) -> Result<(), TError::ErrorType>
    {
        let dir = self.content_dir.join(name);
        if dir.exists()
        {
            if is_empty(&dir)
            {
                if let Err(e) = std::fs::remove_dir_all(&dir)
                {
                    return Err(TError::io(e));
                }
            }
            else
            {
                self.delete_dirs.push(dir.clone());
            }
        }
        self.cache.insert(resid, dir);
        return Ok(());
    }

    fn install_cached(&mut self, path: String, folder: usize) -> Result<(), TError::ErrorType>
    {
        let src = PathBuf::from(self.cache.parse_string(&path)?);
        let dst;
        let dstf;

        if folder == 0
        {
            dst = self.content_dir;
        }
        else
        {
            dst = Path::new(self.cache.get(folder)?);
        }
        if let Some(name) = src.file_name()
        {
            dstf = dst.join(name);
        }
        else
        {
            dstf = dst.join(&src);
        }
        if let Err(e) = auto_remove(&dstf)
        {
            return Err(TError::io(e));
        }
        let mut drained = Vec::new();
        self.delete_dirs.retain(|v|
        {
            if is_empty(&v)
            {
                drained.push(v.clone());
                return false;
            }
            return true;
        });
        for v in drained
        {
            if let Err(e) = auto_remove(&v)
            {
                return Err(TError::io(e));
            }
        }
        return Ok(());
    }

    fn install_resource(&mut self, path: &'static str, folder: usize) -> Result<(), TError::ErrorType>
    {
        let dst;

        if folder == 0
        {
            dst = self.content_dir;
            let name =
            {
                if let Some(id) = path.rfind('/')
                {
                    &path[id + 1..]
                }
                else
                {
                    path
                }
            };
            let dstf = dst.join(name);
            if dstf.exists()
            {
                if let Err(e) = std::fs::remove_file(dstf)
                {
                    return Err(TError::io(e));
                }
            }
        }
        return Ok(());
    }

    fn add_to_path(&mut self, path: String) -> Result<(), TError::ErrorType>
    {
        let src = self.content_dir.join(path);
        let name =
        {
            if let Some(n) = src.file_name()
            {
                n
            }
            else
            {
                src.as_os_str()
            }
        };
        let dst = self.bin_dir.join(name);
        if dst.exists()
        {
            #[cfg(unix)]
            {
                if let Err(e) = std::fs::remove_file(dst)
                {
                    return Err(TError::io(e));
                }
            }
            #[cfg(windows)]
            {
                use std::ffi::OsString;

                let mut dst1 = OsString::from(dst.as_os_str());
                dst1.push(".bat");
                if let Err(e) = std::fs::remove_file(dst1)
                {
                    return Err(TError::io(e));
                }
            }
        }
        return Ok(());
    }

    fn add_shortcut(&mut self, path: String) -> Result<(), TError::ErrorType>
    {
        let file;
        #[cfg(target_os="linux")]
        {
            let mut name = platform::common::get_target_desktop_name(&path);
            if path.ends_with(".AppImage")
            {
                //Clean-up extracted icon for AppImage
                let mut copy = name.clone();
                copy.push_str(".icon");
                let icon = self.content_dir.join(copy);
                if icon.exists()
                {
                    if let Err(e) = std::fs::remove_file(icon)
                    {
                        return Err(TError::io(e));
                    }
                }
            }
            let dir = platform::common::get_desktop_folder(self.install_dir, self.method);
            name.push_str(".desktop");
            file = dir.join(name);
        }
        #[cfg(windows)]
        {
            let dir = platform::common::get_desktop_folder(self.method);
            let mut name = platform::common::get_target_desktop_name(&path);
            name.push_str(".lnk");
            file = dir.join(name);
        }
        if file.exists()
        {
            if let Err(e) = std::fs::remove_file(file)
            {
                return Err(TError::io(e));
            }
        }
        return Ok(());
    }
}

impl <TError: Error> Interpreter<InstallCommand> for UninstallInterpreter<'_, TError>
{
    type ErrorType = TError::ErrorType;

    fn execute(&mut self, resid: usize, cmd: InstallCommand) -> Result<(), TError::ErrorType>
    {
        match cmd
        {
            InstallCommand::DownloadFile(f, _, _) => self.download_file(resid, f)?,
            InstallCommand::UnpackCached(p) => self.unpack_cached(resid, p)?,
            InstallCommand::ExtractResource(p) => self.extract_resource(resid, p)?,
            InstallCommand::UserInput(p, m) => self.user_input(resid, p, m)?,
            InstallCommand::AddToPath(p) => self.add_to_path(p)?,
            InstallCommand::CreateFolder(n) => self.create_folder(resid, n)?,
            InstallCommand::InstallCached(p, f) => self.install_cached(p, f)?,
            InstallCommand::InstallResource(p, f) => self.install_resource(p, f)?,
            InstallCommand::AddShortcut(p) => self.add_shortcut(p)?
        }
        return Ok(());
    }

    fn progress(&mut self, cur: usize, max: usize) -> Result<(), Self::ErrorType>
    {
        self.interpreter.update_step((cur as f64 / max as f64) as f32)?;
        return Ok(());
    }
}