Implement uninstall pipeline

This commit is contained in:
James 2018-08-03 23:44:35 +10:00
parent 66ce12c0aa
commit cb29b4acdf
6 changed files with 196 additions and 5 deletions

View File

@ -19,6 +19,7 @@ use config::Config;
use sources::types::Version; use sources::types::Version;
use tasks::install::InstallTask; use tasks::install::InstallTask;
use tasks::uninstall::UninstallTask;
use tasks::DependencyTree; use tasks::DependencyTree;
/// A message thrown during the installation of packages. /// A message thrown during the installation of packages.
@ -75,7 +76,7 @@ impl InstallerFramework {
} }
/// Sends a request for something to be installed. /// 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 /// messages: Channel used to send progress messages
/// fresh_install: If the install directory must be empty /// fresh_install: If the install directory must be empty
pub fn install( pub fn install(
@ -109,6 +110,26 @@ impl InstallerFramework {
}).map(|_x| ()) }).map(|_x| ())
} }
/// Sends a request for everything to be uninstalled.
pub fn uninstall(&mut self, messages: &Sender<InstallMessage>) -> Result<(), String> {
// TODO: Cleanup maintenance tool
let items: Vec<String> = 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. /// Saves the applications database.
pub fn save_database(&self) -> Result<(), String> { pub fn save_database(&self) -> Result<(), String> {
// We have to have a install path for us to be able to do anything // We have to have a install path for us to be able to do anything

View File

@ -143,6 +143,49 @@ impl Service for WebService {
.with_body(file) .with_body(file)
} }
// Streams the installation of a particular set of packages // 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::<hyper::Body>::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") => { (&Post, "/api/start-install") => {
// We need to bit of pipelining to get this to work // We need to bit of pipelining to get this to work
let framework = self.framework.clone(); let framework = self.framework.clone();

View File

@ -16,6 +16,7 @@ pub mod install_pkg;
pub mod resolver; pub mod resolver;
pub mod save_database; pub mod save_database;
pub mod save_executable; pub mod save_executable;
pub mod uninstall;
pub mod uninstall_pkg; pub mod uninstall_pkg;
/// An abstraction over the various paramaters that can be passed around. /// An abstraction over the various paramaters that can be passed around.

44
src/tasks/uninstall.rs Normal file
View File

@ -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<String>,
}
impl Task for UninstallTask {
fn execute(
&mut self,
_: Vec<TaskParamType>,
_: &mut InstallerFramework,
messenger: &Fn(&str, f32),
) -> Result<TaskParamType, String> {
messenger("Wrapping up...", 0.0);
Ok(TaskParamType::None)
}
fn dependencies(&self) -> Vec<Box<Task>> {
let mut elements = Vec::<Box<Task>>::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")
}
}

View File

@ -5,6 +5,11 @@ use installer::InstallerFramework;
use tasks::Task; use tasks::Task;
use tasks::TaskParamType; use tasks::TaskParamType;
use installer::LocalInstallation;
use std::fs::remove_dir;
use std::fs::remove_file;
pub struct UninstallPackageTask { pub struct UninstallPackageTask {
pub name: String, pub name: String,
pub optional: bool, pub optional: bool,
@ -13,13 +18,69 @@ pub struct UninstallPackageTask {
impl Task for UninstallPackageTask { impl Task for UninstallPackageTask {
fn execute( fn execute(
&mut self, &mut self,
_: Vec<TaskParamType>, input: Vec<TaskParamType>,
_: &mut InstallerFramework, context: &mut InstallerFramework,
messenger: &Fn(&str, f32), messenger: &Fn(&str, f32),
) -> Result<TaskParamType, String> { ) -> Result<TaskParamType, String> {
assert_eq!(input.len(), 0);
let path = context
.install_path
.as_ref()
.expect("No install path specified");
let mut metadata: Option<LocalInstallation> = 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); 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) Ok(TaskParamType::None)
} }

View File

@ -127,7 +127,7 @@
<p class="modal-card-title">Are you sure you want to uninstall {{ config.general.name }}?</p> <p class="modal-card-title">Are you sure you want to uninstall {{ config.general.name }}?</p>
</header> </header>
<footer class="modal-card-foot"> <footer class="modal-card-foot">
<button class="button is-danger">Yes</button> <button class="button is-danger" v-on:click="uninstall">Yes</button>
<button class="button" v-on:click="cancel_uninstall">No</button> <button class="button" v-on:click="cancel_uninstall">No</button>
</footer> </footer>
</div> </div>
@ -216,6 +216,27 @@
"cancel_uninstall": function() { "cancel_uninstall": function() {
app.confirm_uninstall = false; 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() { "exit": function() {
ajax("/api/exit", function() {}); ajax("/api/exit", function() {});
} }