diff --git a/.gitignore b/.gitignore index ea8c4bf..2e0ed6d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ +/.vscode /target diff --git a/wfassoc/src/assoc.rs b/wfassoc/src/assoc.rs index ceb083f..da2adcf 100644 --- a/wfassoc/src/assoc.rs +++ b/wfassoc/src/assoc.rs @@ -98,8 +98,8 @@ impl From<&str> for ProgId { impl Display for ProgId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ProgId::Other(v) => v.fmt(f), - ProgId::Std(v) => v.fmt(f), + ProgId::Other(v) => write!(f, "{}", v), + ProgId::Std(v) => write!(f, "{}", v), } } } diff --git a/wfassoc/src/lib.rs b/wfassoc/src/lib.rs index 59dd723..6c88ab0 100644 --- a/wfassoc/src/lib.rs +++ b/wfassoc/src/lib.rs @@ -9,6 +9,7 @@ pub mod utilities; pub mod wincmd; pub mod winreg_extra; +use assoc::{Ext, ProgId}; use indexmap::{IndexMap, IndexSet}; use regex::Regex; use std::ffi::OsStr; @@ -19,7 +20,6 @@ use winreg::RegKey; use winreg::enums::{ HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_READ, KEY_WRITE, }; -use assoc::{Ext, ProgId}; // region: Error Types @@ -327,7 +327,7 @@ impl Program { impl Program { const CLASSES: &str = "Software\\Classes"; - /// Set the default "open with" of given extension to this program. + /// Set the default "open with" of given token associated extension to this program. pub fn link_ext(&self, ext: Token, scope: Scope) -> Result<()> { // Check privilege if !scope.has_privilege() { @@ -357,10 +357,10 @@ impl Program { Ok(()) } - /// Remove this program from the default "open with" of given extension. + /// Remove this program from the default "open with" of given token associated extension. /// /// If the default "open with" of given extension is not our program, - /// this function do nothing. + /// or there is no such file extension, this function do nothing. pub fn unlink_ext(&self, ext: Token, scope: Scope) -> Result<()> { // Check privilege if !scope.has_privilege() { @@ -398,6 +398,44 @@ impl Program { // Okey Ok(()) } + + /// Query the default "open with" of given token associated extension. + /// + /// This function will return its associated ProgId if it is existing. + pub fn query_ext(&self, ext: Token, view: View) -> Result> { + // Fetch file extension + let (ext, _) = match self.exts.get_index(ext) { + Some(v) => v, + None => return Err(Error::InvalidExtToken), + }; + + // Fetch root key and navigate to Classes + let hk = match view { + View::User => RegKey::predef(HKEY_CURRENT_USER), + View::System => RegKey::predef(HKEY_LOCAL_MACHINE), + View::Hybrid => RegKey::predef(HKEY_CLASSES_ROOT), + }; + let classes = match view { + View::User | View::System => hk.open_subkey_with_flags(Self::CLASSES, KEY_READ)?, + View::Hybrid => hk.open_subkey_with_flags("", KEY_READ)?, + }; + + // Open key for this extension if possible + let rv = + match winreg_extra::try_open_subkey_with_flags(&classes, ext.to_string(), KEY_READ)? { + Some(subkey) => { + // Try get associated ProgId if possible + match winreg_extra::try_get_value::(&subkey, "")? { + Some(value) => Some(ProgId::from(value.as_str())), + None => None, + } + } + None => None, + }; + + // Okey + Ok(rv) + } } impl Program { @@ -429,7 +467,7 @@ impl Program { ProgId::Std(assoc::StdProgId::new( &self.identifier, &utilities::capitalize_first_ascii(ext.inner()), - None + None, )) } } diff --git a/wfassoc_exec/src/main.rs b/wfassoc_exec/src/main.rs index 1048207..9b50984 100644 --- a/wfassoc_exec/src/main.rs +++ b/wfassoc_exec/src/main.rs @@ -2,14 +2,15 @@ pub(crate) mod cli; pub(crate) mod manifest; use clap::Parser; +use cli::{Cli, Commands}; +use comfy_table::Table; +use manifest::Manifest; use std::collections::HashMap; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::process; use std::str::FromStr; use thiserror::Error as TeError; -use wfassoc::{Program, Token}; -use cli::{Cli, Commands}; -use manifest::Manifest; +use wfassoc::{Program, Scope, Token, View}; // region: Basic Types @@ -25,10 +26,7 @@ enum Error { /// Error when specifying invalid manner name for extension. #[error("extension {ext} associated manner {manner} is invalid in manifest file")] - InvalidMannerName { - manner: String, - ext: String - }, + InvalidMannerName { manner: String, ext: String }, } /// Result type used in this executable. @@ -38,27 +36,59 @@ type Result = std::result::Result; // region: Correponding Runner -fn build_program(cli: &Cli) -> Result { - // Open file and read manifest TOML file - let mf = Manifest::from_file(&cli.config_file)?; - // Create instance - let mut rv = Program::new(&mf.identifier, PathBuf::from_str(&cli.config_file).unwrap().as_path())?; - // Setup manner - let mut manners: HashMap<&str, Token> = HashMap::new(); - for (k, v) in mf.manners.iter() { - let token = rv.add_manner(v.as_str())?; - manners.insert(k.as_str(), token); +struct Composition { + program: Program, + ext_tokens: Vec, +} + +impl Composition { + pub fn new(cli: &Cli) -> Result { + // Open file and read manifest TOML file + let mf = Manifest::from_file(&cli.config_file)?; + // Create instance + let mut program = Program::new( + &mf.identifier, + PathBuf::from_str(&cli.config_file).unwrap().as_path(), + )?; + // Setup manner + let mut manner_token_map: HashMap<&str, Token> = HashMap::new(); + for (k, v) in mf.manners.iter() { + let token = program.add_manner(v.as_str())?; + manner_token_map.insert(k.as_str(), token); + } + // Setup extension + let mut ext_tokens = Vec::new(); + for (k, v) in mf.exts.iter() { + let token = match manner_token_map.get(v.as_str()) { + Some(v) => v, + None => { + return Err(Error::InvalidMannerName { + manner: v.to_string(), + ext: k.clone(), + }); + } + }; + let token = program.add_ext(k.as_str(), *token)?; + ext_tokens.push(token); + } + // Okey + Ok(Self { + program, + ext_tokens, + }) } - // Setup extension - for (k, v) in mf.exts.iter() { - let token = match manners.get(v.as_str()) { - Some(v) => v, - None => return Err(Error::InvalidMannerName { manner: v.to_string(), ext: k.clone() }), - }; - rv.add_ext(k.as_str(), *token)?; + + pub fn get_program(&self) -> &Program { + &self.program + } + + pub fn get_mut_program(&mut self) -> &mut Program { + &mut self.program + } + + pub fn iter_ext_tokens(&self) -> impl Iterator { + self.ext_tokens.iter().copied() } - // Okey - Ok(rv) } fn run_register(cli: &Cli) -> Result<()> { @@ -75,8 +105,32 @@ fn run_unregister(cli: &Cli) -> Result<()> { } fn run_query(cli: &Cli) -> Result<()> { - let program = build_program(cli)?; - print!("{:?}", program); + let composition = Composition::new(cli)?; + + let mut table = Table::new(); + table.set_header(["Extension", "Hybrid", "User", "System"]); + + let program = composition.get_program(); + for token in composition.iter_ext_tokens() { + let cell_ext = program.get_ext_str(token).unwrap(); + let cell_hybrid = program + .query_ext(token, View::Hybrid)? + .map(|pi| pi.to_string()) + .unwrap_or_default(); + let cell_user = program + .query_ext(token, View::User)? + .map(|pi| pi.to_string()) + .unwrap_or_default(); + let cell_system = program + .query_ext(token, View::System)? + .map(|pi| pi.to_string()) + .unwrap_or_default(); + + table.add_row([cell_ext, cell_hybrid, cell_user, cell_system]); + } + + println!("{table}"); + Ok(()) }