1
0

feat: make a clear boundary for lowlevel and highlevel API

This commit is contained in:
2026-04-17 15:01:28 +08:00
parent d2bd4425df
commit e9ca5dd5ec
3 changed files with 313 additions and 207 deletions

102
wfassoc/src/highlevel.rs Normal file
View File

@@ -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<T> = std::result::Result<T, Error>;
// 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<String, String>,
behaviors: HashMap<String, String>,
exts: HashMap<String, SchemaExt>,
}
/// 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> {
Program::new(self)
}
}
/// Program is a complete and immutable program representer
pub struct Program {}
impl TryFrom<Schema> for Program {
type Error = Error;
fn try_from(value: Schema) -> std::result::Result<Self, Self::Error> {
Self::new(value)
}
}
impl Program {
pub fn new(schema: Schema) -> Result<Self> {
todo!()
}
}

View File

@@ -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<Scope> for View {
fn from(value: Scope) -> Self {
match value {
Scope::User => Self::User,
Scope::System => Self::System,
}
}
}
impl TryFrom<View> for Scope {
type Error = TryFromViewError;
fn try_from(value: View) -> Result<Self, Self::Error> {
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<Option<RegKey>, 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<Option<RegKey>, 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};

208
wfassoc/src/lowlevel.rs Normal file
View File

@@ -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<Scope> for View {
fn from(value: Scope) -> Self {
match value {
Scope::User => Self::User,
Scope::System => Self::System,
}
}
}
impl TryFrom<View> for Scope {
type Error = TryFromViewError;
fn try_from(value: View) -> Result<Self, Self::Error> {
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<Option<RegKey>, 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<Option<RegKey>, 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