Skip to content

Commit

Permalink
Implement trust subcommand and committing home
Browse files Browse the repository at this point in the history
  • Loading branch information
filiptibell committed Mar 22, 2024
1 parent 540dca8 commit 0172406
Show file tree
Hide file tree
Showing 12 changed files with 181 additions and 56 deletions.
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ default-run = "aftman"
[dependencies]
anyhow = "1.0"
clap = { version = "4.5", features = ["derive"] }
dashmap = "5.5"
dialoguer = "0.11"
dirs = "5.0"
futures = "0.3"
Expand Down
61 changes: 43 additions & 18 deletions lib/storage/home.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use std::env::var;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

use tokio::fs::read_to_string;

use super::{StorageError, StorageResult, TrustStorage};

/**
Expand All @@ -17,49 +16,75 @@ use super::{StorageError, StorageResult, TrustStorage};
#[derive(Debug, Clone)]
pub struct Home {
path: Arc<Path>,
saved: Arc<AtomicBool>,
trust: TrustStorage,
}

impl Home {
/**
Creates a new `Home` from the given path.
*/
fn from_path(path: impl Into<PathBuf>) -> Self {
Self {
path: path.into().into(),
}
async fn load_from_path(path: impl Into<PathBuf>) -> StorageResult<Self> {
let path: Arc<Path> = path.into().into();
let saved = Arc::new(AtomicBool::new(false));

let trust = TrustStorage::load(&path).await?;

Ok(Self { path, saved, trust })
}

/**
Creates a new `Home` from the environment.
This will read, and if necessary, create the Aftman home directory
and its contents - including trust storage, tools storage, etc.
If the `AFTMAN_ROOT` environment variable is set, this will use
that as the home directory. Otherwise, it will use `$HOME/.aftman`.
*/
pub fn from_env() -> StorageResult<Self> {
pub async fn load_from_env() -> StorageResult<Self> {
Ok(match var("AFTMAN_ROOT") {
Ok(root_str) => Self::from_path(root_str),
Ok(root_str) => Self::load_from_path(root_str).await?,
Err(_) => {
let path = dirs::home_dir()
.ok_or(StorageError::HomeNotFound)?
.join(".aftman");

Self::from_path(path)
Self::load_from_path(path).await?
}
})
}

/**
Reads the trust storage for this `Home`.
Returns a reference to the `TrustStorage` for this `Home`.
*/
pub fn trust(&self) -> &TrustStorage {
&self.trust
}

This function will return an error if the trust storage file
cannot be read - if it does not exist, it will be created.
/**
Saves the contents of this `Home` to disk.
*/
pub async fn trust_storage(&self) -> StorageResult<TrustStorage> {
let path = self.path.join("trusted.txt");
match read_to_string(&path).await {
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(TrustStorage::new()),
Err(e) => Err(e.into()),
Ok(s) => Ok(TrustStorage::from_str(s)),
pub async fn save(&self) -> StorageResult<()> {
self.trust.save(&self.path).await?;
self.saved.store(true, Ordering::SeqCst);
Ok(())
}
}

// Implement Drop with an error message if the Home was dropped
// without being saved - this should never happen since a Home
// should always be loaded once on startup and saved on shutdown
// in the CLI, but this detail may be missed during refactoring.
// In the future, if AsyncDrop ever becomes a thing, we can just
// force the save to happen in the Drop implementation instead.
impl Drop for Home {
fn drop(&mut self) {
if !self.saved.load(Ordering::SeqCst) {
tracing::error!(
"Aftman home was dropped without being saved!\
\nChanges to trust, tools, and more may have been lost."
)
}
}
}
23 changes: 23 additions & 0 deletions lib/storage/load_and_save.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use std::path::Path;

use tokio::fs::{read_to_string, write};

use super::{StorageResult, TrustStorage};

const FILE_PATH_TRUST: &str = "trusted.txt";

impl TrustStorage {
pub(super) async fn load(home_path: impl AsRef<Path>) -> StorageResult<Self> {
let path = home_path.as_ref().join(FILE_PATH_TRUST);
match read_to_string(&path).await {
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(TrustStorage::new()),
Err(e) => Err(e.into()),
Ok(s) => Ok(TrustStorage::from_str(s)),
}
}

pub(super) async fn save(&self, home_path: impl AsRef<Path>) -> StorageResult<()> {
let path = home_path.as_ref().join(FILE_PATH_TRUST);
Ok(write(path, self.to_string()).await?)
}
}
1 change: 1 addition & 0 deletions lib/storage/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod home;
mod load_and_save;
mod result;
mod trust;

Expand Down
34 changes: 20 additions & 14 deletions lib/storage/trust.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
#![allow(clippy::should_implement_trait)]
#![allow(clippy::inherent_to_string)]

use std::{collections::BTreeSet, convert::Infallible, str::FromStr};
use std::{convert::Infallible, str::FromStr, sync::Arc};

use dashmap::DashSet;

use crate::tool::ToolId;

/**
Storage for trusted tool identifiers.
*/
#[derive(Debug, Default)]
#[derive(Debug, Default, Clone)]
pub struct TrustStorage {
tools: BTreeSet<ToolId>,
tools: Arc<DashSet<ToolId>>,
}

impl TrustStorage {
Expand All @@ -35,16 +37,18 @@ impl TrustStorage {
.as_ref()
.lines()
.filter_map(|line| line.parse::<ToolId>().ok())
.collect();
Self { tools }
.collect::<DashSet<_>>();
Self {
tools: Arc::new(tools),
}
}

/**
Add a tool to this `TrustStorage`.
Returns `true` if the tool was added and not already trusted.
*/
pub fn add_tool(&mut self, tool: ToolId) -> bool {
pub fn add_tool(&self, tool: ToolId) -> bool {
self.tools.insert(tool)
}

Expand All @@ -53,8 +57,8 @@ impl TrustStorage {
Returns `true` if the tool was previously trusted and has now been removed.
*/
pub fn remove_tool(&mut self, tool: &ToolId) -> bool {
self.tools.remove(tool)
pub fn remove_tool(&self, tool: &ToolId) -> bool {
self.tools.remove(tool).is_some()
}

/**
Expand All @@ -65,10 +69,12 @@ impl TrustStorage {
}

/**
Get an iterator over the tools in this `TrustStorage`.
Get a sorted copy of the trusted tools in this `TrustStorage`.
*/
pub fn iter_tools(&self) -> impl Iterator<Item = &ToolId> {
self.tools.iter()
pub fn all_tools(&self) -> Vec<ToolId> {
let mut sorted_tools = self.tools.iter().map(|id| id.clone()).collect::<Vec<_>>();
sorted_tools.sort();
sorted_tools
}

/**
Expand All @@ -78,9 +84,9 @@ impl TrustStorage {
*/
pub fn to_string(&self) -> String {
let mut contents = self
.tools
.iter()
.map(ToString::to_string)
.all_tools()
.into_iter()
.map(|id| id.to_string())
.collect::<Vec<_>>()
.join("\n");
contents.push('\n');
Expand Down
8 changes: 4 additions & 4 deletions src/cli/debug_system_info.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use anyhow::Result;
use clap::Parser;

use aftman::system::Description;
use aftman::{storage::Home, system::Description};

/// Prints out information about the system detected by Aftman.
#[derive(Debug, Parser)]
pub struct GetSystemInfoSubcommand {}
pub struct DebugSystemInfoSubcommand {}

impl GetSystemInfoSubcommand {
pub async fn run(&self) -> Result<()> {
impl DebugSystemInfoSubcommand {
pub async fn run(&self, _home: &Home) -> Result<()> {
let desc = Description::current();
println!("Current system information:");
println!("{desc:#?}");
Expand Down
10 changes: 4 additions & 6 deletions src/cli/debug_trusted_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@ use aftman::storage::Home;

/// Prints out information about currently trusted tools.
#[derive(Debug, Parser)]
pub struct GetTrustedToolsSubcommand {}
pub struct DebugTrustedToolsSubcommand {}

impl GetTrustedToolsSubcommand {
pub async fn run(&self) -> Result<()> {
let home = Home::from_env()?;
let storage = home.trust_storage().await?;
impl DebugTrustedToolsSubcommand {
pub async fn run(&self, home: &Home) -> Result<()> {
println!("Trusted tools:");
for tool in storage.iter_tools() {
for tool in home.trust().all_tools() {
println!("{tool}");
}
Ok(())
Expand Down
4 changes: 3 additions & 1 deletion src/cli/list.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use anyhow::Result;
use clap::Parser;

use aftman::storage::Home;

/// Lists all existing tools managed by Aftman.
#[derive(Debug, Parser)]
pub struct ListSubcommand {}

impl ListSubcommand {
pub async fn run(&self) -> Result<()> {
pub async fn run(&self, _home: &Home) -> Result<()> {
Ok(())
}
}
29 changes: 20 additions & 9 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
use aftman::storage::Home;
use anyhow::Result;
use clap::Parser;

mod debug_system_info;
mod debug_trusted_tools;
mod list;
mod trust;

use self::debug_system_info::GetSystemInfoSubcommand;
use self::debug_trusted_tools::GetTrustedToolsSubcommand;
use self::debug_system_info::DebugSystemInfoSubcommand;
use self::debug_trusted_tools::DebugTrustedToolsSubcommand;
use self::list::ListSubcommand;
use self::trust::TrustSubcommand;

#[derive(Debug, Parser)]
#[clap(author, version, about)]
Expand All @@ -26,21 +29,29 @@ impl Args {
pub enum Subcommand {
// Hidden subcommands (for debugging)
#[clap(hide = true)]
DebugSystemInfo(GetSystemInfoSubcommand),
DebugSystemInfo(DebugSystemInfoSubcommand),
#[clap(hide = true)]
DebugTrustedTools(GetTrustedToolsSubcommand),
DebugTrustedTools(DebugTrustedToolsSubcommand),
// Public subcommands
List(ListSubcommand),
Trust(TrustSubcommand),
}

impl Subcommand {
pub async fn run(self) -> Result<()> {
match self {
let home = Home::load_from_env().await?;

let result = match self {
// Hidden subcommands
Self::DebugSystemInfo(cmd) => cmd.run().await,
Self::DebugTrustedTools(cmd) => cmd.run().await,
Self::DebugSystemInfo(cmd) => cmd.run(&home).await,
Self::DebugTrustedTools(cmd) => cmd.run(&home).await,
// Public subcommands
Self::List(cmd) => cmd.run().await,
}
Self::List(cmd) => cmd.run(&home).await,
Self::Trust(cmd) => cmd.run(&home).await,
};

home.save().await?;

result
}
}
Loading

0 comments on commit 0172406

Please sign in to comment.