1
0

refactor(wfassoc): restructure association module for registry operations

- Removed unused regex and uuid dependencies
- Replaced custom Ext and ProgId implementations with windows-specific types
- Added Scope and View enums for registry access control
- Introduced ExtKey and ProgIdKey structs for registry key handling
- Added error types for better error handling
- Implemented stub methods for registry operations
- Restructured module organization for better clarity
- Added utility functions for privilege checking
This commit is contained in:
2025-10-28 14:10:28 +08:00
parent 4d679588f8
commit 3f1a070b65

View File

@ -1,238 +1,178 @@
//! The module including all struct representing Windows file association concept, //! The module including all struct representing Windows file association concept,
//! like file extension, ProgId, CLSID and etc. //! like file extension, ProgId, CLSID and etc.
use regex::Regex;
use std::fmt::Display; use std::fmt::Display;
use std::str::FromStr; use std::str::FromStr;
use std::sync::LazyLock;
use thiserror::Error as TeError; use thiserror::Error as TeError;
use uuid::Uuid; use winreg::RegKey;
use winreg::enums::{
// region: File Extension HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_READ, KEY_WRITE,
/// The struct representing an file extension which must start with dot (`.`)
/// and followed by at least one arbitrary characters.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Ext {
/// The body of file extension (excluding dot).
body: String,
}
impl Ext {
/// Create an new file extension.
pub fn new(raw: &str) -> Result<Self, ParseExtError> {
Self::from_str(raw)
}
/// Get the body part of file extension (excluding dot)
pub fn inner(&self) -> &str {
&self.body
}
}
/// The error occurs when try parsing string into FileExt.
#[derive(Debug, TeError)]
#[error("given file extension name \"{inner}\" is invalid")]
pub struct ParseExtError {
inner: String,
}
impl ParseExtError {
fn new(inner: &str) -> Self {
Self {
inner: inner.to_string(),
}
}
}
impl Display for Ext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, ".{}", self.body)
}
}
impl FromStr for Ext {
type Err = ParseExtError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^\.([^\.]+)$").unwrap());
match RE.captures(s) {
Some(v) => Ok(Self {
body: v[1].to_string(),
}),
None => Err(ParseExtError::new(s)),
}
}
}
// endregion
// region: Programmatic Identifiers (ProgId)
/// The struct representing Programmatic Identifiers (ProgId).
///
/// Because there is optional part in standard ProgId, and not all software developers
/// are willing to following Microsoft suggestions, there is no strict constaint for ProgId.
/// So this struct is actually an enum which holding any possible ProgId format.
///
/// Reference:
/// - https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids
/// - https://learn.microsoft.com/en-us/windows/win32/com/-progid--key
pub enum ProgId {
Other(String),
Std(StdProgId),
}
impl From<&str> for ProgId {
fn from(s: &str) -> Self {
// match it for standard ProgId first
if let Ok(v) = StdProgId::from_str(s) {
Self::Std(v)
} else {
// fallback with other
Self::Other(s.to_string())
}
}
}
impl Display for ProgId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ProgId::Other(v) => write!(f, "{}", v),
ProgId::Std(v) => write!(f, "{}", v),
}
}
}
/// The error occurs when parsing ProgId.
#[derive(Debug, TeError)]
#[error("given ProgId \"{inner}\" is invalid")]
pub struct ParseProgIdError {
inner: String,
}
impl ParseProgIdError {
fn new(s: &str) -> Self {
Self {
inner: s.to_string(),
}
}
}
/// The ProgId exactly follows Microsoft suggested
/// `[Vendor or Application].[Component].[Version]` format.
/// And `[Version]` part is optional.
pub struct StdProgId {
vendor: String,
component: String,
version: Option<u32>,
}
impl StdProgId {
/// Create a new standard ProgId.
pub fn new(vendor: &str, component: &str, version: Option<u32>) -> Self {
Self {
vendor: vendor.to_string(),
component: component.to_string(),
version,
}
}
/// Get the vendor part of standard ProgId.
pub fn get_vendor(&self) -> &str {
&self.vendor
}
/// Get the component part of standard ProgId.
pub fn get_component(&self) -> &str {
&self.component
}
/// Get the version part of standard ProgId.
pub fn get_version(&self) -> Option<u32> {
self.version
}
}
impl FromStr for StdProgId {
type Err = ParseProgIdError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
static RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^([a-zA-Z0-9]+)\.([a-zA-Z0-9]+)(\.([0-9]+))?$").unwrap());
let caps = RE.captures(s);
if let Some(caps) = caps {
let vendor = &caps[1];
let component = &caps[2];
let version = match caps.get(4) {
Some(sv) => Some(
sv.as_str()
.parse::<u32>()
.map_err(|_| ParseProgIdError::new(s))?,
),
None => None,
}; };
Ok(Self::new(vendor, component, version)) use crate::extra::windows::{Ext, ProgId};
} else { use crate::utilities;
Err(ParseProgIdError::new(s))
}
}
}
impl Display for StdProgId { // region: Error Types
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.version {
Some(version) => write!(f, "{}.{}.{}", self.vendor, self.component, version),
None => write!(f, "{}.{}", self.vendor, self.component),
}
}
}
// endregion /// All possible error occurs in this crate.
// region: CLSID
pub struct Clsid {
inner: Uuid,
}
impl Clsid {
pub fn new(uuid: &str) -> Result<Self, ParseClsidError> {
Self::from_str(uuid)
}
// TODO: May add CLSID generator in there.
}
/// The error occurs when parsing CLSID
#[derive(Debug, TeError)] #[derive(Debug, TeError)]
#[error("given string \"{inner}\" is invalid for uuid")] pub enum Error {
pub struct ParseClsidError {
inner: String,
} }
impl ParseClsidError { /// The result type used in this crate.
fn new(s: &str) -> Self { pub type Result<T> = std::result::Result<T, Error>;
Self {
inner: s.to_string(), // endregion
// region: Data Types
/// The token for access registered items in Program.
/// This is usually returned when you registering them.
pub type Token = usize;
// region: Scope
/// The scope where wfassoc will register and unregister application.
#[derive(Debug, Copy, Clone)]
pub enum Scope {
/// Scope for current user.
User,
/// Scope for all users under this computer.
System,
}
/// The error occurs when cast View into Scope.
#[derive(Debug, TeError)]
#[error("hybrid View can not be cast into Scope")]
pub struct TryFromViewError {}
impl TryFromViewError {
fn new() -> Self {
Self {}
}
}
impl TryFrom<View> for Scope {
type Error = TryFromViewError;
fn try_from(value: View) -> std::result::Result<Self, Self::Error> {
match value {
View::User => Ok(Self::User),
View::System => Ok(Self::System),
View::Hybrid => Err(TryFromViewError::new()),
} }
} }
} }
impl FromStr for Clsid { impl Scope {
type Err = ParseClsidError; /// Check whether we have enough privilege when operating in current scope.
/// If we have, return true, otherwise false.
fn from_str(s: &str) -> Result<Self, Self::Err> { pub fn has_privilege(&self) -> bool {
Ok(Self { // If we operate on System, and we do not has privilege,
inner: Uuid::parse_str(s).map_err(|_| ParseClsidError::new(s))?, // we think we do not have privilege, otherwise,
}) // there is no privilege required.
} !matches!(self, Self::System if !utilities::has_privilege())
}
impl Display for Clsid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.inner.braced().to_string())
} }
} }
// endregion
// region: View
/// The view when wfassoc querying file extension association.
#[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,
}
impl From<Scope> for View {
fn from(value: Scope) -> Self {
match value {
Scope::User => Self::User,
Scope::System => Self::System,
}
}
}
// endregion
// region ProgId Kind
/// The variant of ProgId for the compatibility
/// with those software which do not follow Microsoft suggestions.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum ProgIdKind {
/// Other ProgId which not follow Microsoft standards.
Other(String),
/// Standard ProgId.
Std(ProgId)
}
// endregion
// endregion
// region: File Extension Registry Key
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ExtKey {
ext: Ext,
}
impl ExtKey {
fn open_ext_parent_key(&self) -> Result<RegKey> {
todo!()
}
fn open_ext_key(&self) -> Result<RegKey> {
todo!()
}
fn try_open_ext_key(&self) -> Result<Option<RegKey>> {
todo!()
}
}
impl ExtKey {
/// Set the default "Open With" of this file extension to given ProgId.
pub fn link_prog_id(&self, prog_id: &ProgIdKey, scope: Scope) -> Result<()> {
todo!()
}
/// Reset the default "Open With" of this file extension to blank.
///
/// If the default "Open With" of this file extension is not given ProgId,
/// or there is no such file extension, this function do nothing.
pub fn unlink_prog_id(&self, prog_id: &ProgIdKey, scope: Scope) -> Result<()> {
todo!()
}
/// Query the default "Open With" of this file extension associated ProgId.
///
/// This function will return its associated ProgId if "Open With" was set.
pub fn query_prog_id(&self, view: View) -> Result<Option<ProgIdKey>> {
todo!()
}
}
// endregion
// region: ProgId Registry Key
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ProgIdKey {
prog_id: ProgIdKind,
}
impl ProgIdKey {
}
// endregion // endregion