diff --git a/wfassoc-exec/src/runner.rs b/wfassoc-exec/src/runner.rs index e4bf922..63d0389 100644 --- a/wfassoc-exec/src/runner.rs +++ b/wfassoc-exec/src/runner.rs @@ -65,7 +65,7 @@ fn stringified_exts_to_indices( // If star is present alone, return fixed list from zero to the maximum ext index. if has_star { - return Ok((0..program.get_ext_count()).collect()); + return Ok((0..program.exts_len()).collect()); } // Convert each extension name to index using program.find_ext() @@ -138,7 +138,7 @@ fn run_ext_list( ) -> Result<()> { // Fetch info let mut ext_list: HashMap> = HashMap::new(); - for index in 0..program.get_ext_count() { + for index in 0..program.exts_len() { let ext = program.get_ext(index)?; let status = program.query_ext(view, index)?; ext_list.insert(ext.dotted_inner(), status.map(|s| s.get_name().to_string())); diff --git a/wfassoc/src/highlevel.rs b/wfassoc/src/highlevel.rs index befa42a..a38f02e 100644 --- a/wfassoc/src/highlevel.rs +++ b/wfassoc/src/highlevel.rs @@ -1,61 +1,4 @@ -use crate::{ - lowlevel, utilities, - win32::{self, concept}, -}; -use regex::Regex; -use std::collections::HashMap; -use std::ffi::OsStr; -use std::path::Path; -use std::sync::Arc; -use std::sync::LazyLock; -use thiserror::Error as TeError; - -pub use lowlevel::{Scope, View}; - -// region: Error Type - -/// Error occurs when operating with [Schema]. -#[derive(Debug, TeError)] -pub enum SchemaError { - #[error("duplicate key: {0}")] - DuplicateKey(String), -} - -/// Error occurs when trying converting [Schema] into [Program]. -#[derive(Debug, TeError)] -pub enum ParseProgramError { - #[error("{0}")] - BadExtBody(#[from] concept::BadExtBodyError), - #[error("{0}")] - BadProgIdPart(#[from] concept::BadProgIdPartError), - #[error("{0}")] - BadFileName(#[from] concept::BadFileNameError), - #[error("{0}")] - ParseCmdLine(#[from] concept::ParseCmdLineError), - #[error("{0}")] - CastOsStr(#[from] utilities::CastOsStrError), - #[error("given path doesn't has legal file name part")] - NoFileNamePart, - #[error("given path doesn't has legal directory part")] - NoDirNamePart, - #[error("given identifier is not presented in dict")] - NoSuchIdentifier, - #[error("extension name should not be empty")] - EmptyExtension, - #[error("given program identifier is not allowed")] - BadIdentifier, -} - -/// Error occurs when operating with [Program]. -#[derive(Debug, TeError)] -pub enum ProgramError { - #[error("{0}")] - Lowlevel(#[from] lowlevel::Error), - #[error("given index is invalid")] - BadIndex, -} - -// endregion +use crate::lowlevel; // region: Utilities @@ -78,712 +21,13 @@ macro_rules! debug_println { // endregion -// region: Schema +// region: Exposed Stuff -// region: Schema Body +mod schema; +mod program; -/// The sketchpad of complete [Program]. -/// -/// In suggested usage, we will create a [Schema] first, -/// fill some essential and optional properties, -/// then add file extensions which we need. -/// And finally convert it into immutable [Program] for formal using. -#[derive(Debug)] -pub struct Schema { - identifier: String, - path: String, - clsid: String, - - name: Option, - icon: Option, - behavior: Option, - - strs: HashMap, - icons: HashMap, - behaviors: HashMap, - exts: HashMap, -} - -impl Schema { - pub fn new() -> Self { - Self { - identifier: String::new(), - path: String::new(), - clsid: String::new(), - name: None, - icon: None, - behavior: None, - strs: HashMap::new(), - icons: HashMap::new(), - behaviors: HashMap::new(), - exts: HashMap::new(), - } - } - - /// Set the identifier of the schema. - /// - /// The identifier is used to build ProgId. - /// So it should starts with an ASCII letter and followed by zero or more ASCII letters, digits, underline and hyphen. - /// And it should not be empty. - pub fn set_identifier(&mut self, identifier: &str) -> () { - self.identifier = identifier.to_string(); - } - - /// Set the absolute path to the executable file. - pub fn set_path(&mut self, exe_path: &str) -> () { - self.path = exe_path.to_string(); - } - - pub fn set_clsid(&mut self, clsid: &str) -> () { - self.clsid = clsid.to_string(); - } - - pub fn set_name(&mut self, name: Option<&str>) -> () { - self.name = name.map(|n| n.to_string()); - } - - pub fn set_icon(&mut self, icon: Option<&str>) -> () { - self.icon = icon.map(|i| i.to_string()); - } - - pub fn set_behavior(&mut self, behavior: Option<&str>) -> () { - self.behavior = behavior.map(|b| b.to_string()); - } - - pub fn add_str(&mut self, name: &str, value: &str) -> Result<(), SchemaError> { - match self.strs.insert(name.to_string(), value.to_string()) { - Some(_) => Err(SchemaError::DuplicateKey(name.to_string())), - None => Ok(()), - } - } - - pub fn add_icon(&mut self, name: &str, value: &str) -> Result<(), SchemaError> { - match self.icons.insert(name.to_string(), value.to_string()) { - Some(_) => Err(SchemaError::DuplicateKey(name.to_string())), - None => Ok(()), - } - } - - pub fn add_behavior(&mut self, name: &str, value: &str) -> Result<(), SchemaError> { - match self.behaviors.insert(name.to_string(), value.to_string()) { - Some(_) => Err(SchemaError::DuplicateKey(name.to_string())), - None => Ok(()), - } - } - - /// Add a file extension to the schema. - /// - /// The parameter `ext` is the file extension without leading dot `.`. - pub fn add_ext( - &mut self, - ext: &str, - ext_name: &str, - ext_icon: &str, - ext_behavior: &str, - ) -> Result<(), SchemaError> { - match self.exts.insert( - ext.to_string(), - SchemaExt::new(ext_name, ext_icon, ext_behavior), - ) { - Some(_) => Err(SchemaError::DuplicateKey(ext.to_string())), - None => Ok(()), - } - } - - /// Try converting [Schema] into [Program]. - /// - /// This is equivalent to [Program::new]. - pub fn into_program(self) -> Result { - Program::new(self) - } -} - -// endregion - -// region: Schema Internals - -/// Internal used struct as the Schema file extensions hashmap value type. -#[derive(Debug)] -struct SchemaExt { - name: String, - icon: String, - behavior: String, -} - -impl SchemaExt { - fn new(name: &str, icon: &str, behavior: &str) -> Self { - Self { - name: name.to_string(), - icon: icon.to_string(), - behavior: behavior.to_string(), - } - } -} - -// endregion - -// endregion - -// region: Program - -// region: Program Body - -/// Program is a complete and immutable program representer -pub struct Program { - app_paths_key: lowlevel::AppPathsKey, - applications_key: lowlevel::ApplicationsKey, - app_path: String, - app_dir_path: String, - name: Option>, - icon: Option>, - behavior: Option>, - - #[allow(dead_code)] - strs: Vec>, - #[allow(dead_code)] - icons: Vec>, - #[allow(dead_code)] - behaviors: Vec>, - - ext_keys: Vec, - ext_keys_map: HashMap, -} - -impl TryFrom for Program { - type Error = ParseProgramError; - - fn try_from(value: Schema) -> Result { - Self::new(value) - } -} - -impl Program { - /// Extract the file name part from full path to application, - /// which was used in Registry path component. - fn extract_file_name(full_path: &Path) -> Result<&OsStr, ParseProgramError> { - full_path - .file_name() - .ok_or(ParseProgramError::NoFileNamePart) - } - - /// Extract the start in path from full path to application, - /// which basically is the stem of full path. - fn extract_dir_path(full_path: &Path) -> Result<&OsStr, ParseProgramError> { - full_path - .parent() - .map(|p| p.as_os_str()) - .ok_or(ParseProgramError::NoDirNamePart) - } - - fn flat_hashmap( - hashmap: &HashMap, - f: F, - ) -> Result<(Vec, HashMap), ParseProgramError> - where - F: Fn(&V) -> Result, - { - let mut indexmap: HashMap = HashMap::with_capacity(hashmap.len()); - let mut vector: Vec = Vec::with_capacity(hashmap.len()); - for (key, value) in hashmap.into_iter() { - indexmap.insert(key.clone(), vector.len()); - vector.push(f(value)?); - } - Ok((vector, indexmap)) - } - - fn resolve_index( - key: &String, - vector: &Vec>, - index_map: &HashMap, - ) -> Result, ParseProgramError> { - match index_map.get(key) { - Some(index) => Ok(vector - .get(*index) - .expect("unexpected invalid index") - .clone()), - None => Err(ParseProgramError::NoSuchIdentifier), - } - } - - /// Build ProgId from identifier and given file extension. - fn build_progid( - identifier: &str, - ext: &str, - ) -> Result { - // Use Regex to check identifier - static RE: LazyLock = LazyLock::new(|| { - Regex::new(r"^[a-zA-Z][a-zA-Z0-9_-]*$").expect("unexpected bad regex pattern string") - }); - let identifier = match RE.captures(identifier) { - Some(_) => identifier, - None => return Err(ParseProgramError::BadIdentifier), - }; - - // Capitalize first ASCII of ext - let ext = utilities::capitalize_first_ascii(ext); - - // Build strict ProgId - let progid = concept::ProgId::new(identifier, &ext, None)?; - // Then build losse ProgId - let losse_progid: lowlevel::LosseProgId = progid.into(); - // Return built result - Ok(losse_progid) - } - - /// Try converting [Schema] into [Program]. - /// - /// During this process, some checks will be performed to ensure the validity of the data. - /// For example, the reference to icon, name, or behavior must exist in their respective dictionaries. - /// The identifier must be suit for building ProgId. - pub fn new(schema: Schema) -> Result { - // Extract file name part and directory name part respectively. - let schema_path = Path::new(&schema.path); - let app_path = schema.path.clone(); - let app_file_name = Self::extract_file_name(schema_path)?; - let app_file_name = String::from(utilities::osstr_to_str(app_file_name)?); - let app_dir_path = Self::extract_dir_path(schema_path)?; - let app_dir_path = String::from(utilities::osstr_to_str(app_dir_path)?); - // Build app paths key and applications key respectively - let key = concept::FileName::new(&app_file_name)?; - let app_paths_key = lowlevel::AppPathsKey::new(key.clone()); - let applications_key = lowlevel::ApplicationsKey::new(key.clone()); - - // Build string, icon and behavior list, - // and build mapper at the same time. - let (strs, strs_index_map) = Self::flat_hashmap(&schema.strs, |entry| { - let str_res_variant: lowlevel::StrResVariant = entry.as_str().into(); - let program_str = ProgramStr { - inner: str_res_variant, - }; - Ok(Arc::new(program_str)) - })?; - let (icons, icons_index_map) = Self::flat_hashmap(&schema.icons, |entry| { - let icon_res_variant: lowlevel::IconResVariant = entry.as_str().into(); - let program_icon = ProgramIcon { - inner: icon_res_variant, - }; - Ok(Arc::new(program_icon)) - })?; - let (behaviors, behaviors_index_map) = Self::flat_hashmap(&schema.behaviors, |entry| { - // We simply always use "Open" verb. - let cmdline: concept::CmdLine = entry.as_str().parse()?; - let verb = concept::Verb::OPEN(); - let shell_verb = lowlevel::ShellVerb::new(verb, cmdline); - let program_behavior = ProgramBehavior { inner: shell_verb }; - Ok(Arc::new(program_behavior)) - })?; - - // Setup default name, icon and behavior - let name = schema - .name - .map(|name| Self::resolve_index(&name, &strs, &strs_index_map)) - .transpose()?; - let icon = schema - .icon - .map(|icon| Self::resolve_index(&icon, &icons, &icons_index_map)) - .transpose()?; - let behavior = schema - .behavior - .map(|behavior| Self::resolve_index(&behavior, &behaviors, &behaviors_index_map)) - .transpose()?; - - // We build ext keys - let mut ext_keys: Vec = Vec::with_capacity(schema.exts.len()); - for (key, value) in &schema.exts { - // Build ProgId first. - let progid = Self::build_progid(&schema.identifier, key.as_str())?; - // Then build ProgId key. - let progid_key = lowlevel::ProgIdKey::new(progid); - - // Build essential fields for program ProgId key struct. - let name = Self::resolve_index(&value.name, &strs, &strs_index_map)?; - let icon = Self::resolve_index(&value.icon, &icons, &icons_index_map)?; - let behavior = Self::resolve_index(&value.behavior, &behaviors, &behaviors_index_map)?; - - // Build Ext. - let ext = concept::Ext::new(key.as_str())?; - let ext_key = lowlevel::ExtKey::new(ext); - - // Create program ProgId Ext key struct - let progid_ext_key = ProgramProgIdExtKey { - ext_key, - progid_key, - name, - icon, - behavior, - }; - - // Add them into list - ext_keys.push(progid_ext_key); - } - // The build ext keys map - let ext_keys_map = ext_keys - .iter() - .enumerate() - .map(|(i, ext)| (ext.ext_key.inner().inner().to_string(), i)) - .collect(); - - // Everything is okey - Ok(Self { - app_paths_key, - applications_key, - app_path, - app_dir_path, - name, - icon, - behavior, - strs, - icons, - behaviors, - ext_keys, - ext_keys_map, - }) - } -} - -impl Program { - pub fn resolve_name(&self) -> Result { - todo!() - } - - pub fn resolve_icon(&self) -> Result { - todo!() - } - - pub fn get_ext_count(&self) -> usize { - self.ext_keys.len() - } - - pub fn get_ext(&self, index: usize) -> Result<&concept::Ext, ProgramError> { - match self.ext_keys.get(index) { - Some(program_key) => { - let ext_key = &program_key.ext_key; - Ok(ext_key.inner()) - } - None => Err(ProgramError::BadIndex), - } - } - - pub fn find_ext(&self, body: &str) -> Option { - self.ext_keys_map.get(body).copied() - } -} - -impl Program { - /// Register this application. - /// - /// If there is complete or partial registration of this application - /// (partial registration may occurs when registration failed), - /// this function does nothing. - pub fn register(&mut self, scope: Scope) -> Result<(), ProgramError> { - // Create App Paths subkey - debug_println!("Adding App Paths subkey..."); - self.app_paths_key.ensure(scope)?; - // Write App Paths values - self.app_paths_key.set_default(scope, &self.app_path)?; - self.app_paths_key.set_path(scope, &self.app_dir_path)?; - - // Create Applications subkey - debug_println!("Adding Applications subkey..."); - self.applications_key.ensure(scope)?; - // Write Applications values - self.applications_key - .set_shell_verb(scope, self.behavior.as_ref().map(|beh| &beh.inner))?; - self.applications_key - .set_default_icon(scope, self.icon.as_ref().map(|ico| &ico.inner))?; - self.applications_key - .set_friendly_app_name(scope, self.name.as_ref().map(|name| &name.inner))?; - let exts: Vec<&concept::Ext> = self - .ext_keys - .iter() - .map(|key| key.ext_key.inner()) - .collect(); - self.applications_key - .set_supported_types(scope, Some(&exts))?; - self.applications_key.set_no_open_with(scope, true)?; - - // Create ProgId subkeys one by one - debug_println!("Adding ProgId subkey..."); - for program_key in &mut self.ext_keys { - let progid_key = &mut program_key.progid_key; - debug_println!( - "Adding ProgId \"{0}\" subkey...", - progid_key.inner().to_string() - ); - - // Create ProgId subkey - progid_key.ensure(scope)?; - // Write ProgId values - let name = Some(&program_key.name.inner); - progid_key.set_default(scope, name)?; - progid_key.set_shell_verb(scope, Some(&program_key.behavior.inner))?; - progid_key.set_friendly_type_name(scope, name)?; - progid_key.set_default_icon(scope, Some(&program_key.icon.inner))?; - - // Add this progid to file extension "open with" list. - let ext_key = &mut program_key.ext_key; - ext_key.add_into_open_with_progids(scope, progid_key.inner())?; - } - - // Everything is okey. - // Notify changes and return - win32::utilities::notify_assoc_changed(); - Ok(()) - } - - /// Unregister this application. - /// - /// No matter whether there is registration of this application, - /// this function always make sure that there is no registration after running this function. - pub fn unregister(&mut self, scope: Scope) -> Result<(), ProgramError> { - // Delete App Paths subkey - debug_println!("Deleting App Paths subkey..."); - self.app_paths_key.delete(scope)?; - - // Delete Applications subkey - debug_println!("Deleting Applications subkey..."); - self.applications_key.delete(scope)?; - - // Delete ProgId subkeys one by one. - debug_println!("Adding ProgId subkey..."); - for program_key in &mut self.ext_keys { - let progid_key = &mut program_key.progid_key; - debug_println!( - "Deleting ProgId \"{0}\" subkey...", - progid_key.inner().to_string() - ); - - // YYC MARK: - // According to Microsoft document, when uninstalling application, - // there is no need to reset the default open way of file extension. - // So we simply remove it from "open with" list. - - // Remove this ProgId from file extension "open with" list. - let ext_key = &mut program_key.ext_key; - ext_key.remove_from_open_with_progids(scope, progid_key.inner())?; - - // Delete ProgId subkey - progid_key.delete(scope)?; - } - - // Everything is okey. - // Notify changes and return - win32::utilities::notify_assoc_changed(); - Ok(()) - } - - /// Check whether this application has been registered in given view. - /// - /// Please note that this is a rough check and do not validate any data. - /// - /// The return value only ensures the pre-requirement of `register` and `unregister`. - pub fn is_registered(&self, scope: Scope) -> Result { - // Check App Paths subkey. - debug_println!("Checking App Paths subkey..."); - if !self.app_paths_key.is_exist(scope)? { - return Ok(false); - } - - // Check Application subkey. - debug_println!("Checking Applications subkey..."); - if !self.applications_key.is_exist(scope.into())? { - return Ok(false); - } - - // Check ProgId subkey. - debug_println!("Checking ProgId subkey..."); - for program_key in &self.ext_keys { - let progid_key = &program_key.progid_key; - debug_println!( - "Checking ProgId \"{0}\" subkey...", - progid_key.inner().to_string() - ); - - if !progid_key.is_exist(scope.into())? { - return Ok(false); - } - } - - // Every subkeys are roughly existing. - Ok(true) - } - - pub fn link_ext(&mut self, scope: Scope, index: usize) -> Result<(), ProgramError> { - match self.ext_keys.get_mut(index) { - Some(program_key) => { - let ext_key = &mut program_key.ext_key; - let progid_key = &program_key.progid_key; - debug_println!( - "Linking ProgId \"{0}\" to extension \"{1}\" subkey...", - progid_key.inner().to_string(), - ext_key.inner().to_string() - ); - - // Before setting it, we must make sure this extension is existing - ext_key.ensure(scope)?; - ext_key.set_default(scope, Some(progid_key.inner()))?; - } - None => return Err(ProgramError::BadIndex), - }; - - // Everything is okey. - // Notify changes and return - win32::utilities::notify_assoc_changed(); - Ok(()) - } - - pub fn unlink_ext(&mut self, scope: Scope, index: usize) -> Result<(), ProgramError> { - match self.ext_keys.get_mut(index) { - Some(program_key) => { - let ext_key = &mut program_key.ext_key; - debug_println!( - "Unlinking for extension \"{0}\" subkey...", - ext_key.inner().to_string() - ); - - // Before setting it, we must make sure this extension is existing - ext_key.ensure(scope)?; - ext_key.set_default(scope, None)?; - } - None => return Err(ProgramError::BadIndex), - } - - // Everything is okey. - // Notify changes and return - win32::utilities::notify_assoc_changed(); - Ok(()) - } - - pub fn query_ext( - &self, - view: View, - index: usize, - ) -> Result, ProgramError> { - match self.ext_keys.get(index) { - Some(program_key) => { - let ext_key = &program_key.ext_key; - debug_println!( - "Querying for extension \"{0}\"subkey...", - ext_key.inner().to_string() - ); - - // If there is no such extension key, return None about this extension. - if !ext_key.is_exist(view)? { - return Ok(None); - } - // Let we fetch its associated default ProgId. - // If there is no such key, return None instead. - let progid = match ext_key.get_default(view)? { - Some(progid) => progid, - None => return Ok(None), - }; - // Now we build ProgId key from gotten association - let progid_key = lowlevel::ProgIdKey::new(progid); - // If this associated ProgId key is not presented, - // we return None instead. - if !progid_key.is_exist(view)? { - return Ok(None); - } - // Now try fetch its diaplay name in modern way first. - // If there is no modern way, use legacy way instead. - // If there is still no display name, use ProgId self instead as display name. - let mut name: Option = None; - if let None = name { - name = progid_key - .get_friendly_type_name(view)? - .map(|name| name.extract().ok()) - .flatten(); - } - if let None = name { - name = progid_key - .get_default(view)? - .map(|name| name.extract().ok()) - .flatten(); - } - let name = name.unwrap_or(progid_key.inner().to_string()); - // Now try to fetch icon. - let icon = progid_key - .get_default_icon(view)? - .map(|ico| ico.extract(concept::IconSizeKind::Small).ok()) - .flatten(); - - // Okey, return it. - Ok(Some(ProgramExtStatus::new(name, icon))) - } - None => Err(ProgramError::BadIndex), - } - } -} - -// endregion - -// region: Program Exposed Structs - -/// Exposed struct representing the default associated program of specific file extension. -/// -/// The data including the diaplay name and icon. -pub struct ProgramExtStatus { - name: String, - icon: Option, -} - -impl ProgramExtStatus { - fn new(name: String, icon: Option) -> Self { - Self { name, icon } - } - - /// Get the display name of this program. - /// - /// The program provided display name will be used firstly. - /// If this program has no display name, the stringified ProgId will be used instead. - pub fn get_name(&self) -> &str { - self.name.as_str() - } - - /// Get the icon of this program. - /// - /// Due to the icon is optional, if there is no icon, return None. - pub fn get_icon(&self) -> Option<&concept::IconRc> { - self.icon.as_ref() - } -} - -// endregion - -// region: Program Internals - -/// Internal used enum presenting a Program string resource. -#[derive(Debug)] -struct ProgramStr { - inner: lowlevel::StrResVariant, -} - -/// Internal used enum presenting a Program icon resource. -#[derive(Debug)] -struct ProgramIcon { - inner: lowlevel::IconResVariant, -} - -/// Internal used enum presenting a Program behavior (command line setups). -#[derive(Debug)] -struct ProgramBehavior { - inner: lowlevel::ShellVerb, -} - -/// Internal used struct presenting a Program ProgId and associated file extension. -/// -/// Another reason combine ProgId and Ext is that we can't operate ProgId in mutable mode, -/// due to the internal immutable of Rc in Rust. -#[derive(Debug)] -struct ProgramProgIdExtKey { - ext_key: lowlevel::ExtKey, - - progid_key: lowlevel::ProgIdKey, - name: Arc, - icon: Arc, - behavior: Arc, -} - -// endregion +pub use schema::{Schema, SchemaError}; +pub use program::{Program, ParseProgramError, ProgramError, ProgramExtStatus}; +pub use lowlevel::{Scope, View}; // endregion diff --git a/wfassoc/src/highlevel/program.rs b/wfassoc/src/highlevel/program.rs new file mode 100644 index 0000000..d66bb2c --- /dev/null +++ b/wfassoc/src/highlevel/program.rs @@ -0,0 +1,613 @@ +use super::Schema; +use crate::{ + lowlevel::{self, Scope, View}, + utilities, + win32::{self, concept}, +}; +use regex::Regex; +use std::collections::HashMap; +use std::ffi::OsStr; +use std::path::Path; +use std::sync::Arc; +use std::sync::LazyLock; +use thiserror::Error as TeError; + +// region: Error Type + +/// Error occurs when trying converting [Schema] into [Program]. +#[derive(Debug, TeError)] +pub enum ParseProgramError { + #[error("{0}")] + BadExtBody(#[from] concept::BadExtBodyError), + #[error("{0}")] + BadProgIdPart(#[from] concept::BadProgIdPartError), + #[error("{0}")] + BadFileName(#[from] concept::BadFileNameError), + #[error("{0}")] + ParseCmdLine(#[from] concept::ParseCmdLineError), + #[error("{0}")] + CastOsStr(#[from] utilities::CastOsStrError), + #[error("given path doesn't has legal file name part")] + NoFileNamePart, + #[error("given path doesn't has legal directory part")] + NoDirNamePart, + #[error("given identifier is not presented in dict")] + NoSuchIdentifier, + #[error("extension name should not be empty")] + EmptyExtension, + #[error("given program identifier is not allowed")] + BadIdentifier, +} + +/// Error occurs when operating with [Program]. +#[derive(Debug, TeError)] +pub enum ProgramError { + #[error("{0}")] + Lowlevel(#[from] lowlevel::Error), + #[error("given index is invalid")] + BadIndex, +} + +// endregion + +// region: Program + +/// Program is a complete and immutable program representer +pub struct Program { + app_paths_key: lowlevel::AppPathsKey, + applications_key: lowlevel::ApplicationsKey, + app_path: String, + app_dir_path: String, + name: Option>, + icon: Option>, + behavior: Option>, + + #[allow(dead_code)] + strs: Vec>, + #[allow(dead_code)] + icons: Vec>, + #[allow(dead_code)] + behaviors: Vec>, + + ext_keys: Vec, + ext_keys_map: HashMap, +} + +impl TryFrom for Program { + type Error = ParseProgramError; + + fn try_from(value: Schema) -> Result { + Self::new(value) + } +} + +impl Program { + /// Extract the file name part from full path to application, + /// which was used in Registry path component. + fn extract_file_name(full_path: &Path) -> Result<&OsStr, ParseProgramError> { + full_path + .file_name() + .ok_or(ParseProgramError::NoFileNamePart) + } + + /// Extract the start in path from full path to application, + /// which basically is the stem of full path. + fn extract_dir_path(full_path: &Path) -> Result<&OsStr, ParseProgramError> { + full_path + .parent() + .map(|p| p.as_os_str()) + .ok_or(ParseProgramError::NoDirNamePart) + } + + fn flat_hashmap( + hashmap: &HashMap, + f: F, + ) -> Result<(Vec, HashMap), ParseProgramError> + where + F: Fn(&V) -> Result, + { + let mut indexmap: HashMap = HashMap::with_capacity(hashmap.len()); + let mut vector: Vec = Vec::with_capacity(hashmap.len()); + for (key, value) in hashmap.into_iter() { + indexmap.insert(key.clone(), vector.len()); + vector.push(f(value)?); + } + Ok((vector, indexmap)) + } + + fn resolve_index( + key: &str, + vector: &Vec>, + index_map: &HashMap, + ) -> Result, ParseProgramError> { + match index_map.get(key) { + Some(index) => Ok(vector + .get(*index) + .expect("unexpected invalid index") + .clone()), + None => Err(ParseProgramError::NoSuchIdentifier), + } + } + + /// Build ProgId from identifier and given file extension. + fn build_progid( + identifier: &str, + ext: &str, + ) -> Result { + // Use Regex to check identifier + static RE: LazyLock = LazyLock::new(|| { + Regex::new(r"^[a-zA-Z][a-zA-Z0-9_-]*$").expect("unexpected bad regex pattern string") + }); + let identifier = match RE.captures(identifier) { + Some(_) => identifier, + None => return Err(ParseProgramError::BadIdentifier), + }; + + // Capitalize first ASCII of ext + let ext = utilities::capitalize_first_ascii(ext); + + // Build strict ProgId + let progid = concept::ProgId::new(identifier, &ext, None)?; + // Then build losse ProgId + let losse_progid: lowlevel::LosseProgId = progid.into(); + // Return built result + Ok(losse_progid) + } + + /// Try converting [Schema] into [Program]. + /// + /// During this process, some checks will be performed to ensure the validity of the data. + /// For example, the reference to icon, name, or behavior must exist in their respective dictionaries. + /// The identifier must be suit for building ProgId. + pub fn new(schema: Schema) -> Result { + // Extract file name part and directory name part respectively. + let schema_path = Path::new(schema.get_path()); + let app_path = schema.get_path().to_string(); + let app_file_name = Self::extract_file_name(schema_path)?; + let app_file_name = String::from(utilities::osstr_to_str(app_file_name)?); + let app_dir_path = Self::extract_dir_path(schema_path)?; + let app_dir_path = String::from(utilities::osstr_to_str(app_dir_path)?); + // Build app paths key and applications key respectively + let key = concept::FileName::new(&app_file_name)?; + let app_paths_key = lowlevel::AppPathsKey::new(key.clone()); + let applications_key = lowlevel::ApplicationsKey::new(key.clone()); + + // Build string, icon and behavior list, + // and build mapper at the same time. + let (strs, strs_index_map) = Self::flat_hashmap(schema.get_strs(), |entry| { + let str_res_variant: lowlevel::StrResVariant = entry.as_str().into(); + let program_str = ProgramStr { + inner: str_res_variant, + }; + Ok(Arc::new(program_str)) + })?; + let (icons, icons_index_map) = Self::flat_hashmap(schema.get_icons(), |entry| { + let icon_res_variant: lowlevel::IconResVariant = entry.as_str().into(); + let program_icon = ProgramIcon { + inner: icon_res_variant, + }; + Ok(Arc::new(program_icon)) + })?; + let (behaviors, behaviors_index_map) = + Self::flat_hashmap(schema.get_behaviors(), |entry| { + // We simply always use "Open" verb. + let cmdline: concept::CmdLine = entry.as_str().parse()?; + let verb = concept::Verb::OPEN(); + let shell_verb = lowlevel::ShellVerb::new(verb, cmdline); + let program_behavior = ProgramBehavior { inner: shell_verb }; + Ok(Arc::new(program_behavior)) + })?; + + // Setup default name, icon and behavior + let name = schema + .get_name() + .map(|name| Self::resolve_index(name, &strs, &strs_index_map)) + .transpose()?; + let icon = schema + .get_icon() + .map(|icon| Self::resolve_index(icon, &icons, &icons_index_map)) + .transpose()?; + let behavior = schema + .get_behavior() + .map(|behavior| Self::resolve_index(behavior, &behaviors, &behaviors_index_map)) + .transpose()?; + + // We build ext keys + let mut ext_keys: Vec = Vec::with_capacity(schema.get_exts().len()); + for (key, value) in schema.get_exts() { + // Build ProgId first. + let progid = Self::build_progid(schema.get_identifier(), key.as_str())?; + // Then build ProgId key. + let progid_key = lowlevel::ProgIdKey::new(progid); + + // Build essential fields for program ProgId key struct. + let name = Self::resolve_index(value.get_name(), &strs, &strs_index_map)?; + let icon = Self::resolve_index(value.get_icon(), &icons, &icons_index_map)?; + let behavior = + Self::resolve_index(value.get_behavior(), &behaviors, &behaviors_index_map)?; + + // Build Ext. + let ext = concept::Ext::new(key.as_str())?; + let ext_key = lowlevel::ExtKey::new(ext); + + // Create program ProgId Ext key struct + let progid_ext_key = ProgramProgIdExtKey { + ext_key, + progid_key, + name, + icon, + behavior, + }; + + // Add them into list + ext_keys.push(progid_ext_key); + } + // The build ext keys map + let ext_keys_map = ext_keys + .iter() + .enumerate() + .map(|(i, ext)| (ext.ext_key.inner().inner().to_string(), i)) + .collect(); + + // Everything is okey + Ok(Self { + app_paths_key, + applications_key, + app_path, + app_dir_path, + name, + icon, + behavior, + strs, + icons, + behaviors, + ext_keys, + ext_keys_map, + }) + } +} + +impl Program { + pub fn resolve_name(&self) -> Result { + todo!() + } + + pub fn resolve_icon(&self) -> Result { + todo!() + } + + pub fn exts_len(&self) -> usize { + self.ext_keys.len() + } + + pub fn get_ext(&self, index: usize) -> Result<&concept::Ext, ProgramError> { + match self.ext_keys.get(index) { + Some(program_key) => { + let ext_key = &program_key.ext_key; + Ok(ext_key.inner()) + } + None => Err(ProgramError::BadIndex), + } + } + + pub fn find_ext(&self, body: &str) -> Option { + self.ext_keys_map.get(body).copied() + } +} + +impl Program { + /// Register this application. + /// + /// If there is complete or partial registration of this application + /// (partial registration may occurs when registration failed), + /// this function does nothing. + pub fn register(&mut self, scope: Scope) -> Result<(), ProgramError> { + // Create App Paths subkey + debug_println!("Adding App Paths subkey..."); + self.app_paths_key.ensure(scope)?; + // Write App Paths values + self.app_paths_key.set_default(scope, &self.app_path)?; + self.app_paths_key.set_path(scope, &self.app_dir_path)?; + + // Create Applications subkey + debug_println!("Adding Applications subkey..."); + self.applications_key.ensure(scope)?; + // Write Applications values + self.applications_key + .set_shell_verb(scope, self.behavior.as_ref().map(|beh| &beh.inner))?; + self.applications_key + .set_default_icon(scope, self.icon.as_ref().map(|ico| &ico.inner))?; + self.applications_key + .set_friendly_app_name(scope, self.name.as_ref().map(|name| &name.inner))?; + let exts: Vec<&concept::Ext> = self + .ext_keys + .iter() + .map(|key| key.ext_key.inner()) + .collect(); + self.applications_key + .set_supported_types(scope, Some(&exts))?; + self.applications_key.set_no_open_with(scope, true)?; + + // Create ProgId subkeys one by one + debug_println!("Adding ProgId subkey..."); + for program_key in &mut self.ext_keys { + let progid_key = &mut program_key.progid_key; + debug_println!( + "Adding ProgId \"{0}\" subkey...", + progid_key.inner().to_string() + ); + + // Create ProgId subkey + progid_key.ensure(scope)?; + // Write ProgId values + let name = Some(&program_key.name.inner); + progid_key.set_default(scope, name)?; + progid_key.set_shell_verb(scope, Some(&program_key.behavior.inner))?; + progid_key.set_friendly_type_name(scope, name)?; + progid_key.set_default_icon(scope, Some(&program_key.icon.inner))?; + + // Add this progid to file extension "open with" list. + let ext_key = &mut program_key.ext_key; + ext_key.add_into_open_with_progids(scope, progid_key.inner())?; + } + + // Everything is okey. + // Notify changes and return + win32::utilities::notify_assoc_changed(); + Ok(()) + } + + /// Unregister this application. + /// + /// No matter whether there is registration of this application, + /// this function always make sure that there is no registration after running this function. + pub fn unregister(&mut self, scope: Scope) -> Result<(), ProgramError> { + // Delete App Paths subkey + debug_println!("Deleting App Paths subkey..."); + self.app_paths_key.delete(scope)?; + + // Delete Applications subkey + debug_println!("Deleting Applications subkey..."); + self.applications_key.delete(scope)?; + + // Delete ProgId subkeys one by one. + debug_println!("Adding ProgId subkey..."); + for program_key in &mut self.ext_keys { + let progid_key = &mut program_key.progid_key; + debug_println!( + "Deleting ProgId \"{0}\" subkey...", + progid_key.inner().to_string() + ); + + // YYC MARK: + // According to Microsoft document, when uninstalling application, + // there is no need to reset the default open way of file extension. + // So we simply remove it from "open with" list. + + // Remove this ProgId from file extension "open with" list. + let ext_key = &mut program_key.ext_key; + ext_key.remove_from_open_with_progids(scope, progid_key.inner())?; + + // Delete ProgId subkey + progid_key.delete(scope)?; + } + + // Everything is okey. + // Notify changes and return + win32::utilities::notify_assoc_changed(); + Ok(()) + } + + /// Check whether this application has been registered in given view. + /// + /// Please note that this is a rough check and do not validate any data. + /// + /// The return value only ensures the pre-requirement of `register` and `unregister`. + pub fn is_registered(&self, scope: Scope) -> Result { + // Check App Paths subkey. + debug_println!("Checking App Paths subkey..."); + if !self.app_paths_key.is_exist(scope)? { + return Ok(false); + } + + // Check Application subkey. + debug_println!("Checking Applications subkey..."); + if !self.applications_key.is_exist(scope.into())? { + return Ok(false); + } + + // Check ProgId subkey. + debug_println!("Checking ProgId subkey..."); + for program_key in &self.ext_keys { + let progid_key = &program_key.progid_key; + debug_println!( + "Checking ProgId \"{0}\" subkey...", + progid_key.inner().to_string() + ); + + if !progid_key.is_exist(scope.into())? { + return Ok(false); + } + } + + // Every subkeys are roughly existing. + Ok(true) + } + + pub fn link_ext(&mut self, scope: Scope, index: usize) -> Result<(), ProgramError> { + match self.ext_keys.get_mut(index) { + Some(program_key) => { + let ext_key = &mut program_key.ext_key; + let progid_key = &program_key.progid_key; + debug_println!( + "Linking ProgId \"{0}\" to extension \"{1}\" subkey...", + progid_key.inner().to_string(), + ext_key.inner().to_string() + ); + + // Before setting it, we must make sure this extension is existing + ext_key.ensure(scope)?; + ext_key.set_default(scope, Some(progid_key.inner()))?; + } + None => return Err(ProgramError::BadIndex), + }; + + // Everything is okey. + // Notify changes and return + win32::utilities::notify_assoc_changed(); + Ok(()) + } + + pub fn unlink_ext(&mut self, scope: Scope, index: usize) -> Result<(), ProgramError> { + match self.ext_keys.get_mut(index) { + Some(program_key) => { + let ext_key = &mut program_key.ext_key; + debug_println!( + "Unlinking for extension \"{0}\" subkey...", + ext_key.inner().to_string() + ); + + // Before setting it, we must make sure this extension is existing + ext_key.ensure(scope)?; + ext_key.set_default(scope, None)?; + } + None => return Err(ProgramError::BadIndex), + } + + // Everything is okey. + // Notify changes and return + win32::utilities::notify_assoc_changed(); + Ok(()) + } + + pub fn query_ext( + &self, + view: View, + index: usize, + ) -> Result, ProgramError> { + match self.ext_keys.get(index) { + Some(program_key) => { + let ext_key = &program_key.ext_key; + debug_println!( + "Querying for extension \"{0}\"subkey...", + ext_key.inner().to_string() + ); + + // If there is no such extension key, return None about this extension. + if !ext_key.is_exist(view)? { + return Ok(None); + } + // Let we fetch its associated default ProgId. + // If there is no such key, return None instead. + let progid = match ext_key.get_default(view)? { + Some(progid) => progid, + None => return Ok(None), + }; + // Now we build ProgId key from gotten association + let progid_key = lowlevel::ProgIdKey::new(progid); + // If this associated ProgId key is not presented, + // we return None instead. + if !progid_key.is_exist(view)? { + return Ok(None); + } + // Now try fetch its diaplay name in modern way first. + // If there is no modern way, use legacy way instead. + // If there is still no display name, use ProgId self instead as display name. + let mut name: Option = None; + if let None = name { + name = progid_key + .get_friendly_type_name(view)? + .map(|name| name.extract().ok()) + .flatten(); + } + if let None = name { + name = progid_key + .get_default(view)? + .map(|name| name.extract().ok()) + .flatten(); + } + let name = name.unwrap_or(progid_key.inner().to_string()); + // Now try to fetch icon. + let icon = progid_key + .get_default_icon(view)? + .map(|ico| ico.extract(concept::IconSizeKind::Small).ok()) + .flatten(); + + // Okey, return it. + Ok(Some(ProgramExtStatus::new(name, icon))) + } + None => Err(ProgramError::BadIndex), + } + } +} + +// endregion + +// region: Internal Stuff + +/// Internal used enum presenting a Program string resource. +#[derive(Debug)] +struct ProgramStr { + inner: lowlevel::StrResVariant, +} + +/// Internal used enum presenting a Program icon resource. +#[derive(Debug)] +struct ProgramIcon { + inner: lowlevel::IconResVariant, +} + +/// Internal used enum presenting a Program behavior (command line setups). +#[derive(Debug)] +struct ProgramBehavior { + inner: lowlevel::ShellVerb, +} + +/// Internal used struct presenting a Program ProgId and associated file extension. +/// +/// Another reason combine ProgId and Ext is that we can't operate ProgId in mutable mode, +/// due to the internal immutable of Rc in Rust. +#[derive(Debug)] +struct ProgramProgIdExtKey { + ext_key: lowlevel::ExtKey, + + progid_key: lowlevel::ProgIdKey, + name: Arc, + icon: Arc, + behavior: Arc, +} + +// endregion + +// region: Exposed Stuff + +/// Exposed struct representing the default associated program of specific file extension. +/// +/// The data including the diaplay name and icon. +pub struct ProgramExtStatus { + name: String, + icon: Option, +} + +impl ProgramExtStatus { + fn new(name: String, icon: Option) -> Self { + Self { name, icon } + } + + /// Get the display name of this program. + /// + /// The program provided display name will be used firstly. + /// If this program has no display name, the stringified ProgId will be used instead. + pub fn get_name(&self) -> &str { + self.name.as_str() + } + + /// Get the icon of this program. + /// + /// Due to the icon is optional, if there is no icon, return None. + pub fn get_icon(&self) -> Option<&concept::IconRc> { + self.icon.as_ref() + } +} + +// endregion diff --git a/wfassoc/src/highlevel/schema.rs b/wfassoc/src/highlevel/schema.rs new file mode 100644 index 0000000..1b8183c --- /dev/null +++ b/wfassoc/src/highlevel/schema.rs @@ -0,0 +1,215 @@ +use std::collections::HashMap; +use thiserror::Error as TeError; +use super::{Program, ParseProgramError}; + +// region: Error Type + +/// Error occurs when operating with [Schema]. +#[derive(Debug, TeError)] +pub enum SchemaError { + #[error("duplicate key: {0}")] + DuplicateKey(String), +} + +// endregion + +// region: Schema + +/// The sketchpad of complete [Program]. +/// +/// In suggested usage, we will create a [Schema] first, +/// fill some essential and optional properties, +/// then add file extensions which we need. +/// And finally convert it into immutable [Program] for formal using. +#[derive(Debug)] +pub struct Schema { + identifier: String, + path: String, + clsid: String, + + name: Option, + icon: Option, + behavior: Option, + + strs: HashMap, + icons: HashMap, + behaviors: HashMap, + exts: HashMap, +} + +impl Schema { + pub fn new() -> Self { + Self { + identifier: String::new(), + path: String::new(), + clsid: String::new(), + name: None, + icon: None, + behavior: None, + strs: HashMap::new(), + icons: HashMap::new(), + behaviors: HashMap::new(), + exts: HashMap::new(), + } + } + + /// Try converting [Schema] into [Program]. + /// + /// This is equivalent to [Program::new]. + pub fn into_program(self) -> Result { + Program::new(self) + } +} + +impl Schema { + /// Set the identifier of the schema. + /// + /// The identifier is used to build ProgId. + /// So it should starts with an ASCII letter and followed by zero or more ASCII letters, digits, underline and hyphen. + /// And it should not be empty. + pub fn set_identifier(&mut self, identifier: &str) -> () { + self.identifier = identifier.to_string(); + } + + /// Set the absolute path to the executable file. + pub fn set_path(&mut self, exe_path: &str) -> () { + self.path = exe_path.to_string(); + } + + pub fn set_clsid(&mut self, clsid: &str) -> () { + self.clsid = clsid.to_string(); + } + + pub fn set_name(&mut self, name: Option<&str>) -> () { + self.name = name.map(|n| n.to_string()); + } + + pub fn set_icon(&mut self, icon: Option<&str>) -> () { + self.icon = icon.map(|i| i.to_string()); + } + + pub fn set_behavior(&mut self, behavior: Option<&str>) -> () { + self.behavior = behavior.map(|b| b.to_string()); + } + + pub fn add_str(&mut self, name: &str, value: &str) -> Result<(), SchemaError> { + match self.strs.insert(name.to_string(), value.to_string()) { + Some(_) => Err(SchemaError::DuplicateKey(name.to_string())), + None => Ok(()), + } + } + + pub fn add_icon(&mut self, name: &str, value: &str) -> Result<(), SchemaError> { + match self.icons.insert(name.to_string(), value.to_string()) { + Some(_) => Err(SchemaError::DuplicateKey(name.to_string())), + None => Ok(()), + } + } + + pub fn add_behavior(&mut self, name: &str, value: &str) -> Result<(), SchemaError> { + match self.behaviors.insert(name.to_string(), value.to_string()) { + Some(_) => Err(SchemaError::DuplicateKey(name.to_string())), + None => Ok(()), + } + } + + /// Add a file extension to the schema. + /// + /// The parameter `ext` is the file extension without leading dot `.`. + pub fn add_ext( + &mut self, + ext: &str, + ext_name: &str, + ext_icon: &str, + ext_behavior: &str, + ) -> Result<(), SchemaError> { + match self.exts.insert( + ext.to_string(), + SchemaExt::new(ext_name, ext_icon, ext_behavior), + ) { + Some(_) => Err(SchemaError::DuplicateKey(ext.to_string())), + None => Ok(()), + } + } + +} + +impl Schema { + pub(super) fn get_identifier(&self) -> &str { + &self.identifier + } + + pub(super) fn get_path(&self) -> &str { + &self.path + } + + pub(super) fn get_clsid(&self) -> &str { + &self.clsid + } + + pub(super) fn get_name(&self) -> Option<&str> { + self.name.as_ref().map(|v| v.as_str()) + } + + pub(super) fn get_icon(&self) -> Option<&str> { + self.icon.as_ref().map(|v| v.as_str()) + } + + pub(super) fn get_behavior(&self) -> Option<&str> { + self.icon.as_ref().map(|v| v.as_str()) + } + + pub(super) fn get_strs(&self) -> &HashMap { + &self.strs + } + + pub(super) fn get_icons(&self) -> &HashMap { + &self.icons + } + + pub(super) fn get_behaviors(&self) -> &HashMap { + &self.behaviors + } + + pub(super) fn get_exts(&self) -> &HashMap { + &self.exts + } +} + +// endregion + +// region: Internal Stuff + +/// Internal used struct as the Schema file extensions hashmap value type. +#[derive(Debug)] +pub(crate) struct SchemaExt { + name: String, + icon: String, + behavior: String, +} + +impl SchemaExt { + fn new(name: &str, icon: &str, behavior: &str) -> Self { + Self { + name: name.to_string(), + icon: icon.to_string(), + behavior: behavior.to_string(), + } + } +} + +impl SchemaExt { + pub(super) fn get_name(&self) -> &str { + &self.name + } + + pub(super) fn get_icon(&self) -> &str { + &self.icon + } + + pub(super) fn get_behavior(&self) -> &str { + &self.behavior + } +} + +// endregion diff --git a/wfassoc/src/lowlevel/app_path_key.rs b/wfassoc/src/lowlevel/app_path_key.rs index b47b3f7..d705b4c 100644 --- a/wfassoc/src/lowlevel/app_path_key.rs +++ b/wfassoc/src/lowlevel/app_path_key.rs @@ -5,8 +5,6 @@ use crate::win32::{concept, regext}; use winreg::RegKey; use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; -// region: App Paths Key - #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct AppPathsKey { key_name: concept::FileName, @@ -147,5 +145,3 @@ impl AppPathsKey { Ok(()) } } - -// endregion diff --git a/wfassoc/src/lowlevel/applications_key.rs b/wfassoc/src/lowlevel/applications_key.rs index 59594b6..fec9c41 100644 --- a/wfassoc/src/lowlevel/applications_key.rs +++ b/wfassoc/src/lowlevel/applications_key.rs @@ -6,8 +6,6 @@ use crate::win32::{concept, regext}; use winreg::RegKey; use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; -// region: Applications Key - #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ApplicationsKey { key_name: concept::FileName, @@ -327,5 +325,3 @@ impl ApplicationsKey { Ok(()) } } - -// endregion diff --git a/wfassoc/src/lowlevel/ext_key.rs b/wfassoc/src/lowlevel/ext_key.rs index 31e1c5c..ec1ffdc 100644 --- a/wfassoc/src/lowlevel/ext_key.rs +++ b/wfassoc/src/lowlevel/ext_key.rs @@ -6,8 +6,6 @@ use crate::win32::{concept, regext}; use winreg::RegKey; use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; -// region: File Extension Key - #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ExtKey { ext: concept::Ext, @@ -90,10 +88,12 @@ impl ExtKey { // YYC MARK: // Reference: https://learn.microsoft.com/en-us/windows/win32/shell/fa-file-types#setting-optional-subkeys-and-file-type-extension-attributes - // + + // TODO: // We do not support "Content Type" and "PerceivedType" // because current interface are enough to use, // and these types has not been made as concept struct in Rust. + // We may expand these in future. fn open_view_for_getter(&self, view: View) -> Result { self.open_view_for_read(view)? @@ -209,5 +209,3 @@ impl ExtKey { Ok(()) } } - -// endregion diff --git a/wfassoc/src/lowlevel/progid_key.rs b/wfassoc/src/lowlevel/progid_key.rs index 10935b6..15768a3 100644 --- a/wfassoc/src/lowlevel/progid_key.rs +++ b/wfassoc/src/lowlevel/progid_key.rs @@ -6,8 +6,6 @@ use crate::win32::regext; use winreg::RegKey; use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; -// region: ProgId Key - #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ProgIdKey { progid: LosseProgId, @@ -90,9 +88,11 @@ impl ProgIdKey { // YYC MARK: // Reference: https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids#programmatic-identifier-elements-used-by-file-associations - // - // Currently we only support (Default), FriendlyTypeName and DefaultIcon + + // TODO: + // Currently we only support (Default), FriendlyTypeName and DefaultIcon // to just cover the basic usage. + // We may expand these in future. fn open_view_for_getter(&self, view: View) -> Result { self.open_view_for_read(view)? @@ -291,5 +291,3 @@ impl ProgIdKey { Ok(()) } } - -// endregion