1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
use crate::{util, AvailablePlugin, InstalledPlugin};
use anyhow::Result;
use std::fs;
use std::path::PathBuf;

/// Possible icon names, in order of preference
const ICON_NAMES: [&str; 4] = ["icon@2x.png", "icon@2x.jpg", "icon.png", "icon.jpg"];

#[derive(Debug, Clone)]
pub struct Plugin {
    pub(crate) installed: Option<InstalledPlugin>,
    pub(crate) available: Option<AvailablePlugin>,
}

impl Plugin {
    pub fn is_installed(&self) -> bool {
        self.installed.is_some()
    }

    pub fn is_available(&self) -> bool {
        self.available.is_some()
    }

    pub fn name(&self) -> &str {
        if let Some(i) = &self.installed {
            &i.name
        } else if let Some(a) = &self.available {
            &a.name
        } else {
            panic!("Plugin that is neither installed nor available")
        }
    }

    /// Returns the plug-in's path, if it is installed
    pub fn path(&self) -> Option<PathBuf> {
        Some(self.installed.as_ref()?.path())
    }

    /// Attempts to retrieve the plug-in's icon from a number of sources.
    pub fn retrieve_icon(&self) -> Option<Vec<u8>> {
        if let Some(i) = &self.installed {
            for name in ICON_NAMES.iter() {
                let mut path = i.path();
                path.push(name);
                if path.exists() {
                    return std::fs::read(path).ok();
                }
            }
        }
        if let Some(a) = &self.available {
            return util::download(a.icon_url.as_ref()?).ok();
        }
        None
    }

    /// Downloads & installs the plug-in to `es_plugin_folder()`/`name()`
    pub fn download(&mut self) -> Result<()> {
        self.installed = Some(
            self.available
                .as_ref()
                .ok_or_else(|| anyhow!("Not an available Plug-In"))?
                .download()?,
        );
        Ok(())
    }

    /// Removes the plug-in locally
    pub fn remove(&mut self) -> Result<()> {
        if let Some(path) = self.path() {
            info!("Removing {}", path.to_string_lossy());
            fs::remove_dir_all(path)?;
            self.installed = None;
            Ok(())
        } else {
            Err(anyhow!("Not an installed Plug-In"))
        }
    }

    /// Returns the (`installed`, `available`) versions, if known
    pub fn versions(&self) -> (Option<&str>, Option<&str>) {
        (
            self.installed.as_ref().map(|i| i.version.as_str()),
            self.available.as_ref().map(|a| a.version.as_str()),
        )
    }
}