1
0

refactor: seperate highlevel into 2 individual files

This commit is contained in:
2026-05-19 10:26:17 +08:00
parent f8db414da3
commit 658806e9ff
8 changed files with 844 additions and 784 deletions

View File

@@ -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 star is present alone, return fixed list from zero to the maximum ext index.
if has_star { 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() // Convert each extension name to index using program.find_ext()
@@ -138,7 +138,7 @@ fn run_ext_list(
) -> Result<()> { ) -> Result<()> {
// Fetch info // Fetch info
let mut ext_list: HashMap<String, Option<String>> = HashMap::new(); let mut ext_list: HashMap<String, Option<String>> = HashMap::new();
for index in 0..program.get_ext_count() { for index in 0..program.exts_len() {
let ext = program.get_ext(index)?; let ext = program.get_ext(index)?;
let status = program.query_ext(view, index)?; let status = program.query_ext(view, index)?;
ext_list.insert(ext.dotted_inner(), status.map(|s| s.get_name().to_string())); ext_list.insert(ext.dotted_inner(), status.map(|s| s.get_name().to_string()));

View File

@@ -1,61 +1,4 @@
use crate::{ use crate::lowlevel;
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
// region: Utilities // region: Utilities
@@ -78,712 +21,13 @@ macro_rules! debug_println {
// endregion // endregion
// region: Schema // region: Exposed Stuff
// region: Schema Body mod schema;
mod program;
/// The sketchpad of complete [Program]. pub use schema::{Schema, SchemaError};
/// pub use program::{Program, ParseProgramError, ProgramError, ProgramExtStatus};
/// In suggested usage, we will create a [Schema] first, pub use lowlevel::{Scope, View};
/// 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<String>,
icon: Option<String>,
behavior: Option<String>,
strs: HashMap<String, String>,
icons: HashMap<String, String>,
behaviors: HashMap<String, String>,
exts: HashMap<String, SchemaExt>,
}
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, ParseProgramError> {
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<Arc<ProgramStr>>,
icon: Option<Arc<ProgramIcon>>,
behavior: Option<Arc<ProgramBehavior>>,
#[allow(dead_code)]
strs: Vec<Arc<ProgramStr>>,
#[allow(dead_code)]
icons: Vec<Arc<ProgramIcon>>,
#[allow(dead_code)]
behaviors: Vec<Arc<ProgramBehavior>>,
ext_keys: Vec<ProgramProgIdExtKey>,
ext_keys_map: HashMap<String, usize>,
}
impl TryFrom<Schema> for Program {
type Error = ParseProgramError;
fn try_from(value: Schema) -> Result<Self, Self::Error> {
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<V, U, F>(
hashmap: &HashMap<String, V>,
f: F,
) -> Result<(Vec<U>, HashMap<String, usize>), ParseProgramError>
where
F: Fn(&V) -> Result<U, ParseProgramError>,
{
let mut indexmap: HashMap<String, usize> = HashMap::with_capacity(hashmap.len());
let mut vector: Vec<U> = 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<T>(
key: &String,
vector: &Vec<Arc<T>>,
index_map: &HashMap<String, usize>,
) -> Result<Arc<T>, 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<lowlevel::LosseProgId, ParseProgramError> {
// Use Regex to check identifier
static RE: LazyLock<Regex> = 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<Self, ParseProgramError> {
// 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<ProgramProgIdExtKey> = 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<String, ProgramError> {
todo!()
}
pub fn resolve_icon(&self) -> Result<concept::IconRc, ProgramError> {
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<usize> {
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<bool, ProgramError> {
// 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<Option<ProgramExtStatus>, 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<String> = 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<concept::IconRc>,
}
impl ProgramExtStatus {
fn new(name: String, icon: Option<concept::IconRc>) -> 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<ProgramStr>,
icon: Arc<ProgramIcon>,
behavior: Arc<ProgramBehavior>,
}
// endregion
// endregion // endregion

View File

@@ -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<Arc<ProgramStr>>,
icon: Option<Arc<ProgramIcon>>,
behavior: Option<Arc<ProgramBehavior>>,
#[allow(dead_code)]
strs: Vec<Arc<ProgramStr>>,
#[allow(dead_code)]
icons: Vec<Arc<ProgramIcon>>,
#[allow(dead_code)]
behaviors: Vec<Arc<ProgramBehavior>>,
ext_keys: Vec<ProgramProgIdExtKey>,
ext_keys_map: HashMap<String, usize>,
}
impl TryFrom<Schema> for Program {
type Error = ParseProgramError;
fn try_from(value: Schema) -> Result<Self, Self::Error> {
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<V, U, F>(
hashmap: &HashMap<String, V>,
f: F,
) -> Result<(Vec<U>, HashMap<String, usize>), ParseProgramError>
where
F: Fn(&V) -> Result<U, ParseProgramError>,
{
let mut indexmap: HashMap<String, usize> = HashMap::with_capacity(hashmap.len());
let mut vector: Vec<U> = 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<T>(
key: &str,
vector: &Vec<Arc<T>>,
index_map: &HashMap<String, usize>,
) -> Result<Arc<T>, 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<lowlevel::LosseProgId, ParseProgramError> {
// Use Regex to check identifier
static RE: LazyLock<Regex> = 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<Self, ParseProgramError> {
// 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<ProgramProgIdExtKey> = 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<String, ProgramError> {
todo!()
}
pub fn resolve_icon(&self) -> Result<concept::IconRc, ProgramError> {
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<usize> {
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<bool, ProgramError> {
// 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<Option<ProgramExtStatus>, 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<String> = 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<ProgramStr>,
icon: Arc<ProgramIcon>,
behavior: Arc<ProgramBehavior>,
}
// 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<concept::IconRc>,
}
impl ProgramExtStatus {
fn new(name: String, icon: Option<concept::IconRc>) -> 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

View File

@@ -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<String>,
icon: Option<String>,
behavior: Option<String>,
strs: HashMap<String, String>,
icons: HashMap<String, String>,
behaviors: HashMap<String, String>,
exts: HashMap<String, SchemaExt>,
}
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, ParseProgramError> {
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<String, String> {
&self.strs
}
pub(super) fn get_icons(&self) -> &HashMap<String, String> {
&self.icons
}
pub(super) fn get_behaviors(&self) -> &HashMap<String, String> {
&self.behaviors
}
pub(super) fn get_exts(&self) -> &HashMap<String, SchemaExt> {
&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

View File

@@ -5,8 +5,6 @@ use crate::win32::{concept, regext};
use winreg::RegKey; use winreg::RegKey;
use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
// region: App Paths Key
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AppPathsKey { pub struct AppPathsKey {
key_name: concept::FileName, key_name: concept::FileName,
@@ -147,5 +145,3 @@ impl AppPathsKey {
Ok(()) Ok(())
} }
} }
// endregion

View File

@@ -6,8 +6,6 @@ use crate::win32::{concept, regext};
use winreg::RegKey; use winreg::RegKey;
use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
// region: Applications Key
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ApplicationsKey { pub struct ApplicationsKey {
key_name: concept::FileName, key_name: concept::FileName,
@@ -327,5 +325,3 @@ impl ApplicationsKey {
Ok(()) Ok(())
} }
} }
// endregion

View File

@@ -6,8 +6,6 @@ use crate::win32::{concept, regext};
use winreg::RegKey; use winreg::RegKey;
use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
// region: File Extension Key
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ExtKey { pub struct ExtKey {
ext: concept::Ext, ext: concept::Ext,
@@ -90,10 +88,12 @@ impl ExtKey {
// YYC MARK: // YYC MARK:
// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/fa-file-types#setting-optional-subkeys-and-file-type-extension-attributes // 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" // We do not support "Content Type" and "PerceivedType"
// because current interface are enough to use, // because current interface are enough to use,
// and these types has not been made as concept struct in Rust. // 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<RegKey> { fn open_view_for_getter(&self, view: View) -> Result<RegKey> {
self.open_view_for_read(view)? self.open_view_for_read(view)?
@@ -209,5 +209,3 @@ impl ExtKey {
Ok(()) Ok(())
} }
} }
// endregion

View File

@@ -6,8 +6,6 @@ use crate::win32::regext;
use winreg::RegKey; use winreg::RegKey;
use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
// region: ProgId Key
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ProgIdKey { pub struct ProgIdKey {
progid: LosseProgId, progid: LosseProgId,
@@ -90,9 +88,11 @@ impl ProgIdKey {
// YYC MARK: // YYC MARK:
// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids#programmatic-identifier-elements-used-by-file-associations // 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. // to just cover the basic usage.
// We may expand these in future.
fn open_view_for_getter(&self, view: View) -> Result<RegKey> { fn open_view_for_getter(&self, view: View) -> Result<RegKey> {
self.open_view_for_read(view)? self.open_view_for_read(view)?
@@ -291,5 +291,3 @@ impl ProgIdKey {
Ok(()) Ok(())
} }
} }
// endregion