jannie

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit 1bbbcadab128f75ff7904c109ab5371b1aa71daa
parent 650cc623aeb211fa79d44aaf9a00cff2f33000d6
Author: Andy Khramtsov <>
Date:   Tue, 10 Feb 2026 16:07:53 +0300

refactor: error handling

Diffstat:
Msrc/filetree/mod.rs | 5+++++
Msrc/filetree/node.rs | 4++++
Msrc/lib.rs | 190++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/state.rs | 23+++++++++++------------
4 files changed, 127 insertions(+), 95 deletions(-)

diff --git a/src/filetree/mod.rs b/src/filetree/mod.rs @@ -55,6 +55,11 @@ impl<M> Filetree<M> { .map(|node_id| self.nodes.get(node_id).expect("Filetree should have node")) .map(Rc::clone) } + + /// Checks if a node with this path exists + pub fn check_path(&self, path: &Path) -> bool { + self.paths.get(path).is_some() + } } impl<M> Filetree<M> { diff --git a/src/filetree/node.rs b/src/filetree/node.rs @@ -38,6 +38,10 @@ impl<M> Node<M> { &mut self.meta } + pub fn set_meta(&mut self, meta: M) { + self.meta = meta; + } + /// A node is a leaf if it has no children pub fn is_leaf(&self) -> bool { self.children.is_empty() diff --git a/src/lib.rs b/src/lib.rs @@ -1,4 +1,9 @@ -use std::{cell::BorrowError, collections::VecDeque, path::Path}; +use std::{ + cell::{BorrowError, BorrowMutError}, + collections::VecDeque, + path::{Path, StripPrefixError}, + string::FromUtf8Error, +}; use clap::Parser; @@ -31,19 +36,20 @@ pub fn result_main() -> Result<(), Error> { tracing::debug!("Read inventory: {:#?}", state.inventory); - state.runtime.block_on(read(&state)); + state.runtime.block_on(read(&state))?; Ok(()) } +/// This meta is used for display tree. #[derive(Clone, Debug)] struct Meta { inventory: Option<Option<String>>, blacklist: Option<bool>, } -/// Scan workspace -async fn read(state: &State) { +/// Scan workspace and display its tree. +async fn read(state: &State) -> Result<(), Error> { let root = Node::new( state.config.root.clone(), Meta { @@ -53,9 +59,10 @@ async fn read(state: &State) { ); let mut filetree = Filetree::new(root); - let mut buffer = Vec::new(); + let mut processing_buffer = Vec::new(); - buffer.extend( + // Add all whitelist to buffer + processing_buffer.extend( state .config .filters @@ -69,81 +76,78 @@ async fn read(state: &State) { ); // This makes it so every prefix goes first. - buffer.sort(); - - // Always partially sorted - let mut buffer = VecDeque::from(buffer); - - while let Some(dir) = buffer.pop_front() { - tracing::debug!("Adding {:?} to tree", dir); - let mut base = dir.clone(); - while filetree.node_by_path(&base).is_none() { - base.pop(); - } - - let allowed = allowed(state, &dir); - let in_inventory = state - .inventory_tree - .node_by_path(&dir) - .filter(|node| node.borrow().is_leaf()) - .map(|node| node.borrow().meta().clone()) - .flatten() - .map(|check| match check { + processing_buffer.sort(); + + // Has some specific ordering so the tree is built in the correct order. + let mut processing_buffer = VecDeque::from(processing_buffer); + + while let Some(item) = processing_buffer.pop_front() { + tracing::debug!("Adding {:?} to tree", item); + + let allowed_to_scan = allowed_to_scan(state, &item)?; + let inventory_message = (|| -> Result<Option<Option<String>>, Error> { + let Some(node) = state.inventory_tree.node_by_path(&item) else { + return Ok(None); + }; + let node = node.try_borrow()?; + if !node.is_leaf() { + return Ok(None); + } + let Some(check) = node.meta().clone() else { + return Err(Error::NoInventoryCheckInfo); + }; + Ok(Some(match check { inventory::Check::None => None, - inventory::Check::Shell(cmd) => Some( - String::from_utf8( - std::process::Command::new("sh") - .arg("-c") - .arg(cmd) - .output() - .expect("Should finish command") - .stdout, - ) - .expect("Should be utf8 string"), - ), - }); - - if base != dir { - let mut suffix = dir.strip_prefix(&base).unwrap().to_owned(); + inventory::Check::Shell(cmd) => Some(String::from_utf8( + std::process::Command::new("sh") + .arg("-c") + .arg(cmd) + .output() + .map_err(Error::Command)? + .stdout, + )?), + })) + })()?; + + if let Some(node) = filetree.node_by_path(&item) { + node.try_borrow_mut()?.meta_mut().blacklist = Some(!allowed_to_scan); + } else { + let mut base = item.clone(); + while !filetree.check_path(&base) { + base.pop(); + } + let mut suffix = item + .strip_prefix(&base) + .map_err(Error::BasePrefix)? + .to_owned(); suffix.pop(); for component in suffix.components() { base.push(component); - filetree - .insert(Node::new( - base.clone(), - Meta { - blacklist: None, - inventory: None, - }, - )) - .unwrap(); - } - filetree - .insert(Node::new( - dir.clone(), + filetree.insert(Node::new( + base.clone(), Meta { - blacklist: Some(!allowed), - inventory: in_inventory, + blacklist: None, + inventory: None, }, - )) - .unwrap(); - } else { - filetree - .node_by_path(&dir) - .unwrap() - .borrow_mut() - .meta_mut() - .blacklist = Some(!allowed); + ))?; + } + filetree.insert(Node::new( + item.clone(), + Meta { + blacklist: Some(!allowed_to_scan), + inventory: inventory_message, + }, + ))?; } - if allowed && should_scan(state, &dir).unwrap() { - tracing::debug!("Scanning {:?}", dir.file_name()); - let mut iter = tokio::fs::read_dir(&dir).await.unwrap(); + if allowed_to_scan && should_scan(state, &item)? { + tracing::debug!("Scanning {:?}", item.file_name()); + let mut iter = tokio::fs::read_dir(&item).await.map_err(Error::ReadFs)?; let mut dir_buffer = Vec::new(); - while let Some(result) = iter.next_entry().await.unwrap() { + while let Some(result) = iter.next_entry().await.map_err(Error::ReadFs)? { let path = result.path(); dir_buffer.push(path) } @@ -151,12 +155,14 @@ async fn read(state: &State) { dir_buffer.sort(); while let Some(path) = dir_buffer.pop() { - buffer.push_front(path); + processing_buffer.push_front(path); } } } - print(&filetree).unwrap(); + print(&filetree)?; + + Ok(()) } /// Does not check if allowed. @@ -170,20 +176,22 @@ fn should_scan(state: &State, path: &Path) -> Result<bool, Error> { false }; - Ok(state.blacklist_tree.node_by_path(path).is_some() || inventory) + Ok(state.blacklist_tree.check_path(path) || inventory) } /// If a directory is allowed to be scanned. /// Whitelist has more priority than blacklist on the same level of specificity. -fn allowed(state: &State, path: &Path) -> bool { +fn allowed_to_scan(state: &State, path: &Path) -> Result<bool, Error> { // If there is no not-overridden blacklist - let path = path.strip_prefix(&state.config.root).unwrap(); + let path = path + .strip_prefix(&state.config.root) + .map_err(Error::RootPrefix)?; // If no such blacklist filter found that blacklists the path such that // for this blacklist filter no such whitelist found that whitelists the // path back aka for which the blacklist is the prefix - !state + Ok(!state .config .filters .iter() @@ -199,7 +207,7 @@ fn allowed(state: &State, path: &Path) -> bool { path.starts_with(&wl_filter.path) && wl_filter.path.starts_with(&bl_filter.path) }) - }) + })) } fn print(tree: &Filetree<Meta>) -> Result<(), Error> { @@ -207,7 +215,7 @@ fn print(tree: &Filetree<Meta>) -> Result<(), Error> { println!( "{}", tree.root() - .borrow() + .try_borrow()? .path() .to_str() .unwrap_or("UNKNOWN") @@ -215,14 +223,14 @@ fn print(tree: &Filetree<Meta>) -> Result<(), Error> { ); print_buffer.extend( tree.root() - .borrow() + .try_borrow()? .children() .map(|child_id| (1, child_id)), ); while let Some((offset, node_id)) = print_buffer.pop_front() { let node = tree.node(node_id).expect("Shold have the node"); let name = node - .borrow() + .try_borrow()? .path() .file_name() .map(|name| name.to_str()) @@ -230,21 +238,21 @@ fn print(tree: &Filetree<Meta>) -> Result<(), Error> { .unwrap_or("UNKNOWN") .to_owned(); let offset_text = " ".repeat(offset); - if let Some(msg) = node.borrow().meta().inventory.as_ref() { + if let Some(msg) = node.try_borrow()?.meta().inventory.as_ref() { let msg = msg.as_deref().unwrap_or("\x1b[31mno info\x1b[0m"); println!( "{}\x1b[2m|\x1b[0m \x1b[32m/{}\x1b[0m \x1b[3m{}\x1b[0m", offset_text, name, msg, ); - } else if let Some(true) = node.borrow().meta().blacklist { + } else if let Some(true) = node.try_borrow()?.meta().blacklist { println!("{}\x1b[2m|\x1b[0m \x1b[2;9m/{}\x1b[0m", offset_text, name); - } else if node.borrow().is_leaf() { + } else if node.try_borrow()?.is_leaf() { println!("{}\x1b[2m|\x1b[0m \x1b[31m/{}\x1b[0m", offset_text, name); } else { println!("{}\x1b[2m|\x1b[0m /{}", offset_text, name); } for child in node - .borrow() + .try_borrow()? .children() .map(|child_id| (offset + 1, child_id)) .collect::<Vec<_>>() @@ -265,6 +273,22 @@ pub enum Error { State(#[from] state::Error), #[error("Error setting up logging: {0}")] Logging(#[from] logging::Error), + #[error("Error building filetree: {0}")] + Filetree(#[from] filetree::Error), #[error("Borrow error: {0}")] Borrow(#[from] BorrowError), + #[error("Borrow mut error: {0}")] + BorrowMut(#[from] BorrowMutError), + #[error("Error stripping root from path: {0}")] + RootPrefix(StripPrefixError), + #[error("Error stripping base from path: {0}")] + BasePrefix(StripPrefixError), + #[error("No inventory check info on tree node")] + NoInventoryCheckInfo, + #[error("Error executing command: {0}")] + Command(std::io::Error), + #[error("String returned by command is not utf8: {0}")] + StringUtf8(#[from] FromUtf8Error), + #[error("Error reading filesystem: {0}")] + ReadFs(tokio::io::Error), } diff --git a/src/state.rs b/src/state.rs @@ -34,14 +34,13 @@ impl State { .block_on(tokio::fs::read(inventory_path)) .map_err(Error::ReadFile)?; - let inventory: Inventory = match config.inventory_format { - InventoryFormat::Yaml => { - serde_yaml::from_slice(inventory.as_slice()).map_err(Error::Yaml)? - } - InventoryFormat::Json => { - serde_json::from_slice(inventory.as_slice()).map_err(Error::Json)? - } - }; + let inventory: Inventory = + match config.inventory_format { + InventoryFormat::Yaml => serde_yaml::from_slice(inventory.as_slice()) + .map_err(Error::ReadInventoryYaml)?, + InventoryFormat::Json => serde_json::from_slice(inventory.as_slice()) + .map_err(Error::ReadInventoryJson)?, + }; let mut blacklist_tree = Filetree::new(Node::new("/".into(), ())); let mut whitelist_tree = Filetree::new(Node::new("/".into(), ())); @@ -136,10 +135,10 @@ pub enum Error { Runtime(tokio::io::Error), #[error("Error reading file: {0}")] ReadFile(std::io::Error), - #[error("Error parsing yaml: {0}")] - Yaml(serde_yaml::Error), - #[error("Error parsing json: {0}")] - Json(serde_json::Error), + #[error("Error parsing yaml inventory: {0}")] + ReadInventoryYaml(serde_yaml::Error), + #[error("Error parsing json inventory: {0}")] + ReadInventoryJson(serde_json::Error), #[error("Error building filetree: {0}")] Filetree(#[from] filetree::Error), }