diff --git a/src/installer.rs b/src/installer.rs index 1193ffe..35e42db 100644 --- a/src/installer.rs +++ b/src/installer.rs @@ -19,6 +19,7 @@ use config::Config; use sources::types::Version; use tasks::install::InstallTask; +use tasks::uninstall::UninstallTask; use tasks::DependencyTree; /// A message thrown during the installation of packages. @@ -75,7 +76,7 @@ impl InstallerFramework { } /// Sends a request for something to be installed. - /// items: Array of named packages to be installed + /// items: Array of named packages to be installed/kept /// messages: Channel used to send progress messages /// fresh_install: If the install directory must be empty pub fn install( @@ -109,6 +110,26 @@ impl InstallerFramework { }).map(|_x| ()) } + /// Sends a request for everything to be uninstalled. + pub fn uninstall(&mut self, messages: &Sender) -> Result<(), String> { + // TODO: Cleanup maintenance tool + + let items: Vec = self.database.iter().map(|x| x.name.clone()).collect(); + + let task = Box::new(UninstallTask { items }); + + let mut tree = DependencyTree::build(task); + + println!("Dependency tree:\n{}", tree); + + tree.execute(self, &|msg: &str, progress: f32| match messages + .send(InstallMessage::Status(msg.to_string(), progress as _)) + { + Err(v) => eprintln!("Failed to submit queue message: {:?}", v), + _ => {} + }).map(|_x| ()) + } + /// Saves the applications database. pub fn save_database(&self) -> Result<(), String> { // We have to have a install path for us to be able to do anything diff --git a/src/rest.rs b/src/rest.rs index c722002..7706805 100644 --- a/src/rest.rs +++ b/src/rest.rs @@ -143,6 +143,49 @@ impl Service for WebService { .with_body(file) } // Streams the installation of a particular set of packages + (&Post, "/api/uninstall") => { + // We need to bit of pipelining to get this to work + let framework = self.framework.clone(); + + return Box::new(req.body().concat2().map(move |_b| { + let (sender, receiver) = channel(); + let (tx, rx) = hyper::Body::pair(); + + // Startup a thread to do this operation for us + thread::spawn(move || { + let mut framework = framework.write().unwrap(); + + match framework.uninstall(&sender) { + Err(v) => sender.send(InstallMessage::Error(v)).unwrap(), + _ => {} + } + sender.send(InstallMessage::EOF).unwrap(); + }); + + // Spawn a thread for transforming messages to chunk messages + thread::spawn(move || { + let mut tx = tx; + loop { + let response = receiver.recv().unwrap(); + + match &response { + &InstallMessage::EOF => break, + _ => {} + } + + let mut response = serde_json::to_string(&response).unwrap(); + response.push('\n'); + tx = tx.send(Ok(response.into_bytes().into())).wait().unwrap(); + } + }); + + Response::::new() + //.with_header(ContentLength(file.len() as u64)) + .with_header(ContentType::plaintext()) + .with_body(rx) + })); + } + // Streams the installation of a particular set of packages (&Post, "/api/start-install") => { // We need to bit of pipelining to get this to work let framework = self.framework.clone(); diff --git a/src/tasks/mod.rs b/src/tasks/mod.rs index 0e96f97..021f285 100644 --- a/src/tasks/mod.rs +++ b/src/tasks/mod.rs @@ -16,6 +16,7 @@ pub mod install_pkg; pub mod resolver; pub mod save_database; pub mod save_executable; +pub mod uninstall; pub mod uninstall_pkg; /// An abstraction over the various paramaters that can be passed around. diff --git a/src/tasks/uninstall.rs b/src/tasks/uninstall.rs new file mode 100644 index 0000000..5d47b05 --- /dev/null +++ b/src/tasks/uninstall.rs @@ -0,0 +1,44 @@ +//! Uninstalls a set of packages. + +use installer::InstallerFramework; + +use tasks::Task; +use tasks::TaskParamType; + +use tasks::save_database::SaveDatabaseTask; +use tasks::uninstall_pkg::UninstallPackageTask; + +pub struct UninstallTask { + pub items: Vec, +} + +impl Task for UninstallTask { + fn execute( + &mut self, + _: Vec, + _: &mut InstallerFramework, + messenger: &Fn(&str, f32), + ) -> Result { + messenger("Wrapping up...", 0.0); + Ok(TaskParamType::None) + } + + fn dependencies(&self) -> Vec> { + let mut elements = Vec::>::new(); + + for item in &self.items { + elements.push(Box::new(UninstallPackageTask { + name: item.clone(), + optional: false, + })); + } + + elements.push(Box::new(SaveDatabaseTask {})); + + elements + } + + fn name(&self) -> String { + format!("UninstallTask") + } +} diff --git a/src/tasks/uninstall_pkg.rs b/src/tasks/uninstall_pkg.rs index 8b6fa6a..98b57cd 100644 --- a/src/tasks/uninstall_pkg.rs +++ b/src/tasks/uninstall_pkg.rs @@ -5,6 +5,11 @@ use installer::InstallerFramework; use tasks::Task; use tasks::TaskParamType; +use installer::LocalInstallation; + +use std::fs::remove_dir; +use std::fs::remove_file; + pub struct UninstallPackageTask { pub name: String, pub optional: bool, @@ -13,13 +18,69 @@ pub struct UninstallPackageTask { impl Task for UninstallPackageTask { fn execute( &mut self, - _: Vec, - _: &mut InstallerFramework, + input: Vec, + context: &mut InstallerFramework, messenger: &Fn(&str, f32), ) -> Result { + assert_eq!(input.len(), 0); + + let path = context + .install_path + .as_ref() + .expect("No install path specified"); + + let mut metadata: Option = None; + for i in 0..context.database.len() { + if &self.name == &context.database[i].name { + metadata = Some(context.database.remove(i)); + break; + } + } + + let mut package = match metadata { + Some(v) => v, + None => { + if self.optional { + return Ok(TaskParamType::None); + } + + return Err(format!( + "Package {:?} could not be found for uninstall.", + self.name + )); + } + }; + messenger(&format!("Uninstalling package {:?}...", self.name), 0.0); - // TODO: Find files to uninstall, wipe them out, then clean up DB + // Reverse, as to delete directories last + package.files.reverse(); + + let max = package.files.len(); + let mut i = 0; + for file in package.files { + let name = file.clone(); + let file = path.join(file); + println!("Deleting {:?}", file); + + messenger( + &format!("Deleting {} ({} of {})", name, i + 1, max), + (i as f32) / (max as f32), + ); + + let result = if file.is_dir() { + remove_dir(file) + } else { + remove_file(file) + }; + + match result { + Err(v) => eprintln!("Failed to delete file: {:?}", v), + _ => {} + } + + i += 1; + } Ok(TaskParamType::None) } diff --git a/static/index.html b/static/index.html index e61d8bf..d4be602 100644 --- a/static/index.html +++ b/static/index.html @@ -127,7 +127,7 @@ @@ -216,6 +216,27 @@ "cancel_uninstall": function() { app.confirm_uninstall = false; }, + "uninstall": function() { + app.confirm_uninstall = false; + this.select_packages = false; + this.is_installing = true; + + stream_ajax("/api/uninstall", function(line) { + if (line.hasOwnProperty("Status")) { + app.progress_message = line.Status[0]; + app.progress = line.Status[1] * 100; + } + + if (line.hasOwnProperty("Error")) { + app.is_installing = false; + app.has_error = true; + app.error = line.Error; + } + }, function(e) { + app.is_installing = false; + app.is_finished = true; + }, undefined, {}); + }, "exit": function() { ajax("/api/exit", function() {}); }