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:
@ -1,238 +1,178 @@
|
||||
//! The module including all struct representing Windows file association concept,
|
||||
//! like file extension, ProgId, CLSID and etc.
|
||||
|
||||
use regex::Regex;
|
||||
use std::fmt::Display;
|
||||
use std::str::FromStr;
|
||||
use std::sync::LazyLock;
|
||||
use thiserror::Error as TeError;
|
||||
use uuid::Uuid;
|
||||
|
||||
// region: File Extension
|
||||
|
||||
/// 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,
|
||||
use winreg::RegKey;
|
||||
use winreg::enums::{
|
||||
HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_READ, KEY_WRITE,
|
||||
};
|
||||
Ok(Self::new(vendor, component, version))
|
||||
} else {
|
||||
Err(ParseProgIdError::new(s))
|
||||
}
|
||||
}
|
||||
}
|
||||
use crate::extra::windows::{Ext, ProgId};
|
||||
use crate::utilities;
|
||||
|
||||
impl Display for StdProgId {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
// region: Error Types
|
||||
|
||||
// endregion
|
||||
|
||||
// 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
|
||||
/// All possible error occurs in this crate.
|
||||
#[derive(Debug, TeError)]
|
||||
#[error("given string \"{inner}\" is invalid for uuid")]
|
||||
pub struct ParseClsidError {
|
||||
inner: String,
|
||||
pub enum Error {
|
||||
|
||||
}
|
||||
|
||||
impl ParseClsidError {
|
||||
fn new(s: &str) -> Self {
|
||||
Self {
|
||||
inner: s.to_string(),
|
||||
/// The result type used in this crate.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
// 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 {
|
||||
type Err = ParseClsidError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self {
|
||||
inner: Uuid::parse_str(s).map_err(|_| ParseClsidError::new(s))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Clsid {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.inner.braced().to_string())
|
||||
impl Scope {
|
||||
/// Check whether we have enough privilege when operating in current scope.
|
||||
/// If we have, return true, otherwise false.
|
||||
pub fn has_privilege(&self) -> bool {
|
||||
// If we operate on System, and we do not has privilege,
|
||||
// we think we do not have privilege, otherwise,
|
||||
// there is no privilege required.
|
||||
!matches!(self, Self::System if !utilities::has_privilege())
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user