Implement open,get,set

This commit is contained in:
Greg 2020-04-08 15:53:00 -04:00
parent b49441940e
commit 0da454a557
No known key found for this signature in database
GPG key ID: 2E44FAEEDC94B1E2
7 changed files with 465 additions and 105 deletions

0
.rustfmt.toml Normal file
View file

258
Cargo.lock generated
View file

@ -9,6 +9,15 @@ dependencies = [
"const-random",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
dependencies = [
"winapi",
]
[[package]]
name = "anyhow"
version = "1.0.28"
@ -27,6 +36,17 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.0.0"
@ -39,6 +59,12 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "blake2b_simd"
version = "0.5.10"
@ -89,6 +115,21 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "clap"
version = "2.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "const-random"
version = "0.1.8"
@ -173,6 +214,17 @@ dependencies = [
"num_cpus",
]
[[package]]
name = "derive_more"
version = "0.99.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2323f3f47db9a0e77ce7a300605d8d2098597fc451ed1a97bb1f6411bb550a7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "digest"
version = "0.8.1"
@ -236,6 +288,15 @@ dependencies = [
"wasi",
]
[[package]]
name = "heck"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hermit-abi"
version = "0.1.10"
@ -245,6 +306,26 @@ dependencies = [
"libc",
]
[[package]]
name = "idna"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
dependencies = [
"matches",
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "itertools"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
dependencies = [
"either",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -263,6 +344,12 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "matches"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
[[package]]
name = "maybe-uninit"
version = "2.0.0"
@ -284,10 +371,31 @@ version = "0.1.0"
dependencies = [
"anyhow",
"dashmap",
"derive_more",
"dirs",
"itertools",
"mime_guess",
"pest",
"pest_derive",
"rayon",
"structopt",
"url",
]
[[package]]
name = "mime"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "mime_guess"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212"
dependencies = [
"mime 0.3.16",
"unicase",
]
[[package]]
@ -306,6 +414,12 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
[[package]]
name = "percent-encoding"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pest"
version = "2.1.3"
@ -349,6 +463,32 @@ dependencies = [
"sha-1",
]
[[package]]
name = "proc-macro-error"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn-mid",
"version_check",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.15"
@ -444,6 +584,42 @@ dependencies = [
"opaque-debug",
]
[[package]]
name = "smallvec"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "structopt"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8faa2719539bbe9d77869bfb15d4ee769f99525e707931452c97b693b3f159d"
dependencies = [
"clap",
"lazy_static",
"structopt-derive",
]
[[package]]
name = "structopt-derive"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f88b8e18c69496aad6f9ddf4630dd7d585bcaf765786cb415b9aec2fe5a0430"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "1.0.17"
@ -455,6 +631,26 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "syn-mid"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "typenum"
version = "1.11.2"
@ -467,12 +663,74 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
"version_check",
]
[[package]]
name = "unicode-bidi"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
dependencies = [
"matches",
]
[[package]]
name = "unicode-normalization"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4"
dependencies = [
"smallvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
[[package]]
name = "unicode-width"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
[[package]]
name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
[[package]]
name = "url"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
dependencies = [
"idna",
"matches",
"percent-encoding",
]
[[package]]
name = "vec_map"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
[[package]]
name = "version_check"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"

View file

@ -13,3 +13,8 @@ pest = "2.1.3"
pest_derive = "2.1.0"
rayon = "1.3.0"
dashmap = "3.10.0"
structopt = "0.3.12"
url = "2.1.1"
mime_guess = "2.0.3"
itertools = "0.9.0"
derive_more = {version = "0.99.5", default-features = false, features = ["display"] }

View file

@ -3,12 +3,58 @@ use pest::Parser;
use std::convert::TryFrom;
use std::path::PathBuf;
#[derive(Clone, Hash, PartialEq, Eq)]
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Mime(pub String);
impl std::fmt::Debug for Mime {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
impl std::str::FromStr for Mime {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.to_owned()))
}
}
#[derive(Debug, derive_more::Display, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Handler(String);
impl std::str::FromStr for Handler {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::resolve(s.to_owned())
}
}
impl Handler {
pub fn assume_valid(name: String) -> Self {
Self(name)
}
pub fn get_path(name: &str) -> Option<PathBuf> {
let locally = {
let mut local_dir = dirs::data_dir().unwrap();
local_dir.push("applications");
local_dir.push(name);
Some(local_dir).filter(|p| p.exists())
};
let system = {
let mut sys = std::path::PathBuf::from("/usr/share/applications");
sys.push(name);
Some(sys).filter(|p| p.exists())
};
locally.or(system)
}
pub fn resolve(name: String) -> Result<Self> {
let path =
Self::get_path(&name).ok_or_else(|| anyhow::Error::msg("Handler does not exist"))?;
DesktopEntry::try_from(path)?;
Ok(Self(name))
}
pub fn get_entry(&self) -> Result<DesktopEntry> {
DesktopEntry::try_from(Self::get_path(&self.0).unwrap())
}
pub fn run(&self, arg: &str) -> Result<()> {
std::process::Command::new("gtk-launch")
.args(&[self.0.as_str(), arg])
.stdout(std::process::Stdio::null())
.spawn()?;
Ok(())
}
}
@ -17,16 +63,18 @@ impl std::fmt::Debug for Mime {
pub struct DesktopEntry {
pub(crate) name: String,
pub(crate) exec: String,
pub(crate) file_name: String,
pub(crate) mimes: Vec<Mime>,
}
impl TryFrom<PathBuf> for DesktopEntry {
type Error = anyhow::Error;
fn try_from(p: PathBuf) -> Result<DesktopEntry> {
let raw = std::fs::read_to_string(p)?;
let raw = std::fs::read_to_string(&p)?;
let file = Self::parse(Rule::file, &raw)?.next().unwrap();
let mut entry = Self::default();
entry.file_name = p.file_name().unwrap().to_str().unwrap().to_owned();
for line in file.into_inner() {
match line.as_rule() {

View file

@ -1,27 +1,45 @@
use anyhow::Result;
use structopt::StructOpt;
mod common;
mod mimeapps;
mod systemapps;
pub use common::{DesktopEntry, Mime};
pub use common::{DesktopEntry, Handler, Mime};
#[derive(StructOpt)]
enum Options {
List,
Open { path: String },
Get { mime: Mime },
Set { mime: Mime, handler: Handler },
}
fn main() -> Result<()> {
let mut args: Vec<_> = std::env::args().collect();
let user = mimeapps::MimeApps::read()?;
let sys = systemapps::SystemApps::populate()?;
let cmd = Options::from_args();
let mime = Mime(args.remove(1));
let mut user = mimeapps::MimeApps::read()?;
let user_handler = user.get_handler(&mime);
match user_handler {
Some(h) => {
dbg!(&h);
match cmd {
Options::Set { mime, handler } => {
user.set_handler(mime, handler)?;
}
None => {
let s = sys.get_handlers(&mime);
dbg!(&s);
Options::Get { mime } => {
println!("{}", user.get_handler(&mime)?);
}
Options::Open { path } => match url::Url::parse(&path) {
Ok(url) => {
let mime = Mime(format!("x-scheme-handler/{}", url.scheme()));
user.get_handler(&mime)?.run(&path)?;
}
Err(_) => {
let guess = mime_guess::from_path(&path)
.first_raw()
.ok_or_else(|| anyhow::Error::msg("Could not determine mime type"))?;
user.get_handler(&Mime(guess.to_owned()))?.run(&path)?;
}
},
_ => {}
};
Ok(())
}

View file

@ -1,62 +1,55 @@
use crate::{DesktopEntry, Mime};
use crate::{DesktopEntry, Handler, Mime};
use anyhow::Result;
use dashmap::DashMap;
use pest::Parser;
use std::collections::HashMap;
use std::collections::{HashMap, VecDeque};
use std::path::PathBuf;
#[derive(Debug, pest_derive::Parser, Default)]
#[derive(Debug, pest_derive::Parser)]
#[grammar = "ini.pest"]
pub struct MimeApps {
added_associations: HashMap<Mime, Vec<PathBuf>>,
default_apps: HashMap<Mime, Vec<PathBuf>>,
}
fn handler_exists(name: &str) -> Option<std::path::PathBuf> {
let locally = {
let mut local_dir = dirs::data_dir().unwrap();
local_dir.push("applications");
local_dir.push(name);
Some(local_dir).filter(|p| p.exists())
};
let system = {
let mut sys = std::path::PathBuf::from("/usr/share/applications");
sys.push(name);
Some(sys).filter(|p| p.exists())
};
locally.or(system)
added_associations: HashMap<Mime, VecDeque<Handler>>,
default_apps: HashMap<Mime, VecDeque<Handler>>,
system_apps: SystemApps,
}
impl MimeApps {
pub fn get_handler(&self, mime: &Mime) -> Option<DesktopEntry> {
use std::convert::TryFrom;
Some(
self.default_apps
.get(mime)
.or_else(|| self.added_associations.get(mime))
.map(|hs| hs.get(0).unwrap().clone())
.map(DesktopEntry::try_from)
.map(Result::ok)
.flatten()?
.clone(),
)
pub fn set_handler(&mut self, mime: Mime, handler: Handler) -> Result<()> {
let handlers = self.default_apps.entry(mime).or_default();
handlers.push_front(handler);
self.print()?;
Ok(())
}
pub fn read() -> Result<Self> {
let path = dirs::config_dir()
pub fn get_handler(&self, mime: &Mime) -> Result<Handler> {
Ok(self
.default_apps
.get(mime)
.or_else(|| self.added_associations.get(mime))
.map(|hs| hs.get(0).unwrap().clone())
.or_else(|| self.system_apps.get_handler(mime))
.ok_or(anyhow::Error::msg("No handlers found"))?)
}
pub fn path() -> Result<PathBuf> {
dirs::config_dir()
.map(|mut data_dir| {
data_dir.push("mimeapps.list");
data_dir
})
.ok_or_else(|| anyhow::Error::msg("Could not determine xdg data dir"))?;
let raw_conf = std::fs::read_to_string(path)?;
.ok_or_else(|| anyhow::Error::msg("Could not determine xdg data dir"))
}
pub fn read() -> Result<Self> {
let raw_conf = std::fs::read_to_string(Self::path()?)?;
let file = Self::parse(Rule::file, &raw_conf)
.expect("unsuccessful parse") // unwrap the parse result
.next()
.unwrap();
let mut current_section_name = "".to_string();
let mut conf = Self::default();
let mut conf = Self {
added_associations: HashMap::default(),
default_apps: HashMap::default(),
system_apps: SystemApps::populate()?,
};
file.into_inner().for_each(|line| {
match line.as_rule() {
@ -67,23 +60,29 @@ impl MimeApps {
let mut inner_rules = line.into_inner(); // { name ~ "=" ~ value }
let name = inner_rules.next().unwrap().as_str();
let mut handlers = inner_rules
.next()
.unwrap()
.as_str()
.split(";")
.filter_map(handler_exists)
.collect::<Vec<_>>();
handlers.pop();
let handlers = {
use itertools::Itertools;
use std::str::FromStr;
inner_rules
.next()
.unwrap()
.as_str()
.split(";")
.filter(|s| !s.is_empty())
.unique()
.filter_map(|s| Handler::from_str(s).ok())
.collect::<VecDeque<_>>()
};
if !handlers.is_empty() {
match current_section_name.as_str() {
"Added Associations" => conf
.added_associations
.insert(Mime(name.to_owned()), handlers.to_owned()),
"Default Applications" => conf
.default_apps
.insert(Mime(name.to_owned()), handlers.to_owned()),
.insert(Mime(name.to_owned()), handlers),
"Default Applications" => {
conf.default_apps.insert(Mime(name.to_owned()), handlers)
}
_ => None,
};
}
@ -94,4 +93,71 @@ impl MimeApps {
Ok(conf)
}
pub fn print(&self) -> Result<()> {
use itertools::Itertools;
use std::io::{prelude::*, BufWriter};
let f = std::fs::OpenOptions::new()
.read(true)
.write(true)
.truncate(true)
.open(Self::path()?)?;
let mut writer = BufWriter::new(f);
writer.write_all(b"[Added Associations]\n")?;
for (k, v) in self.added_associations.iter().sorted() {
writer.write_all(k.0.as_ref())?;
writer.write_all(b"=")?;
writer.write_all(v.iter().join(";").as_ref())?;
writer.write_all(b";\n")?;
}
writer.write_all(b"\n[Default Applications]\n")?;
for (k, v) in self.default_apps.iter().sorted() {
writer.write_all(k.0.as_ref())?;
writer.write_all(b"=")?;
writer.write_all(v.iter().join(";").as_ref())?;
writer.write_all(b";\n")?;
}
writer.flush()?;
Ok(())
}
}
#[derive(Debug)]
pub struct SystemApps(pub DashMap<Mime, Vec<Handler>>);
impl SystemApps {
pub fn get_handlers(&self, mime: &Mime) -> Option<Vec<Handler>> {
Some(self.0.get(mime)?.value().clone())
}
pub fn get_handler(&self, mime: &Mime) -> Option<Handler> {
Some(self.get_handlers(mime)?.get(0).unwrap().clone())
}
pub fn populate() -> Result<Self> {
use rayon::iter::ParallelBridge;
use rayon::prelude::ParallelIterator;
use std::convert::TryFrom;
let map = DashMap::<Mime, Vec<Handler>>::with_capacity(50);
std::fs::read_dir("/usr/share/applications")?
.par_bridge()
.filter_map(|path| {
path.ok()
.map(|p| DesktopEntry::try_from(p.path()).ok())
.flatten()
})
.for_each(|entry| {
let (file_name, mimes) = (entry.file_name, entry.mimes);
mimes.into_iter().for_each(|mime| {
map.entry(mime)
.or_default()
.push(Handler::assume_valid(file_name.clone()));
});
});
Ok(Self(map))
}
}

View file

@ -1,39 +1,4 @@
use crate::{DesktopEntry, Mime};
use crate::{DesktopEntry, Handler, Mime};
use anyhow::Result;
use dashmap::DashMap;
use std::convert::TryFrom;
#[derive(Debug)]
pub struct SystemApps(pub DashMap<Mime, Vec<DesktopEntry>>);
impl SystemApps {
pub fn get_handlers(&self, mime: &Mime) -> Option<Vec<DesktopEntry>> {
Some(self.0.get(mime)?.value().clone())
}
pub fn populate() -> Result<Self> {
use rayon::iter::ParallelBridge;
use rayon::prelude::ParallelIterator;
let map = DashMap::<Mime, Vec<DesktopEntry>>::with_capacity(50);
std::fs::read_dir("/usr/share/applications")?
.par_bridge()
.filter_map(|path| {
path.ok()
.map(|p| DesktopEntry::try_from(p.path()).ok())
.flatten()
})
.for_each(|entry| {
let (name, exec, mimes) = (entry.name, entry.exec, entry.mimes);
mimes.into_iter().for_each(|mime| {
map.entry(mime).or_default().push(DesktopEntry {
name: name.clone(),
exec: exec.clone(),
mimes: Vec::new(),
});
});
});
Ok(Self(map))
}
}