//! ## Auto update //! //! Automatic update module. This module is used to upgrade the current version of termscp to the latest available on Github /** * MIT License * * termscp - Copyright (c) 2021 Christian Visintin * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ use crate::utils::parser::parse_semver; pub use self_update::errors::Error as UpdateError; use self_update::{ backends::github::Update as GithubUpdater, cargo_crate_version, update::Release as UpdRelease, Status, }; /// ### UpdateStatus /// /// The status of the update in case of success #[derive(Debug, Eq, PartialEq)] pub enum UpdateStatus { /// Termscp is already up to date AlreadyUptodate, /// The update has been correctly installed UpdateInstalled(String), } /// ## Release /// /// Info related to a github release #[derive(Debug)] pub struct Release { pub version: String, pub body: String, } /// ## Update /// /// The update structure defines the options used to install the update. /// Once you're fine with the options, just call the `upgrade()` method to upgrade termscp. #[derive(Debug)] pub struct Update { ask_confirm: bool, progress: bool, } impl Update { /// ### show_progress /// /// Set whether to show or not the progress bar pub fn show_progress(mut self, opt: bool) -> Self { self.progress = opt; self } /// ### ask_confirm /// /// Set whether to ask for confirm when updating pub fn ask_confirm(mut self, opt: bool) -> Self { self.ask_confirm = opt; self } pub fn upgrade(self) -> Result { info!("Updating termscp..."); GithubUpdater::configure() // Set default options .repo_owner("veeso") .repo_name("termscp") .bin_name("termscp") .current_version(cargo_crate_version!()) .no_confirm(!self.ask_confirm) .show_download_progress(self.progress) .show_output(self.progress) .build()? .update() .map(UpdateStatus::from) } /// ### is_new_version_available /// /// Returns whether a new version of termscp is available /// In case of success returns Ok(Option), where the Option is Some(new_version); /// otherwise if no version is available, return None /// In case of error returns Error with the error description pub fn is_new_version_available() -> Result, UpdateError> { info!("Checking whether a new version is available..."); GithubUpdater::configure() // Set default options .repo_owner("veeso") .repo_name("termscp") .bin_name("termscp") .current_version(cargo_crate_version!()) .no_confirm(true) .show_download_progress(false) .show_output(false) .build()? .get_latest_release() .map(Release::from) .map(Self::check_version) } /// ### check_version /// /// In case received version is newer than current one, version as Some is returned; otherwise None fn check_version(r: Release) -> Option { match parse_semver(r.version.as_str()) { Some(new_version) => { // Check if version is different debug!( "New version: {}; current version: {}", new_version, cargo_crate_version!() ); if new_version.as_str() > cargo_crate_version!() { Some(r) // New version is available } else { None // No new version } } None => None, } } } impl Default for Update { fn default() -> Self { Self { progress: false, ask_confirm: false, } } } impl From for UpdateStatus { fn from(s: Status) -> Self { match s { Status::UpToDate(_) => Self::AlreadyUptodate, Status::Updated(v) => Self::UpdateInstalled(v), } } } impl From for Release { fn from(r: UpdRelease) -> Self { Self { version: r.version, body: r.body.unwrap_or_default(), } } } #[cfg(test)] mod test { use super::*; use pretty_assertions::assert_eq; #[test] fn auto_update_default() { let upd: Update = Update::default(); assert_eq!(upd.ask_confirm, false); assert_eq!(upd.progress, false); let upd = upd.ask_confirm(true).show_progress(true); assert_eq!(upd.ask_confirm, true); assert_eq!(upd.progress, true); } #[test] #[cfg(not(all(target_os = "macos", feature = "github-actions")))] fn auto_update() { // Wno version assert_eq!( Update::default() .show_progress(true) .upgrade() .ok() .unwrap(), UpdateStatus::AlreadyUptodate, ); } #[test] #[cfg(not(all(target_os = "macos", feature = "github-actions")))] fn check_for_updates() { println!("{:?}", Update::is_new_version_available()); assert!(Update::is_new_version_available().is_ok()); } #[test] fn update_status() { assert_eq!( UpdateStatus::from(Status::Updated(String::from("0.6.0"))), UpdateStatus::UpdateInstalled(String::from("0.6.0")) ); assert_eq!( UpdateStatus::from(Status::UpToDate(String::from("0.6.0"))), UpdateStatus::AlreadyUptodate ); } #[test] fn release() { let release: UpdRelease = UpdRelease { name: String::from("termscp 0.7.0"), version: String::from("0.7.0"), date: String::from("2021-09-12T00:00:00Z"), body: Some(String::from("fixed everything")), assets: vec![], }; let release: Release = Release::from(release); assert_eq!(release.body.as_str(), "fixed everything"); assert_eq!(release.version.as_str(), "0.7.0"); } }