Skip to content

Commit

Permalink
refactor: move command processing to cmd module
Browse files Browse the repository at this point in the history
Move the command processing code into its own module.

This keeps the `main` module clean of utility code, allowing it to just define what the commands are
and provide a high-level match statement for each of them.

Also remove some text from the documentation of the `perform_update_assessment` function which did
not apply after removing support for different privilege levels.
  • Loading branch information
jacderida authored and joshuef committed Jul 17, 2023
1 parent 0708bcc commit 3e9395d
Show file tree
Hide file tree
Showing 3 changed files with 282 additions and 273 deletions.
278 changes: 278 additions & 0 deletions src/cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
// Copyright 2023 MaidSafe.net limited.
//
// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

use crate::github::GithubReleaseRepository;
use crate::install::{AssetType, Settings};
use crate::s3::S3AssetRepository;
use crate::update::{perform_update_assessment, UpdateAssessmentResult};
use color_eyre::{eyre::eyre, Result};
use lazy_static::lazy_static;
use prettytable::{Cell, Row, Table};
use std::collections::HashMap;
use std::env::consts::{ARCH, OS};
use std::path::PathBuf;

const GITHUB_API_URL: &str = "https://api.github.com";
const ORG_NAME: &str = "maidsafe";
const REPO_NAME: &str = "safe_network";
const WRAP_LENGTH: usize = 80;

lazy_static! {
static ref ASSET_TYPE_BUCKET_MAP: HashMap<AssetType, &'static str> = {
let mut m = HashMap::new();
m.insert(
AssetType::Client,
"https://sn-cli.s3.eu-west-2.amazonaws.com",
);
m.insert(
AssetType::Node,
"https://sn-node.s3.eu-west-2.amazonaws.com",
);
m.insert(
AssetType::Testnet,
"https://sn-testnet.s3.eu-west-2.amazonaws.com",
);
m
};
}

pub(crate) async fn process_install_cmd(
asset_type: AssetType,
custom_path: Option<PathBuf>,
version: Option<String>,
no_modify_shell_profile: bool,
) -> Result<()> {
let safe_config_dir_path = get_safe_config_dir_path()?;
let dest_dir_path = if let Some(path) = custom_path {
path
} else {
get_default_install_path()?
};

do_install_binary(&asset_type, dest_dir_path.clone(), version).await?;

if !no_modify_shell_profile {
crate::install::configure_shell_profile(
&dest_dir_path.clone(),
&get_shell_profile_path()?,
&safe_config_dir_path.join("env"),
)
.await?
}

Ok(())
}

pub(crate) async fn process_update_cmd() -> Result<()> {
let platform = get_platform()?;
let safe_config_dir_path = get_safe_config_dir_path()?;
let settings_file_path = safe_config_dir_path.join("safeup.json");
let settings = Settings::read(&settings_file_path)?;
let release_repository = GithubReleaseRepository::new(GITHUB_API_URL, ORG_NAME, REPO_NAME);
for asset_type in AssetType::variants() {
println!("Retrieving latest version for {asset_type}...");
let (_, latest_version) = release_repository
.get_latest_asset_name(&asset_type, &platform)
.await?;
println!("Latest version of {asset_type} is {latest_version}");
if settings.is_installed(&asset_type) {
println!(
"Current version of {asset_type} is {}",
settings.get_installed_version(&asset_type)
);
}
let decision = perform_update_assessment(&asset_type, &latest_version, &settings)?;
match decision {
UpdateAssessmentResult::PerformUpdate => {
println!("Updating {asset_type} to {latest_version}...");
let installed_path = settings.get_install_path(&asset_type).clone();
let installed_dir_path = installed_path
.parent()
.ok_or_else(|| eyre!("could not retrieve parent directory"))?;
do_install_binary(
&asset_type,
installed_dir_path.to_path_buf(),
Some(latest_version),
)
.await?;
}
UpdateAssessmentResult::AtLatestVersion => {
println!(
"{asset_type} is already at {latest_version}, \
which is the latest version."
);
println!("No update will be performed.")
}
UpdateAssessmentResult::NoPreviousInstallation => {
println!("There is no previous installation for the {asset_type} component.");
println!("No update will be performed.")
}
}
println!();
}
Ok(())
}

pub(crate) fn process_ls_command() -> Result<()> {
let safe_config_dir_path = get_safe_config_dir_path()?;
let settings_file_path = safe_config_dir_path.join("safeup.json");
let settings = Settings::read(&settings_file_path)?;
let mut table = Table::new();
table.add_row(Row::new(vec![
Cell::new("Name"),
Cell::new("Version"),
Cell::new("Path"),
]));
for asset_type in AssetType::variants() {
let installed_path = settings.get_install_path(&asset_type);
let wrapped_install_path = textwrap::wrap(
installed_path
.to_str()
.ok_or_else(|| eyre!("could not obtain install path"))?,
WRAP_LENGTH,
)
.join("\n");
table.add_row(Row::new(vec![
Cell::new(&asset_type.to_string()),
Cell::new(&settings.get_installed_version(&asset_type)),
Cell::new(&wrapped_install_path),
]));
}
table.printstd();
Ok(())
}

async fn do_install_binary(
asset_type: &AssetType,
dest_dir_path: PathBuf,
version: Option<String>,
) -> Result<()> {
let platform = get_platform()?;
let asset_repository = S3AssetRepository::new(ASSET_TYPE_BUCKET_MAP[asset_type]);
let release_repository = GithubReleaseRepository::new(GITHUB_API_URL, ORG_NAME, REPO_NAME);
let (installed_version, bin_path) = crate::install::install_bin(
asset_type.clone(),
release_repository,
asset_repository,
&platform,
dest_dir_path.clone(),
version,
)
.await?;

let safe_config_dir_path = get_safe_config_dir_path()?;
let settings_file_path = safe_config_dir_path.join("safeup.json");
let mut settings = Settings::read(&settings_file_path)?;
match asset_type {
AssetType::Client => {
settings.safe_path = bin_path;
settings.safe_version = installed_version;
}
AssetType::Node => {
settings.safenode_path = bin_path;
settings.safenode_version = installed_version;
}
AssetType::Testnet => {
settings.testnet_path = bin_path;
settings.testnet_version = installed_version;
}
}
settings.save(&settings_file_path)?;

Ok(())
}

fn get_platform() -> Result<String> {
match OS {
"linux" => match ARCH {
"x86_64" => Ok(format!("{}-unknown-{}-musl", ARCH, OS)),
"armv7" => Ok(format!("{}-unknown-{}-musleabihf", ARCH, OS)),
"arm" => Ok(format!("{}-unknown-{}-musleabi", ARCH, OS)),
"aarch64" => Ok(format!("{}-unknown-{}-musl", ARCH, OS)),
&_ => Err(eyre!(
"We currently do not have binaries for the {OS}/{ARCH} combination"
)),
},
"windows" => {
if ARCH != "x86_64" {
return Err(eyre!(
"We currently only have x86_64 binaries available for Windows"
));
}
Ok(format!("{}-pc-{}-msvc", ARCH, OS))
}
"macos" => {
if ARCH != "x86_64" {
println!(
"We currently only have x86_64 binaries available for macOS. On Mx Macs, Rosetta will run these x86_64 binaries."
);
}
Ok(format!("{}-apple-darwin", ARCH))
}
&_ => Err(eyre!("{OS} is not currently supported by safeup")),
}
}

#[cfg(target_os = "linux")]
fn get_shell_profile_path() -> Result<PathBuf> {
let home_dir_path =
dirs_next::home_dir().ok_or_else(|| eyre!("Could not retrieve user's home directory"))?;
Ok(home_dir_path.join(".bashrc"))
}

/// We won't actually end up doing anything on Windows with the shell profile, so we can just
/// return back the home directory.
#[cfg(target_os = "windows")]
fn get_shell_profile_path() -> Result<PathBuf> {
let home_dir_path =
dirs_next::home_dir().ok_or_else(|| eyre!("Could not retrieve user's home directory"))?;
Ok(home_dir_path.to_path_buf())
}

/// On macOS, it's not necessarily the case that ZSH will be the default shell.
/// On GHA they are running Bash.
#[cfg(target_os = "macos")]
fn get_shell_profile_path() -> Result<PathBuf> {
let profile_file_name = match std::env::var("SHELL") {
Ok(shell) => match shell.as_str() {
"/bin/bash" => ".bashrc",
"/bin/zsh" => ".zshrc",
_ => return Err(eyre!("shell {shell} is not supported by safeup")),
},
Err(e) => return Err(eyre!(e)),
};
let home_dir_path =
dirs_next::home_dir().ok_or_else(|| eyre!("Could not retrieve user's home directory"))?;
Ok(home_dir_path.join(profile_file_name))
}

fn get_safe_config_dir_path() -> Result<PathBuf> {
let config_dir_path = dirs_next::config_dir()
.ok_or_else(|| eyre!("Could not retrieve user's config directory"))?;
let safe_config_dir_path = config_dir_path.join("safe");
std::fs::create_dir_all(safe_config_dir_path.clone())?;
Ok(safe_config_dir_path)
}

#[cfg(target_os = "windows")]
fn get_default_install_path() -> Result<PathBuf> {
let home_dir_path =
dirs_next::home_dir().ok_or_else(|| eyre!("Could not retrieve user's home directory"))?;
let safe_dir_path = home_dir_path.join("safe");
std::fs::create_dir_all(safe_dir_path.clone())?;
Ok(safe_dir_path)
}

#[cfg(target_family = "unix")]
fn get_default_install_path() -> Result<PathBuf> {
let home_dir_path =
dirs_next::home_dir().ok_or_else(|| eyre!("Could not retrieve user's home directory"))?;
let safe_dir_path = home_dir_path.join(".local").join("bin");
std::fs::create_dir_all(safe_dir_path.clone())?;
Ok(safe_dir_path)
}
Loading

0 comments on commit 3e9395d

Please sign in to comment.