feat: make a clear boundary for lowlevel and highlevel API
This commit is contained in:
102
wfassoc/src/highlevel.rs
Normal file
102
wfassoc/src/highlevel.rs
Normal 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!()
|
||||
}
|
||||
}
|
||||
@@ -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
208
wfassoc/src/lowlevel.rs
Normal 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
|
||||
Reference in New Issue
Block a user