1
0

write shit

This commit is contained in:
2025-10-10 20:54:44 +08:00
parent f7d92243c9
commit 07a8c6a11d

View File

@ -6,10 +6,10 @@ compile_error!("Crate wfassoc is only supported on Windows.");
use regex::Regex; use regex::Regex;
use std::fmt::Display; use std::fmt::Display;
use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use std::sync::LazyLock; use std::sync::LazyLock;
use thiserror::Error as TeError; use thiserror::Error as TeError;
use winreg::RegKey;
// region: Error Types // region: Error Types
@ -21,37 +21,16 @@ pub enum Error {
)] )]
NoPrivilege, NoPrivilege,
#[error("{0}")] #[error("{0}")]
BadFileExt(#[from] ParseFileExtError), Register(#[from] std::io::Error),
#[error("{0}")] #[error("{0}")]
BadExecRc(#[from] ParseExecRcError), BadFileExt(#[from] ParseFileExtError),
#[error("{0}")] #[error("{0}")]
BadProgId(#[from] ParseProgIdError), BadProgId(#[from] ParseProgIdError),
} }
// endregion // endregion
// region: Basic Types // region: Privilege, 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,
}
/// Check whether current process has administrative privilege. /// Check whether current process has administrative privilege.
/// ///
@ -106,6 +85,71 @@ pub fn has_privilege() -> bool {
is_member != 0 is_member != 0
} }
/// 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 !has_privilege()) {
Err(Error::NoPrivilege)
} else {
Ok(())
}
}
}
// endregion // endregion
// region: File Extension // region: File Extension
@ -120,23 +164,13 @@ pub struct FileExt {
impl FileExt { impl FileExt {
pub fn new(file_ext: &str) -> Result<Self, ParseFileExtError> { pub fn new(file_ext: &str) -> Result<Self, ParseFileExtError> {
static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^\.([^\.]+)$").unwrap()); Self::from_str(file_ext)
match RE.captures(file_ext) {
Some(v) => Ok(Self {
inner: v[1].to_string(),
}),
None => Err(ParseFileExtError::new()),
}
}
pub fn query(&self, view: View) -> Option<FileExtAssoc> {
FileExtAssoc::new(self, view)
} }
} }
/// The error occurs when try parsing string into FileExt. /// The error occurs when try parsing string into FileExt.
#[derive(Debug, TeError)] #[derive(Debug, TeError)]
#[error("given file extension is illegal")] #[error("given file extension is invalid")]
pub struct ParseFileExtError {} pub struct ParseFileExtError {}
impl ParseFileExtError { impl ParseFileExtError {
@ -155,7 +189,81 @@ impl FromStr for FileExt {
type Err = ParseFileExtError; type Err = ParseFileExtError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s) static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^\.([^\.]+)$").unwrap());
match RE.captures(s) {
Some(v) => Ok(Self {
inner: v[1].to_string(),
}),
None => Err(ParseFileExtError::new()),
}
}
}
impl FileExt {
fn open_scope(&self, scope: Scope) -> Result<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)?;
// okey
Ok(classes)
}
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)?,
};
// check whether there is this ext
classes.
// open extension key if possible
let thisext = classes.open_subkey_with_flags(file_ext.to_string(), KEY_READ)?;
// okey
Ok(classes)
}
pub fn get_current(&self, view: View) -> Option<ProgId> {
todo!()
}
pub fn set_current(&mut self, scope: Scope, prog_id: Option<&ProgId>) -> Result<(), Error> {
scope.check_privilege()?;
todo!()
}
pub fn iter_open_with(&self, view: View) -> Result<impl Iterator<Item = ProgId>, Error> {
todo!()
}
pub fn insert_open_with(&mut self, scope: Scope, prog_id: &ProgId) -> Result<(), Error> {
scope.check_privilege()?;
todo!()
}
pub fn flash_open_with(
&mut self,
scope: Scope,
prog_ids: impl Iterator<Item = ProgId>,
) -> Result<(), Error> {
scope.check_privilege()?;
todo!()
} }
} }
@ -280,30 +388,30 @@ impl FileExtAssoc {
// region: Programmatic Identifiers (ProgId) // region: Programmatic Identifiers (ProgId)
/// The struct representing Programmatic Identifiers (ProgId). /// The struct representing Programmatic Identifiers (ProgId).
/// ///
/// Because there is optional part in ProgId, and not all software developer /// Because there is optional part in standard ProgId, and not all software developers
/// are willing to following Microsoft standard, there is no strict constaint for ProgId. /// 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. /// 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
pub enum ProgId { pub enum ProgId {
Plain(String), Plain(String),
Loose(LosseProgId), Loose(LosseProgId),
Strict(StrictProgId), Strict(StrictProgId),
} }
impl FromStr for ProgId { impl From<&str> for ProgId {
type Err = ParseProgIdError; fn from(s: &str) -> Self {
fn from_str(s: &str) -> Result<Self, Self::Err> {
// match it for strict ProgId first // match it for strict ProgId first
if let Ok(v) = StrictProgId::from_str(s) { if let Ok(v) = StrictProgId::from_str(s) {
return Ok(Self::Strict(v)); return Self::Strict(v);
} }
// then match for loose ProgId // then match for loose ProgId
if let Ok(v) = LosseProgId::from_str(s) { if let Ok(v) = LosseProgId::from_str(s) {
return Ok(Self::Loose(v)); return Self::Loose(v);
} }
// fallback with plain // fallback with plain
Ok(Self::Plain(s.to_string())) Self::Plain(s.to_string())
} }
} }
@ -413,7 +521,9 @@ impl FromStr for StrictProgId {
if let Some(caps) = caps { if let Some(caps) = caps {
let vendor = &caps[1]; let vendor = &caps[1];
let component = &caps[2]; let component = &caps[2];
let version = caps[3].parse::<u32>().map_err(|_| ParseProgIdError::new())?; let version = caps[3]
.parse::<u32>()
.map_err(|_| ParseProgIdError::new())?;
Ok(Self::new(vendor, component, version)) Ok(Self::new(vendor, component, version))
} else { } else {
Err(ParseProgIdError::new()) Err(ParseProgIdError::new())