diff --git a/config.toml b/config.toml index 5d8c9fb..5f49e8d 100644 --- a/config.toml +++ b/config.toml @@ -5,7 +5,6 @@ installing_message = "Reminder: yuzu is an experimental emulator. Stuff w [[packages]] name = "yuzu Nightly" description = "The nightly build of yuzu contains already reviewed and tested features." -default = true [packages.source] name = "github" match = "^yuzu-#PLATFORM#(-mingw)?-[0-9]*-[0-9a-f]*.zip$" @@ -20,3 +19,13 @@ description = "The canary build of yuzu has additional features that are still w match = "^yuzu-#PLATFORM#(-mingw)?-[0-9]*-[0-9a-f]*.zip$" [packages.source.config] repo = "yuzu-emu/yuzu-canary" + +[[packages]] +name = "Test package" +description = "Just a testing package" +default = true + [packages.source] + name = "github" + match = "^TestPackage.zip$" + [packages.source.config] + repo = "j-selby/test-installer" diff --git a/src/installer.rs b/src/installer.rs index a92dcff..b006b62 100644 --- a/src/installer.rs +++ b/src/installer.rs @@ -19,11 +19,11 @@ use std::env::var; use std::env::current_exe; use std::env::consts::OS; +use std::path::Path; use std::path::PathBuf; use std::io::Cursor; use std::io::copy; -use std::io::Write; use std::sync::Arc; use std::sync::Mutex; @@ -47,6 +47,9 @@ pub enum InstallMessage { /// etc. pub struct InstallerFramework { config: Config, + database: Vec, + install_path: Option, + preexisting_install: bool } /// Used to track the amount of data that has been downloaded during a HTTP request. @@ -83,13 +86,21 @@ impl InstallerFramework { } /// Sends a request for something to be installed. + /// items: Array of named packages to be installed + /// messages: Channel used to send progress messages + /// fresh_install: If the install directory must be empty pub fn install( - &self, + &mut self, items: Vec, - path: &str, messages: &Sender, + fresh_install: bool ) -> Result<(), String> { - // TODO: Error handling + // 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, path); // Create our install directory @@ -106,13 +117,15 @@ impl InstallerFramework { } // Make sure it is empty - let paths = match read_dir(&path) { - Ok(v) => v, - Err(v) => return Err(format!("Failed to read install destination: {:?}", v)), - }; + 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.")); + if paths.count() != 0 { + return Err(format!("Install destination is not empty.")); + } } // Resolve items in config @@ -126,12 +139,12 @@ impl InstallerFramework { 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; - let mut installed_packages = Vec::new(); - for package in to_install.iter() { let base_package_percentage = count / max; let base_package_range = ((count + 1.0) / max) - base_package_percentage; @@ -165,6 +178,8 @@ impl InstallerFramework { 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() @@ -304,7 +319,8 @@ impl InstallerFramework { } // Save metadata about this package - installed_packages.push(LocalInstallation { + // TODO: Check if this package already exists + self.database.push(LocalInstallation { name: package.name.to_owned(), version: latest_version, files: installed_files, @@ -313,22 +329,7 @@ impl InstallerFramework { count += 1.0; } - // Make copy of metadata - // TODO: Combine with already installed database, if needed - let metadata_compiled = serde_json::to_string(&installed_packages).unwrap(); - - { - let metadata_path = path.join("metadata.json"); - let mut metadata_file = match File::create(metadata_path) { - Ok(v) => v, - Err(v) => return Err(format!("Unable to open file handle: {:?}", v)), - }; - - match metadata_file.write_all(metadata_compiled.as_bytes()) { - Ok(v) => v, - Err(v) => return Err(format!("Unable to write to file: {:?}", v)), - }; - } + self.save_database()?; // Copy installer binary to target directory messages @@ -369,8 +370,63 @@ impl InstallerFramework { Ok(()) } + /// 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 + let path = match self.install_path.clone() { + Some(v) => v, + None => return Err(format!("No install directory for installer")) + }; + + let metadata_path = Path::new(&path).join("metadata.json"); + let metadata_file = match File::create(metadata_path) { + Ok(v) => v, + Err(v) => return Err(format!("Unable to open file handle: {:?}", v)), + }; + + match serde_json::to_writer(metadata_file, &self.database) { + Ok(v) => v, + Err(v) => return Err(format!("Unable to write to file: {:?}", v)), + }; + + Ok(()) + } + + /// Configures this installer to install to the specified location. + /// If there was a currently configured install path, this will be left as-is. + pub fn set_install_dir(&mut self, dir : &str) { + self.install_path = Some(dir.to_owned()); + } + /// Creates a new instance of the Installer Framework with a specified Config. pub fn new(config: Config) -> Self { - InstallerFramework { config } + InstallerFramework { + config, + database : Vec::new(), + install_path : None, + preexisting_install : false + } + } + + /// Creates a new instance of the Installer Framework with a specified Config, managing + /// a pre-existing installation. + pub fn new_with_db(config: Config, install_path : String) -> Result { + let metadata_path = Path::new(&install_path).join("metadata.json"); + let metadata_file = match File::open(metadata_path) { + Ok(v) => v, + Err(v) => return Err(format!("Unable to open file handle: {:?}", v)), + }; + + let database : Vec = match serde_json::from_reader(metadata_file) { + Ok(v) => v, + Err(v) => return Err(format!("Unable to read metadata file: {:?}", v)), + }; + + Ok(InstallerFramework { + config, + database, + install_path : Some(install_path), + preexisting_install : true + }) } } diff --git a/src/main.rs b/src/main.rs index 41607ba..e95f601 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,6 +38,7 @@ use config::Config; use installer::InstallerFramework; use rest::WebServer; +use std::path::Path; // TODO: Fetch this over a HTTP request? static RAW_CONFIG: &'static str = include_str!("../config.toml"); @@ -47,7 +48,12 @@ fn main() { let app_name = config.general.name.clone(); - let framework = InstallerFramework::new(config); + let metadata_file = Path::new("metadata.json"); + let framework = if metadata_file.exists() { + InstallerFramework::new_with_db(config, format!("./")).unwrap() + } else { + InstallerFramework::new(config) + }; let server = WebServer::new(framework).unwrap(); diff --git a/src/rest.rs b/src/rest.rs index b1c622b..6e7b9b8 100644 --- a/src/rest.rs +++ b/src/rest.rs @@ -31,6 +31,7 @@ use assets; use installer::InstallerFramework; use installer::InstallMessage; +use std::sync::RwLock; #[derive(Serialize)] struct FileSelection { @@ -63,7 +64,7 @@ impl WebServer { let (sender, receiver) = channel(); let handle = thread::spawn(move || { - let shared_framework = Arc::new(framework); + let shared_framework = Arc::new(RwLock::new(framework)); let server = Http::new() .bind(&addr, move || { @@ -89,7 +90,7 @@ impl WebServer { /// Holds internal state for Hyper struct WebService { - framework: Arc, + framework: Arc>, } impl Service for WebService { @@ -104,9 +105,11 @@ impl Service for WebService { // This endpoint should be usable directly from a