diff --git a/wfassoc/src/highlevel.rs b/wfassoc/src/highlevel.rs index 7e30ecd..6589882 100644 --- a/wfassoc/src/highlevel.rs +++ b/wfassoc/src/highlevel.rs @@ -1,4 +1,4 @@ -use crate::{lowlevel, utilities, win32::concept}; +use crate::{lowlevel, utilities, win32::{self, concept}}; use regex::Regex; use std::collections::HashMap; use std::ffi::OsStr; @@ -45,7 +45,31 @@ pub enum ParseProgramError { /// Error occurs when operating with [Program]. #[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 @@ -54,10 +78,10 @@ pub enum ProgramError {} // region: Schema Body /// 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, -/// then add file extensions which we need. +/// then add file extensions which we need. /// And finally convert it into immutable [Program] for formal using. #[derive(Debug)] pub struct Schema { @@ -92,7 +116,7 @@ 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. @@ -143,7 +167,7 @@ impl Schema { } /// Add a file extension to the schema. - /// + /// /// The parameter `ext` is the file extension without leading dot `.`. pub fn add_ext( &mut self, @@ -162,7 +186,7 @@ impl Schema { } /// Try converting [Schema] into [Program]. - /// + /// /// This is equivalent to [Program::new]. pub fn into_program(self) -> Result { Program::new(self) @@ -203,8 +227,8 @@ impl SchemaExt { pub struct Program { app_paths_key: lowlevel::AppPathsKey, applications_key: lowlevel::ApplicationsKey, - file_name: String, - dir_name: String, + app_path: String, + app_dir_path: String, name: Option>, icon: Option>, behavior: Option>, @@ -213,8 +237,7 @@ pub struct Program { icons: Vec>, behaviors: Vec>, - progid_keys: Vec>, - ext_keys: Vec, + ext_keys: Vec, } impl TryFrom for Program { @@ -236,7 +259,7 @@ impl Program { /// Extract the start in path from full path to application, /// 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 .parent() .map(|p| p.as_os_str()) @@ -299,19 +322,20 @@ impl Program { } /// 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 file_name = Self::extract_file_name(schema_path)?; - let file_name = String::from(utilities::osstr_to_str(file_name)?); - let dir_name = Self::extract_dir_name(schema_path)?; - let dir_name = String::from(utilities::osstr_to_str(dir_name)?); + 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(&file_name)?; + 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()); @@ -355,8 +379,7 @@ impl Program { .transpose()?; // We build ProgIdKey and ExtKey list at the same time - let mut progid_keys: Vec> = Vec::with_capacity(schema.exts.len()); - let mut ext_keys: Vec = Vec::with_capacity(schema.exts.len()); + 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())?; @@ -368,44 +391,35 @@ impl Program { let icon = Self::resolve_index(&value.icon, &icons, &icons_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. let ext = concept::Ext::new(key.as_str())?; let ext_key = lowlevel::ExtKey::new(ext); // Create program Ext key struct - let ext_key = ProgramExtKey { - key: ext_key, - assoc: progid_key.clone(), + let progid_ext_key = ProgramProgIdExtKey { + ext_key, + progid_key, + name, + icon, + behavior, }; // Add them into list - progid_keys.push(progid_key); - ext_keys.push(ext_key); + ext_keys.push(progid_ext_key); } // Everything is okey Ok(Self { app_paths_key, applications_key, - file_name, - dir_name, + app_path, + app_dir_path, name, icon, behavior, strs, icons, behaviors, - progid_keys, ext_keys, }) } @@ -414,16 +428,62 @@ impl Program { impl Program { /// Register this application. /// - /// If there is registration of this application, - /// this function will return error. + /// 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> { - 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. /// - /// If there is no registration of this application, - /// this function will return error. + /// 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> { todo!() } @@ -472,22 +532,20 @@ struct ProgramBehavior { 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)] -struct ProgramProgIdKey { - key: lowlevel::ProgIdKey, +struct ProgramProgIdExtKey { + ext_key: lowlevel::ExtKey, + + progid_key: lowlevel::ProgIdKey, name: Rc, icon: Rc, behavior: Rc, } -/// Internal used struct presenting a Program file extension. -#[derive(Debug)] -struct ProgramExtKey { - key: lowlevel::ExtKey, - assoc: Rc, -} - // endregion // endregion diff --git a/wfassoc/src/lowlevel.rs b/wfassoc/src/lowlevel.rs index 1130ec8..a03bc1e 100644 --- a/wfassoc/src/lowlevel.rs +++ b/wfassoc/src/lowlevel.rs @@ -603,11 +603,11 @@ impl ApplicationsKey { const NAMEOF_SHELL_VERB_PART1: &str = "shell"; const NAMEOF_SHELL_VERB_PART3: &str = "command"; - pub fn get_shell_verb(&self, view: View) -> Result { + pub fn get_shell_verb(&self, view: View) -> Result, Error> { 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!() } @@ -618,7 +618,7 @@ impl ApplicationsKey { } pub fn set_default_icon( - &self, + &mut self, scope: Scope, icon: Option<&IconResVariant>, ) -> Result<(), Error> { @@ -632,7 +632,7 @@ impl ApplicationsKey { } pub fn set_friendly_app_name( - &self, + &mut self, scope: Scope, name: Option<&StrResVariant>, ) -> Result<(), Error> { @@ -646,10 +646,11 @@ impl ApplicationsKey { } pub fn set_supported_types( - &self, + &mut self, scope: Scope, - tys: Option<&[concept::Ext]>, + tys: Option<&[&concept::Ext]>, ) -> Result<(), Error> { + // TODO: If given slice is empty, do not create this key. todo!() } @@ -659,7 +660,7 @@ impl ApplicationsKey { 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!() } } @@ -722,6 +723,18 @@ impl ExtKey { Ok(OpenedKey::new(classes, this_ext)) } + pub fn is_exist(&self, view: View) -> Result { + todo!() + } + + pub fn ensure(&mut self, scope: Scope) -> Result { + todo!() + } + + pub fn delete(&mut self, scope: Scope) -> Result<(), Error> { + todo!() + } + // YYC MARK: // We do not support "Content Type" and "PerceivedType" // because current interface are enough to use, @@ -795,6 +808,18 @@ impl ProgIdKey { todo!() } + pub fn is_exist(&self, view: View) -> Result { + todo!() + } + + pub fn ensure(&mut self, scope: Scope) -> Result { + todo!() + } + + pub fn delete(&mut self, scope: Scope) -> Result<(), Error> { + todo!() + } + // YYC MARK: // Currently we only support (Default), FriendlyTypeName and DefaultIcon // to just cover the basic usage. @@ -805,6 +830,9 @@ impl ProgIdKey { todo!() } + /// + /// + /// The legacy way to set friendly name for this ProgId. pub fn set_default(&mut self, scope: Scope, name: Option<&StrResVariant>) -> Result<(), Error> { todo!() } @@ -826,8 +854,11 @@ impl ProgIdKey { todo!() } + /// + /// + /// Set this entry to a friendly name for the ProgID. pub fn set_friendly_type_name( - &self, + &mut self, scope: Scope, name: Option<&StrResVariant>, ) -> Result<(), Error> { @@ -841,7 +872,7 @@ impl ProgIdKey { } pub fn set_default_icon( - &self, + &mut self, scope: Scope, icon: Option<&IconResVariant>, ) -> Result<(), Error> { diff --git a/wfassoc/src/utilities.rs b/wfassoc/src/utilities.rs index e5c7c06..ba45d51 100644 --- a/wfassoc/src/utilities.rs +++ b/wfassoc/src/utilities.rs @@ -5,24 +5,6 @@ use std::iter::FusedIterator; use std::path::Path; 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 /// The error occurs when casting `OsStr` into `str`.