diff --git a/wfassoc/src/assoc.rs b/wfassoc/src/assoc.rs index 07b0784..c91dbcb 100644 --- a/wfassoc/src/assoc.rs +++ b/wfassoc/src/assoc.rs @@ -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; +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 (`.`) -/// and followed by at least one arbitrary characters. +/// All possible error occurs in this crate. +#[derive(Debug, TeError)] +pub enum Error { + +} + +/// The result type used in this crate. +pub type Result = std::result::Result; + +// 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 for Scope { + type Error = TryFromViewError; + + fn try_from(value: View) -> std::result::Result { + 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 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)] -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::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 { - static RE: LazyLock = 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 { +enum ProgIdKind { + /// Other ProgId which not follow Microsoft standards. Other(String), - Std(StdProgId), + /// Standard ProgId. + Std(ProgId) } -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()) - } +// 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 { + todo!() + } + + fn open_ext_key(&self) -> Result { + todo!() + } + + fn try_open_ext_key(&self) -> Result> { + todo!() } } -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, -} - -impl StdProgId { - /// Create a new standard ProgId. - pub fn new(vendor: &str, component: &str, version: Option) -> Self { - Self { - vendor: vendor.to_string(), - component: component.to_string(), - version, - } +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!() } - /// Get the vendor part of standard ProgId. - pub fn get_vendor(&self) -> &str { - &self.vendor + /// 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!() } - /// 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 { - self.version - } -} - -impl FromStr for StdProgId { - type Err = ParseProgIdError; - - fn from_str(s: &str) -> Result { - static RE: LazyLock = - 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::() - .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), - } + /// 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> { + todo!() } } // endregion -// region: CLSID +// region: ProgId Registry Key -pub struct Clsid { - inner: Uuid, +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ProgIdKey { + prog_id: ProgIdKind, } -impl Clsid { - pub fn new(uuid: &str) -> Result { - Self::from_str(uuid) - } +impl ProgIdKey { - // 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 { - 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