feat(registry): implement file extension linking in Windows registry
- Add `link_ext` and `unlink_ext` methods to Program for managing file associations - Introduce new error variants for invalid tokens - Add utility function `capitalize_first_ascii` for ProgId generation - Implement registry key operations for user and system scopes
This commit is contained in:
@ -42,8 +42,10 @@ pub enum Error {
|
||||
DupManner(String),
|
||||
#[error("file extension \"{0}\" is already registered")]
|
||||
DupExt(String),
|
||||
#[error("the token of associated manner for file extension is invalid")]
|
||||
InvalidAssocManner,
|
||||
#[error("the token of manner is invalid")]
|
||||
InvalidMannerToken,
|
||||
#[error("the token of file extension is invalid")]
|
||||
InvalidExtToken,
|
||||
}
|
||||
|
||||
/// The result type used in this crate.
|
||||
@ -194,7 +196,7 @@ impl Program {
|
||||
pub fn add_ext(&mut self, ext: &str, token: Token) -> Result<Token> {
|
||||
// Check manner token
|
||||
if let None = self.manners.get_index(token) {
|
||||
return Err(Error::InvalidAssocManner);
|
||||
return Err(Error::InvalidMannerToken);
|
||||
}
|
||||
|
||||
// Create extension from string
|
||||
@ -321,6 +323,74 @@ impl Program {
|
||||
}
|
||||
}
|
||||
|
||||
impl Program {
|
||||
const CLASSES: &str = "Software\\Classes";
|
||||
|
||||
/// Set the default "open with" of given extension to this program.
|
||||
pub fn link_ext(&self, ext: Token, scope: Scope) -> Result<()> {
|
||||
// Check privilege
|
||||
if !scope.has_privilege() {
|
||||
return Err(Error::NoPrivilege);
|
||||
}
|
||||
|
||||
// Fetch file extension and build ProgId from it
|
||||
let (ext, _) = match self.exts.get_index(ext) {
|
||||
Some(v) => v,
|
||||
None => return Err(Error::InvalidExtToken),
|
||||
};
|
||||
let prog_id = self.build_prog_id(ext);
|
||||
|
||||
// Fetch root key and navigate to Classes
|
||||
let hk = RegKey::predef(match scope {
|
||||
Scope::User => HKEY_CURRENT_USER,
|
||||
Scope::System => HKEY_LOCAL_MACHINE,
|
||||
});
|
||||
let classes = hk.open_subkey_with_flags(Self::CLASSES, KEY_READ)?;
|
||||
|
||||
// Open or create this extension key
|
||||
let (subkey, _) = classes.create_subkey_with_flags(ext.to_string(), KEY_WRITE)?;
|
||||
// Set the default way to open this file extension
|
||||
subkey.set_value("", &prog_id.to_string())?;
|
||||
|
||||
// Okey
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove this program from the default "open with" of given extension.
|
||||
///
|
||||
/// If the default "open with" of given extension is not our program,
|
||||
/// this function do nothing.
|
||||
pub fn unlink_ext(&self, ext: Token, scope: Scope) -> Result<()> {
|
||||
// Check privilege
|
||||
if !scope.has_privilege() {
|
||||
return Err(Error::NoPrivilege);
|
||||
}
|
||||
|
||||
// Fetch file extension and build ProgId from it
|
||||
let (ext, _) = match self.exts.get_index(ext) {
|
||||
Some(v) => v,
|
||||
None => return Err(Error::InvalidExtToken),
|
||||
};
|
||||
let prog_id = self.build_prog_id(ext);
|
||||
|
||||
// Fetch root key and navigate to Classes
|
||||
let hk = RegKey::predef(match scope {
|
||||
Scope::User => HKEY_CURRENT_USER,
|
||||
Scope::System => HKEY_LOCAL_MACHINE,
|
||||
});
|
||||
let classes = hk.open_subkey_with_flags(Self::CLASSES, KEY_READ)?;
|
||||
|
||||
// Open key for this extension.
|
||||
// If there is no such key, return directly.
|
||||
let subkey = classes.open_subkey_with_flags(ext.to_string(), KEY_WRITE)?;
|
||||
// Delete the default key.
|
||||
subkey.delete_value("")?;
|
||||
|
||||
// Okey
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Program {
|
||||
/// Extract the file name part from full path to application,
|
||||
/// which was used in Registry path component.
|
||||
@ -344,20 +414,12 @@ impl Program {
|
||||
.and_then(|p| if p.is_empty() { None } else { Some(p) })
|
||||
.ok_or(Error::BadFullAppPath)
|
||||
}
|
||||
}
|
||||
|
||||
impl Program {
|
||||
/// Set the default "open with" of given extension to this program.
|
||||
pub fn link_ext(&self, ext: Token) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Remove this program from the default "open with" of given extension.
|
||||
///
|
||||
/// If the default "open with" of given extension is not our program,
|
||||
/// this function do nothing.
|
||||
pub fn unlink_ext(&self, ext: Token) -> Result<()> {
|
||||
todo!()
|
||||
/// Build ProgId from identifier and given file extension.
|
||||
fn build_prog_id(&self, ext: &assoc::Ext) -> assoc::ProgId {
|
||||
let vendor = utilities::capitalize_first_ascii(&self.identifier);
|
||||
let component = utilities::capitalize_first_ascii(ext.inner());
|
||||
assoc::ProgId::Loose(assoc::LosseProgId::new(&vendor, &component))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
//! The module containing useful stuff used in this crate.
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::iter::FusedIterator;
|
||||
use std::path::Path;
|
||||
use thiserror::Error as TeError;
|
||||
|
||||
@ -117,3 +118,51 @@ pub fn osstr_to_str(osstr: &OsStr) -> Result<&str, CastOsStrError> {
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region: Capitalize First ASCII Letter
|
||||
|
||||
struct CapitalizeFirstAscii<T>
|
||||
where
|
||||
T: Iterator<Item = char>,
|
||||
{
|
||||
is_first: bool,
|
||||
iter: T,
|
||||
}
|
||||
|
||||
impl<T> CapitalizeFirstAscii<T>
|
||||
where
|
||||
T: Iterator<Item = char>,
|
||||
{
|
||||
fn new(iter: T) -> Self {
|
||||
Self {
|
||||
is_first: false,
|
||||
iter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Iterator for CapitalizeFirstAscii<T>
|
||||
where
|
||||
T: Iterator<Item = char>,
|
||||
{
|
||||
type Item = char;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.iter.next().map(|c| {
|
||||
if self.is_first {
|
||||
self.is_first = false;
|
||||
c.to_ascii_uppercase()
|
||||
} else {
|
||||
c.to_ascii_lowercase()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FusedIterator for CapitalizeFirstAscii<T> where T: Iterator<Item = char> + FusedIterator {}
|
||||
|
||||
pub fn capitalize_first_ascii(s: &str) -> String {
|
||||
CapitalizeFirstAscii::new(s.chars()).collect()
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
Reference in New Issue
Block a user