install_framework_cli/
lib.rs

1// Copyright 2021 Yuri6037
2
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"),
5// to deal in the Software without restriction, including without limitation
6// the rights to use, copy, modify, merge, publish, distribute, sublicense,
7// and/or sell copies of the Software, and to permit persons to whom the
8// Software is furnished to do so, subject to the following conditions:
9
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19// IN THE SOFTWARE.
20
21use std::string::String;
22use std::collections::HashMap;
23use std::path::Path;
24use clap::clap_app;
25use std::vec::Vec;
26use install_framework_core::interface::Interface;
27use install_framework_core::interface::ConstructibleInterface;
28use install_framework_core::interface::Installer;
29use install_framework_core::interface::InstallMethod;
30use install_framework_core::interface::PostInstall;
31use install_framework_core::interface::PostUninstall;
32use install_framework_core::builder::InstallerBuilder;
33use install_framework_base::interface::BaseInterface;
34
35mod error;
36mod interpreter;
37
38use error::CliError;
39
40pub struct CliInterface
41{
42    base: BaseInterface<interpreter::CliInterpreter, CliError>,
43    install: bool,
44    status: bool,
45    local: bool,
46    yes: bool,
47    components: Vec<String>
48}
49
50impl ConstructibleInterface<'_> for CliInterface
51{
52    fn run(mut builder: InstallerBuilder) -> i32
53    {
54        let mut interface = CliInterface
55        {
56            base: BaseInterface::new(interpreter::CliInterpreter::new()),
57            install: false,
58            status: false,
59            local: true,
60            yes: false,
61            components: Vec::new()
62        };
63        return builder.run(&mut interface);
64    }
65}
66
67impl CliInterface
68{
69    fn check_install_status(&mut self, installer: &mut dyn Installer, install_dir: &Path, resources: &HashMap<&'static str, &'static [u8]>) -> Result<(), CliError>
70    {
71        println!("==> Install status <==");
72        let components = self.base.get_components(installer, resources)?;
73        let state = self.base.get_installation_state(components, installer, install_dir)?;
74        if let Some(v) = state
75        {
76            println!("{}", v.manifest);
77            println!("Installed components:");
78            for c in v.installed_components
79            {
80                println!("    - {} ({:?})", c.name, c.version);
81            }
82            println!("Not yet installed components:");
83            for c in v.non_installed_components
84            {
85                println!("    - {} ({:?})", c.name, c.version);
86            }
87        }
88        else
89        {
90            println!("Software not installed");
91            return Ok(());
92        }
93        println!("==> End <==");
94        return Ok(());
95    }
96}
97
98impl Interface for CliInterface
99{
100    type ErrorType = CliError;
101
102    fn welcome(&mut self, name: &'static str, version: &'static str, author: &'static str) -> Result<(), Self::ErrorType>
103    {
104        println!("Starting application installer for {} ({})...", name, version);
105        let matches = clap_app!(name =>
106            (version: version)
107            (author: author)
108            (about: "Powered by InstallFramework <https://gitlab.com/Yuri6037/install-framework>")
109            (@arg install: -i --install "Install one or more component(s)")
110            (@arg uninstall: -u --uninstall "Uninstall one or more component(s)")
111            (@arg status: -s --status "Show install status including which component(s) have been installed")
112            (@arg components: +takes_value +multiple -c --component "Specifies a component to be modified")
113            (@arg local: -l --local "Run a local user install (if permitted by this installer)")
114            (@arg yes: -y --yes "Agree to all licenses")
115            (@arg props: +multiple -p --property +takes_value "Set a property to avoid user input prompt (usefull in CI builds)")
116        ).get_matches();
117        if !matches.is_present("install") && !matches.is_present("uninstall") && !matches.is_present("status")
118        {
119            return Err(CliError::Generic(String::from("Nothing to do, please specify an install, uninstall or status command")));
120        }
121        if (matches.is_present("install") && matches.is_present("uninstall"))
122            || (matches.is_present("install") && matches.is_present("status"))
123            || (matches.is_present("uninstall") && matches.is_present("status"))
124        {
125            return Err(CliError::Generic(String::from("Multiple action commands cannot co-exist")));
126        }
127        self.base.set_static_info(name, version, author);
128        self.yes = matches.is_present("yes");
129        self.install = matches.is_present("install");
130        if matches.is_present("status")
131        {
132            self.status = true;
133            self.install = true;
134        }
135        if let Some(obj) = matches.values_of("components")
136        {
137            for v in obj
138            {
139                self.components.push(String::from(v));
140            }
141        }
142        if let Some(obj) = matches.values_of("props")
143        {
144            for v in obj
145            {
146                if let Some(idx) = v.find('=')
147                {
148                    let key = String::from(&v[0..idx]);
149                    let value = String::from(&v[idx + 1..]);
150                    self.base.set_prop(key, value);
151                }
152                else
153                {
154                    return Err(CliError::Generic(format!("Invalid property {}", v)));
155                }
156            }
157        }
158        self.local = matches.is_present("local");
159        return Ok(());
160    }
161
162    fn get_install_method(&mut self) -> Result<InstallMethod, Self::ErrorType>
163    {
164        let mut m = InstallMethod::SystemInstall; //Default is system install
165        if self.local
166        {
167            m = InstallMethod::UserInstall;
168        }
169        return Ok(m);
170    }
171
172    fn should_uninstall(&self) -> Result<bool, Self::ErrorType>
173    {
174        return Ok(!self.install);
175    }
176
177    fn run_install(&mut self, installer: &mut dyn Installer, dir: &Path, method: InstallMethod, resources: &HashMap<&'static str, &'static [u8]>) -> Result<(), Self::ErrorType>
178    {
179        if self.status
180        {
181            self.check_install_status(installer, dir, resources)?;
182            return Err(CliError::Skip);
183        }
184        println!("==> Installing components... <==");
185        let components = self.base.get_components(installer, resources)?;
186        for c in &self.components
187        {
188            if let Some(c) = components.iter().find(|s| s.name == *c)
189            {
190                println!("");
191                println!("Installing component {}...", c.name);
192                if let Some(v) = &c.version
193                {
194                    println!("Version: {}", v);
195                }
196                if let Some(v) = &c.readme
197                {
198                    println!("> README");
199                    println!("{}", v);
200                }
201                if let Some(v) = &c.license
202                {
203                    println!("> LICENSE");
204                    println!("{}", v);
205                }
206                if !self.yes
207                {
208                    println!("Proceed (y for yes, else no)?");
209                    let line = interpreter::read_console()?;
210                    if !line.starts_with("y")
211                    {
212                        return Err(CliError::Generic(String::from("User canceled installation")));
213                    }
214                }
215                self.base.install_component(&c, installer, dir, method, resources)?;
216            }
217            else
218            {
219                return Err(CliError::Generic(format!("Unknown component: {}", c)));
220            }
221        }
222        println!("==> End <==");
223        return Ok(());
224    }
225
226    fn run_post_install(&mut self, post: &mut dyn PostInstall, dir: &Path) -> Result<(), Self::ErrorType>
227    {
228        return self.base.run_post_install(post, dir);
229    }
230
231    fn run_uninstall(&mut self, installer: &mut dyn Installer, dir: &Path, method: InstallMethod, resources: &HashMap<&'static str, &'static [u8]>) -> Result<(), Self::ErrorType>
232    {
233        println!("==> Uninstalling components... <==");
234        let components = self.base.get_components(installer, resources)?;
235        for c in &self.components
236        {
237            if let Some(c) = components.iter().find(|s| s.name == *c)
238            {
239                println!("");
240                println!("Uninstalling component {}...", c.name);
241                if !self.yes
242                {
243                    println!("Proceed (y for yes, else no)?");
244                    let line = interpreter::read_console()?;
245                    if !line.starts_with("y")
246                    {
247                        return Err(CliError::Generic(String::from("User canceled installation")));
248                    }
249                }
250                self.base.uninstall_component(&c, installer, dir, method)?;
251            }
252            else
253            {
254                return Err(CliError::Generic(format!("Unknown component: {}", c)));
255            }
256        }
257        println!("==> End <==");
258        return Ok(());
259    }
260
261    fn run_post_uninstall(&mut self, post: &mut dyn PostUninstall, dir: &Path) -> Result<(), Self::ErrorType>
262    {
263        return self.base.run_post_uninstall(post, dir);
264    }
265
266    fn error(&mut self, e: Self::ErrorType) -> i32
267    {
268        match e
269        {
270            CliError::Generic(s) =>
271            {
272                eprintln!("A generic error has occured: {}", s);
273                return 1;
274            },
275            CliError::Io(e) =>
276            {
277                eprintln!("An IO error has occured: {}", e);
278                return 2;
279            },
280            CliError::Skip => return 0,
281            CliError::Network(e) =>
282            {
283                eprintln!("A network error has occured: {}", e);
284                return 3;
285            },
286            CliError::Zip(e) =>
287            {
288                eprintln!("A zip error has occured: {}", e);
289                return 4;
290            }
291        }
292    }
293
294    fn finish(&mut self) -> i32
295    {
296        println!("Install succeeded!");
297        return 0;
298    }
299}