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),
|
DupManner(String),
|
||||||
#[error("file extension \"{0}\" is already registered")]
|
#[error("file extension \"{0}\" is already registered")]
|
||||||
DupExt(String),
|
DupExt(String),
|
||||||
#[error("the token of associated manner for file extension is invalid")]
|
#[error("the token of manner is invalid")]
|
||||||
InvalidAssocManner,
|
InvalidMannerToken,
|
||||||
|
#[error("the token of file extension is invalid")]
|
||||||
|
InvalidExtToken,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result type used in this crate.
|
/// 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> {
|
pub fn add_ext(&mut self, ext: &str, token: Token) -> Result<Token> {
|
||||||
// Check manner token
|
// Check manner token
|
||||||
if let None = self.manners.get_index(token) {
|
if let None = self.manners.get_index(token) {
|
||||||
return Err(Error::InvalidAssocManner);
|
return Err(Error::InvalidMannerToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create extension from string
|
// 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 {
|
impl Program {
|
||||||
/// Extract the file name part from full path to application,
|
/// Extract the file name part from full path to application,
|
||||||
/// which was used in Registry path component.
|
/// which was used in Registry path component.
|
||||||
@ -344,20 +414,12 @@ impl Program {
|
|||||||
.and_then(|p| if p.is_empty() { None } else { Some(p) })
|
.and_then(|p| if p.is_empty() { None } else { Some(p) })
|
||||||
.ok_or(Error::BadFullAppPath)
|
.ok_or(Error::BadFullAppPath)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Program {
|
/// Build ProgId from identifier and given file extension.
|
||||||
/// Set the default "open with" of given extension to this program.
|
fn build_prog_id(&self, ext: &assoc::Ext) -> assoc::ProgId {
|
||||||
pub fn link_ext(&self, ext: Token) -> Result<()> {
|
let vendor = utilities::capitalize_first_ascii(&self.identifier);
|
||||||
todo!()
|
let component = utilities::capitalize_first_ascii(ext.inner());
|
||||||
}
|
assoc::ProgId::Loose(assoc::LosseProgId::new(&vendor, &component))
|
||||||
|
|
||||||
/// 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!()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
//! The module containing useful stuff used in this crate.
|
//! The module containing useful stuff used in this crate.
|
||||||
|
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
|
use std::iter::FusedIterator;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use thiserror::Error as TeError;
|
use thiserror::Error as TeError;
|
||||||
|
|
||||||
@ -117,3 +118,51 @@ pub fn osstr_to_str(osstr: &OsStr) -> Result<&str, CastOsStrError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// 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