From 7630180c4f43c67c7fc5cdb25ee53f25ec0ebc46 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 3 Aug 2018 20:45:42 +1000 Subject: [PATCH] Overhaul installer pipeline, breaking down components into tasks --- src/http.rs | 5 +- src/installer.rs | 338 ++++------------------------------- src/tasks/download_pkg.rs | 81 +++++++++ src/tasks/install.rs | 52 ++++++ src/tasks/install_dir.rs | 60 +++++++ src/tasks/install_pkg.rs | 137 ++++++++++++++ src/tasks/mod.rs | 73 +++++--- src/tasks/resolver.rs | 95 ++++++++++ src/tasks/save_database.rs | 32 ++++ src/tasks/save_executable.rs | 80 +++++++++ src/tasks/uninstall_pkg.rs | 37 ++++ 11 files changed, 652 insertions(+), 338 deletions(-) create mode 100644 src/tasks/download_pkg.rs create mode 100644 src/tasks/install.rs create mode 100644 src/tasks/install_dir.rs create mode 100644 src/tasks/install_pkg.rs create mode 100644 src/tasks/resolver.rs create mode 100644 src/tasks/save_database.rs create mode 100644 src/tasks/save_executable.rs create mode 100644 src/tasks/uninstall_pkg.rs diff --git a/src/http.rs b/src/http.rs index 4c75630..2c5d1e3 100644 --- a/src/http.rs +++ b/src/http.rs @@ -8,10 +8,9 @@ use reqwest; use std::io::Read; /// Streams a file from a HTTP server. -pub fn stream_file(url: String, callback: F) -> Result<(), String> -// |data : Vec, total : u64| +pub fn stream_file(url: String, mut callback: F) -> Result<(), String> where - F: Fn(Vec, u64) -> (), + F: FnMut(Vec, u64) -> (), { let mut client = match reqwest::get(&url) { Ok(v) => v, diff --git a/src/installer.rs b/src/installer.rs index 6551fdd..3f8dbcf 100644 --- a/src/installer.rs +++ b/src/installer.rs @@ -1,39 +1,25 @@ /// installer.rs /// /// Contains the main installer structure, as well as high-level means of controlling it. -use regex::Regex; - -use zip::ZipArchive; use serde_json; -use number_prefix::{decimal_prefix, Prefixed, Standalone}; - -use std::fs::create_dir_all; -use std::fs::read_dir; use std::fs::File; -use std::env::consts::OS; -use std::env::current_exe; use std::env::home_dir; use std::env::var; use std::path::Path; use std::path::PathBuf; -use std::io::copy; -use std::io::Cursor; - use std::sync::mpsc::Sender; -use std::sync::Arc; -use std::sync::Mutex; use config::Config; -use http::stream_file; - use sources::types::Version; -use std::fs::OpenOptions; + +use tasks::install::InstallTask; +use tasks::DependencyTree; /// A message thrown during the installation of packages. #[derive(Serialize)] @@ -46,31 +32,26 @@ pub enum InstallMessage { /// The installer framework contains metadata about packages, what is installable, what isn't, /// etc. pub struct InstallerFramework { - config: Config, - database: Vec, - install_path: Option, - preexisting_install: bool, + pub config: Config, + pub database: Vec, + pub install_path: Option, + pub preexisting_install: bool, } /// Contains basic properties on the status of the session. Subset of InstallationFramework. #[derive(Serialize)] pub struct InstallationStatus { - database: Vec, - install_path: Option, - preexisting_install: bool, -} - -/// Used to track the amount of data that has been downloaded during a HTTP request. -struct DownloadProgress { - downloaded: usize, + pub database: Vec, + pub install_path: Option, + pub preexisting_install: bool, } /// Tracks the state of a local installation #[derive(Debug, Serialize, Deserialize, Clone)] pub struct LocalInstallation { - name: String, - version: Version, - files: Vec, + pub name: String, + pub version: Version, + pub files: Vec, } impl InstallerFramework { @@ -103,288 +84,29 @@ impl InstallerFramework { messages: &Sender, fresh_install: bool, ) -> Result<(), String> { - // We have to have a install path for us to be able to do anything - let path = match self.install_path.clone() { - Some(v) => v, - None => return Err(format!("No install directory for installer")), - }; + println!( + "Framework: Installing {:?} to {:?}", + items, + self.install_path + .clone() + .expect("Install directory not initialised") + ); - println!("Framework: Installing {:?} to {:?}", items, path); + let task = Box::new(InstallTask { + items, + fresh_install, + }); - // Create our install directory - if !path.exists() { - match create_dir_all(&path) { - Ok(_) => {} - Err(v) => return Err(format!("Failed to create install directory: {:?}", v)), - } - } + let mut tree = DependencyTree::build(task); - if !path.is_dir() { - return Err(format!("Install destination is not a directory.")); - } + println!("Dependency tree:\n{}", tree); - // Make sure it is empty - if fresh_install { - let paths = match read_dir(&path) { - Ok(v) => v, - Err(v) => return Err(format!("Failed to read install destination: {:?}", v)), - }; - - if paths.count() != 0 { - return Err(format!("Install destination is not empty.")); - } - } - - // Resolve items in config - let mut to_install = Vec::new(); - - for description in &self.config.packages { - if items.contains(&description.name) { - to_install.push(description.clone()); - } - } - - println!("Resolved to {:?}", to_install); - - // TODO: Uninstall pre-existing packages - - // Install packages - let mut count = 0.0 as f64; - let max = to_install.len() as f64; - - for package in to_install.iter() { - let base_package_percentage = count / max; - let base_package_range = ((count + 1.0) / max) - base_package_percentage; - - println!("Installing {}", package.name); - - // 10%: polling - messages - .send(InstallMessage::Status( - format!( - "Polling {} for latest version of {}", - package.source.name, package.name - ), - base_package_percentage + base_package_range * 0.10, - )) - .unwrap(); - - let results = package.source.get_current_releases()?; - - // 20%: waiting for parse/HTTP - messages - .send(InstallMessage::Status( - format!("Resolving dependency for {}", package.name), - base_package_percentage + base_package_range * 0.20, - )) - .unwrap(); - - let filtered_regex = package.source.match_regex.replace("#PLATFORM#", OS); - let regex = match Regex::new(&filtered_regex) { - Ok(v) => v, - Err(v) => return Err(format!("An error occured while compiling regex: {:?}", v)), - }; - - println!("Releases: {:?}", results); - - // Find the latest release in here - let latest_result = results - .into_iter() - .filter(|f| f.files.iter().filter(|x| regex.is_match(&x.name)).count() > 0) - .max_by_key(|f| f.version.clone()); - - let latest_result = match latest_result { - Some(v) => v, - None => return Err(format!("No release with correct file found")), - }; - - let latest_version = latest_result.version.clone(); - - // Find the matching file in here - let latest_file = latest_result - .files - .into_iter() - .filter(|x| regex.is_match(&x.name)) - .next() - .unwrap(); - - println!("{:?}", latest_file); - - // Download this file - let lock = Arc::new(Mutex::new(DownloadProgress { downloaded: 0 })); - let data_storage: Arc>> = Arc::new(Mutex::new(Vec::new())); - - // 33-66%: downloading file - stream_file(latest_file.url, |data, size| { - { - let mut data_lock = data_storage.lock().unwrap(); - data_lock.extend_from_slice(&data); - } - - let mut reference = lock.lock().unwrap(); - reference.downloaded += data.len(); - - let base_percentage = base_package_percentage + base_package_range * 0.33; - let range_percentage = base_package_range / 3.0; - - let global_percentage = if size == 0 { - base_percentage - } else { - let download_percentage = (reference.downloaded as f64) / (size as f64); - // Split up the bar for this download in half (for metadata download + parse), then - // add on our current percentage - base_percentage + range_percentage * download_percentage - }; - - // Pretty print data volumes - let pretty_current = match decimal_prefix(reference.downloaded as f64) { - Standalone(bytes) => format!("{} bytes", bytes), - Prefixed(prefix, n) => format!("{:.0} {}B", n, prefix), - }; - let pretty_total = match decimal_prefix(size as f64) { - Standalone(bytes) => format!("{} bytes", bytes), - Prefixed(prefix, n) => format!("{:.0} {}B", n, prefix), - }; - - messages - .send(InstallMessage::Status( - format!( - "Downloading {} ({} of {})", - package.name, pretty_current, pretty_total - ), - global_percentage, - )) - .unwrap(); - })?; - - println!("File downloaded successfully"); - - // Extract this downloaded file - let mut installed_files = Vec::new(); - - // TODO: Handle files other then zips - // TODO: Make database for uninstall - let data = data_storage.lock().unwrap(); - let data_cursor = Cursor::new(data.as_slice()); - let mut zip = match ZipArchive::new(data_cursor) { - Ok(v) => v, - Err(v) => return Err(format!("Unable to open .zip file: {:?}", v)), - }; - - let extract_base_percentage = base_package_percentage + base_package_range * 0.66; - let extract_range_percentage = base_package_range / 3.0; - - let zip_size = zip.len(); - - for i in 0..zip_size { - let mut file = zip.by_index(i).unwrap(); - - let percentage = - extract_base_percentage + extract_range_percentage / zip_size as f64 * i as f64; - - messages - .send(InstallMessage::Status( - format!("Extracting {} ({} of {})", file.name(), i + 1, zip_size), - percentage, - )) - .unwrap(); - - // Create target file - let target_path = path.join(file.name()); - println!("target_path: {:?}", target_path); - - installed_files.push(file.name().to_owned()); - - // Check to make sure this isn't a directory - if file.name().ends_with("/") || file.name().ends_with("\\") { - // Create this directory and move on - match create_dir_all(target_path) { - Ok(v) => v, - Err(v) => return Err(format!("Unable to open file: {:?}", v)), - } - continue; - } - - match target_path.parent() { - Some(v) => match create_dir_all(v) { - Ok(v) => v, - Err(v) => return Err(format!("Unable to open file: {:?}", v)), - }, - None => {} - } - - let mut target_file = match File::create(target_path) { - Ok(v) => v, - Err(v) => return Err(format!("Unable to open file handle: {:?}", v)), - }; - - // Cross the streams - match copy(&mut file, &mut target_file) { - Ok(v) => v, - Err(v) => return Err(format!("Unable to write to file: {:?}", v)), - }; - } - - // Save metadata about this package - // TODO: Check if this package already exists - self.database.push(LocalInstallation { - name: package.name.to_owned(), - version: latest_version, - files: installed_files, - }); - - count += 1.0; - } - - self.save_database()?; - - // Copy installer binary to target directory - messages - .send(InstallMessage::Status( - format!("Copying installer binary"), - 0.99, - )) - .unwrap(); - - let current_app = match current_exe() { - Ok(v) => v, - Err(v) => return Err(format!("Unable to locate installer binary: {:?}", v)), - }; - - let mut current_app_file = match File::open(current_app) { - Ok(v) => v, - Err(v) => return Err(format!("Unable to open installer binary: {:?}", v)), - }; - - let platform_extension = if cfg!(windows) { - "maintenancetool.exe" - } else { - "maintenancetool" - }; - - let new_app = path.join(platform_extension); - - let mut file_metadata = OpenOptions::new(); - file_metadata.write(true).create_new(true); - - #[cfg(unix)] + tree.execute(self, &|msg: &str, progress: f32| match messages + .send(InstallMessage::Status(msg.to_string(), progress as _)) { - use std::os::unix::fs::OpenOptionsExt; - - file_metadata.mode(0o770); - } - - let mut new_app_file = match file_metadata.open(new_app) { - Ok(v) => v, - Err(v) => return Err(format!("Unable to open installer binary: {:?}", v)), - }; - - match copy(&mut current_app_file, &mut new_app_file) { - Err(v) => return Err(format!("Unable to copy installer binary: {:?}", v)), + Err(v) => eprintln!("Failed to submit queue message: {:?}", v), _ => {} - }; - - Ok(()) + }).map(|_x| ()) } /// Saves the applications database. diff --git a/src/tasks/download_pkg.rs b/src/tasks/download_pkg.rs new file mode 100644 index 0000000..9dcb4d7 --- /dev/null +++ b/src/tasks/download_pkg.rs @@ -0,0 +1,81 @@ +//! Downloads a package into memory. + +use installer::InstallerFramework; + +use tasks::Task; +use tasks::TaskParamType; + +use tasks::resolver::ResolvePackageTask; + +use http::stream_file; + +use number_prefix::{decimal_prefix, Prefixed, Standalone}; + +pub struct DownloadPackageTask { + pub name: String, +} + +impl Task for DownloadPackageTask { + fn execute( + &mut self, + mut input: Vec, + _: &mut InstallerFramework, + messenger: &Fn(&str, f32), + ) -> Result { + assert_eq!(input.len(), 1); + messenger(&format!("Downloading package {:?}...", self.name), 0.0); + + let file = input.pop().expect("Should have input from resolver!"); + let (version, file) = match file { + TaskParamType::File(v, f) => (v, f), + _ => return Err(format!("Unexpected param type to download package")), + }; + + let mut downloaded = 0; + let mut data_storage: Vec = Vec::new(); + + stream_file(file.url, |data, size| { + { + data_storage.extend_from_slice(&data); + } + + downloaded += data.len(); + + let percentage = if size == 0 { + 0.0 + } else { + (downloaded as f32) / (size as f32) + }; + + // Pretty print data volumes + let pretty_current = match decimal_prefix(downloaded as f64) { + Standalone(bytes) => format!("{} bytes", bytes), + Prefixed(prefix, n) => format!("{:.0} {}B", n, prefix), + }; + let pretty_total = match decimal_prefix(size as f64) { + Standalone(bytes) => format!("{} bytes", bytes), + Prefixed(prefix, n) => format!("{:.0} {}B", n, prefix), + }; + + messenger( + &format!( + "Downloading {} ({} of {})...", + self.name, pretty_current, pretty_total + ), + percentage, + ); + })?; + + Ok(TaskParamType::FileContents(version, data_storage)) + } + + fn dependencies(&self) -> Vec> { + vec![Box::new(ResolvePackageTask { + name: self.name.clone(), + })] + } + + fn name(&self) -> String { + format!("DownloadPackageTask (for {:?})", self.name) + } +} diff --git a/src/tasks/install.rs b/src/tasks/install.rs new file mode 100644 index 0000000..98ce2ee --- /dev/null +++ b/src/tasks/install.rs @@ -0,0 +1,52 @@ +//! Overall hierarchy for installing a installation of the application. + +use installer::InstallerFramework; + +use tasks::install_dir::VerifyInstallDirTask; +use tasks::install_pkg::InstallPackageTask; +use tasks::save_database::SaveDatabaseTask; +use tasks::save_executable::SaveExecutableTask; + +use tasks::Task; +use tasks::TaskParamType; + +pub struct InstallTask { + pub items: Vec, + pub fresh_install: bool, +} + +impl Task for InstallTask { + 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(); + + elements.push(Box::new(VerifyInstallDirTask { + clean_install: self.fresh_install, + })); + + for item in &self.items { + elements.push(Box::new(InstallPackageTask { name: item.clone() })); + } + + elements.push(Box::new(SaveDatabaseTask {})); + + if self.fresh_install { + elements.push(Box::new(SaveExecutableTask {})); + } + + elements + } + + fn name(&self) -> String { + format!("InstallTask") + } +} diff --git a/src/tasks/install_dir.rs b/src/tasks/install_dir.rs new file mode 100644 index 0000000..d15f95d --- /dev/null +++ b/src/tasks/install_dir.rs @@ -0,0 +1,60 @@ +//! Verifies properties about the installation directory. + +use installer::InstallerFramework; + +use tasks::Task; +use tasks::TaskParamType; + +use std::fs::create_dir_all; +use std::fs::read_dir; +use std::thread; +use std::time::Duration; + +pub struct VerifyInstallDirTask { + pub clean_install: bool, +} + +impl Task for VerifyInstallDirTask { + fn execute( + &mut self, + input: Vec, + context: &mut InstallerFramework, + messenger: &Fn(&str, f32), + ) -> Result { + assert_eq!(input.len(), 0); + messenger("Polling installation directory...", 0.0); + + let path = context + .install_path + .as_ref() + .expect("No install path specified"); + + if !path.exists() { + create_dir_all(&path) + .map_err(|x| format!("Failed to create install directory: {:?}", x))?; + } + + thread::sleep(Duration::new(1, 0)); + if self.clean_install { + let paths = read_dir(&path) + .map_err(|x| format!("Failed to read install destination: {:?}", x))?; + + if paths.count() != 0 { + return Err(format!("Install destination is not empty.")); + } + } + + Ok(TaskParamType::None) + } + + fn dependencies(&self) -> Vec> { + vec![] + } + + fn name(&self) -> String { + format!( + "VerifyInstallDirTask (with clean-install = {})", + self.clean_install + ) + } +} diff --git a/src/tasks/install_pkg.rs b/src/tasks/install_pkg.rs new file mode 100644 index 0000000..73c93a6 --- /dev/null +++ b/src/tasks/install_pkg.rs @@ -0,0 +1,137 @@ +//! Installs a specific package. + +use installer::InstallerFramework; + +use tasks::Task; +use tasks::TaskParamType; + +use config::PackageDescription; +use installer::LocalInstallation; +use std::fs::create_dir_all; +use std::fs::File; +use std::io::copy; +use std::io::Cursor; +use tasks::download_pkg::DownloadPackageTask; +use tasks::uninstall_pkg::UninstallPackageTask; +use zip::ZipArchive; + +pub struct InstallPackageTask { + pub name: String, +} + +impl Task for InstallPackageTask { + fn execute( + &mut self, + mut input: Vec, + context: &mut InstallerFramework, + messenger: &Fn(&str, f32), + ) -> Result { + messenger(&format!("Installing package {:?}...", self.name), 0.0); + + let path = context + .install_path + .as_ref() + .expect("No install path specified"); + + let mut installed_files = Vec::new(); + + let mut metadata: Option = None; + for description in &context.config.packages { + if &self.name == &description.name { + metadata = Some(description.clone()); + break; + } + } + + let package = match metadata { + Some(v) => v, + None => return Err(format!("Package {:?} could not be found.", self.name)), + }; + + let _ = input.pop().expect("Should have input from uninstaller!"); + + let data = input.pop().expect("Should have input from resolver!"); + let (file, data) = match data { + TaskParamType::FileContents(file, data) => (file, data), + _ => return Err(format!("Unexpected param type to install package")), + }; + + // TODO: Handle files other then zips + let data_cursor = Cursor::new(data.as_slice()); + let mut zip = match ZipArchive::new(data_cursor) { + Ok(v) => v, + Err(v) => return Err(format!("Unable to open .zip file: {:?}", v)), + }; + + let zip_size = zip.len(); + + for i in 0..zip_size { + let mut file = zip.by_index(i).unwrap(); + + messenger( + &format!("Extracting {} ({} of {})", file.name(), i + 1, zip_size), + (i as f32) / (zip_size as f32), + ); + + // Create target file + let target_path = path.join(file.name()); + println!("target_path: {:?}", target_path); + + installed_files.push(file.name().to_string()); + + // Check to make sure this isn't a directory + if file.name().ends_with("/") || file.name().ends_with("\\") { + // Create this directory and move on + match create_dir_all(target_path) { + Ok(v) => v, + Err(v) => return Err(format!("Unable to open file: {:?}", v)), + } + continue; + } + + match target_path.parent() { + Some(v) => match create_dir_all(v) { + Ok(v) => v, + Err(v) => return Err(format!("Unable to open file: {:?}", v)), + }, + None => {} + } + + let mut target_file = match File::create(target_path) { + Ok(v) => v, + Err(v) => return Err(format!("Unable to open file handle: {:?}", v)), + }; + + // Cross the streams + match copy(&mut file, &mut target_file) { + Ok(v) => v, + Err(v) => return Err(format!("Unable to write to file: {:?}", v)), + }; + } + + // Save metadata about this package + context.database.push(LocalInstallation { + name: package.name.to_owned(), + version: file, + files: installed_files, + }); + + Ok(TaskParamType::None) + } + + fn dependencies(&self) -> Vec> { + vec![ + Box::new(DownloadPackageTask { + name: self.name.clone(), + }), + Box::new(UninstallPackageTask { + name: self.name.clone(), + optional: true, + }), + ] + } + + fn name(&self) -> String { + format!("InstallPackageTask (for {:?})", self.name) + } +} diff --git a/src/tasks/mod.rs b/src/tasks/mod.rs index 4846140..0e96f97 100644 --- a/src/tasks/mod.rs +++ b/src/tasks/mod.rs @@ -1,43 +1,57 @@ -use std::any::Any; +//! Contains a framework for the processing of discrete Tasks, as well as +//! various implementations of it for installer-related tasks. use std::fmt; use std::fmt::Display; -/// Allows for any time to be used as an Input to a Task. -type AnyInput = Box; +use installer::InstallerFramework; + +use sources::types::File; +use sources::types::Version; + +pub mod download_pkg; +pub mod install; +pub mod install_dir; +pub mod install_pkg; +pub mod resolver; +pub mod save_database; +pub mod save_executable; +pub mod uninstall_pkg; + +/// An abstraction over the various paramaters that can be passed around. +pub enum TaskParamType { + None, + File(Version, File), + FileContents(Version, Vec), +} /// A Task is a small, async task conforming to a fixed set of inputs/outputs. pub trait Task { - type Input; - type Output; - type Error; - /// Executes this individual task, evaluating to the given Output result. /// /// Each dependency is given an indice in the inputted vector. fn execute( &mut self, - input: Vec, + input: Vec, + context: &mut InstallerFramework, messenger: &Fn(&str, f32), - ) -> Result; + ) -> Result; /// Returns a vector containing all dependencies that need to be executed /// before this task can function. - fn dependencies( - &self, - ) -> Vec>>; + fn dependencies(&self) -> Vec>; /// Returns a short name used for formatting the dependency tree. fn name(&self) -> String; } /// The dependency tree allows for smart iteration on a Task struct. -pub struct DependencyTree { - task: Box>, - dependencies: Vec>, +pub struct DependencyTree { + task: Box, + dependencies: Vec, } -impl DependencyTree { +impl DependencyTree { /// Renders the dependency tree into a user-presentable string. fn render(&self) -> String { let mut buf = self.task.name(); @@ -62,15 +76,19 @@ impl DependencyTree { } /// Executes this pipeline. - pub fn execute(&mut self, messenger: &Fn(&str, f32)) -> Result { + pub fn execute( + &mut self, + context: &mut InstallerFramework, + messenger: &Fn(&str, f32), + ) -> Result { let total_tasks = (self.dependencies.len() + 1) as f32; - let mut inputs = Vec::::with_capacity(self.dependencies.len()); + let mut inputs = Vec::::with_capacity(self.dependencies.len()); let mut count = 0; for i in &mut self.dependencies { - inputs.push(i.execute(&|msg: &str, progress: f32| { + inputs.push(i.execute(context, &|msg: &str, progress: f32| { messenger( msg, progress / total_tasks + (1.0 / total_tasks) * count as f32, @@ -79,16 +97,17 @@ impl DependencyTree { count += 1; } - self.task.execute(inputs, &|msg: &str, progress: f32| { - messenger( - msg, - progress / total_tasks + (1.0 / total_tasks) * count as f32, - ) - }) + self.task + .execute(inputs, context, &|msg: &str, progress: f32| { + messenger( + msg, + progress / total_tasks + (1.0 / total_tasks) * count as f32, + ) + }) } /// Builds a new pipeline from the specified task, iterating on dependencies. - pub fn build(task: Box>) -> DependencyTree { + pub fn build(task: Box) -> DependencyTree { let dependencies = task .dependencies() .into_iter() @@ -99,7 +118,7 @@ impl DependencyTree { } } -impl Display for DependencyTree { +impl Display for DependencyTree { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!(f, "{}", self.render()) } diff --git a/src/tasks/resolver.rs b/src/tasks/resolver.rs new file mode 100644 index 0000000..2acbb5d --- /dev/null +++ b/src/tasks/resolver.rs @@ -0,0 +1,95 @@ +//! Resolves package names into a metadata + version object. + +use std::env::consts::OS; + +use installer::InstallerFramework; + +use tasks::Task; +use tasks::TaskParamType; + +use config::PackageDescription; + +use regex::Regex; + +pub struct ResolvePackageTask { + pub name: String, +} + +impl Task for ResolvePackageTask { + fn execute( + &mut self, + input: Vec, + context: &mut InstallerFramework, + messenger: &Fn(&str, f32), + ) -> Result { + assert_eq!(input.len(), 0); + let mut metadata: Option = None; + for description in &context.config.packages { + if &self.name == &description.name { + metadata = Some(description.clone()); + break; + } + } + + let package = match metadata { + Some(v) => v, + None => return Err(format!("Package {:?} could not be found.", self.name)), + }; + + messenger( + &format!( + "Polling {} for latest version of {:?}...", + package.source.name, package.name + ), + 0.0, + ); + + let results = package.source.get_current_releases()?; + + messenger( + &format!("Resolving dependency for {:?}...", package.name), + 0.5, + ); + + let filtered_regex = package.source.match_regex.replace("#PLATFORM#", OS); + let regex = match Regex::new(&filtered_regex) { + Ok(v) => v, + Err(v) => return Err(format!("An error occured while compiling regex: {:?}", v)), + }; + + println!("Releases: {:?}", results); + + // Find the latest release in here + let latest_result = results + .into_iter() + .filter(|f| f.files.iter().filter(|x| regex.is_match(&x.name)).count() > 0) + .max_by_key(|f| f.version.clone()); + + let latest_result = match latest_result { + Some(v) => v, + None => return Err(format!("No release with correct file found")), + }; + + let latest_version = latest_result.version.clone(); + + // Find the matching file in here + let latest_file = latest_result + .files + .into_iter() + .filter(|x| regex.is_match(&x.name)) + .next() + .unwrap(); + + println!("{:?}", latest_file); + + Ok(TaskParamType::File(latest_version, latest_file)) + } + + fn dependencies(&self) -> Vec> { + vec![] + } + + fn name(&self) -> String { + format!("ResolvePackageTask (for {:?})", self.name) + } +} diff --git a/src/tasks/save_database.rs b/src/tasks/save_database.rs new file mode 100644 index 0000000..2b1d3ac --- /dev/null +++ b/src/tasks/save_database.rs @@ -0,0 +1,32 @@ +//! Saves the main database into the installation directory. + +use installer::InstallerFramework; + +use tasks::Task; +use tasks::TaskParamType; + +pub struct SaveDatabaseTask {} + +impl Task for SaveDatabaseTask { + fn execute( + &mut self, + input: Vec, + context: &mut InstallerFramework, + messenger: &Fn(&str, f32), + ) -> Result { + assert_eq!(input.len(), 0); + messenger("Saving application database...", 0.0); + + context.save_database()?; + + Ok(TaskParamType::None) + } + + fn dependencies(&self) -> Vec> { + vec![] + } + + fn name(&self) -> String { + format!("SaveDatabaseTask") + } +} diff --git a/src/tasks/save_executable.rs b/src/tasks/save_executable.rs new file mode 100644 index 0000000..bf0d7a8 --- /dev/null +++ b/src/tasks/save_executable.rs @@ -0,0 +1,80 @@ +//! Saves the installer executable into the install directory. + +use installer::InstallerFramework; + +use tasks::Task; +use tasks::TaskParamType; + +use std::fs::File; +use std::fs::OpenOptions; + +use std::io::copy; + +use std::env::current_exe; + +pub struct SaveExecutableTask {} + +impl Task for SaveExecutableTask { + fn execute( + &mut self, + input: Vec, + context: &mut InstallerFramework, + messenger: &Fn(&str, f32), + ) -> Result { + assert_eq!(input.len(), 0); + messenger("Copying installer binary...", 0.0); + + let path = context + .install_path + .as_ref() + .expect("No install path specified"); + + let current_app = match current_exe() { + Ok(v) => v, + Err(v) => return Err(format!("Unable to locate installer binary: {:?}", v)), + }; + + let mut current_app_file = match File::open(current_app) { + Ok(v) => v, + Err(v) => return Err(format!("Unable to open installer binary: {:?}", v)), + }; + + let platform_extension = if cfg!(windows) { + "maintenancetool.exe" + } else { + "maintenancetool" + }; + + let new_app = path.join(platform_extension); + + let mut file_metadata = OpenOptions::new(); + file_metadata.write(true).create_new(true); + + #[cfg(unix)] + { + use std::os::unix::fs::OpenOptionsExt; + + file_metadata.mode(0o770); + } + + let mut new_app_file = match file_metadata.open(new_app) { + Ok(v) => v, + Err(v) => return Err(format!("Unable to open installer binary: {:?}", v)), + }; + + match copy(&mut current_app_file, &mut new_app_file) { + Err(v) => return Err(format!("Unable to copy installer binary: {:?}", v)), + _ => {} + }; + + Ok(TaskParamType::None) + } + + fn dependencies(&self) -> Vec> { + vec![] + } + + fn name(&self) -> String { + format!("SaveExecutableTask") + } +} diff --git a/src/tasks/uninstall_pkg.rs b/src/tasks/uninstall_pkg.rs new file mode 100644 index 0000000..8b6fa6a --- /dev/null +++ b/src/tasks/uninstall_pkg.rs @@ -0,0 +1,37 @@ +//! Uninstalls a specific package. + +use installer::InstallerFramework; + +use tasks::Task; +use tasks::TaskParamType; + +pub struct UninstallPackageTask { + pub name: String, + pub optional: bool, +} + +impl Task for UninstallPackageTask { + fn execute( + &mut self, + _: Vec, + _: &mut InstallerFramework, + messenger: &Fn(&str, f32), + ) -> Result { + messenger(&format!("Uninstalling package {:?}...", self.name), 0.0); + + // TODO: Find files to uninstall, wipe them out, then clean up DB + + Ok(TaskParamType::None) + } + + fn dependencies(&self) -> Vec> { + vec![] + } + + fn name(&self) -> String { + format!( + "UninstallPackageTask (for {:?}, optional = {})", + self.name, self.optional + ) + } +}