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,
|
//! 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::{
|
||||||
|
HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_READ, KEY_WRITE,
|
||||||
|
};
|
||||||
|
use crate::extra::windows::{Ext, ProgId};
|
||||||
|
use crate::utilities;
|
||||||
|
|
||||||
// region: File Extension
|
// region: Error Types
|
||||||
|
|
||||||
/// The struct representing an file extension which must start with dot (`.`)
|
/// All possible error occurs in this crate.
|
||||||
/// and followed by at least one arbitrary characters.
|
#[derive(Debug, TeError)]
|
||||||
|
pub enum Error {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 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)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct Ext {
|
enum ProgIdKind {
|
||||||
/// The body of file extension (excluding dot).
|
/// Other ProgId which not follow Microsoft standards.
|
||||||
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),
|
Other(String),
|
||||||
Std(StdProgId),
|
/// Standard ProgId.
|
||||||
|
Std(ProgId)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&str> for ProgId {
|
// endregion
|
||||||
fn from(s: &str) -> Self {
|
|
||||||
// match it for standard ProgId first
|
// endregion
|
||||||
if let Ok(v) = StdProgId::from_str(s) {
|
|
||||||
Self::Std(v)
|
// region: File Extension Registry Key
|
||||||
} else {
|
|
||||||
// fallback with other
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
Self::Other(s.to_string())
|
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 Display for ProgId {
|
impl ExtKey {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
/// Set the default "Open With" of this file extension to given ProgId.
|
||||||
match self {
|
pub fn link_prog_id(&self, prog_id: &ProgIdKey, scope: Scope) -> Result<()> {
|
||||||
ProgId::Other(v) => write!(f, "{}", v),
|
todo!()
|
||||||
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.
|
/// Reset the default "Open With" of this file extension to blank.
|
||||||
pub fn get_vendor(&self) -> &str {
|
///
|
||||||
&self.vendor
|
/// 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!()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the component part of standard ProgId.
|
/// Query the default "Open With" of this file extension associated ProgId.
|
||||||
pub fn get_component(&self) -> &str {
|
///
|
||||||
&self.component
|
/// This function will return its associated ProgId if "Open With" was set.
|
||||||
}
|
pub fn query_prog_id(&self, view: View) -> Result<Option<ProgIdKey>> {
|
||||||
|
todo!()
|
||||||
/// 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))
|
|
||||||
} else {
|
|
||||||
Err(ParseProgIdError::new(s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region: CLSID
|
// region: ProgId Registry Key
|
||||||
|
|
||||||
pub struct Clsid {
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
inner: Uuid,
|
pub struct ProgIdKey {
|
||||||
|
prog_id: ProgIdKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clsid {
|
impl ProgIdKey {
|
||||||
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)]
|
|
||||||
#[error("given string \"{inner}\" is invalid for uuid")]
|
|
||||||
pub struct ParseClsidError {
|
|
||||||
inner: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ParseClsidError {
|
|
||||||
fn new(s: &str) -> Self {
|
|
||||||
Self {
|
|
||||||
inner: s.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|||||||
Reference in New Issue
Block a user