refactor: seperate highlevel into 2 individual files
This commit is contained in:
@@ -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()));
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
613
wfassoc/src/highlevel/program.rs
Normal file
613
wfassoc/src/highlevel/program.rs
Normal 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
|
||||||
215
wfassoc/src/highlevel/schema.rs
Normal file
215
wfassoc/src/highlevel/schema.rs
Normal 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
|
||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
Reference in New Issue
Block a user