refactor: move something and implement FileName
This commit is contained in:
@@ -5,98 +5,212 @@
|
|||||||
compile_error!("Crate wfassoc is only supported on Windows.");
|
compile_error!("Crate wfassoc is only supported on Windows.");
|
||||||
|
|
||||||
pub mod utilities;
|
pub mod utilities;
|
||||||
pub mod concept;
|
pub mod win32;
|
||||||
|
|
||||||
// pub mod utilities;
|
use std::fmt::Display;
|
||||||
// pub mod winconcept;
|
use std::str::FromStr;
|
||||||
// pub mod win32ext;
|
use thiserror::Error as TeError;
|
||||||
// pub mod winregext;
|
use winreg::RegKey;
|
||||||
|
|
||||||
// use std::collections::HashMap;
|
// region: Error Process
|
||||||
// use thiserror::Error as TeError;
|
|
||||||
|
|
||||||
// /// Error occurs in this module.
|
/// Error occurs in this module.
|
||||||
// #[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
// pub enum Error {}
|
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.
|
// endregion
|
||||||
// type Result<T> = std::result::Result<T, Error>;
|
|
||||||
|
|
||||||
// /// Schema is the sketchpad of complete Program.
|
// region: Scope and View
|
||||||
// ///
|
|
||||||
// /// 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>,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Internal used struct as the Schema file extensions hashmap value type.
|
/// The scope where wfassoc will register and unregister.
|
||||||
// #[derive(Debug)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
// struct SchemaExt {
|
pub enum Scope {
|
||||||
// name: String,
|
/// Scope for current user.
|
||||||
// icon: String,
|
User,
|
||||||
// behavior: String,
|
/// Scope for all users under this computer.
|
||||||
// }
|
System,
|
||||||
|
}
|
||||||
|
|
||||||
// impl Schema {
|
/// The view when wfassoc querying infomations.
|
||||||
// pub fn new() -> Self {
|
#[derive(Debug, Copy, Clone)]
|
||||||
// Self {
|
pub enum View {
|
||||||
// identifier: String::new(),
|
/// The view of current user.
|
||||||
// path: String::new(),
|
User,
|
||||||
// clsid: String::new(),
|
/// The view of system.
|
||||||
// icons: HashMap::new(),
|
System,
|
||||||
// behaviors: HashMap::new(),
|
/// Hybrid view of User and System.
|
||||||
// exts: HashMap::new(),
|
/// 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(
|
impl Scope {
|
||||||
// &mut self,
|
/// Check whether we have enough privilege when operating in current scope.
|
||||||
// ext: &str,
|
/// If we have, simply return, otherwise return error.
|
||||||
// ext_name: &str,
|
fn check_privilege(&self) -> Result<(), Error> {
|
||||||
// ext_icon: &str,
|
if matches!(self, Self::System if !win32::utilities::has_privilege()) {
|
||||||
// ext_behavior: &str,
|
Err(Error::NoPrivilege)
|
||||||
// ) -> Result<()> {
|
} else {
|
||||||
// }
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// pub fn into_program(self) -> Result<Program> {
|
// endregion
|
||||||
// Program::new(self)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Program is a complete and immutable program representer
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
// pub struct Program {}
|
pub struct AppPathsKey {
|
||||||
|
key: win32::concept::FileName,
|
||||||
|
}
|
||||||
|
|
||||||
// impl TryFrom<Schema> for Program {
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
// type Error = Error;
|
pub struct ApplicationsKey {
|
||||||
|
key: win32::concept::FileName,
|
||||||
|
}
|
||||||
|
|
||||||
// fn try_from(value: Schema) -> std::result::Result<Self, Self::Error> {
|
// region: File Extension Key
|
||||||
// Self::new(value)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Program {
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
// pub fn new(schema: Schema) -> Result<Self> {}
|
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`.
|
/// The error occurs when casting `OsStr` into `str`.
|
||||||
#[derive(Debug, TeError)]
|
#[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 {
|
pub fn inner(&self) -> &str {
|
||||||
&self.body
|
&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.
|
/// The error occurs when try parsing string into FileExt.
|
||||||
@@ -123,7 +129,7 @@ impl BadProgIdPartError {
|
|||||||
/// `[Vendor or Application].[Component].[Version]` format.
|
/// `[Vendor or Application].[Component].[Version]` format.
|
||||||
///
|
///
|
||||||
/// Additionally, `[Version]` part is optional.
|
/// Additionally, `[Version]` part is optional.
|
||||||
///
|
///
|
||||||
/// However, most of applications do no follow this standard,
|
/// However, most of applications do no follow this standard,
|
||||||
/// this scenario is not convered by this struct in there.
|
/// this scenario is not convered by this struct in there.
|
||||||
/// It should be done by other structs in other places.
|
/// 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
|
/// The struct representing Windows CLSID looks like
|
||||||
/// `{26EE0668-A00A-44D7-9371-BEB064C98683}` (case insensitive).
|
/// `{26EE0668-A00A-44D7-9371-BEB064C98683}` (case insensitive).
|
||||||
///
|
///
|
||||||
/// Please note that the curly brace is the essential part of CLSID.
|
/// Please note that the curly brace is the essential part of CLSID.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct Clsid {
|
pub struct Clsid {
|
||||||
@@ -312,7 +318,7 @@ impl ParseIconRefStrError {
|
|||||||
|
|
||||||
/// The struct representing an Icon Reference String
|
/// The struct representing an Icon Reference String
|
||||||
/// looks like `%SystemRoot%\System32\imageres.dll,-72`.
|
/// looks like `%SystemRoot%\System32\imageres.dll,-72`.
|
||||||
///
|
///
|
||||||
/// As far as I know, the minus token `-` does nothing in this string.
|
/// As far as I know, the minus token `-` does nothing in this string.
|
||||||
/// The following number is just the index.
|
/// The following number is just the index.
|
||||||
pub struct IconRefStr {
|
pub struct IconRefStr {
|
||||||
@@ -405,7 +411,7 @@ impl ParseStrRefStrError {
|
|||||||
|
|
||||||
/// The struct representing an String Reference String
|
/// The struct representing an String Reference String
|
||||||
/// looks like `@%SystemRoot%\System32\shell32.dll,-30596`.
|
/// looks like `@%SystemRoot%\System32\shell32.dll,-30596`.
|
||||||
///
|
///
|
||||||
/// As far as I know, the minus token `-` does nothing in this string.
|
/// As far as I know, the minus token `-` does nothing in this string.
|
||||||
/// The following number is just the index.
|
/// The following number is just the index.
|
||||||
pub struct StrRefStr {
|
pub struct StrRefStr {
|
||||||
@@ -757,3 +763,67 @@ impl FromStr for ExpandString {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// 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.
|
/// Get the subkey with given name.
|
||||||
///
|
///
|
||||||
/// If error occurs when fetching given subkey, it return `Err(...)`,
|
/// 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.
|
/// or `Ok(None)` if there is no suchsub key.
|
||||||
///
|
///
|
||||||
/// Comparing with the function provided by winreg,
|
/// Comparing with the function provided by winreg,
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use wfassoc::concept::*;
|
use wfassoc::win32::concept::*;
|
||||||
|
|
||||||
// region: File Extension
|
// region: File Extension
|
||||||
|
|
||||||
@@ -230,3 +230,25 @@ fn test_expand_string() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// 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