diff --git a/wfassoc/src/lib.rs b/wfassoc/src/lib.rs index 6c88ab0..eaf50bf 100644 --- a/wfassoc/src/lib.rs +++ b/wfassoc/src/lib.rs @@ -13,7 +13,7 @@ use assoc::{Ext, ProgId}; use indexmap::{IndexMap, IndexSet}; use regex::Regex; use std::ffi::OsStr; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::sync::LazyLock; use thiserror::Error as TeError; use winreg::RegKey; @@ -157,16 +157,19 @@ impl Program { /// More preciously, `identifier` will be used as the vendor part of ProgId. /// /// `full_path` is the fully qualified path to the application. - pub fn new(identifier: &str, full_path: &Path) -> Result { + pub fn new(identifier: &str, full_path: &str) -> Result { // Check identifier static RE: LazyLock = LazyLock::new(|| Regex::new(r"^[a-zA-Z0-9]*$").unwrap()); if !RE.is_match(identifier) { return Err(Error::BadIdentifier(identifier.to_string())); } + // Everything is okey, build self. Ok(Self { identifier: identifier.to_string(), - full_path: full_path.to_path_buf(), + // The error type of PathBuf FromStr trait is Infallible, + // so it must be okey and we can use unwrap safely. + full_path: full_path.parse().unwrap(), manners: IndexSet::new(), exts: IndexMap::new(), }) @@ -260,6 +263,20 @@ impl Program { } } + // Create ProgId subkeys + debug_println!("Adding ProgId subkey..."); + let subkey_parent = hk.open_subkey_with_flags(Self::CLASSES, KEY_READ)?; + for (ext, manner_token) in self.exts.iter() { + let manner = self.manners.get_index(*manner_token).ok_or(Error::InvalidMannerToken)?; + let prog_id = self.build_prog_id(ext); + + debug_println!("Adding ProgId \"{0}\" subkey...", prog_id.to_string()); + let (subkey, _) = subkey_parent.create_subkey_with_flags(prog_id.to_string(), KEY_READ)?; + let (subkey_verb, _) = subkey.create_subkey_with_flags("open", KEY_READ)?; + let (subkey_command, _) = subkey_verb.create_subkey_with_flags("command", KEY_WRITE)?; + subkey_command.set_value("", manner)?; + } + // Okey utilities::notify_assoc_changed(); Ok(()) @@ -289,6 +306,16 @@ impl Program { let subkey_parent = hk.open_subkey_with_flags(Self::APPLICATIONS, KEY_READ)?; subkey_parent.delete_subkey_all(file_name)?; + // Remove ProgId subkeys + debug_println!("Removing ProgId subkey..."); + let subkey_parent = hk.open_subkey_with_flags(Self::CLASSES, KEY_READ)?; + for ext in self.exts.keys() { + let prog_id = self.build_prog_id(ext); + + debug_println!("Removing ProgId \"{0}\" subkey...", prog_id.to_string()); + subkey_parent.delete_subkey_all(prog_id.to_string())?; + } + // Okey utilities::notify_assoc_changed(); Ok(()) diff --git a/wfassoc_exec/src/cli.rs b/wfassoc_exec/src/cli.rs index 6642ccb..7124fb6 100644 --- a/wfassoc_exec/src/cli.rs +++ b/wfassoc_exec/src/cli.rs @@ -1,10 +1,28 @@ -use clap::{Parser, Subcommand}; +use clap::{Parser, Subcommand, ValueEnum}; +use wfassoc::Scope; + +#[derive(Debug, Clone, ValueEnum)] +pub enum Target { + #[value(name = "user")] + User, + #[value(name = "system")] + System, +} + +impl From for Scope { + fn from(target: Target) -> Self { + match target { + Target::User => Scope::User, + Target::System => Scope::System, + } + } +} /// Simple program to manage Windows file associations #[derive(Parser)] #[command(name = "Windows File Association Operator", version, about)] pub struct Cli { - /// The toml file introducing the complete program + /// The TOML file representing the complete program #[arg( short = 'c', long = "config", @@ -13,40 +31,30 @@ pub struct Cli { )] pub(crate) config_file: String, - /// The scope where wfassoc operate - #[arg(short = 'f', long = "for", value_name = "TARGET", value_enum, default_value_t = ForTarget::User)] - pub(crate) for_which: ForTarget, - #[command(subcommand)] pub(crate) command: Commands, } -#[derive(clap::ValueEnum, Clone)] -pub enum ForTarget { - #[value(name = "user")] - User, - #[value(name = "system")] - System, -} - -// impl From for RegisterKind { -// fn from(target: ForTarget) -> Self { -// match target { -// ForTarget::User => RegisterKind::User, -// ForTarget::System => RegisterKind::System, -// } -// } -// } - #[derive(Subcommand)] pub enum Commands { /// Register the program #[command(name = "register")] - Register, + #[command(about = "Register application with given manifest and scope.")] + Register { + /// The scope where wfassoc operate + #[arg(short = 't', long = "target", value_name = "TARGET", value_enum, default_value_t = Target::User)] + target: Target, + }, /// Unregister the program #[command(name = "unregister")] - Unregister, + #[command(about = "Unregister application with given manifest and scope.")] + Unregister { + /// The scope where wfassoc operate + #[arg(short = 't', long = "target", value_name = "TARGET", value_enum, default_value_t = Target::User)] + target: Target, + }, /// Query file associations #[command(name = "query")] + #[command(about = "Query file extensions association infos according toh given manifest represented application.")] Query, } diff --git a/wfassoc_exec/src/main.rs b/wfassoc_exec/src/main.rs index 9b50984..344bc3f 100644 --- a/wfassoc_exec/src/main.rs +++ b/wfassoc_exec/src/main.rs @@ -2,13 +2,11 @@ pub(crate) mod cli; pub(crate) mod manifest; use clap::Parser; -use cli::{Cli, Commands}; +use cli::{Cli, Commands, Target}; use comfy_table::Table; use manifest::Manifest; use std::collections::HashMap; -use std::path::PathBuf; use std::process; -use std::str::FromStr; use thiserror::Error as TeError; use wfassoc::{Program, Scope, Token, View}; @@ -46,10 +44,7 @@ impl Composition { // 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(), - )?; + let mut program = Program::new(&mf.identifier, &cli.config_file)?; // Setup manner let mut manner_token_map: HashMap<&str, Token> = HashMap::new(); for (k, v) in mf.manners.iter() { @@ -82,25 +77,40 @@ impl Composition { &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() } } -fn run_register(cli: &Cli) -> Result<()> { - // let program = Program::new(); - // let kind: RegisterKind = cli.for_which.into(); - // program.register(kind)?; +fn run_register(cli: &Cli, target: &Target) -> Result<()> { + let composition = Composition::new(cli)?; + let scope = target.clone().into(); + + // Register program + let program = composition.get_program(); + program.register(scope)?; + // Link all file extensions + for token in composition.iter_ext_tokens() { + program.link_ext(token, scope)?; + } + + println!("Register OK."); Ok(()) } -fn run_unregister(cli: &Cli) -> Result<()> { - // let program = Program::new(); - // program.unregister()?; +fn run_unregister(cli: &Cli, target: &Target) -> Result<()> { + let composition = Composition::new(cli)?; + let scope = target.clone().into(); + + // Unlink all file extensions + let program = composition.get_program(); + for token in composition.iter_ext_tokens() { + program.unlink_ext(token, scope)?; + } + // Unregister prorgam + program.unregister(scope)?; + + println!("Unregister OK."); Ok(()) } @@ -140,8 +150,8 @@ fn main() { let cli = Cli::parse(); let rv = match &cli.command { - Commands::Register => run_register(&cli), - Commands::Unregister => run_unregister(&cli), + Commands::Register { target } => run_register(&cli, target), + Commands::Unregister { target } => run_unregister(&cli, target), Commands::Query => run_query(&cli), }; rv.unwrap_or_else(|e| {