1
0

refactor: move something and implement FileName

This commit is contained in:
2026-04-15 15:12:34 +08:00
parent 69c7592f41
commit 1509723ada
7 changed files with 291 additions and 82 deletions

View File

@@ -5,98 +5,212 @@
compile_error!("Crate wfassoc is only supported on Windows.");
pub mod utilities;
pub mod concept;
pub mod win32;
// pub mod utilities;
// pub mod winconcept;
// pub mod win32ext;
// pub mod winregext;
use std::fmt::Display;
use std::str::FromStr;
use thiserror::Error as TeError;
use winreg::RegKey;
// use std::collections::HashMap;
// use thiserror::Error as TeError;
// region: Error Process
// /// Error occurs in this module.
// #[derive(Debug, TeError)]
// pub enum Error {}
/// 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),
}
// /// 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>,
// }
// region: Scope and View
// /// Internal used struct as the Schema file extensions hashmap value type.
// #[derive(Debug)]
// struct SchemaExt {
// name: String,
// icon: String,
// behavior: String,
// }
/// 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,
}
// 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(),
// }
// }
/// 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,
}
// pub fn set_identifier(&mut self, identifier: &str) -> Result<()> {}
/// The error occurs when cast View into Scope.
#[derive(Debug, TeError)]
#[error("hybrid view can not be cast into any scope")]
pub struct TryFromViewError {}
// pub fn set_path(&mut self, exe_path: &str) -> Result<()> {}
impl TryFromViewError {
fn new() -> Self {
Self {}
}
}
// pub fn set_clsid(&mut self, clsid: &str) -> Result<()> {}
impl From<Scope> for View {
fn from(value: Scope) -> Self {
match value {
Scope::User => Self::User,
Scope::System => Self::System,
}
}
}
// pub fn add_icon(&mut self, name: &str, value: &str) -> Result<()> {}
impl TryFrom<View> for Scope {
type Error = TryFromViewError;
// pub fn add_behavior(&mut self, name: &str, value: &str) -> Result<()> {}
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()),
}
}
}
// pub fn add_ext(
// &mut self,
// ext: &str,
// ext_name: &str,
// ext_icon: &str,
// ext_behavior: &str,
// ) -> Result<()> {
// }
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(())
}
}
}
// pub fn into_program(self) -> Result<Program> {
// Program::new(self)
// }
// }
// endregion
// /// Program is a complete and immutable program representer
// pub struct Program {}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AppPathsKey {
key: win32::concept::FileName,
}
// impl TryFrom<Schema> for Program {
// type Error = Error;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ApplicationsKey {
key: win32::concept::FileName,
}
// fn try_from(value: Schema) -> std::result::Result<Self, Self::Error> {
// Self::new(value)
// }
// }
// region: File Extension Key
// impl Program {
// pub fn new(schema: Schema) -> Result<Self> {}
// }
#[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)
}
// #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
// pub struct ExtKey {
// inner: winconcept::Ext
// }
}
// 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

View File

@@ -23,7 +23,7 @@ macro_rules! debug_println {
};
}
// region OS String Related
// region: OS String Related
/// The error occurs when casting `OsStr` into `str`.
#[derive(Debug, TeError)]

3
wfassoc/src/win32.rs Normal file
View File

@@ -0,0 +1,3 @@
pub mod concept;
pub mod utilities;
pub mod regext;

View File

@@ -59,6 +59,12 @@ impl Ext {
pub fn inner(&self) -> &str {
&self.body
}
/// Get the body part of file extension (with leading dot)
pub fn dotted_inner(&self) -> String {
// Reuse Display trait result
self.to_string()
}
}
/// The error occurs when try parsing string into FileExt.
@@ -123,7 +129,7 @@ impl BadProgIdPartError {
/// `[Vendor or Application].[Component].[Version]` format.
///
/// Additionally, `[Version]` part is optional.
///
///
/// However, most of applications do no follow this standard,
/// this scenario is not convered by this struct in there.
/// It should be done by other structs in other places.
@@ -237,7 +243,7 @@ impl FromStr for ProgId {
/// The struct representing Windows CLSID looks like
/// `{26EE0668-A00A-44D7-9371-BEB064C98683}` (case insensitive).
///
///
/// Please note that the curly brace is the essential part of CLSID.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Clsid {
@@ -312,7 +318,7 @@ impl ParseIconRefStrError {
/// The struct representing an Icon Reference String
/// looks like `%SystemRoot%\System32\imageres.dll,-72`.
///
///
/// As far as I know, the minus token `-` does nothing in this string.
/// The following number is just the index.
pub struct IconRefStr {
@@ -405,7 +411,7 @@ impl ParseStrRefStrError {
/// The struct representing an String Reference String
/// looks like `@%SystemRoot%\System32\shell32.dll,-30596`.
///
///
/// As far as I know, the minus token `-` does nothing in this string.
/// The following number is just the index.
pub struct StrRefStr {
@@ -757,3 +763,67 @@ impl FromStr for ExpandString {
}
// endregion
// region: File Name
pub type BadFileNameError = ParseFileNameError;
/// The struct representing a legal Windows file name.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FileName {
/// The validated legal Windows file name.
filename: String,
}
impl FileName {
/// Create a new file name.
///
/// `filename` is the file name for validating.
/// This function will validate whether given file name is legal in Windows,
/// in other words, checking whether it contain illegal words or Windows reserved name in given name
/// such as `?`, `COM` and etc.
pub fn new(filename: &str) -> Result<Self, BadFileNameError> {
Self::from_str(filename)
}
/// Get the validated file name.
pub fn inner(&self) -> &str {
&self.filename
}
}
/// The error occurs when constructing FileName with bad file name.
#[derive(Debug, TeError)]
#[error("given file name is illegal in Windows")]
pub enum ParseFileNameError {
/// Given string has embedded NUL.
EmbeddedNul(#[from] widestring::error::ContainsNul<WideChar>),
/// Given string has illegal char as Windows file name.
InvalidChar,
}
impl Display for FileName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.filename.fmt(f)
}
}
impl FromStr for FileName {
type Err = ParseFileNameError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use windows_sys::Win32::UI::Shell::PathCleanupSpec;
// Make buffer and call function.
let mut spec = WideCString::from_str(s)?;
let rv = unsafe { PathCleanupSpec(std::ptr::null(), spec.as_mut_ptr()) };
if rv != 0 {
return Err(ParseFileNameError::InvalidChar);
}
// Build self and return
Ok(Self { filename: s.to_string() })
}
}
// endregion

View File

@@ -14,7 +14,7 @@ use winreg::types::FromRegValue;
/// Get the subkey with given name.
///
/// If error occurs when fetching given subkey, it return `Err(...)`,
/// otherwise, it will return `Ok(Some(...))` if aubkey is existing,
/// otherwise, it will return `Ok(Some(...))` if subkey is existing,
/// or `Ok(None)` if there is no suchsub key.
///
/// Comparing with the function provided by winreg,

View File

@@ -1,5 +1,5 @@
use std::str::FromStr;
use wfassoc::concept::*;
use wfassoc::win32::concept::*;
// region: File Extension
@@ -230,3 +230,25 @@ fn test_expand_string() {
}
// endregion
// region: File Name
#[test]
fn test_file_name() {
fn ok_tester(s: &str) {
let rv = FileName::from_str(s);
assert!(rv.is_ok());
let rv = rv.unwrap();
assert_eq!(s, rv.inner());
}
fn err_tester(s: &str) {
let rv = FileName::from_str(s);
assert!(rv.is_err());
}
ok_tester("GoodExecutable.exe");
err_tester("*.?xaml");
err_tester(r#"\\\lol///"#);
}
// endregion