diff --git a/wfassoc/src/assoc_old.rs b/wfassoc/src/assoc_old.rs deleted file mode 100644 index a461272..0000000 --- a/wfassoc/src/assoc_old.rs +++ /dev/null @@ -1,391 +0,0 @@ -//! The module including all struct representing Windows file association concept, -//! like file extension, ProgId, CLSID and etc. - -use crate::extra::windows::{Ext, ProgId}; -use crate::extra::winreg as winreg_extra; -use crate::utilities; -use std::fmt::Display; -use std::str::FromStr; -use thiserror::Error as TeError; -use winreg::RegKey; -use winreg::enums::{ - HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_READ, KEY_WRITE, -}; - -// region: Error Types - -/// All possible error occurs in this crate. -#[derive(Debug, TeError)] -pub enum Error { - #[error("error occurs when manipulating with Registry: {0}")] - BadRegOper(#[from] std::io::Error), - #[error("{0}")] - ParseExt(#[from] crate::extra::windows::ParseExtError), - #[error("{0}")] - ParseProgId(#[from] crate::extra::windows::ParseProgIdError), - #[error("{0}")] - ParseProgIdKind(#[from] ParseProgIdKindError), - #[error("{0}")] - BlankPath(#[from] crate::extra::winreg::BlankPathError), -} - -/// The result type used in this crate. -pub type Result = std::result::Result; - -// endregion - -// region: Data Types - -/// The token for access registered items in Program. -/// This is usually returned when you registering them. -pub type Token = usize; - -// region: Scope - -/// The scope where wfassoc will register and unregister application. -#[derive(Debug, Copy, Clone)] -pub enum Scope { - /// Scope for current user. - User, - /// Scope for all users under this computer. - System, -} - -/// The error occurs when cast View into Scope. -#[derive(Debug, TeError)] -#[error("hybrid View can not be cast into Scope")] -pub struct TryFromViewError {} - -impl TryFromViewError { - fn new() -> Self { - Self {} - } -} - -impl TryFrom for Scope { - type Error = TryFromViewError; - - fn try_from(value: View) -> std::result::Result { - match value { - View::User => Ok(Self::User), - View::System => Ok(Self::System), - View::Hybrid => Err(TryFromViewError::new()), - } - } -} - -impl Scope { - /// Check whether we have enough privilege when operating in current scope. - /// If we have, return true, otherwise false. - pub fn has_privilege(&self) -> bool { - // If we operate on System, and we do not has privilege, - // we think we do not have privilege, otherwise, - // there is no privilege required. - !matches!(self, Self::System if !utilities::has_privilege()) - } -} - -// endregion - -// region: View - -/// The view when wfassoc querying file extension association. -#[derive(Debug, Copy, Clone)] -pub enum View { - /// The view of current user. - User, - /// The view of system. - System, - /// Hybrid view of User and System. - /// It can be seen as that we use System first and then use User to override any existing items. - Hybrid, -} - -impl From for View { - fn from(value: Scope) -> Self { - match value { - Scope::User => Self::User, - Scope::System => Self::System, - } - } -} - -// endregion - -// region: ProgId Kind - -/// The error occurs when parsing ProgId kind. -#[derive(Debug, TeError)] -pub enum ParseProgIdKindError { - #[error("{0}")] - FromStd(#[from] crate::extra::windows::ParseProgIdError), - #[error("given ProgId is blank")] - BlankProgId, -} - -/// The variant of ProgId for the compatibility -/// with those software which do not follow Microsoft suggestions. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum ProgIdKind { - /// Other ProgId which not follow Microsoft standards. - Other(String), - /// Standard ProgId. - Std(ProgId), -} - -impl Display for ProgIdKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ProgIdKind::Other(v) => write!(f, "{}", v), - ProgIdKind::Std(prog_id) => write!(f, "{}", prog_id), - } - } -} - -impl FromStr for ProgIdKind { - type Err = ParseProgIdKindError; - - fn from_str(s: &str) -> std::result::Result { - if s.is_empty() { - Err(ParseProgIdKindError::BlankProgId) - } else { - Ok(match s.parse::() { - Ok(v) => Self::Std(v), - Err(_) => Self::Other(s.to_string()), - }) - } - } -} - -// endregion - -// endregion - -// region: Registry Visitor - -// region: Application Visitor - -/// The static struct for visiting "Application" in registry -pub struct ApplicationVisitor {} - -impl ApplicationVisitor { - const APP_PATHS: &str = "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths"; - const APPLICATIONS: &str = "Software\\Classes\\Applications"; - - /// Open a readonly registry key to "App Paths" with given scope. - pub fn open_app_paths(scope: Scope) -> Result { - let hk = RegKey::predef(match scope { - Scope::User => HKEY_CURRENT_USER, - Scope::System => HKEY_LOCAL_MACHINE, - }); - let app_paths = hk.open_subkey_with_flags(Self::APP_PATHS, KEY_READ)?; - - Ok(app_paths) - } - - /// Open a readonly registry key to "Applications" with given scope. - pub fn open_applications(scope: Scope) -> Result { - let hk = RegKey::predef(match scope { - Scope::User => HKEY_CURRENT_USER, - Scope::System => HKEY_LOCAL_MACHINE, - }); - let applications = hk.open_subkey_with_flags(Self::APPLICATIONS, KEY_READ)?; - - Ok(applications) - } -} - -// endregion - -// region: Classes Visitor - -/// The static struct for visiting "Classes" in registry. -pub struct ClassesVisitor {} - -impl ClassesVisitor { - const CLASSES: &str = "Software\\Classes"; - - /// Open a readonly registry key to "Classes" with given view. - pub fn open_with_view(view: View) -> Result { - // 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)?, - }; - - Ok(classes) - } - - /// Open a readonly registry key to "Classes" with given scope. - pub fn open_with_scope(scope: Scope) -> Result { - // Fetch root key and navigate to Classes - let hk = RegKey::predef(match scope { - Scope::User => HKEY_CURRENT_USER, - Scope::System => HKEY_LOCAL_MACHINE, - }); - let classes = hk.open_subkey_with_flags(Self::CLASSES, KEY_READ)?; - - Ok(classes) - } -} - -// endregion - -// endregion - -// region: File Extension Registry Key - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ExtKey { - ext: Ext, -} - -impl ExtKey { - /// Create new file extension registry key representer. - pub fn new(s: &str) -> Result { - Ok(Self { - ext: Ext::from_str(s)?, - }) - } - - /// Fetch the reference to inner extension representer. - pub fn as_inner(&self) -> &Ext { - &self.ext - } -} - -impl ExtKey { - /// Set the default "Open With" of this file extension to given ProgId. - pub fn link(&self, prog_id: &ProgIdKey, scope: Scope) -> Result<()> { - use winreg_extra::blank_path_guard; - - // Open Classes key - let classes = ClassesVisitor::open_with_scope(scope)?; - - // Open or create this extension key - let (subkey, _) = - classes.create_subkey_with_flags(blank_path_guard(self.ext.to_string())?, KEY_WRITE)?; - // Set the default way to open this file extension - subkey.set_value("", &prog_id.as_inner().to_string())?; - - // Okey - Ok(()) - } - - /// Reset the default "Open With" of this file extension to blank. - /// - /// If the default "Open With" of this file extension is not given ProgId, - /// or there is no such file extension, this function do nothing. - pub fn unlink(&self, prog_id: &ProgIdKey, scope: Scope) -> Result<()> { - use winreg_extra::{blank_path_guard, try_get_value, try_open_subkey_with_flags}; - - // Open Classes key - let classes = ClassesVisitor::open_with_scope(scope)?; - - // Open key for this extension. - // If there is no such key, return directly. - if let Some(subkey) = try_open_subkey_with_flags( - &classes, - blank_path_guard(self.ext.to_string())?, - KEY_WRITE, - )? { - // Only delete the default key if it is equal to our ProgId - if let Some(value) = try_get_value::(&subkey, "")? { - if value == prog_id.as_inner().to_string() { - // Delete the default key. - subkey.delete_value("")?; - } - } - } - - // Okey - Ok(()) - } - - /// Query the default "Open With" of this file extension associated ProgId. - /// - /// This function will return its associated ProgId if "Open With" was set. - pub fn query(&self, view: View) -> Result> { - use winreg_extra::{blank_path_guard, try_get_value, try_open_subkey_with_flags}; - - // Open Classes key - let classes = ClassesVisitor::open_with_view(view)?; - - // Open key for this extension if possible - let rv = match try_open_subkey_with_flags( - &classes, - blank_path_guard(self.ext.to_string())?, - KEY_READ, - )? { - Some(subkey) => { - // Try get associated ProgId if possible - match try_get_value::(&subkey, "")? { - Some(value) => Some(ProgIdKey::new(value.as_str())?), - None => None, - } - } - None => None, - }; - - // Okey - Ok(rv) - } -} - -// endregion - -// region: ProgId Registry Key - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ProgIdKey { - prog_id: ProgIdKind, -} - -impl ProgIdKey { - /// Create new ProgId registry representer. - pub fn new(s: &str) -> Result { - Ok(Self { - prog_id: ProgIdKind::from_str(s)?, - }) - } - - /// Fetch the reference to inner ProgId. - pub fn as_inner(&self) -> &ProgIdKind { - &self.prog_id - } -} - -impl ProgIdKey { - /// Create ProgId into Registry in given scope with given parameters - pub fn create(&self, scope: Scope, command: &str) -> Result<()> { - use winreg_extra::blank_path_guard; - - let classes = ClassesVisitor::open_with_scope(scope)?; - let (subkey, _) = classes - .create_subkey_with_flags(blank_path_guard(self.prog_id.to_string())?, KEY_WRITE)?; - - // Create verb - 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("", &command.to_string())?; - - Ok(()) - } - - /// Delete this ProgId from registry in given scope. - pub fn delete(&self, scope: Scope) -> Result<()> { - use winreg_extra::blank_path_guard; - - let classes = ClassesVisitor::open_with_scope(scope)?; - classes.delete_subkey_all(blank_path_guard(self.prog_id.to_string())?)?; - - Ok(()) - } -} - -// endregion diff --git a/wfassoc/src/lib_old.rs b/wfassoc/src/lib_old.rs deleted file mode 100644 index 1940099..0000000 --- a/wfassoc/src/lib_old.rs +++ /dev/null @@ -1,501 +0,0 @@ -//! This crate provide utilities fetching and manilupating Windows file association. -//! All code under crate are following Microsoft document: https://learn.microsoft.com/en-us/windows/win32/shell/customizing-file-types-bumper - -#[cfg(not(target_os = "windows"))] -compile_error!("Crate wfassoc is only supported on Windows."); - -pub mod extra; -pub mod utilities; -pub mod assoc; - -use assoc::{Ext, ProgId}; -use indexmap::{IndexMap, IndexSet}; -use regex::Regex; -use std::ffi::OsStr; -use std::path::PathBuf; -use std::sync::LazyLock; -use thiserror::Error as TeError; -use winreg::RegKey; -use winreg::enums::{ - HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_READ, KEY_WRITE, -}; - -// region: Error Types - -/// All possible error occurs in this crate. -#[derive(Debug, TeError)] -pub enum Error { - #[error("error occurs when manipulating with Registry: {0}")] - BadRegOper(#[from] std::io::Error), - #[error("{0}")] - CastOsStr(#[from] utilities::CastOsStrError), - #[error("{0}")] - ParseExt(#[from] assoc::ParseExtError), - - #[error("no administrative privilege")] - NoPrivilege, - #[error("given identifier \"{0}\" of application is invalid")] - BadIdentifier(String), - #[error("given full path to application is invalid")] - BadFullAppPath, - #[error("manner \"{0}\" is already registered")] - DupManner(String), - #[error("file extension \"{0}\" is already registered")] - DupExt(String), - #[error("the token of manner is invalid")] - InvalidMannerToken, - #[error("the token of file extension is invalid")] - InvalidExtToken, -} - -/// The result type used in this crate. -pub type Result = std::result::Result; - -// endregion - -// region: Types - -/// The token for access registered items in Program. -/// This is usually returned when you registering them. -pub type Token = usize; - -/// The scope where wfassoc will register and unregister application. -#[derive(Debug, Copy, Clone)] -pub enum Scope { - /// Scope for current user. - User, - /// Scope for all users under this computer. - System, -} - -/// The error occurs when cast View into Scope. -#[derive(Debug, TeError)] -#[error("hybrid View can not be cast into Scope")] -pub struct TryFromViewError {} - -impl TryFromViewError { - fn new() -> Self { - Self {} - } -} - -impl TryFrom for Scope { - type Error = TryFromViewError; - - fn try_from(value: View) -> std::result::Result { - match value { - View::User => Ok(Self::User), - View::System => Ok(Self::System), - View::Hybrid => Err(TryFromViewError::new()), - } - } -} - -impl Scope { - /// Check whether we have enough privilege when operating in current scope. - /// If we have, return true, otherwise false. - pub fn has_privilege(&self) -> bool { - // If we operate on System, and we do not has privilege, - // we think we do not have privilege, otherwise, - // there is no privilege required. - !matches!(self, Self::System if !utilities::has_privilege()) - } -} - -/// The view when wfassoc querying file extension association. -#[derive(Debug, Copy, Clone)] -pub enum View { - /// The view of current user. - User, - /// The view of system. - System, - /// Hybrid view of User and System. - /// It can be seen as that we use System first and then use User to override any existing items. - Hybrid, -} - -impl From for View { - fn from(value: Scope) -> Self { - match value { - Scope::User => Self::User, - Scope::System => Self::System, - } - } -} - -// endregion - -// region: Program - -/// The struct representing a complete program for registration and unregistration. -#[derive(Debug)] -pub struct Program { - /// The identifier of this program. - identifier: String, - /// The fully qualified path to the application. - full_path: PathBuf, - /// The collection holding all manners of this program. - manners: IndexSet, - /// The collection holding all file extensions supported by this program. - /// The key is file estension and value is its associated manner for opening it. - exts: IndexMap, -} - -impl Program { - /// Create a new registrar for following operations. - /// - /// `identifier` is the unique name of this program. - /// If should only contain digits and alphabet chars, - /// and should not start with any digits. - /// For example, "MyApp" is okey but following names are not okey: - /// - /// - `My App` - /// - `3DViewer` - /// - `我的Qt程序` (means "My Qt App" in English) - /// - /// 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: &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(), - // 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(), - }) - } - - /// Add manner provided by this program. - pub fn add_manner(&mut self, manner: &str) -> Result { - // TODO: Use wincmd::CmdArgs instead of String. - // Create manner from string - let manner = manner.to_string(); - // Backup a stringfied manner for error output. - let manner_str = manner.to_string(); - // Insert manner. - let idx = self.manners.len(); - if self.manners.insert(manner) { - Ok(idx) - } else { - Err(Error::DupManner(manner_str)) - } - } - - /// Get the string display of manner represented by given token - pub fn get_manner_str(&self, token: Token) -> Option { - self.manners.get_index(token).map(|s| s.clone()) - } - - /// Add file extension supported by this program and its associated manner. - pub fn add_ext(&mut self, ext: &str, token: Token) -> Result { - // Check manner token - if let None = self.manners.get_index(token) { - return Err(Error::InvalidMannerToken); - } - - // Create extension from string - let ext = Ext::new(ext)?; - // Backup a stringfied extension for error output. - let ext_str = ext.to_string(); - // Insert file extension - let idx = self.exts.len(); - if let None = self.exts.insert(ext, token) { - Ok(idx) - } else { - Err(Error::DupExt(ext_str)) - } - } - - /// Get the string display of file extension represented by given token - pub fn get_ext_str(&self, token: Token) -> Option { - self.exts.get_index(token).map(|p| p.0.to_string()) - } -} - -impl Program { - const APP_PATHS: &str = "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths"; - const APPLICATIONS: &str = "Software\\Classes\\Applications"; - - /// Register this application. - pub fn register(&self, scope: Scope) -> Result<()> { - // Check privilege - if !scope.has_privilege() { - return Err(Error::NoPrivilege); - } - - // Fetch root key. - let hk = RegKey::predef(match scope { - Scope::User => HKEY_CURRENT_USER, - Scope::System => HKEY_LOCAL_MACHINE, - }); - // Fetch file name and start in path. - let file_name = self.extract_file_name()?; - let start_in = self.extract_start_in()?; - - // Create App Paths subkey - debug_println!("Adding App Paths subkey..."); - let subkey_parent = hk.open_subkey_with_flags(Self::APP_PATHS, KEY_READ)?; - let (subkey, _) = subkey_parent.create_subkey_with_flags(file_name, KEY_WRITE)?; - // Write App Paths values - subkey.set_value("", &utilities::path_to_str(&self.full_path)?)?; - subkey.set_value("Path", &utilities::osstr_to_str(&start_in)?)?; - - // Create Applications subkey - debug_println!("Adding Applications subkey..."); - let subkey_parent = hk.open_subkey_with_flags(Self::APPLICATIONS, KEY_READ)?; - let (subkey, _) = subkey_parent.create_subkey_with_flags(file_name, KEY_WRITE)?; - // Write Applications values - if !self.exts.is_empty() { - let (supported_types, _) = - subkey.create_subkey_with_flags("SupportedTypes", KEY_WRITE)?; - for ext in self.exts.keys() { - supported_types.set_value(ext.to_string(), &"")?; - } - } - - // 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(()) - } - - /// Unregister this application. - pub fn unregister(&self, scope: Scope) -> Result<()> { - // Check privilege - if !scope.has_privilege() { - return Err(Error::NoPrivilege); - } - - // Fetch root key and file name. - let hk = RegKey::predef(match scope { - Scope::User => HKEY_CURRENT_USER, - Scope::System => HKEY_LOCAL_MACHINE, - }); - let file_name = self.extract_file_name()?; - - // Remove App Paths subkey - debug_println!("Removing App Paths subkey..."); - let subkey_parent = hk.open_subkey_with_flags(Self::APP_PATHS, KEY_WRITE)?; - subkey_parent.delete_subkey_all(file_name)?; - - // Remove Applications subkey - debug_println!("Removing Applications subkey..."); - 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(()) - } - - /// Check whether this application has been registered. - /// - /// Please note that this is a rough check and do not validate any data. - pub fn is_registered(&self, scope: Scope) -> Result { - // Fetch root key and file name. - let hk = RegKey::predef(match scope { - Scope::User => HKEY_CURRENT_USER, - Scope::System => HKEY_LOCAL_MACHINE, - }); - let file_name = self.extract_file_name()?; - - // Check App Paths subkey. - debug_println!("Checking App Paths subkey..."); - let subkey_parent = hk.open_subkey_with_flags(Self::APP_PATHS, KEY_READ)?; - if let Err(_) = subkey_parent.open_subkey_with_flags(file_name, KEY_READ) { - return Ok(false); - } - - // Check Application subkey. - debug_println!("Checking Applications subkey..."); - let subkey_parent = hk.open_subkey_with_flags(Self::APPLICATIONS, KEY_READ)?; - if let Err(_) = subkey_parent.open_subkey_with_flags(file_name, KEY_READ) { - return Ok(false); - } - - // Both subkeys are roughly existing. - Ok(true) - } -} - -impl Program { - const CLASSES: &str = "Software\\Classes"; - - /// 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() { - return Err(Error::NoPrivilege); - } - - // Fetch file extension and build ProgId from it - let (ext, _) = match self.exts.get_index(ext) { - Some(v) => v, - None => return Err(Error::InvalidExtToken), - }; - let prog_id = self.build_prog_id(ext); - - // Fetch root key and navigate to Classes - let hk = RegKey::predef(match scope { - Scope::User => HKEY_CURRENT_USER, - Scope::System => HKEY_LOCAL_MACHINE, - }); - let classes = hk.open_subkey_with_flags(Self::CLASSES, KEY_READ)?; - - // Open or create this extension key - let (subkey, _) = classes.create_subkey_with_flags(ext.to_string(), KEY_WRITE)?; - // Set the default way to open this file extension - subkey.set_value("", &prog_id.to_string())?; - - // Okey - Ok(()) - } - - /// 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, - /// 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() { - return Err(Error::NoPrivilege); - } - - // Fetch file extension and build ProgId from it - let (ext, _) = match self.exts.get_index(ext) { - Some(v) => v, - None => return Err(Error::InvalidExtToken), - }; - let prog_id = self.build_prog_id(ext); - - // Fetch root key and navigate to Classes - let hk = RegKey::predef(match scope { - Scope::User => HKEY_CURRENT_USER, - Scope::System => HKEY_LOCAL_MACHINE, - }); - let classes = hk.open_subkey_with_flags(Self::CLASSES, KEY_READ)?; - - // Open key for this extension. - // If there is no such key, return directly. - if let Some(subkey) = - extra::winreg::try_open_subkey_with_flags(&classes, ext.to_string(), KEY_WRITE)? - { - // Only delete the default key if it is equal to our ProgId - if let Some(value) = extra::winreg::try_get_value::(&subkey, "")? { - if value == prog_id.to_string() { - // Delete the default key. - subkey.delete_value("")?; - } - } - } - - // 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 extra::winreg::try_open_subkey_with_flags(&classes, ext.to_string(), KEY_READ)? { - Some(subkey) => { - // Try get associated ProgId if possible - match extra::winreg::try_get_value::(&subkey, "")? { - Some(value) => Some(ProgId::from(value.as_str())), - None => None, - } - } - None => None, - }; - - // Okey - Ok(rv) - } -} - -impl Program { - /// Extract the file name part from full path to application, - /// which was used in Registry path component. - fn extract_file_name(&self) -> Result<&OsStr> { - // Get the file name part and make sure it is not empty. - // Empty checker is CRUCIAL! - self.full_path - .file_name() - .and_then(|p| if p.is_empty() { None } else { Some(p) }) - .ok_or(Error::BadFullAppPath) - } - - /// Extract the start in path from full path to application, - /// which basically is the stem of full path. - fn extract_start_in(&self) -> Result<&OsStr> { - // Get parent part and make sure it is not empty - // Empty checker is CRUCIAL! - self.full_path - .parent() - .map(|p| p.as_os_str()) - .and_then(|p| if p.is_empty() { None } else { Some(p) }) - .ok_or(Error::BadFullAppPath) - } - - /// Build ProgId from identifier and given file extension. - fn build_prog_id(&self, ext: &Ext) -> ProgId { - ProgId::Std(assoc::StdProgId::new( - &self.identifier, - &utilities::capitalize_first_ascii(ext.inner()), - None, - )) - } -} - -// endregion diff --git a/wfassoc/src/lib_old_old.rs b/wfassoc/src/lib_old_old.rs deleted file mode 100644 index e81c03f..0000000 --- a/wfassoc/src/lib_old_old.rs +++ /dev/null @@ -1,582 +0,0 @@ - -/// The expand of winreg crate according to our module requirements. -mod winregex; - -use regex::Regex; -use std::fmt::Display; -use std::str::FromStr; -use std::sync::LazyLock; -use thiserror::Error as TeError; -use winreg::RegKey; - -// region: Error Types - -/// All possible error occurs in this crate. -#[derive(Debug, TeError)] -pub enum Error { - #[error( - "can not register because lack essential privilege. please consider running with Administrator role" - )] - NoPrivilege, - #[error("{0}")] - Register(#[from] std::io::Error), - #[error("{0}")] - BadFileExt(#[from] ParseFileExtError), - #[error("{0}")] - BadProgId(#[from] ParseProgIdError), -} - -// endregion - -// region: Privilege, Scope and View - -/// Check whether current process has administrative privilege. -/// -/// It usually means that checking whether current process is running as Administrator. -/// Return true if it is, otherwise false. -/// -/// Reference: https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-checktokenmembership -pub fn has_privilege() -> bool { - use windows_sys::Win32::Foundation::HANDLE; - use windows_sys::Win32::Security::{ - AllocateAndInitializeSid, CheckTokenMembership, FreeSid, PSID, SECURITY_NT_AUTHORITY, - }; - use windows_sys::Win32::System::SystemServices::{ - DOMAIN_ALIAS_RID_ADMINS, SECURITY_BUILTIN_DOMAIN_RID, - }; - use windows_sys::core::BOOL; - - let nt_authority = SECURITY_NT_AUTHORITY.clone(); - let mut administrators_group: PSID = PSID::default(); - let success: BOOL = unsafe { - AllocateAndInitializeSid( - &nt_authority, - 2, - SECURITY_BUILTIN_DOMAIN_RID as u32, - DOMAIN_ALIAS_RID_ADMINS as u32, - 0, - 0, - 0, - 0, - 0, - 0, - &mut administrators_group, - ) - }; - - if success == 0 { - panic!("Win32 AllocateAndInitializeSid() failed"); - } - - let mut is_member: BOOL = BOOL::default(); - let success: BOOL = - unsafe { CheckTokenMembership(HANDLE::default(), administrators_group, &mut is_member) }; - - unsafe { - FreeSid(administrators_group); - } - - if success == 0 { - panic!("Win32 CheckTokenMembership() failed"); - } - - is_member != 0 -} - -/// The scope where wfassoc will register and unregister. -#[derive(Debug, Copy, Clone)] -pub enum Scope { - /// Scope for current user. - User, - /// Scope for all users under this computer. - System, -} - -/// The view when wfassoc querying infomations. -#[derive(Debug, Copy, Clone)] -pub enum View { - /// The view of current user. - User, - /// The view of system. - System, - /// Hybrid view of User and System. - /// It can be seen as that we use System first and then use User to override any existing items. - Hybrid, -} - -/// The error occurs when cast View into Scope. -#[derive(Debug, TeError)] -#[error("hybrid view can not be cast into any scope")] -pub struct TryFromViewError {} - -impl TryFromViewError { - fn new() -> Self { - Self {} - } -} - -impl From for View { - fn from(value: Scope) -> Self { - match value { - Scope::User => Self::User, - Scope::System => Self::System, - } - } -} - -impl TryFrom for Scope { - type Error = TryFromViewError; - - fn try_from(value: View) -> Result { - match value { - View::User => Ok(Self::User), - View::System => Ok(Self::System), - View::Hybrid => Err(TryFromViewError::new()), - } - } -} - -impl Scope { - /// Check whether we have enough privilege when operating in current scope. - /// If we have, simply return, otherwise return error. - fn check_privilege(&self) -> Result<(), Error> { - if matches!(self, Self::System if !has_privilege()) { - Err(Error::NoPrivilege) - } else { - Ok(()) - } - } -} - -// endregion - -// region: File Extension - -/// The struct representing an file extension which must start with dot (`.`) -/// and followed by at least one arbitrary characters. -#[derive(Debug, Clone)] -pub struct FileExt { - /// The body of file extension (excluding dot). - inner: String, -} - -impl FileExt { - pub fn new(file_ext: &str) -> Result { - Self::from_str(file_ext) - } -} - -/// The error occurs when try parsing string into FileExt. -#[derive(Debug, TeError)] -#[error("given file extension is invalid")] -pub struct ParseFileExtError {} - -impl ParseFileExtError { - fn new() -> Self { - Self {} - } -} - -impl Display for FileExt { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, ".{}", self.inner) - } -} - -impl FromStr for FileExt { - type Err = ParseFileExtError; - - fn from_str(s: &str) -> Result { - static RE: LazyLock = LazyLock::new(|| Regex::new(r"^\.([^\.]+)$").unwrap()); - match RE.captures(s) { - Some(v) => Ok(Self { - inner: v[1].to_string(), - }), - None => Err(ParseFileExtError::new()), - } - } -} - -impl FileExt { - fn open_scope(&self, scope: Scope) -> Result { - use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_READ, KEY_WRITE}; - - // check privilege - scope.check_privilege()?; - // get the root key - let hk = match scope { - Scope::User => RegKey::predef(HKEY_CURRENT_USER), - Scope::System => RegKey::predef(HKEY_LOCAL_MACHINE), - }; - // navigate to classes - let classes = hk.open_subkey_with_flags("Software\\Classes", KEY_READ | KEY_WRITE)?; - // okey - Ok(classes) - } - - fn open_view(&self, view: View) -> Result, Error> { - use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_READ}; - - // navigate to extension container - 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("Software\\Classes", KEY_READ)? - } - View::Hybrid => hk.open_subkey_with_flags("", KEY_READ)?, - }; - // check whether there is this ext - classes. - // open extension key if possible - let thisext = classes.open_subkey_with_flags(file_ext.to_string(), KEY_READ)?; - // okey - Ok(classes) - } - - pub fn get_current(&self, view: View) -> Option { - todo!() - } - - pub fn set_current(&mut self, scope: Scope, prog_id: Option<&ProgId>) -> Result<(), Error> { - scope.check_privilege()?; - todo!() - } - - pub fn iter_open_with(&self, view: View) -> Result, Error> { - let viewer = match self.open_view(view)? { - Some(viewer) => viewer, - None => return Ok(std::iter::empty::()), - }; - let it = winregex::iter_sz_keys(&viewer); - let it = winregex::exclude_default_key(it); - - Ok(it.map(|s| ProgId::from(s.as_str()))) - } - - pub fn insert_open_with(&mut self, scope: Scope, prog_id: &ProgId) -> Result<(), Error> { - scope.check_privilege()?; - todo!() - } - - pub fn flash_open_with( - &mut self, - scope: Scope, - prog_ids: impl Iterator, - ) -> Result<(), Error> { - scope.check_privilege()?; - todo!() - } -} - -/// The association infomations of specific file extension. -#[derive(Debug)] -pub struct FileExtAssoc { - default: String, - open_with_progids: Vec, -} - -impl FileExtAssoc { - fn new(file_ext: &FileExt, view: View) -> Option { - use winreg::RegKey; - use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_READ}; - - // navigate to extension container - 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("Software\\Classes", KEY_READ) - .unwrap(), - View::Hybrid => hk.open_subkey_with_flags("", KEY_READ).unwrap(), - }; - - // open extension key if possible - let thisext = match classes.open_subkey_with_flags(file_ext.to_string(), KEY_READ) { - Ok(v) => v, - Err(_) => return None, - }; - - // fetch extension infos. - let default = thisext.get_value("").unwrap_or(String::new()); - let open_with_progids = - if let Ok(progids) = thisext.open_subkey_with_flags("OpenWithProdIds", KEY_READ) { - progids - .enum_keys() - .map(|x| x.unwrap()) - .filter(|k| !k.is_empty()) - .collect() - } else { - Vec::new() - }; - - Some(Self { - default, - open_with_progids, - }) - } - - pub fn get_default(&self) -> &str { - &self.default - } - - pub fn len_open_with_progid(&self) -> usize { - self.open_with_progids.len() - } - - pub fn iter_open_with_progids(&self) -> impl Iterator { - self.open_with_progids.iter().map(|s| s.as_str()) - } -} - -// endregion - -// region: Executable Resource - -// /// The struct representing an Windows executable resources path like -// /// `path_to_file.exe,1`. -// pub struct ExecRc { -// /// The path to binary for finding resources. -// binary: PathBuf, -// /// The inner index of resources. -// index: u32, -// } - -// impl ExecRc { -// pub fn new(res_str: &str) -> Result { -// static RE: LazyLock = LazyLock::new(|| Regex::new(r"^([^,]+),([0-9]+)$").unwrap()); -// let caps = RE.captures(res_str); -// if let Some(caps) = caps { -// let binary = PathBuf::from_str(&caps[1])?; -// let index = caps[2].parse::()?; -// Ok(Self { binary, index }) -// } else { -// Err(ParseExecRcError::NoCapture) -// } -// } -// } - -// /// The error occurs when try parsing string into ExecRc. -// #[derive(Debug, TeError)] -// #[error("given string is not a valid executable resource string")] -// pub enum ParseExecRcError { -// /// Given string is not matched with format. -// NoCapture, -// /// Fail to convert executable part into path. -// BadBinaryPath(#[from] std::convert::Infallible), -// /// Fail to convert index part into valid number. -// BadIndex(#[from] std::num::ParseIntError), -// } - -// impl FromStr for ExecRc { -// type Err = ParseExecRcError; - -// fn from_str(s: &str) -> Result { -// ExecRc::new(s) -// } -// } - -// impl Display for ExecRc { -// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -// write!(f, "{},{}", self.binary.to_str().unwrap(), self.index) -// } -// } - -// endregion - -// region: Programmatic Identifiers (ProgId) - -/// The struct representing Programmatic Identifiers (ProgId). -/// -/// Because there is optional part in standard ProgId, and not all software developers -/// are willing to following Microsoft suggestions, there is no strict constaint for ProgId. -/// So this struct is actually an enum which holding any possible ProgId format. -/// -/// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids -pub enum ProgId { - Plain(String), - Loose(LosseProgId), - Strict(StrictProgId), -} - -impl From<&str> for ProgId { - fn from(s: &str) -> Self { - // match it for strict ProgId first - if let Ok(v) = StrictProgId::from_str(s) { - return Self::Strict(v); - } - // then match for loose ProgId - if let Ok(v) = LosseProgId::from_str(s) { - return Self::Loose(v); - } - // fallback with plain - Self::Plain(s.to_string()) - } -} - -impl Display for ProgId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ProgId::Plain(v) => v.fmt(f), - ProgId::Loose(v) => v.fmt(f), - ProgId::Strict(v) => v.fmt(f), - } - } -} - -/// The error occurs when parsing ProgId. -#[derive(Debug, TeError)] -#[error("given ProgId string is invalid")] -pub struct ParseProgIdError {} - -impl ParseProgIdError { - fn new() -> Self { - Self {} - } -} - -/// The ProgId similar with strict ProgId, but no version part. -pub struct LosseProgId { - vendor: String, - component: String, -} - -impl LosseProgId { - pub fn new(vendor: &str, component: &str) -> Self { - Self { - vendor: vendor.to_string(), - component: component.to_string(), - } - } - - pub fn get_vendor(&self) -> &str { - &self.vendor - } - - pub fn get_component(&self) -> &str { - &self.component - } -} - -impl FromStr for LosseProgId { - type Err = ParseProgIdError; - - fn from_str(s: &str) -> Result { - static RE: LazyLock = - LazyLock::new(|| Regex::new(r"^([a-zA-Z0-9]+)\.([a-zA-Z0-9]+)$").unwrap()); - let caps = RE.captures(s); - if let Some(caps) = caps { - let vendor = &caps[1]; - let component = &caps[2]; - Ok(Self::new(vendor, component)) - } else { - Err(ParseProgIdError::new()) - } - } -} - -impl Display for LosseProgId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}.{}", self.vendor, self.component) - } -} - -/// The ProgId exactly follows `[Vendor or Application].[Component].[Version]` format. -pub struct StrictProgId { - vendor: String, - component: String, - version: u32, -} - -impl StrictProgId { - pub fn new(vendor: &str, component: &str, version: u32) -> Self { - Self { - vendor: vendor.to_string(), - component: component.to_string(), - version, - } - } - - pub fn get_vendor(&self) -> &str { - &self.vendor - } - - pub fn get_component(&self) -> &str { - &self.component - } - - pub fn get_version(&self) -> u32 { - self.version - } -} - -impl FromStr for StrictProgId { - type Err = ParseProgIdError; - - fn from_str(s: &str) -> Result { - static RE: LazyLock = - LazyLock::new(|| Regex::new(r"^([a-zA-Z0-9]+)\.([a-zA-Z0-9]+)\.([0-9]+)$").unwrap()); - let caps = RE.captures(s); - if let Some(caps) = caps { - let vendor = &caps[1]; - let component = &caps[2]; - let version = caps[3] - .parse::() - .map_err(|_| ParseProgIdError::new())?; - Ok(Self::new(vendor, component, version)) - } else { - Err(ParseProgIdError::new()) - } - } -} - -impl Display for StrictProgId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}.{}.{}", self.vendor, self.component, self.version) - } -} - -// endregion - -// region: Program - -// /// The struct representing a complete Win32 program. -// pub struct Program { -// file_exts: Vec, -// } - -// impl Program { -// /// Create a program descriptor. -// pub fn new() -> Self { -// Self { -// file_exts: Vec::new(), -// } -// } -// } - -// impl Program { -// /// Register program in this computer -// pub fn register(&self, kind: RegisterKind) -> Result<(), Error> { -// todo!("pretend to register >_<...") -// } - -// /// Unregister program from this computer. -// pub fn unregister(&self) -> Result<(), Error> { -// todo!("pretend to unregister >_<...") -// } -// } - -// impl Program { -// /// Query file extension infos which this program want to associate with. -// pub fn query(&self) -> Result<(), Error> { -// todo!("pretend to query >_<...") -// } -// } - -// endregion