From 935d1daa8700a0b3545a953a48f11b925f0f6f5a Mon Sep 17 00:00:00 2001 From: Gregory <gregory.mkv@gmail.com> Date: Sat, 6 Jun 2020 00:24:32 -0400 Subject: [PATCH] Reorganize and partially rewrite --- Cargo.lock | 397 ++++++++++++++++++++++------ Cargo.toml | 4 +- src/apps/mod.rs | 5 + src/apps/system.rs | 47 ++++ src/{mimeapps.rs => apps/user.rs} | 82 ++---- src/common.rs | 188 ------------- src/{mime_types.rs => common/db.rs} | 30 +-- src/common/desktop_entry.rs | 129 +++++++++ src/common/handler.rs | 41 +++ src/{ => common}/ini.pest | 0 src/common/mime_types.rs | 80 ++++++ src/common/mod.rs | 9 + src/error.rs | 26 +- src/main.rs | 135 ++++++---- 14 files changed, 749 insertions(+), 424 deletions(-) create mode 100644 src/apps/mod.rs create mode 100644 src/apps/system.rs rename src/{mimeapps.rs => apps/user.rs} (69%) delete mode 100644 src/common.rs rename src/{mime_types.rs => common/db.rs} (51%) create mode 100644 src/common/desktop_entry.rs create mode 100644 src/common/handler.rs rename src/{ => common}/ini.pest (100%) create mode 100644 src/common/mime_types.rs create mode 100644 src/common/mod.rs diff --git a/Cargo.lock b/Cargo.lock index f111698..23ceae2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,6 +50,18 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" +[[package]] +name = "base64" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42" + +[[package]] +name = "bitflags" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" + [[package]] name = "bitflags" version = "1.2.1" @@ -67,6 +79,12 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + [[package]] name = "block-buffer" version = "0.7.3" @@ -90,9 +108,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5356f1d23ee24a1f785a56d1d1a5f0fd5b0f6a0c0fb2412ce11da71649ab78f6" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" [[package]] name = "byte-tools" @@ -124,6 +142,17 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" +[[package]] +name = "chrono" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" +dependencies = [ + "num-integer", + "num-traits", + "time", +] + [[package]] name = "clap" version = "3.0.0-beta.1" @@ -131,7 +160,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "860643c53f980f0d38a5e25dfab6c3c93b2cb3aa1fe192643d17a293c6c41936" dependencies = [ "atty", - "bitflags", + "bitflags 1.2.1", "clap_derive", "indexmap", "lazy_static", @@ -152,8 +181,8 @@ dependencies = [ "heck", "proc-macro-error", "proc-macro2", - "quote", - "syn", + "quote 1.0.6", + "syn 1.0.30", ] [[package]] @@ -189,6 +218,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "dbus" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b17a12ffaff26515889b006fc029493a3e340366a137c13cec2cdd545ea3b8" +dependencies = [ + "libc", + "libdbus-sys", +] + [[package]] name = "digest" version = "0.8.1" @@ -198,6 +237,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "dirs" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" +dependencies = [ + "libc", + "redox_users", + "winapi 0.3.8", +] + [[package]] name = "dirs" version = "2.0.2" @@ -274,7 +324,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" dependencies = [ - "bitflags", + "bitflags 1.2.1", "fuchsia-zircon-sys", ] @@ -373,11 +423,13 @@ name = "handlr" version = "0.3.2" dependencies = [ "ascii_table", + "atty", "clap", "itertools", "json", "mime", "mime-db", + "notify-rust", "once_cell", "pest", "pest_derive", @@ -436,9 +488,9 @@ checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" [[package]] name = "hyper" -version = "0.13.5" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96816e1d921eca64d208a85aab4f7798455a8e34229ee5a88c935bdee1b78b14" +checksum = "a6e7655b9594024ad0ee439f3b5a7299369dc2a3f459b47c696f9ff676f9aa1f" dependencies = [ "bytes", "futures-channel", @@ -450,8 +502,8 @@ dependencies = [ "httparse", "itoa", "log", - "net2", "pin-project", + "socket2", "time", "tokio", "tower-service", @@ -484,9 +536,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" +checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe" dependencies = [ "autocfg", ] @@ -517,9 +569,9 @@ checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" [[package]] name = "js-sys" -version = "0.3.39" +version = "0.3.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa5a448de267e7358beaf4a5d849518fe9a0c13fce7afd44b06e68550e5562a7" +checksum = "ce10c23ad2ea25ceca0093bd3192229da4c5b3c0f2de499c1ecac0d98d452177" dependencies = [ "wasm-bindgen", ] @@ -553,7 +605,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f86d66d380c9c5a685aaac7a11818bdfa1f733198dfd9ec09c70b762cd12ad6f" dependencies = [ "arrayvec 0.4.12", - "bitflags", + "bitflags 1.2.1", "cfg-if", "rustc_version", "ryu", @@ -562,9 +614,18 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.70" +version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f" +checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" + +[[package]] +name = "libdbus-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc12a3bc971424edbbf7edaf6e5740483444db63aa8e23d3751ff12a30f306f0" +dependencies = [ + "pkg-config", +] [[package]] name = "log" @@ -575,6 +636,27 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "mac-notification-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb6b71a9a89cd38b395d994214297447e8e63b1ba5708a9a2b0b1048ceda76" +dependencies = [ + "cc", + "chrono", + "dirs 1.0.5", + "objc-foundation", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + [[package]] name = "maplit" version = "1.0.2" @@ -698,6 +780,36 @@ dependencies = [ "version_check", ] +[[package]] +name = "notify-rust" +version = "4.0.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "963d07a7dc24a56e2e0036d444b37caf4f0418cfd31c4b4b018ffc92cc06b56b" +dependencies = [ + "dbus", + "mac-notification-sys", + "winrt-notification", +] + +[[package]] +name = "num-integer" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.13.0" @@ -708,6 +820,35 @@ dependencies = [ "libc", ] +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + [[package]] name = "once_cell" version = "1.4.0" @@ -726,7 +867,7 @@ version = "0.10.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cee6d85f4cb4c4f59a6a85d5b68a233d280c82e29e822913b9c8b129fbf20bdd" dependencies = [ - "bitflags", + "bitflags 1.2.1", "cfg-if", "foreign-types", "lazy_static", @@ -793,8 +934,8 @@ dependencies = [ "pest", "pest_meta", "proc-macro2", - "quote", - "syn", + "quote 1.0.6", + "syn 1.0.30", ] [[package]] @@ -810,29 +951,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "0.4.17" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc93aeee735e60ecb40cf740eb319ff23eab1c5748abfdb5c180e4ce49f7791" +checksum = "ba3a1acf4a3e70849f8a673497ef984f043f95d2d8252dcdf74d54e6a1e47e8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "0.4.17" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40" +checksum = "194e88048b71a3e02eb4ee36a6995fed9b8236c11a7bb9f7247a9d9835b3f265" dependencies = [ "proc-macro2", - "quote", - "syn", + "quote 1.0.6", + "syn 1.0.30", ] [[package]] name = "pin-project-lite" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7505eeebd78492e0f6108f7171c4948dbb120ee8119d9d77d0afa5469bef67f" +checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715" [[package]] name = "pin-utils" @@ -860,8 +1001,8 @@ checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7" dependencies = [ "proc-macro-error-attr", "proc-macro2", - "quote", - "syn", + "quote 1.0.6", + "syn 1.0.30", "version_check", ] @@ -872,21 +1013,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de" dependencies = [ "proc-macro2", - "quote", - "syn", + "quote 1.0.6", + "syn 1.0.30", "syn-mid", "version_check", ] [[package]] name = "proc-macro2" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" dependencies = [ - "unicode-xid", + "unicode-xid 0.2.0", ] +[[package]] +name = "quote" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" + [[package]] name = "quote" version = "1.0.6" @@ -980,11 +1127,11 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.10.4" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b81e49ddec5109a9dcfc5f2a317ff53377c915e9ae9d4f2fb50914b85614e2" +checksum = "3b82c9238b305f26f53443e3a4bc8528d64b8d0bee408ec949eb7bf5635ec680" dependencies = [ - "base64", + "base64 0.12.1", "bytes", "encoding_rs", "futures-core", @@ -1004,7 +1151,6 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "time", "tokio", "tokio-tls", "url", @@ -1020,7 +1166,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" dependencies = [ - "base64", + "base64 0.11.0", "blake2b_simd", "constant_time_eq", "crossbeam-utils", @@ -1037,9 +1183,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "schannel" @@ -1057,7 +1203,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535" dependencies = [ - "bitflags", + "bitflags 1.2.1", "core-foundation", "core-foundation-sys", "libc", @@ -1091,22 +1237,22 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.110" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" +checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.110" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" +checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250" dependencies = [ "proc-macro2", - "quote", - "syn", + "quote 1.0.6", + "syn 1.0.30", ] [[package]] @@ -1162,6 +1308,18 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" +[[package]] +name = "socket2" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "winapi 0.3.8", +] + [[package]] name = "static_assertions" version = "0.3.4" @@ -1175,14 +1333,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] -name = "syn" -version = "1.0.23" +name = "strum" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b5f192649e48a5302a13f2feb224df883b98933222369e4b3b0fe2a5447269" +checksum = "4ca6e4730f517e041e547ffe23d29daab8de6b73af4b6ae2a002108169f5e7da" + +[[package]] +name = "strum_macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3384590878eb0cab3b128e844412e2d010821e7e091211b9d87324173ada7db8" +dependencies = [ + "quote 0.3.15", + "syn 0.11.11", +] + +[[package]] +name = "syn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" +dependencies = [ + "quote 0.3.15", + "synom", + "unicode-xid 0.0.4", +] + +[[package]] +name = "syn" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93a56fabc59dce20fe48b6c832cc249c713e7ed88fa28b0ee0a3bfcaae5fe4e2" dependencies = [ "proc-macro2", - "quote", - "unicode-xid", + "quote 1.0.6", + "unicode-xid 0.2.0", ] [[package]] @@ -1192,8 +1377,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" dependencies = [ "proc-macro2", - "quote", - "syn", + "quote 1.0.6", + "syn 1.0.30", +] + +[[package]] +name = "synom" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +dependencies = [ + "unicode-xid 0.0.4", ] [[package]] @@ -1244,8 +1438,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479" dependencies = [ "proc-macro2", - "quote", - "syn", + "quote 1.0.6", + "syn 1.0.30", ] [[package]] @@ -1284,8 +1478,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389" dependencies = [ "proc-macro2", - "quote", - "syn", + "quote 1.0.6", + "syn 1.0.30", ] [[package]] @@ -1375,6 +1569,12 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" +[[package]] +name = "unicode-xid" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" + [[package]] name = "unicode-xid" version = "0.2.0" @@ -1394,9 +1594,9 @@ dependencies = [ [[package]] name = "vcpkg" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" +checksum = "55d1e41d56121e07f1e223db0a4def204e45c85425f6a16d462fd07c8d10d74c" [[package]] name = "vec_map" @@ -1428,9 +1628,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasm-bindgen" -version = "0.2.62" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c7d40d09cdbf0f4895ae58cf57d92e1e57a9dd8ed2e8390514b54a47cc5551" +checksum = "4c2dc4aa152834bc334f506c1a06b866416a8b6697d5c9f75b9a689c8486def0" dependencies = [ "cfg-if", "serde", @@ -1440,24 +1640,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.62" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3972e137ebf830900db522d6c8fd74d1900dcfc733462e9a12e942b00b4ac94" +checksum = "ded84f06e0ed21499f6184df0e0cb3494727b0c5da89534e0fcc55c51d812101" dependencies = [ "bumpalo", "lazy_static", "log", "proc-macro2", - "quote", - "syn", + "quote 1.0.6", + "syn 1.0.30", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a369c5e1dfb7569e14d62af4da642a3cbc2f9a3652fe586e26ac22222aa4b04" +checksum = "64487204d863f109eb77e8462189d111f27cb5712cc9fdb3461297a76963a2f6" dependencies = [ "cfg-if", "js-sys", @@ -1467,38 +1667,38 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.62" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd85aa2c579e8892442954685f0d801f9129de24fa2136b2c6a539c76b65776" +checksum = "838e423688dac18d73e31edce74ddfac468e37b1506ad163ffaf0a46f703ffe3" dependencies = [ - "quote", + "quote 1.0.6", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.62" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb197bd3a47553334907ffd2f16507b4f4f01bbec3ac921a7719e0decdfe72a" +checksum = "3156052d8ec77142051a533cdd686cba889537b213f948cd1d20869926e68e92" dependencies = [ "proc-macro2", - "quote", - "syn", + "quote 1.0.6", + "syn 1.0.30", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.62" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91c2916119c17a8e316507afaaa2dd94b47646048014bbdf6bef098c1bb58ad" +checksum = "c9ba19973a58daf4db6f352eda73dc0e289493cd29fb2632eb172085b6521acd" [[package]] name = "web-sys" -version = "0.3.39" +version = "0.3.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bc359e5dd3b46cb9687a051d50a2fdd228e4ba7cf6fcf861a5365c3d671a642" +checksum = "7b72fe77fd39e4bd3eaa4412fd299a0be6b3dfe9d2597e2f1c20beb968f41d17" dependencies = [ "js-sys", "wasm-bindgen", @@ -1549,13 +1749,35 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winreg" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "winrt" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e30cba82e22b083dc5a422c2ee77e20dc7927271a0dc981360c57c1453cb48d" +dependencies = [ + "winapi 0.3.8", +] + +[[package]] +name = "winrt-notification" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c31a65da50d792c6f9bd2e3216249566c4fb1d2d34f9b7d2d66d2e93f62a242" +dependencies = [ + "strum", + "strum_macros", + "winapi 0.3.8", + "winrt", + "xml-rs", +] + [[package]] name = "ws2_32-sys" version = "0.2.1" @@ -1577,9 +1799,18 @@ name = "xdg-mime" version = "0.3.0" source = "git+https://github.com/ebassi/xdg-mime-rs#68f9130f316a67c6fbce440698788b94b45380c5" dependencies = [ - "dirs", + "dirs 2.0.2", "glob", "mime", "nom", "unicase", ] + +[[package]] +name = "xml-rs" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1945e12e16b951721d7976520b0832496ef79c31602c7a29d950de79ba74621" +dependencies = [ + "bitflags 0.9.1", +] diff --git a/Cargo.toml b/Cargo.toml index 66aefab..082582c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ description = "Manage mimeapps.list and default applications with ease" [dependencies] pest = "2.1.3" pest_derive = "2.1.0" -clap = { version = "3.0.0-beta.1" } +clap = "3.0.0-beta.1" url = "2.1.1" itertools = "0.9.0" json = "0.12.4" @@ -22,6 +22,8 @@ once_cell = "1.4.0" regex = { version = "1.3.9", default-features = false, features = ["std"] } mime-db = "0.1.5" xdg-mime = { git = "https://github.com/ebassi/xdg-mime-rs" } +atty = "0.2.14" +notify-rust = "4.0.0-rc.1" [profile.release] opt-level=3 diff --git a/src/apps/mod.rs b/src/apps/mod.rs new file mode 100644 index 0000000..57f7b93 --- /dev/null +++ b/src/apps/mod.rs @@ -0,0 +1,5 @@ +mod system; +mod user; + +pub use system::SystemApps; +pub use user::MimeApps; diff --git a/src/apps/system.rs b/src/apps/system.rs new file mode 100644 index 0000000..4d07339 --- /dev/null +++ b/src/apps/system.rs @@ -0,0 +1,47 @@ +use crate::{ + common::{DesktopEntry, Handler}, + Result, +}; +use mime::Mime; +use std::{collections::HashMap, convert::TryFrom, ffi::OsStr}; + +#[derive(Debug)] +pub struct SystemApps(pub HashMap<Mime, Vec<Handler>>); + +impl SystemApps { + pub fn get_handlers(&self, mime: &Mime) -> Option<Vec<Handler>> { + Some(self.0.get(mime)?.clone()) + } + pub fn get_handler(&self, mime: &Mime) -> Option<Handler> { + Some(self.get_handlers(mime)?.get(0).unwrap().clone()) + } + pub fn populate() -> Result<Self> { + let mut map = HashMap::<Mime, Vec<Handler>>::with_capacity(50); + + xdg::BaseDirectories::new()? + .get_data_dirs() + .into_iter() + .map(|mut data_dir| { + data_dir.push("applications"); + data_dir + }) + .filter_map(|data_dir| std::fs::read_dir(data_dir).ok()) + .for_each(|dir| { + dir.filter_map(Result::ok) + .filter(|p| { + p.path().extension() == Some(OsStr::new("desktop")) + }) + .filter_map(|p| DesktopEntry::try_from(p.path()).ok()) + .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)) + } +} diff --git a/src/mimeapps.rs b/src/apps/user.rs similarity index 69% rename from src/mimeapps.rs rename to src/apps/user.rs index b0aee22..5aacf4a 100644 --- a/src/mimeapps.rs +++ b/src/apps/user.rs @@ -1,12 +1,14 @@ -use crate::{DesktopEntry, Error, Handler, Mime, Result}; +use crate::{apps::SystemApps, DesktopEntry, Error, Handler, Result}; +use mime::Mime; use pest::Parser; use std::{ collections::{HashMap, VecDeque}, path::PathBuf, + str::FromStr, }; #[derive(Debug, pest_derive::Parser)] -#[grammar = "ini.pest"] +#[grammar = "common/ini.pest"] pub struct MimeApps { added_associations: HashMap<Mime, VecDeque<Handler>>, default_apps: HashMap<Mime, VecDeque<Handler>>, @@ -42,7 +44,7 @@ impl MimeApps { .or_else(|| self.added_associations.get(mime)) .map(|hs| hs.get(0).unwrap().clone()) .or_else(|| self.system_apps.get_handler(mime)) - .ok_or(Error::NotFound) + .ok_or(Error::NotFound(mime.to_string())) } pub fn show_handler(&self, mime: &Mime, output_json: bool) -> Result<()> { let handler = self.get_handler(mime)?; @@ -90,7 +92,6 @@ impl MimeApps { let name = inner_rules.next().unwrap().as_str(); let handlers = { use itertools::Itertools; - use std::str::FromStr; inner_rules .next() @@ -104,13 +105,17 @@ impl MimeApps { }; if !handlers.is_empty() { - match current_section_name.as_str() { - "Added Associations" => conf - .added_associations - .insert(Mime(name.to_owned()), handlers), - "Default Applications" => conf - .default_apps - .insert(Mime(name.to_owned()), handlers), + match ( + Mime::from_str(name), + current_section_name.as_str(), + ) { + (Ok(mime), "Added Associations") => { + conf.added_associations.insert(mime, handlers) + } + + (Ok(mime), "Default Applications") => { + conf.default_apps.insert(mime, handlers) + } _ => None, }; } @@ -134,7 +139,7 @@ impl MimeApps { 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(k.essence_str().as_ref())?; writer.write_all(b"=")?; writer.write_all(v.iter().join(";").as_ref())?; writer.write_all(b";\n")?; @@ -142,7 +147,7 @@ impl MimeApps { 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(k.essence_str().as_ref())?; writer.write_all(b"=")?; writer.write_all(v.iter().join(";").as_ref())?; writer.write_all(b";\n")?; @@ -158,7 +163,7 @@ impl MimeApps { .default_apps .iter() .sorted() - .map(|(k, v)| vec![k.0.clone(), v.iter().join(", ")]) + .map(|(k, v)| vec![k.to_string(), v.iter().join(", ")]) .collect::<Vec<_>>(); ascii_table::AsciiTable::default().print(rows); @@ -166,7 +171,7 @@ impl MimeApps { Ok(()) } pub fn list_handlers(&self) -> Result<()> { - use std::{convert::TryFrom, io::Write}; + use std::{convert::TryFrom, io::Write, os::unix::ffi::OsStrExt}; let stdout = std::io::stdout(); let mut stdout = stdout.lock(); @@ -188,50 +193,3 @@ impl MimeApps { Ok(()) } } - -#[derive(Debug)] -pub struct SystemApps(pub HashMap<Mime, Vec<Handler>>); - -impl SystemApps { - pub fn get_handlers(&self, mime: &Mime) -> Option<Vec<Handler>> { - Some(self.0.get(mime)?.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 std::convert::TryFrom; - - let mut map = HashMap::<Mime, Vec<Handler>>::with_capacity(50); - - xdg::BaseDirectories::new()? - .get_data_dirs() - .into_iter() - .map(|mut data_dir| { - data_dir.push("applications"); - data_dir - }) - .filter_map(|data_dir| std::fs::read_dir(data_dir).ok()) - .for_each(|dir| { - dir.filter_map(Result::ok) - .filter(|p| { - p.path() - .extension() - .map(std::ffi::OsStr::to_str) - .flatten() - == Some("desktop") - }) - .filter_map(|p| DesktopEntry::try_from(p.path()).ok()) - .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)) - } -} diff --git a/src/common.rs b/src/common.rs deleted file mode 100644 index ccc2029..0000000 --- a/src/common.rs +++ /dev/null @@ -1,188 +0,0 @@ -use crate::{mime_types, Error, Result}; -use std::{convert::TryFrom, path::PathBuf}; - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct Mime(pub String); - -impl Mime { - pub fn try_from_path(path: &str) -> Result<Self> { - if let Ok(url) = url::Url::parse(path) { - return Ok(Mime(format!("x-scheme-handler/{}", url.scheme()))); - } - - mime_types::from_file(path) - } -} - -impl std::str::FromStr for Mime { - type Err = Error; - fn from_str(s: &str) -> Result<Self, Self::Err> { - if s.starts_with(".") { - mime_types::from_file(s) - } else { - Ok(Mime(mime_types::verify(&s)?.to_owned())) - } - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct Handler(String); - -impl std::fmt::Display for Handler { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.0) - } -} - -impl std::str::FromStr for Handler { - type Err = 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> { - xdg::BaseDirectories::new() - .ok()? - .find_data_file(&format!("applications/{}", name)) - } - pub fn resolve(name: String) -> Result<Self> { - let path = Self::get_path(&name).ok_or(Error::NotFound)?; - 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 open(&self, arg: String) -> Result<()> { - let (cmd, args) = self.get_entry()?.get_cmd(Some(arg))?; - if self.get_entry()?.term { - std::process::Command::new(cmd) - .args(args) - .status()?; - } else { - std::process::Command::new(cmd) - .args(args) - .stdout(std::process::Stdio::null()) - .spawn()?; - } - Ok(()) - } - pub fn launch(&self, args: Vec<String>) -> Result<()> { - let (cmd, mut base_args) = self.get_entry()?.get_cmd(None)?; - base_args.extend_from_slice(&args); - if self.get_entry()?.term { - std::process::Command::new(cmd) - .args(base_args) - .status()?; - } else { - std::process::Command::new(cmd) - .args(base_args) - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()) - .spawn()?; - } - Ok(()) - } -} - -#[derive(Debug, Clone, pest_derive::Parser, Default, PartialEq, Eq)] -#[grammar = "ini.pest"] -pub struct DesktopEntry { - pub(crate) name: String, - pub(crate) exec: String, - pub(crate) file_name: String, - pub(crate) term: bool, - pub(crate) mimes: Vec<Mime>, -} - -impl DesktopEntry { - pub fn get_cmd( - &self, - arg: Option<String>, - ) -> Result<(String, Vec<String>)> { - let special = regex::Regex::new("%(f|F|u|U)").unwrap(); - let mut split = shlex::split(&self.exec) - .ok_or(Error::BadCmd)? - .into_iter() - .map(|s| match s.as_str() { - "%f" | "%F" | "%u" | "%U" => arg.clone(), - s if special.is_match(s) => Some( - special - .replace_all(s, arg.as_deref().unwrap_or_default()) - .into(), - ), - _ => Some(s), - }) - .filter_map(std::convert::identity) - .collect::<Vec<_>>(); - - Ok((split.remove(0), split)) - } -} - -impl TryFrom<PathBuf> for DesktopEntry { - type Error = Error; - fn try_from(p: PathBuf) -> Result<DesktopEntry> { - use pest::Parser; - let raw = std::fs::read_to_string(&p)?; - let file = Self::parse(Rule::file, &raw)?.next().unwrap(); - - let mut section: &str = Default::default(); - 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() { - Rule::section => { - section = line.into_inner().as_str(); - } - Rule::property if section == "Desktop Entry" => { - let mut inner_rules = line.into_inner(); // { name ~ "=" ~ value } - - let name = inner_rules.next().unwrap().as_str(); - match name { - "Name" if entry.name.is_empty() => { - entry.name = - inner_rules.next().unwrap().as_str().into(); - } - "Exec" => { - entry.exec = - inner_rules.next().unwrap().as_str().into(); - } - "MimeType" => { - let mut mimes = inner_rules - .next() - .unwrap() - .as_str() - .split(";") - .map(ToOwned::to_owned) - .map(Mime) - .collect::<Vec<_>>(); - mimes.pop(); - entry.mimes = mimes; - } - "Terminal" => { - entry.term = match inner_rules.next().unwrap().as_str() { - "true" => true, - _ => false, - } - } - _ => {} - } - } - _ => {} - } - } - - if !entry.name.is_empty() && !entry.exec.is_empty() { - Ok(entry) - } else { - Err(Error::BadCmd) - } - } -} diff --git a/src/mime_types.rs b/src/common/db.rs similarity index 51% rename from src/mime_types.rs rename to src/common/db.rs index 12056ff..31fccfc 100644 --- a/src/mime_types.rs +++ b/src/common/db.rs @@ -1,9 +1,9 @@ -use crate::{Error, Mime, Result}; +use crate::Result; use once_cell::sync::Lazy; -use std::path::Path; use xdg_mime::SharedMimeInfo; -static SHARED_MIME_DB: Lazy<SharedMimeInfo> = Lazy::new(SharedMimeInfo::new); +pub static SHARED_MIME_DB: Lazy<SharedMimeInfo> = + Lazy::new(SharedMimeInfo::new); static CUSTOM_MIMES: &[&'static str] = &[ "inode/directory", @@ -11,29 +11,7 @@ static CUSTOM_MIMES: &[&'static str] = &[ "x-scheme-handler/https", ]; -pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Mime> { - let guess = SHARED_MIME_DB.guess_mime_type().path(path).guess(); - - match guess.mime_type().essence_str() { - "application/octet-stream" => Err(Error::Ambiguous), - mime => Ok(Mime(mime.to_owned())), - } -} - -pub fn verify(mime: &str) -> Result<&str> { - if mime.starts_with("x-scheme-handler/") || CUSTOM_MIMES.contains(&mime) { - return Ok(mime); - } - - mime_db::TYPES - .iter() - .find(|(m, _, _)| m == &mime) - .ok_or(Error::BadMime(mime.to_owned()))?; - - Ok(mime) -} - -pub fn list() -> Result<()> { +pub fn autocomplete() -> Result<()> { use std::io::Write; let stdout = std::io::stdout(); diff --git a/src/common/desktop_entry.rs b/src/common/desktop_entry.rs new file mode 100644 index 0000000..e580277 --- /dev/null +++ b/src/common/desktop_entry.rs @@ -0,0 +1,129 @@ +use crate::{Error, Result}; +use mime::Mime; +use std::{ + convert::TryFrom, + ffi::OsString, + path::PathBuf, + process::{Command, Stdio}, + str::FromStr, +}; + +#[derive(Debug, Clone, pest_derive::Parser, Default, PartialEq, Eq)] +#[grammar = "common/ini.pest"] +pub struct DesktopEntry { + pub(crate) name: String, + pub(crate) exec: String, + pub(crate) file_name: OsString, + pub(crate) term: bool, + pub(crate) mimes: Vec<Mime>, +} + +impl DesktopEntry { + pub fn exec(&self, arguments: Vec<String>) -> Result<()> { + let supports_multiple = + self.exec.contains("%F") || self.exec.contains("%U"); + if arguments.is_empty() { + self.exec_inner(None)? + } else if supports_multiple { + self.exec_inner(Some(arguments.join(" ")))?; + } else { + for arg in arguments { + self.exec_inner(Some(arg))?; + } + }; + + Ok(()) + } + fn exec_inner(&self, arg: Option<String>) -> Result<()> { + let (cmd, args) = self.get_cmd(arg)?; + let mut cmd = Command::new(cmd); + cmd.args(args); + if self.term { + cmd.status()?; + } else { + cmd.stdout(Stdio::null()).stderr(Stdio::null()).spawn()?; + }; + Ok(()) + } + pub fn get_cmd( + &self, + arg: Option<String>, + ) -> Result<(String, Vec<String>)> { + let special = regex::Regex::new("%(f|F|u|U)").unwrap(); + + let mut split = shlex::split(&self.exec) + .unwrap() + .into_iter() + .filter_map(|s| match s.as_str() { + "%f" | "%F" | "%u" | "%U" => arg.clone(), + s if special.is_match(s) => Some( + special + .replace_all(s, arg.as_deref().unwrap_or_default()) + .into(), + ), + _ => Some(s), + }) + .collect::<Vec<_>>(); + + Ok((split.remove(0), split)) + } +} + +impl TryFrom<PathBuf> for DesktopEntry { + type Error = Error; + fn try_from(p: PathBuf) -> Result<DesktopEntry> { + use pest::Parser; + 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_owned(); + let mut section = ""; + + for line in file.into_inner() { + match line.as_rule() { + Rule::section => { + section = line.into_inner().as_str(); + } + Rule::property if section == "Desktop Entry" => { + let mut inner_rules = line.into_inner(); // { name ~ "=" ~ value } + + let name = inner_rules.next().unwrap().as_str(); + match name { + "Name" if entry.name.is_empty() => { + entry.name = + inner_rules.next().unwrap().as_str().into(); + } + "Exec" => { + entry.exec = + inner_rules.next().unwrap().as_str().into(); + } + "MimeType" => { + let mut mimes = inner_rules + .next() + .unwrap() + .as_str() + .split(";") + .filter_map(|m| Mime::from_str(m).ok()) + .collect::<Vec<_>>(); + mimes.pop(); + entry.mimes = mimes; + } + "Terminal" => { + entry.term = + inner_rules.next().unwrap().as_str() == "true" + } + _ => {} + } + } + _ => {} + } + } + + if !entry.name.is_empty() && !entry.exec.is_empty() { + Ok(entry) + } else { + Err(Error::BadEntry(p.clone())) + } + } +} diff --git a/src/common/handler.rs b/src/common/handler.rs new file mode 100644 index 0000000..e8d76f6 --- /dev/null +++ b/src/common/handler.rs @@ -0,0 +1,41 @@ +use crate::{common::DesktopEntry, Error, Result}; +use std::{convert::TryFrom, ffi::OsString, path::PathBuf}; + +#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct Handler(OsString); + +impl std::fmt::Display for Handler { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.0.to_string_lossy()) + } +} + +impl std::str::FromStr for Handler { + type Err = Error; + fn from_str(s: &str) -> Result<Self, Self::Err> { + Self::resolve(s.into()) + } +} + +impl Handler { + pub fn assume_valid(name: OsString) -> Self { + Self(name) + } + pub fn get_path(name: &std::ffi::OsStr) -> Option<PathBuf> { + let mut path = PathBuf::from("applications"); + path.push(name); + xdg::BaseDirectories::new().ok()?.find_data_file(path) + } + pub fn resolve(name: OsString) -> Result<Self> { + let path = Self::get_path(&name) + .ok_or(Error::NotFound(name.to_string_lossy().into()))?; + 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 launch(&self, args: Vec<String>) -> Result<()> { + self.get_entry()?.exec(args) + } +} diff --git a/src/ini.pest b/src/common/ini.pest similarity index 100% rename from src/ini.pest rename to src/common/ini.pest diff --git a/src/common/mime_types.rs b/src/common/mime_types.rs new file mode 100644 index 0000000..d12fdd3 --- /dev/null +++ b/src/common/mime_types.rs @@ -0,0 +1,80 @@ +use crate::{common::SHARED_MIME_DB, Error, Result}; +use mime::Mime; +use std::{ + convert::TryFrom, + path::{Path, PathBuf}, + str::FromStr, +}; + +// A mime derived from a path or URL +#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct FlexibleMime(pub Mime); + +impl TryFrom<&str> for FlexibleMime { + type Error = Error; + + fn try_from(arg: &str) -> Result<Self> { + if let Ok(url) = url::Url::parse(arg) { + Ok(Self( + format!("x-scheme-handler/{}", url.scheme()) + .parse::<Mime>() + .unwrap(), + )) + } else { + Self::try_from(&*PathBuf::from(arg)) + } + } +} + +impl TryFrom<&Path> for FlexibleMime { + type Error = Error; + fn try_from(path: &Path) -> Result<Self> { + let guess = SHARED_MIME_DB.guess_mime_type().path(path).guess(); + let guess = guess.mime_type().clone(); + + if guess == mime::APPLICATION_OCTET_STREAM { + Err(Error::Ambiguous(path.to_string_lossy().into())) + } else { + Ok(Self(guess)) + } + } +} + +// Mime derived from user input: extension(.pdf) or type like image/jpg +#[derive(Debug)] +pub struct MimeOrExtension(pub Mime); +impl FromStr for MimeOrExtension { + type Err = Error; + fn from_str(s: &str) -> Result<Self> { + if s.starts_with(".") { + Ok(Self(FlexibleMime::try_from(s)?.0)) + } else { + Ok(Self(Mime::from_str(s)?)) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn user_input() { + "image/jpg".parse::<MimeOrExtension>().unwrap(); + ".jpg".parse::<MimeOrExtension>().unwrap(); + "image//jpg".parse::<MimeOrExtension>().unwrap_err(); + "image".parse::<MimeOrExtension>().unwrap_err(); + } + + #[test] + fn from_path_with_extension() { + assert_eq!( + FlexibleMime::try_from(".pdf").unwrap().0, + mime::APPLICATION_PDF + ); + assert_eq!( + FlexibleMime::try_from(".").unwrap().0.essence_str(), + "inode/directory" + ); + } +} diff --git a/src/common/mod.rs b/src/common/mod.rs new file mode 100644 index 0000000..9568d8d --- /dev/null +++ b/src/common/mod.rs @@ -0,0 +1,9 @@ +mod db; +mod desktop_entry; +mod handler; +mod mime_types; + +pub use self::db::{autocomplete as db_autocomplete, SHARED_MIME_DB}; +pub use desktop_entry::{DesktopEntry, Rule as PestRule}; +pub use handler::Handler; +pub use mime_types::{FlexibleMime, MimeOrExtension}; diff --git a/src/error.rs b/src/error.rs index b4de979..08e6cba 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,25 +1,21 @@ #[derive(Debug, thiserror::Error)] pub enum Error { #[error(transparent)] - Parse(#[from] pest::error::Error<crate::common::Rule>), + Parse(#[from] pest::error::Error<crate::common::PestRule>), #[error(transparent)] Io(#[from] std::io::Error), - #[error("no handler defined for this mime/extension")] - NotFound, - #[error("badly-formatted desktop entry")] - BadCmd, - #[error("could not locate config dir")] - NoConfigDir, - #[error("could not figure out the mime type")] - Ambiguous, - #[error("could not figure out the mime type from extension .{0}")] - UnknownExtension(String), - #[error("invalid mime {0}")] - BadMime(String), - #[error("either mime (via -m) or extension (via -e) must be provided")] - MissingMimeOrExt, + #[error(transparent)] + Notify(#[from] notify_rust::error::Error), #[error(transparent)] Xdg(#[from] xdg::BaseDirectoriesError), + #[error("no handler defined for this mime/extension")] + NotFound(String), + #[error("could not figure out the mime type .{0}")] + Ambiguous(String), + #[error(transparent)] + BadMimeType(#[from] mime::FromStrError), + #[error("Malformed desktop entry")] + BadEntry(std::path::PathBuf), } pub type Result<T, E = Error> = std::result::Result<T, E>; diff --git a/src/main.rs b/src/main.rs index 0ad8fdc..8969c20 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,39 +1,57 @@ use clap::Clap; use error::{Error, Result}; +use notify_rust::Notification; +use std::convert::TryFrom; +mod apps; mod common; mod error; -mod mime_types; -mod mimeapps; -pub use common::{DesktopEntry, Handler, Mime}; +use common::{DesktopEntry, FlexibleMime, Handler, MimeOrExtension}; #[derive(Clap)] +#[clap(global_setting = clap::AppSettings::DeriveDisplayOrder)] +#[clap(global_setting = clap::AppSettings::DisableHelpSubcommand)] +#[clap(version = clap::crate_version!())] enum Cmd { + /// List default apps and the associated handlers List, + + /// Open a path/URL with its default handler Open { - path: String, + #[clap(required = true)] + path: Vec<String>, }, + + /// Set the default handler for mime/extension + Set { + mime: MimeOrExtension, + handler: Handler, + }, + + /// Unset the default handler for mime/extension + Unset { mime: MimeOrExtension }, + + /// Launch the handler for specified extension/mime with optional arguments + Launch { + mime: MimeOrExtension, + args: Vec<String>, + }, + + /// Get handler for this mime/extension Get { #[clap(long)] json: bool, - mime: Mime, - }, - Launch { - mime: Mime, - args: Vec<String>, + mime: MimeOrExtension, }, + + /// Add a handler for given mime/extension + /// Note that the first handler is the default Add { - mime: Mime, + mime: MimeOrExtension, handler: Handler, }, - Set { - mime: Mime, - handler: Handler, - }, - Unset { - mime: Mime, - }, + #[clap(setting = clap::AppSettings::Hidden)] Autocomplete { #[clap(short)] @@ -44,41 +62,60 @@ enum Cmd { } fn main() -> Result<()> { - let mut apps = mimeapps::MimeApps::read()?; + let mut apps = apps::MimeApps::read()?; - match Cmd::parse() { - Cmd::Set { mime, handler } => { - apps.set_handler(mime, handler)?; - } - Cmd::Add { mime, handler } => { - apps.add_handler(mime, handler)?; - } - Cmd::Launch { mime, args } => { - apps.get_handler(&mime)?.launch(args)?; - } - Cmd::Get { mime, json } => { - apps.show_handler(&mime, json)?; - } - Cmd::Open { path } => { - apps.get_handler(&Mime::try_from_path(&path)?)?.open(path)?; - } - Cmd::List => { - apps.print()?; - } - Cmd::Unset { mime } => { - apps.remove_handler(&mime)?; - } - Cmd::Autocomplete { - desktop_files, - mimes, - } => { - if desktop_files { - apps.list_handlers()?; - } else if mimes { - mime_types::list()?; + let res = || -> Result<()> { + match Cmd::parse() { + Cmd::Set { mime, handler } => { + apps.set_handler(mime.0, handler)?; + } + Cmd::Add { mime, handler } => { + apps.add_handler(mime.0, handler)?; + } + Cmd::Launch { mime, args } => { + apps.get_handler(&mime.0)?.launch(args)?; + } + Cmd::Get { mime, json } => { + apps.show_handler(&mime.0, json)?; + } + Cmd::Open { path } => { + std::process::Command::new("notify-send") + .arg(&format!("{:?}", path)) + .spawn()?; + apps.get_handler( + &FlexibleMime::try_from(path.get(0).unwrap().as_str())?.0, + )? + .launch(path)?; + } + Cmd::List => { + apps.print()?; + } + Cmd::Unset { mime } => { + apps.remove_handler(&mime.0)?; + } + Cmd::Autocomplete { + desktop_files, + mimes, + } => { + if desktop_files { + apps.list_handlers()?; + } else if mimes { + common::db_autocomplete()?; + } } } - }; + Ok(()) + }(); + match (res, atty::is(atty::Stream::Stdout)) { + (Err(e), true) => eprintln!("{}", e), + (Err(e), false) => { + Notification::new() + .summary("handlr error") + .body(&e.to_string()) + .show()?; + } + _ => {} + }; Ok(()) }