From f0bd2c0b733aef31bf528dc46a25b10a9d847b45 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Fri, 29 May 2026 08:58:15 +0800 Subject: [PATCH] feat: add arbitrarily_delete_subkey_all for regext --- wfassoc/src/lowlevel/app_path_key.rs | 17 ++++++------ wfassoc/src/lowlevel/applications_key.rs | 23 +++++++++++----- wfassoc/src/lowlevel/ext_key.rs | 19 +++++++++---- wfassoc/src/lowlevel/progid_key.rs | 23 +++++++++++----- wfassoc/src/win32/regext.rs | 35 +++++++++++++++++++++--- wfassoc/tests/lowlevel.rs | 1 + 6 files changed, 87 insertions(+), 31 deletions(-) diff --git a/wfassoc/src/lowlevel/app_path_key.rs b/wfassoc/src/lowlevel/app_path_key.rs index d705b4c..76556d1 100644 --- a/wfassoc/src/lowlevel/app_path_key.rs +++ b/wfassoc/src/lowlevel/app_path_key.rs @@ -62,7 +62,7 @@ impl AppPathsKey { Ok(key.is_some()) } - /// Ensure this application key is presented in App Paths. + /// Ensure this application key is presented in App Paths key. /// /// Return true if we newly create this key, /// otherwise false indicating there already is an existing key. @@ -79,15 +79,16 @@ impl AppPathsKey { } } - /// Delete this application key from App Paths. + /// Delete this application key from App Paths key. /// - /// If there is no such key in App Paths, - /// this function does nothing. - pub fn delete(&mut self, scope: Scope) -> Result<()> { + /// Return true if we successfully delete this key, + /// otherwise false indicating there is no such key (already deleted). + pub fn delete(&mut self, scope: Scope) -> Result { let key = self.open_scope_for_write(scope)?; - key.parent_key - .delete_subkey_all(regext::blank_path_guard(self.key_name.inner())?)?; - Ok(()) + Ok(regext::arbitrarily_delete_subkey_all( + &key.parent_key, + regext::blank_path_guard(self.key_name.inner())?, + )?) } fn open_scope_for_getter(&self, scope: Scope) -> Result { diff --git a/wfassoc/src/lowlevel/applications_key.rs b/wfassoc/src/lowlevel/applications_key.rs index fec9c41..405ad93 100644 --- a/wfassoc/src/lowlevel/applications_key.rs +++ b/wfassoc/src/lowlevel/applications_key.rs @@ -68,6 +68,10 @@ impl ApplicationsKey { Ok(key.is_some()) } + /// Ensure this application key is presented in Applications key. + /// + /// Return true if we newly create this key, + /// otherwise false indicating there already is an existing key. pub fn ensure(&mut self, scope: Scope) -> Result { let key = self.open_scope_for_write(scope)?; if let None = key.this_key { @@ -81,11 +85,16 @@ impl ApplicationsKey { } } - pub fn delete(&mut self, scope: Scope) -> Result<()> { + /// Delete this application key from Applications key. + /// + /// Return true if we successfully delete this key, + /// otherwise false indicating there is no such key (already deleted). + pub fn delete(&mut self, scope: Scope) -> Result { let key = self.open_scope_for_write(scope)?; - key.parent_key - .delete_subkey_all(regext::blank_path_guard(self.key_name.inner())?)?; - Ok(()) + Ok(regext::arbitrarily_delete_subkey_all( + &key.parent_key, + regext::blank_path_guard(self.key_name.inner())?, + )?) } // YYC MARK: @@ -176,7 +185,7 @@ impl ApplicationsKey { } None => { // Delete shell and its all subkey. - key.delete_subkey_all(Self::NAMEOF_SHELL_VERB_PART1)?; + regext::arbitrarily_delete_subkey_all(&key, Self::NAMEOF_SHELL_VERB_PART1)?; } } @@ -217,7 +226,7 @@ impl ApplicationsKey { } None => { // Delete shell and its all subkey. - key.delete_subkey_all(Self::NAMEOF_DEFAULT_ICON_PART1)?; + regext::arbitrarily_delete_subkey_all(&key, Self::NAMEOF_DEFAULT_ICON_PART1)?; } } @@ -298,7 +307,7 @@ impl ApplicationsKey { } None => { // Delete this subkey. - key.delete_subkey_all(Self::NAMEOF_SUPPORTED_TYPES)?; + regext::arbitrarily_delete_subkey_all(&key, Self::NAMEOF_SUPPORTED_TYPES)?; } } diff --git a/wfassoc/src/lowlevel/ext_key.rs b/wfassoc/src/lowlevel/ext_key.rs index ec1ffdc..7cc19dc 100644 --- a/wfassoc/src/lowlevel/ext_key.rs +++ b/wfassoc/src/lowlevel/ext_key.rs @@ -66,6 +66,10 @@ impl ExtKey { Ok(key.is_some()) } + /// Ensure this file extension key is presented in Classes key. + /// + /// Return true if we newly create this key, + /// otherwise false indicating there already is an existing key. pub fn ensure(&mut self, scope: Scope) -> Result { let key = self.open_scope_for_write(scope)?; if let None = key.this_key { @@ -79,16 +83,21 @@ impl ExtKey { } } - pub fn delete(&mut self, scope: Scope) -> Result<()> { + /// Delete this file extension key from Classes key. + /// + /// Return true if we successfully delete this key, + /// otherwise false indicating there is no such key (already deleted). + pub fn delete(&mut self, scope: Scope) -> Result { let key = self.open_scope_for_write(scope)?; - key.parent_key - .delete_subkey_all(regext::blank_path_guard(self.ext.dotted_inner())?)?; - Ok(()) + Ok(regext::arbitrarily_delete_subkey_all( + &key.parent_key, + regext::blank_path_guard(self.ext.dotted_inner())?, + )?) } // 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, diff --git a/wfassoc/src/lowlevel/progid_key.rs b/wfassoc/src/lowlevel/progid_key.rs index 15768a3..bc1514d 100644 --- a/wfassoc/src/lowlevel/progid_key.rs +++ b/wfassoc/src/lowlevel/progid_key.rs @@ -66,6 +66,10 @@ impl ProgIdKey { Ok(key.is_some()) } + /// Ensure this ProgId key is presented in Classes key. + /// + /// Return true if we newly create this key, + /// otherwise false indicating there already is an existing key. pub fn ensure(&mut self, scope: Scope) -> Result { let key = self.open_scope_for_write(scope)?; if let None = key.this_key { @@ -79,18 +83,23 @@ impl ProgIdKey { } } - pub fn delete(&mut self, scope: Scope) -> Result<()> { + /// Delete this ProgId key from Classes key. + /// + /// Return true if we successfully delete this key, + /// otherwise false indicating there is no such key (already deleted). + pub fn delete(&mut self, scope: Scope) -> Result { let key = self.open_scope_for_write(scope)?; - key.parent_key - .delete_subkey_all(regext::blank_path_guard(self.progid.to_string())?)?; - Ok(()) + Ok(regext::arbitrarily_delete_subkey_all( + &key.parent_key, + regext::blank_path_guard(self.progid.to_string())?, + )?) } // YYC MARK: // Reference: https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids#programmatic-identifier-elements-used-by-file-associations // TODO: - // Currently we only support (Default), FriendlyTypeName and DefaultIcon + // Currently we only support (Default), FriendlyTypeName and DefaultIcon // to just cover the basic usage. // We may expand these in future. @@ -209,7 +218,7 @@ impl ProgIdKey { } None => { // Delete shell and its all subkey. - key.delete_subkey_all(Self::NAMEOF_SHELL_VERB_PART1)?; + regext::arbitrarily_delete_subkey_all(&key, Self::NAMEOF_SHELL_VERB_PART1)?; } } @@ -284,7 +293,7 @@ impl ProgIdKey { } None => { // Delete shell and its all subkey. - key.delete_subkey_all(Self::NAMEOF_DEFAULT_ICON_PART1)?; + regext::arbitrarily_delete_subkey_all(&key, Self::NAMEOF_DEFAULT_ICON_PART1)?; } } diff --git a/wfassoc/src/win32/regext.rs b/wfassoc/src/win32/regext.rs index 60d69e4..2d9e37f 100644 --- a/wfassoc/src/win32/regext.rs +++ b/wfassoc/src/win32/regext.rs @@ -61,12 +61,36 @@ pub fn try_get_value>( } } +/// Delete all tree of given path of given key anyway. +/// +/// This function was invented to fix the shortcoming of [RegKey::delete_subkey_all]. +/// This function always delete given path of given key no matter it is existing. +/// Oppositely, [RegKey::delete_subkey_all] will return error if there is no such path. +/// +/// Return true if we successfully delete this key, +/// otherwise false indicating there is no such key (already deleted). +pub fn arbitrarily_delete_subkey_all>( + regkey: &RegKey, + path: P, +) -> std::io::Result { + match regkey.delete_subkey_all(path) { + Ok(()) => Ok(true), + Err(e) => match e.raw_os_error() { + Some(errno) => match errno as u32 { + ERROR_FILE_NOT_FOUND => Ok(false), + _ => Err(e), + }, + _ => Err(e), + }, + } +} + /// Get the name of only subkey in given key. /// /// If there is only one subkey in given key, the return value is its name. /// If there is no any subkey, or has multiple subkeys, return None instead. /// If error occurs when fetching data, return Err(_). -/// +/// /// This is usually used for ShellVerb fetching. pub fn get_sole_subkey_name(regkey: &RegKey) -> std::io::Result> { let mut subkey_enumerator = regkey.enum_keys(); @@ -85,7 +109,7 @@ pub fn get_sole_subkey_name(regkey: &RegKey) -> std::io::Result> } /// Get the name list of all "string" subkeys in given key. -/// +/// /// This is usually used for "OpenWithProgIds" subkey. pub fn get_all_string_subkey_names(regkey: &RegKey) -> std::io::Result> { regkey @@ -105,11 +129,14 @@ pub fn get_all_string_subkey_names(regkey: &RegKey) -> std::io::Result std::io::Result<()> { + // There is no possibility that this key do not existing, + // because what we are cleaning is self content. + // So directly use delete_subkey_all is okey. regkey.delete_subkey_all("") } @@ -135,7 +162,7 @@ impl BlankPathError { /// Because it will cause unexpected behavior that returning key self, rather than subkey. /// This is VERY dangerous especially for those registry delete functions. /// So I create this function to prevent any harmful blank path was passed into registry function. -/// +/// /// This function MUST be used for the value, whose content can not be confirmed at compile time, /// and it will be passed to get/set value, or create/delete key functions. pub fn blank_path_guard>(path: P) -> std::result::Result { diff --git a/wfassoc/tests/lowlevel.rs b/wfassoc/tests/lowlevel.rs index 8b9322b..d497791 100644 --- a/wfassoc/tests/lowlevel.rs +++ b/wfassoc/tests/lowlevel.rs @@ -31,6 +31,7 @@ fn test_app_paths_key() { // delete and ensure let rv = key.delete(scope); + eprintln!("{rv:?}"); assert!(rv.is_ok()); let rv = key.is_exist(scope); assert!(rv.is_ok());