From e9ca5dd5ecdbf77e66810e91a8774976d2389cee Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Fri, 17 Apr 2026 15:01:28 +0800 Subject: [PATCH] feat: make a clear boundary for lowlevel and highlevel API --- wfassoc/src/highlevel.rs | 102 +++++++++++++++++++ wfassoc/src/lib.rs | 210 +-------------------------------------- wfassoc/src/lowlevel.rs | 208 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 313 insertions(+), 207 deletions(-) create mode 100644 wfassoc/src/highlevel.rs create mode 100644 wfassoc/src/lowlevel.rs diff --git a/wfassoc/src/highlevel.rs b/wfassoc/src/highlevel.rs new file mode 100644 index 0000000..edd81f0 --- /dev/null +++ b/wfassoc/src/highlevel.rs @@ -0,0 +1,102 @@ +use crate::lowlevel; +use std::collections::HashMap; +use thiserror::Error as TeError; + +pub use lowlevel::{Scope, View}; + +// region: Error Type + +/// Error occurs in this module. +#[derive(Debug, TeError)] +pub enum Error {} + +/// Result type used in this module. +type Result = std::result::Result; + +// endregion + +/// Schema is the sketchpad of complete Program. +/// +/// We will create a Schema first, fill some properties, add file extensions, +/// then convert it into immutable Program for following using. +#[derive(Debug)] +pub struct Schema { + identifier: String, + path: String, + clsid: String, + icons: HashMap, + behaviors: HashMap, + exts: HashMap, +} + +/// Internal used struct as the Schema file extensions hashmap value type. +#[derive(Debug)] +struct SchemaExt { + name: String, + icon: String, + behavior: String, +} + +impl Schema { + pub fn new() -> Self { + Self { + identifier: String::new(), + path: String::new(), + clsid: String::new(), + icons: HashMap::new(), + behaviors: HashMap::new(), + exts: HashMap::new(), + } + } + + pub fn set_identifier(&mut self, identifier: &str) -> Result<()> { + todo!() + } + + pub fn set_path(&mut self, exe_path: &str) -> Result<()> { + todo!() + } + + pub fn set_clsid(&mut self, clsid: &str) -> Result<()> { + todo!() + } + + pub fn add_icon(&mut self, name: &str, value: &str) -> Result<()> { + todo!() + } + + pub fn add_behavior(&mut self, name: &str, value: &str) -> Result<()> { + todo!() + } + + pub fn add_ext( + &mut self, + ext: &str, + ext_name: &str, + ext_icon: &str, + ext_behavior: &str, + ) -> Result<()> { + todo!() + } + + pub fn into_program(self) -> Result { + Program::new(self) + } +} + +/// Program is a complete and immutable program representer +pub struct Program {} + +impl TryFrom for Program { + type Error = Error; + + fn try_from(value: Schema) -> std::result::Result { + Self::new(value) + } +} + +impl Program { + pub fn new(schema: Schema) -> Result { + todo!() + } +} diff --git a/wfassoc/src/lib.rs b/wfassoc/src/lib.rs index 986714c..5a7ef46 100644 --- a/wfassoc/src/lib.rs +++ b/wfassoc/src/lib.rs @@ -6,211 +6,7 @@ compile_error!("Crate wfassoc is only supported on Windows."); pub mod utilities; pub mod win32; +pub mod lowlevel; +pub mod highlevel; -use std::fmt::Display; -use std::str::FromStr; -use thiserror::Error as TeError; -use winreg::RegKey; - -// region: Error Process - -/// Error occurs in this module. -#[derive(Debug, TeError)] -pub enum Error { - #[error("can not perform this operation because lack essential privilege.")] - NoPrivilege, - #[error("{0}")] - BadRegOp(#[from] std::io::Error), - #[error("{0}")] - UnexpectedBlankKey(#[from] win32::regext::BlankPathError), -} - -// endregion - -// region: Scope and View - -/// 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 !win32::utilities::has_privilege()) { - Err(Error::NoPrivilege) - } else { - Ok(()) - } - } -} - -// endregion - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct AppPathsKey { - key: win32::concept::FileName, -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ApplicationsKey { - key: win32::concept::FileName, -} - -// region: File Extension Key - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ExtKey { - ext: win32::concept::Ext, -} - -impl ExtKey { - fn open_scope(&self, scope: Scope) -> Result, Error> { - 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)?; - // open extension key if possible - let thisext = win32::regext::try_open_subkey_with_flags( - &classes, - win32::regext::blank_path_guard(self.ext.dotted_inner())?, - KEY_READ | KEY_WRITE, - )?; - // okey - Ok(thisext) - } - - 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)?, - }; - // open extension key if possible - let thisext = win32::regext::try_open_subkey_with_flags( - &classes, - win32::regext::blank_path_guard(self.ext.dotted_inner())?, - KEY_READ, - )?; - // okey - Ok(thisext) - } - - -} - -// endregion - -// region: ProgId Key - -// region: Losse ProgId - -/// The enum representing a losse Programmatic Identifiers (ProgId). -/// -/// In real world, not all software developers are willing to following Microsoft suggestions to use ProgId, -/// They use string which do not have any regulation as ProgId. -/// This enum is designed for handling this scenario. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -enum LosseProgId { - Plain(String), - Strict(win32::concept::ProgId), -} - -impl Display for LosseProgId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - LosseProgId::Plain(v) => write!(f, "{}", v), - LosseProgId::Strict(v) => write!(f, "{}", v), - } - } -} - -impl From<&str> for LosseProgId { - fn from(s: &str) -> Self { - // match it for standard ProgId first - if let Ok(v) = win32::concept::ProgId::from_str(s) { - Self::Strict(v) - } else { - // fallback with other - Self::Plain(s.to_string()) - } - } -} - -// endregion - -// region: ProgId Key - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ProgIdKey { - progid: LosseProgId, -} - -// endregion - -// endregion +pub use highlevel::{Scope, View, Schema, Program}; diff --git a/wfassoc/src/lowlevel.rs b/wfassoc/src/lowlevel.rs new file mode 100644 index 0000000..c6d5915 --- /dev/null +++ b/wfassoc/src/lowlevel.rs @@ -0,0 +1,208 @@ +use crate::win32; +use std::fmt::Display; +use std::str::FromStr; +use thiserror::Error as TeError; +use winreg::RegKey; + +// region: Error Type + +/// Error occurs in this module. +#[derive(Debug, TeError)] +pub enum Error { + #[error("can not perform this operation because lack essential privilege.")] + NoPrivilege, + #[error("{0}")] + BadRegOp(#[from] std::io::Error), + #[error("{0}")] + UnexpectedBlankKey(#[from] win32::regext::BlankPathError), +} + +// endregion + +// region: Scope and View + +/// 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 !win32::utilities::has_privilege()) { + Err(Error::NoPrivilege) + } else { + Ok(()) + } + } +} + +// endregion + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct AppPathsKey { + key: win32::concept::FileName, +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ApplicationsKey { + key: win32::concept::FileName, +} + +// region: File Extension Key + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ExtKey { + ext: win32::concept::Ext, +} + +impl ExtKey { + fn open_scope(&self, scope: Scope) -> Result, Error> { + 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)?; + // open extension key if possible + let thisext = win32::regext::try_open_subkey_with_flags( + &classes, + win32::regext::blank_path_guard(self.ext.dotted_inner())?, + KEY_READ | KEY_WRITE, + )?; + // okey + Ok(thisext) + } + + 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)?, + }; + // open extension key if possible + let thisext = win32::regext::try_open_subkey_with_flags( + &classes, + win32::regext::blank_path_guard(self.ext.dotted_inner())?, + KEY_READ, + )?; + // okey + Ok(thisext) + } + + +} + +// endregion + +// region: ProgId Key + +// region: Losse ProgId + +/// The enum representing a losse Programmatic Identifiers (ProgId). +/// +/// In real world, not all software developers are willing to following Microsoft suggestions to use ProgId, +/// They use string which do not have any regulation as ProgId. +/// This enum is designed for handling this scenario. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +enum LosseProgId { + Plain(String), + Strict(win32::concept::ProgId), +} + +impl Display for LosseProgId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LosseProgId::Plain(v) => write!(f, "{}", v), + LosseProgId::Strict(v) => write!(f, "{}", v), + } + } +} + +impl From<&str> for LosseProgId { + fn from(s: &str) -> Self { + // match it for standard ProgId first + if let Ok(v) = win32::concept::ProgId::from_str(s) { + Self::Strict(v) + } else { + // fallback with other + Self::Plain(s.to_string()) + } + } +} + +// endregion + +// region: ProgId Key + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ProgIdKey { + progid: LosseProgId, +} + +// endregion + +// endregion