From 07a8c6a11d7fe8ece21c63f40d250d17f1aeb8ca Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Fri, 10 Oct 2025 20:54:44 +0800 Subject: [PATCH] write shit --- wfassoc/src/lib.rs | 208 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 159 insertions(+), 49 deletions(-) diff --git a/wfassoc/src/lib.rs b/wfassoc/src/lib.rs index d8b9873..c9a509f 100644 --- a/wfassoc/src/lib.rs +++ b/wfassoc/src/lib.rs @@ -6,10 +6,10 @@ compile_error!("Crate wfassoc is only supported on Windows."); use regex::Regex; use std::fmt::Display; -use std::path::PathBuf; use std::str::FromStr; use std::sync::LazyLock; use thiserror::Error as TeError; +use winreg::RegKey; // region: Error Types @@ -21,37 +21,16 @@ pub enum Error { )] NoPrivilege, #[error("{0}")] - BadFileExt(#[from] ParseFileExtError), + Register(#[from] std::io::Error), #[error("{0}")] - BadExecRc(#[from] ParseExecRcError), + BadFileExt(#[from] ParseFileExtError), #[error("{0}")] BadProgId(#[from] ParseProgIdError), } // endregion -// region: Basic Types - -/// 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, -} +// region: Privilege, Scope and View /// Check whether current process has administrative privilege. /// @@ -106,6 +85,71 @@ pub fn has_privilege() -> bool { 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 @@ -120,23 +164,13 @@ pub struct FileExt { impl FileExt { pub fn new(file_ext: &str) -> Result { - static RE: LazyLock = LazyLock::new(|| Regex::new(r"^\.([^\.]+)$").unwrap()); - match RE.captures(file_ext) { - Some(v) => Ok(Self { - inner: v[1].to_string(), - }), - None => Err(ParseFileExtError::new()), - } - } - - pub fn query(&self, view: View) -> Option { - FileExtAssoc::new(self, view) + Self::from_str(file_ext) } } /// The error occurs when try parsing string into FileExt. #[derive(Debug, TeError)] -#[error("given file extension is illegal")] +#[error("given file extension is invalid")] pub struct ParseFileExtError {} impl ParseFileExtError { @@ -155,7 +189,81 @@ impl FromStr for FileExt { type Err = ParseFileExtError; fn from_str(s: &str) -> Result { - Self::new(s) + 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> { + todo!() + } + + 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!() } } @@ -280,30 +388,30 @@ impl FileExtAssoc { // region: Programmatic Identifiers (ProgId) /// The struct representing Programmatic Identifiers (ProgId). -/// -/// Because there is optional part in ProgId, and not all software developer -/// are willing to following Microsoft standard, there is no strict constaint for 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 FromStr for ProgId { - type Err = ParseProgIdError; - - fn from_str(s: &str) -> Result { +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 Ok(Self::Strict(v)); + return Self::Strict(v); } // then match for loose ProgId if let Ok(v) = LosseProgId::from_str(s) { - return Ok(Self::Loose(v)); + return Self::Loose(v); } // fallback with plain - Ok(Self::Plain(s.to_string())) + Self::Plain(s.to_string()) } } @@ -413,7 +521,9 @@ impl FromStr for StrictProgId { if let Some(caps) = caps { let vendor = &caps[1]; let component = &caps[2]; - let version = caps[3].parse::().map_err(|_| ParseProgIdError::new())?; + let version = caps[3] + .parse::() + .map_err(|_| ParseProgIdError::new())?; Ok(Self::new(vendor, component, version)) } else { Err(ParseProgIdError::new())