1use 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; 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}