1
0

feat: finish highlevel register function

This commit is contained in:
2026-05-08 10:41:33 +08:00
parent 94f868ebda
commit 8f762928db
3 changed files with 152 additions and 81 deletions

View File

@@ -1,4 +1,4 @@
use crate::{lowlevel, utilities, win32::concept}; use crate::{lowlevel, utilities, win32::{self, concept}};
use regex::Regex; use regex::Regex;
use std::collections::HashMap; use std::collections::HashMap;
use std::ffi::OsStr; use std::ffi::OsStr;
@@ -45,7 +45,31 @@ pub enum ParseProgramError {
/// Error occurs when operating with [Program]. /// Error occurs when operating with [Program].
#[derive(Debug, TeError)] #[derive(Debug, TeError)]
pub enum ProgramError {} pub enum ProgramError {
#[error("{0}")]
Lowlevel(#[from] lowlevel::Error),
}
// endregion
// region: Utilities
/// The println macro only works on Debug mode
/// for tracing the execution of some important functions.
macro_rules! debug_println {
// For no argument.
() => {
if cfg!(debug_assertions) {
eprintln!();
}
};
// For one or more arguments like println!.
($($arg:tt)*) => {
if cfg!(debug_assertions) {
eprintln!($($arg)*);
}
};
}
// endregion // endregion
@@ -54,10 +78,10 @@ pub enum ProgramError {}
// region: Schema Body // region: Schema Body
/// The sketchpad of complete [Program]. /// The sketchpad of complete [Program].
/// ///
/// In suggested usage, we will create a [Schema] first, /// In suggested usage, we will create a [Schema] first,
/// fill some essential and optional properties, /// fill some essential and optional properties,
/// then add file extensions which we need. /// then add file extensions which we need.
/// And finally convert it into immutable [Program] for formal using. /// And finally convert it into immutable [Program] for formal using.
#[derive(Debug)] #[derive(Debug)]
pub struct Schema { pub struct Schema {
@@ -92,7 +116,7 @@ impl Schema {
} }
/// Set the identifier of the schema. /// Set the identifier of the schema.
/// ///
/// The identifier is used to build ProgId. /// 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. /// 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. /// And it should not be empty.
@@ -143,7 +167,7 @@ impl Schema {
} }
/// Add a file extension to the schema. /// Add a file extension to the schema.
/// ///
/// The parameter `ext` is the file extension without leading dot `.`. /// The parameter `ext` is the file extension without leading dot `.`.
pub fn add_ext( pub fn add_ext(
&mut self, &mut self,
@@ -162,7 +186,7 @@ impl Schema {
} }
/// Try converting [Schema] into [Program]. /// Try converting [Schema] into [Program].
/// ///
/// This is equivalent to [Program::new]. /// This is equivalent to [Program::new].
pub fn into_program(self) -> Result<Program, ParseProgramError> { pub fn into_program(self) -> Result<Program, ParseProgramError> {
Program::new(self) Program::new(self)
@@ -203,8 +227,8 @@ impl SchemaExt {
pub struct Program { pub struct Program {
app_paths_key: lowlevel::AppPathsKey, app_paths_key: lowlevel::AppPathsKey,
applications_key: lowlevel::ApplicationsKey, applications_key: lowlevel::ApplicationsKey,
file_name: String, app_path: String,
dir_name: String, app_dir_path: String,
name: Option<Rc<ProgramStr>>, name: Option<Rc<ProgramStr>>,
icon: Option<Rc<ProgramIcon>>, icon: Option<Rc<ProgramIcon>>,
behavior: Option<Rc<ProgramBehavior>>, behavior: Option<Rc<ProgramBehavior>>,
@@ -213,8 +237,7 @@ pub struct Program {
icons: Vec<Rc<ProgramIcon>>, icons: Vec<Rc<ProgramIcon>>,
behaviors: Vec<Rc<ProgramBehavior>>, behaviors: Vec<Rc<ProgramBehavior>>,
progid_keys: Vec<Rc<ProgramProgIdKey>>, ext_keys: Vec<ProgramProgIdExtKey>,
ext_keys: Vec<ProgramExtKey>,
} }
impl TryFrom<Schema> for Program { impl TryFrom<Schema> for Program {
@@ -236,7 +259,7 @@ impl Program {
/// Extract the start in path from full path to application, /// Extract the start in path from full path to application,
/// which basically is the stem of full path. /// which basically is the stem of full path.
fn extract_dir_name(full_path: &Path) -> Result<&OsStr, ParseProgramError> { fn extract_dir_path(full_path: &Path) -> Result<&OsStr, ParseProgramError> {
full_path full_path
.parent() .parent()
.map(|p| p.as_os_str()) .map(|p| p.as_os_str())
@@ -299,19 +322,20 @@ impl Program {
} }
/// Try converting [Schema] into [Program]. /// Try converting [Schema] into [Program].
/// ///
/// During this process, some checks will be performed to ensure the validity of the data. /// 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. /// For example, the reference to icon, name, or behavior must exist in their respective dictionaries.
/// The identifier must be suit for building ProgId. /// The identifier must be suit for building ProgId.
pub fn new(schema: Schema) -> Result<Self, ParseProgramError> { pub fn new(schema: Schema) -> Result<Self, ParseProgramError> {
// Extract file name part and directory name part respectively. // Extract file name part and directory name part respectively.
let schema_path = Path::new(&schema.path); let schema_path = Path::new(&schema.path);
let file_name = Self::extract_file_name(schema_path)?; let app_path = schema.path.clone();
let file_name = String::from(utilities::osstr_to_str(file_name)?); let app_file_name = Self::extract_file_name(schema_path)?;
let dir_name = Self::extract_dir_name(schema_path)?; let app_file_name = String::from(utilities::osstr_to_str(app_file_name)?);
let dir_name = String::from(utilities::osstr_to_str(dir_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 // Build app paths key and applications key respectively
let key = concept::FileName::new(&file_name)?; let key = concept::FileName::new(&app_file_name)?;
let app_paths_key = lowlevel::AppPathsKey::new(key.clone()); let app_paths_key = lowlevel::AppPathsKey::new(key.clone());
let applications_key = lowlevel::ApplicationsKey::new(key.clone()); let applications_key = lowlevel::ApplicationsKey::new(key.clone());
@@ -355,8 +379,7 @@ impl Program {
.transpose()?; .transpose()?;
// We build ProgIdKey and ExtKey list at the same time // We build ProgIdKey and ExtKey list at the same time
let mut progid_keys: Vec<Rc<ProgramProgIdKey>> = Vec::with_capacity(schema.exts.len()); let mut ext_keys: Vec<ProgramProgIdExtKey> = Vec::with_capacity(schema.exts.len());
let mut ext_keys: Vec<ProgramExtKey> = Vec::with_capacity(schema.exts.len());
for (key, value) in &schema.exts { for (key, value) in &schema.exts {
// Build ProgId first. // Build ProgId first.
let progid = Self::build_progid(&schema.identifier, key.as_str())?; let progid = Self::build_progid(&schema.identifier, key.as_str())?;
@@ -368,44 +391,35 @@ impl Program {
let icon = Self::resolve_index(&value.icon, &icons, &icons_index_map)?; let icon = Self::resolve_index(&value.icon, &icons, &icons_index_map)?;
let behavior = Self::resolve_index(&value.behavior, &behaviors, &behaviors_index_map)?; let behavior = Self::resolve_index(&value.behavior, &behaviors, &behaviors_index_map)?;
// Create program ProgId key struct
let progid_key = ProgramProgIdKey {
key: progid_key,
name,
icon,
behavior,
};
// Create Rc style
let progid_key = Rc::new(progid_key);
// Build Ext. // Build Ext.
let ext = concept::Ext::new(key.as_str())?; let ext = concept::Ext::new(key.as_str())?;
let ext_key = lowlevel::ExtKey::new(ext); let ext_key = lowlevel::ExtKey::new(ext);
// Create program Ext key struct // Create program Ext key struct
let ext_key = ProgramExtKey { let progid_ext_key = ProgramProgIdExtKey {
key: ext_key, ext_key,
assoc: progid_key.clone(), progid_key,
name,
icon,
behavior,
}; };
// Add them into list // Add them into list
progid_keys.push(progid_key); ext_keys.push(progid_ext_key);
ext_keys.push(ext_key);
} }
// Everything is okey // Everything is okey
Ok(Self { Ok(Self {
app_paths_key, app_paths_key,
applications_key, applications_key,
file_name, app_path,
dir_name, app_dir_path,
name, name,
icon, icon,
behavior, behavior,
strs, strs,
icons, icons,
behaviors, behaviors,
progid_keys,
ext_keys, ext_keys,
}) })
} }
@@ -414,16 +428,62 @@ impl Program {
impl Program { impl Program {
/// Register this application. /// Register this application.
/// ///
/// If there is registration of this application, /// If there is complete or partial registration of this application
/// this function will return error. /// (partial registration may occurs when registration failed),
/// this function does nothing.
pub fn register(&mut self, scope: Scope) -> Result<(), ProgramError> { pub fn register(&mut self, scope: Scope) -> Result<(), ProgramError> {
todo!() // 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_progid_key in &mut self.ext_keys {
let progid_key = &mut program_progid_key.progid_key;
// Create ProgId subkey
debug_println!("Adding ProgId \"{0}\" subkey...", progid_key.inner().to_string());
progid_key.ensure(scope)?;
// Write ProgId values
let name = Some(&program_progid_key.name.inner);
progid_key.set_default(scope, name)?;
progid_key.set_shell_verb(scope, &program_progid_key.behavior.inner)?;
progid_key.set_friendly_type_name(scope, name)?;
progid_key.set_default_icon(scope, Some(&program_progid_key.icon.inner))?;
}
// Everything is okey.
// Notify changes and return
win32::utilities::notify_assoc_changed();
Ok(())
} }
/// Unregister this application. /// Unregister this application.
/// ///
/// If there is no registration of this application, /// No matter whether there is registration of this application,
/// this function will return error. /// this function always make sure that there is no registration after running this function.
pub fn unregister(&mut self, scope: Scope) -> Result<(), ProgramError> { pub fn unregister(&mut self, scope: Scope) -> Result<(), ProgramError> {
todo!() todo!()
} }
@@ -472,22 +532,20 @@ struct ProgramBehavior {
inner: lowlevel::ShellVerb, inner: lowlevel::ShellVerb,
} }
/// Internal used struct presenting a Program ProgId. /// 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)] #[derive(Debug)]
struct ProgramProgIdKey { struct ProgramProgIdExtKey {
key: lowlevel::ProgIdKey, ext_key: lowlevel::ExtKey,
progid_key: lowlevel::ProgIdKey,
name: Rc<ProgramStr>, name: Rc<ProgramStr>,
icon: Rc<ProgramIcon>, icon: Rc<ProgramIcon>,
behavior: Rc<ProgramBehavior>, behavior: Rc<ProgramBehavior>,
} }
/// Internal used struct presenting a Program file extension.
#[derive(Debug)]
struct ProgramExtKey {
key: lowlevel::ExtKey,
assoc: Rc<ProgramProgIdKey>,
}
// endregion // endregion
// endregion // endregion

View File

@@ -603,11 +603,11 @@ impl ApplicationsKey {
const NAMEOF_SHELL_VERB_PART1: &str = "shell"; const NAMEOF_SHELL_VERB_PART1: &str = "shell";
const NAMEOF_SHELL_VERB_PART3: &str = "command"; const NAMEOF_SHELL_VERB_PART3: &str = "command";
pub fn get_shell_verb(&self, view: View) -> Result<ShellVerb, Error> { pub fn get_shell_verb(&self, view: View) -> Result<Option<ShellVerb>, Error> {
todo!() todo!()
} }
pub fn set_shell_verb(&mut self, scope: Scope, sv: &ShellVerb) -> Result<(), Error> { pub fn set_shell_verb(&mut self, scope: Scope, sv: Option<&ShellVerb>) -> Result<(), Error> {
todo!() todo!()
} }
@@ -618,7 +618,7 @@ impl ApplicationsKey {
} }
pub fn set_default_icon( pub fn set_default_icon(
&self, &mut self,
scope: Scope, scope: Scope,
icon: Option<&IconResVariant>, icon: Option<&IconResVariant>,
) -> Result<(), Error> { ) -> Result<(), Error> {
@@ -632,7 +632,7 @@ impl ApplicationsKey {
} }
pub fn set_friendly_app_name( pub fn set_friendly_app_name(
&self, &mut self,
scope: Scope, scope: Scope,
name: Option<&StrResVariant>, name: Option<&StrResVariant>,
) -> Result<(), Error> { ) -> Result<(), Error> {
@@ -646,10 +646,11 @@ impl ApplicationsKey {
} }
pub fn set_supported_types( pub fn set_supported_types(
&self, &mut self,
scope: Scope, scope: Scope,
tys: Option<&[concept::Ext]>, tys: Option<&[&concept::Ext]>,
) -> Result<(), Error> { ) -> Result<(), Error> {
// TODO: If given slice is empty, do not create this key.
todo!() todo!()
} }
@@ -659,7 +660,7 @@ impl ApplicationsKey {
todo!() todo!()
} }
pub fn set_no_open_with(&self, scope: Scope, flag: bool) -> Result<(), Error> { pub fn set_no_open_with(&mut self, scope: Scope, flag: bool) -> Result<(), Error> {
todo!() todo!()
} }
} }
@@ -722,6 +723,18 @@ impl ExtKey {
Ok(OpenedKey::new(classes, this_ext)) Ok(OpenedKey::new(classes, this_ext))
} }
pub fn is_exist(&self, view: View) -> Result<bool, Error> {
todo!()
}
pub fn ensure(&mut self, scope: Scope) -> Result<bool, Error> {
todo!()
}
pub fn delete(&mut self, scope: Scope) -> Result<(), Error> {
todo!()
}
// YYC MARK: // YYC MARK:
// 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,
@@ -795,6 +808,18 @@ impl ProgIdKey {
todo!() todo!()
} }
pub fn is_exist(&self, view: View) -> Result<bool, Error> {
todo!()
}
pub fn ensure(&mut self, scope: Scope) -> Result<bool, Error> {
todo!()
}
pub fn delete(&mut self, scope: Scope) -> Result<(), Error> {
todo!()
}
// YYC MARK: // YYC MARK:
// Currently we only support (Default), FriendlyTypeName and DefaultIcon // Currently we only support (Default), FriendlyTypeName and DefaultIcon
// to just cover the basic usage. // to just cover the basic usage.
@@ -805,6 +830,9 @@ impl ProgIdKey {
todo!() todo!()
} }
///
///
/// The legacy way to set friendly name for this ProgId.
pub fn set_default(&mut self, scope: Scope, name: Option<&StrResVariant>) -> Result<(), Error> { pub fn set_default(&mut self, scope: Scope, name: Option<&StrResVariant>) -> Result<(), Error> {
todo!() todo!()
} }
@@ -826,8 +854,11 @@ impl ProgIdKey {
todo!() todo!()
} }
///
///
/// Set this entry to a friendly name for the ProgID.
pub fn set_friendly_type_name( pub fn set_friendly_type_name(
&self, &mut self,
scope: Scope, scope: Scope,
name: Option<&StrResVariant>, name: Option<&StrResVariant>,
) -> Result<(), Error> { ) -> Result<(), Error> {
@@ -841,7 +872,7 @@ impl ProgIdKey {
} }
pub fn set_default_icon( pub fn set_default_icon(
&self, &mut self,
scope: Scope, scope: Scope,
icon: Option<&IconResVariant>, icon: Option<&IconResVariant>,
) -> Result<(), Error> { ) -> Result<(), Error> {

View File

@@ -5,24 +5,6 @@ use std::iter::FusedIterator;
use std::path::Path; use std::path::Path;
use thiserror::Error as TeError; use thiserror::Error as TeError;
/// The println macro only works on Debug mode
/// for tracing the execution of some important functions.
#[macro_export]
macro_rules! debug_println {
// For no argument.
() => {
if cfg!(debug_assertions) {
eprintln!();
}
};
// For one or more arguments like println!.
($($arg:tt)*) => {
if cfg!(debug_assertions) {
eprintln!($($arg)*);
}
};
}
// region: OS String Related // region: OS String Related
/// The error occurs when casting `OsStr` into `str`. /// The error occurs when casting `OsStr` into `str`.