From 4a50ce366639ca9dac7324d6a47d6a0e6c7fccdf Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Wed, 21 Apr 2021 18:13:51 +0100 Subject: [PATCH] Bugfixes, show time ago, perf improvements Also allow unique listing and more ergonomic cwd usage --- Cargo.lock | 382 ++++++++++++++++----------------- Cargo.toml | 3 +- Dockerfile | 6 + atuin-client/Cargo.toml | 2 +- atuin-client/src/api_client.rs | 120 ++++++++++- atuin-client/src/database.rs | 113 ++++++++-- atuin-client/src/encryption.rs | 39 +++- atuin-client/src/history.rs | 2 +- atuin-client/src/settings.rs | 5 +- atuin-client/src/sync.rs | 20 +- atuin-server/Cargo.toml | 3 +- atuin-server/src/settings.rs | 5 +- src/command/history.rs | 38 ++-- src/command/login.rs | 26 +-- src/command/mod.rs | 20 +- src/command/register.rs | 31 +-- src/command/search.rs | 91 ++++++-- src/command/stats.rs | 2 +- src/main.rs | 19 +- src/shell/atuin.zsh | 6 +- 20 files changed, 586 insertions(+), 347 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4786405..a8d6b94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,9 +51,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "async-trait" -version = "0.1.49" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589652ce7ccb335d1e7ecb3be145425702b290dbcb7029bbeaae263fc1d87b48" +checksum = "0b98e84bbb4cbcdd97da190ba0c58a1bb0de2c1fdf67d159e192ed766aeca722" dependencies = [ "proc-macro2", "quote", @@ -94,13 +94,12 @@ dependencies = [ "cli-table", "directories", "eyre", - "fern", "fork", - "humantime", + "humantime 2.1.0", "indicatif", "itertools", "log", - "reqwest", + "pretty_env_logger", "rusqlite", "serde 1.0.125", "serde_derive", @@ -125,7 +124,7 @@ dependencies = [ "directories", "eyre", "fern", - "humantime", + "humantime 2.1.0", "indicatif", "itertools", "log", @@ -180,7 +179,6 @@ dependencies = [ "log", "parse_duration", "rand 0.8.3", - "reqwest", "rmp-serde", "rust-crypto", "serde 1.0.125", @@ -270,9 +268,9 @@ dependencies = [ [[package]] name = "build_const" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" +checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" [[package]] name = "bumpalo" @@ -282,9 +280,9 @@ checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" [[package]] name = "byteorder" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" @@ -300,9 +298,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "cc" -version = "1.0.66" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" +checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" [[package]] name = "cfg-if" @@ -326,9 +324,9 @@ dependencies = [ [[package]] name = "chrono-english" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4233ee19352739cfdcb5d7c2085005b166f6170ef2845ed9eef27a8fa5f95206" +checksum = "0be5180df5f7c41fc2416bc038bc8d78d44db8136c415b94ccbc95f523dc38e9" dependencies = [ "chrono", "scanlex", @@ -402,9 +400,9 @@ dependencies = [ [[package]] name = "console" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc80946b3480f421c2f17ed1cb841753a371c7c5104f51d507e13f532c856aa" +checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45" dependencies = [ "encode_unicode", "lazy_static", @@ -421,22 +419,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" -[[package]] -name = "core-foundation" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" - [[package]] name = "cpuid-bool" version = "0.1.2" @@ -474,9 +456,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" +checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" dependencies = [ "autocfg", "cfg-if", @@ -601,6 +583,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime 1.3.0", + "log", + "regex", + "termcolor", +] + [[package]] name = "eyre" version = "0.6.5" @@ -639,21 +634,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "fork" version = "0.1.18" @@ -935,9 +915,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.3.5" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "615caabe2c3160b313d52ccc905335f4ed5f10881dd63dc5699d47e90be85691" +checksum = "4a1ce40d6fc9764887c2fdc7305c3dcc429ba11ff981c1509416afd5697e4437" [[package]] name = "httpdate" @@ -945,6 +925,15 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + [[package]] name = "humantime" version = "2.1.0" @@ -976,23 +965,25 @@ dependencies = [ ] [[package]] -name = "hyper-tls" -version = "0.5.0" +name = "hyper-rustls" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" dependencies = [ - "bytes", + "futures-util", "hyper", - "native-tls", + "log", + "rustls", "tokio", - "tokio-native-tls", + "tokio-rustls", + "webpki", ] [[package]] name = "idna" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89829a5d69c23d348314a7ac337fe39173b61149a9864deabd260983aed48c21" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" dependencies = [ "matches", "unicode-bidi", @@ -1001,15 +992,15 @@ dependencies = [ [[package]] name = "indenter" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4d5eb2e114fec2b7fe0fadc22888ad2658789bb7acac4dbee9cf8389f971ec8" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.6.1" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1fa934250de4de8aef298d81c729a7d33d8c239daa3a7575e6b92bfc7313b" +checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" dependencies = [ "autocfg", "hashbrown", @@ -1096,9 +1087,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.86" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" +checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" [[package]] name = "libsodium-sys" @@ -1130,9 +1121,9 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] name = "lock_api" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176" dependencies = [ "scopeguard", ] @@ -1231,24 +1222,6 @@ dependencies = [ "twoway", ] -[[package]] -name = "native-tls" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "nom" version = "5.1.2" @@ -1392,9 +1365,9 @@ checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" [[package]] name = "once_cell" -version = "1.5.2" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" +checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" [[package]] name = "opaque-debug" @@ -1402,39 +1375,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "openssl" -version = "0.10.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a61075b62a23fef5a29815de7536d940aa35ce96d18ce0cc5076272db678a577" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-sys", -] - -[[package]] -name = "openssl-probe" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" - -[[package]] -name = "openssl-sys" -version = "0.9.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "313752393519e876837e09e1fa183ddef0be7735868dced3196f4472d536277f" -dependencies = [ - "autocfg", - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "parking_lot" version = "0.11.1" @@ -1455,7 +1395,7 @@ dependencies = [ "cfg-if", "instant", "libc", - "redox_syscall 0.2.4", + "redox_syscall 0.2.6", "smallvec", "winapi", ] @@ -1479,18 +1419,18 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pin-project" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc174859768806e91ae575187ada95c91a29e96a98dc5d2cd9a1fed039501ba6" +checksum = "c7509cc106041c40a4518d2af7a61530e1eed0e6285296a3d8c5472806ccc4a4" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a490329918e856ed1b083f244e3bfe2d8c4f336407e4ea9e1a9f479ff09049e5" +checksum = "48c950132583b500556b1efd71d45b319029f2b71518d979fcc208e16b42426f" dependencies = [ "proc-macro2", "quote", @@ -1521,6 +1461,16 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[package]] +name = "pretty_env_logger" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" +dependencies = [ + "env_logger", + "log", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1723,9 +1673,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ec8ca9416c5ea37062b502703cd7fcb207736bc294f6e0cf367ac6fc234570" +checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041" dependencies = [ "bitflags", ] @@ -1736,7 +1686,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" dependencies = [ - "redox_syscall 0.2.4", + "redox_syscall 0.2.6", ] [[package]] @@ -1757,19 +1707,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ "getrandom 0.2.2", - "redox_syscall 0.2.4", + "redox_syscall 0.2.6", ] [[package]] name = "regex" -version = "1.4.3" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" +checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" dependencies = [ "aho-corasick", "memchr", "regex-syntax", - "thread_local", ] [[package]] @@ -1783,9 +1732,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.22" +version = "0.6.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" +checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" [[package]] name = "remove_dir_all" @@ -1810,27 +1759,43 @@ dependencies = [ "http", "http-body", "hyper", - "hyper-tls", + "hyper-rustls", "ipnet", "js-sys", "lazy_static", "log", "mime", - "native-tls", "percent-encoding", "pin-project-lite", + "rustls", "serde 1.0.125", "serde_json", "serde_urlencoded", "tokio", - "tokio-native-tls", + "tokio-rustls", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots", "winreg", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rmp" version = "0.8.10" @@ -1904,6 +1869,19 @@ version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64", + "log", + "ring", + "sct", + "webpki", +] + [[package]] name = "ryu" version = "1.0.5" @@ -1922,16 +1900,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "088c5d71572124929ea7549a8ce98e1a6fd33d0a38367b09027b382e67c033db" -[[package]] -name = "schannel" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" -dependencies = [ - "lazy_static", - "winapi", -] - [[package]] name = "scoped-tls" version = "1.0.0" @@ -1945,26 +1913,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] -name = "security-framework" -version = "2.2.0" +name = "sct" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3670b1d2fdf6084d192bc71ead7aabe6c06aa2ea3fbd9cc3ac111fa5c2b1bd84" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3676258fd3cfe2c9a0ec99ce3038798d847ce3e4bb17746373eb9f0f1ac16339" -dependencies = [ - "core-foundation-sys", - "libc", + "ring", + "untrusted", ] [[package]] @@ -2074,9 +2029,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" [[package]] name = "smallvec" @@ -2105,6 +2060,12 @@ dependencies = [ "serde 1.0.125", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "sqlformat" version = "0.1.6" @@ -2162,6 +2123,7 @@ dependencies = [ "parking_lot", "percent-encoding", "rand 0.8.3", + "rustls", "serde 1.0.125", "serde_json", "sha-1", @@ -2174,6 +2136,8 @@ dependencies = [ "tokio-stream", "url", "uuid", + "webpki", + "webpki-roots", "whoami", ] @@ -2202,10 +2166,9 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6ae97ab05063ed515cdc23d90253213aa24dda0a288c5ec079af3d10f9771bc" dependencies = [ - "native-tls", "once_cell", "tokio", - "tokio-native-tls", + "tokio-rustls", ] [[package]] @@ -2286,7 +2249,7 @@ dependencies = [ "cfg-if", "libc", "rand 0.8.3", - "redox_syscall 0.2.4", + "redox_syscall 0.2.6", "remove_dir_all", "winapi", ] @@ -2318,7 +2281,7 @@ checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" dependencies = [ "libc", "numtoa", - "redox_syscall 0.2.4", + "redox_syscall 0.2.6", "redox_termios", ] @@ -2351,15 +2314,6 @@ dependencies = [ "syn", ] -[[package]] -name = "thread_local" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" -dependencies = [ - "once_cell", -] - [[package]] name = "time" version = "0.1.43" @@ -2372,9 +2326,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023" +checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" dependencies = [ "tinyvec_macros", ] @@ -2387,9 +2341,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134af885d758d645f0f0505c9a8b3f9bf8a348fd822e112ab5248138348f1722" +checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5" dependencies = [ "autocfg", "bytes", @@ -2417,13 +2371,14 @@ dependencies = [ ] [[package]] -name = "tokio-native-tls" -version = "0.3.0" +name = "tokio-rustls" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" dependencies = [ - "native-tls", + "rustls", "tokio", + "webpki", ] [[package]] @@ -2452,9 +2407,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5143d049e85af7fbc36f5454d990e62c2df705b3589f123b71f441b6b59f443f" +checksum = "940a12c99365c31ea8dd9ba04ec1be183ffe4920102bb7122c2f515437601e8e" dependencies = [ "bytes", "futures-core", @@ -2549,9 +2504,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" +checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" [[package]] name = "unicase" @@ -2564,9 +2519,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" dependencies = [ "matches", ] @@ -2604,6 +2559,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" version = "2.2.1" @@ -2624,9 +2585,9 @@ checksum = "c9232eb53352b4442e40d7900465dfc534e8cb2dc8f18656fcb2ac16112b5593" [[package]] name = "utf-8" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "uuid" @@ -2639,9 +2600,9 @@ dependencies = [ [[package]] name = "vcpkg" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb" +checksum = "cbdbff6266a24120518560b5dc983096efb98462e51d0d68169895b237be3e5d" [[package]] name = "vec_map" @@ -2651,9 +2612,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" [[package]] name = "want" @@ -2784,6 +2745,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +dependencies = [ + "webpki", +] + [[package]] name = "whoami" version = "1.1.2" diff --git a/Cargo.toml b/Cargo.toml index 0ee82ca..8995a75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ atuin-client = { path = "atuin-client", version = "0.6.0" } atuin-common = { path = "atuin-common", version = "0.6.0" } log = "0.4" -fern = {version = "0.6.0", features = ["colored"] } +pretty_env_logger = "0.4" chrono = { version = "0.4", features = ["serde"] } eyre = "0.6" structopt = "0.3" @@ -33,7 +33,6 @@ tokio = { version = "1", features = ["full"] } async-trait = "0.1.49" chrono-english = "0.1.4" cli-table = "0.4" -reqwest = { version = "0.11", features = ["blocking", "json"] } base64 = "0.13.0" humantime = "2.1.0" diff --git a/Dockerfile b/Dockerfile index e712541..4f0d615 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,6 +17,12 @@ COPY --from=cacher $CARGO_HOME $CARGO_HOME RUN cargo build --release --bin atuin FROM debian:buster-slim as runtime + WORKDIR app + +ENV TZ=Etc/UTC +ENV RUST_LOG=info +ENV ATUIN_CONFIG_DIR=/config + COPY --from=builder /app/target/release/atuin /usr/local/bin ENTRYPOINT ["/usr/local/bin/atuin"] diff --git a/atuin-client/Cargo.toml b/atuin-client/Cargo.toml index 9d639d1..09cf9c4 100644 --- a/atuin-client/Cargo.toml +++ b/atuin-client/Cargo.toml @@ -26,7 +26,7 @@ serde = "1.0.125" serde_json = "1.0.64" rmp-serde = "0.15.4" sodiumoxide = "0.2.6" -reqwest = { version = "0.11", features = ["blocking", "json"] } +reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features = false } base64 = "0.13.0" parse_duration = "2.1.1" rand = "0.8.3" diff --git a/atuin-client/src/api_client.rs b/atuin-client/src/api_client.rs index db2802c..a8ce7b2 100644 --- a/atuin-client/src/api_client.rs +++ b/atuin-client/src/api_client.rs @@ -1,15 +1,24 @@ +use std::collections::HashMap; + use chrono::Utc; -use eyre::Result; -use reqwest::header::{HeaderMap, AUTHORIZATION}; -use reqwest::Url; +use eyre::{eyre, Result}; +use reqwest::header::{HeaderMap, AUTHORIZATION, USER_AGENT}; +use reqwest::{StatusCode, Url}; use sodiumoxide::crypto::secretbox; -use atuin_common::api::{AddHistoryRequest, CountResponse, SyncHistoryResponse}; +use atuin_common::api::{ + AddHistoryRequest, CountResponse, LoginResponse, RegisterResponse, SyncHistoryResponse, +}; use atuin_common::utils::hash_str; -use crate::encryption::decrypt; +use crate::encryption::{decode_key, decrypt}; use crate::history::History; +const VERSION: &str = env!("CARGO_PKG_VERSION"); + +// TODO: remove all references to the encryption key from this +// It should be handled *elsewhere* + pub struct Client<'a> { sync_addr: &'a str, token: &'a str, @@ -17,14 +26,70 @@ pub struct Client<'a> { client: reqwest::Client, } +pub fn register( + address: &str, + username: &str, + email: &str, + password: &str, +) -> Result { + let mut map = HashMap::new(); + map.insert("username", username); + map.insert("email", email); + map.insert("password", password); + + let url = format!("{}/user/{}", address, username); + let resp = reqwest::blocking::get(url)?; + + if resp.status().is_success() { + return Err(eyre!("username already in use")); + } + + let url = format!("{}/register", address); + let client = reqwest::blocking::Client::new(); + let resp = client + .post(url) + .header(USER_AGENT, format!("atuin/{}", VERSION)) + .json(&map) + .send()?; + + if !resp.status().is_success() { + return Err(eyre!("failed to register user")); + } + + let session = resp.json::()?; + Ok(session) +} + +pub fn login(address: &str, username: &str, password: &str) -> Result { + let mut map = HashMap::new(); + map.insert("username", username); + map.insert("password", password); + + let url = format!("{}/login", address); + let client = reqwest::blocking::Client::new(); + + let resp = client + .post(url) + .header(USER_AGENT, format!("atuin/{}", VERSION)) + .json(&map) + .send()?; + + if resp.status() != reqwest::StatusCode::OK { + return Err(eyre!("invalid login details")); + } + + let session = resp.json::()?; + Ok(session) +} + impl<'a> Client<'a> { - pub fn new(sync_addr: &'a str, token: &'a str, key: secretbox::Key) -> Self { - Client { + pub fn new(sync_addr: &'a str, token: &'a str, key: String) -> Result { + Ok(Client { sync_addr, token, - key, + key: decode_key(key)?, client: reqwest::Client::new(), - } + }) } pub async fn count(&self) -> Result { @@ -36,7 +101,17 @@ impl<'a> Client<'a> { let mut headers = HeaderMap::new(); headers.insert(AUTHORIZATION, token); - let resp = self.client.get(url).headers(headers).send().await?; + let resp = self + .client + .get(url) + .header(USER_AGENT, format!("atuin/{}", VERSION)) + .headers(headers) + .send() + .await?; + + if resp.status() != StatusCode::OK { + return Err(eyre!("failed to get count (are you logged in?)")); + } let count = resp.json::().await?; @@ -66,6 +141,7 @@ impl<'a> Client<'a> { .client .get(url) .header(AUTHORIZATION, format!("Token {}", self.token)) + .header(USER_AGENT, format!("atuin/{}", VERSION)) .send() .await?; @@ -88,9 +164,33 @@ impl<'a> Client<'a> { .post(url) .json(history) .header(AUTHORIZATION, format!("Token {}", self.token)) + .header(USER_AGENT, format!("atuin/{}", VERSION)) .send() .await?; Ok(()) } + + pub async fn login(&self, username: &str, password: &str) -> Result { + let mut map = HashMap::new(); + map.insert("username", username); + map.insert("password", password); + + let url = format!("{}/login", self.sync_addr); + let resp = self + .client + .post(url) + .json(&map) + .header(USER_AGENT, format!("atuin/{}", VERSION)) + .send() + .await?; + + if resp.status() != reqwest::StatusCode::OK { + return Err(eyre!("invalid login details")); + } + + let session = resp.json::().await?; + + Ok(session) + } } diff --git a/atuin-client/src/database.rs b/atuin-client/src/database.rs index abc22bb..0855359 100644 --- a/atuin-client/src/database.rs +++ b/atuin-client/src/database.rs @@ -2,7 +2,7 @@ use chrono::prelude::*; use chrono::Utc; use std::path::Path; -use eyre::Result; +use eyre::{eyre, Result}; use rusqlite::{params, Connection}; use rusqlite::{Params, Transaction}; @@ -14,7 +14,7 @@ pub trait Database { fn save_bulk(&mut self, h: &[History]) -> Result<()>; fn load(&self, id: &str) -> Result; - fn list(&self) -> Result>; + fn list(&self, max: Option, unique: bool) -> Result>; fn range(&self, from: chrono::DateTime, to: chrono::DateTime) -> Result>; @@ -27,6 +27,8 @@ pub trait Database { fn before(&self, timestamp: chrono::DateTime, count: i64) -> Result>; fn prefix_search(&self, query: &str) -> Result>; + + fn search(&self, cwd: Option, exit: Option, query: &str) -> Result>; } // Intended for use on a developer machine and not a sync server. @@ -81,6 +83,16 @@ impl Sqlite { [], )?; + conn.execute( + "create index if not exists idx_history_timestamp on history(timestamp)", + [], + )?; + + conn.execute( + "create index if not exists idx_history_command on history(command)", + [], + )?; + Ok(()) } @@ -136,16 +148,19 @@ impl Database for Sqlite { } fn load(&self, id: &str) -> Result { - debug!("loading history item"); + debug!("loading history item {}", id); - let mut stmt = self.conn.prepare( + let history = self.query( "select id, timestamp, duration, exit, command, cwd, session, hostname from history - where id = ?1", + where id = ?1 limit 1", + &[id], )?; - let history = stmt.query_row(params![id], |row| { - history_from_sqlite_row(Some(id.to_string()), row) - })?; + if history.is_empty() { + return Err(eyre!("could not find history with id {}", id)); + } + + let history = history[0].clone(); Ok(history) } @@ -163,16 +178,39 @@ impl Database for Sqlite { Ok(()) } - fn list(&self) -> Result> { + // make a unique list, that only shows the *newest* version of things + fn list(&self, max: Option, unique: bool) -> Result> { debug!("listing history"); - let mut stmt = self - .conn - .prepare("SELECT * FROM history order by timestamp asc")?; + // very likely vulnerable to SQL injection + // however, this is client side, and only used by the client, on their + // own data. They can just open the db file... + // otherwise building the query is awkward + let query = format!( + "select * from history h + {} + order by timestamp desc + {}", + // inject the unique check + if unique { + "where timestamp = ( + select max(timestamp) from history + where h.command = history.command + )" + } else { + "" + }, + // inject the limit + if let Some(max) = max { + format!("limit {}", max) + } else { + "".to_string() + } + ); - let history_iter = stmt.query_map(params![], |row| history_from_sqlite_row(None, row))?; + let history = self.query(query.as_str(), params![])?; - Ok(history_iter.filter_map(Result::ok).collect()) + Ok(history) } fn range( @@ -207,7 +245,7 @@ impl Database for Sqlite { fn last(&self) -> Result { let mut stmt = self .conn - .prepare("SELECT * FROM history order by timestamp desc limit 1")?; + .prepare("SELECT * FROM history where duration >= 0 order by timestamp desc limit 1")?; let history = stmt.query_row(params![], |row| history_from_sqlite_row(None, row))?; @@ -235,9 +273,17 @@ impl Database for Sqlite { } fn prefix_search(&self, query: &str) -> Result> { + let query = query.to_string().replace("*", "%"); // allow wildcard char + self.query( - "select * from history where command like ?1 || '%' order by timestamp asc limit 1000", - &[query], + "select * from history h + where command like ?1 || '%' + and timestamp = ( + select max(timestamp) from history + where h.command = history.command + ) + order by timestamp desc limit 200", + &[query.as_str()], ) } @@ -248,6 +294,39 @@ impl Database for Sqlite { Ok(res) } + + fn search(&self, cwd: Option, exit: Option, query: &str) -> Result> { + match (cwd, exit) { + (Some(cwd), Some(exit)) => self.query( + "select * from history + where command like ?1 || '%' + and cwd = ?2 + and exit = ?3 + order by timestamp asc limit 1000", + &[query, cwd.as_str(), exit.to_string().as_str()], + ), + (Some(cwd), None) => self.query( + "select * from history + where command like ?1 || '%' + and cwd = ?2 + order by timestamp asc limit 1000", + &[query, cwd.as_str()], + ), + (None, Some(exit)) => self.query( + "select * from history + where command like ?1 || '%' + and exit = ?2 + order by timestamp asc limit 1000", + &[query, exit.to_string().as_str()], + ), + (None, None) => self.query( + "select * from history + where command like ?1 || '%' + order by timestamp asc limit 1000", + &[query], + ), + } + } } fn history_from_sqlite_row( diff --git a/atuin-client/src/encryption.rs b/atuin-client/src/encryption.rs index 37153f9..19b773a 100644 --- a/atuin-client/src/encryption.rs +++ b/atuin-client/src/encryption.rs @@ -29,20 +29,51 @@ pub fn load_key(settings: &Settings) -> Result { let path = settings.key_path.as_str(); if PathBuf::from(path).exists() { - let bytes = std::fs::read(path)?; - let key: secretbox::Key = rmp_serde::from_read_ref(&bytes)?; + let key = std::fs::read_to_string(path)?; + let key = decode_key(key)?; Ok(key) } else { let key = secretbox::gen_key(); - let buf = rmp_serde::to_vec(&key)?; + let encoded = encode_key(key.clone())?; let mut file = File::create(path)?; - file.write_all(&buf)?; + file.write_all(encoded.as_bytes())?; Ok(key) } } +pub fn load_encoded_key(settings: &Settings) -> Result { + let path = settings.key_path.as_str(); + + if PathBuf::from(path).exists() { + let key = std::fs::read_to_string(path)?; + Ok(key) + } else { + let key = secretbox::gen_key(); + let encoded = encode_key(key)?; + + let mut file = File::create(path)?; + file.write_all(encoded.as_bytes())?; + + Ok(encoded) + } +} + +pub fn encode_key(key: secretbox::Key) -> Result { + let buf = rmp_serde::to_vec(&key)?; + let buf = base64::encode(buf); + + Ok(buf) +} + +pub fn decode_key(key: String) -> Result { + let buf = base64::decode(key)?; + let buf: secretbox::Key = rmp_serde::from_read_ref(&buf)?; + + Ok(buf) +} + pub fn encrypt(history: &History, key: &secretbox::Key) -> Result { // serialize with msgpack let buf = rmp_serde::to_vec(history)?; diff --git a/atuin-client/src/history.rs b/atuin-client/src/history.rs index 7f60778..8dd161d 100644 --- a/atuin-client/src/history.rs +++ b/atuin-client/src/history.rs @@ -6,7 +6,7 @@ use chrono::Utc; use atuin_common::utils::uuid_v4; // Any new fields MUST be Optional<>! -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Ord, PartialOrd)] pub struct History { pub id: String, pub timestamp: chrono::DateTime, diff --git a/atuin-client/src/settings.rs b/atuin-client/src/settings.rs index e28963c..254bca6 100644 --- a/atuin-client/src/settings.rs +++ b/atuin-client/src/settings.rs @@ -78,15 +78,16 @@ impl Settings { create_dir_all(config_dir)?; - let config_file = if let Ok(p) = std::env::var("ATUIN_CONFIG") { + let mut config_file = if let Ok(p) = std::env::var("ATUIN_CONFIG_DIR") { PathBuf::from(p) } else { let mut config_file = PathBuf::new(); config_file.push(config_dir); - config_file.push("config.toml"); config_file }; + config_file.push("config.toml"); + let mut s = Config::new(); let db_path = ProjectDirs::from("com", "elliehuxtable", "atuin") diff --git a/atuin-client/src/sync.rs b/atuin-client/src/sync.rs index 0ca8d3a..5d81a5e 100644 --- a/atuin-client/src/sync.rs +++ b/atuin-client/src/sync.rs @@ -7,7 +7,7 @@ use atuin_common::{api::AddHistoryRequest, utils::hash_str}; use crate::api_client; use crate::database::Database; -use crate::encryption::{encrypt, load_key}; +use crate::encryption::{encrypt, load_encoded_key, load_key}; use crate::settings::{Settings, HISTORY_PAGE_SIZE}; // Currently sync is kinda naive, and basically just pages backwards through @@ -26,6 +26,8 @@ async fn sync_download( client: &api_client::Client<'_>, db: &mut (impl Database + Send), ) -> Result<(i64, i64)> { + debug!("starting sync download"); + let remote_count = client.count().await?; let initial_local = db.history_count()?; @@ -46,14 +48,14 @@ async fn sync_download( .get_history(last_sync, last_timestamp, host.clone()) .await?; - if page.len() < HISTORY_PAGE_SIZE.try_into().unwrap() { - break; - } - db.save_bulk(&page)?; local_count = db.history_count()?; + if page.len() < HISTORY_PAGE_SIZE.try_into().unwrap() { + break; + } + let page_last = page .last() .expect("could not get last element of page") @@ -80,11 +82,15 @@ async fn sync_upload( client: &api_client::Client<'_>, db: &mut (impl Database + Send), ) -> Result<()> { + debug!("starting sync upload"); + let initial_remote_count = client.count().await?; let mut remote_count = initial_remote_count; let local_count = db.history_count()?; + debug!("remote has {}, we have {}", remote_count, local_count); + let key = load_key(settings)?; // encryption key // first just try the most recent set @@ -127,8 +133,8 @@ pub async fn sync(settings: &Settings, force: bool, db: &mut (impl Database + Se let client = api_client::Client::new( settings.sync_address.as_str(), settings.session_token.as_str(), - load_key(settings)?, - ); + load_encoded_key(settings)?, + )?; sync_upload(settings, force, &client, db).await?; diff --git a/atuin-server/Cargo.toml b/atuin-server/Cargo.toml index 548a97c..d4db38c 100644 --- a/atuin-server/Cargo.toml +++ b/atuin-server/Cargo.toml @@ -25,7 +25,6 @@ serde_json = "1.0.64" rmp-serde = "0.15.4" unicode-width = "0.1" sodiumoxide = "0.2.6" -reqwest = { version = "0.11", features = ["blocking", "json"] } base64 = "0.13.0" fork = "0.1.18" parse_duration = "2.1.1" @@ -33,6 +32,6 @@ rand = "0.8.3" rust-crypto = "^0.2" tokio = { version = "1", features = ["full"] } warp = "0.3" -sqlx = { version = "0.5", features = [ "runtime-tokio-native-tls", "uuid", "chrono", "postgres" ] } +sqlx = { version = "0.5", features = [ "runtime-tokio-rustls", "uuid", "chrono", "postgres" ] } async-trait = "0.1.49" urlencoding = "1.1.1" diff --git a/atuin-server/src/settings.rs b/atuin-server/src/settings.rs index 596b901..e51b6b2 100644 --- a/atuin-server/src/settings.rs +++ b/atuin-server/src/settings.rs @@ -23,15 +23,16 @@ impl Settings { create_dir_all(config_dir)?; - let config_file = if let Ok(p) = std::env::var("ATUIN_CONFIG") { + let mut config_file = if let Ok(p) = std::env::var("ATUIN_CONFIG_DIR") { PathBuf::from(p) } else { let mut config_file = PathBuf::new(); config_file.push(config_dir); - config_file.push("server.toml"); config_file }; + config_file.push("server.toml"); + // create the config file if it does not exist let mut s = Config::new(); diff --git a/src/command/history.rs b/src/command/history.rs index 2b691ba..a88aeae 100644 --- a/src/command/history.rs +++ b/src/command/history.rs @@ -1,7 +1,6 @@ use std::env; use eyre::Result; -use fork::{fork, Fork}; use structopt::StructOpt; use atuin_client::database::Database; @@ -44,6 +43,12 @@ pub enum Cmd { aliases=&["se", "sea", "sear", "searc"], )] Search { query: Vec }, + + #[structopt( + about="get the last command ran", + aliases=&["la", "las"], + )] + Last {}, } fn print_list(h: &[History]) { @@ -74,22 +79,24 @@ impl Cmd { } let mut h = db.load(id)?; + + if h.duration > 0 { + debug!("cannot end history - already has duration"); + + // returning OK as this can occur if someone Ctrl-c a prompt + return Ok(()); + } + h.exit = *exit; h.duration = chrono::Utc::now().timestamp_nanos() - h.timestamp.timestamp_nanos(); db.update(&h)?; if settings.should_sync()? { - match fork() { - Ok(Fork::Parent(child)) => { - debug!("launched sync background process with PID {}", child); - } - Ok(Fork::Child) => { - debug!("running periodic background sync"); - sync::sync(settings, false, db).await?; - } - Err(_) => println!("Fork failed"), - } + debug!("running periodic background sync"); + sync::sync(settings, false, db).await?; + } else { + debug!("sync disabled! not syncing"); } Ok(()) @@ -107,7 +114,7 @@ impl Cmd { let session = env::var("ATUIN_SESSION")?; let history = match params { - (false, false) => db.list()?, + (false, false) => db.list(None, false)?, (true, false) => db.query(QUERY_SESSION, &[session.as_str()])?, (false, true) => db.query(QUERY_DIR, &[cwd.as_str()])?, (true, true) => { @@ -126,6 +133,13 @@ impl Cmd { Ok(()) } + + Self::Last {} => { + let last = db.last()?; + print_list(&[last]); + + Ok(()) + } } } } diff --git a/src/command/login.rs b/src/command/login.rs index eaeb297..eacb210 100644 --- a/src/command/login.rs +++ b/src/command/login.rs @@ -1,10 +1,10 @@ -use std::collections::HashMap; use std::fs::File; use std::io::prelude::*; -use eyre::{eyre, Result}; +use eyre::Result; use structopt::StructOpt; +use atuin_client::api_client; use atuin_client::settings::Settings; #[derive(StructOpt)] @@ -22,25 +22,15 @@ pub struct Cmd { impl Cmd { pub fn run(&self, settings: &Settings) -> Result<()> { - let mut map = HashMap::new(); - map.insert("username", self.username.clone()); - map.insert("password", self.password.clone()); - - let url = format!("{}/login", settings.sync_address); - let client = reqwest::blocking::Client::new(); - - let resp = client.post(url).json(&map).send()?; - - if resp.status() != reqwest::StatusCode::OK { - return Err(eyre!("invalid login details")); - } - - let session = resp.json::>()?; - let session = session["session"].clone(); + let session = api_client::login( + settings.sync_address.as_str(), + self.username.as_str(), + self.password.as_str(), + )?; let session_path = settings.session_path.as_str(); let mut file = File::create(session_path)?; - file.write_all(session.as_bytes())?; + file.write_all(session.session.as_bytes())?; let key_path = settings.key_path.as_str(); let mut file = File::create(key_path)?; diff --git a/src/command/mod.rs b/src/command/mod.rs index 6fd5261..805ad9f 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -43,7 +43,18 @@ pub enum AtuinCmd { Uuid, #[structopt(about = "interactive history search")] - Search { query: Vec }, + Search { + #[structopt(long, short, about = "filter search result by directory")] + cwd: Option, + + #[structopt(long, short, about = "filter search result by exit code")] + exit: Option, + + #[structopt(long, short, about = "open interactive search UI")] + interactive: bool, + + query: Vec, + }, #[structopt(about = "sync with the configured server")] Sync { @@ -76,7 +87,12 @@ impl AtuinCmd { Self::Server(server) => server.run(&server_settings).await, Self::Stats(stats) => stats.run(&mut db, &client_settings), Self::Init => init::init(), - Self::Search { query } => search::run(&query, &mut db), + Self::Search { + cwd, + exit, + interactive, + query, + } => search::run(cwd, exit, interactive, &query, &mut db), Self::Sync { force } => sync::run(&client_settings, force, &mut db).await, Self::Login(l) => l.run(&client_settings), diff --git a/src/command/register.rs b/src/command/register.rs index 1126645..acf9b1a 100644 --- a/src/command/register.rs +++ b/src/command/register.rs @@ -1,10 +1,10 @@ -use std::collections::HashMap; use std::fs::File; use std::io::prelude::*; -use eyre::{eyre, Result}; +use eyre::Result; use structopt::StructOpt; +use atuin_client::api_client; use atuin_client::settings::Settings; #[derive(StructOpt)] @@ -21,34 +21,11 @@ pub struct Cmd { } pub fn run(settings: &Settings, username: &str, email: &str, password: &str) -> Result<()> { - let mut map = HashMap::new(); - map.insert("username", username); - map.insert("email", email); - map.insert("password", password); - - let url = format!("{}/user/{}", settings.sync_address, username); - let resp = reqwest::blocking::get(url)?; - - if resp.status().is_success() { - println!("Username is already in use! Please try another."); - return Ok(()); - } - - let url = format!("{}/register", settings.sync_address); - let client = reqwest::blocking::Client::new(); - let resp = client.post(url).json(&map).send()?; - - if !resp.status().is_success() { - println!("Failed to register user - please check your details and try again"); - return Err(eyre!("failed to register user")); - } - - let session = resp.json::>()?; - let session = session["session"].clone(); + let session = api_client::register(settings.sync_address.as_str(), username, email, password)?; let path = settings.session_path.as_str(); let mut file = File::create(path)?; - file.write_all(session.as_bytes())?; + file.write_all(session.session.as_bytes())?; Ok(()) } diff --git a/src/command/search.rs b/src/command/search.rs index 773c112..b074371 100644 --- a/src/command/search.rs +++ b/src/command/search.rs @@ -1,7 +1,6 @@ use eyre::Result; -use itertools::Itertools; -use std::io::stdout; use std::time::Duration; +use std::{io::stdout, ops::Sub}; use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen}; use tui::{ @@ -31,7 +30,7 @@ struct State { #[allow(clippy::clippy::cast_sign_loss)] impl State { - fn durations(&self) -> Vec { + fn durations(&self) -> Vec<(String, String)> { self.results .iter() .map(|h| { @@ -40,7 +39,33 @@ impl State { let duration = humantime::format_duration(duration).to_string(); let duration: Vec<&str> = duration.split(' ').collect(); - duration[0].to_string() + let ago = chrono::Utc::now().sub(h.timestamp); + let ago = humantime::format_duration(ago.to_std().unwrap()).to_string(); + let ago: Vec<&str> = ago.split(' ').collect(); + + ( + duration[0] + .to_string() + .replace("days", "d") + .replace("day", "d") + .replace("weeks", "w") + .replace("week", "w") + .replace("months", "mo") + .replace("month", "mo") + .replace("years", "y") + .replace("year", "y"), + ago[0] + .to_string() + .replace("days", "d") + .replace("day", "d") + .replace("weeks", "w") + .replace("week", "w") + .replace("months", "mo") + .replace("month", "mo") + .replace("years", "y") + .replace("year", "y") + + " ago", + ) }) .collect() } @@ -51,9 +76,9 @@ impl State { r: tui::layout::Rect, ) { let durations = self.durations(); - let max_length = durations - .iter() - .fold(0, |largest, i| std::cmp::max(largest, i.len())); + let max_length = durations.iter().fold(0, |largest, i| { + std::cmp::max(largest, i.0.len() + i.1.len()) + }); let results: Vec = self .results @@ -64,10 +89,10 @@ impl State { let mut command = Span::raw(command); - let mut duration = durations[i].clone(); + let (duration, mut ago) = durations[i].clone(); - while duration.len() < max_length { - duration.push(' '); + while (duration.len() + ago.len()) < max_length { + ago = " ".to_owned() + ago.as_str(); } let duration = Span::styled( @@ -79,6 +104,8 @@ impl State { }), ); + let ago = Span::styled(ago, Style::default().fg(Color::Blue)); + if let Some(selected) = self.results_state.selected() { if selected == i { command.style = @@ -86,7 +113,8 @@ impl State { } } - let spans = Spans::from(vec![duration, Span::raw(" "), command]); + let spans = + Spans::from(vec![duration, Span::raw(" "), ago, Span::raw(" "), command]); ListItem::new(spans) }) @@ -103,12 +131,12 @@ impl State { fn query_results(app: &mut State, db: &mut impl Database) { let results = match app.input.as_str() { - "" => db.list(), + "" => db.list(Some(200), true), i => db.prefix_search(i), }; if let Ok(results) = results { - app.results = results.into_iter().rev().unique().collect(); + app.results = results; } if app.results.is_empty() { @@ -120,7 +148,8 @@ fn query_results(app: &mut State, db: &mut impl Database) { fn key_handler(input: Key, db: &mut impl Database, app: &mut State) -> Option { match input { - Key::Esc | Key::Char('\n') => { + Key::Esc => return Some(String::from("")), + Key::Char('\n') => { let i = app.results_state.selected().unwrap_or(0); return Some( @@ -268,9 +297,37 @@ fn select_history(query: &[String], db: &mut impl Database) -> Result { } } -pub fn run(query: &[String], db: &mut impl Database) -> Result<()> { - let item = select_history(query, db)?; - eprintln!("{}", item); +pub fn run( + cwd: Option, + exit: Option, + interactive: bool, + query: &[String], + db: &mut impl Database, +) -> Result<()> { + let dir = if let Some(cwd) = cwd { + if cwd == "." { + let current = std::env::current_dir()?; + let current = current.as_os_str(); + let current = current.to_str().unwrap(); + + Some(current.to_owned()) + } else { + Some(cwd) + } + } else { + None + }; + + if interactive { + let item = select_history(query, db)?; + eprintln!("{}", item); + } else { + let results = db.search(dir, exit, query.join(" ").as_str())?; + + for i in &results { + println!("{}", i.command); + } + } Ok(()) } diff --git a/src/command/stats.rs b/src/command/stats.rs index 0da303d..5c9a9db 100644 --- a/src/command/stats.rs +++ b/src/command/stats.rs @@ -94,7 +94,7 @@ impl Cmd { } Self::All => { - let history = db.list()?; + let history = db.list(None, false)?; compute_stats(&history)?; diff --git a/src/main.rs b/src/main.rs index c116d1f..184c032 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,6 @@ #![allow(clippy::use_self)] // not 100% reliable use eyre::Result; -use fern::colors::{Color, ColoredLevelConfig}; use structopt::{clap::AppSettings, StructOpt}; #[macro_use] @@ -32,23 +31,7 @@ impl Atuin { #[tokio::main] async fn main() -> Result<()> { - let colors = ColoredLevelConfig::new() - .warn(Color::Yellow) - .error(Color::Red); - - fern::Dispatch::new() - .format(move |out, message, record| { - out.finish(format_args!( - "{} [{}] {}", - chrono::Local::now().to_rfc3339(), - colors.color(record.level()), - message - )) - }) - .level(log::LevelFilter::Info) - .level_for("sqlx", log::LevelFilter::Warn) - .chain(std::io::stdout()) - .apply()?; + pretty_env_logger::init(); Atuin::from_args().run().await } diff --git a/src/shell/atuin.zsh b/src/shell/atuin.zsh index d6d58f5..cdef5e5 100644 --- a/src/shell/atuin.zsh +++ b/src/shell/atuin.zsh @@ -15,8 +15,8 @@ _atuin_precmd(){ [[ -z "${ATUIN_HISTORY_ID}" ]] && return - atuin history end $ATUIN_HISTORY_ID --exit $EXIT - export ATUIN_HISTORY_ID="" + + (RUST_LOG=error atuin history end $ATUIN_HISTORY_ID --exit $EXIT &) > /dev/null 2>&1 } _atuin_search(){ @@ -27,7 +27,7 @@ _atuin_search(){ echoti rmkx # swap stderr and stdout, so that the tui stuff works # TODO: not this - output=$(atuin search $BUFFER 3>&1 1>&2 2>&3) + output=$(RUST_LOG=error atuin search -i $BUFFER 3>&1 1>&2 2>&3) echoti smkx if [[ -n $output ]] ; then