refactor: move something and implement FileName
This commit is contained in:
@@ -5,98 +5,212 @@
|
||||
compile_error!("Crate wfassoc is only supported on Windows.");
|
||||
|
||||
pub mod utilities;
|
||||
pub mod concept;
|
||||
pub mod win32;
|
||||
|
||||
// pub mod utilities;
|
||||
// pub mod winconcept;
|
||||
// pub mod win32ext;
|
||||
// pub mod winregext;
|
||||
use std::fmt::Display;
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error as TeError;
|
||||
use winreg::RegKey;
|
||||
|
||||
// use std::collections::HashMap;
|
||||
// use thiserror::Error as TeError;
|
||||
// region: Error Process
|
||||
|
||||
// /// Error occurs in this module.
|
||||
// #[derive(Debug, TeError)]
|
||||
// pub enum Error {}
|
||||
/// Error occurs in this module.
|
||||
#[derive(Debug, TeError)]
|
||||
pub enum Error {
|
||||
#[error("can not perform this operation because lack essential privilege.")]
|
||||
NoPrivilege,
|
||||
#[error("{0}")]
|
||||
BadRegOp(#[from] std::io::Error),
|
||||
#[error("{0}")]
|
||||
UnexpectedBlankKey(#[from] win32::regext::BlankPathError),
|
||||
}
|
||||
|
||||
// /// Result type used in this module.
|
||||
// type Result<T> = std::result::Result<T, Error>;
|
||||
// endregion
|
||||
|
||||
// /// Schema is the sketchpad of complete Program.
|
||||
// ///
|
||||
// /// We will create a Schema first, fill some properties, add file extensions,
|
||||
// /// then convert it into immutable Program for following using.
|
||||
// #[derive(Debug)]
|
||||
// pub struct Schema {
|
||||
// identifier: String,
|
||||
// path: String,
|
||||
// clsid: String,
|
||||
// icons: HashMap<String, String>,
|
||||
// behaviors: HashMap<String, String>,
|
||||
// exts: HashMap<String, SchemaExt>,
|
||||
// }
|
||||
// region: Scope and View
|
||||
|
||||
// /// Internal used struct as the Schema file extensions hashmap value type.
|
||||
// #[derive(Debug)]
|
||||
// struct SchemaExt {
|
||||
// name: String,
|
||||
// icon: String,
|
||||
// behavior: String,
|
||||
// }
|
||||
/// 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,
|
||||
}
|
||||
|
||||
// impl Schema {
|
||||
// pub fn new() -> Self {
|
||||
// Self {
|
||||
// identifier: String::new(),
|
||||
// path: String::new(),
|
||||
// clsid: String::new(),
|
||||
// icons: HashMap::new(),
|
||||
// behaviors: HashMap::new(),
|
||||
// exts: HashMap::new(),
|
||||
// }
|
||||
// }
|
||||
/// 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,
|
||||
}
|
||||
|
||||
// pub fn set_identifier(&mut self, identifier: &str) -> Result<()> {}
|
||||
/// The error occurs when cast View into Scope.
|
||||
#[derive(Debug, TeError)]
|
||||
#[error("hybrid view can not be cast into any scope")]
|
||||
pub struct TryFromViewError {}
|
||||
|
||||
// pub fn set_path(&mut self, exe_path: &str) -> Result<()> {}
|
||||
impl TryFromViewError {
|
||||
fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn set_clsid(&mut self, clsid: &str) -> Result<()> {}
|
||||
impl From<Scope> for View {
|
||||
fn from(value: Scope) -> Self {
|
||||
match value {
|
||||
Scope::User => Self::User,
|
||||
Scope::System => Self::System,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn add_icon(&mut self, name: &str, value: &str) -> Result<()> {}
|
||||
impl TryFrom<View> for Scope {
|
||||
type Error = TryFromViewError;
|
||||
|
||||
// pub fn add_behavior(&mut self, name: &str, value: &str) -> Result<()> {}
|
||||
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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn add_ext(
|
||||
// &mut self,
|
||||
// ext: &str,
|
||||
// ext_name: &str,
|
||||
// ext_icon: &str,
|
||||
// ext_behavior: &str,
|
||||
// ) -> Result<()> {
|
||||
// }
|
||||
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 !win32::utilities::has_privilege()) {
|
||||
Err(Error::NoPrivilege)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn into_program(self) -> Result<Program> {
|
||||
// Program::new(self)
|
||||
// }
|
||||
// }
|
||||
// endregion
|
||||
|
||||
// /// Program is a complete and immutable program representer
|
||||
// pub struct Program {}
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct AppPathsKey {
|
||||
key: win32::concept::FileName,
|
||||
}
|
||||
|
||||
// impl TryFrom<Schema> for Program {
|
||||
// type Error = Error;
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct ApplicationsKey {
|
||||
key: win32::concept::FileName,
|
||||
}
|
||||
|
||||
// fn try_from(value: Schema) -> std::result::Result<Self, Self::Error> {
|
||||
// Self::new(value)
|
||||
// }
|
||||
// }
|
||||
// region: File Extension Key
|
||||
|
||||
// impl Program {
|
||||
// pub fn new(schema: Schema) -> Result<Self> {}
|
||||
// }
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct ExtKey {
|
||||
ext: win32::concept::Ext,
|
||||
}
|
||||
|
||||
impl ExtKey {
|
||||
fn open_scope(&self, scope: Scope) -> Result<Option<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)?;
|
||||
// open extension key if possible
|
||||
let thisext = win32::regext::try_open_subkey_with_flags(
|
||||
&classes,
|
||||
win32::regext::blank_path_guard(self.ext.dotted_inner())?,
|
||||
KEY_READ | KEY_WRITE,
|
||||
)?;
|
||||
// okey
|
||||
Ok(thisext)
|
||||
}
|
||||
|
||||
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)?,
|
||||
};
|
||||
// open extension key if possible
|
||||
let thisext = win32::regext::try_open_subkey_with_flags(
|
||||
&classes,
|
||||
win32::regext::blank_path_guard(self.ext.dotted_inner())?,
|
||||
KEY_READ,
|
||||
)?;
|
||||
// okey
|
||||
Ok(thisext)
|
||||
}
|
||||
|
||||
|
||||
// #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
// pub struct ExtKey {
|
||||
// inner: winconcept::Ext
|
||||
// }
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region: ProgId Key
|
||||
|
||||
// region: Losse ProgId
|
||||
|
||||
/// The enum representing a losse Programmatic Identifiers (ProgId).
|
||||
///
|
||||
/// In real world, not all software developers are willing to following Microsoft suggestions to use ProgId,
|
||||
/// They use string which do not have any regulation as ProgId.
|
||||
/// This enum is designed for handling this scenario.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
enum LosseProgId {
|
||||
Plain(String),
|
||||
Strict(win32::concept::ProgId),
|
||||
}
|
||||
|
||||
impl Display for LosseProgId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
LosseProgId::Plain(v) => write!(f, "{}", v),
|
||||
LosseProgId::Strict(v) => write!(f, "{}", v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for LosseProgId {
|
||||
fn from(s: &str) -> Self {
|
||||
// match it for standard ProgId first
|
||||
if let Ok(v) = win32::concept::ProgId::from_str(s) {
|
||||
Self::Strict(v)
|
||||
} else {
|
||||
// fallback with other
|
||||
Self::Plain(s.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region: ProgId Key
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct ProgIdKey {
|
||||
progid: LosseProgId,
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// endregion
|
||||
|
||||
@@ -23,7 +23,7 @@ macro_rules! debug_println {
|
||||
};
|
||||
}
|
||||
|
||||
// region OS String Related
|
||||
// region: OS String Related
|
||||
|
||||
/// The error occurs when casting `OsStr` into `str`.
|
||||
#[derive(Debug, TeError)]
|
||||
|
||||
3
wfassoc/src/win32.rs
Normal file
3
wfassoc/src/win32.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod concept;
|
||||
pub mod utilities;
|
||||
pub mod regext;
|
||||
@@ -59,6 +59,12 @@ impl Ext {
|
||||
pub fn inner(&self) -> &str {
|
||||
&self.body
|
||||
}
|
||||
|
||||
/// Get the body part of file extension (with leading dot)
|
||||
pub fn dotted_inner(&self) -> String {
|
||||
// Reuse Display trait result
|
||||
self.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// The error occurs when try parsing string into FileExt.
|
||||
@@ -123,7 +129,7 @@ impl BadProgIdPartError {
|
||||
/// `[Vendor or Application].[Component].[Version]` format.
|
||||
///
|
||||
/// Additionally, `[Version]` part is optional.
|
||||
///
|
||||
///
|
||||
/// However, most of applications do no follow this standard,
|
||||
/// this scenario is not convered by this struct in there.
|
||||
/// It should be done by other structs in other places.
|
||||
@@ -237,7 +243,7 @@ impl FromStr for ProgId {
|
||||
|
||||
/// The struct representing Windows CLSID looks like
|
||||
/// `{26EE0668-A00A-44D7-9371-BEB064C98683}` (case insensitive).
|
||||
///
|
||||
///
|
||||
/// Please note that the curly brace is the essential part of CLSID.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Clsid {
|
||||
@@ -312,7 +318,7 @@ impl ParseIconRefStrError {
|
||||
|
||||
/// The struct representing an Icon Reference String
|
||||
/// looks like `%SystemRoot%\System32\imageres.dll,-72`.
|
||||
///
|
||||
///
|
||||
/// As far as I know, the minus token `-` does nothing in this string.
|
||||
/// The following number is just the index.
|
||||
pub struct IconRefStr {
|
||||
@@ -405,7 +411,7 @@ impl ParseStrRefStrError {
|
||||
|
||||
/// The struct representing an String Reference String
|
||||
/// looks like `@%SystemRoot%\System32\shell32.dll,-30596`.
|
||||
///
|
||||
///
|
||||
/// As far as I know, the minus token `-` does nothing in this string.
|
||||
/// The following number is just the index.
|
||||
pub struct StrRefStr {
|
||||
@@ -757,3 +763,67 @@ impl FromStr for ExpandString {
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region: File Name
|
||||
|
||||
pub type BadFileNameError = ParseFileNameError;
|
||||
|
||||
/// The struct representing a legal Windows file name.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct FileName {
|
||||
/// The validated legal Windows file name.
|
||||
filename: String,
|
||||
}
|
||||
|
||||
impl FileName {
|
||||
/// Create a new file name.
|
||||
///
|
||||
/// `filename` is the file name for validating.
|
||||
/// This function will validate whether given file name is legal in Windows,
|
||||
/// in other words, checking whether it contain illegal words or Windows reserved name in given name
|
||||
/// such as `?`, `COM` and etc.
|
||||
pub fn new(filename: &str) -> Result<Self, BadFileNameError> {
|
||||
Self::from_str(filename)
|
||||
}
|
||||
|
||||
/// Get the validated file name.
|
||||
pub fn inner(&self) -> &str {
|
||||
&self.filename
|
||||
}
|
||||
}
|
||||
|
||||
/// The error occurs when constructing FileName with bad file name.
|
||||
#[derive(Debug, TeError)]
|
||||
#[error("given file name is illegal in Windows")]
|
||||
pub enum ParseFileNameError {
|
||||
/// Given string has embedded NUL.
|
||||
EmbeddedNul(#[from] widestring::error::ContainsNul<WideChar>),
|
||||
/// Given string has illegal char as Windows file name.
|
||||
InvalidChar,
|
||||
}
|
||||
|
||||
impl Display for FileName {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.filename.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for FileName {
|
||||
type Err = ParseFileNameError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
use windows_sys::Win32::UI::Shell::PathCleanupSpec;
|
||||
|
||||
// Make buffer and call function.
|
||||
let mut spec = WideCString::from_str(s)?;
|
||||
let rv = unsafe { PathCleanupSpec(std::ptr::null(), spec.as_mut_ptr()) };
|
||||
if rv != 0 {
|
||||
return Err(ParseFileNameError::InvalidChar);
|
||||
}
|
||||
|
||||
// Build self and return
|
||||
Ok(Self { filename: s.to_string() })
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
@@ -14,7 +14,7 @@ use winreg::types::FromRegValue;
|
||||
/// Get the subkey with given name.
|
||||
///
|
||||
/// If error occurs when fetching given subkey, it return `Err(...)`,
|
||||
/// otherwise, it will return `Ok(Some(...))` if aubkey is existing,
|
||||
/// otherwise, it will return `Ok(Some(...))` if subkey is existing,
|
||||
/// or `Ok(None)` if there is no suchsub key.
|
||||
///
|
||||
/// Comparing with the function provided by winreg,
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::str::FromStr;
|
||||
use wfassoc::concept::*;
|
||||
use wfassoc::win32::concept::*;
|
||||
|
||||
// region: File Extension
|
||||
|
||||
@@ -230,3 +230,25 @@ fn test_expand_string() {
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region: File Name
|
||||
|
||||
#[test]
|
||||
fn test_file_name() {
|
||||
fn ok_tester(s: &str) {
|
||||
let rv = FileName::from_str(s);
|
||||
assert!(rv.is_ok());
|
||||
let rv = rv.unwrap();
|
||||
assert_eq!(s, rv.inner());
|
||||
}
|
||||
fn err_tester(s: &str) {
|
||||
let rv = FileName::from_str(s);
|
||||
assert!(rv.is_err());
|
||||
}
|
||||
|
||||
ok_tester("GoodExecutable.exe");
|
||||
err_tester("*.?xaml");
|
||||
err_tester(r#"\\\lol///"#);
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
Reference in New Issue
Block a user