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 utilities;
|
||||||
pub mod win32;
|
pub mod win32;
|
||||||
|
pub mod lowlevel;
|
||||||
|
pub mod highlevel;
|
||||||
|
|
||||||
use std::fmt::Display;
|
pub use highlevel::{Scope, View, Schema, Program};
|
||||||
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
|
|
||||||
|
|||||||
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